mirror of
https://github.com/github/codeql.git
synced 2026-05-05 21:55:19 +02:00
Merge branch 'main' into refacReDoS
This commit is contained in:
@@ -1,3 +1,26 @@
|
||||
## 0.5.2
|
||||
|
||||
## 0.5.1
|
||||
|
||||
### Deprecated APIs
|
||||
|
||||
- The documentation of API graphs (the `API` module) has been expanded, and some of the members predicates of `API::Node`
|
||||
have been renamed as follows:
|
||||
- `getAnImmediateUse` -> `asSource`
|
||||
- `getARhs` -> `asSink`
|
||||
- `getAUse` -> `getAValueReachableFromSource`
|
||||
- `getAValueReachingRhs` -> `getAValueReachingSink`
|
||||
|
||||
### Minor Analysis Improvements
|
||||
|
||||
* Improved modeling of sensitive data sources, so common words like `certain` and `secretary` are no longer considered a certificate and a secret (respectively).
|
||||
|
||||
## 0.5.0
|
||||
|
||||
### Deprecated APIs
|
||||
|
||||
* The `BarrierGuard` class has been deprecated. Such barriers and sanitizers can now instead be created using the new `BarrierGuard` parameterized module.
|
||||
|
||||
## 0.4.1
|
||||
|
||||
## 0.4.0
|
||||
|
||||
519
python/ql/lib/analysis/DefinitionTracking.qll
Normal file
519
python/ql/lib/analysis/DefinitionTracking.qll
Normal file
@@ -0,0 +1,519 @@
|
||||
/**
|
||||
* Definition tracking for jump-to-defn query.
|
||||
*/
|
||||
|
||||
import python
|
||||
import semmle.python.pointsto.PointsTo
|
||||
import IDEContextual
|
||||
|
||||
private newtype TDefinition =
|
||||
TLocalDefinition(AstNode a) { a instanceof Expr or a instanceof Stmt or a instanceof Module }
|
||||
|
||||
/** A definition for the purposes of jump-to-definition. */
|
||||
class Definition extends TLocalDefinition {
|
||||
/** Gets a textual representation of this element. */
|
||||
string toString() { result = "Definition " + this.getAstNode().getLocation().toString() }
|
||||
|
||||
/** Gets the AST Node associated with this element */
|
||||
AstNode getAstNode() { this = TLocalDefinition(result) }
|
||||
|
||||
/** Gets the Module associated with this element */
|
||||
Module getModule() { result = this.getAstNode().getScope().getEnclosingModule() }
|
||||
|
||||
/** Gets the source location of the AST Node associated with this element */
|
||||
Location getLocation() { result = this.getAstNode().getLocation() }
|
||||
}
|
||||
|
||||
private predicate jump_to_defn(ControlFlowNode use, Definition defn) {
|
||||
exists(EssaVariable var |
|
||||
use = var.getASourceUse() and
|
||||
ssa_variable_defn(var, defn)
|
||||
)
|
||||
or
|
||||
exists(string name |
|
||||
use.isLoad() and
|
||||
jump_to_defn_attribute(use.(AttrNode).getObject(name), name, defn)
|
||||
)
|
||||
or
|
||||
exists(PythonModuleObject mod |
|
||||
use.(ImportExprNode).refersTo(mod) and
|
||||
defn.getAstNode() = mod.getModule()
|
||||
)
|
||||
or
|
||||
exists(PythonModuleObject mod, string name |
|
||||
use.(ImportMemberNode).getModule(name).refersTo(mod) and
|
||||
scope_jump_to_defn_attribute(mod.getModule(), name, defn)
|
||||
)
|
||||
or
|
||||
exists(PackageObject package |
|
||||
use.(ImportExprNode).refersTo(package) and
|
||||
defn.getAstNode() = package.getInitModule().getModule()
|
||||
)
|
||||
or
|
||||
exists(PackageObject package, string name |
|
||||
use.(ImportMemberNode).getModule(name).refersTo(package) and
|
||||
scope_jump_to_defn_attribute(package.getInitModule().getModule(), name, defn)
|
||||
)
|
||||
or
|
||||
(use instanceof PyFunctionObject or use instanceof ClassObject) and
|
||||
defn.getAstNode() = use.getNode()
|
||||
}
|
||||
|
||||
/* Prefer class and functions to class-expressions and function-expressions. */
|
||||
private predicate preferred_jump_to_defn(Expr use, Definition def) {
|
||||
not use instanceof ClassExpr and
|
||||
not use instanceof FunctionExpr and
|
||||
jump_to_defn(use.getAFlowNode(), def)
|
||||
}
|
||||
|
||||
private predicate unique_jump_to_defn(Expr use, Definition def) {
|
||||
preferred_jump_to_defn(use, def) and
|
||||
not exists(Definition other |
|
||||
other != def and
|
||||
preferred_jump_to_defn(use, other)
|
||||
)
|
||||
}
|
||||
|
||||
private predicate ssa_variable_defn(EssaVariable var, Definition defn) {
|
||||
ssa_defn_defn(var.getDefinition(), defn)
|
||||
}
|
||||
|
||||
/** Holds if the phi-function `phi` refers to (`value`, `cls`, `origin`) given the context `context`. */
|
||||
private predicate ssa_phi_defn(PhiFunction phi, Definition defn) {
|
||||
ssa_variable_defn(phi.getAnInput(), defn)
|
||||
}
|
||||
|
||||
/** Holds if the ESSA defn `def` refers to (`value`, `cls`, `origin`) given the context `context`. */
|
||||
private predicate ssa_defn_defn(EssaDefinition def, Definition defn) {
|
||||
ssa_phi_defn(def, defn)
|
||||
or
|
||||
ssa_node_defn(def, defn)
|
||||
or
|
||||
ssa_filter_defn(def, defn)
|
||||
or
|
||||
ssa_node_refinement_defn(def, defn)
|
||||
}
|
||||
|
||||
/** Holds if ESSA edge refinement, `def`, is defined by `defn` */
|
||||
predicate ssa_filter_defn(PyEdgeRefinement def, Definition defn) {
|
||||
ssa_variable_defn(def.getInput(), defn)
|
||||
}
|
||||
|
||||
/** Holds if ESSA defn, `uniphi`,is defined by `defn` */
|
||||
predicate uni_edged_phi_defn(SingleSuccessorGuard uniphi, Definition defn) {
|
||||
ssa_variable_defn(uniphi.getInput(), defn)
|
||||
}
|
||||
|
||||
pragma[noinline]
|
||||
private predicate ssa_node_defn(EssaNodeDefinition def, Definition defn) {
|
||||
assignment_jump_to_defn(def, defn)
|
||||
or
|
||||
parameter_defn(def, defn)
|
||||
or
|
||||
delete_defn(def, defn)
|
||||
or
|
||||
scope_entry_defn(def, defn)
|
||||
or
|
||||
implicit_submodule_defn(def, defn)
|
||||
}
|
||||
|
||||
/* Definition for normal assignments `def = ...` */
|
||||
private predicate assignment_jump_to_defn(AssignmentDefinition def, Definition defn) {
|
||||
defn = TLocalDefinition(def.getValue().getNode())
|
||||
}
|
||||
|
||||
pragma[noinline]
|
||||
private predicate ssa_node_refinement_defn(EssaNodeRefinement def, Definition defn) {
|
||||
method_callsite_defn(def, defn)
|
||||
or
|
||||
import_star_defn(def, defn)
|
||||
or
|
||||
attribute_assignment_defn(def, defn)
|
||||
or
|
||||
callsite_defn(def, defn)
|
||||
or
|
||||
argument_defn(def, defn)
|
||||
or
|
||||
attribute_delete_defn(def, defn)
|
||||
or
|
||||
uni_edged_phi_defn(def, defn)
|
||||
}
|
||||
|
||||
/* Definition for parameter. `def foo(param): ...` */
|
||||
private predicate parameter_defn(ParameterDefinition def, Definition defn) {
|
||||
defn.getAstNode() = def.getDefiningNode().getNode()
|
||||
}
|
||||
|
||||
/* Definition for deletion: `del name` */
|
||||
private predicate delete_defn(DeletionDefinition def, Definition defn) { none() }
|
||||
|
||||
/* Implicit "defn" of the names of submodules at the start of an `__init__.py` file. */
|
||||
private predicate implicit_submodule_defn(ImplicitSubModuleDefinition def, Definition defn) {
|
||||
exists(PackageObject package, ModuleObject mod |
|
||||
package.getInitModule().getModule() = def.getDefiningNode().getScope() and
|
||||
mod = package.submodule(def.getSourceVariable().getName()) and
|
||||
defn.getAstNode() = mod.getModule()
|
||||
)
|
||||
}
|
||||
|
||||
/*
|
||||
* Helper for scope_entry_value_transfer(...).
|
||||
* Transfer of values from the callsite to the callee, for enclosing variables, but not arguments/parameters
|
||||
*/
|
||||
|
||||
private predicate scope_entry_value_transfer_at_callsite(
|
||||
EssaVariable pred_var, ScopeEntryDefinition succ_def
|
||||
) {
|
||||
exists(CallNode callsite, FunctionObject f |
|
||||
f.getACall() = callsite and
|
||||
pred_var.getSourceVariable() = succ_def.getSourceVariable() and
|
||||
pred_var.getAUse() = callsite and
|
||||
succ_def.getDefiningNode() = f.getFunction().getEntryNode()
|
||||
)
|
||||
}
|
||||
|
||||
/* Model the transfer of values at scope-entry points. Transfer from `pred_var, pred_context` to `succ_def, succ_context` */
|
||||
private predicate scope_entry_value_transfer(EssaVariable pred_var, ScopeEntryDefinition succ_def) {
|
||||
BaseFlow::scope_entry_value_transfer_from_earlier(pred_var, _, succ_def, _)
|
||||
or
|
||||
scope_entry_value_transfer_at_callsite(pred_var, succ_def)
|
||||
or
|
||||
class_entry_value_transfer(pred_var, succ_def)
|
||||
}
|
||||
|
||||
/* Helper for scope_entry_value_transfer */
|
||||
private predicate class_entry_value_transfer(EssaVariable pred_var, ScopeEntryDefinition succ_def) {
|
||||
exists(ImportTimeScope scope, ControlFlowNode class_def |
|
||||
class_def = pred_var.getAUse() and
|
||||
scope.entryEdge(class_def, succ_def.getDefiningNode()) and
|
||||
pred_var.getSourceVariable() = succ_def.getSourceVariable()
|
||||
)
|
||||
}
|
||||
|
||||
/* Definition for implicit variable declarations at scope-entry. */
|
||||
pragma[noinline]
|
||||
private predicate scope_entry_defn(ScopeEntryDefinition def, Definition defn) {
|
||||
/* Transfer from another scope */
|
||||
exists(EssaVariable var |
|
||||
scope_entry_value_transfer(var, def) and
|
||||
ssa_variable_defn(var, defn)
|
||||
)
|
||||
}
|
||||
|
||||
/*
|
||||
* Definition for a variable (possibly) redefined by a call:
|
||||
* Just assume that call does not define variable
|
||||
*/
|
||||
|
||||
pragma[noinline]
|
||||
private predicate callsite_defn(CallsiteRefinement def, Definition defn) {
|
||||
ssa_variable_defn(def.getInput(), defn)
|
||||
}
|
||||
|
||||
/* Pass through for `self` for the implicit re-defn of `self` in `self.foo()` */
|
||||
private predicate method_callsite_defn(MethodCallsiteRefinement def, Definition defn) {
|
||||
/* The value of self remains the same, only the attributes may change */
|
||||
ssa_variable_defn(def.getInput(), defn)
|
||||
}
|
||||
|
||||
/** Helpers for import_star_defn */
|
||||
pragma[noinline]
|
||||
private predicate module_and_name_for_import_star(
|
||||
ModuleObject mod, string name, ImportStarRefinement def
|
||||
) {
|
||||
exists(ImportStarNode im_star |
|
||||
module_and_name_for_import_star_helper(mod, name, im_star, def) and
|
||||
mod.exports(name)
|
||||
)
|
||||
}
|
||||
|
||||
pragma[noinline]
|
||||
private predicate module_and_name_for_import_star_helper(
|
||||
ModuleObject mod, string name, ImportStarNode im_star, ImportStarRefinement def
|
||||
) {
|
||||
im_star = def.getDefiningNode() and
|
||||
im_star.getModule().refersTo(mod) and
|
||||
name = def.getSourceVariable().getName()
|
||||
}
|
||||
|
||||
/** Holds if `def` is technically a defn of `var`, but the `from ... import *` does not in fact define `var` */
|
||||
pragma[noinline]
|
||||
private predicate variable_not_redefined_by_import_star(EssaVariable var, ImportStarRefinement def) {
|
||||
var = def.getInput() and
|
||||
exists(ModuleObject mod |
|
||||
def.getDefiningNode().(ImportStarNode).getModule().refersTo(mod) and
|
||||
not mod.exports(var.getSourceVariable().getName())
|
||||
)
|
||||
}
|
||||
|
||||
/* Definition for `from ... import *` */
|
||||
private predicate import_star_defn(ImportStarRefinement def, Definition defn) {
|
||||
exists(ModuleObject mod, string name | module_and_name_for_import_star(mod, name, def) |
|
||||
/* Attribute from imported module */
|
||||
scope_jump_to_defn_attribute(mod.getModule(), name, defn)
|
||||
)
|
||||
or
|
||||
exists(EssaVariable var |
|
||||
/* Retain value held before import */
|
||||
variable_not_redefined_by_import_star(var, def) and
|
||||
ssa_variable_defn(var, defn)
|
||||
)
|
||||
}
|
||||
|
||||
/** Attribute assignments have no effect as far as defn tracking is concerned */
|
||||
private predicate attribute_assignment_defn(AttributeAssignment def, Definition defn) {
|
||||
ssa_variable_defn(def.getInput(), defn)
|
||||
}
|
||||
|
||||
/** Ignore the effects of calls on their arguments. This is an approximation, but attempting to improve accuracy would be very expensive for very little gain. */
|
||||
private predicate argument_defn(ArgumentRefinement def, Definition defn) {
|
||||
ssa_variable_defn(def.getInput(), defn)
|
||||
}
|
||||
|
||||
/** Attribute deletions have no effect as far as value tracking is concerned. */
|
||||
pragma[noinline]
|
||||
private predicate attribute_delete_defn(EssaAttributeDeletion def, Definition defn) {
|
||||
ssa_variable_defn(def.getInput(), defn)
|
||||
}
|
||||
|
||||
/*
|
||||
* Definition flow for attributes. These mirror the "normal" defn predicates.
|
||||
* For each defn predicate `xxx_defn(XXX def, Definition defn)`
|
||||
* There is an equivalent predicate that tracks the values in attributes:
|
||||
* `xxx_jump_to_defn_attribute(XXX def, string name, Definition defn)`
|
||||
*/
|
||||
|
||||
/**
|
||||
* INTERNAL -- Public for testing only.
|
||||
* Holds if the attribute `name` of the ssa variable `var` refers to (`value`, `cls`, `origin`)
|
||||
*/
|
||||
predicate ssa_variable_jump_to_defn_attribute(EssaVariable var, string name, Definition defn) {
|
||||
ssa_defn_jump_to_defn_attribute(var.getDefinition(), name, defn)
|
||||
}
|
||||
|
||||
/** Helper for ssa_variable_jump_to_defn_attribute */
|
||||
private predicate ssa_defn_jump_to_defn_attribute(EssaDefinition def, string name, Definition defn) {
|
||||
ssa_phi_jump_to_defn_attribute(def, name, defn)
|
||||
or
|
||||
ssa_node_jump_to_defn_attribute(def, name, defn)
|
||||
or
|
||||
ssa_node_refinement_jump_to_defn_attribute(def, name, defn)
|
||||
or
|
||||
ssa_filter_jump_to_defn_attribute(def, name, defn)
|
||||
}
|
||||
|
||||
/** Holds if ESSA edge refinement, `def`, is defined by `defn` of `priority` */
|
||||
predicate ssa_filter_jump_to_defn_attribute(PyEdgeRefinement def, string name, Definition defn) {
|
||||
ssa_variable_jump_to_defn_attribute(def.getInput(), name, defn)
|
||||
}
|
||||
|
||||
/** Holds if the attribute `name` of the ssa phi-function defn `phi` refers to (`value`, `cls`, `origin`) */
|
||||
private predicate ssa_phi_jump_to_defn_attribute(PhiFunction phi, string name, Definition defn) {
|
||||
ssa_variable_jump_to_defn_attribute(phi.getAnInput(), name, defn)
|
||||
}
|
||||
|
||||
/** Helper for ssa_defn_jump_to_defn_attribute */
|
||||
pragma[noinline]
|
||||
private predicate ssa_node_jump_to_defn_attribute(
|
||||
EssaNodeDefinition def, string name, Definition defn
|
||||
) {
|
||||
assignment_jump_to_defn_attribute(def, name, defn)
|
||||
or
|
||||
self_parameter_jump_to_defn_attribute(def, name, defn)
|
||||
or
|
||||
scope_entry_jump_to_defn_attribute(def, name, defn)
|
||||
}
|
||||
|
||||
/** Helper for ssa_defn_jump_to_defn_attribute */
|
||||
pragma[noinline]
|
||||
private predicate ssa_node_refinement_jump_to_defn_attribute(
|
||||
EssaNodeRefinement def, string name, Definition defn
|
||||
) {
|
||||
attribute_assignment_jump_to_defn_attribute(def, name, defn)
|
||||
or
|
||||
argument_jump_to_defn_attribute(def, name, defn)
|
||||
}
|
||||
|
||||
pragma[noinline]
|
||||
private predicate scope_entry_jump_to_defn_attribute(
|
||||
ScopeEntryDefinition def, string name, Definition defn
|
||||
) {
|
||||
exists(EssaVariable var |
|
||||
scope_entry_value_transfer(var, def) and
|
||||
ssa_variable_jump_to_defn_attribute(var, name, defn)
|
||||
)
|
||||
}
|
||||
|
||||
private predicate scope_jump_to_defn_attribute(ImportTimeScope s, string name, Definition defn) {
|
||||
exists(EssaVariable var |
|
||||
BaseFlow::reaches_exit(var) and
|
||||
var.getScope() = s and
|
||||
var.getName() = name
|
||||
|
|
||||
ssa_variable_defn(var, defn)
|
||||
)
|
||||
}
|
||||
|
||||
private predicate jump_to_defn_attribute(ControlFlowNode use, string name, Definition defn) {
|
||||
/* Local attribute */
|
||||
exists(EssaVariable var |
|
||||
use = var.getASourceUse() and
|
||||
ssa_variable_jump_to_defn_attribute(var, name, defn)
|
||||
)
|
||||
or
|
||||
/* Instance attributes */
|
||||
exists(ClassObject cls | use.refersTo(_, cls, _) |
|
||||
scope_jump_to_defn_attribute(cls.getPyClass(), name, defn)
|
||||
)
|
||||
or
|
||||
/* Super attributes */
|
||||
exists(AttrNode f, SuperBoundMethod sbm, Object function |
|
||||
use = f.getObject(name) and
|
||||
f.refersTo(sbm) and
|
||||
function = sbm.getFunction(_) and
|
||||
function.getOrigin() = defn.getAstNode()
|
||||
)
|
||||
or
|
||||
/* Class or module attribute */
|
||||
exists(Object obj, Scope scope |
|
||||
use.refersTo(obj) and
|
||||
scope_jump_to_defn_attribute(scope, name, defn)
|
||||
|
|
||||
obj.(ClassObject).getPyClass() = scope
|
||||
or
|
||||
obj.(PythonModuleObject).getModule() = scope
|
||||
or
|
||||
obj.(PackageObject).getInitModule().getModule() = scope
|
||||
)
|
||||
}
|
||||
|
||||
pragma[noinline]
|
||||
private predicate assignment_jump_to_defn_attribute(
|
||||
AssignmentDefinition def, string name, Definition defn
|
||||
) {
|
||||
jump_to_defn_attribute(def.getValue(), name, defn)
|
||||
}
|
||||
|
||||
pragma[noinline]
|
||||
private predicate attribute_assignment_jump_to_defn_attribute(
|
||||
AttributeAssignment def, string name, Definition defn
|
||||
) {
|
||||
defn.getAstNode() = def.getDefiningNode().getNode() and name = def.getName()
|
||||
or
|
||||
ssa_variable_jump_to_defn_attribute(def.getInput(), name, defn) and not name = def.getName()
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if `def` defines the attribute `name`
|
||||
* `def` takes the form `setattr(use, "name")` where `use` is the input to the defn.
|
||||
*/
|
||||
private predicate sets_attribute(ArgumentRefinement def, string name) {
|
||||
exists(CallNode call |
|
||||
call = def.getDefiningNode() and
|
||||
call.getFunction().refersTo(Object::builtin("setattr")) and
|
||||
def.getInput().getAUse() = call.getArg(0) and
|
||||
call.getArg(1).getNode().(StrConst).getText() = name
|
||||
)
|
||||
}
|
||||
|
||||
pragma[noinline]
|
||||
private predicate argument_jump_to_defn_attribute(
|
||||
ArgumentRefinement def, string name, Definition defn
|
||||
) {
|
||||
if sets_attribute(def, name)
|
||||
then jump_to_defn(def.getDefiningNode().(CallNode).getArg(2), defn)
|
||||
else ssa_variable_jump_to_defn_attribute(def.getInput(), name, defn)
|
||||
}
|
||||
|
||||
/** Gets the (temporally) preceding variable for "self", e.g. `def` is in method foo() and `result` is in `__init__()`. */
|
||||
private EssaVariable preceding_self_variable(ParameterDefinition def) {
|
||||
def.isSelf() and
|
||||
exists(Function preceding, Function method |
|
||||
method = def.getScope() and
|
||||
// Only methods
|
||||
preceding.isMethod() and
|
||||
preceding.precedes(method) and
|
||||
BaseFlow::reaches_exit(result) and
|
||||
result.getSourceVariable().(Variable).isSelf() and
|
||||
result.getScope() = preceding
|
||||
)
|
||||
}
|
||||
|
||||
pragma[noinline]
|
||||
private predicate self_parameter_jump_to_defn_attribute(
|
||||
ParameterDefinition def, string name, Definition defn
|
||||
) {
|
||||
ssa_variable_jump_to_defn_attribute(preceding_self_variable(def), name, defn)
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a definition for 'use'.
|
||||
* This exists primarily for testing use `getPreferredDefinition()` instead.
|
||||
*/
|
||||
Definition getADefinition(Expr use) {
|
||||
jump_to_defn(use.getAFlowNode(), result) and
|
||||
not use instanceof Call and
|
||||
not use.isArtificial() and
|
||||
// Not the use itself
|
||||
not result = TLocalDefinition(use)
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the unique definition for 'use', if one can be found.
|
||||
* Helper for the jump-to-definition query.
|
||||
*/
|
||||
Definition getUniqueDefinition(Expr use) {
|
||||
unique_jump_to_defn(use, result) and
|
||||
not use instanceof Call and
|
||||
not use.isArtificial() and
|
||||
// Not the use itself
|
||||
not result = TLocalDefinition(use)
|
||||
}
|
||||
|
||||
/** A helper class to get suitable locations for attributes */
|
||||
class NiceLocationExpr extends Expr {
|
||||
/** Gets a textual representation of this element. */
|
||||
override string toString() { result = this.(Expr).toString() }
|
||||
|
||||
/**
|
||||
* Holds if this element is at the specified location.
|
||||
* The location spans column `bc` of line `bl` to
|
||||
* column `ec` of line `el` in file `f`.
|
||||
* For more information, see
|
||||
* [Locations](https://codeql.github.com/docs/writing-codeql-queries/providing-locations-in-codeql-queries/).
|
||||
*/
|
||||
predicate hasLocationInfo(string f, int bl, int bc, int el, int ec) {
|
||||
/* Attribute location for x.y is that of 'y' so that url does not overlap with that of 'x' */
|
||||
exists(int abl, int abc | this.(Attribute).getLocation().hasLocationInfo(f, abl, abc, el, ec) |
|
||||
bl = el and bc = ec - this.(Attribute).getName().length() + 1
|
||||
)
|
||||
or
|
||||
this.(Name).getLocation().hasLocationInfo(f, bl, bc, el, ec)
|
||||
or
|
||||
// Show xxx for `xxx` in `from xxx import y` or
|
||||
// for `import xxx` or for `import xxx as yyy`.
|
||||
this.(ImportExpr).getLocation().hasLocationInfo(f, bl, bc, el, ec)
|
||||
or
|
||||
/* Show y for `y` in `from xxx import y` */
|
||||
exists(string name |
|
||||
name = this.(ImportMember).getName() and
|
||||
this.(ImportMember).getLocation().hasLocationInfo(f, _, _, el, ec) and
|
||||
bl = el and
|
||||
bc = ec - name.length() + 1
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the definition (of kind `kind`) for the expression `use`, if one can be found.
|
||||
*/
|
||||
cached
|
||||
Definition definitionOf(NiceLocationExpr use, string kind) {
|
||||
exists(string f, int l |
|
||||
result = getUniqueDefinition(use) and
|
||||
kind = "Definition" and
|
||||
use.hasLocationInfo(f, l, _, _, _) and
|
||||
// Ignore if the definition is on the same line as the use
|
||||
not result.getLocation().hasLocationInfo(f, l, _, _, _)
|
||||
)
|
||||
}
|
||||
22
python/ql/lib/analysis/IDEContextual.qll
Normal file
22
python/ql/lib/analysis/IDEContextual.qll
Normal file
@@ -0,0 +1,22 @@
|
||||
/**
|
||||
* Provides shared predicates related to contextual queries in the code viewer.
|
||||
*/
|
||||
|
||||
import semmle.files.FileSystem
|
||||
|
||||
/**
|
||||
* Returns the `File` matching the given source file name as encoded by the VS
|
||||
* Code extension.
|
||||
*/
|
||||
cached
|
||||
File getFileBySourceArchiveName(string name) {
|
||||
// The name provided for a file in the source archive by the VS Code extension
|
||||
// has some differences from the absolute path in the database:
|
||||
// 1. colons are replaced by underscores
|
||||
// 2. there's a leading slash, even for Windows paths: "C:/foo/bar" ->
|
||||
// "/C_/foo/bar"
|
||||
// 3. double slashes in UNC prefixes are replaced with a single slash
|
||||
// We can handle 2 and 3 together by unconditionally adding a leading slash
|
||||
// before replacing double slashes.
|
||||
name = ("/" + result.getAbsolutePath().replaceAll(":", "_")).replaceAll("//", "/")
|
||||
}
|
||||
20
python/ql/lib/analysis/LocalDefinitions.ql
Normal file
20
python/ql/lib/analysis/LocalDefinitions.ql
Normal file
@@ -0,0 +1,20 @@
|
||||
/**
|
||||
* @name Jump-to-definition links
|
||||
* @description Generates use-definition pairs that provide the data
|
||||
* for jump-to-definition in the code viewer.
|
||||
* @kind definitions
|
||||
* @id py/ide-jump-to-definition
|
||||
* @tags ide-contextual-queries/local-definitions
|
||||
*/
|
||||
|
||||
import python
|
||||
import analysis.DefinitionTracking
|
||||
|
||||
external string selectedSourceFile();
|
||||
|
||||
from NiceLocationExpr use, Definition defn, string kind, string f
|
||||
where
|
||||
defn = definitionOf(use, kind) and
|
||||
use.hasLocationInfo(f, _, _, _, _) and
|
||||
getFileBySourceArchiveName(selectedSourceFile()).getAbsolutePath() = f
|
||||
select use, defn, kind
|
||||
19
python/ql/lib/analysis/LocalReferences.ql
Normal file
19
python/ql/lib/analysis/LocalReferences.ql
Normal file
@@ -0,0 +1,19 @@
|
||||
/**
|
||||
* @name Find-references links
|
||||
* @description Generates use-definition pairs that provide the data
|
||||
* for find-references in the code viewer.
|
||||
* @kind definitions
|
||||
* @id py/ide-find-references
|
||||
* @tags ide-contextual-queries/local-references
|
||||
*/
|
||||
|
||||
import python
|
||||
import analysis.DefinitionTracking
|
||||
|
||||
external string selectedSourceFile();
|
||||
|
||||
from NiceLocationExpr use, Definition defn, string kind
|
||||
where
|
||||
defn = definitionOf(use, kind) and
|
||||
defn.getLocation().getFile() = getFileBySourceArchiveName(selectedSourceFile())
|
||||
select use, defn, kind
|
||||
@@ -0,0 +1,4 @@
|
||||
---
|
||||
category: minorAnalysis
|
||||
---
|
||||
* Change `.getASubclass()` on `API::Node` so it allows to follow subclasses even if the class has a class decorator.
|
||||
@@ -1,4 +0,0 @@
|
||||
---
|
||||
category: minorAnalysis
|
||||
---
|
||||
* Improved modeling of sensitive data sources, so common words like `certain` and `secretary` are no longer considered a certificate and a secret (respectively).
|
||||
@@ -1,4 +1,5 @@
|
||||
---
|
||||
category: deprecated
|
||||
---
|
||||
## 0.5.0
|
||||
|
||||
### Deprecated APIs
|
||||
|
||||
* The `BarrierGuard` class has been deprecated. Such barriers and sanitizers can now instead be created using the new `BarrierGuard` parameterized module.
|
||||
14
python/ql/lib/change-notes/released/0.5.1.md
Normal file
14
python/ql/lib/change-notes/released/0.5.1.md
Normal file
@@ -0,0 +1,14 @@
|
||||
## 0.5.1
|
||||
|
||||
### Deprecated APIs
|
||||
|
||||
- The documentation of API graphs (the `API` module) has been expanded, and some of the members predicates of `API::Node`
|
||||
have been renamed as follows:
|
||||
- `getAnImmediateUse` -> `asSource`
|
||||
- `getARhs` -> `asSink`
|
||||
- `getAUse` -> `getAValueReachableFromSource`
|
||||
- `getAValueReachingRhs` -> `getAValueReachingSink`
|
||||
|
||||
### Minor Analysis Improvements
|
||||
|
||||
* Improved modeling of sensitive data sources, so common words like `certain` and `secretary` are no longer considered a certificate and a secret (respectively).
|
||||
1
python/ql/lib/change-notes/released/0.5.2.md
Normal file
1
python/ql/lib/change-notes/released/0.5.2.md
Normal file
@@ -0,0 +1 @@
|
||||
## 0.5.2
|
||||
@@ -1,2 +1,2 @@
|
||||
---
|
||||
lastReleaseVersion: 0.4.1
|
||||
lastReleaseVersion: 0.5.2
|
||||
|
||||
28
python/ql/lib/printAst.ql
Normal file
28
python/ql/lib/printAst.ql
Normal file
@@ -0,0 +1,28 @@
|
||||
/**
|
||||
* @name Print AST
|
||||
* @description Outputs a representation of a file's Abstract Syntax Tree. This
|
||||
* query is used by the VS Code extension.
|
||||
* @id py/print-ast
|
||||
* @kind graph
|
||||
* @tags ide-contextual-queries/print-ast
|
||||
*/
|
||||
|
||||
import python
|
||||
import semmle.python.PrintAst
|
||||
import analysis.DefinitionTracking
|
||||
|
||||
/**
|
||||
* Gets the source file that will be used to generate the AST.
|
||||
*/
|
||||
external string selectedSourceFile();
|
||||
|
||||
class PrintAstConfigurationOverride extends PrintAstConfiguration {
|
||||
/**
|
||||
* Holds if the location matches the selected file in the VS Code extension and
|
||||
* the element is not a synthetic constructor.
|
||||
*/
|
||||
override predicate shouldPrint(AstNode e, Location l) {
|
||||
super.shouldPrint(e, l) and
|
||||
l.getFile() = getFileBySourceArchiveName(selectedSourceFile())
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
name: codeql/python-all
|
||||
version: 0.5.0-dev
|
||||
version: 0.5.3-dev
|
||||
groups: python
|
||||
dbscheme: semmlecode.python.dbscheme
|
||||
extractor: python
|
||||
|
||||
@@ -12,76 +12,165 @@ import semmle.python.dataflow.new.DataFlow
|
||||
private import semmle.python.internal.CachedStages
|
||||
|
||||
/**
|
||||
* Provides classes and predicates for working with APIs used in a database.
|
||||
* Provides classes and predicates for working with the API boundary between the current
|
||||
* codebase and external libraries.
|
||||
*
|
||||
* See `API::Node` for more in-depth documentation.
|
||||
*/
|
||||
module API {
|
||||
/**
|
||||
* An abstract representation of a definition or use of an API component such as a function
|
||||
* exported by a Python package, or its result.
|
||||
* A node in the API graph, representing a value that has crossed the boundary between this
|
||||
* codebase and an external library (or in general, any external codebase).
|
||||
*
|
||||
* ### Basic usage
|
||||
*
|
||||
* API graphs are typically used to identify "API calls", that is, calls to an external function
|
||||
* whose implementation is not necessarily part of the current codebase.
|
||||
*
|
||||
* The most basic use of API graphs is typically as follows:
|
||||
* 1. Start with `API::moduleImport` for the relevant library.
|
||||
* 2. Follow up with a chain of accessors such as `getMember` describing how to get to the relevant API function.
|
||||
* 3. Map the resulting API graph nodes to data-flow nodes, using `asSource` or `asSink`.
|
||||
*
|
||||
* For example, a simplified way to get the first argument of a call to `json.dumps` would be
|
||||
* ```ql
|
||||
* API::moduleImport("json").getMember("dumps").getParameter(0).asSink()
|
||||
* ```
|
||||
*
|
||||
* The most commonly used accessors are `getMember`, `getParameter`, and `getReturn`.
|
||||
*
|
||||
* ### API graph nodes
|
||||
*
|
||||
* There are two kinds of nodes in the API graphs, distinguished by who is "holding" the value:
|
||||
* - **Use-nodes** represent values held by the current codebase, which came from an external library.
|
||||
* (The current codebase is "using" a value that came from the library).
|
||||
* - **Def-nodes** represent values held by the external library, which came from this codebase.
|
||||
* (The current codebase "defines" the value seen by the library).
|
||||
*
|
||||
* API graph nodes are associated with data-flow nodes in the current codebase.
|
||||
* (API graphs are designed to work when external libraries are not part of the database,
|
||||
* so we do not associate with concrete data-flow nodes from the external library).
|
||||
* - **Use-nodes** are associated with data-flow nodes where a value enters the current codebase,
|
||||
* such as the return value of a call to an external function.
|
||||
* - **Def-nodes** are associated with data-flow nodes where a value leaves the current codebase,
|
||||
* such as an argument passed in a call to an external function.
|
||||
*
|
||||
*
|
||||
* ### Access paths and edge labels
|
||||
*
|
||||
* Nodes in the API graph are associated with a set of access paths, describing a series of operations
|
||||
* that may be performed to obtain that value.
|
||||
*
|
||||
* For example, the access path `API::moduleImport("json").getMember("dumps")` represents the action of
|
||||
* importing `json` and then accessing the member `dumps` on the resulting object.
|
||||
*
|
||||
* Each edge in the graph is labelled by such an "operation". For an edge `A->B`, the type of the `A` node
|
||||
* determines who is performing the operation, and the type of the `B` node determines who ends up holding
|
||||
* the result:
|
||||
* - An edge starting from a use-node describes what the current codebase is doing to a value that
|
||||
* came from a library.
|
||||
* - An edge starting from a def-node describes what the external library might do to a value that
|
||||
* came from the current codebase.
|
||||
* - An edge ending in a use-node means the result ends up in the current codebase (at its associated data-flow node).
|
||||
* - An edge ending in a def-node means the result ends up in external code (its associated data-flow node is
|
||||
* the place where it was "last seen" in the current codebase before flowing out)
|
||||
*
|
||||
* Because the implementation of the external library is not visible, it is not known exactly what operations
|
||||
* it will perform on values that flow there. Instead, the edges starting from a def-node are operations that would
|
||||
* lead to an observable effect within the current codebase; without knowing for certain if the library will actually perform
|
||||
* those operations. (When constructing these edges, we assume the library is somewhat well-behaved).
|
||||
*
|
||||
* For example, given this snippet:
|
||||
* ```python
|
||||
* import foo
|
||||
* foo.bar(lambda x: doSomething(x))
|
||||
* ```
|
||||
* A callback is passed to the external function `foo.bar`. We can't know if `foo.bar` will actually invoke this callback.
|
||||
* But _if_ the library should decide to invoke the callback, then a value will flow into the current codebase via the `x` parameter.
|
||||
* For that reason, an edge is generated representing the argument-passing operation that might be performed by `foo.bar`.
|
||||
* This edge is going from the def-node associated with the callback to the use-node associated with the parameter `x`.
|
||||
*/
|
||||
class Node extends Impl::TApiNode {
|
||||
/**
|
||||
* Gets a data-flow node corresponding to a use of the API component represented by this node.
|
||||
* Gets a data-flow node where this value may flow after entering the current codebase.
|
||||
*
|
||||
* For example, `import re; re.escape` is a use of the `escape` function from the
|
||||
* `re` module, and `import re; re.escape("hello")` is a use of the return of that function.
|
||||
*
|
||||
* This includes indirect uses found via data flow, meaning that in
|
||||
* ```python
|
||||
* def f(x):
|
||||
* pass
|
||||
*
|
||||
* f(obj.foo)
|
||||
* ```
|
||||
* both `obj.foo` and `x` are uses of the `foo` member from `obj`.
|
||||
* This is similar to `asSource()` but additionally includes nodes that are transitively reachable by data flow.
|
||||
* See `asSource()` for examples.
|
||||
*/
|
||||
DataFlow::Node getAUse() {
|
||||
DataFlow::Node getAValueReachableFromSource() {
|
||||
exists(DataFlow::LocalSourceNode src | Impl::use(this, src) |
|
||||
Impl::trackUseNode(src).flowsTo(result)
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a data-flow node corresponding to the right-hand side of a definition of the API
|
||||
* component represented by this node.
|
||||
* Gets a data-flow node where this value leaves the current codebase and flows into an
|
||||
* external library (or in general, any external codebase).
|
||||
*
|
||||
* For example, in the property write `foo.bar = x`, variable `x` is the the right-hand side
|
||||
* of a write to the `bar` property of `foo`.
|
||||
* Concretely, this is either an argument passed to a call to external code,
|
||||
* or the right-hand side of an attribute write on an object flowing into such a call.
|
||||
*
|
||||
* Note that for parameters, it is the arguments flowing into that parameter that count as
|
||||
* right-hand sides of the definition, not the declaration of the parameter itself.
|
||||
* Consequently, in :
|
||||
* For example:
|
||||
* ```python
|
||||
* from mypkg import foo;
|
||||
* import foo
|
||||
*
|
||||
* # 'x' is matched by API::moduleImport("foo").getMember("bar").getParameter(0).asSink()
|
||||
* foo.bar(x)
|
||||
*
|
||||
* # 'x' is matched by API::moduleImport("foo").getMember("bar").getParameter(0).getMember("prop").asSink()
|
||||
* obj.prop = x
|
||||
* foo.bar(obj);
|
||||
* ```
|
||||
* `x` is the right-hand side of a definition of the first parameter of `bar` from the `mypkg.foo` module.
|
||||
*
|
||||
* This predicate does not include nodes transitively reaching the sink by data flow;
|
||||
* use `getAValueReachingSink` for that.
|
||||
*/
|
||||
DataFlow::Node getARhs() { Impl::rhs(this, result) }
|
||||
DataFlow::Node asSink() { Impl::rhs(this, result) }
|
||||
|
||||
/**
|
||||
* Gets a data-flow node that may interprocedurally flow to the right-hand side of a definition
|
||||
* of the API component represented by this node.
|
||||
* Gets a data-flow node that transitively flows to an external library (or in general, any external codebase).
|
||||
*
|
||||
* This is similar to `asSink()` but additionally includes nodes that transitively reach a sink by data flow.
|
||||
* See `asSink()` for examples.
|
||||
*/
|
||||
DataFlow::Node getAValueReachingRhs() { result = Impl::trackDefNode(this.getARhs()) }
|
||||
DataFlow::Node getAValueReachingSink() { result = Impl::trackDefNode(this.asSink()) }
|
||||
|
||||
/**
|
||||
* Gets an immediate use of the API component represented by this node.
|
||||
* Gets a data-flow node where this value enters the current codebase.
|
||||
*
|
||||
* For example, `import re; re.escape` is a an immediate use of the `escape` member
|
||||
* from the `re` module.
|
||||
* For example:
|
||||
* ```python
|
||||
* # API::moduleImport("re").asSource()
|
||||
* import re
|
||||
*
|
||||
* Unlike `getAUse()`, this predicate only gets the immediate references, not the indirect uses
|
||||
* found via data flow. This means that in `x = re.escape` only `re.escape` is a reference
|
||||
* to the `escape` member of `re`, neither `x` nor any node that `x` flows to is a reference to
|
||||
* this API component.
|
||||
* # API::moduleImport("re").getMember("escape").asSource()
|
||||
* re.escape
|
||||
*
|
||||
* # API::moduleImport("re").getMember("escape").getReturn().asSource()
|
||||
* re.escape()
|
||||
* ```
|
||||
*
|
||||
* This predicate does not include nodes transitively reachable by data flow;
|
||||
* use `getAValueReachableFromSource` for that.
|
||||
*/
|
||||
DataFlow::LocalSourceNode getAnImmediateUse() { Impl::use(this, result) }
|
||||
DataFlow::LocalSourceNode asSource() { Impl::use(this, result) }
|
||||
|
||||
/** DEPRECATED. This predicate has been renamed to `getAValueReachableFromSource()`. */
|
||||
deprecated DataFlow::Node getAUse() { result = this.getAValueReachableFromSource() }
|
||||
|
||||
/** DEPRECATED. This predicate has been renamed to `asSource()`. */
|
||||
deprecated DataFlow::LocalSourceNode getAnImmediateUse() { result = this.asSource() }
|
||||
|
||||
/** DEPRECATED. This predicate has been renamed to `asSink()`. */
|
||||
deprecated DataFlow::Node getARhs() { result = this.asSink() }
|
||||
|
||||
/** DEPRECATED. This predicate has been renamed to `getAValueReachingSink()`. */
|
||||
deprecated DataFlow::Node getAValueReachingRhs() { result = this.getAValueReachingSink() }
|
||||
|
||||
/**
|
||||
* Gets a call to the function represented by this API component.
|
||||
*/
|
||||
CallNode getACall() { result = this.getReturn().getAnImmediateUse() }
|
||||
CallNode getACall() { result = this.getReturn().asSource() }
|
||||
|
||||
/**
|
||||
* Gets a node representing member `m` of this API component.
|
||||
@@ -306,7 +395,7 @@ module API {
|
||||
class CallNode extends DataFlow::CallCfgNode {
|
||||
API::Node callee;
|
||||
|
||||
CallNode() { this = callee.getReturn().getAnImmediateUse() }
|
||||
CallNode() { this = callee.getReturn().asSource() }
|
||||
|
||||
/** Gets the API node for the `i`th parameter of this invocation. */
|
||||
pragma[nomagic]
|
||||
@@ -319,14 +408,14 @@ module API {
|
||||
* Gets an API node where a RHS of the node is the `i`th argument to this call.
|
||||
*/
|
||||
pragma[noinline]
|
||||
private Node getAParameterCandidate(int i) { result.getARhs() = this.getArg(i) }
|
||||
private Node getAParameterCandidate(int i) { result.asSink() = this.getArg(i) }
|
||||
|
||||
/** Gets the API node for a parameter of this invocation. */
|
||||
Node getAParameter() { result = this.getParameter(_) }
|
||||
|
||||
/** Gets the object that this method-call is being called on, if this is a method-call */
|
||||
Node getSelfParameter() {
|
||||
result.getARhs() = this.(DataFlow::MethodCallNode).getObject() and
|
||||
result.asSink() = this.(DataFlow::MethodCallNode).getObject() and
|
||||
result = callee.getSelfParameter()
|
||||
}
|
||||
|
||||
@@ -346,13 +435,13 @@ module API {
|
||||
|
||||
pragma[noinline]
|
||||
private Node getAKeywordParameterCandidate(string name) {
|
||||
result.getARhs() = this.getArgByName(name)
|
||||
result.asSink() = this.getArgByName(name)
|
||||
}
|
||||
|
||||
/** Gets the API node for the return value of this call. */
|
||||
Node getReturn() {
|
||||
result = callee.getReturn() and
|
||||
result.getAnImmediateUse() = this
|
||||
result.asSource() = this
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -591,8 +680,16 @@ module API {
|
||||
or
|
||||
// Subclassing a node
|
||||
lbl = Label::subclass() and
|
||||
exists(DataFlow::Node superclass | pred.flowsTo(superclass) |
|
||||
ref.asExpr().(PY::ClassExpr).getABase() = superclass.asExpr()
|
||||
exists(PY::ClassExpr clsExpr, DataFlow::Node superclass | pred.flowsTo(superclass) |
|
||||
clsExpr.getABase() = superclass.asExpr() and
|
||||
// Potentially a class decorator could do anything, but we assume they are
|
||||
// "benign" and let subclasses edges flow through anyway.
|
||||
// see example in https://github.com/django/django/blob/c2250cfb80e27cdf8d098428824da2800a18cadf/tests/auth_tests/test_views.py#L40-L46
|
||||
(
|
||||
ref.asExpr() = clsExpr
|
||||
or
|
||||
ref.asExpr() = clsExpr.getADecoratorCall()
|
||||
)
|
||||
)
|
||||
or
|
||||
// awaiting
|
||||
|
||||
@@ -1000,11 +1000,22 @@ class RegExpBackRef extends RegExpTerm, TRegExpBackRef {
|
||||
|
||||
/** Gets the capture group this back reference refers to. */
|
||||
RegExpGroup getGroup() {
|
||||
result.getLiteral() = this.getLiteral() and
|
||||
(
|
||||
result.getNumber() = this.getNumber() or
|
||||
result.getName() = this.getName()
|
||||
)
|
||||
this.hasLiteralAndNumber(result.getLiteral(), result.getNumber()) or
|
||||
this.hasLiteralAndName(result.getLiteral(), result.getName())
|
||||
}
|
||||
|
||||
/** Join-order helper for `getGroup`. */
|
||||
pragma[nomagic]
|
||||
private predicate hasLiteralAndNumber(RegExpLiteral literal, int number) {
|
||||
literal = this.getLiteral() and
|
||||
number = this.getNumber()
|
||||
}
|
||||
|
||||
/** Join-order helper for `getGroup`. */
|
||||
pragma[nomagic]
|
||||
private predicate hasLiteralAndName(RegExpLiteral literal, string name) {
|
||||
literal = this.getLiteral() and
|
||||
name = this.getName()
|
||||
}
|
||||
|
||||
override RegExpTerm getChild(int i) { none() }
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -527,16 +527,55 @@ class StarPatternElementNode extends Node, TStarPatternElementNode {
|
||||
override Location getLocation() { result = consumer.getLocation() }
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a node that controls whether other nodes are evaluated.
|
||||
*
|
||||
* In the base case, this is the last node of `conditionBlock`, and `flipped` is `false`.
|
||||
* This definition accounts for (short circuting) `and`- and `or`-expressions, as the structure
|
||||
* of basic blocks will reflect their semantics.
|
||||
*
|
||||
* However, in the program
|
||||
* ```python
|
||||
* if not is_safe(path):
|
||||
* return
|
||||
* ```
|
||||
* the last node in the `ConditionBlock` is `not is_safe(path)`.
|
||||
*
|
||||
* We would like to consider also `is_safe(path)` a guard node, albeit with `flipped` being `true`.
|
||||
* Thus we recurse through `not`-expressions.
|
||||
*/
|
||||
ControlFlowNode guardNode(ConditionBlock conditionBlock, boolean flipped) {
|
||||
// Base case: the last node truly does determine which successor is chosen
|
||||
result = conditionBlock.getLastNode() and
|
||||
flipped = false
|
||||
or
|
||||
// Recursive case: if a guard node is a `not`-expression,
|
||||
// the operand is also a guard node, but with inverted polarity.
|
||||
exists(UnaryExprNode notNode |
|
||||
result = notNode.getOperand() and
|
||||
notNode.getNode().getOp() instanceof Not
|
||||
|
|
||||
notNode = guardNode(conditionBlock, flipped.booleanNot())
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* A node that controls whether other nodes are evaluated.
|
||||
*
|
||||
* The field `flipped` allows us to match `GuardNode`s underneath
|
||||
* `not`-expressions and still choose the appropriate branch.
|
||||
*/
|
||||
class GuardNode extends ControlFlowNode {
|
||||
ConditionBlock conditionBlock;
|
||||
boolean flipped;
|
||||
|
||||
GuardNode() { this = conditionBlock.getLastNode() }
|
||||
GuardNode() { this = guardNode(conditionBlock, flipped) }
|
||||
|
||||
/** Holds if this guard controls block `b` upon evaluating to `branch`. */
|
||||
predicate controlsBlock(BasicBlock b, boolean branch) { conditionBlock.controls(b, branch) }
|
||||
predicate controlsBlock(BasicBlock b, boolean branch) {
|
||||
branch in [true, false] and
|
||||
conditionBlock.controls(b, branch.booleanXor(flipped))
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -9,7 +9,7 @@ class UnitTestClass extends TestScope, Class {
|
||||
testCaseString.matches("%TestCase") and
|
||||
testCaseClass = any(API::Node mod).getMember(testCaseString)
|
||||
|
|
||||
this.getParent() = testCaseClass.getASubclass*().getAnImmediateUse().asExpr()
|
||||
this.getParent() = testCaseClass.getASubclass*().asSource().asExpr()
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -243,9 +243,7 @@ module AiohttpWebModel {
|
||||
|
||||
/** A class that has a super-type which is an aiohttp.web View class. */
|
||||
class AiohttpViewClassFromSuperClass extends AiohttpViewClass {
|
||||
AiohttpViewClassFromSuperClass() {
|
||||
this.getParent() = View::subclassRef().getAnImmediateUse().asExpr()
|
||||
}
|
||||
AiohttpViewClassFromSuperClass() { this.getParent() = View::subclassRef().asSource().asExpr() }
|
||||
}
|
||||
|
||||
/** A class that is used in a route-setup, therefore being considered an aiohttp.web View class. */
|
||||
@@ -628,7 +626,8 @@ module AiohttpWebModel {
|
||||
// and just go with the LHS
|
||||
this.asCfgNode() = subscript
|
||||
|
|
||||
subscript.getObject() = aiohttpResponseInstance().getMember("cookies").getAUse().asCfgNode() and
|
||||
subscript.getObject() =
|
||||
aiohttpResponseInstance().getMember("cookies").getAValueReachableFromSource().asCfgNode() and
|
||||
value.asCfgNode() = subscript.(DefinitionNode).getValue() and
|
||||
index.asCfgNode() = subscript.getIndex()
|
||||
)
|
||||
@@ -686,8 +685,8 @@ private module AiohttpClientModel {
|
||||
DataFlow::Node disablingNode, DataFlow::Node argumentOrigin
|
||||
) {
|
||||
exists(API::Node param | param = this.getKeywordParameter(["ssl", "verify_ssl"]) |
|
||||
disablingNode = param.getARhs() and
|
||||
argumentOrigin = param.getAValueReachingRhs() and
|
||||
disablingNode = param.asSink() and
|
||||
argumentOrigin = param.getAValueReachingSink() and
|
||||
// aiohttp.client treats `None` as the default and all other "falsey" values as `False`.
|
||||
argumentOrigin.asExpr().(ImmutableLiteral).booleanValue() = false and
|
||||
not argumentOrigin.asExpr() instanceof None
|
||||
|
||||
@@ -53,7 +53,7 @@ private module Aiomysql {
|
||||
class CursorExecuteCall extends SqlConstruction::Range, API::CallNode {
|
||||
CursorExecuteCall() { this = cursor().getMember("execute").getACall() }
|
||||
|
||||
override DataFlow::Node getSql() { result = this.getParameter(0, "operation").getARhs() }
|
||||
override DataFlow::Node getSql() { result = this.getParameter(0, "operation").asSink() }
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -63,7 +63,7 @@ private module Aiomysql {
|
||||
class AwaitedCursorExecuteCall extends SqlExecution::Range {
|
||||
CursorExecuteCall executeCall;
|
||||
|
||||
AwaitedCursorExecuteCall() { this = executeCall.getReturn().getAwaited().getAnImmediateUse() }
|
||||
AwaitedCursorExecuteCall() { this = executeCall.getReturn().getAwaited().asSource() }
|
||||
|
||||
override DataFlow::Node getSql() { result = executeCall.getSql() }
|
||||
}
|
||||
@@ -94,7 +94,7 @@ private module Aiomysql {
|
||||
class SAConnectionExecuteCall extends SqlConstruction::Range, API::CallNode {
|
||||
SAConnectionExecuteCall() { this = saConnection().getMember("execute").getACall() }
|
||||
|
||||
override DataFlow::Node getSql() { result = this.getParameter(0, "query").getARhs() }
|
||||
override DataFlow::Node getSql() { result = this.getParameter(0, "query").asSink() }
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -104,7 +104,7 @@ private module Aiomysql {
|
||||
class AwaitedSAConnectionExecuteCall extends SqlExecution::Range {
|
||||
SAConnectionExecuteCall execute;
|
||||
|
||||
AwaitedSAConnectionExecuteCall() { this = execute.getReturn().getAwaited().getAnImmediateUse() }
|
||||
AwaitedSAConnectionExecuteCall() { this = execute.getReturn().getAwaited().asSource() }
|
||||
|
||||
override DataFlow::Node getSql() { result = execute.getSql() }
|
||||
}
|
||||
|
||||
@@ -53,7 +53,7 @@ private module Aiopg {
|
||||
class CursorExecuteCall extends SqlConstruction::Range, API::CallNode {
|
||||
CursorExecuteCall() { this = cursor().getMember("execute").getACall() }
|
||||
|
||||
override DataFlow::Node getSql() { result = this.getParameter(0, "operation").getARhs() }
|
||||
override DataFlow::Node getSql() { result = this.getParameter(0, "operation").asSink() }
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -63,7 +63,7 @@ private module Aiopg {
|
||||
class AwaitedCursorExecuteCall extends SqlExecution::Range {
|
||||
CursorExecuteCall execute;
|
||||
|
||||
AwaitedCursorExecuteCall() { this = execute.getReturn().getAwaited().getAnImmediateUse() }
|
||||
AwaitedCursorExecuteCall() { this = execute.getReturn().getAwaited().asSource() }
|
||||
|
||||
override DataFlow::Node getSql() { result = execute.getSql() }
|
||||
}
|
||||
@@ -90,7 +90,7 @@ private module Aiopg {
|
||||
class SAConnectionExecuteCall extends SqlConstruction::Range, API::CallNode {
|
||||
SAConnectionExecuteCall() { this = saConnection().getMember("execute").getACall() }
|
||||
|
||||
override DataFlow::Node getSql() { result = this.getParameter(0, "query").getARhs() }
|
||||
override DataFlow::Node getSql() { result = this.getParameter(0, "query").asSink() }
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -100,7 +100,7 @@ private module Aiopg {
|
||||
class AwaitedSAConnectionExecuteCall extends SqlExecution::Range {
|
||||
SAConnectionExecuteCall excute;
|
||||
|
||||
AwaitedSAConnectionExecuteCall() { this = excute.getReturn().getAwaited().getAnImmediateUse() }
|
||||
AwaitedSAConnectionExecuteCall() { this = excute.getReturn().getAwaited().asSource() }
|
||||
|
||||
override DataFlow::Node getSql() { result = excute.getSql() }
|
||||
}
|
||||
|
||||
@@ -61,7 +61,7 @@ private module Asyncpg {
|
||||
this = ModelOutput::getATypeNode("asyncpg", "Connection").getMember("cursor").getACall()
|
||||
}
|
||||
|
||||
override DataFlow::Node getSql() { result = this.getParameter(0, "query").getARhs() }
|
||||
override DataFlow::Node getSql() { result = this.getParameter(0, "query").asSink() }
|
||||
}
|
||||
|
||||
/** The creation of a `Cursor` executes the associated query. */
|
||||
@@ -71,14 +71,14 @@ private module Asyncpg {
|
||||
CursorCreation() {
|
||||
exists(CursorConstruction c |
|
||||
sql = c.getSql() and
|
||||
this = c.getReturn().getAwaited().getAnImmediateUse()
|
||||
this = c.getReturn().getAwaited().asSource()
|
||||
)
|
||||
or
|
||||
exists(API::CallNode prepareCall |
|
||||
prepareCall =
|
||||
ModelOutput::getATypeNode("asyncpg", "Connection").getMember("prepare").getACall()
|
||||
|
|
||||
sql = prepareCall.getParameter(0, "query").getARhs() and
|
||||
sql = prepareCall.getParameter(0, "query").asSink() and
|
||||
this =
|
||||
prepareCall
|
||||
.getReturn()
|
||||
@@ -86,7 +86,7 @@ private module Asyncpg {
|
||||
.getMember("cursor")
|
||||
.getReturn()
|
||||
.getAwaited()
|
||||
.getAnImmediateUse()
|
||||
.asSource()
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -164,7 +164,7 @@ private module CryptodomeModel {
|
||||
.getMember("Cipher")
|
||||
.getMember(cipherName)
|
||||
.getMember(modeName)
|
||||
.getAUse()
|
||||
.getAValueReachableFromSource()
|
||||
|
|
||||
result = modeName.splitAt("_", 1)
|
||||
)
|
||||
|
||||
@@ -144,12 +144,10 @@ private module CryptographyModel {
|
||||
DataFlow::Node getCurveArg() { result in [this.getArg(0), this.getArgByName("curve")] }
|
||||
|
||||
override int getKeySizeWithOrigin(DataFlow::Node origin) {
|
||||
exists(API::Node n |
|
||||
n = Ecc::predefinedCurveClass(result) and origin = n.getAnImmediateUse()
|
||||
|
|
||||
this.getCurveArg() = n.getAUse()
|
||||
exists(API::Node n | n = Ecc::predefinedCurveClass(result) and origin = n.asSource() |
|
||||
this.getCurveArg() = n.getAValueReachableFromSource()
|
||||
or
|
||||
this.getCurveArg() = n.getReturn().getAUse()
|
||||
this.getCurveArg() = n.getReturn().getAValueReachableFromSource()
|
||||
)
|
||||
}
|
||||
|
||||
@@ -191,12 +189,12 @@ private module CryptographyModel {
|
||||
.getMember("ciphers")
|
||||
.getMember("Cipher")
|
||||
.getACall() and
|
||||
algorithmClassRef(algorithmName).getReturn().getAUse() in [
|
||||
algorithmClassRef(algorithmName).getReturn().getAValueReachableFromSource() in [
|
||||
call.getArg(0), call.getArgByName("algorithm")
|
||||
] and
|
||||
exists(DataFlow::Node modeArg | modeArg in [call.getArg(1), call.getArgByName("mode")] |
|
||||
if modeArg = modeClassRef(_).getReturn().getAUse()
|
||||
then modeArg = modeClassRef(modeName).getReturn().getAUse()
|
||||
if modeArg = modeClassRef(_).getReturn().getAValueReachableFromSource()
|
||||
then modeArg = modeClassRef(modeName).getReturn().getAValueReachableFromSource()
|
||||
else modeName = "<None or unknown>"
|
||||
)
|
||||
)
|
||||
@@ -254,7 +252,7 @@ private module CryptographyModel {
|
||||
.getMember("hashes")
|
||||
.getMember("Hash")
|
||||
.getACall() and
|
||||
algorithmClassRef(algorithmName).getReturn().getAUse() in [
|
||||
algorithmClassRef(algorithmName).getReturn().getAValueReachableFromSource() in [
|
||||
call.getArg(0), call.getArgByName("algorithm")
|
||||
]
|
||||
)
|
||||
|
||||
@@ -562,7 +562,7 @@ module PrivateDjango {
|
||||
|
||||
/** A `django.db.connection` is a PEP249 compliant DB connection. */
|
||||
class DjangoDbConnection extends PEP249::Connection::InstanceSource {
|
||||
DjangoDbConnection() { this = connection().getAnImmediateUse() }
|
||||
DjangoDbConnection() { this = connection().asSource() }
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
@@ -869,7 +869,7 @@ module PrivateDjango {
|
||||
|
||||
/** Gets the (AST) class of the Django model class `modelClass`. */
|
||||
Class getModelClassClass(API::Node modelClass) {
|
||||
result.getParent() = modelClass.getAnImmediateUse().asExpr() and
|
||||
result.getParent() = modelClass.asSource().asExpr() and
|
||||
modelClass = Model::subclassRef()
|
||||
}
|
||||
|
||||
@@ -2202,9 +2202,7 @@ module PrivateDjango {
|
||||
* thereby handling user input.
|
||||
*/
|
||||
class DjangoFormClass extends Class, SelfRefMixin {
|
||||
DjangoFormClass() {
|
||||
this.getParent() = Django::Forms::Form::subclassRef().getAnImmediateUse().asExpr()
|
||||
}
|
||||
DjangoFormClass() { this.getParent() = Django::Forms::Form::subclassRef().asSource().asExpr() }
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -2237,7 +2235,7 @@ module PrivateDjango {
|
||||
*/
|
||||
class DjangoFormFieldClass extends Class {
|
||||
DjangoFormFieldClass() {
|
||||
this.getParent() = Django::Forms::Field::subclassRef().getAnImmediateUse().asExpr()
|
||||
this.getParent() = Django::Forms::Field::subclassRef().asSource().asExpr()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2340,7 +2338,7 @@ module PrivateDjango {
|
||||
*/
|
||||
class DjangoViewClassFromSuperClass extends DjangoViewClass {
|
||||
DjangoViewClassFromSuperClass() {
|
||||
this.getParent() = Django::Views::View::subclassRef().getAnImmediateUse().asExpr()
|
||||
this.getParent() = Django::Views::View::subclassRef().asSource().asExpr()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2743,7 +2741,7 @@ module PrivateDjango {
|
||||
.getMember("utils")
|
||||
.getMember("log")
|
||||
.getMember("request_logger")
|
||||
.getAnImmediateUse()
|
||||
.asSource()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2801,7 +2799,7 @@ module PrivateDjango {
|
||||
.getMember("decorators")
|
||||
.getMember("csrf")
|
||||
.getMember(decoratorName)
|
||||
.getAUse() and
|
||||
.getAValueReachableFromSource() and
|
||||
this.asExpr() = function.getADecorator()
|
||||
}
|
||||
|
||||
|
||||
@@ -179,7 +179,7 @@ private module FabricV2 {
|
||||
DataFlow::ParameterNode {
|
||||
FabricTaskFirstParamConnectionInstance() {
|
||||
exists(Function func |
|
||||
func.getADecorator() = Fabric::Tasks::task().getAUse().asExpr() and
|
||||
func.getADecorator() = Fabric::Tasks::task().getAValueReachableFromSource().asExpr() and
|
||||
this.getParameter() = func.getArg(0)
|
||||
)
|
||||
}
|
||||
|
||||
@@ -90,7 +90,8 @@ private module FastApi {
|
||||
private class PydanticModelRequestHandlerParam extends Pydantic::BaseModel::InstanceSource,
|
||||
DataFlow::ParameterNode {
|
||||
PydanticModelRequestHandlerParam() {
|
||||
this.getParameter().getAnnotation() = Pydantic::BaseModel::subclassRef().getAUse().asExpr() and
|
||||
this.getParameter().getAnnotation() =
|
||||
Pydantic::BaseModel::subclassRef().getAValueReachableFromSource().asExpr() and
|
||||
any(FastApiRouteSetup rs).getARequestHandler().getArgByName(_) = this.getParameter()
|
||||
}
|
||||
}
|
||||
@@ -104,7 +105,8 @@ private module FastApi {
|
||||
private class WebSocketRequestHandlerParam extends Starlette::WebSocket::InstanceSource,
|
||||
DataFlow::ParameterNode {
|
||||
WebSocketRequestHandlerParam() {
|
||||
this.getParameter().getAnnotation() = Starlette::WebSocket::classRef().getAUse().asExpr() and
|
||||
this.getParameter().getAnnotation() =
|
||||
Starlette::WebSocket::classRef().getAValueReachableFromSource().asExpr() and
|
||||
any(FastApiRouteSetup rs).getARequestHandler().getArgByName(_) = this.getParameter()
|
||||
}
|
||||
}
|
||||
@@ -165,8 +167,8 @@ private module FastApi {
|
||||
// user-defined subclasses
|
||||
exists(Class cls, API::Node base |
|
||||
base = getModeledResponseClass(_).getASubclass*() and
|
||||
cls.getABase() = base.getAUse().asExpr() and
|
||||
responseClass.getAnImmediateUse().asExpr() = cls.getParent()
|
||||
cls.getABase() = base.getAValueReachableFromSource().asExpr() and
|
||||
responseClass.asSource().asExpr() = cls.getParent()
|
||||
|
|
||||
exists(Assign assign | assign = cls.getAStmt() |
|
||||
assign.getATarget().(Name).getId() = "media_type" and
|
||||
@@ -257,7 +259,7 @@ private module FastApi {
|
||||
|
||||
override string getMimetypeDefault() {
|
||||
exists(API::Node responseClass |
|
||||
responseClass.getAUse() = routeSetup.getResponseClassArg() and
|
||||
responseClass.getAValueReachableFromSource() = routeSetup.getResponseClassArg() and
|
||||
result = getDefaultMimeType(responseClass)
|
||||
)
|
||||
or
|
||||
@@ -274,7 +276,7 @@ private module FastApi {
|
||||
FileSystemAccess::Range {
|
||||
FastApiRequestHandlerFileResponseReturn() {
|
||||
exists(API::Node responseClass |
|
||||
responseClass.getAUse() = routeSetup.getResponseClassArg() and
|
||||
responseClass.getAValueReachableFromSource() = routeSetup.getResponseClassArg() and
|
||||
responseClass = getModeledResponseClass("FileResponse").getASubclass*()
|
||||
)
|
||||
}
|
||||
@@ -292,7 +294,7 @@ private module FastApi {
|
||||
HTTP::Server::HttpRedirectResponse::Range {
|
||||
FastApiRequestHandlerRedirectReturn() {
|
||||
exists(API::Node responseClass |
|
||||
responseClass.getAUse() = routeSetup.getResponseClassArg() and
|
||||
responseClass.getAValueReachableFromSource() = routeSetup.getResponseClassArg() and
|
||||
responseClass = getModeledResponseClass("RedirectResponse").getASubclass*()
|
||||
)
|
||||
}
|
||||
@@ -311,7 +313,7 @@ private module FastApi {
|
||||
class RequestHandlerParam extends InstanceSource, DataFlow::ParameterNode {
|
||||
RequestHandlerParam() {
|
||||
this.getParameter().getAnnotation() =
|
||||
getModeledResponseClass(_).getASubclass*().getAUse().asExpr() and
|
||||
getModeledResponseClass(_).getASubclass*().getAValueReachableFromSource().asExpr() and
|
||||
any(FastApiRouteSetup rs).getARequestHandler().getArgByName(_) = this.getParameter()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -195,7 +195,7 @@ module Flask {
|
||||
|
||||
FlaskViewClass() {
|
||||
api_node = Views::View::subclassRef() and
|
||||
this.getParent() = api_node.getAnImmediateUse().asExpr()
|
||||
this.getParent() = api_node.asSource().asExpr()
|
||||
}
|
||||
|
||||
/** Gets a function that could handle incoming requests, if any. */
|
||||
@@ -220,7 +220,7 @@ module Flask {
|
||||
class FlaskMethodViewClass extends FlaskViewClass {
|
||||
FlaskMethodViewClass() {
|
||||
api_node = Views::MethodView::subclassRef() and
|
||||
this.getParent() = api_node.getAnImmediateUse().asExpr()
|
||||
this.getParent() = api_node.asSource().asExpr()
|
||||
}
|
||||
|
||||
override Function getARequestHandler() {
|
||||
@@ -305,7 +305,7 @@ module Flask {
|
||||
)
|
||||
or
|
||||
exists(FlaskViewClass vc |
|
||||
this.getViewArg() = vc.asViewResult().getAUse() and
|
||||
this.getViewArg() = vc.asViewResult().getAValueReachableFromSource() and
|
||||
result = vc.getARequestHandler()
|
||||
)
|
||||
}
|
||||
@@ -339,7 +339,7 @@ module Flask {
|
||||
*/
|
||||
private class FlaskRequestSource extends RemoteFlowSource::Range {
|
||||
FlaskRequestSource() {
|
||||
this = request().getAUse() and
|
||||
this = request().getAValueReachableFromSource() and
|
||||
not any(Import imp).contains(this.asExpr()) and
|
||||
not exists(ControlFlowNode def | this.asVar().getSourceVariable().hasDefiningNode(def) |
|
||||
any(Import imp).contains(def.getNode())
|
||||
@@ -357,7 +357,7 @@ module Flask {
|
||||
private class InstanceTaintSteps extends InstanceTaintStepsHelper {
|
||||
InstanceTaintSteps() { this = "flask.Request" }
|
||||
|
||||
override DataFlow::Node getInstance() { result = request().getAUse() }
|
||||
override DataFlow::Node getInstance() { result = request().getAValueReachableFromSource() }
|
||||
|
||||
override string getAttributeName() {
|
||||
result in [
|
||||
@@ -404,7 +404,7 @@ module Flask {
|
||||
|
||||
private class RequestAttrMultiDict extends Werkzeug::MultiDict::InstanceSource {
|
||||
RequestAttrMultiDict() {
|
||||
this = request().getMember(["args", "values", "form", "files"]).getAnImmediateUse()
|
||||
this = request().getMember(["args", "values", "form", "files"]).asSource()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -415,26 +415,25 @@ module Flask {
|
||||
// be able to do something more structured for providing modeling of the members
|
||||
// of a container-object.
|
||||
exists(API::Node files | files = request().getMember("files") |
|
||||
this.asCfgNode().(SubscriptNode).getObject() = files.getAUse().asCfgNode()
|
||||
this.asCfgNode().(SubscriptNode).getObject() =
|
||||
files.getAValueReachableFromSource().asCfgNode()
|
||||
or
|
||||
this = files.getMember("get").getACall()
|
||||
or
|
||||
this.asCfgNode().(SubscriptNode).getObject() =
|
||||
files.getMember("getlist").getReturn().getAUse().asCfgNode()
|
||||
files.getMember("getlist").getReturn().getAValueReachableFromSource().asCfgNode()
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/** An `Headers` instance that originates from a flask request. */
|
||||
private class FlaskRequestHeadersInstances extends Werkzeug::Headers::InstanceSource {
|
||||
FlaskRequestHeadersInstances() { this = request().getMember("headers").getAnImmediateUse() }
|
||||
FlaskRequestHeadersInstances() { this = request().getMember("headers").asSource() }
|
||||
}
|
||||
|
||||
/** An `Authorization` instance that originates from a flask request. */
|
||||
private class FlaskRequestAuthorizationInstances extends Werkzeug::Authorization::InstanceSource {
|
||||
FlaskRequestAuthorizationInstances() {
|
||||
this = request().getMember("authorization").getAnImmediateUse()
|
||||
}
|
||||
FlaskRequestAuthorizationInstances() { this = request().getMember("authorization").asSource() }
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
@@ -574,6 +573,6 @@ module Flask {
|
||||
* - https://flask.palletsprojects.com/en/2.0.x/logging/
|
||||
*/
|
||||
private class FlaskLogger extends Stdlib::Logger::InstanceSource {
|
||||
FlaskLogger() { this = FlaskApp::instance().getMember("logger").getAnImmediateUse() }
|
||||
FlaskLogger() { this = FlaskApp::instance().getMember("logger").asSource() }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -35,7 +35,7 @@ private module FlaskSqlAlchemy {
|
||||
/** Access on a DB resulting in an Engine */
|
||||
private class DbEngine extends SqlAlchemy::Engine::InstanceSource {
|
||||
DbEngine() {
|
||||
this = dbInstance().getMember("engine").getAnImmediateUse()
|
||||
this = dbInstance().getMember("engine").asSource()
|
||||
or
|
||||
this = dbInstance().getMember("get_engine").getACall()
|
||||
}
|
||||
@@ -44,7 +44,7 @@ private module FlaskSqlAlchemy {
|
||||
/** Access on a DB resulting in a Session */
|
||||
private class DbSession extends SqlAlchemy::Session::InstanceSource {
|
||||
DbSession() {
|
||||
this = dbInstance().getMember("session").getAnImmediateUse()
|
||||
this = dbInstance().getMember("session").asSource()
|
||||
or
|
||||
this = dbInstance().getMember("create_session").getReturn().getACall()
|
||||
or
|
||||
|
||||
@@ -44,8 +44,8 @@ private module HttpxModel {
|
||||
override predicate disablesCertificateValidation(
|
||||
DataFlow::Node disablingNode, DataFlow::Node argumentOrigin
|
||||
) {
|
||||
disablingNode = this.getKeywordParameter("verify").getARhs() and
|
||||
argumentOrigin = this.getKeywordParameter("verify").getAValueReachingRhs() and
|
||||
disablingNode = this.getKeywordParameter("verify").asSink() and
|
||||
argumentOrigin = this.getKeywordParameter("verify").getAValueReachingSink() and
|
||||
// unlike `requests`, httpx treats `None` as turning off verify (and not as the default)
|
||||
argumentOrigin.asExpr().(ImmutableLiteral).booleanValue() = false
|
||||
// TODO: Handling of insecure SSLContext passed to verify argument
|
||||
@@ -89,8 +89,8 @@ private module HttpxModel {
|
||||
constructor = classRef().getACall() and
|
||||
this = constructor.getReturn().getMember(methodName).getACall()
|
||||
|
|
||||
disablingNode = constructor.getKeywordParameter("verify").getARhs() and
|
||||
argumentOrigin = constructor.getKeywordParameter("verify").getAValueReachingRhs() and
|
||||
disablingNode = constructor.getKeywordParameter("verify").asSink() and
|
||||
argumentOrigin = constructor.getKeywordParameter("verify").getAValueReachingSink() and
|
||||
// unlike `requests`, httpx treats `None` as turning off verify (and not as the default)
|
||||
argumentOrigin.asExpr().(ImmutableLiteral).booleanValue() = false
|
||||
// TODO: Handling of insecure SSLContext passed to verify argument
|
||||
|
||||
@@ -39,7 +39,8 @@ private module Invoke {
|
||||
result = InvokeModule::Context::ContextClass::classRef().getACall()
|
||||
or
|
||||
exists(Function func |
|
||||
func.getADecorator() = invoke().getMember("task").getAUse().asExpr() and
|
||||
func.getADecorator() =
|
||||
invoke().getMember("task").getAValueReachableFromSource().asExpr() and
|
||||
result.(DataFlow::ParameterNode).getParameter() = func.getArg(0)
|
||||
)
|
||||
)
|
||||
|
||||
@@ -141,17 +141,18 @@ private module Lxml {
|
||||
// resolve_entities has default True
|
||||
not exists(this.getArgByName("resolve_entities"))
|
||||
or
|
||||
this.getKeywordParameter("resolve_entities").getAValueReachingRhs().asExpr() = any(True t)
|
||||
this.getKeywordParameter("resolve_entities").getAValueReachingSink().asExpr() =
|
||||
any(True t)
|
||||
)
|
||||
or
|
||||
kind.isXmlBomb() and
|
||||
this.getKeywordParameter("huge_tree").getAValueReachingRhs().asExpr() = any(True t) and
|
||||
not this.getKeywordParameter("resolve_entities").getAValueReachingRhs().asExpr() =
|
||||
this.getKeywordParameter("huge_tree").getAValueReachingSink().asExpr() = any(True t) and
|
||||
not this.getKeywordParameter("resolve_entities").getAValueReachingSink().asExpr() =
|
||||
any(False t)
|
||||
or
|
||||
kind.isDtdRetrieval() and
|
||||
this.getKeywordParameter("load_dtd").getAValueReachingRhs().asExpr() = any(True t) and
|
||||
this.getKeywordParameter("no_network").getAValueReachingRhs().asExpr() = any(False t)
|
||||
this.getKeywordParameter("load_dtd").getAValueReachingSink().asExpr() = any(True t) and
|
||||
this.getKeywordParameter("no_network").getAValueReachingSink().asExpr() = any(False t)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -318,11 +319,11 @@ private module Lxml {
|
||||
kind.isXxe()
|
||||
or
|
||||
kind.isXmlBomb() and
|
||||
this.getKeywordParameter("huge_tree").getAValueReachingRhs().asExpr() = any(True t)
|
||||
this.getKeywordParameter("huge_tree").getAValueReachingSink().asExpr() = any(True t)
|
||||
or
|
||||
kind.isDtdRetrieval() and
|
||||
this.getKeywordParameter("load_dtd").getAValueReachingRhs().asExpr() = any(True t) and
|
||||
this.getKeywordParameter("no_network").getAValueReachingRhs().asExpr() = any(False t)
|
||||
this.getKeywordParameter("load_dtd").getAValueReachingSink().asExpr() = any(True t) and
|
||||
this.getKeywordParameter("no_network").getAValueReachingSink().asExpr() = any(False t)
|
||||
}
|
||||
|
||||
override predicate mayExecuteInput() { none() }
|
||||
|
||||
@@ -61,8 +61,8 @@ private module Requests {
|
||||
override predicate disablesCertificateValidation(
|
||||
DataFlow::Node disablingNode, DataFlow::Node argumentOrigin
|
||||
) {
|
||||
disablingNode = this.getKeywordParameter("verify").getARhs() and
|
||||
argumentOrigin = this.getKeywordParameter("verify").getAValueReachingRhs() and
|
||||
disablingNode = this.getKeywordParameter("verify").asSink() and
|
||||
argumentOrigin = this.getKeywordParameter("verify").getAValueReachingSink() and
|
||||
// requests treats `None` as the default and all other "falsey" values as `False`.
|
||||
argumentOrigin.asExpr().(ImmutableLiteral).booleanValue() = false and
|
||||
not argumentOrigin.asExpr() instanceof None
|
||||
|
||||
@@ -115,7 +115,7 @@ private module RestFramework {
|
||||
*/
|
||||
class RestFrameworkApiViewClass extends PrivateDjango::DjangoViewClassFromSuperClass {
|
||||
RestFrameworkApiViewClass() {
|
||||
this.getParent() = any(ModeledApiViewClasses c).getASubclass*().getAnImmediateUse().asExpr()
|
||||
this.getParent() = any(ModeledApiViewClasses c).getASubclass*().asSource().asExpr()
|
||||
}
|
||||
|
||||
override Function getARequestHandler() {
|
||||
|
||||
@@ -44,7 +44,7 @@ private module RuamelYaml {
|
||||
API::moduleImport("ruamel")
|
||||
.getMember("yaml")
|
||||
.getMember(["SafeLoader", "BaseLoader", "CSafeLoader", "CBaseLoader"])
|
||||
.getAUse()
|
||||
.getAValueReachableFromSource()
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -274,7 +274,7 @@ module Stdlib {
|
||||
ClassInstantiation() {
|
||||
this = subclassRef().getACall()
|
||||
or
|
||||
this = API::moduleImport("logging").getMember("root").getAnImmediateUse()
|
||||
this = API::moduleImport("logging").getMember("root").asSource()
|
||||
or
|
||||
this = API::moduleImport("logging").getMember("getLogger").getACall()
|
||||
}
|
||||
@@ -1727,15 +1727,16 @@ private module StdlibPrivate {
|
||||
private DataFlow::TypeTrackingNode fieldList(DataFlow::TypeTracker t) {
|
||||
t.start() and
|
||||
// TODO: Should have better handling of subscripting
|
||||
result.asCfgNode().(SubscriptNode).getObject() = instance().getAUse().asCfgNode()
|
||||
result.asCfgNode().(SubscriptNode).getObject() =
|
||||
instance().getAValueReachableFromSource().asCfgNode()
|
||||
or
|
||||
exists(DataFlow::TypeTracker t2 | result = fieldList(t2).track(t2, t))
|
||||
}
|
||||
|
||||
/** Gets a reference to a list of fields. */
|
||||
DataFlow::Node fieldList() {
|
||||
result = getlistResult().getAUse() or
|
||||
result = getvalueResult().getAUse() or
|
||||
result = getlistResult().getAValueReachableFromSource() or
|
||||
result = getvalueResult().getAValueReachableFromSource() or
|
||||
fieldList(DataFlow::TypeTracker::end()).flowsTo(result)
|
||||
}
|
||||
|
||||
@@ -1744,16 +1745,16 @@ private module StdlibPrivate {
|
||||
t.start() and
|
||||
// TODO: Should have better handling of subscripting
|
||||
result.asCfgNode().(SubscriptNode).getObject() =
|
||||
[instance().getAUse(), fieldList()].asCfgNode()
|
||||
[instance().getAValueReachableFromSource(), fieldList()].asCfgNode()
|
||||
or
|
||||
exists(DataFlow::TypeTracker t2 | result = field(t2).track(t2, t))
|
||||
}
|
||||
|
||||
/** Gets a reference to a field. */
|
||||
DataFlow::Node field() {
|
||||
result = getfirstResult().getAUse()
|
||||
result = getfirstResult().getAValueReachableFromSource()
|
||||
or
|
||||
result = getvalueResult().getAUse()
|
||||
result = getvalueResult().getAValueReachableFromSource()
|
||||
or
|
||||
field(DataFlow::TypeTracker::end()).flowsTo(result)
|
||||
}
|
||||
@@ -1762,20 +1763,23 @@ private module StdlibPrivate {
|
||||
override predicate step(DataFlow::Node nodeFrom, DataFlow::Node nodeTo) {
|
||||
// Methods
|
||||
nodeFrom = nodeTo.(DataFlow::AttrRead).getObject() and
|
||||
nodeFrom = instance().getAUse() and
|
||||
nodeTo = [getvalueRef(), getfirstRef(), getlistRef()].getAUse()
|
||||
nodeFrom = instance().getAValueReachableFromSource() and
|
||||
nodeTo = [getvalueRef(), getfirstRef(), getlistRef()].getAValueReachableFromSource()
|
||||
or
|
||||
nodeFrom.asCfgNode() = nodeTo.asCfgNode().(CallNode).getFunction() and
|
||||
(
|
||||
nodeFrom = getvalueRef().getAUse() and nodeTo = getvalueResult().getAnImmediateUse()
|
||||
nodeFrom = getvalueRef().getAValueReachableFromSource() and
|
||||
nodeTo = getvalueResult().asSource()
|
||||
or
|
||||
nodeFrom = getfirstRef().getAUse() and nodeTo = getfirstResult().getAnImmediateUse()
|
||||
nodeFrom = getfirstRef().getAValueReachableFromSource() and
|
||||
nodeTo = getfirstResult().asSource()
|
||||
or
|
||||
nodeFrom = getlistRef().getAUse() and nodeTo = getlistResult().getAnImmediateUse()
|
||||
nodeFrom = getlistRef().getAValueReachableFromSource() and
|
||||
nodeTo = getlistResult().asSource()
|
||||
)
|
||||
or
|
||||
// Indexing
|
||||
nodeFrom in [instance().getAUse(), fieldList()] and
|
||||
nodeFrom in [instance().getAValueReachableFromSource(), fieldList()] and
|
||||
nodeTo.asCfgNode().(SubscriptNode).getObject() = nodeFrom.asCfgNode()
|
||||
or
|
||||
// Attributes on Field
|
||||
@@ -1939,7 +1943,7 @@ private module StdlibPrivate {
|
||||
|
||||
/** A HttpRequestHandler class definition (most likely in project code). */
|
||||
class HttpRequestHandlerClassDef extends Class {
|
||||
HttpRequestHandlerClassDef() { this.getParent() = subclassRef().getAnImmediateUse().asExpr() }
|
||||
HttpRequestHandlerClassDef() { this.getParent() = subclassRef().asSource().asExpr() }
|
||||
}
|
||||
|
||||
/** DEPRECATED: Alias for HttpRequestHandlerClassDef */
|
||||
@@ -2037,7 +2041,7 @@ private module StdlibPrivate {
|
||||
.getMember("simple_server")
|
||||
.getMember("WSGIServer")
|
||||
.getASubclass*()
|
||||
.getAnImmediateUse()
|
||||
.asSource()
|
||||
.asExpr()
|
||||
}
|
||||
}
|
||||
@@ -2553,7 +2557,7 @@ private module StdlibPrivate {
|
||||
override DataFlow::Node getAPathArgument() {
|
||||
result = super.getAPathArgument()
|
||||
or
|
||||
result = this.getParameter(0, "target").getARhs()
|
||||
result = this.getParameter(0, "target").asSink()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2570,7 +2574,7 @@ private module StdlibPrivate {
|
||||
override DataFlow::Node getAPathArgument() {
|
||||
result = super.getAPathArgument()
|
||||
or
|
||||
result = this.getParameter(0, "target").getARhs()
|
||||
result = this.getParameter(0, "target").asSink()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2585,7 +2589,7 @@ private module StdlibPrivate {
|
||||
override DataFlow::Node getAPathArgument() {
|
||||
result = super.getAPathArgument()
|
||||
or
|
||||
result = this.getParameter(0, "other_path").getARhs()
|
||||
result = this.getParameter(0, "other_path").asSink()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2653,7 +2657,7 @@ private module StdlibPrivate {
|
||||
/** Gets a call to `hashlib.new` with `algorithmName` as the first argument. */
|
||||
private API::CallNode hashlibNewCall(string algorithmName) {
|
||||
algorithmName =
|
||||
result.getParameter(0, "name").getAValueReachingRhs().asExpr().(StrConst).getText() and
|
||||
result.getParameter(0, "name").getAValueReachingSink().asExpr().(StrConst).getText() and
|
||||
result = API::moduleImport("hashlib").getMember("new").getACall()
|
||||
}
|
||||
|
||||
@@ -2670,7 +2674,7 @@ private module StdlibPrivate {
|
||||
|
||||
override Cryptography::CryptographicAlgorithm getAlgorithm() { result.matchesName(hashName) }
|
||||
|
||||
override DataFlow::Node getAnInput() { result = this.getParameter(1, "data").getARhs() }
|
||||
override DataFlow::Node getAnInput() { result = this.getParameter(1, "data").asSink() }
|
||||
|
||||
override Cryptography::BlockMode getBlockMode() { none() }
|
||||
}
|
||||
@@ -3433,13 +3437,13 @@ private module StdlibPrivate {
|
||||
private DataFlow::Node saxParserWithFeatureExternalGesTurnedOn(DataFlow::TypeTracker t) {
|
||||
t.start() and
|
||||
exists(SaxParserSetFeatureCall call |
|
||||
call.getFeatureArg().getARhs() =
|
||||
call.getFeatureArg().asSink() =
|
||||
API::moduleImport("xml")
|
||||
.getMember("sax")
|
||||
.getMember("handler")
|
||||
.getMember("feature_external_ges")
|
||||
.getAUse() and
|
||||
call.getStateArg().getAValueReachingRhs().asExpr().(BooleanLiteral).booleanValue() = true and
|
||||
.getAValueReachableFromSource() and
|
||||
call.getStateArg().getAValueReachingSink().asExpr().(BooleanLiteral).booleanValue() = true and
|
||||
result = call.getObject()
|
||||
)
|
||||
or
|
||||
@@ -3449,13 +3453,13 @@ private module StdlibPrivate {
|
||||
// take account of that we can set the feature to False, which makes the parser safe again
|
||||
not exists(SaxParserSetFeatureCall call |
|
||||
call.getObject() = result and
|
||||
call.getFeatureArg().getARhs() =
|
||||
call.getFeatureArg().asSink() =
|
||||
API::moduleImport("xml")
|
||||
.getMember("sax")
|
||||
.getMember("handler")
|
||||
.getMember("feature_external_ges")
|
||||
.getAUse() and
|
||||
call.getStateArg().getAValueReachingRhs().asExpr().(BooleanLiteral).booleanValue() = false
|
||||
.getAValueReachableFromSource() and
|
||||
call.getStateArg().getAValueReachingSink().asExpr().(BooleanLiteral).booleanValue() = false
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -92,7 +92,7 @@ private module Tornado {
|
||||
|
||||
/** A RequestHandler class (most likely in project code). */
|
||||
class RequestHandlerClass extends Class {
|
||||
RequestHandlerClass() { this.getParent() = subclassRef().getAnImmediateUse().asExpr() }
|
||||
RequestHandlerClass() { this.getParent() = subclassRef().asSource().asExpr() }
|
||||
|
||||
/** Gets a function that could handle incoming requests, if any. */
|
||||
Function getARequestHandler() {
|
||||
|
||||
@@ -33,7 +33,7 @@ private module Twisted {
|
||||
.getMember("resource")
|
||||
.getMember("Resource")
|
||||
.getASubclass*()
|
||||
.getAnImmediateUse()
|
||||
.asSource()
|
||||
.asExpr()
|
||||
}
|
||||
|
||||
|
||||
@@ -71,14 +71,15 @@ private module Urllib3 {
|
||||
|
|
||||
// cert_reqs
|
||||
// see https://urllib3.readthedocs.io/en/stable/user-guide.html?highlight=cert_reqs#certificate-verification
|
||||
disablingNode = constructor.getKeywordParameter("cert_reqs").getARhs() and
|
||||
argumentOrigin = constructor.getKeywordParameter("cert_reqs").getAValueReachingRhs() and
|
||||
disablingNode = constructor.getKeywordParameter("cert_reqs").asSink() and
|
||||
argumentOrigin = constructor.getKeywordParameter("cert_reqs").getAValueReachingSink() and
|
||||
argumentOrigin.asExpr().(StrConst).getText() = "CERT_NONE"
|
||||
or
|
||||
// assert_hostname
|
||||
// see https://urllib3.readthedocs.io/en/stable/reference/urllib3.connectionpool.html?highlight=assert_hostname#urllib3.HTTPSConnectionPool
|
||||
disablingNode = constructor.getKeywordParameter("assert_hostname").getARhs() and
|
||||
argumentOrigin = constructor.getKeywordParameter("assert_hostname").getAValueReachingRhs() and
|
||||
disablingNode = constructor.getKeywordParameter("assert_hostname").asSink() and
|
||||
argumentOrigin =
|
||||
constructor.getKeywordParameter("assert_hostname").getAValueReachingSink() and
|
||||
argumentOrigin.asExpr().(BooleanLiteral).booleanValue() = false
|
||||
)
|
||||
}
|
||||
|
||||
@@ -285,7 +285,7 @@ private module WerkzeugOld {
|
||||
* See https://werkzeug.palletsprojects.com/en/1.0.x/datastructures/#werkzeug.datastructures.Headers.getlist
|
||||
*/
|
||||
deprecated DataFlow::Node getlist() {
|
||||
result = any(InstanceSourceApiNode a).getMember("getlist").getAUse()
|
||||
result = any(InstanceSourceApiNode a).getMember("getlist").getAValueReachableFromSource()
|
||||
}
|
||||
|
||||
private class MultiDictAdditionalTaintStep extends TaintTracking::AdditionalTaintStep {
|
||||
@@ -331,7 +331,9 @@ private module WerkzeugOld {
|
||||
abstract deprecated class InstanceSourceApiNode extends API::Node { }
|
||||
|
||||
/** Gets a reference to an instance of `werkzeug.datastructures.FileStorage`. */
|
||||
deprecated DataFlow::Node instance() { result = any(InstanceSourceApiNode a).getAUse() }
|
||||
deprecated DataFlow::Node instance() {
|
||||
result = any(InstanceSourceApiNode a).getAValueReachableFromSource()
|
||||
}
|
||||
|
||||
private class FileStorageAdditionalTaintStep extends TaintTracking::AdditionalTaintStep {
|
||||
override predicate step(DataFlow::Node nodeFrom, DataFlow::Node nodeTo) {
|
||||
|
||||
@@ -29,7 +29,7 @@ private module Xmltodict {
|
||||
|
||||
override predicate vulnerableTo(XML::XmlParsingVulnerabilityKind kind) {
|
||||
kind.isXmlBomb() and
|
||||
this.getKeywordParameter("disable_entities").getAValueReachingRhs().asExpr() = any(False f)
|
||||
this.getKeywordParameter("disable_entities").getAValueReachingSink().asExpr() = any(False f)
|
||||
}
|
||||
|
||||
override predicate mayExecuteInput() { none() }
|
||||
|
||||
@@ -64,7 +64,7 @@ private module Yaml {
|
||||
loader_arg =
|
||||
API::moduleImport("yaml")
|
||||
.getMember(["SafeLoader", "BaseLoader", "CSafeLoader", "CBaseLoader"])
|
||||
.getAUse()
|
||||
.getAValueReachableFromSource()
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -23,7 +23,7 @@ private import semmle.python.dataflow.new.TaintTracking
|
||||
* A remote flow source originating from a CSV source row.
|
||||
*/
|
||||
private class RemoteFlowSourceFromCsv extends RemoteFlowSource {
|
||||
RemoteFlowSourceFromCsv() { this = ModelOutput::getASourceNode("remote").getAnImmediateUse() }
|
||||
RemoteFlowSourceFromCsv() { this = ModelOutput::getASourceNode("remote").asSource() }
|
||||
|
||||
override string getSourceType() { result = "Remote flow (from model)" }
|
||||
}
|
||||
@@ -34,8 +34,8 @@ private class RemoteFlowSourceFromCsv extends RemoteFlowSource {
|
||||
private predicate summaryStepNodes(DataFlow::Node pred, DataFlow::Node succ, string kind) {
|
||||
exists(API::Node predNode, API::Node succNode |
|
||||
Specific::summaryStep(predNode, succNode, kind) and
|
||||
pred = predNode.getARhs() and
|
||||
succ = succNode.getAnImmediateUse()
|
||||
pred = predNode.asSink() and
|
||||
succ = succNode.asSource()
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -33,7 +33,9 @@ module PEP249 {
|
||||
}
|
||||
|
||||
/** Gets a reference to the `connect` function of a module that implements PEP 249. */
|
||||
DataFlow::Node connect() { result = any(PEP249ModuleApiNode a).getMember("connect").getAUse() }
|
||||
DataFlow::Node connect() {
|
||||
result = any(PEP249ModuleApiNode a).getMember("connect").getAValueReachableFromSource()
|
||||
}
|
||||
|
||||
/**
|
||||
* Provides models for database connections (following PEP 249).
|
||||
|
||||
@@ -74,9 +74,7 @@ private module NotExposed {
|
||||
}
|
||||
|
||||
/** DEPRECATED: Alias for fullyQualifiedToApiGraphPath */
|
||||
deprecated string fullyQualifiedToAPIGraphPath(string fullyQaulified) {
|
||||
result = fullyQualifiedToApiGraphPath(fullyQaulified)
|
||||
}
|
||||
deprecated predicate fullyQualifiedToAPIGraphPath = fullyQualifiedToApiGraphPath/1;
|
||||
|
||||
bindingset[this]
|
||||
abstract class FindSubclassesSpec extends string {
|
||||
@@ -152,7 +150,7 @@ private module NotExposed {
|
||||
FindSubclassesSpec spec, string newAliasFullyQualified, ImportMember importMember, Module mod,
|
||||
Location loc
|
||||
) {
|
||||
importMember = newOrExistingModeling(spec).getAUse().asExpr() and
|
||||
importMember = newOrExistingModeling(spec).getAValueReachableFromSource().asExpr() and
|
||||
importMember.getScope() = mod and
|
||||
loc = importMember.getLocation() and
|
||||
(
|
||||
@@ -182,7 +180,7 @@ private module NotExposed {
|
||||
// WHAT A HACK :D :D
|
||||
relevantClass.getPath() =
|
||||
relevantClass.getAPredecessor().getPath() + ".getMember(\"" + relevantName + "\")" and
|
||||
relevantClass.getAPredecessor().getAUse().asExpr() = importStar.getModule() and
|
||||
relevantClass.getAPredecessor().getAValueReachableFromSource().asExpr() = importStar.getModule() and
|
||||
(
|
||||
mod.isPackageInit() and
|
||||
newAliasFullyQualified = mod.getPackageName() + "." + relevantName
|
||||
@@ -204,7 +202,7 @@ private module NotExposed {
|
||||
FindSubclassesSpec spec, string newSubclassQualified, ClassExpr classExpr, Module mod,
|
||||
Location loc
|
||||
) {
|
||||
classExpr = newOrExistingModeling(spec).getASubclass*().getAnImmediateUse().asExpr() and
|
||||
classExpr = newOrExistingModeling(spec).getASubclass*().asSource().asExpr() and
|
||||
classExpr.getScope() = mod and
|
||||
newSubclassQualified = mod.getName() + "." + classExpr.getName() and
|
||||
loc = classExpr.getLocation() and
|
||||
|
||||
@@ -121,7 +121,7 @@ module Stages {
|
||||
or
|
||||
exists(any(NewDataFlow::TypeTracker t).append(_))
|
||||
or
|
||||
exists(any(API::Node n).getAMember().getAUse())
|
||||
exists(any(API::Node n).getAMember().getAValueReachableFromSource())
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -344,12 +344,12 @@ private class ClassListList extends TClassListList {
|
||||
)
|
||||
}
|
||||
|
||||
private predicate legalMergeCandidate(ClassObjectInternal cls, ClassListList remaining) {
|
||||
cls = this.getAHead() and remaining = this
|
||||
private predicate legalMergeCandidate(ClassObjectInternal cls, ClassListList remainingList) {
|
||||
cls = this.getAHead() and remainingList = this
|
||||
or
|
||||
this.legalMergeCandidate(cls, ConsList(Empty(), remaining))
|
||||
this.legalMergeCandidate(cls, ConsList(Empty(), remainingList))
|
||||
or
|
||||
this.legalMergeCandidateNonEmpty(cls, remaining, Empty())
|
||||
this.legalMergeCandidateNonEmpty(cls, remainingList, Empty())
|
||||
}
|
||||
|
||||
pragma[noinline]
|
||||
@@ -386,10 +386,10 @@ private class ClassListList extends TClassListList {
|
||||
|
||||
private ClassList flatten_list(ClassListList list, int n) {
|
||||
need_flattening(list) and
|
||||
exists(ClassList head, ClassListList tail | list = ConsList(head, tail) |
|
||||
exists(ClassList head, ClassListList tail | pragma[only_bind_out](list) = ConsList(head, tail) |
|
||||
n = head.length() and result = tail.flatten()
|
||||
or
|
||||
result = Cons(head.getItem(n), flatten_list(list, n + 1))
|
||||
result = Cons(head.getItem(n), flatten_list(pragma[only_bind_out](list), n + 1))
|
||||
)
|
||||
}
|
||||
|
||||
@@ -419,7 +419,9 @@ private ClassListList list_of_linearization_of_bases_plus_bases(ClassObjectInter
|
||||
result = ConsList(bases(cls), EmptyList()) and n = Types::base_count(cls) and n > 1
|
||||
or
|
||||
exists(ClassListList partial |
|
||||
partial = list_of_linearization_of_bases_plus_bases(cls, n + 1) and
|
||||
partial =
|
||||
list_of_linearization_of_bases_plus_bases(pragma[only_bind_into](cls),
|
||||
pragma[only_bind_into](n + 1)) and
|
||||
result = ConsList(Mro::newStyleMro(Types::getBase(cls, n)), partial)
|
||||
)
|
||||
}
|
||||
|
||||
@@ -75,7 +75,7 @@ private string canonical_name(API::Node flag) {
|
||||
*/
|
||||
private DataFlow::TypeTrackingNode re_flag_tracker(string flag_name, DataFlow::TypeTracker t) {
|
||||
t.start() and
|
||||
exists(API::Node flag | flag_name = canonical_name(flag) and result = flag.getAnImmediateUse())
|
||||
exists(API::Node flag | flag_name = canonical_name(flag) and result = flag.asSource())
|
||||
or
|
||||
exists(BinaryExprNode binop, DataFlow::Node operand |
|
||||
operand.getALocalSource() = re_flag_tracker(flag_name, t.continue()) and
|
||||
|
||||
@@ -64,7 +64,7 @@ module PathInjection {
|
||||
private import semmle.python.frameworks.data.ModelsAsData
|
||||
|
||||
private class DataAsFileSink extends Sink {
|
||||
DataAsFileSink() { this = ModelOutput::getASinkNode("path-injection").getARhs() }
|
||||
DataAsFileSink() { this = ModelOutput::getASinkNode("path-injection").asSink() }
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -67,6 +67,6 @@ module SqlInjection {
|
||||
|
||||
/** A sink for sql-injection from model data. */
|
||||
private class DataAsSqlSink extends Sink {
|
||||
DataAsSqlSink() { this = ModelOutput::getASinkNode("sql-injection").getARhs() }
|
||||
DataAsSqlSink() { this = ModelOutput::getASinkNode("sql-injection").asSink() }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,142 @@
|
||||
/**
|
||||
* Provides default sources, sinks and sanitizers for detecting
|
||||
* "tar slip"
|
||||
* vulnerabilities, as well as extension points for adding your own.
|
||||
*/
|
||||
|
||||
private import python
|
||||
private import semmle.python.dataflow.new.DataFlow
|
||||
private import semmle.python.Concepts
|
||||
private import semmle.python.dataflow.new.BarrierGuards
|
||||
private import semmle.python.ApiGraphs
|
||||
|
||||
/**
|
||||
* Provides default sources, sinks and sanitizers for detecting
|
||||
* "tar slip"
|
||||
* vulnerabilities, as well as extension points for adding your own.
|
||||
*/
|
||||
module TarSlip {
|
||||
/**
|
||||
* A data flow source for "tar slip" vulnerabilities.
|
||||
*/
|
||||
abstract class Source extends DataFlow::Node { }
|
||||
|
||||
/**
|
||||
* A data flow sink for "tar slip" vulnerabilities.
|
||||
*/
|
||||
abstract class Sink extends DataFlow::Node { }
|
||||
|
||||
/**
|
||||
* A sanitizer for "tar slip" vulnerabilities.
|
||||
*/
|
||||
abstract class Sanitizer extends DataFlow::Node { }
|
||||
|
||||
/**
|
||||
* A call to `tarfile.open`, considered as a flow source.
|
||||
*/
|
||||
class TarfileOpen extends Source {
|
||||
TarfileOpen() {
|
||||
this = API::moduleImport("tarfile").getMember("open").getACall() and
|
||||
// If argument refers to a string object, then it's a hardcoded path and
|
||||
// this tarfile is safe.
|
||||
not this.(DataFlow::CallCfgNode).getArg(0).getALocalSource().asExpr() instanceof StrConst and
|
||||
// Ignore opens within the tarfile module itself
|
||||
not this.getLocation().getFile().getBaseName() = "tarfile.py"
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A sanitizer based on file name. This because we extract the standard library.
|
||||
*
|
||||
* For efficiency we don't want to track the flow of taint
|
||||
* around the tarfile module.
|
||||
*/
|
||||
class ExcludeTarFilePy extends Sanitizer {
|
||||
ExcludeTarFilePy() { this.getLocation().getFile().getBaseName() = "tarfile.py" }
|
||||
}
|
||||
|
||||
/**
|
||||
* A sink capturing method calls to `extractall`.
|
||||
*
|
||||
* For a call to `file.extractall` without arguments, `file` is considered a sink.
|
||||
*/
|
||||
class ExtractAllSink extends Sink {
|
||||
ExtractAllSink() {
|
||||
exists(DataFlow::CallCfgNode call |
|
||||
call =
|
||||
API::moduleImport("tarfile")
|
||||
.getMember("open")
|
||||
.getReturn()
|
||||
.getMember("extractall")
|
||||
.getACall() and
|
||||
not exists(call.getArg(_)) and
|
||||
not exists(call.getArgByName(_)) and
|
||||
this = call.(DataFlow::MethodCallNode).getObject()
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* An argument to `extract` is considered a sink.
|
||||
*/
|
||||
class ExtractSink extends Sink {
|
||||
ExtractSink() {
|
||||
exists(DataFlow::CallCfgNode call |
|
||||
call =
|
||||
API::moduleImport("tarfile").getMember("open").getReturn().getMember("extract").getACall() and
|
||||
this = call.getArg(0)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/** The `members` argument `extractall` is considered a sink. */
|
||||
class ExtractMembersSink extends Sink {
|
||||
ExtractMembersSink() {
|
||||
exists(DataFlow::CallCfgNode call |
|
||||
call =
|
||||
API::moduleImport("tarfile")
|
||||
.getMember("open")
|
||||
.getReturn()
|
||||
.getMember("extractall")
|
||||
.getACall() and
|
||||
this in [call.getArg(0), call.getArgByName("members")]
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if `g` clears taint for `tarInfo`.
|
||||
*
|
||||
* The test `if <check_path>(info.name)` should clear taint for `info`,
|
||||
* where `<check_path>` is any function matching `"%path"`.
|
||||
* `info` is assumed to be a `TarInfo` instance.
|
||||
*/
|
||||
predicate tarFileInfoSanitizer(DataFlow::GuardNode g, ControlFlowNode tarInfo, boolean branch) {
|
||||
exists(CallNode call, AttrNode attr |
|
||||
g = call and
|
||||
// We must test the name of the tar info object.
|
||||
attr = call.getAnArg() and
|
||||
attr.getName() = "name" and
|
||||
attr.getObject() = tarInfo
|
||||
|
|
||||
// The assumption that any test that matches %path is a sanitizer might be too broad.
|
||||
call.getAChild*().(AttrNode).getName().matches("%path")
|
||||
or
|
||||
call.getAChild*().(NameNode).getId().matches("%path")
|
||||
) and
|
||||
branch = false
|
||||
}
|
||||
|
||||
/**
|
||||
* A sanitizer guard heuristic.
|
||||
*
|
||||
* The test `if <check_path>(info.name)` should clear taint for `info`,
|
||||
* where `<check_path>` is any function matching `"%path"`.
|
||||
* `info` is assumed to be a `TarInfo` instance.
|
||||
*/
|
||||
class TarFileInfoSanitizer extends Sanitizer {
|
||||
TarFileInfoSanitizer() {
|
||||
this = DataFlow::BarrierGuard<tarFileInfoSanitizer/3>::getABarrierNode()
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
/**
|
||||
* Provides a taint-tracking configuration for detecting "command injection" vulnerabilities.
|
||||
*
|
||||
* Note, for performance reasons: only import this file if
|
||||
* `TarSlip::Configuration` is needed, otherwise
|
||||
* `TarSlipCustomizations` should be imported instead.
|
||||
*/
|
||||
|
||||
private import python
|
||||
import semmle.python.dataflow.new.DataFlow
|
||||
import semmle.python.dataflow.new.TaintTracking
|
||||
import TarSlipCustomizations::TarSlip
|
||||
|
||||
/**
|
||||
* A taint-tracking configuration for detecting "command injection" vulnerabilities.
|
||||
*/
|
||||
class Configuration extends TaintTracking::Configuration {
|
||||
Configuration() { this = "TarSlip" }
|
||||
|
||||
override predicate isSource(DataFlow::Node source) { source instanceof Source }
|
||||
|
||||
override predicate isSink(DataFlow::Node sink) { sink instanceof Sink }
|
||||
|
||||
override predicate isSanitizer(DataFlow::Node node) { node instanceof Sanitizer }
|
||||
}
|
||||
Reference in New Issue
Block a user