Merge branch 'main' of https://github.com/github/codeql into post-release-prep/codeql-cli-2.25.1

This commit is contained in:
Óscar San José
2026-03-30 10:51:12 +02:00
765 changed files with 13826 additions and 27987 deletions

View File

@@ -0,0 +1,5 @@
---
category: fix
---
- Fixed the resolution of relative imports such as `from . import helper` inside namespace packages (directories without an `__init__.py` file), which previously did not work correctly, leading to missing flow.

View File

@@ -17,6 +17,10 @@ private predicate valid_module_name(string name) {
exists(Module m | m.getName() = name)
or
exists(Builtin cmod | cmod.getClass() = Builtin::special("ModuleType") and cmod.getName() = name)
or
// Namespace packages may not have a corresponding Module entity,
// but their names are still valid for the purpose of import resolution.
name = moduleNameFromFile(any(Folder f))
}
/** An artificial expression representing an import */

View File

@@ -32,7 +32,9 @@ module Builtins {
"UnicodeDecodeError", "UnicodeEncodeError", "UnicodeError", "UnicodeTranslateError",
"UnicodeWarning", "UserWarning", "ValueError", "Warning", "ZeroDivisionError",
// Added for compatibility
"exec"
"exec",
// Added by the `site` module (available by default unless `-S` is used)
"copyright", "credits", "exit", "quit"
]
or
// Built-in constants shared between Python 2 and 3
@@ -51,8 +53,8 @@ module Builtins {
or
// Python 2 only
result in [
"basestring", "cmp", "execfile", "file", "long", "raw_input", "reduce", "reload", "unichr",
"unicode", "xrange"
"apply", "basestring", "cmp", "execfile", "file", "long", "raw_input", "reduce", "reload",
"unichr", "unicode", "xrange"
]
}

View File

@@ -1977,3 +1977,185 @@ private module OutNodes {
* `kind`.
*/
OutNode getAnOutNode(DataFlowCall call, ReturnKind kind) { call = result.getCall(kind) }
/**
* Provides predicates for approximating type properties of user-defined classes
* based on their structure (method declarations, base classes).
*
* This module should _not_ be used in the call graph computation itself, as parts of it may depend
* on layers that themselves build upon the call graph (e.g. API graphs).
*/
module DuckTyping {
private import semmle.python.ApiGraphs
/**
* Holds if `cls` or any of its resolved superclasses declares a method with the given `name`.
*/
predicate hasMethod(Class cls, string name) {
cls.getAMethod().getName() = name
or
hasMethod(getADirectSuperclass(cls), name)
}
/**
* Holds if `cls` has a base class that cannot be resolved to a user-defined class
* and is not just `object`, meaning it may inherit methods from an unknown class.
*/
predicate hasUnresolvedBase(Class cls) {
exists(Expr base | base = cls.getABase() |
not base = classTracker(_).asExpr() and
not base = API::builtin("object").getAValueReachableFromSource().asExpr()
)
}
/**
* Holds if `cls` supports the container protocol, i.e. it declares
* `__contains__`, `__iter__`, or `__getitem__`.
*/
predicate isContainer(Class cls) {
hasMethod(cls, "__contains__") or
hasMethod(cls, "__iter__") or
hasMethod(cls, "__getitem__")
}
/**
* Holds if `cls` supports the iterable protocol, i.e. it declares
* `__iter__` or `__getitem__`.
*/
predicate isIterable(Class cls) {
hasMethod(cls, "__iter__") or
hasMethod(cls, "__getitem__")
}
/**
* Holds if `cls` supports the iterator protocol, i.e. it declares
* both `__iter__` and `__next__`.
*/
predicate isIterator(Class cls) {
hasMethod(cls, "__iter__") and
hasMethod(cls, "__next__")
}
/**
* Holds if `cls` supports the context manager protocol, i.e. it declares
* both `__enter__` and `__exit__`.
*/
predicate isContextManager(Class cls) {
hasMethod(cls, "__enter__") and
hasMethod(cls, "__exit__")
}
/**
* Holds if `cls` supports the descriptor protocol, i.e. it declares
* `__get__`, `__set__`, or `__delete__`.
*/
predicate isDescriptor(Class cls) {
hasMethod(cls, "__get__") or
hasMethod(cls, "__set__") or
hasMethod(cls, "__delete__")
}
/**
* Holds if `cls` directly assigns to an attribute named `name` in its class body.
* This covers attribute assignments like `x = value`, but not method definitions.
*/
predicate declaresAttribute(Class cls, string name) { exists(getAnAttributeValue(cls, name)) }
/**
* Gets the value expression assigned to attribute `name` directly in the class body of `cls`.
*/
Expr getAnAttributeValue(Class cls, string name) {
exists(Assign a |
a.getScope() = cls and
a.getATarget().(Name).getId() = name and
result = a.getValue()
)
}
/**
* Holds if `cls` is callable, i.e. it declares `__call__`.
*/
predicate isCallable(Class cls) { hasMethod(cls, "__call__") }
/**
* Holds if `cls` supports the mapping protocol, i.e. it declares
* `__getitem__` and `keys`, or `__getitem__` and `__iter__`.
*/
predicate isMapping(Class cls) {
hasMethod(cls, "__getitem__") and
(hasMethod(cls, "keys") or hasMethod(cls, "__iter__"))
}
/**
* Holds if `cls` is a new-style class. In Python 3, all classes are new-style.
* In Python 2, a class is new-style if it (transitively) inherits from `object`,
* or has a declared `__metaclass__`, or is in a module with a module-level
* `__metaclass__` declaration, or has an unresolved base class.
*/
predicate isNewStyle(Class cls) {
major_version() = 3
or
major_version() = 2 and
(
cls.getABase() = API::builtin("object").getAValueReachableFromSource().asExpr()
or
isNewStyle(getADirectSuperclass(cls))
or
hasUnresolvedBase(cls)
or
exists(cls.getMetaClass())
or
// Module-level __metaclass__ = type makes all classes in the module new-style
exists(Assign a |
a.getScope() = cls.getEnclosingModule() and
a.getATarget().(Name).getId() = "__metaclass__" and
a.getValue() = API::builtin("type").getAValueReachableFromSource().asExpr()
)
)
}
/**
* Gets the `__init__` function that will be invoked when `cls` is constructed,
* resolved according to the MRO.
*/
Function getInit(Class cls) { result = invokedFunctionFromClassConstruction(cls, "__init__") }
/**
* Holds if `cls` or any of its superclasses uses multiple inheritance, or
* has an unresolved base class. In these cases, our MRO approximation may
* resolve to the wrong `__init__`, so we should not flag argument mismatches.
*/
predicate hasUnreliableMro(Class cls) {
exists(Class sup | sup = getADirectSuperclass*(cls) |
exists(sup.getBase(1))
or
hasUnresolvedBase(sup)
)
}
/**
* Holds if `f` overrides a method in a superclass with the same name.
*/
predicate overridesMethod(Function f) { overridesMethod(f, _, _) }
/**
* Holds if `f` overrides `overridden` declared in `superclass`.
*/
predicate overridesMethod(Function f, Class superclass, Function overridden) {
exists(Class cls |
f.getScope() = cls and
superclass = getADirectSuperclass+(cls) and
overridden = superclass.getMethod(f.getName())
)
}
/**
* Holds if `f` is a property accessor (decorated with `@property`, `@name.setter`,
* or `@name.deleter`).
*/
predicate isPropertyAccessor(Function f) {
exists(Attribute a | a = f.getADecorator() | a.getName() = "setter" or a.getName() = "deleter")
or
f.getADecorator().(Name).getId() = "property"
}
}

View File

@@ -11,10 +11,14 @@
*/
import python
private import LegacyPointsTo
private import semmle.python.dataflow.new.internal.DataFlowDispatch
from PropertyObject prop, ClassObject cls
where cls.declaredAttribute(_) = prop and not cls.failedInference() and not cls.isNewStyle()
select prop,
from Function prop, Class cls, Name decorator
where
prop.getScope() = cls and
decorator = prop.getADecorator() and
decorator.getId() = "property" and
not DuckTyping::isNewStyle(cls)
select decorator,
"Property " + prop.getName() + " will not work properly, as class " + cls.getName() +
" is an old-style class."

View File

@@ -14,10 +14,12 @@
*/
import python
private import LegacyPointsTo
private import semmle.python.dataflow.new.internal.DataFlowDispatch
from ClassValue c
where not c.isBuiltin() and not c.isContextManager() and exists(c.declaredAttribute("__del__"))
from Class c
where
not DuckTyping::isContextManager(c) and
exists(c.getMethod("__del__"))
select c,
"Class " + c.getName() +
" implements __del__ (presumably to release some resource). Consider making it a context manager."

View File

@@ -12,9 +12,11 @@
*/
import python
private import LegacyPointsTo
private import semmle.python.dataflow.new.internal.DataFlowDispatch
from ClassObject c
where not c.isNewStyle() and c.declaresAttribute("__slots__") and not c.failedInference()
from Class c
where
not DuckTyping::isNewStyle(c) and
DuckTyping::declaresAttribute(c, "__slots__")
select c,
"Using '__slots__' in an old style class just creates a class attribute called '__slots__'."

View File

@@ -11,14 +11,13 @@
*/
import python
private import LegacyPointsTo
private import semmle.python.dataflow.new.internal.DataFlowDispatch
predicate uses_of_super_in_old_style_class(Call s) {
exists(Function f, ClassObject c |
exists(Function f, Class c |
s.getScope() = f and
f.getScope() = c.getPyClass() and
not c.failedInference() and
not c.isNewStyle() and
f.getScope() = c and
not DuckTyping::isNewStyle(c) and
s.getFunc().(Name).getId() = "super"
)
}

View File

@@ -13,7 +13,7 @@
*/
import python
private import LegacyPointsTo
private import semmle.python.dataflow.new.internal.DataFlowDispatch
predicate fewer_than_two_public_methods(Class cls, int methods) {
(methods = 0 or methods = 1) and
@@ -25,13 +25,8 @@ predicate does_not_define_special_method(Class cls) {
}
predicate no_inheritance(Class c) {
not exists(ClassValue cls, ClassValue other |
cls.getScope() = c and
other != ClassValue::object()
|
other.getABaseType() = cls or
cls.getABaseType() = other
) and
not exists(getADirectSubclass(c)) and
not exists(getADirectSuperclass(c)) and
not exists(Expr base | base = c.getABase() |
not base instanceof Name or base.(Name).getId() != "object"
)

View File

@@ -10,9 +10,10 @@
*/
import python
private import LegacyPointsTo
private import semmle.python.types.Builtins
private import semmle.python.ApiGraphs
from CallNode call, ControlFlowNodeWithPointsTo func
where major_version() = 2 and call.getFunction() = func and func.pointsTo(Value::named("apply"))
from CallNode call
where
major_version() = 2 and
call = API::builtin("apply").getACall().asCfgNode()
select call, "Call to the obsolete builtin function 'apply'."

View File

@@ -10,16 +10,17 @@
*/
import python
private import LegacyPointsTo
private import semmle.python.dataflow.new.internal.DataFlowDispatch
predicate slice_method_name(string name) {
name = "__getslice__" or name = "__setslice__" or name = "__delslice__"
}
from PythonFunctionValue f, string meth
from Function f, string meth
where
f.getScope().isMethod() and
not f.isOverridingMethod() and
f.isMethod() and
slice_method_name(meth) and
f.getName() = meth
f.getName() = meth and
not DuckTyping::overridesMethod(f) and
not DuckTyping::hasUnresolvedBase(getADirectSuperclass*(f.getScope()))
select f, meth + " method has been deprecated since Python 2.0."

View File

@@ -11,7 +11,7 @@
*/
import python
private import LegacyPointsTo
private import semmle.python.ApiGraphs
/**
* Holds if the module `name` was deprecated in Python version `major`.`minor`,
@@ -80,7 +80,7 @@ where
name = imp.getName() and
deprecated_module(name, instead, _, _) and
not exists(Try try, ExceptStmt except | except = try.getAHandler() |
except.getType().(ExprWithPointsTo).pointsTo(ClassValue::importError()) and
except.getType() = API::builtin("ImportError").getAValueReachableFromSource().asExpr() and
except.containsInScope(imp)
)
select imp, deprecation_message(name) + replacement_message(name)

View File

@@ -4,7 +4,7 @@
* cause a cross-site scripting vulnerability.
* @kind problem
* @problem.severity error
* @security-severity 6.1
* @security-severity 7.8
* @precision medium
* @id py/jinja2/autoescape-false
* @tags security

View File

@@ -4,7 +4,7 @@
* allows for a cross-site scripting vulnerability.
* @kind path-problem
* @problem.severity error
* @security-severity 6.1
* @security-severity 7.8
* @sub-severity high
* @precision high
* @id py/reflective-xss

View File

@@ -4,7 +4,7 @@
* insertion of forged log entries by a malicious user.
* @kind path-problem
* @problem.severity error
* @security-severity 7.8
* @security-severity 6.1
* @precision medium
* @id py/log-injection
* @tags security

View File

@@ -17,7 +17,7 @@
*/
import python
private import LegacyPointsTo
private import semmle.python.dataflow.new.internal.DataFlowDispatch
predicate needs_docstring(Scope s) {
s.isPublic() and
@@ -29,15 +29,13 @@ predicate needs_docstring(Scope s) {
}
predicate function_needs_docstring(FunctionMetrics f) {
not exists(FunctionValue fo, FunctionValue base | fo.overrides(base) and fo.getScope() = f |
not function_needs_docstring(base.getScope())
not exists(Function base |
DuckTyping::overridesMethod(f, _, base) and
not function_needs_docstring(base)
) and
f.getName() != "lambda" and
(f.getNumberOfLinesOfCode() - count(f.getADecorator())) > 2 and
not exists(PythonPropertyObject p |
p.getGetter().getFunction() = f or
p.getSetter().getFunction() = f
)
not DuckTyping::isPropertyAccessor(f)
}
string scope_type(Scope s) {

View File

@@ -13,7 +13,7 @@
*/
import python
private import LegacyPointsTo
private import semmle.python.ApiGraphs
predicate func_with_side_effects(Expr e) {
exists(string name | name = e.(Attribute).getName() or name = e.(Name).getId() |
@@ -24,11 +24,11 @@ predicate func_with_side_effects(Expr e) {
}
predicate call_with_side_effect(Call e) {
e.getAFlowNode() = Value::named("subprocess.call").getACall()
or
e.getAFlowNode() = Value::named("subprocess.check_call").getACall()
or
e.getAFlowNode() = Value::named("subprocess.check_output").getACall()
e.getAFlowNode() =
API::moduleImport("subprocess")
.getMember(["call", "check_call", "check_output"])
.getACall()
.asCfgNode()
}
predicate probable_side_effect(Expr e) {

View File

@@ -12,7 +12,6 @@
*/
import python
private import LegacyPointsTo
predicate main_eq_name(If i) {
exists(Name n, StringLiteral m, Compare c |
@@ -32,10 +31,19 @@ predicate is_print_stmt(Stmt s) {
)
}
/**
* Holds if module `m` is likely used as a module (imported by another module),
* as opposed to being exclusively used as a script.
*/
predicate is_used_as_module(Module m) {
m.isPackageInit()
or
exists(ImportingStmt i | i.getAnImportedModuleName() = m.getName())
}
from Stmt p
where
is_print_stmt(p) and
// TODO: Need to discuss how we would like to handle ModuleObject.getKind in the glorious future
exists(ModuleValue m | m.getScope() = p.getScope() and m.isUsedAsModule()) and
is_used_as_module(p.getScope()) and
not exists(If i | main_eq_name(i) and i.getASubStatement().getASubStatement*() = p)
select p, "Print statement may execute during import."

View File

@@ -13,7 +13,7 @@
*/
import python
private import LegacyPointsTo
private import semmle.python.ApiGraphs
predicate isInsideLoop(AstNode node) {
node.getParentNode() instanceof While
@@ -33,9 +33,9 @@ where
not isInsideLoop(del) and
// False positive: calling `sys.exc_info` within a function results in a
// reference cycle, and an explicit call to `del` helps break this cycle.
not exists(FunctionValue ex |
ex = Value::named("sys.exc_info") and
ex.getACall().getScope() = f
not exists(API::CallNode call |
call = API::moduleImport("sys").getMember("exc_info").getACall() and
call.getScope() = f
)
select del, "Unnecessary deletion of local variable $@ in function $@.", e, e.toString(), f,
f.getName()

View File

@@ -12,7 +12,7 @@
*/
import python
private import LegacyPointsTo
private import semmle.python.ApiGraphs
import Definition
predicate is_increment(Stmt s) {
@@ -41,26 +41,19 @@ predicate one_item_only(For f) {
)
}
predicate points_to_call_to_range(ControlFlowNode f) {
/* (x)range is a function in Py2 and a class in Py3, so we must treat it as a plain object */
exists(Value range |
range = Value::named("range") or
range = Value::named("xrange")
|
f = range.getACall()
)
/** Holds if `node` is a call to `range`, `xrange`, or `list(range(...))`. */
predicate call_to_range(DataFlow::Node node) {
node = API::builtin(["range", "xrange"]).getACall()
or
/* In case points-to fails due to 'from six.moves import range' or similar. */
exists(string range | f.getNode().(Call).getFunc().(Name).getId() = range |
range = "range" or range = "xrange"
)
/* Handle 'from six.moves import range' or similar. */
node = API::moduleImport("six").getMember("moves").getMember(["range", "xrange"]).getACall()
or
/* Handle list(range(...)) and list(list(range(...))) */
f.(CallNode).(ControlFlowNodeWithPointsTo).pointsTo().getClass() = ClassValue::list() and
points_to_call_to_range(f.(CallNode).getArg(0))
node = API::builtin("list").getACall() and
call_to_range(node.(DataFlow::CallCfgNode).getArg(0))
}
/** Whether n is a use of a variable that is a not effectively a constant. */
/** Whether n is a use of a variable that is not effectively a constant. */
predicate use_of_non_constant(Name n) {
exists(Variable var |
n.uses(var) and
@@ -102,8 +95,8 @@ from For f, Variable v, string msg
where
f.getTarget() = v.getAnAccess() and
not f.getAStmt().contains(v.getAnAccess()) and
not points_to_call_to_range(f.getIter().getAFlowNode()) and
not points_to_call_to_range(get_comp_iterable(f)) and
not call_to_range(DataFlow::exprNode(f.getIter())) and
not call_to_range(DataFlow::exprNode(get_comp_iterable(f).getNode())) and
not name_acceptable_for_unused_variable(v) and
not f.getScope().getName() = "genexpr" and
not empty_loop(f) and

View File

@@ -0,0 +1,5 @@
---
category: queryMetadata
---
* The `@security-severity` metadata of `py/log-injection` has been reduced from 7.8 (high) to 6.1 (medium).
* The `@security-severity` metadata of `py/jinja2/autoescape-false` and `py/reflective-xss` has been increased from 6.1 (medium) to 7.8 (high).

View File

@@ -0,0 +1,5 @@
---
category: majorAnalysis
---
- Several quality queries have been ported away from using the legacy points-to library. This may lead to changes in alerts.

View File

@@ -1 +1 @@
| property_old_style.py:8:6:8:13 | Property piosc | Property piosc will not work properly, as class OldStyle is an old-style class. |
| property_old_style.py:8:6:8:13 | property | Property piosc will not work properly, as class OldStyle is an old-style class. |

View File

@@ -1 +1 @@
| newstyle_test.py:4:1:4:16 | class OldStyle1 | Using '__slots__' in an old style class just creates a class attribute called '__slots__'. |
| newstyle_test.py:4:1:4:16 | Class OldStyle1 | Using '__slots__' in an old style class just creates a class attribute called '__slots__'. |

View File

@@ -0,0 +1,5 @@
from . import helper
def use_relative():
tainted = source()
helper.process(tainted)

View File

@@ -0,0 +1,5 @@
def process(value):
sink(value) #$ flow=source
def process2(value):
sink(value) #$ flow=source

View File

@@ -0,0 +1,5 @@
from .. import helper
def use_multi_level_relative():
tainted = source()
helper.process2(tainted)

View File

@@ -0,0 +1,35 @@
import python
import semmle.python.dataflow.new.DataFlow
import semmle.python.dataflow.new.TaintTracking
import utils.test.InlineExpectationsTest
private module TestConfig implements DataFlow::ConfigSig {
predicate isSource(DataFlow::Node node) {
node.(DataFlow::CallCfgNode).getFunction().asCfgNode().(NameNode).getId() = "source"
}
predicate isSink(DataFlow::Node node) {
exists(DataFlow::CallCfgNode call |
call.getFunction().asCfgNode().(NameNode).getId() = "sink" and
node = call.getArg(0)
)
}
}
private module TestFlow = TaintTracking::Global<TestConfig>;
module FlowTest implements TestSig {
string getARelevantTag() { result = "flow" }
predicate hasActualResult(Location location, string element, string tag, string value) {
exists(DataFlow::Node sink |
TestFlow::flow(_, sink) and
tag = "flow" and
location = sink.getLocation() and
value = "source" and
element = sink.toString()
)
}
}
import MakeTest<FlowTest>

View File

@@ -1,2 +1,2 @@
| should_be_context_manager.py:3:1:3:22 | class MegaDel | Class MegaDel implements __del__ (presumably to release some resource). Consider making it a context manager. |
| should_be_context_manager.py:16:1:16:22 | class MiniDel | Class MiniDel implements __del__ (presumably to release some resource). Consider making it a context manager. |
| should_be_context_manager.py:3:1:3:22 | Class MegaDel | Class MegaDel implements __del__ (presumably to release some resource). Consider making it a context manager. |
| should_be_context_manager.py:16:1:16:22 | Class MiniDel | Class MiniDel implements __del__ (presumably to release some resource). Consider making it a context manager. |

View File

@@ -1,3 +1,3 @@
| functions_test.py:95:5:95:40 | Function DeprecatedSliceMethods.__getslice__ | __getslice__ method has been deprecated since Python 2.0. |
| functions_test.py:98:5:98:47 | Function DeprecatedSliceMethods.__setslice__ | __setslice__ method has been deprecated since Python 2.0. |
| functions_test.py:101:5:101:40 | Function DeprecatedSliceMethods.__delslice__ | __delslice__ method has been deprecated since Python 2.0. |
| functions_test.py:95:5:95:40 | Function __getslice__ | __getslice__ method has been deprecated since Python 2.0. |
| functions_test.py:98:5:98:47 | Function __setslice__ | __setslice__ method has been deprecated since Python 2.0. |
| functions_test.py:101:5:101:40 | Function __delslice__ | __delslice__ method has been deprecated since Python 2.0. |