mirror of
https://github.com/github/codeql.git
synced 2025-12-20 18:56:32 +01:00
Merge branch 'main' into rc/3.7
This commit is contained in:
4
python/ql/lib/change-notes/2022-08-25-variable-fix.md
Normal file
4
python/ql/lib/change-notes/2022-08-25-variable-fix.md
Normal file
@@ -0,0 +1,4 @@
|
||||
---
|
||||
category: minorAnalysis
|
||||
---
|
||||
* Reads of global/non-local variables (without annotations) inside functions defined on classes now works properly in the case where the class had an attribute defined with the same name as the non-local variable.
|
||||
@@ -0,0 +1,4 @@
|
||||
---
|
||||
category: fix
|
||||
---
|
||||
* Fixed an issue in the taint tracking analysis where implicit reads were not allowed by default in sinks or additional taint steps that used flow states.
|
||||
@@ -0,0 +1,4 @@
|
||||
---
|
||||
category: minorAnalysis
|
||||
---
|
||||
* Changed `CallNode.getArgByName` such that it has results for keyword arguments given after a dictionary unpacking argument, as the `bar=2` argument in `func(foo=1, **kwargs, bar=2)`.
|
||||
@@ -0,0 +1,4 @@
|
||||
---
|
||||
category: deprecated
|
||||
---
|
||||
* Some unused predicates in `SsaDefinitions.qll`, `TObject.qll`, `protocols.qll`, and the `pointsto/` folder have been deprecated.
|
||||
5
python/ql/lib/change-notes/2022-09-12-uppercase.md
Normal file
5
python/ql/lib/change-notes/2022-09-12-uppercase.md
Normal file
@@ -0,0 +1,5 @@
|
||||
---
|
||||
category: deprecated
|
||||
---
|
||||
* Some classes/modules with upper-case acronyms in their name have been renamed to follow our style-guide.
|
||||
The old name still exists as a deprecated alias.
|
||||
24
python/ql/lib/design.md
Normal file
24
python/ql/lib/design.md
Normal file
@@ -0,0 +1,24 @@
|
||||
# The Python libraries
|
||||
|
||||
The Python libraries are a collection of libraries for analysing Python code.
|
||||
Everythng can be imported by importing `python.qll`.
|
||||
|
||||
## The analysis layers
|
||||
|
||||
The analysis is built up in layers. the stack looks like this:
|
||||
|
||||
- AST (coms from the extractor)
|
||||
- Control flow graph (CFG) (built by the extractor)
|
||||
- SSA
|
||||
- Call graph
|
||||
- Data flow
|
||||
|
||||
## Avoiding non-monotonic recursion
|
||||
|
||||
Given the many interactivg layers, it is imprtant to decie which predicates are allowed to be mutually recursive in order to avoid non-monotonic recursion when negation is used to express the predicates.
|
||||
As an example, we have defined local source as those whcih do not receive local flow. This means that the local flow relation is not allowed to be recursive with anything depending on local sources.
|
||||
|
||||
Some particular reatrictions to keep in mind:
|
||||
|
||||
- Typetracking needs to use a local flow step not including summaries
|
||||
- Typetracking needs to use a call graph not including summaries
|
||||
@@ -380,6 +380,21 @@ module API {
|
||||
not m.matches("%.%")
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if an import of module `m` exists.
|
||||
*
|
||||
* This is determined without referring to `Node`,
|
||||
* allowing this predicate to be used in a negative
|
||||
* context when constructing new nodes.
|
||||
*/
|
||||
predicate moduleImportExists(string m) {
|
||||
Impl::isImported(m) and
|
||||
// restrict `moduleImport` so it will never give results for a dotted name. Note
|
||||
// that we cannot move this logic to the `MkModuleImport` construction, since we
|
||||
// need the intermediate API graph nodes for the prefixes in `import foo.bar.baz`.
|
||||
not m.matches("%.%")
|
||||
}
|
||||
|
||||
/** Gets a node corresponding to the built-in with the given name, if any. */
|
||||
Node builtin(string n) { result = moduleImport("builtins").getMember(n) }
|
||||
|
||||
@@ -453,6 +468,30 @@ module API {
|
||||
int getNumArgument() { result = count(this.getArg(_)) }
|
||||
}
|
||||
|
||||
/**
|
||||
* An API entry point.
|
||||
*
|
||||
* By default, API graph nodes are only created for nodes that come from an external
|
||||
* library or escape into an external library. The points where values are cross the boundary
|
||||
* between codebases are called "entry points".
|
||||
*
|
||||
* Anything imported from an external package is considered to be an entry point, but
|
||||
* additional entry points may be added by extending this class.
|
||||
*/
|
||||
abstract class EntryPoint extends string {
|
||||
bindingset[this]
|
||||
EntryPoint() { any() }
|
||||
|
||||
/** Gets a data-flow node corresponding to a use-node for this entry point. */
|
||||
DataFlow::LocalSourceNode getASource() { none() }
|
||||
|
||||
/** Gets a data-flow node corresponding to a def-node for this entry point. */
|
||||
DataFlow::Node getASink() { none() }
|
||||
|
||||
/** Gets an API-node for this entry point. */
|
||||
API::Node getANode() { result = root().getASuccessor(Label::entryPoint(this)) }
|
||||
}
|
||||
|
||||
/**
|
||||
* Provides the actual implementation of API graphs, cached for performance.
|
||||
*
|
||||
@@ -581,14 +620,38 @@ module API {
|
||||
*
|
||||
* Ignores relative imports, such as `from ..foo.bar import baz`.
|
||||
*/
|
||||
private predicate imports(DataFlow::Node imp, string name) {
|
||||
private predicate imports(DataFlow::CfgNode imp, string name) {
|
||||
exists(PY::ImportExprNode iexpr |
|
||||
imp.asCfgNode() = iexpr and
|
||||
imp.getNode() = iexpr and
|
||||
not iexpr.getNode().isRelative() and
|
||||
name = iexpr.getNode().getImportedModuleName()
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if the module `name` is imported.
|
||||
*
|
||||
* This is determined syntactically.
|
||||
*/
|
||||
cached
|
||||
predicate isImported(string name) {
|
||||
// Ignore the following module name for Python 2, as we alias `__builtin__` to `builtins` elsewhere
|
||||
(name != "__builtin__" or PY::major_version() = 3) and
|
||||
(
|
||||
exists(PY::ImportExpr iexpr |
|
||||
not iexpr.isRelative() and
|
||||
name = iexpr.getImportedModuleName()
|
||||
)
|
||||
or
|
||||
// When we `import foo.bar.baz` we want to create API graph nodes also for the prefixes
|
||||
// `foo` and `foo.bar`:
|
||||
name = any(PY::ImportExpr e | not e.isRelative()).getAnImportedModuleName()
|
||||
)
|
||||
or
|
||||
// The `builtins` module should always be implicitly available
|
||||
name = "builtins"
|
||||
}
|
||||
|
||||
private import semmle.python.dataflow.new.internal.Builtins
|
||||
private import semmle.python.dataflow.new.internal.ImportStar
|
||||
|
||||
@@ -607,7 +670,7 @@ module API {
|
||||
*/
|
||||
private TApiNode potential_import_star_base(PY::Scope s) {
|
||||
exists(DataFlow::Node n |
|
||||
n.asCfgNode() = ImportStar::potentialImportStarBase(s) and
|
||||
n.(DataFlow::CfgNode).getNode() = ImportStar::potentialImportStarBase(s) and
|
||||
use(result, n)
|
||||
)
|
||||
}
|
||||
@@ -629,17 +692,17 @@ module API {
|
||||
or
|
||||
// TODO: I had expected `DataFlow::AttrWrite` to contain the attribute writes from a dict, that's how JS works.
|
||||
exists(PY::Dict dict, PY::KeyValuePair item |
|
||||
dict = pred.asExpr() and
|
||||
dict = pred.(DataFlow::ExprNode).getNode().getNode() and
|
||||
dict.getItem(_) = item and
|
||||
lbl = Label::member(item.getKey().(PY::StrConst).getS()) and
|
||||
rhs.asExpr() = item.getValue()
|
||||
rhs.(DataFlow::ExprNode).getNode().getNode() = item.getValue()
|
||||
)
|
||||
or
|
||||
exists(PY::CallableExpr fn | fn = pred.asExpr() |
|
||||
exists(PY::CallableExpr fn | fn = pred.(DataFlow::ExprNode).getNode().getNode() |
|
||||
not fn.getInnerScope().isAsync() and
|
||||
lbl = Label::return() and
|
||||
exists(PY::Return ret |
|
||||
rhs.asExpr() = ret.getValue() and
|
||||
rhs.(DataFlow::ExprNode).getNode().getNode() = ret.getValue() and
|
||||
ret.getScope() = fn.getInnerScope()
|
||||
)
|
||||
)
|
||||
@@ -652,6 +715,12 @@ module API {
|
||||
|
|
||||
lbl = Label::memberFromRef(aw)
|
||||
)
|
||||
or
|
||||
exists(EntryPoint entry |
|
||||
base = root() and
|
||||
lbl = Label::entryPoint(entry) and
|
||||
rhs = entry.getASink()
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -686,9 +755,9 @@ module API {
|
||||
// "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
|
||||
ref.(DataFlow::ExprNode).getNode().getNode() = clsExpr
|
||||
or
|
||||
ref.asExpr() = clsExpr.getADecoratorCall()
|
||||
ref.(DataFlow::ExprNode).getNode().getNode() = clsExpr.getADecoratorCall()
|
||||
)
|
||||
)
|
||||
or
|
||||
@@ -701,7 +770,7 @@ module API {
|
||||
)
|
||||
or
|
||||
exists(DataFlow::Node def, PY::CallableExpr fn |
|
||||
rhs(base, def) and fn = trackDefNode(def).asExpr()
|
||||
rhs(base, def) and fn = trackDefNode(def).(DataFlow::ExprNode).getNode().getNode()
|
||||
|
|
||||
exists(int i, int offset |
|
||||
if exists(PY::Parameter p | p = fn.getInnerScope().getAnArg() and p.isSelf())
|
||||
@@ -709,18 +778,19 @@ module API {
|
||||
else offset = 0
|
||||
|
|
||||
lbl = Label::parameter(i - offset) and
|
||||
ref.asExpr() = fn.getInnerScope().getArg(i)
|
||||
ref.(DataFlow::ExprNode).getNode().getNode() = fn.getInnerScope().getArg(i)
|
||||
)
|
||||
or
|
||||
exists(string name, PY::Parameter param |
|
||||
lbl = Label::keywordParameter(name) and
|
||||
param = fn.getInnerScope().getArgByName(name) and
|
||||
not param.isSelf() and
|
||||
ref.asExpr() = param
|
||||
ref.(DataFlow::ExprNode).getNode().getNode() = param
|
||||
)
|
||||
or
|
||||
lbl = Label::selfParameter() and
|
||||
ref.asExpr() = any(PY::Parameter p | p = fn.getInnerScope().getAnArg() and p.isSelf())
|
||||
ref.(DataFlow::ExprNode).getNode().getNode() =
|
||||
any(PY::Parameter p | p = fn.getInnerScope().getAnArg() and p.isSelf())
|
||||
)
|
||||
or
|
||||
// Built-ins, treated as members of the module `builtins`
|
||||
@@ -732,9 +802,15 @@ module API {
|
||||
base = potential_import_star_base(s) and
|
||||
lbl =
|
||||
Label::member(any(string name |
|
||||
ImportStar::namePossiblyDefinedInImportStar(ref.asCfgNode(), name, s)
|
||||
ImportStar::namePossiblyDefinedInImportStar(ref.(DataFlow::CfgNode).getNode(), name, s)
|
||||
))
|
||||
)
|
||||
or
|
||||
exists(EntryPoint entry |
|
||||
base = root() and
|
||||
lbl = Label::entryPoint(entry) and
|
||||
ref = entry.getASource()
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -818,7 +894,7 @@ module API {
|
||||
DataFlow::LocalSourceNode trackUseNode(DataFlow::LocalSourceNode src) {
|
||||
Stages::TypeTracking::ref() and
|
||||
result = trackUseNode(src, DataFlow::TypeTracker::end()) and
|
||||
not result instanceof DataFlow::ModuleVariableNode
|
||||
result instanceof DataFlow::ExprNode
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -909,7 +985,8 @@ module API {
|
||||
MkLabelSelfParameter() or
|
||||
MkLabelReturn() or
|
||||
MkLabelSubclass() or
|
||||
MkLabelAwait()
|
||||
MkLabelAwait() or
|
||||
MkLabelEntryPoint(EntryPoint ep)
|
||||
|
||||
/** A label for a module. */
|
||||
class LabelModule extends ApiLabel, MkLabelModule {
|
||||
@@ -983,6 +1060,15 @@ module API {
|
||||
class LabelAwait extends ApiLabel, MkLabelAwait {
|
||||
override string toString() { result = "getAwaited()" }
|
||||
}
|
||||
|
||||
/** A label for entry points. */
|
||||
class LabelEntryPoint extends ApiLabel, MkLabelEntryPoint {
|
||||
private EntryPoint entry;
|
||||
|
||||
LabelEntryPoint() { this = MkLabelEntryPoint(entry) }
|
||||
|
||||
override string toString() { result = "entryPoint(\"" + entry + "\")" }
|
||||
}
|
||||
}
|
||||
|
||||
/** Gets the edge label for the module `m`. */
|
||||
@@ -998,7 +1084,7 @@ module API {
|
||||
ApiLabel memberFromRef(DataFlow::AttrRef ref) {
|
||||
result = member(ref.getAttributeName())
|
||||
or
|
||||
not exists(ref.getAttributeName()) and
|
||||
ref.unknownAttribute() and
|
||||
result = unknownMember()
|
||||
}
|
||||
|
||||
@@ -1019,5 +1105,8 @@ module API {
|
||||
|
||||
/** Gets the `await` edge label. */
|
||||
LabelAwait await() { any() }
|
||||
|
||||
/** Gets the label going from the root node to the nodes associated with the given entry point. */
|
||||
LabelEntryPoint entryPoint(EntryPoint ep) { result = MkLabelEntryPoint(ep) }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -565,7 +565,7 @@ module XML {
|
||||
}
|
||||
|
||||
/** Provides classes for modeling LDAP-related APIs. */
|
||||
module LDAP {
|
||||
module Ldap {
|
||||
/**
|
||||
* A data-flow node that executes an LDAP query.
|
||||
*
|
||||
@@ -598,6 +598,9 @@ module LDAP {
|
||||
}
|
||||
}
|
||||
|
||||
/** DEPRECATED: Alias for Ldap */
|
||||
deprecated module LDAP = Ldap;
|
||||
|
||||
/**
|
||||
* A data-flow node that escapes meta-characters, which could be used to prevent
|
||||
* injection attacks.
|
||||
@@ -706,7 +709,7 @@ class LdapFilterEscaping extends Escaping {
|
||||
}
|
||||
|
||||
/** Provides classes for modeling HTTP-related APIs. */
|
||||
module HTTP {
|
||||
module Http {
|
||||
/** Gets an HTTP verb, in upper case */
|
||||
string httpVerb() { result in ["GET", "POST", "PUT", "PATCH", "DELETE", "OPTIONS", "HEAD"] }
|
||||
|
||||
@@ -917,7 +920,7 @@ module HTTP {
|
||||
* Extend this class to model new APIs. If you want to refine existing API models,
|
||||
* extend `HttpResponse` instead.
|
||||
*/
|
||||
abstract class Range extends HTTP::Server::HttpResponse::Range {
|
||||
abstract class Range extends Http::Server::HttpResponse::Range {
|
||||
/** Gets the data-flow node that specifies the location of this HTTP redirect response. */
|
||||
abstract DataFlow::Node getRedirectLocation();
|
||||
}
|
||||
@@ -1046,73 +1049,14 @@ module HTTP {
|
||||
}
|
||||
}
|
||||
|
||||
/** Provides classes for modeling HTTP clients. */
|
||||
module Client {
|
||||
/**
|
||||
* A data-flow node that makes an outgoing HTTP request.
|
||||
*
|
||||
* Extend this class to refine existing API models. If you want to model new APIs,
|
||||
* extend `HTTP::Client::Request::Range` instead.
|
||||
*/
|
||||
class Request extends DataFlow::Node instanceof Request::Range {
|
||||
/**
|
||||
* Gets a data-flow node that contributes to the URL of the request.
|
||||
* Depending on the framework, a request may have multiple nodes which contribute to the URL.
|
||||
*/
|
||||
DataFlow::Node getAUrlPart() { result = super.getAUrlPart() }
|
||||
|
||||
/** Gets a string that identifies the framework used for this request. */
|
||||
string getFramework() { result = super.getFramework() }
|
||||
|
||||
/**
|
||||
* Holds if this request is made using a mode that disables SSL/TLS
|
||||
* certificate validation, where `disablingNode` represents the point at
|
||||
* which the validation was disabled, and `argumentOrigin` represents the origin
|
||||
* of the argument that disabled the validation (which could be the same node as
|
||||
* `disablingNode`).
|
||||
*/
|
||||
predicate disablesCertificateValidation(
|
||||
DataFlow::Node disablingNode, DataFlow::Node argumentOrigin
|
||||
) {
|
||||
super.disablesCertificateValidation(disablingNode, argumentOrigin)
|
||||
}
|
||||
}
|
||||
|
||||
/** Provides a class for modeling new HTTP requests. */
|
||||
module Request {
|
||||
/**
|
||||
* A data-flow node that makes an outgoing HTTP request.
|
||||
*
|
||||
* Extend this class to model new APIs. If you want to refine existing API models,
|
||||
* extend `HTTP::Client::Request` instead.
|
||||
*/
|
||||
abstract class Range extends DataFlow::Node {
|
||||
/**
|
||||
* Gets a data-flow node that contributes to the URL of the request.
|
||||
* Depending on the framework, a request may have multiple nodes which contribute to the URL.
|
||||
*/
|
||||
abstract DataFlow::Node getAUrlPart();
|
||||
|
||||
/** Gets a string that identifies the framework used for this request. */
|
||||
abstract string getFramework();
|
||||
|
||||
/**
|
||||
* Holds if this request is made using a mode that disables SSL/TLS
|
||||
* certificate validation, where `disablingNode` represents the point at
|
||||
* which the validation was disabled, and `argumentOrigin` represents the origin
|
||||
* of the argument that disabled the validation (which could be the same node as
|
||||
* `disablingNode`).
|
||||
*/
|
||||
abstract predicate disablesCertificateValidation(
|
||||
DataFlow::Node disablingNode, DataFlow::Node argumentOrigin
|
||||
);
|
||||
}
|
||||
}
|
||||
// TODO: investigate whether we should treat responses to client requests as
|
||||
// remote-flow-sources in general.
|
||||
}
|
||||
import semmle.python.internal.ConceptsShared::Http::Client as Client
|
||||
// TODO: investigate whether we should treat responses to client requests as
|
||||
// remote-flow-sources in general.
|
||||
}
|
||||
|
||||
/** DEPRECATED: Alias for Http */
|
||||
deprecated module HTTP = Http;
|
||||
|
||||
/**
|
||||
* Provides models for cryptographic things.
|
||||
*
|
||||
|
||||
@@ -376,7 +376,7 @@ class CallNode extends ControlFlowNode {
|
||||
ControlFlowNode getArgByName(string name) {
|
||||
exists(Call c, Keyword k |
|
||||
this.getNode() = c and
|
||||
k = c.getAKeyword() and
|
||||
k = c.getANamedArg() and
|
||||
k.getValue() = result.getNode() and
|
||||
k.getArg() = name and
|
||||
result.getBasicBlock().dominates(this.getBasicBlock())
|
||||
|
||||
107
python/ql/lib/semmle/python/dataflow/new/FlowSummary.qll
Normal file
107
python/ql/lib/semmle/python/dataflow/new/FlowSummary.qll
Normal file
@@ -0,0 +1,107 @@
|
||||
/** Provides classes and predicates for defining flow summaries. */
|
||||
|
||||
private import python
|
||||
private import semmle.python.dataflow.new.DataFlow
|
||||
private import semmle.python.frameworks.data.ModelsAsData
|
||||
private import semmle.python.ApiGraphs
|
||||
private import internal.FlowSummaryImpl as Impl
|
||||
private import internal.DataFlowUtil
|
||||
private import internal.DataFlowPrivate
|
||||
|
||||
// import all instances below
|
||||
private module Summaries {
|
||||
private import semmle.python.Frameworks
|
||||
}
|
||||
|
||||
class SummaryComponent = Impl::Public::SummaryComponent;
|
||||
|
||||
/** Provides predicates for constructing summary components. */
|
||||
module SummaryComponent {
|
||||
private import Impl::Public::SummaryComponent as SC
|
||||
|
||||
predicate parameter = SC::parameter/1;
|
||||
|
||||
predicate argument = SC::argument/1;
|
||||
|
||||
predicate content = SC::content/1;
|
||||
|
||||
/** Gets a summary component that represents a list element. */
|
||||
SummaryComponent listElement() { result = content(any(ListElementContent c)) }
|
||||
|
||||
/** Gets a summary component that represents the return value of a call. */
|
||||
SummaryComponent return() { result = SC::return(any(ReturnKind rk)) }
|
||||
}
|
||||
|
||||
class SummaryComponentStack = Impl::Public::SummaryComponentStack;
|
||||
|
||||
/** Provides predicates for constructing stacks of summary components. */
|
||||
module SummaryComponentStack {
|
||||
private import Impl::Public::SummaryComponentStack as SCS
|
||||
|
||||
predicate singleton = SCS::singleton/1;
|
||||
|
||||
predicate push = SCS::push/2;
|
||||
|
||||
predicate argument = SCS::argument/1;
|
||||
|
||||
/** Gets a singleton stack representing the return value of a call. */
|
||||
SummaryComponentStack return() { result = singleton(SummaryComponent::return()) }
|
||||
}
|
||||
|
||||
/** A callable with a flow summary, identified by a unique string. */
|
||||
abstract class SummarizedCallable extends LibraryCallable, Impl::Public::SummarizedCallable {
|
||||
bindingset[this]
|
||||
SummarizedCallable() { any() }
|
||||
|
||||
/**
|
||||
* Same as
|
||||
*
|
||||
* ```ql
|
||||
* propagatesFlow(
|
||||
* SummaryComponentStack input, SummaryComponentStack output, boolean preservesValue
|
||||
* )
|
||||
* ```
|
||||
*
|
||||
* but uses an external (string) representation of the input and output stacks.
|
||||
*/
|
||||
pragma[nomagic]
|
||||
predicate propagatesFlowExt(string input, string output, boolean preservesValue) { none() }
|
||||
}
|
||||
|
||||
class RequiredSummaryComponentStack = Impl::Public::RequiredSummaryComponentStack;
|
||||
// // This gives access to getNodeFromPath, which is not constrained to `CallNode`s
|
||||
// // as `resolvedSummaryBase` is.
|
||||
// private import semmle.python.frameworks.data.internal.ApiGraphModels as AGM
|
||||
//
|
||||
// private class SummarizedCallableFromModel extends SummarizedCallable {
|
||||
// string package;
|
||||
// string type;
|
||||
// string path;
|
||||
// SummarizedCallableFromModel() {
|
||||
// ModelOutput::relevantSummaryModel(package, type, path, _, _, _) and
|
||||
// this = package + ";" + type + ";" + path
|
||||
// }
|
||||
// override CallCfgNode getACall() {
|
||||
// exists(API::CallNode base |
|
||||
// ModelOutput::resolvedSummaryBase(package, type, path, base) and
|
||||
// result = base.getACall()
|
||||
// )
|
||||
// }
|
||||
// override ArgumentNode getACallback() {
|
||||
// exists(API::Node base |
|
||||
// base = AGM::getNodeFromPath(package, type, path) and
|
||||
// result = base.getAValueReachableFromSource()
|
||||
// )
|
||||
// }
|
||||
// override predicate propagatesFlowExt(string input, string output, boolean preservesValue) {
|
||||
// exists(string kind |
|
||||
// ModelOutput::relevantSummaryModel(package, type, path, input, output, kind)
|
||||
// |
|
||||
// kind = "value" and
|
||||
// preservesValue = true
|
||||
// or
|
||||
// kind = "taint" and
|
||||
// preservesValue = false
|
||||
// )
|
||||
// }
|
||||
// }
|
||||
@@ -40,7 +40,7 @@ abstract class AttrRef extends Node {
|
||||
or
|
||||
exists(LocalSourceNode nodeFrom |
|
||||
nodeFrom.flowsTo(this.getAttributeNameExpr()) and
|
||||
attrName = nodeFrom.asExpr().(StrConst).getText()
|
||||
attrName = nodeFrom.(CfgNode).getNode().getNode().(StrConst).getText()
|
||||
)
|
||||
}
|
||||
|
||||
@@ -50,6 +50,9 @@ abstract class AttrRef extends Node {
|
||||
* better results.
|
||||
*/
|
||||
abstract string getAttributeName();
|
||||
|
||||
/** Holds if a name could not be determined for this attribute. */
|
||||
predicate unknownAttribute() { not exists(this.getAttributeName()) }
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -175,7 +178,7 @@ private class SetAttrCallAsAttrWrite extends AttrWrite, CfgNode {
|
||||
override ExprNode getAttributeNameExpr() { result.asCfgNode() = node.getName() }
|
||||
|
||||
override string getAttributeName() {
|
||||
result = this.getAttributeNameExpr().asExpr().(StrConst).getText()
|
||||
result = this.getAttributeNameExpr().(CfgNode).getNode().getNode().(StrConst).getText()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -251,7 +254,7 @@ private class GetAttrCallAsAttrRead extends AttrRead, CfgNode {
|
||||
override ExprNode getAttributeNameExpr() { result.asCfgNode() = node.getName() }
|
||||
|
||||
override string getAttributeName() {
|
||||
result = this.getAttributeNameExpr().asExpr().(StrConst).getText()
|
||||
result = this.getAttributeNameExpr().(CfgNode).getNode().getNode().(StrConst).getText()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -60,9 +60,9 @@ module Builtins {
|
||||
* Currently this is an over-approximation, and may not account for things like overwriting a
|
||||
* built-in with a different value.
|
||||
*/
|
||||
DataFlow::Node likelyBuiltin(string name) {
|
||||
DataFlow::CfgNode likelyBuiltin(string name) {
|
||||
exists(Module m |
|
||||
result.asCfgNode() =
|
||||
result.getNode() =
|
||||
any(NameNode n |
|
||||
possible_builtin_accessed_in_module(n, name, m) and
|
||||
not possible_builtin_defined_in_module(name, m)
|
||||
|
||||
@@ -7,15 +7,22 @@
|
||||
private import python
|
||||
private import DataFlowPublic
|
||||
private import semmle.python.SpecialMethods
|
||||
private import FlowSummaryImpl as FlowSummaryImpl
|
||||
|
||||
/** A parameter position represented by an integer. */
|
||||
class ParameterPosition extends int {
|
||||
ParameterPosition() { exists(any(DataFlowCallable c).getParameter(this)) }
|
||||
|
||||
/** Holds if this position represents a positional parameter at position `pos`. */
|
||||
predicate isPositional(int pos) { this = pos } // with the current representation, all parameters are positional
|
||||
}
|
||||
|
||||
/** An argument position represented by an integer. */
|
||||
class ArgumentPosition extends int {
|
||||
ArgumentPosition() { exists(any(DataFlowCall c).getArg(this)) }
|
||||
ArgumentPosition() { this in [-2, -1] or exists(any(Call c).getArg(this)) }
|
||||
|
||||
/** Holds if this position represents a positional argument at position `pos`. */
|
||||
predicate isPositional(int pos) { this = pos } // with the current representation, all arguments are positional
|
||||
}
|
||||
|
||||
/** Holds if arguments at position `apos` match parameters at position `ppos`. */
|
||||
@@ -96,7 +103,7 @@ module ArgumentPassing {
|
||||
* Used to limit the size of predicates.
|
||||
*/
|
||||
predicate connects(CallNode call, CallableValue callable) {
|
||||
exists(DataFlowCall c |
|
||||
exists(NormalCall c |
|
||||
call = c.getNode() and
|
||||
callable = c.getCallable().getCallableValue()
|
||||
)
|
||||
@@ -268,6 +275,18 @@ module ArgumentPassing {
|
||||
|
||||
import ArgumentPassing
|
||||
|
||||
/** A callable defined in library code, identified by a unique string. */
|
||||
abstract class LibraryCallable extends string {
|
||||
bindingset[this]
|
||||
LibraryCallable() { any() }
|
||||
|
||||
/** Gets a call to this library callable. */
|
||||
abstract CallCfgNode getACall();
|
||||
|
||||
/** Gets a data-flow node, where this library callable is used as a call-back. */
|
||||
abstract ArgumentNode getACallback();
|
||||
}
|
||||
|
||||
/**
|
||||
* IPA type for DataFlowCallable.
|
||||
*
|
||||
@@ -282,27 +301,33 @@ newtype TDataFlowCallable =
|
||||
callable instanceof ClassValue
|
||||
} or
|
||||
TLambda(Function lambda) { lambda.isLambda() } or
|
||||
TModule(Module m)
|
||||
TModule(Module m) or
|
||||
TLibraryCallable(LibraryCallable callable)
|
||||
|
||||
/** A callable. */
|
||||
abstract class DataFlowCallable extends TDataFlowCallable {
|
||||
class DataFlowCallable extends TDataFlowCallable {
|
||||
/** Gets a textual representation of this element. */
|
||||
abstract string toString();
|
||||
string toString() { result = "DataFlowCallable" }
|
||||
|
||||
/** Gets a call to this callable. */
|
||||
abstract CallNode getACall();
|
||||
CallNode getACall() { none() }
|
||||
|
||||
/** Gets the scope of this callable */
|
||||
abstract Scope getScope();
|
||||
Scope getScope() { none() }
|
||||
|
||||
/** Gets the specified parameter of this callable */
|
||||
abstract NameNode getParameter(int n);
|
||||
NameNode getParameter(int n) { none() }
|
||||
|
||||
/** Gets the name of this callable. */
|
||||
abstract string getName();
|
||||
string getName() { none() }
|
||||
|
||||
/** Gets a callable value for this callable, if one exists. */
|
||||
abstract CallableValue getCallableValue();
|
||||
/** Gets a callable value for this callable, if any. */
|
||||
CallableValue getCallableValue() { none() }
|
||||
|
||||
/** Gets the underlying library callable, if any. */
|
||||
LibraryCallable asLibraryCallable() { this = TLibraryCallable(result) }
|
||||
|
||||
Location getLocation() { none() }
|
||||
}
|
||||
|
||||
/** A class representing a callable value. */
|
||||
@@ -343,6 +368,8 @@ class DataFlowLambda extends DataFlowCallable, TLambda {
|
||||
override FunctionValue getCallableValue() {
|
||||
result.getOrigin().getNode() = lambda.getDefinition()
|
||||
}
|
||||
|
||||
Expr getDefinition() { result = lambda.getDefinition() }
|
||||
}
|
||||
|
||||
/** A class representing the scope in which a `ModuleVariableNode` appears. */
|
||||
@@ -364,6 +391,27 @@ class DataFlowModuleScope extends DataFlowCallable, TModule {
|
||||
override CallableValue getCallableValue() { none() }
|
||||
}
|
||||
|
||||
class LibraryCallableValue extends DataFlowCallable, TLibraryCallable {
|
||||
LibraryCallable callable;
|
||||
|
||||
LibraryCallableValue() { this = TLibraryCallable(callable) }
|
||||
|
||||
override string toString() { result = callable.toString() }
|
||||
|
||||
override CallNode getACall() { result = callable.getACall().getNode() }
|
||||
|
||||
/** Gets a data-flow node, where this library callable is used as a call-back. */
|
||||
ArgumentNode getACallback() { result = callable.getACallback() }
|
||||
|
||||
override Scope getScope() { none() }
|
||||
|
||||
override NameNode getParameter(int n) { none() }
|
||||
|
||||
override string getName() { result = callable }
|
||||
|
||||
override LibraryCallable asLibraryCallable() { result = callable }
|
||||
}
|
||||
|
||||
/**
|
||||
* IPA type for DataFlowCall.
|
||||
*
|
||||
@@ -379,90 +427,140 @@ class DataFlowModuleScope extends DataFlowCallable, TModule {
|
||||
* TODO: Add `TClassMethodCall` mapping `cls` appropriately.
|
||||
*/
|
||||
newtype TDataFlowCall =
|
||||
TFunctionCall(CallNode call) { call = any(FunctionValue f).getAFunctionCall() } or
|
||||
/** Bound methods need to make room for the explicit self parameter */
|
||||
TMethodCall(CallNode call) { call = any(FunctionValue f).getAMethodCall() } or
|
||||
TClassCall(CallNode call) { call = any(ClassValue c | not c.isAbsent()).getACall() } or
|
||||
TSpecialCall(SpecialMethodCallNode special)
|
||||
/**
|
||||
* Includes function calls, method calls, class calls and library calls.
|
||||
* All these will be associated with a `CallNode`.
|
||||
*/
|
||||
TNormalCall(CallNode call) or
|
||||
/**
|
||||
* Includes calls to special methods.
|
||||
* These will be associated with a `SpecialMethodCallNode`.
|
||||
*/
|
||||
TSpecialCall(SpecialMethodCallNode special) or
|
||||
/** A synthesized call inside a summarized callable */
|
||||
TSummaryCall(FlowSummaryImpl::Public::SummarizedCallable c, Node receiver) {
|
||||
FlowSummaryImpl::Private::summaryCallbackRange(c, receiver)
|
||||
}
|
||||
|
||||
/** A call. */
|
||||
/** A call found in the program source (as opposed to a synthesised summary call). */
|
||||
class TExtractedDataFlowCall = TSpecialCall or TNormalCall;
|
||||
|
||||
/** A call that is taken into account by the global data flow computation. */
|
||||
abstract class DataFlowCall extends TDataFlowCall {
|
||||
/** Gets a textual representation of this element. */
|
||||
abstract string toString();
|
||||
|
||||
/** Get the callable to which this call goes. */
|
||||
/** Get the callable to which this call goes, if such exists. */
|
||||
abstract DataFlowCallable getCallable();
|
||||
|
||||
/**
|
||||
* Gets the argument to this call that will be sent
|
||||
* to the `n`th parameter of the callable.
|
||||
* to the `n`th parameter of the callable, if any.
|
||||
*/
|
||||
abstract Node getArg(int n);
|
||||
|
||||
/** Get the control flow node representing this call. */
|
||||
/** Get the control flow node representing this call, if any. */
|
||||
abstract ControlFlowNode getNode();
|
||||
|
||||
/** Gets the enclosing callable of this call. */
|
||||
abstract DataFlowCallable getEnclosingCallable();
|
||||
|
||||
/** Gets the location of this dataflow call. */
|
||||
Location getLocation() { result = this.getNode().getLocation() }
|
||||
abstract Location getLocation();
|
||||
|
||||
/**
|
||||
* Holds if this element is at the specified location.
|
||||
* The location spans column `startcolumn` of line `startline` to
|
||||
* column `endcolumn` of line `endline` in file `filepath`.
|
||||
* For more information, see
|
||||
* [Locations](https://codeql.github.com/docs/writing-codeql-queries/providing-locations-in-codeql-queries/).
|
||||
*/
|
||||
predicate hasLocationInfo(
|
||||
string filepath, int startline, int startcolumn, int endline, int endcolumn
|
||||
) {
|
||||
this.getLocation().hasLocationInfo(filepath, startline, startcolumn, endline, endcolumn)
|
||||
}
|
||||
}
|
||||
|
||||
/** A call found in the program source (as opposed to a synthesised call). */
|
||||
abstract class ExtractedDataFlowCall extends DataFlowCall, TExtractedDataFlowCall {
|
||||
final override Location getLocation() { result = this.getNode().getLocation() }
|
||||
|
||||
abstract override DataFlowCallable getCallable();
|
||||
|
||||
abstract override Node getArg(int n);
|
||||
|
||||
abstract override ControlFlowNode getNode();
|
||||
}
|
||||
|
||||
/** A call associated with a `CallNode`. */
|
||||
class NormalCall extends ExtractedDataFlowCall, TNormalCall {
|
||||
CallNode call;
|
||||
|
||||
NormalCall() { this = TNormalCall(call) }
|
||||
|
||||
override string toString() { result = call.toString() }
|
||||
|
||||
abstract override Node getArg(int n);
|
||||
|
||||
override CallNode getNode() { result = call }
|
||||
|
||||
abstract override DataFlowCallable getCallable();
|
||||
|
||||
override DataFlowCallable getEnclosingCallable() { result.getScope() = call.getNode().getScope() }
|
||||
}
|
||||
|
||||
/**
|
||||
* A call to a function/lambda.
|
||||
* A call to a function.
|
||||
* This excludes calls to bound methods, classes, and special methods.
|
||||
* Bound method calls and class calls insert an argument for the explicit
|
||||
* `self` parameter, and special method calls have special argument passing.
|
||||
*/
|
||||
class FunctionCall extends DataFlowCall, TFunctionCall {
|
||||
CallNode call;
|
||||
DataFlowCallable callable;
|
||||
class FunctionCall extends NormalCall {
|
||||
DataFlowCallableValue callable;
|
||||
|
||||
FunctionCall() {
|
||||
this = TFunctionCall(call) and
|
||||
call = any(FunctionValue f).getAFunctionCall() and
|
||||
call = callable.getACall()
|
||||
}
|
||||
|
||||
override string toString() { result = call.toString() }
|
||||
override Node getArg(int n) { result = getArg(call, TNoShift(), callable.getCallableValue(), n) }
|
||||
|
||||
override DataFlowCallable getCallable() { result = callable }
|
||||
}
|
||||
|
||||
/** A call to a lambda. */
|
||||
class LambdaCall extends NormalCall {
|
||||
DataFlowLambda callable;
|
||||
|
||||
LambdaCall() {
|
||||
call = callable.getACall() and
|
||||
callable = TLambda(any(Function f))
|
||||
}
|
||||
|
||||
override Node getArg(int n) { result = getArg(call, TNoShift(), callable.getCallableValue(), n) }
|
||||
|
||||
override ControlFlowNode getNode() { result = call }
|
||||
|
||||
override DataFlowCallable getCallable() { result = callable }
|
||||
|
||||
override DataFlowCallable getEnclosingCallable() { result.getScope() = call.getNode().getScope() }
|
||||
}
|
||||
|
||||
/**
|
||||
* Represents a call to a bound method call.
|
||||
* The node representing the instance is inserted as argument to the `self` parameter.
|
||||
*/
|
||||
class MethodCall extends DataFlowCall, TMethodCall {
|
||||
CallNode call;
|
||||
class MethodCall extends NormalCall {
|
||||
FunctionValue bm;
|
||||
|
||||
MethodCall() {
|
||||
this = TMethodCall(call) and
|
||||
call = bm.getACall()
|
||||
}
|
||||
MethodCall() { call = bm.getAMethodCall() }
|
||||
|
||||
private CallableValue getCallableValue() { result = bm }
|
||||
|
||||
override string toString() { result = call.toString() }
|
||||
|
||||
override Node getArg(int n) {
|
||||
n > 0 and result = getArg(call, TShiftOneUp(), this.getCallableValue(), n)
|
||||
or
|
||||
n = 0 and result = TCfgNode(call.getFunction().(AttrNode).getObject())
|
||||
}
|
||||
|
||||
override ControlFlowNode getNode() { result = call }
|
||||
|
||||
override DataFlowCallable getCallable() { result = TCallableValue(this.getCallableValue()) }
|
||||
|
||||
override DataFlowCallable getEnclosingCallable() { result.getScope() = call.getScope() }
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -471,34 +569,27 @@ class MethodCall extends DataFlowCall, TMethodCall {
|
||||
* That makes the call node be the post-update node holding the value of the object
|
||||
* after the constructor has run.
|
||||
*/
|
||||
class ClassCall extends DataFlowCall, TClassCall {
|
||||
CallNode call;
|
||||
class ClassCall extends NormalCall {
|
||||
ClassValue c;
|
||||
|
||||
ClassCall() {
|
||||
this = TClassCall(call) and
|
||||
not c.isAbsent() and
|
||||
call = c.getACall()
|
||||
}
|
||||
|
||||
private CallableValue getCallableValue() { c.getScope().getInitMethod() = result.getScope() }
|
||||
|
||||
override string toString() { result = call.toString() }
|
||||
|
||||
override Node getArg(int n) {
|
||||
n > 0 and result = getArg(call, TShiftOneUp(), this.getCallableValue(), n)
|
||||
or
|
||||
n = 0 and result = TSyntheticPreUpdateNode(TCfgNode(call))
|
||||
}
|
||||
|
||||
override ControlFlowNode getNode() { result = call }
|
||||
|
||||
override DataFlowCallable getCallable() { result = TCallableValue(this.getCallableValue()) }
|
||||
|
||||
override DataFlowCallable getEnclosingCallable() { result.getScope() = call.getScope() }
|
||||
}
|
||||
|
||||
/** A call to a special method. */
|
||||
class SpecialCall extends DataFlowCall, TSpecialCall {
|
||||
class SpecialCall extends ExtractedDataFlowCall, TSpecialCall {
|
||||
SpecialMethodCallNode special;
|
||||
|
||||
SpecialCall() { this = TSpecialCall(special) }
|
||||
@@ -518,8 +609,172 @@ class SpecialCall extends DataFlowCall, TSpecialCall {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A call to a summarized callable, a `LibraryCallable`.
|
||||
*
|
||||
* We currently exclude all resolved calls. This means that a call to, say, `map`, which
|
||||
* is a `ClassCall`, cannot currently be given a summary.
|
||||
* We hope to lift this restriction in the future and include all potential calls to summaries
|
||||
* in this class.
|
||||
*/
|
||||
class LibraryCall extends NormalCall {
|
||||
LibraryCall() {
|
||||
// TODO: share this with `resolvedCall`
|
||||
not (
|
||||
call = any(DataFlowCallableValue cv).getACall()
|
||||
or
|
||||
call = any(DataFlowLambda l).getACall()
|
||||
or
|
||||
// TODO: this should be covered by `DataFlowCallableValue`, but a `ClassValue` is not a `CallableValue`.
|
||||
call = any(ClassValue c).getACall()
|
||||
)
|
||||
}
|
||||
|
||||
// TODO: Implement Python calling convention?
|
||||
override Node getArg(int n) { result = TCfgNode(call.getArg(n)) }
|
||||
|
||||
// We cannot refer to a `LibraryCallable` here,
|
||||
// as that could in turn refer to type tracking.
|
||||
// This call will be tied to a `LibraryCallable` via
|
||||
// `getViableCallabe` when the global data flow is assembled.
|
||||
override DataFlowCallable getCallable() { none() }
|
||||
}
|
||||
|
||||
/**
|
||||
* A synthesized call inside a callable with a flow summary.
|
||||
*
|
||||
* For example, in
|
||||
* ```python
|
||||
* map(lambda x: x + 1, [1, 2, 3])
|
||||
* ```
|
||||
*
|
||||
* there is a synthesized call to the lambda argument inside `map`.
|
||||
*/
|
||||
class SummaryCall extends DataFlowCall, TSummaryCall {
|
||||
private FlowSummaryImpl::Public::SummarizedCallable c;
|
||||
private Node receiver;
|
||||
|
||||
SummaryCall() { this = TSummaryCall(c, receiver) }
|
||||
|
||||
/** Gets the data flow node that this call targets. */
|
||||
Node getReceiver() { result = receiver }
|
||||
|
||||
override DataFlowCallable getEnclosingCallable() { result.asLibraryCallable() = c }
|
||||
|
||||
override DataFlowCallable getCallable() { none() }
|
||||
|
||||
override Node getArg(int n) { none() }
|
||||
|
||||
override ControlFlowNode getNode() { none() }
|
||||
|
||||
override string toString() { result = "[summary] call to " + receiver + " in " + c }
|
||||
|
||||
override Location getLocation() { none() }
|
||||
}
|
||||
|
||||
/**
|
||||
* The value of a parameter at function entry, viewed as a node in a data
|
||||
* flow graph.
|
||||
*/
|
||||
abstract class ParameterNodeImpl extends Node {
|
||||
abstract Parameter getParameter();
|
||||
|
||||
/**
|
||||
* Holds if this node is the parameter of callable `c` at the
|
||||
* (zero-based) index `i`.
|
||||
*/
|
||||
abstract predicate isParameterOf(DataFlowCallable c, int i);
|
||||
}
|
||||
|
||||
/** A parameter for a library callable with a flow summary. */
|
||||
class SummaryParameterNode extends ParameterNodeImpl, TSummaryParameterNode {
|
||||
private FlowSummaryImpl::Public::SummarizedCallable sc;
|
||||
private int pos;
|
||||
|
||||
SummaryParameterNode() { this = TSummaryParameterNode(sc, pos) }
|
||||
|
||||
override Parameter getParameter() { none() }
|
||||
|
||||
override predicate isParameterOf(DataFlowCallable c, int i) {
|
||||
sc = c.asLibraryCallable() and i = pos
|
||||
}
|
||||
|
||||
override DataFlowCallable getEnclosingCallable() { result.asLibraryCallable() = sc }
|
||||
|
||||
override string toString() { result = "parameter " + pos + " of " + sc }
|
||||
|
||||
// Hack to return "empty location"
|
||||
override predicate hasLocationInfo(
|
||||
string file, int startline, int startcolumn, int endline, int endcolumn
|
||||
) {
|
||||
file = "" and
|
||||
startline = 0 and
|
||||
startcolumn = 0 and
|
||||
endline = 0 and
|
||||
endcolumn = 0
|
||||
}
|
||||
}
|
||||
|
||||
/** A data-flow node used to model flow summaries. */
|
||||
class SummaryNode extends Node, TSummaryNode {
|
||||
private FlowSummaryImpl::Public::SummarizedCallable c;
|
||||
private FlowSummaryImpl::Private::SummaryNodeState state;
|
||||
|
||||
SummaryNode() { this = TSummaryNode(c, state) }
|
||||
|
||||
override DataFlowCallable getEnclosingCallable() { result.asLibraryCallable() = c }
|
||||
|
||||
override string toString() { result = "[summary] " + state + " in " + c }
|
||||
|
||||
// Hack to return "empty location"
|
||||
override predicate hasLocationInfo(
|
||||
string file, int startline, int startcolumn, int endline, int endcolumn
|
||||
) {
|
||||
file = "" and
|
||||
startline = 0 and
|
||||
startcolumn = 0 and
|
||||
endline = 0 and
|
||||
endcolumn = 0
|
||||
}
|
||||
}
|
||||
|
||||
private class SummaryReturnNode extends SummaryNode, ReturnNode {
|
||||
private ReturnKind rk;
|
||||
|
||||
SummaryReturnNode() { FlowSummaryImpl::Private::summaryReturnNode(this, rk) }
|
||||
|
||||
override ReturnKind getKind() { result = rk }
|
||||
}
|
||||
|
||||
private class SummaryArgumentNode extends SummaryNode, ArgumentNode {
|
||||
SummaryArgumentNode() { FlowSummaryImpl::Private::summaryArgumentNode(_, this, _) }
|
||||
|
||||
override predicate argumentOf(DataFlowCall call, ArgumentPosition pos) {
|
||||
FlowSummaryImpl::Private::summaryArgumentNode(call, this, pos)
|
||||
}
|
||||
}
|
||||
|
||||
private class SummaryPostUpdateNode extends SummaryNode, PostUpdateNode {
|
||||
private Node pre;
|
||||
|
||||
SummaryPostUpdateNode() { FlowSummaryImpl::Private::summaryPostUpdateNode(this, pre) }
|
||||
|
||||
override Node getPreUpdateNode() { result = pre }
|
||||
}
|
||||
|
||||
/** Gets a viable run-time target for the call `call`. */
|
||||
DataFlowCallable viableCallable(DataFlowCall call) { result = call.getCallable() }
|
||||
DataFlowCallable viableCallable(ExtractedDataFlowCall call) {
|
||||
result = call.getCallable()
|
||||
or
|
||||
// A call to a library callable with a flow summary
|
||||
// In this situation we can not resolve the callable from the call,
|
||||
// as that would make data flow depend on type tracking.
|
||||
// Instead we reolve the call from the summary.
|
||||
exists(LibraryCallable callable |
|
||||
result = TLibraryCallable(callable) and
|
||||
call.getNode() = callable.getACall().getNode()
|
||||
)
|
||||
}
|
||||
|
||||
private newtype TReturnKind = TNormalReturnKind()
|
||||
|
||||
@@ -533,26 +788,51 @@ class ReturnKind extends TReturnKind {
|
||||
}
|
||||
|
||||
/** A data flow node that represents a value returned by a callable. */
|
||||
class ReturnNode extends CfgNode {
|
||||
Return ret;
|
||||
|
||||
// See `TaintTrackingImplementation::returnFlowStep`
|
||||
ReturnNode() { node = ret.getValue().getAFlowNode() }
|
||||
|
||||
abstract class ReturnNode extends Node {
|
||||
/** Gets the kind of this return node. */
|
||||
ReturnKind getKind() { any() }
|
||||
}
|
||||
|
||||
/** A data flow node that represents the output of a call. */
|
||||
class OutNode extends CfgNode {
|
||||
OutNode() { node instanceof CallNode }
|
||||
/** A data flow node that represents a value returned by a callable. */
|
||||
class ExtractedReturnNode extends ReturnNode, CfgNode {
|
||||
// See `TaintTrackingImplementation::returnFlowStep`
|
||||
ExtractedReturnNode() { node = any(Return ret).getValue().getAFlowNode() }
|
||||
|
||||
override ReturnKind getKind() { any() }
|
||||
}
|
||||
|
||||
/** A data-flow node that represents the output of a call. */
|
||||
abstract class OutNode extends Node {
|
||||
/** Gets the underlying call, where this node is a corresponding output of kind `kind`. */
|
||||
abstract DataFlowCall getCall(ReturnKind kind);
|
||||
}
|
||||
|
||||
private module OutNodes {
|
||||
/**
|
||||
* A data-flow node that reads a value returned directly by a callable.
|
||||
*/
|
||||
class ExprOutNode extends OutNode, ExprNode {
|
||||
private DataFlowCall call;
|
||||
|
||||
ExprOutNode() { call.(ExtractedDataFlowCall).getNode() = this.getNode() }
|
||||
|
||||
override DataFlowCall getCall(ReturnKind kind) {
|
||||
result = call and
|
||||
kind = kind
|
||||
}
|
||||
}
|
||||
|
||||
private class SummaryOutNode extends SummaryNode, OutNode {
|
||||
SummaryOutNode() { FlowSummaryImpl::Private::summaryOutNode(_, this, _) }
|
||||
|
||||
override DataFlowCall getCall(ReturnKind kind) {
|
||||
FlowSummaryImpl::Private::summaryOutNode(result, this, kind)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a node that can read the value returned from `call` with return kind
|
||||
* `kind`.
|
||||
*/
|
||||
OutNode getAnOutNode(DataFlowCall call, ReturnKind kind) {
|
||||
call.getNode() = result.getNode() and
|
||||
kind = TNormalReturnKind()
|
||||
}
|
||||
OutNode getAnOutNode(DataFlowCall call, ReturnKind kind) { call = result.getCall(kind) }
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
private import python
|
||||
private import DataFlowPublic
|
||||
private import semmle.python.essa.SsaCompute
|
||||
private import semmle.python.dataflow.new.internal.ImportStar
|
||||
private import semmle.python.dataflow.new.internal.ImportResolution
|
||||
private import FlowSummaryImpl as FlowSummaryImpl
|
||||
// Since we allow extra data-flow steps from modeled frameworks, we import these
|
||||
// up-front, to ensure these are included. This provides a more seamless experience from
|
||||
// a user point of view, since they don't need to know they need to import a specific
|
||||
@@ -21,7 +22,7 @@ import DataFlowDispatchPointsTo
|
||||
DataFlowCallable nodeGetEnclosingCallable(Node n) { result = n.getEnclosingCallable() }
|
||||
|
||||
/** Holds if `p` is a `ParameterNode` of `c` with position `pos`. */
|
||||
predicate isParameterNode(ParameterNode p, DataFlowCallable c, ParameterPosition pos) {
|
||||
predicate isParameterNode(ParameterNodeImpl p, DataFlowCallable c, ParameterPosition pos) {
|
||||
p.isParameterOf(c, pos)
|
||||
}
|
||||
|
||||
@@ -77,7 +78,7 @@ module SyntheticPreUpdateNode {
|
||||
* that is mapped to the `self` parameter. That way, constructor calls represent the value of the
|
||||
* object after the constructor (currently only `__init__`) has run.
|
||||
*/
|
||||
CfgNode objectCreationNode() { result.getNode().(CallNode) = any(ClassCall c).getNode() }
|
||||
CfgNode objectCreationNode() { result.getNode() = any(ClassCall c).getNode() }
|
||||
}
|
||||
|
||||
import SyntheticPreUpdateNode
|
||||
@@ -87,6 +88,8 @@ deprecated module syntheticPostUpdateNode = SyntheticPostUpdateNode;
|
||||
|
||||
/** A module collecting the different reasons for synthesising a post-update node. */
|
||||
module SyntheticPostUpdateNode {
|
||||
private import semmle.python.SpecialMethods
|
||||
|
||||
/** A post-update node is synthesized for all nodes which satisfy `NeedsSyntheticPostUpdateNode`. */
|
||||
class SyntheticPostUpdateNode extends PostUpdateNode, TSyntheticPostUpdateNode {
|
||||
NeedsSyntheticPostUpdateNode pre;
|
||||
@@ -136,6 +139,8 @@ module SyntheticPostUpdateNode {
|
||||
Node argumentPreUpdateNode() {
|
||||
result = any(FunctionCall c).getArg(_)
|
||||
or
|
||||
result = any(LambdaCall c).getArg(_)
|
||||
or
|
||||
// Avoid argument 0 of method calls as those have read post-update nodes.
|
||||
exists(MethodCall c, int n | n > 0 | result = c.getArg(n))
|
||||
or
|
||||
@@ -145,11 +150,18 @@ module SyntheticPostUpdateNode {
|
||||
exists(ClassCall c, int n | n > 0 | result = c.getArg(n))
|
||||
or
|
||||
// any argument of any call that we have not been able to resolve
|
||||
exists(CallNode call | not call = any(DataFlowCall c).getNode() |
|
||||
exists(CallNode call | not resolvedCall(call) |
|
||||
result.(CfgNode).getNode() in [call.getArg(_), call.getArgByName(_)]
|
||||
)
|
||||
}
|
||||
|
||||
/** Holds if `call` can be resolved as a normal call */
|
||||
private predicate resolvedCall(CallNode call) {
|
||||
call = any(DataFlowCallableValue cv).getACall()
|
||||
or
|
||||
call = any(DataFlowLambda l).getACall()
|
||||
}
|
||||
|
||||
/** Gets the pre-update node associated with a store. This is used for when an object might have its value changed after a store. */
|
||||
CfgNode storePreUpdateNode() {
|
||||
exists(Attribute a |
|
||||
@@ -287,10 +299,22 @@ module EssaFlow {
|
||||
* This is the local flow predicate that is used as a building block in global
|
||||
* data flow.
|
||||
*
|
||||
* It includes flow steps from flow summaries.
|
||||
*/
|
||||
predicate simpleLocalFlowStep(Node nodeFrom, Node nodeTo) {
|
||||
simpleLocalFlowStepForTypetracking(nodeFrom, nodeTo)
|
||||
or
|
||||
summaryFlowSteps(nodeFrom, nodeTo)
|
||||
}
|
||||
|
||||
/**
|
||||
* This is the local flow predicate that is used as a building block in
|
||||
* type tracking, it does _not_ include steps from flow summaries.
|
||||
*
|
||||
* Local flow can happen either at import time, when the module is initialised
|
||||
* or at runtime when callables in the module are called.
|
||||
*/
|
||||
predicate simpleLocalFlowStep(Node nodeFrom, Node nodeTo) {
|
||||
predicate simpleLocalFlowStepForTypetracking(Node nodeFrom, Node nodeTo) {
|
||||
// If there is local flow out of a node `node`, we want flow
|
||||
// both out of `node` and any post-update node of `node`.
|
||||
exists(Node node |
|
||||
@@ -326,6 +350,34 @@ predicate runtimeLocalFlowStep(Node nodeFrom, Node nodeTo) {
|
||||
EssaFlow::essaFlowStep(nodeFrom, nodeTo)
|
||||
}
|
||||
|
||||
predicate summaryFlowSteps(Node nodeFrom, Node nodeTo) {
|
||||
// If there is local flow out of a node `node`, we want flow
|
||||
// both out of `node` and any post-update node of `node`.
|
||||
exists(Node node |
|
||||
nodeFrom = update(node) and
|
||||
(
|
||||
importTimeSummaryFlowStep(node, nodeTo) or
|
||||
runtimeSummaryFlowStep(node, nodeTo)
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
predicate importTimeSummaryFlowStep(Node nodeFrom, Node nodeTo) {
|
||||
// As a proxy for whether statements can be executed at import time,
|
||||
// we check if they appear at the top level.
|
||||
// This will miss statements inside functions called from the top level.
|
||||
isTopLevel(nodeFrom) and
|
||||
isTopLevel(nodeTo) and
|
||||
FlowSummaryImpl::Private::Steps::summaryLocalStep(nodeFrom, nodeTo, true)
|
||||
}
|
||||
|
||||
predicate runtimeSummaryFlowStep(Node nodeFrom, Node nodeTo) {
|
||||
// Anything not at the top level can be executed at runtime.
|
||||
not isTopLevel(nodeFrom) and
|
||||
not isTopLevel(nodeTo) and
|
||||
FlowSummaryImpl::Private::Steps::summaryLocalStep(nodeFrom, nodeTo, true)
|
||||
}
|
||||
|
||||
/** `ModuleVariable`s are accessed via jump steps at runtime. */
|
||||
predicate runtimeJumpStep(Node nodeFrom, Node nodeTo) {
|
||||
// Module variable read
|
||||
@@ -335,11 +387,7 @@ predicate runtimeJumpStep(Node nodeFrom, Node nodeTo) {
|
||||
nodeFrom = nodeTo.(ModuleVariableNode).getAWrite()
|
||||
or
|
||||
// Setting the possible values of the variable at the end of import time
|
||||
exists(SsaVariable def |
|
||||
def = any(SsaVariable var).getAnUltimateDefinition() and
|
||||
def.getDefinition() = nodeFrom.asCfgNode() and
|
||||
def.getVariable() = nodeTo.(ModuleVariableNode).getVariable()
|
||||
)
|
||||
nodeFrom = nodeTo.(ModuleVariableNode).getADefiningWrite()
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -423,9 +471,9 @@ predicate jumpStepSharedWithTypeTracker(Node nodeFrom, Node nodeTo) {
|
||||
runtimeJumpStep(nodeFrom, nodeTo)
|
||||
or
|
||||
// Read of module attribute:
|
||||
exists(AttrRead r, ModuleValue mv |
|
||||
r.getObject().asCfgNode().pointsTo(mv) and
|
||||
module_export(mv.getScope(), r.getAttributeName(), nodeFrom) and
|
||||
exists(AttrRead r |
|
||||
ImportResolution::module_export(ImportResolution::getModule(r.getObject()),
|
||||
r.getAttributeName(), nodeFrom) and
|
||||
nodeTo = r
|
||||
)
|
||||
or
|
||||
@@ -449,22 +497,6 @@ predicate jumpStepNotSharedWithTypeTracker(Node nodeFrom, Node nodeTo) {
|
||||
any(Orm::AdditionalOrmSteps es).jumpStep(nodeFrom, nodeTo)
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if the module `m` defines a name `name` by assigning `defn` to it. This is an
|
||||
* overapproximation, as `name` may not in fact be exported (e.g. by defining an `__all__` that does
|
||||
* not include `name`).
|
||||
*/
|
||||
private predicate module_export(Module m, string name, CfgNode defn) {
|
||||
exists(EssaVariable v |
|
||||
v.getName() = name and
|
||||
v.getAUse() = ImportStar::getStarImported*(m).getANormalExit()
|
||||
|
|
||||
defn.getNode() = v.getDefinition().(AssignmentDefinition).getValue()
|
||||
or
|
||||
defn.getNode() = v.getDefinition().(ArgumentRefinement).getArgument()
|
||||
)
|
||||
}
|
||||
|
||||
//--------
|
||||
// Field flow
|
||||
//--------
|
||||
@@ -494,6 +526,8 @@ predicate storeStep(Node nodeFrom, Content c, Node nodeTo) {
|
||||
matchStoreStep(nodeFrom, c, nodeTo)
|
||||
or
|
||||
any(Orm::AdditionalOrmSteps es).storeStep(nodeFrom, c, nodeTo)
|
||||
or
|
||||
FlowSummaryImpl::Private::Steps::summaryStoreStep(nodeFrom, c, nodeTo)
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -687,6 +721,8 @@ predicate readStep(Node nodeFrom, Content c, Node nodeTo) {
|
||||
attributeReadStep(nodeFrom, c, nodeTo)
|
||||
or
|
||||
kwUnpackReadStep(nodeFrom, c, nodeTo)
|
||||
or
|
||||
FlowSummaryImpl::Private::Steps::summaryReadStep(nodeFrom, c, nodeTo)
|
||||
}
|
||||
|
||||
/** Data flows from a sequence to a subscript of the sequence. */
|
||||
@@ -811,6 +847,8 @@ predicate clearsContent(Node n, Content c) {
|
||||
matchClearStep(n, c)
|
||||
or
|
||||
attributeClearStep(n, c)
|
||||
or
|
||||
FlowSummaryImpl::Private::Steps::summaryClearsContent(n, c)
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -862,15 +900,38 @@ int accessPathLimit() { result = 5 }
|
||||
predicate forceHighPrecision(Content c) { none() }
|
||||
|
||||
/** Holds if `n` should be hidden from path explanations. */
|
||||
predicate nodeIsHidden(Node n) { none() }
|
||||
predicate nodeIsHidden(Node n) {
|
||||
n instanceof SummaryNode
|
||||
or
|
||||
n instanceof SummaryParameterNode
|
||||
}
|
||||
|
||||
class LambdaCallKind = Unit;
|
||||
|
||||
/** Holds if `creation` is an expression that creates a lambda of kind `kind` for `c`. */
|
||||
predicate lambdaCreation(Node creation, LambdaCallKind kind, DataFlowCallable c) { none() }
|
||||
predicate lambdaCreation(Node creation, LambdaCallKind kind, DataFlowCallable c) {
|
||||
// lambda
|
||||
kind = kind and
|
||||
creation.asExpr() = c.(DataFlowLambda).getDefinition()
|
||||
or
|
||||
// normal function
|
||||
exists(FunctionDef def |
|
||||
def.defines(creation.asVar().getSourceVariable()) and
|
||||
def.getDefinedFunction() = c.(DataFlowCallableValue).getCallableValue().getScope()
|
||||
)
|
||||
or
|
||||
// summarized function
|
||||
exists(Call call |
|
||||
creation.asExpr() = call.getAnArg() and
|
||||
creation = c.(LibraryCallableValue).getACallback()
|
||||
)
|
||||
}
|
||||
|
||||
/** Holds if `call` is a lambda call of kind `kind` where `receiver` is the lambda expression. */
|
||||
predicate lambdaCall(DataFlowCall call, LambdaCallKind kind, Node receiver) { none() }
|
||||
predicate lambdaCall(DataFlowCall call, LambdaCallKind kind, Node receiver) {
|
||||
receiver = call.(SummaryCall).getReceiver() and
|
||||
exists(kind)
|
||||
}
|
||||
|
||||
/** Extra data-flow steps needed for lambda flow analysis. */
|
||||
predicate additionalLambdaFlowStep(Node nodeFrom, Node nodeTo, boolean preservesValue) { none() }
|
||||
@@ -882,4 +943,6 @@ predicate additionalLambdaFlowStep(Node nodeFrom, Node nodeTo, boolean preserves
|
||||
* One example would be to allow flow like `p.foo = p.bar;`, which is disallowed
|
||||
* by default as a heuristic.
|
||||
*/
|
||||
predicate allowParameterReturnInSelf(ParameterNode p) { none() }
|
||||
predicate allowParameterReturnInSelf(ParameterNode p) {
|
||||
FlowSummaryImpl::Private::summaryAllowParameterReturnInSelf(p)
|
||||
}
|
||||
|
||||
@@ -9,6 +9,7 @@ import Attributes
|
||||
import LocalSources
|
||||
private import semmle.python.essa.SsaCompute
|
||||
private import semmle.python.dataflow.new.internal.ImportStar
|
||||
private import FlowSummaryImpl as FlowSummaryImpl
|
||||
|
||||
/**
|
||||
* IPA type for data flow nodes.
|
||||
@@ -100,7 +101,17 @@ newtype TNode =
|
||||
//
|
||||
// So for now we live with having these synthetic ORM nodes for _all_ classes, which
|
||||
// is a bit wasteful, but we don't think it will hurt too much.
|
||||
TSyntheticOrmModelNode(Class cls)
|
||||
TSyntheticOrmModelNode(Class cls) or
|
||||
TSummaryNode(
|
||||
FlowSummaryImpl::Public::SummarizedCallable c, FlowSummaryImpl::Private::SummaryNodeState state
|
||||
) {
|
||||
FlowSummaryImpl::Private::summaryNodeRange(c, state)
|
||||
} or
|
||||
TSummaryParameterNode(FlowSummaryImpl::Public::SummarizedCallable c, ParameterPosition pos) {
|
||||
FlowSummaryImpl::Private::summaryParameterNodeRange(c, pos)
|
||||
}
|
||||
|
||||
class TParameterNode = TCfgNode or TSummaryParameterNode;
|
||||
|
||||
/** Helper for `Node::getEnclosingCallable`. */
|
||||
private DataFlowCallable getCallableScope(Scope s) {
|
||||
@@ -277,40 +288,56 @@ ExprNode exprNode(DataFlowExpr e) { result.getNode().getNode() = e }
|
||||
* The value of a parameter at function entry, viewed as a node in a data
|
||||
* flow graph.
|
||||
*/
|
||||
class ParameterNode extends CfgNode, LocalSourceNode {
|
||||
class ParameterNode extends Node, TParameterNode instanceof ParameterNodeImpl {
|
||||
/** Gets the parameter corresponding to this node, if any. */
|
||||
final Parameter getParameter() { result = super.getParameter() }
|
||||
}
|
||||
|
||||
/** A parameter node found in the source code (not in a summary). */
|
||||
class ExtractedParameterNode extends ParameterNodeImpl, CfgNode {
|
||||
//, LocalSourceNode {
|
||||
ParameterDefinition def;
|
||||
|
||||
ParameterNode() {
|
||||
ExtractedParameterNode() {
|
||||
node = def.getDefiningNode() and
|
||||
// Disregard parameters that we cannot resolve
|
||||
// TODO: Make this unnecessary
|
||||
exists(DataFlowCallable c | node = c.getParameter(_))
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if this node is the parameter of callable `c` at the
|
||||
* (zero-based) index `i`.
|
||||
*/
|
||||
predicate isParameterOf(DataFlowCallable c, int i) { node = c.getParameter(i) }
|
||||
override predicate isParameterOf(DataFlowCallable c, int i) { node = c.getParameter(i) }
|
||||
|
||||
override DataFlowCallable getEnclosingCallable() { this.isParameterOf(result, _) }
|
||||
|
||||
/** Gets the `Parameter` this `ParameterNode` represents. */
|
||||
Parameter getParameter() { result = def.getParameter() }
|
||||
override Parameter getParameter() { result = def.getParameter() }
|
||||
}
|
||||
|
||||
class LocalSourceParameterNode extends ExtractedParameterNode, LocalSourceNode { }
|
||||
|
||||
/** Gets a node corresponding to parameter `p`. */
|
||||
ParameterNode parameterNode(Parameter p) { result.getParameter() = p }
|
||||
ExtractedParameterNode parameterNode(Parameter p) { result.getParameter() = p }
|
||||
|
||||
/** A data flow node that represents a call argument. */
|
||||
class ArgumentNode extends Node {
|
||||
ArgumentNode() { this = any(DataFlowCall c).getArg(_) }
|
||||
|
||||
abstract class ArgumentNode extends Node {
|
||||
/** Holds if this argument occurs at the given position in the given call. */
|
||||
predicate argumentOf(DataFlowCall call, int pos) { this = call.getArg(pos) }
|
||||
abstract predicate argumentOf(DataFlowCall call, ArgumentPosition pos);
|
||||
|
||||
/** Gets the call in which this node is an argument. */
|
||||
final DataFlowCall getCall() { this.argumentOf(result, _) }
|
||||
/** Gets the call in which this node is an argument, if any. */
|
||||
final ExtractedDataFlowCall getCall() { this.argumentOf(result, _) }
|
||||
}
|
||||
|
||||
/** A data flow node that represents a call argument found in the source code. */
|
||||
class ExtractedArgumentNode extends ArgumentNode {
|
||||
ExtractedArgumentNode() { this = any(ExtractedDataFlowCall c).getArg(_) }
|
||||
|
||||
final override predicate argumentOf(DataFlowCall call, ArgumentPosition pos) {
|
||||
this.extractedArgumentOf(call, pos)
|
||||
}
|
||||
|
||||
predicate extractedArgumentOf(ExtractedDataFlowCall call, ArgumentPosition pos) {
|
||||
this = call.getArg(pos)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -390,7 +417,16 @@ class ModuleVariableNode extends Node, TModuleVariableNode {
|
||||
|
||||
/** Gets an `EssaNode` that corresponds to an assignment of this global variable. */
|
||||
EssaNode getAWrite() {
|
||||
result.asVar().getDefinition().(EssaNodeDefinition).definedBy(var, any(DefinitionNode defn))
|
||||
result.getVar().getDefinition().(EssaNodeDefinition).definedBy(var, any(DefinitionNode defn))
|
||||
}
|
||||
|
||||
/** Gets the possible values of the variable at the end of import time */
|
||||
CfgNode getADefiningWrite() {
|
||||
exists(SsaVariable def |
|
||||
def = any(SsaVariable ssa_var).getAnUltimateDefinition() and
|
||||
def.getDefinition() = result.asCfgNode() and
|
||||
def.getVariable() = var
|
||||
)
|
||||
}
|
||||
|
||||
override DataFlowCallable getEnclosingCallable() { result.(DataFlowModuleScope).getScope() = mod }
|
||||
|
||||
@@ -5,12 +5,20 @@
|
||||
private import python
|
||||
private import DataFlowPrivate
|
||||
import DataFlowPublic
|
||||
private import FlowSummaryImpl as FlowSummaryImpl
|
||||
|
||||
/**
|
||||
* Holds if data flows from `nodeFrom` to `nodeTo` in exactly one local
|
||||
* (intra-procedural) step.
|
||||
*/
|
||||
predicate localFlowStep(Node nodeFrom, Node nodeTo) { simpleLocalFlowStep(nodeFrom, nodeTo) }
|
||||
predicate localFlowStep(Node nodeFrom, Node nodeTo) {
|
||||
simpleLocalFlowStep(nodeFrom, nodeTo)
|
||||
or
|
||||
// Simple flow through library code is included in the exposed local
|
||||
// step relation, even though flow is technically inter-procedural.
|
||||
// This is a convention followed across languages.
|
||||
FlowSummaryImpl::Private::Steps::summaryThroughStepValue(nodeFrom, nodeTo, _)
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if data flows from `source` to `sink` in zero or more local
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,225 @@
|
||||
/**
|
||||
* Provides Python specific classes and predicates for defining flow summaries.
|
||||
*
|
||||
* Flow summaries are defined for callables that are not extracted.
|
||||
* Such callables go by different names in different parts of our codebase:
|
||||
*
|
||||
* - in `FlowSummary.qll`, which is user facing, they are called `SummarizedCallable`s.
|
||||
* These contain summaries, implemented by the user via the predicates `propagatesFlow` and `propagatesFlowExt`.
|
||||
*
|
||||
* - in the data flow layer, they are called `LibraryCallable`s (as in the Ruby codebase).
|
||||
* These are identified by strings and has predicates for finding calls to them.
|
||||
*
|
||||
* Having both extracted and non-extracted callables means that we now have three types of calls:
|
||||
* - Extracted calls to extracted callables, either `NormalCall` or `SpecialCall`. These are handled by standard data flow.
|
||||
* - Extracted calls to non-extracted callables, `LibraryCall`. These are handled by loking up the relevant summary when the
|
||||
* global data flwo graph is connected up via `getViableCallable`.
|
||||
* - Non-extracted calls, `SummaryCall`. These are synthesised by the flow summary framework.
|
||||
*
|
||||
* The first two can be referred to as `ExtractedDataFlowCall`. In fact, `LibraryCall` is a subclass of `NormalCall`, where
|
||||
* `getCallable` is set to `none()`. The member predicate `ExtractedDataFlowCall::getCallable` is _not_ the mechanism for
|
||||
* call resolution in global data flow. That mechanism is `getViableCallable`.
|
||||
* Resolving a call to a non-extracted callable goes via `LibraryCallable::getACall`, which may involve type tracking.
|
||||
* To avoid that type tracking becomes mutualy recursive with data flow, type tracking must use a call graph not including summaries.
|
||||
* Type tracking sees the callgraph given by `ExtractedDataFlowCall::getACallable`.
|
||||
*
|
||||
* We do not support summaries of special methods via the special methods framework,
|
||||
* the summary would have to identify the call.
|
||||
*
|
||||
* We might, while we still extract the standard library, want to support flow summaries of
|
||||
* extracted callables, so that we can model part of the standard library with flow summaries.
|
||||
* For this to work, we have be careful with the enclosing callable predicate.
|
||||
*/
|
||||
|
||||
private import python
|
||||
private import DataFlowPrivate
|
||||
private import DataFlowPublic
|
||||
private import DataFlowImplCommon
|
||||
private import FlowSummaryImpl::Private
|
||||
private import FlowSummaryImpl::Public
|
||||
private import semmle.python.dataflow.new.FlowSummary as FlowSummary
|
||||
|
||||
class SummarizedCallableBase = string;
|
||||
|
||||
/** View a `SummarizedCallable` as a `DataFlowCallable`. */
|
||||
DataFlowCallable inject(SummarizedCallable c) { result.asLibraryCallable() = c }
|
||||
|
||||
/** Gets the parameter position of the instance parameter. */
|
||||
ArgumentPosition instanceParameterPosition() { none() } // disables implicit summary flow to `this` for callbacks
|
||||
|
||||
/** Gets the synthesized summary data-flow node for the given values. */
|
||||
Node summaryNode(SummarizedCallable c, SummaryNodeState state) { result = TSummaryNode(c, state) }
|
||||
|
||||
/** Gets the synthesized data-flow call for `receiver`. */
|
||||
SummaryCall summaryDataFlowCall(Node receiver) { receiver = result.getReceiver() }
|
||||
|
||||
/** Gets the type of content `c`. */
|
||||
DataFlowType getContentType(Content c) { any() }
|
||||
|
||||
/** Gets the return type of kind `rk` for callable `c`. */
|
||||
bindingset[c, rk]
|
||||
DataFlowType getReturnType(SummarizedCallable c, ReturnKind rk) { any() }
|
||||
|
||||
/**
|
||||
* Gets the type of the `i`th parameter in a synthesized call that targets a
|
||||
* callback of type `t`.
|
||||
*/
|
||||
bindingset[t, i]
|
||||
DataFlowType getCallbackParameterType(DataFlowType t, int i) { any() }
|
||||
|
||||
/**
|
||||
* Gets the return type of kind `rk` in a synthesized call that targets a
|
||||
* callback of type `t`.
|
||||
*/
|
||||
DataFlowType getCallbackReturnType(DataFlowType t, ReturnKind rk) { any() }
|
||||
|
||||
/**
|
||||
* Holds if an external flow summary exists for `c` with input specification
|
||||
* `input`, output specification `output`, kind `kind`, and a flag `generated`
|
||||
* stating whether the summary is autogenerated.
|
||||
*/
|
||||
predicate summaryElement(
|
||||
FlowSummary::SummarizedCallable c, string input, string output, string kind, boolean generated
|
||||
) {
|
||||
exists(boolean preservesValue |
|
||||
c.propagatesFlowExt(input, output, preservesValue) and
|
||||
(if preservesValue = true then kind = "value" else kind = "taint") and
|
||||
generated = false
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if a negative flow summary exists for `c`, which means that there is no
|
||||
* flow through `c`. The flag `generated` states whether the summary is autogenerated.
|
||||
* Note. Negative flow summaries has not been implemented for Python.
|
||||
*/
|
||||
predicate negativeSummaryElement(FlowSummary::SummarizedCallable c, boolean generated) { none() }
|
||||
|
||||
/**
|
||||
* Gets the summary component for specification component `c`, if any.
|
||||
*
|
||||
* This covers all the Python-specific components of a flow summary.
|
||||
*/
|
||||
SummaryComponent interpretComponentSpecific(AccessPathToken c) {
|
||||
c = "ListElement" and
|
||||
result = FlowSummary::SummaryComponent::listElement()
|
||||
}
|
||||
|
||||
/** Gets the textual representation of a summary component in the format used for flow summaries. */
|
||||
string getComponentSpecificCsv(SummaryComponent sc) {
|
||||
sc = TContentSummaryComponent(any(ListElementContent c)) and
|
||||
result = "ListElement"
|
||||
}
|
||||
|
||||
/** Gets the textual representation of a parameter position in the format used for flow summaries. */
|
||||
string getParameterPositionCsv(ParameterPosition pos) { result = pos.toString() }
|
||||
|
||||
/** Gets the textual representation of an argument position in the format used for flow summaries. */
|
||||
string getArgumentPositionCsv(ArgumentPosition pos) { result = pos.toString() }
|
||||
|
||||
/** Holds if input specification component `c` needs a reference. */
|
||||
predicate inputNeedsReferenceSpecific(string c) { none() }
|
||||
|
||||
/** Holds if output specification component `c` needs a reference. */
|
||||
predicate outputNeedsReferenceSpecific(string c) { none() }
|
||||
|
||||
/** Gets the return kind corresponding to specification `"ReturnValue"`. */
|
||||
ReturnKind getReturnValueKind() { any() }
|
||||
|
||||
/**
|
||||
* All definitions in this module are required by the shared implementation
|
||||
* (for source/sink interpretation), but they are unused for Python, where
|
||||
* we rely on API graphs instead.
|
||||
*/
|
||||
private module UnusedSourceSinkInterpretation {
|
||||
/**
|
||||
* Holds if an external source specification exists for `n` with output specification
|
||||
* `output`, kind `kind`, and a flag `generated` stating whether the source specification is
|
||||
* autogenerated.
|
||||
*/
|
||||
predicate sourceElement(AstNode n, string output, string kind, boolean generated) { none() }
|
||||
|
||||
/**
|
||||
* Holds if an external sink specification exists for `n` with input specification
|
||||
* `input`, kind `kind` and a flag `generated` stating whether the sink specification is
|
||||
* autogenerated.
|
||||
*/
|
||||
predicate sinkElement(AstNode n, string input, string kind, boolean generated) { none() }
|
||||
|
||||
class SourceOrSinkElement = AstNode;
|
||||
|
||||
/** An entity used to interpret a source/sink specification. */
|
||||
class InterpretNode extends AstNode_ {
|
||||
// InterpretNode is going away, this is just a dummy implementation.
|
||||
// However, we have some old location tests picking them up, so we
|
||||
// explicitly define them to not exist.
|
||||
InterpretNode() { none() }
|
||||
|
||||
/** Gets the element that this node corresponds to, if any. */
|
||||
SourceOrSinkElement asElement() { none() }
|
||||
|
||||
/** Gets the data-flow node that this node corresponds to, if any. */
|
||||
Node asNode() { none() }
|
||||
|
||||
/** Gets the call that this node corresponds to, if any. */
|
||||
DataFlowCall asCall() { none() }
|
||||
|
||||
/** Gets the callable that this node corresponds to, if any. */
|
||||
DataFlowCallable asCallable() { none() }
|
||||
|
||||
/** Gets the target of this call, if any. */
|
||||
SourceOrSinkElement getCallTarget() { none() }
|
||||
}
|
||||
|
||||
/** Provides additional sink specification logic. */
|
||||
predicate interpretOutputSpecific(string c, InterpretNode mid, InterpretNode node) { none() }
|
||||
|
||||
/** Provides additional source specification logic. */
|
||||
predicate interpretInputSpecific(string c, InterpretNode mid, InterpretNode node) { none() }
|
||||
}
|
||||
|
||||
import UnusedSourceSinkInterpretation
|
||||
|
||||
module ParsePositions {
|
||||
private import FlowSummaryImpl
|
||||
|
||||
private predicate isParamBody(string body) {
|
||||
exists(AccessPathToken tok |
|
||||
tok.getName() = "Parameter" and
|
||||
body = tok.getAnArgument()
|
||||
)
|
||||
}
|
||||
|
||||
private predicate isArgBody(string body) {
|
||||
exists(AccessPathToken tok |
|
||||
tok.getName() = "Argument" and
|
||||
body = tok.getAnArgument()
|
||||
)
|
||||
}
|
||||
|
||||
predicate isParsedParameterPosition(string c, int i) {
|
||||
isParamBody(c) and
|
||||
i = AccessPath::parseInt(c)
|
||||
}
|
||||
|
||||
predicate isParsedArgumentPosition(string c, int i) {
|
||||
isArgBody(c) and
|
||||
i = AccessPath::parseInt(c)
|
||||
}
|
||||
}
|
||||
|
||||
/** Gets the argument position obtained by parsing `X` in `Parameter[X]`. */
|
||||
ArgumentPosition parseParamBody(string s) {
|
||||
exists(int i |
|
||||
ParsePositions::isParsedParameterPosition(s, i) and
|
||||
result.isPositional(i)
|
||||
)
|
||||
}
|
||||
|
||||
/** Gets the parameter position obtained by parsing `X` in `Argument[X]`. */
|
||||
ParameterPosition parseArgBody(string s) {
|
||||
exists(int i |
|
||||
ParsePositions::isParsedArgumentPosition(s, i) and
|
||||
result.isPositional(i)
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
private import python
|
||||
private import semmle.python.dataflow.new.DataFlow
|
||||
private import semmle.python.dataflow.new.internal.ImportStar
|
||||
private import semmle.python.dataflow.new.TypeTracker
|
||||
|
||||
module ImportResolution {
|
||||
/**
|
||||
* Holds if the module `m` defines a name `name` by assigning `defn` to it. This is an
|
||||
* overapproximation, as `name` may not in fact be exported (e.g. by defining an `__all__` that does
|
||||
* not include `name`).
|
||||
*/
|
||||
predicate module_export(Module m, string name, DataFlow::CfgNode defn) {
|
||||
exists(EssaVariable v |
|
||||
v.getName() = name and
|
||||
v.getAUse() = ImportStar::getStarImported*(m).getANormalExit()
|
||||
|
|
||||
defn.getNode() = v.getDefinition().(AssignmentDefinition).getValue()
|
||||
or
|
||||
defn.getNode() = v.getDefinition().(ArgumentRefinement).getArgument()
|
||||
)
|
||||
}
|
||||
|
||||
Module getModule(DataFlow::CfgNode node) {
|
||||
exists(ModuleValue mv |
|
||||
node.getNode().pointsTo(mv) and
|
||||
result = mv.getScope()
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -2,6 +2,8 @@
|
||||
|
||||
private import python
|
||||
private import semmle.python.dataflow.new.internal.Builtins
|
||||
private import semmle.python.dataflow.new.internal.ImportResolution
|
||||
private import semmle.python.dataflow.new.DataFlow
|
||||
|
||||
cached
|
||||
module ImportStar {
|
||||
@@ -71,8 +73,10 @@ module ImportStar {
|
||||
*/
|
||||
cached
|
||||
Module getStarImported(Module m) {
|
||||
exists(ImportStar i |
|
||||
i.getScope() = m and result = i.getModule().pointsTo().(ModuleValue).getScope()
|
||||
exists(ImportStar i, DataFlow::CfgNode imported_module |
|
||||
imported_module.getNode().getNode() = i.getModule() and
|
||||
i.getScope() = m and
|
||||
result = ImportResolution::getModule(imported_module)
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -244,7 +244,7 @@ class UnpackingAssignmentSequenceTarget extends UnpackingAssignmentTarget instan
|
||||
*/
|
||||
predicate iterableUnpackingAssignmentFlowStep(Node nodeFrom, Node nodeTo) {
|
||||
exists(AssignmentTarget target |
|
||||
nodeFrom.asExpr() = target.getValue() and
|
||||
nodeFrom.(CfgNode).getNode().getNode() = target.getValue() and
|
||||
nodeTo = TIterableSequenceNode(target)
|
||||
)
|
||||
}
|
||||
@@ -255,7 +255,7 @@ predicate iterableUnpackingAssignmentFlowStep(Node nodeFrom, Node nodeTo) {
|
||||
*/
|
||||
predicate iterableUnpackingForReadStep(CfgNode nodeFrom, Content c, Node nodeTo) {
|
||||
exists(ForTarget target |
|
||||
nodeFrom.asExpr() = target.getSource() and
|
||||
nodeFrom.getNode().getNode() = target.getSource() and
|
||||
target instanceof SequenceNode and
|
||||
nodeTo = TIterableSequenceNode(target)
|
||||
) and
|
||||
@@ -273,7 +273,7 @@ predicate iterableUnpackingForReadStep(CfgNode nodeFrom, Content c, Node nodeTo)
|
||||
predicate iterableUnpackingTupleFlowStep(Node nodeFrom, Node nodeTo) {
|
||||
exists(UnpackingAssignmentSequenceTarget target |
|
||||
nodeFrom = TIterableSequenceNode(target) and
|
||||
nodeTo.asCfgNode() = target
|
||||
nodeTo.(CfgNode).getNode() = target
|
||||
)
|
||||
}
|
||||
|
||||
@@ -305,7 +305,7 @@ predicate iterableUnpackingConvertingReadStep(Node nodeFrom, Content c, Node nod
|
||||
predicate iterableUnpackingConvertingStoreStep(Node nodeFrom, Content c, Node nodeTo) {
|
||||
exists(UnpackingAssignmentSequenceTarget target |
|
||||
nodeFrom = TIterableElementNode(target) and
|
||||
nodeTo.asCfgNode() = target and
|
||||
nodeTo.(CfgNode).getNode() = target and
|
||||
exists(int index | exists(target.getElement(index)) |
|
||||
c.(TupleElementContent).getIndex() = index
|
||||
)
|
||||
@@ -331,7 +331,7 @@ predicate iterableUnpackingElementReadStep(Node nodeFrom, Content c, Node nodeTo
|
||||
not exists(target.getAnElement().(StarredNode)) and
|
||||
starIndex = -1
|
||||
|
|
||||
nodeFrom.asCfgNode() = target and
|
||||
nodeFrom.(CfgNode).getNode() = target and
|
||||
element = target.getElement(index) and
|
||||
(
|
||||
if starIndex = -1 or index < starIndex
|
||||
|
||||
@@ -36,7 +36,7 @@ class LocalSourceNode extends Node {
|
||||
LocalSourceNode() {
|
||||
Stages::DataFlow::ref() and
|
||||
this instanceof ExprNode and
|
||||
not simpleLocalFlowStep(_, this)
|
||||
not simpleLocalFlowStepForTypetracking(_, this)
|
||||
or
|
||||
// We include all module variable nodes, as these act as stepping stones between writes and
|
||||
// reads of global variables. Without them, type tracking based on `LocalSourceNode`s would be
|
||||
|
||||
@@ -69,8 +69,8 @@ predicate matchSubjectFlowStep(Node nodeFrom, Node nodeTo) {
|
||||
subject = match.getSubject() and
|
||||
target = match.getCase(_).(Case).getPattern()
|
||||
|
|
||||
nodeFrom.asExpr() = subject and
|
||||
nodeTo.asCfgNode().getNode() = target
|
||||
nodeFrom.(CfgNode).getNode().getNode() = subject and
|
||||
nodeTo.(CfgNode).getNode().getNode() = target
|
||||
)
|
||||
}
|
||||
|
||||
@@ -84,12 +84,13 @@ predicate matchAsFlowStep(Node nodeFrom, Node nodeTo) {
|
||||
// That way, information can propagate from the interior pattern to the alias.
|
||||
//
|
||||
// the subject flows to the interior pattern
|
||||
nodeFrom.asCfgNode().getNode() = subject and
|
||||
nodeTo.asCfgNode().getNode() = subject.getPattern()
|
||||
nodeFrom.(CfgNode).getNode().getNode() = subject and
|
||||
nodeTo.(CfgNode).getNode().getNode() = subject.getPattern()
|
||||
or
|
||||
// the interior pattern flows to the alias
|
||||
nodeFrom.asCfgNode().getNode() = subject.getPattern() and
|
||||
nodeTo.asVar().getDefinition().(PatternAliasDefinition).getDefiningNode().getNode() = alias
|
||||
nodeFrom.(CfgNode).getNode().getNode() = subject.getPattern() and
|
||||
nodeTo.(EssaNode).getVar().getDefinition().(PatternAliasDefinition).getDefiningNode().getNode() =
|
||||
alias
|
||||
)
|
||||
}
|
||||
|
||||
@@ -99,8 +100,8 @@ predicate matchAsFlowStep(Node nodeFrom, Node nodeTo) {
|
||||
*/
|
||||
predicate matchOrFlowStep(Node nodeFrom, Node nodeTo) {
|
||||
exists(MatchOrPattern subject, Pattern pattern | pattern = subject.getAPattern() |
|
||||
nodeFrom.asCfgNode().getNode() = subject and
|
||||
nodeTo.asCfgNode().getNode() = pattern
|
||||
nodeFrom.(CfgNode).getNode().getNode() = subject and
|
||||
nodeTo.(CfgNode).getNode().getNode() = pattern
|
||||
)
|
||||
}
|
||||
|
||||
@@ -110,8 +111,8 @@ predicate matchOrFlowStep(Node nodeFrom, Node nodeTo) {
|
||||
*/
|
||||
predicate matchLiteralFlowStep(Node nodeFrom, Node nodeTo) {
|
||||
exists(MatchLiteralPattern pattern, Expr literal | literal = pattern.getLiteral() |
|
||||
nodeFrom.asExpr() = literal and
|
||||
nodeTo.asCfgNode().getNode() = pattern
|
||||
nodeFrom.(CfgNode).getNode().getNode() = literal and
|
||||
nodeTo.(CfgNode).getNode().getNode() = pattern
|
||||
)
|
||||
}
|
||||
|
||||
@@ -121,8 +122,14 @@ predicate matchLiteralFlowStep(Node nodeFrom, Node nodeTo) {
|
||||
*/
|
||||
predicate matchCaptureFlowStep(Node nodeFrom, Node nodeTo) {
|
||||
exists(MatchCapturePattern capture, Name var | capture.getVariable() = var |
|
||||
nodeFrom.asCfgNode().getNode() = capture and
|
||||
nodeTo.asVar().getDefinition().(PatternCaptureDefinition).getDefiningNode().getNode() = var
|
||||
nodeFrom.(CfgNode).getNode().getNode() = capture and
|
||||
nodeTo
|
||||
.(EssaNode)
|
||||
.getVar()
|
||||
.getDefinition()
|
||||
.(PatternCaptureDefinition)
|
||||
.getDefiningNode()
|
||||
.getNode() = var
|
||||
)
|
||||
}
|
||||
|
||||
@@ -132,8 +139,8 @@ predicate matchCaptureFlowStep(Node nodeFrom, Node nodeTo) {
|
||||
*/
|
||||
predicate matchValueFlowStep(Node nodeFrom, Node nodeTo) {
|
||||
exists(MatchValuePattern pattern, Expr value | value = pattern.getValue() |
|
||||
nodeFrom.asExpr() = value and
|
||||
nodeTo.asCfgNode().getNode() = pattern
|
||||
nodeFrom.(CfgNode).getNode().getNode() = value and
|
||||
nodeTo.(CfgNode).getNode().getNode() = pattern
|
||||
)
|
||||
}
|
||||
|
||||
@@ -145,8 +152,8 @@ predicate matchSequenceReadStep(Node nodeFrom, Content c, Node nodeTo) {
|
||||
exists(MatchSequencePattern subject, int index, Pattern element |
|
||||
element = subject.getPattern(index)
|
||||
|
|
||||
nodeFrom.asCfgNode().getNode() = subject and
|
||||
nodeTo.asCfgNode().getNode() = element and
|
||||
nodeFrom.(CfgNode).getNode().getNode() = subject and
|
||||
nodeTo.(CfgNode).getNode().getNode() = element and
|
||||
(
|
||||
// tuple content
|
||||
c.(TupleElementContent).getIndex() = index
|
||||
@@ -173,7 +180,7 @@ predicate matchStarReadStep(Node nodeFrom, Content c, Node nodeTo) {
|
||||
exists(MatchSequencePattern subject, int index, MatchStarPattern star |
|
||||
star = subject.getPattern(index)
|
||||
|
|
||||
nodeFrom.asCfgNode().getNode() = subject and
|
||||
nodeFrom.(CfgNode).getNode().getNode() = subject and
|
||||
nodeTo = TStarPatternElementNode(star) and
|
||||
(
|
||||
// tuple content
|
||||
@@ -200,7 +207,7 @@ predicate matchStarReadStep(Node nodeFrom, Content c, Node nodeTo) {
|
||||
predicate matchStarStoreStep(Node nodeFrom, Content c, Node nodeTo) {
|
||||
exists(MatchStarPattern star |
|
||||
nodeFrom = TStarPatternElementNode(star) and
|
||||
nodeTo.asCfgNode().getNode() = star.getTarget() and
|
||||
nodeTo.(CfgNode).getNode().getNode() = star.getTarget() and
|
||||
c instanceof ListElementContent
|
||||
)
|
||||
}
|
||||
@@ -218,8 +225,8 @@ predicate matchMappingReadStep(Node nodeFrom, Content c, Node nodeTo) {
|
||||
key = keyValue.getKey() and
|
||||
value = keyValue.getValue()
|
||||
|
|
||||
nodeFrom.asCfgNode().getNode() = subject and
|
||||
nodeTo.asCfgNode().getNode() = value and
|
||||
nodeFrom.(CfgNode).getNode().getNode() = subject and
|
||||
nodeTo.(CfgNode).getNode().getNode() = value and
|
||||
c.(DictionaryElementContent).getKey() = key.getLiteral().(StrConst).getText()
|
||||
)
|
||||
}
|
||||
@@ -233,8 +240,8 @@ predicate matchMappingReadStep(Node nodeFrom, Content c, Node nodeTo) {
|
||||
*/
|
||||
predicate matchMappingFlowStep(Node nodeFrom, Node nodeTo) {
|
||||
exists(MatchMappingPattern subject, MatchDoubleStarPattern dstar | dstar = subject.getAMapping() |
|
||||
nodeFrom.asCfgNode().getNode() = subject and
|
||||
nodeTo.asCfgNode().getNode() = dstar.getTarget()
|
||||
nodeFrom.(CfgNode).getNode().getNode() = subject and
|
||||
nodeTo.(CfgNode).getNode().getNode() = dstar.getTarget()
|
||||
)
|
||||
}
|
||||
|
||||
@@ -251,7 +258,7 @@ predicate matchMappingClearStep(Node n, Content c) {
|
||||
key = keyValue.getKey() and
|
||||
dstar = subject.getAMapping()
|
||||
|
|
||||
n.asCfgNode().getNode() = dstar.getTarget() and
|
||||
n.(CfgNode).getNode().getNode() = dstar.getTarget() and
|
||||
c.(DictionaryElementContent).getKey() = key.getLiteral().(StrConst).getText()
|
||||
)
|
||||
}
|
||||
@@ -266,8 +273,8 @@ predicate matchClassReadStep(Node nodeFrom, Content c, Node nodeTo) {
|
||||
attr = keyword.getAttribute() and
|
||||
value = keyword.getValue()
|
||||
|
|
||||
nodeFrom.asCfgNode().getNode() = subject and
|
||||
nodeTo.asCfgNode().getNode() = value and
|
||||
nodeFrom.(CfgNode).getNode().getNode() = subject and
|
||||
nodeTo.(CfgNode).getNode().getNode() = value and
|
||||
c.(AttributeContent).getAttribute() = attr.getId()
|
||||
)
|
||||
}
|
||||
|
||||
@@ -11,7 +11,7 @@ class Node = DataFlowPublic::Node;
|
||||
|
||||
class TypeTrackingNode = DataFlowPublic::TypeTrackingNode;
|
||||
|
||||
predicate simpleLocalFlowStep = DataFlowPrivate::simpleLocalFlowStep/2;
|
||||
predicate simpleLocalFlowStep = DataFlowPrivate::simpleLocalFlowStepForTypetracking/2;
|
||||
|
||||
predicate jumpStep = DataFlowPrivate::jumpStepSharedWithTypeTracker/2;
|
||||
|
||||
@@ -35,16 +35,22 @@ string getPossibleContentName() {
|
||||
*/
|
||||
pragma[nomagic]
|
||||
private DataFlowPrivate::DataFlowCallable getCallableForArgument(
|
||||
DataFlowPublic::ArgumentNode nodeFrom, int i
|
||||
DataFlowPublic::ExtractedArgumentNode nodeFrom, int i
|
||||
) {
|
||||
exists(DataFlowPrivate::DataFlowCall call |
|
||||
nodeFrom.argumentOf(call, i) and
|
||||
exists(DataFlowPrivate::ExtractedDataFlowCall call |
|
||||
nodeFrom.extractedArgumentOf(call, i) and
|
||||
result = call.getCallable()
|
||||
)
|
||||
}
|
||||
|
||||
/** Holds if `nodeFrom` steps to `nodeTo` by being passed as a parameter in a call. */
|
||||
predicate callStep(DataFlowPublic::ArgumentNode nodeFrom, DataFlowPublic::ParameterNode nodeTo) {
|
||||
/**
|
||||
* Holds if `nodeFrom` steps to `nodeTo` by being passed as a parameter in a call.
|
||||
*
|
||||
* Flow into summarized library methods is not included, as that will lead to negative
|
||||
* recursion (or, at best, terrible performance), since identifying calls to library
|
||||
* methods is done using API graphs (which uses type tracking).
|
||||
*/
|
||||
predicate callStep(DataFlowPublic::ArgumentNode nodeFrom, DataFlowPrivate::ParameterNodeImpl nodeTo) {
|
||||
// TODO: Support special methods?
|
||||
exists(DataFlowPrivate::DataFlowCallable callable, int i |
|
||||
callable = getCallableForArgument(nodeFrom, i) and
|
||||
@@ -54,8 +60,9 @@ predicate callStep(DataFlowPublic::ArgumentNode nodeFrom, DataFlowPublic::Parame
|
||||
|
||||
/** Holds if `nodeFrom` steps to `nodeTo` by being returned from a call. */
|
||||
predicate returnStep(DataFlowPrivate::ReturnNode nodeFrom, Node nodeTo) {
|
||||
exists(DataFlowPrivate::DataFlowCall call |
|
||||
nodeFrom.getEnclosingCallable() = call.getCallable() and nodeTo.asCfgNode() = call.getNode()
|
||||
exists(DataFlowPrivate::ExtractedDataFlowCall call |
|
||||
nodeFrom.getEnclosingCallable() = call.getCallable() and
|
||||
nodeTo.(DataFlowPublic::CfgNode).getNode() = call.getNode()
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -172,7 +172,12 @@ abstract class Configuration extends DataFlow::Configuration {
|
||||
}
|
||||
|
||||
override predicate allowImplicitRead(DataFlow::Node node, DataFlow::ContentSet c) {
|
||||
(this.isSink(node) or this.isAdditionalTaintStep(node, _)) and
|
||||
(
|
||||
this.isSink(node) or
|
||||
this.isSink(node, _) or
|
||||
this.isAdditionalTaintStep(node, _) or
|
||||
this.isAdditionalTaintStep(node, _, _, _)
|
||||
) and
|
||||
defaultImplicitTaintRead(node, c)
|
||||
}
|
||||
|
||||
|
||||
@@ -172,7 +172,12 @@ abstract class Configuration extends DataFlow::Configuration {
|
||||
}
|
||||
|
||||
override predicate allowImplicitRead(DataFlow::Node node, DataFlow::ContentSet c) {
|
||||
(this.isSink(node) or this.isAdditionalTaintStep(node, _)) and
|
||||
(
|
||||
this.isSink(node) or
|
||||
this.isSink(node, _) or
|
||||
this.isAdditionalTaintStep(node, _) or
|
||||
this.isAdditionalTaintStep(node, _, _, _)
|
||||
) and
|
||||
defaultImplicitTaintRead(node, c)
|
||||
}
|
||||
|
||||
|
||||
@@ -172,7 +172,12 @@ abstract class Configuration extends DataFlow::Configuration {
|
||||
}
|
||||
|
||||
override predicate allowImplicitRead(DataFlow::Node node, DataFlow::ContentSet c) {
|
||||
(this.isSink(node) or this.isAdditionalTaintStep(node, _)) and
|
||||
(
|
||||
this.isSink(node) or
|
||||
this.isSink(node, _) or
|
||||
this.isAdditionalTaintStep(node, _) or
|
||||
this.isAdditionalTaintStep(node, _, _, _)
|
||||
) and
|
||||
defaultImplicitTaintRead(node, c)
|
||||
}
|
||||
|
||||
|
||||
@@ -172,7 +172,12 @@ abstract class Configuration extends DataFlow::Configuration {
|
||||
}
|
||||
|
||||
override predicate allowImplicitRead(DataFlow::Node node, DataFlow::ContentSet c) {
|
||||
(this.isSink(node) or this.isAdditionalTaintStep(node, _)) and
|
||||
(
|
||||
this.isSink(node) or
|
||||
this.isSink(node, _) or
|
||||
this.isAdditionalTaintStep(node, _) or
|
||||
this.isAdditionalTaintStep(node, _, _, _)
|
||||
) and
|
||||
defaultImplicitTaintRead(node, c)
|
||||
}
|
||||
|
||||
|
||||
@@ -78,7 +78,9 @@ module SsaSource {
|
||||
|
||||
/** Holds if `v` is defined by a `for` statement, the definition being `defn` */
|
||||
cached
|
||||
predicate iteration_defined_variable(Variable v, ControlFlowNode defn, ControlFlowNode sequence) {
|
||||
deprecated predicate iteration_defined_variable(
|
||||
Variable v, ControlFlowNode defn, ControlFlowNode sequence
|
||||
) {
|
||||
exists(ForNode for | for.iterates(defn, sequence)) and
|
||||
defn.(NameNode).defines(v)
|
||||
}
|
||||
|
||||
@@ -59,7 +59,7 @@ module AiohttpWebModel {
|
||||
* Extend this class to refine existing API models. If you want to model new APIs,
|
||||
* extend `AiohttpRouteSetup::Range` instead.
|
||||
*/
|
||||
class AiohttpRouteSetup extends HTTP::Server::RouteSetup::Range {
|
||||
class AiohttpRouteSetup extends Http::Server::RouteSetup::Range {
|
||||
AiohttpRouteSetup::Range range;
|
||||
|
||||
AiohttpRouteSetup() { this = range }
|
||||
@@ -161,7 +161,7 @@ module AiohttpWebModel {
|
||||
|
||||
AiohttpAddRouteCall() {
|
||||
exists(string funcName |
|
||||
funcName = HTTP::httpVerbLower() and
|
||||
funcName = Http::httpVerbLower() and
|
||||
routeArgsStart = 0
|
||||
or
|
||||
funcName = "view" and
|
||||
@@ -192,7 +192,7 @@ module AiohttpWebModel {
|
||||
|
||||
AiohttpDecoratorRouteSetup() {
|
||||
exists(string decoratorName |
|
||||
decoratorName = HTTP::httpVerbLower() and
|
||||
decoratorName = Http::httpVerbLower() and
|
||||
routeArgsStart = 0
|
||||
or
|
||||
decoratorName = "view" and
|
||||
@@ -237,7 +237,7 @@ module AiohttpWebModel {
|
||||
// TODO: This doesn't handle attribute assignment. Should be OK, but analysis is not as complete as with
|
||||
// points-to and `.lookup`, which would handle `post = my_post_handler` inside class def
|
||||
result = this.getAMethod() and
|
||||
result.getName() = HTTP::httpVerbLower()
|
||||
result.getName() = Http::httpVerbLower()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -252,7 +252,7 @@ module AiohttpWebModel {
|
||||
}
|
||||
|
||||
/** A request handler defined in an `aiohttp.web` view class, that has no known route. */
|
||||
private class AiohttpViewClassRequestHandlerWithoutKnownRoute extends HTTP::Server::RequestHandler::Range {
|
||||
private class AiohttpViewClassRequestHandlerWithoutKnownRoute extends Http::Server::RequestHandler::Range {
|
||||
AiohttpViewClassRequestHandlerWithoutKnownRoute() {
|
||||
exists(AiohttpViewClass vc | vc.getARequestHandler() = this) and
|
||||
not exists(AiohttpRouteSetup setup | setup.getARequestHandler() = this)
|
||||
@@ -493,7 +493,7 @@ module AiohttpWebModel {
|
||||
* - https://docs.aiohttp.org/en/stable/web_reference.html#aiohttp.web.Response
|
||||
* - https://docs.aiohttp.org/en/stable/web_quickstart.html#aiohttp-web-exceptions
|
||||
*/
|
||||
class AiohttpWebResponseInstantiation extends HTTP::Server::HttpResponse::Range,
|
||||
class AiohttpWebResponseInstantiation extends Http::Server::HttpResponse::Range,
|
||||
Response::InstanceSource, DataFlow::CallCfgNode {
|
||||
API::Node apiNode;
|
||||
|
||||
@@ -562,7 +562,7 @@ module AiohttpWebModel {
|
||||
* See the part about redirects at https://docs.aiohttp.org/en/stable/web_quickstart.html#aiohttp-web-exceptions
|
||||
*/
|
||||
class AiohttpRedirectExceptionInstantiation extends AiohttpWebResponseInstantiation,
|
||||
HTTP::Server::HttpRedirectResponse::Range {
|
||||
Http::Server::HttpRedirectResponse::Range {
|
||||
AiohttpRedirectExceptionInstantiation() {
|
||||
exists(string httpRedirectExceptionClassName |
|
||||
httpRedirectExceptionClassName in [
|
||||
@@ -585,7 +585,7 @@ module AiohttpWebModel {
|
||||
/**
|
||||
* A call to `set_cookie` on a HTTP Response.
|
||||
*/
|
||||
class AiohttpResponseSetCookieCall extends HTTP::Server::CookieWrite::Range, DataFlow::CallCfgNode {
|
||||
class AiohttpResponseSetCookieCall extends Http::Server::CookieWrite::Range, DataFlow::CallCfgNode {
|
||||
AiohttpResponseSetCookieCall() {
|
||||
this = aiohttpResponseInstance().getMember("set_cookie").getACall()
|
||||
}
|
||||
@@ -600,7 +600,7 @@ module AiohttpWebModel {
|
||||
/**
|
||||
* A call to `del_cookie` on a HTTP Response.
|
||||
*/
|
||||
class AiohttpResponseDelCookieCall extends HTTP::Server::CookieWrite::Range, DataFlow::CallCfgNode {
|
||||
class AiohttpResponseDelCookieCall extends Http::Server::CookieWrite::Range, DataFlow::CallCfgNode {
|
||||
AiohttpResponseDelCookieCall() {
|
||||
this = aiohttpResponseInstance().getMember("del_cookie").getACall()
|
||||
}
|
||||
@@ -616,7 +616,7 @@ module AiohttpWebModel {
|
||||
* A dict-like write to an item of the `cookies` attribute on a HTTP response, such as
|
||||
* `response.cookies[name] = value`.
|
||||
*/
|
||||
class AiohttpResponseCookieSubscriptWrite extends HTTP::Server::CookieWrite::Range {
|
||||
class AiohttpResponseCookieSubscriptWrite extends Http::Server::CookieWrite::Range {
|
||||
DataFlow::Node index;
|
||||
DataFlow::Node value;
|
||||
|
||||
@@ -661,11 +661,11 @@ private module AiohttpClientModel {
|
||||
private API::Node instance() { result = classRef().getReturn() }
|
||||
|
||||
/** A method call on a ClientSession that sends off a request */
|
||||
private class OutgoingRequestCall extends HTTP::Client::Request::Range, API::CallNode {
|
||||
private class OutgoingRequestCall extends Http::Client::Request::Range, API::CallNode {
|
||||
string methodName;
|
||||
|
||||
OutgoingRequestCall() {
|
||||
methodName in [HTTP::httpVerbLower(), "request"] and
|
||||
methodName in [Http::httpVerbLower(), "request"] and
|
||||
this = instance().getMember(methodName).getACall()
|
||||
}
|
||||
|
||||
|
||||
@@ -1155,11 +1155,11 @@ module PrivateDjango {
|
||||
/** Gets a reference to the `django.http` module. */
|
||||
API::Node http() { result = django().getMember("http") }
|
||||
|
||||
/** DEPRECATED: Alias for `Http` */
|
||||
deprecated module http = Http;
|
||||
/** DEPRECATED: Alias for `DjangoHttp` */
|
||||
deprecated module http = DjangoHttp;
|
||||
|
||||
/** Provides models for the `django.http` module */
|
||||
module Http {
|
||||
module DjangoHttp {
|
||||
// ---------------------------------------------------------------------------
|
||||
// django.http.request
|
||||
// ---------------------------------------------------------------------------
|
||||
@@ -1251,7 +1251,7 @@ module PrivateDjango {
|
||||
// special handling of the `build_absolute_uri` method, see
|
||||
// https://docs.djangoproject.com/en/3.0/ref/request-response/#django.http.HttpRequest.build_absolute_uri
|
||||
exists(DataFlow::AttrRead attr, DataFlow::CallCfgNode call, DataFlow::Node instance |
|
||||
instance = DjangoImpl::Http::Request::HttpRequest::instance() and
|
||||
instance = DjangoImpl::DjangoHttp::Request::HttpRequest::instance() and
|
||||
attr.getObject() = instance
|
||||
|
|
||||
attr.getAttributeName() = "build_absolute_uri" and
|
||||
@@ -1359,7 +1359,7 @@ module PrivateDjango {
|
||||
*
|
||||
* Use the predicate `HttpResponse::instance()` to get references to instances of `django.http.response.HttpResponse`.
|
||||
*/
|
||||
abstract class InstanceSource extends HTTP::Server::HttpResponse::Range, DataFlow::Node {
|
||||
abstract class InstanceSource extends Http::Server::HttpResponse::Range, DataFlow::Node {
|
||||
}
|
||||
|
||||
/** A direct instantiation of `django.http.response.HttpResponse`. */
|
||||
@@ -1421,7 +1421,7 @@ module PrivateDjango {
|
||||
* Use the predicate `HttpResponseRedirect::instance()` to get references to instances of `django.http.response.HttpResponseRedirect`.
|
||||
*/
|
||||
abstract class InstanceSource extends HttpResponse::InstanceSource,
|
||||
HTTP::Server::HttpRedirectResponse::Range, DataFlow::Node { }
|
||||
Http::Server::HttpRedirectResponse::Range, DataFlow::Node { }
|
||||
|
||||
/** A direct instantiation of `django.http.response.HttpResponseRedirect`. */
|
||||
private class ClassInstantiation extends InstanceSource, DataFlow::CallCfgNode {
|
||||
@@ -1483,7 +1483,7 @@ module PrivateDjango {
|
||||
* Use the predicate `HttpResponsePermanentRedirect::instance()` to get references to instances of `django.http.response.HttpResponsePermanentRedirect`.
|
||||
*/
|
||||
abstract class InstanceSource extends HttpResponse::InstanceSource,
|
||||
HTTP::Server::HttpRedirectResponse::Range, DataFlow::Node { }
|
||||
Http::Server::HttpRedirectResponse::Range, DataFlow::Node { }
|
||||
|
||||
/** A direct instantiation of `django.http.response.HttpResponsePermanentRedirect`. */
|
||||
private class ClassInstantiation extends InstanceSource, DataFlow::CallCfgNode {
|
||||
@@ -2066,17 +2066,18 @@ module PrivateDjango {
|
||||
|
||||
/** Gets a reference to the `django.http.response.HttpResponse.write` function. */
|
||||
private DataFlow::TypeTrackingNode write(
|
||||
DjangoImpl::Http::Response::HttpResponse::InstanceSource instance, DataFlow::TypeTracker t
|
||||
DjangoImpl::DjangoHttp::Response::HttpResponse::InstanceSource instance,
|
||||
DataFlow::TypeTracker t
|
||||
) {
|
||||
t.startInAttr("write") and
|
||||
instance = DjangoImpl::Http::Response::HttpResponse::instance() and
|
||||
instance = DjangoImpl::DjangoHttp::Response::HttpResponse::instance() and
|
||||
result = instance
|
||||
or
|
||||
exists(DataFlow::TypeTracker t2 | result = write(instance, t2).track(t2, t))
|
||||
}
|
||||
|
||||
/** Gets a reference to the `django.http.response.HttpResponse.write` function. */
|
||||
DataFlow::Node write(DjangoImpl::Http::Response::HttpResponse::InstanceSource instance) {
|
||||
DataFlow::Node write(DjangoImpl::DjangoHttp::Response::HttpResponse::InstanceSource instance) {
|
||||
write(instance, DataFlow::TypeTracker::end()).flowsTo(result)
|
||||
}
|
||||
|
||||
@@ -2085,8 +2086,8 @@ module PrivateDjango {
|
||||
*
|
||||
* See https://docs.djangoproject.com/en/3.1/ref/request-response/#django.http.HttpResponse.write
|
||||
*/
|
||||
class HttpResponseWriteCall extends HTTP::Server::HttpResponse::Range, DataFlow::CallCfgNode {
|
||||
DjangoImpl::Http::Response::HttpResponse::InstanceSource instance;
|
||||
class HttpResponseWriteCall extends Http::Server::HttpResponse::Range, DataFlow::CallCfgNode {
|
||||
DjangoImpl::DjangoHttp::Response::HttpResponse::InstanceSource instance;
|
||||
|
||||
HttpResponseWriteCall() { this.getFunction() = write(instance) }
|
||||
|
||||
@@ -2104,10 +2105,10 @@ module PrivateDjango {
|
||||
/**
|
||||
* A call to `set_cookie` on a HTTP Response.
|
||||
*/
|
||||
class DjangoResponseSetCookieCall extends HTTP::Server::CookieWrite::Range,
|
||||
class DjangoResponseSetCookieCall extends Http::Server::CookieWrite::Range,
|
||||
DataFlow::MethodCallNode {
|
||||
DjangoResponseSetCookieCall() {
|
||||
this.calls(DjangoImpl::Http::Response::HttpResponse::instance(), "set_cookie")
|
||||
this.calls(DjangoImpl::DjangoHttp::Response::HttpResponse::instance(), "set_cookie")
|
||||
}
|
||||
|
||||
override DataFlow::Node getHeaderArg() { none() }
|
||||
@@ -2124,10 +2125,10 @@ module PrivateDjango {
|
||||
/**
|
||||
* A call to `delete_cookie` on a HTTP Response.
|
||||
*/
|
||||
class DjangoResponseDeleteCookieCall extends HTTP::Server::CookieWrite::Range,
|
||||
class DjangoResponseDeleteCookieCall extends Http::Server::CookieWrite::Range,
|
||||
DataFlow::MethodCallNode {
|
||||
DjangoResponseDeleteCookieCall() {
|
||||
this.calls(DjangoImpl::Http::Response::HttpResponse::instance(), "delete_cookie")
|
||||
this.calls(DjangoImpl::DjangoHttp::Response::HttpResponse::instance(), "delete_cookie")
|
||||
}
|
||||
|
||||
override DataFlow::Node getHeaderArg() { none() }
|
||||
@@ -2143,7 +2144,7 @@ module PrivateDjango {
|
||||
* A dict-like write to an item of the `cookies` attribute on a HTTP response, such as
|
||||
* `response.cookies[name] = value`.
|
||||
*/
|
||||
class DjangoResponseCookieSubscriptWrite extends HTTP::Server::CookieWrite::Range {
|
||||
class DjangoResponseCookieSubscriptWrite extends Http::Server::CookieWrite::Range {
|
||||
DataFlow::Node index;
|
||||
DataFlow::Node value;
|
||||
|
||||
@@ -2154,7 +2155,7 @@ module PrivateDjango {
|
||||
this.asCfgNode() = subscript
|
||||
|
|
||||
cookieLookup.getAttributeName() = "cookies" and
|
||||
cookieLookup.getObject() = DjangoImpl::Http::Response::HttpResponse::instance() and
|
||||
cookieLookup.getObject() = DjangoImpl::DjangoHttp::Response::HttpResponse::instance() and
|
||||
exists(DataFlow::Node subscriptObj |
|
||||
subscriptObj.asCfgNode() = subscript.getObject()
|
||||
|
|
||||
@@ -2315,7 +2316,7 @@ module PrivateDjango {
|
||||
// points-to and `.lookup`, which would handle `post = my_post_handler` inside class def
|
||||
result = this.getAMethod() and
|
||||
(
|
||||
result.getName() = HTTP::httpVerbLower()
|
||||
result.getName() = Http::httpVerbLower()
|
||||
or
|
||||
result.getName() = "get_redirect_url"
|
||||
)
|
||||
@@ -2410,7 +2411,7 @@ module PrivateDjango {
|
||||
}
|
||||
|
||||
/** A data-flow node that sets up a route on a server, using the django framework. */
|
||||
abstract class DjangoRouteSetup extends HTTP::Server::RouteSetup::Range, DataFlow::CfgNode {
|
||||
abstract class DjangoRouteSetup extends Http::Server::RouteSetup::Range, DataFlow::CfgNode {
|
||||
/** Gets the data-flow node that is used as the argument for the view handler. */
|
||||
abstract DataFlow::Node getViewArg();
|
||||
|
||||
@@ -2427,7 +2428,7 @@ module PrivateDjango {
|
||||
}
|
||||
|
||||
/** A request handler defined in a django view class, that has no known route. */
|
||||
private class DjangoViewClassHandlerWithoutKnownRoute extends HTTP::Server::RequestHandler::Range,
|
||||
private class DjangoViewClassHandlerWithoutKnownRoute extends Http::Server::RequestHandler::Range,
|
||||
DjangoRouteHandler {
|
||||
DjangoViewClassHandlerWithoutKnownRoute() {
|
||||
exists(DjangoViewClass vc | vc.getARequestHandler() = this) and
|
||||
@@ -2586,7 +2587,7 @@ module PrivateDjango {
|
||||
// HttpRequest taint modeling
|
||||
// ---------------------------------------------------------------------------
|
||||
/** A parameter that will receive the django `HttpRequest` instance when a request handler is invoked. */
|
||||
private class DjangoRequestHandlerRequestParam extends DjangoImpl::Http::Request::HttpRequest::InstanceSource,
|
||||
private class DjangoRequestHandlerRequestParam extends DjangoImpl::DjangoHttp::Request::HttpRequest::InstanceSource,
|
||||
RemoteFlowSource::Range, DataFlow::ParameterNode {
|
||||
DjangoRequestHandlerRequestParam() {
|
||||
this.getParameter() = any(DjangoRouteSetup setup).getARequestHandler().getRequestParam()
|
||||
@@ -2603,7 +2604,7 @@ module PrivateDjango {
|
||||
*
|
||||
* See https://docs.djangoproject.com/en/3.1/topics/class-based-views/generic-display/#dynamic-filtering
|
||||
*/
|
||||
private class DjangoViewClassRequestAttributeRead extends DjangoImpl::Http::Request::HttpRequest::InstanceSource,
|
||||
private class DjangoViewClassRequestAttributeRead extends DjangoImpl::DjangoHttp::Request::HttpRequest::InstanceSource,
|
||||
RemoteFlowSource::Range, DataFlow::Node {
|
||||
DjangoViewClassRequestAttributeRead() {
|
||||
exists(DataFlow::AttrRead read | this = read |
|
||||
@@ -2678,7 +2679,7 @@ module PrivateDjango {
|
||||
*
|
||||
* See https://docs.djangoproject.com/en/3.1/topics/http/shortcuts/#redirect
|
||||
*/
|
||||
private class DjangoShortcutsRedirectCall extends HTTP::Server::HttpRedirectResponse::Range,
|
||||
private class DjangoShortcutsRedirectCall extends Http::Server::HttpRedirectResponse::Range,
|
||||
DataFlow::CallCfgNode {
|
||||
DjangoShortcutsRedirectCall() { this = DjangoImpl::Shortcuts::redirect().getACall() }
|
||||
|
||||
@@ -2712,7 +2713,7 @@ module PrivateDjango {
|
||||
*
|
||||
* See https://docs.djangoproject.com/en/3.1/ref/class-based-views/base/#redirectview
|
||||
*/
|
||||
private class DjangoRedirectViewGetRedirectUrlReturn extends HTTP::Server::HttpRedirectResponse::Range,
|
||||
private class DjangoRedirectViewGetRedirectUrlReturn extends Http::Server::HttpRedirectResponse::Range,
|
||||
DataFlow::CfgNode {
|
||||
DjangoRedirectViewGetRedirectUrlReturn() {
|
||||
node = any(GetRedirectUrlFunction f).getAReturnValueFlowNode()
|
||||
@@ -2751,7 +2752,7 @@ module PrivateDjango {
|
||||
/**
|
||||
* A custom middleware stack
|
||||
*/
|
||||
private class DjangoSettingsMiddlewareStack extends HTTP::Server::CsrfProtectionSetting::Range {
|
||||
private class DjangoSettingsMiddlewareStack extends Http::Server::CsrfProtectionSetting::Range {
|
||||
List list;
|
||||
|
||||
DjangoSettingsMiddlewareStack() {
|
||||
@@ -2787,7 +2788,7 @@ module PrivateDjango {
|
||||
}
|
||||
}
|
||||
|
||||
private class DjangoCsrfDecorator extends HTTP::Server::CsrfLocalProtectionSetting::Range {
|
||||
private class DjangoCsrfDecorator extends Http::Server::CsrfLocalProtectionSetting::Range {
|
||||
string decoratorName;
|
||||
Function function;
|
||||
|
||||
|
||||
@@ -48,10 +48,10 @@ private module FastApi {
|
||||
*
|
||||
* See https://fastapi.tiangolo.com/tutorial/first-steps/#define-a-path-operation-decorator
|
||||
*/
|
||||
private class FastApiRouteSetup extends HTTP::Server::RouteSetup::Range, DataFlow::CallCfgNode {
|
||||
private class FastApiRouteSetup extends Http::Server::RouteSetup::Range, DataFlow::CallCfgNode {
|
||||
FastApiRouteSetup() {
|
||||
exists(string routeAddingMethod |
|
||||
routeAddingMethod = HTTP::httpVerbLower()
|
||||
routeAddingMethod = Http::httpVerbLower()
|
||||
or
|
||||
routeAddingMethod in ["api_route", "websocket"]
|
||||
|
|
||||
@@ -195,7 +195,7 @@ private module FastApi {
|
||||
abstract class InstanceSource extends DataFlow::LocalSourceNode { }
|
||||
|
||||
/** A direct instantiation of a response class. */
|
||||
private class ResponseInstantiation extends InstanceSource, HTTP::Server::HttpResponse::Range,
|
||||
private class ResponseInstantiation extends InstanceSource, Http::Server::HttpResponse::Range,
|
||||
DataFlow::CallCfgNode {
|
||||
API::Node baseApiNode;
|
||||
API::Node responseClass;
|
||||
@@ -223,7 +223,7 @@ private module FastApi {
|
||||
* A direct instantiation of a redirect response.
|
||||
*/
|
||||
private class RedirectResponseInstantiation extends ResponseInstantiation,
|
||||
HTTP::Server::HttpRedirectResponse::Range {
|
||||
Http::Server::HttpRedirectResponse::Range {
|
||||
RedirectResponseInstantiation() { baseApiNode = getModeledResponseClass("RedirectResponse") }
|
||||
|
||||
override DataFlow::Node getRedirectLocation() {
|
||||
@@ -245,7 +245,7 @@ private module FastApi {
|
||||
/**
|
||||
* An implicit response from a return of FastAPI request handler.
|
||||
*/
|
||||
private class FastApiRequestHandlerReturn extends HTTP::Server::HttpResponse::Range,
|
||||
private class FastApiRequestHandlerReturn extends Http::Server::HttpResponse::Range,
|
||||
DataFlow::CfgNode {
|
||||
FastApiRouteSetup routeSetup;
|
||||
|
||||
@@ -291,7 +291,7 @@ private module FastApi {
|
||||
* `response_class` set to a `RedirectResponse`.
|
||||
*/
|
||||
private class FastApiRequestHandlerRedirectReturn extends FastApiRequestHandlerReturn,
|
||||
HTTP::Server::HttpRedirectResponse::Range {
|
||||
Http::Server::HttpRedirectResponse::Range {
|
||||
FastApiRequestHandlerRedirectReturn() {
|
||||
exists(API::Node responseClass |
|
||||
responseClass.getAValueReachableFromSource() = routeSetup.getResponseClassArg() and
|
||||
@@ -332,7 +332,7 @@ private module FastApi {
|
||||
/**
|
||||
* A call to `set_cookie` on a FastAPI Response.
|
||||
*/
|
||||
private class SetCookieCall extends HTTP::Server::CookieWrite::Range, DataFlow::MethodCallNode {
|
||||
private class SetCookieCall extends Http::Server::CookieWrite::Range, DataFlow::MethodCallNode {
|
||||
SetCookieCall() { this.calls(instance(), "set_cookie") }
|
||||
|
||||
override DataFlow::Node getHeaderArg() { none() }
|
||||
@@ -348,7 +348,7 @@ private module FastApi {
|
||||
* A call to `append` on a `headers` of a FastAPI Response, with the `Set-Cookie`
|
||||
* header-key.
|
||||
*/
|
||||
private class HeadersAppendCookie extends HTTP::Server::CookieWrite::Range,
|
||||
private class HeadersAppendCookie extends Http::Server::CookieWrite::Range,
|
||||
DataFlow::MethodCallNode {
|
||||
HeadersAppendCookie() {
|
||||
exists(DataFlow::AttrRead headers, DataFlow::Node keyArg |
|
||||
|
||||
@@ -116,7 +116,7 @@ module Flask {
|
||||
*
|
||||
* Use the predicate `Response::instance()` to get references to instances of `flask.Response`.
|
||||
*/
|
||||
abstract class InstanceSource extends HTTP::Server::HttpResponse::Range, DataFlow::Node { }
|
||||
abstract class InstanceSource extends Http::Server::HttpResponse::Range, DataFlow::Node { }
|
||||
|
||||
/** A direct instantiation of `flask.Response`. */
|
||||
private class ClassInstantiation extends InstanceSource, DataFlow::CallCfgNode {
|
||||
@@ -229,7 +229,7 @@ module Flask {
|
||||
// TODO: This doesn't handle attribute assignment. Should be OK, but analysis is not as complete as with
|
||||
// points-to and `.lookup`, which would handle `post = my_post_handler` inside class def
|
||||
result = this.getAMethod() and
|
||||
result.getName() = HTTP::httpVerbLower()
|
||||
result.getName() = Http::httpVerbLower()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -241,7 +241,7 @@ module Flask {
|
||||
}
|
||||
|
||||
/** A route setup made by flask (sharing handling of URL patterns). */
|
||||
abstract class FlaskRouteSetup extends HTTP::Server::RouteSetup::Range {
|
||||
abstract class FlaskRouteSetup extends Http::Server::RouteSetup::Range {
|
||||
override Parameter getARoutedParameter() {
|
||||
// If we don't know the URL pattern, we simply mark all parameters as a routed
|
||||
// parameter. This should give us more RemoteFlowSources but could also lead to
|
||||
@@ -312,7 +312,7 @@ module Flask {
|
||||
}
|
||||
|
||||
/** A request handler defined in a django view class, that has no known route. */
|
||||
private class FlaskViewClassHandlerWithoutKnownRoute extends HTTP::Server::RequestHandler::Range {
|
||||
private class FlaskViewClassHandlerWithoutKnownRoute extends Http::Server::RequestHandler::Range {
|
||||
FlaskViewClassHandlerWithoutKnownRoute() {
|
||||
exists(FlaskViewClass vc | vc.getARequestHandler() = this) and
|
||||
not exists(FlaskRouteSetup setup | setup.getARequestHandler() = this)
|
||||
@@ -439,7 +439,7 @@ module Flask {
|
||||
// ---------------------------------------------------------------------------
|
||||
// Implicit response from returns of flask request handlers
|
||||
// ---------------------------------------------------------------------------
|
||||
private class FlaskRouteHandlerReturn extends HTTP::Server::HttpResponse::Range, DataFlow::CfgNode {
|
||||
private class FlaskRouteHandlerReturn extends Http::Server::HttpResponse::Range, DataFlow::CfgNode {
|
||||
FlaskRouteHandlerReturn() {
|
||||
exists(Function routeHandler |
|
||||
routeHandler = any(FlaskRouteSetup rs).getARequestHandler() and
|
||||
@@ -462,7 +462,7 @@ module Flask {
|
||||
*
|
||||
* See https://flask.palletsprojects.com/en/1.1.x/api/#flask.redirect
|
||||
*/
|
||||
private class FlaskRedirectCall extends HTTP::Server::HttpRedirectResponse::Range,
|
||||
private class FlaskRedirectCall extends Http::Server::HttpRedirectResponse::Range,
|
||||
DataFlow::CallCfgNode {
|
||||
FlaskRedirectCall() { this = API::moduleImport("flask").getMember("redirect").getACall() }
|
||||
|
||||
@@ -490,7 +490,7 @@ module Flask {
|
||||
*
|
||||
* See https://flask.palletsprojects.com/en/2.0.x/api/#flask.Response.set_cookie
|
||||
*/
|
||||
class FlaskResponseSetCookieCall extends HTTP::Server::CookieWrite::Range,
|
||||
class FlaskResponseSetCookieCall extends Http::Server::CookieWrite::Range,
|
||||
DataFlow::MethodCallNode {
|
||||
FlaskResponseSetCookieCall() { this.calls(Flask::Response::instance(), "set_cookie") }
|
||||
|
||||
@@ -506,7 +506,7 @@ module Flask {
|
||||
*
|
||||
* See https://flask.palletsprojects.com/en/2.0.x/api/#flask.Response.delete_cookie
|
||||
*/
|
||||
class FlaskResponseDeleteCookieCall extends HTTP::Server::CookieWrite::Range,
|
||||
class FlaskResponseDeleteCookieCall extends Http::Server::CookieWrite::Range,
|
||||
DataFlow::MethodCallNode {
|
||||
FlaskResponseDeleteCookieCall() { this.calls(Flask::Response::instance(), "delete_cookie") }
|
||||
|
||||
|
||||
@@ -23,11 +23,11 @@ private module HttpxModel {
|
||||
*
|
||||
* See https://www.python-httpx.org/api/
|
||||
*/
|
||||
private class RequestCall extends HTTP::Client::Request::Range, API::CallNode {
|
||||
private class RequestCall extends Http::Client::Request::Range, API::CallNode {
|
||||
string methodName;
|
||||
|
||||
RequestCall() {
|
||||
methodName in [HTTP::httpVerbLower(), "request", "stream"] and
|
||||
methodName in [Http::httpVerbLower(), "request", "stream"] and
|
||||
this = API::moduleImport("httpx").getMember(methodName).getACall()
|
||||
}
|
||||
|
||||
@@ -64,11 +64,11 @@ private module HttpxModel {
|
||||
}
|
||||
|
||||
/** A method call on a Client that sends off a request */
|
||||
private class OutgoingRequestCall extends HTTP::Client::Request::Range, DataFlow::CallCfgNode {
|
||||
private class OutgoingRequestCall extends Http::Client::Request::Range, DataFlow::CallCfgNode {
|
||||
string methodName;
|
||||
|
||||
OutgoingRequestCall() {
|
||||
methodName in [HTTP::httpVerbLower(), "request", "stream"] and
|
||||
methodName in [Http::httpVerbLower(), "request", "stream"] and
|
||||
this = classRef().getReturn().getMember(methodName).getACall()
|
||||
}
|
||||
|
||||
|
||||
@@ -13,13 +13,13 @@ private import semmle.python.ApiGraphs
|
||||
*
|
||||
* See https://www.python-ldap.org/en/python-ldap-3.3.0/index.html
|
||||
*/
|
||||
private module Ldap {
|
||||
private module PythonLdap {
|
||||
/**
|
||||
* The execution of an `ldap` query.
|
||||
*
|
||||
* See https://www.python-ldap.org/en/python-ldap-3.3.0/reference/ldap.html#functions
|
||||
*/
|
||||
private class LdapQueryExecution extends DataFlow::CallCfgNode, LDAP::LdapExecution::Range {
|
||||
private class LdapQueryExecution extends DataFlow::CallCfgNode, Ldap::LdapExecution::Range {
|
||||
LdapQueryExecution() {
|
||||
this =
|
||||
API::moduleImport("ldap")
|
||||
|
||||
@@ -15,7 +15,7 @@ private import semmle.python.ApiGraphs
|
||||
*/
|
||||
private module Ldap3 {
|
||||
/** The execution of an `ldap` query. */
|
||||
private class LdapQueryExecution extends DataFlow::CallCfgNode, LDAP::LdapExecution::Range {
|
||||
private class LdapQueryExecution extends DataFlow::CallCfgNode, Ldap::LdapExecution::Range {
|
||||
LdapQueryExecution() {
|
||||
this =
|
||||
API::moduleImport("ldap3")
|
||||
|
||||
@@ -22,7 +22,7 @@ private module Libtaxii {
|
||||
* A call to `libtaxii.common.parse`.
|
||||
* When the `allow_url` parameter value is set to `True`, there is an SSRF vulnerability..
|
||||
*/
|
||||
private class ParseCall extends HTTP::Client::Request::Range, DataFlow::CallCfgNode {
|
||||
private class ParseCall extends Http::Client::Request::Range, DataFlow::CallCfgNode {
|
||||
ParseCall() {
|
||||
this = API::moduleImport("libtaxii").getMember("common").getMember("parse").getACall() and
|
||||
this.getArgByName("allow_url").getALocalSource().asExpr() = any(True t)
|
||||
|
||||
@@ -32,7 +32,7 @@ private module Peewee {
|
||||
.getASubclass*()
|
||||
or
|
||||
// Ohter known subclasses, semi auto generated by using
|
||||
// ```codeql
|
||||
// ```ql
|
||||
// class DBClass extends Class, SelfRefMixin {
|
||||
// DBClass() {
|
||||
// exists(this.getLocation().getFile().getRelativePath()) and
|
||||
|
||||
@@ -36,7 +36,7 @@ private module Pycurl {
|
||||
*
|
||||
* See http://pycurl.io/docs/latest/curlobject.html#pycurl.Curl.setopt.
|
||||
*/
|
||||
private class OutgoingRequestCall extends HTTP::Client::Request::Range, DataFlow::CallCfgNode {
|
||||
private class OutgoingRequestCall extends Http::Client::Request::Range, DataFlow::CallCfgNode {
|
||||
OutgoingRequestCall() {
|
||||
this = instance().getMember("setopt").getACall() and
|
||||
this.getArg(0).asCfgNode().(AttrNode).getName() = "URL"
|
||||
|
||||
@@ -28,11 +28,11 @@ private module Requests {
|
||||
*
|
||||
* See https://requests.readthedocs.io/en/latest/api/#requests.request
|
||||
*/
|
||||
private class OutgoingRequestCall extends HTTP::Client::Request::Range, API::CallNode {
|
||||
private class OutgoingRequestCall extends Http::Client::Request::Range, API::CallNode {
|
||||
string methodName;
|
||||
|
||||
OutgoingRequestCall() {
|
||||
methodName in [HTTP::httpVerbLower(), "request"] and
|
||||
methodName in [Http::httpVerbLower(), "request"] and
|
||||
(
|
||||
this = API::moduleImport("requests").getMember(methodName).getACall()
|
||||
or
|
||||
|
||||
@@ -158,7 +158,7 @@ private module RestFramework {
|
||||
* `HTTP::Server::RequestHandler`. We only need this for the ones that doesn't have a
|
||||
* known route setup.
|
||||
*/
|
||||
class RestFrameworkFunctionBasedViewWithoutKnownRoute extends HTTP::Server::RequestHandler::Range,
|
||||
class RestFrameworkFunctionBasedViewWithoutKnownRoute extends Http::Server::RequestHandler::Range,
|
||||
PrivateDjango::DjangoRouteHandler instanceof RestFrameworkFunctionBasedView {
|
||||
RestFrameworkFunctionBasedViewWithoutKnownRoute() {
|
||||
not exists(PrivateDjango::DjangoRouteSetup setup | setup.getARequestHandler() = this)
|
||||
@@ -220,7 +220,7 @@ private module RestFramework {
|
||||
*
|
||||
* Use the predicate `Request::instance()` to get references to instances of `rest_framework.request.Request`.
|
||||
*/
|
||||
abstract class InstanceSource extends PrivateDjango::DjangoImpl::Http::Request::HttpRequest::InstanceSource {
|
||||
abstract class InstanceSource extends PrivateDjango::DjangoImpl::DjangoHttp::Request::HttpRequest::InstanceSource {
|
||||
}
|
||||
|
||||
/** A direct instantiation of `rest_framework.request.Request`. */
|
||||
@@ -296,7 +296,7 @@ private module RestFramework {
|
||||
}
|
||||
|
||||
/** A direct instantiation of `rest_framework.response.Response`. */
|
||||
private class ClassInstantiation extends PrivateDjango::DjangoImpl::Http::Response::HttpResponse::InstanceSource,
|
||||
private class ClassInstantiation extends PrivateDjango::DjangoImpl::DjangoHttp::Response::HttpResponse::InstanceSource,
|
||||
DataFlow::CallCfgNode {
|
||||
ClassInstantiation() { this = classRef().getACall() }
|
||||
|
||||
@@ -320,7 +320,7 @@ private module RestFramework {
|
||||
*/
|
||||
module ApiException {
|
||||
/** A direct instantiation of `rest_framework.exceptions.ApiException` or subclass. */
|
||||
private class ClassInstantiation extends HTTP::Server::HttpResponse::Range,
|
||||
private class ClassInstantiation extends Http::Server::HttpResponse::Range,
|
||||
DataFlow::CallCfgNode {
|
||||
string className;
|
||||
|
||||
|
||||
@@ -85,7 +85,7 @@ module Starlette {
|
||||
}
|
||||
|
||||
/** An attribute read on a `starlette.websockets.WebSocket` instance that is a `starlette.requests.URL` instance. */
|
||||
private class UrlInstances extends URL::InstanceSource {
|
||||
private class UrlInstances extends Url::InstanceSource {
|
||||
UrlInstances() {
|
||||
this.(DataFlow::AttrRead).getObject() = instance() and
|
||||
this.(DataFlow::AttrRead).getAttributeName() = "url"
|
||||
@@ -98,7 +98,7 @@ module Starlette {
|
||||
*
|
||||
* See the URL part of https://www.starlette.io/websockets/.
|
||||
*/
|
||||
module URL {
|
||||
module Url {
|
||||
/** Gets a reference to the `starlette.requests.URL` class. */
|
||||
private API::Node classRef() {
|
||||
result = API::moduleImport("starlette").getMember("requests").getMember("URL")
|
||||
@@ -159,4 +159,7 @@ module Starlette {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** DEPRECATED: Alias for Url */
|
||||
deprecated module URL = Url;
|
||||
}
|
||||
|
||||
@@ -9,6 +9,7 @@ private import semmle.python.dataflow.new.TaintTracking
|
||||
private import semmle.python.dataflow.new.RemoteFlowSources
|
||||
private import semmle.python.Concepts
|
||||
private import semmle.python.ApiGraphs
|
||||
private import semmle.python.dataflow.new.FlowSummary
|
||||
private import semmle.python.frameworks.PEP249
|
||||
private import semmle.python.frameworks.internal.PoorMansFunctionResolution
|
||||
private import semmle.python.frameworks.internal.SelfRefMixin
|
||||
@@ -1877,7 +1878,7 @@ private module StdlibPrivate {
|
||||
API::Node http() { result = API::moduleImport("http") }
|
||||
|
||||
/** Provides models for the `http` module. */
|
||||
module Http {
|
||||
module StdlibHttp {
|
||||
// -------------------------------------------------------------------------
|
||||
// http.server
|
||||
// -------------------------------------------------------------------------
|
||||
@@ -1944,9 +1945,9 @@ private module StdlibPrivate {
|
||||
SimpleHttpServer::SimpleHttpRequestHandler::classRef(),
|
||||
CgiHttpServer::CgiHttpRequestHandler::classRef(),
|
||||
// Python 3
|
||||
Http::Server::BaseHttpRequestHandler::classRef(),
|
||||
Http::Server::SimpleHttpRequestHandler::classRef(),
|
||||
Http::Server::CgiHttpRequestHandler::classRef()
|
||||
StdlibHttp::Server::BaseHttpRequestHandler::classRef(),
|
||||
StdlibHttp::Server::SimpleHttpRequestHandler::classRef(),
|
||||
StdlibHttp::Server::CgiHttpRequestHandler::classRef()
|
||||
].getASubclass*()
|
||||
}
|
||||
|
||||
@@ -2026,10 +2027,10 @@ private module StdlibPrivate {
|
||||
*
|
||||
* Not essential for any functionality, but provides a consistent modeling.
|
||||
*/
|
||||
private class RequestHandlerFunc extends HTTP::Server::RequestHandler::Range {
|
||||
private class RequestHandlerFunc extends Http::Server::RequestHandler::Range {
|
||||
RequestHandlerFunc() {
|
||||
this = any(HttpRequestHandlerClassDef cls).getAMethod() and
|
||||
this.getName() = "do_" + HTTP::httpVerb()
|
||||
this.getName() = "do_" + Http::httpVerb()
|
||||
}
|
||||
|
||||
override Parameter getARoutedParameter() { none() }
|
||||
@@ -2064,7 +2065,7 @@ private module StdlibPrivate {
|
||||
* See https://github.com/python/cpython/blob/b567b9d74bd9e476a3027335873bb0508d6e450f/Lib/wsgiref/handlers.py#L137
|
||||
* for how a request is processed and given to an application.
|
||||
*/
|
||||
class WsgirefSimpleServerApplication extends HTTP::Server::RequestHandler::Range {
|
||||
class WsgirefSimpleServerApplication extends Http::Server::RequestHandler::Range {
|
||||
WsgirefSimpleServerApplication() {
|
||||
exists(DataFlow::Node appArg, DataFlow::CallCfgNode setAppCall |
|
||||
(
|
||||
@@ -2098,8 +2099,8 @@ private module StdlibPrivate {
|
||||
*
|
||||
* See https://docs.python.org/3.10/library/wsgiref.html#wsgiref.simple_server.WSGIRequestHandler.get_environ
|
||||
*/
|
||||
class WSGIEnvirontParameter extends RemoteFlowSource::Range, DataFlow::ParameterNode {
|
||||
WSGIEnvirontParameter() {
|
||||
class WsgiEnvirontParameter extends RemoteFlowSource::Range, DataFlow::ParameterNode {
|
||||
WsgiEnvirontParameter() {
|
||||
exists(WsgirefSimpleServerApplication func |
|
||||
if func.isMethod()
|
||||
then this.getParameter() = func.getArg(1)
|
||||
@@ -2112,6 +2113,9 @@ private module StdlibPrivate {
|
||||
}
|
||||
}
|
||||
|
||||
/** DEPRECATED: Alias for WsgiEnvirontParameter */
|
||||
deprecated class WSGIEnvirontParameter = WsgiEnvirontParameter;
|
||||
|
||||
/**
|
||||
* Gets a reference to the parameter of a `WsgirefSimpleServerApplication` that
|
||||
* takes the `start_response` function.
|
||||
@@ -2163,7 +2167,7 @@ private module StdlibPrivate {
|
||||
*
|
||||
* See https://github.com/python/cpython/blob/b567b9d74bd9e476a3027335873bb0508d6e450f/Lib/wsgiref/handlers.py#L276
|
||||
*/
|
||||
class WsgirefSimpleServerApplicationWriteCall extends HTTP::Server::HttpResponse::Range,
|
||||
class WsgirefSimpleServerApplicationWriteCall extends Http::Server::HttpResponse::Range,
|
||||
DataFlow::CallCfgNode {
|
||||
WsgirefSimpleServerApplicationWriteCall() { this.getFunction() = writeFunction() }
|
||||
|
||||
@@ -2177,7 +2181,7 @@ private module StdlibPrivate {
|
||||
/**
|
||||
* A return from a `WsgirefSimpleServerApplication`, which is included in the response body.
|
||||
*/
|
||||
class WsgirefSimpleServerApplicationReturn extends HTTP::Server::HttpResponse::Range,
|
||||
class WsgirefSimpleServerApplicationReturn extends Http::Server::HttpResponse::Range,
|
||||
DataFlow::CfgNode {
|
||||
WsgirefSimpleServerApplicationReturn() {
|
||||
exists(WsgirefSimpleServerApplication requestHandler |
|
||||
@@ -2264,7 +2268,7 @@ private module StdlibPrivate {
|
||||
}
|
||||
|
||||
/** A method call on a HttpConnection that sends off a request */
|
||||
private class RequestCall extends HTTP::Client::Request::Range, DataFlow::MethodCallNode {
|
||||
private class RequestCall extends Http::Client::Request::Range, DataFlow::MethodCallNode {
|
||||
RequestCall() { this.calls(instance(_), ["request", "_send_request", "putrequest"]) }
|
||||
|
||||
DataFlow::Node getUrlArg() { result in [this.getArg(1), this.getArgByName("url")] }
|
||||
@@ -3667,6 +3671,23 @@ private module StdlibPrivate {
|
||||
|
||||
override DataFlow::Node getAPathArgument() { result = this.getAnInput() }
|
||||
}
|
||||
|
||||
/** A flow summary for `reversed`. */
|
||||
class ReversedSummary extends SummarizedCallable {
|
||||
ReversedSummary() { this = "builtins.reversed" }
|
||||
|
||||
override DataFlow::CallCfgNode getACall() { result = API::builtin("reversed").getACall() }
|
||||
|
||||
override DataFlow::ArgumentNode getACallback() {
|
||||
result = API::builtin("reversed").getAValueReachableFromSource()
|
||||
}
|
||||
|
||||
override predicate propagatesFlowExt(string input, string output, boolean preservesValue) {
|
||||
input = "Argument[0].ListElement" and
|
||||
output = "ReturnValue.ListElement" and
|
||||
preservesValue = true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
@@ -30,7 +30,7 @@ private module Urllib {
|
||||
* See
|
||||
* - https://docs.python.org/3.9/library/urllib.request.html#urllib.request.Request
|
||||
*/
|
||||
private class RequestCall extends HTTP::Client::Request::Range, DataFlow::CallCfgNode {
|
||||
private class RequestCall extends Http::Client::Request::Range, DataFlow::CallCfgNode {
|
||||
RequestCall() {
|
||||
this = API::moduleImport("urllib").getMember("request").getMember("Request").getACall()
|
||||
}
|
||||
@@ -52,7 +52,7 @@ private module Urllib {
|
||||
* See
|
||||
* - https://docs.python.org/3.9/library/urllib.request.html#urllib.request.urlopen
|
||||
*/
|
||||
private class UrlOpenCall extends HTTP::Client::Request::Range, DataFlow::CallCfgNode {
|
||||
private class UrlOpenCall extends Http::Client::Request::Range, DataFlow::CallCfgNode {
|
||||
UrlOpenCall() {
|
||||
this = API::moduleImport("urllib").getMember("request").getMember("urlopen").getACall()
|
||||
}
|
||||
|
||||
@@ -20,7 +20,7 @@ private module Urllib2 {
|
||||
* See
|
||||
* - https://docs.python.org/2/library/urllib2.html#urllib2.Request
|
||||
*/
|
||||
private class RequestCall extends HTTP::Client::Request::Range, DataFlow::CallCfgNode {
|
||||
private class RequestCall extends Http::Client::Request::Range, DataFlow::CallCfgNode {
|
||||
RequestCall() { this = API::moduleImport("urllib2").getMember("Request").getACall() }
|
||||
|
||||
override DataFlow::Node getAUrlPart() { result in [this.getArg(0), this.getArgByName("url")] }
|
||||
@@ -40,7 +40,7 @@ private module Urllib2 {
|
||||
* See
|
||||
* - https://docs.python.org/2/library/urllib2.html#urllib2.urlopen
|
||||
*/
|
||||
private class UrlOpenCall extends HTTP::Client::Request::Range, DataFlow::CallCfgNode {
|
||||
private class UrlOpenCall extends Http::Client::Request::Range, DataFlow::CallCfgNode {
|
||||
UrlOpenCall() { this = API::moduleImport("urllib2").getMember("urlopen").getACall() }
|
||||
|
||||
override DataFlow::Node getAUrlPart() { result in [this.getArg(0), this.getArgByName("url")] }
|
||||
|
||||
@@ -99,7 +99,7 @@ private module Tornado {
|
||||
// TODO: This doesn't handle attribute assignment. Should be OK, but analysis is not as complete as with
|
||||
// points-to and `.lookup`, which would handle `post = my_post_handler` inside class def
|
||||
result = this.getAMethod() and
|
||||
result.getName() = HTTP::httpVerbLower()
|
||||
result.getName() = Http::httpVerbLower()
|
||||
}
|
||||
|
||||
/** Gets a reference to this class. */
|
||||
@@ -375,7 +375,7 @@ private module Tornado {
|
||||
}
|
||||
|
||||
/** A tornado route setup. */
|
||||
abstract class TornadoRouteSetup extends HTTP::Server::RouteSetup::Range {
|
||||
abstract class TornadoRouteSetup extends Http::Server::RouteSetup::Range {
|
||||
override string getFramework() { result = "Tornado" }
|
||||
}
|
||||
|
||||
@@ -437,7 +437,7 @@ private module Tornado {
|
||||
}
|
||||
|
||||
/** A request handler defined in a tornado RequestHandler class, that has no known route. */
|
||||
private class TornadoRequestHandlerWithoutKnownRoute extends HTTP::Server::RequestHandler::Range {
|
||||
private class TornadoRequestHandlerWithoutKnownRoute extends Http::Server::RequestHandler::Range {
|
||||
TornadoRequestHandlerWithoutKnownRoute() {
|
||||
exists(TornadoModule::Web::RequestHandler::RequestHandlerClass cls |
|
||||
cls.getARequestHandler() = this
|
||||
@@ -464,7 +464,7 @@ private module Tornado {
|
||||
*
|
||||
* See https://www.tornadoweb.org/en/stable/web.html#tornado.web.RequestHandler.redirect
|
||||
*/
|
||||
private class TornadoRequestHandlerRedirectCall extends HTTP::Server::HttpRedirectResponse::Range,
|
||||
private class TornadoRequestHandlerRedirectCall extends Http::Server::HttpRedirectResponse::Range,
|
||||
DataFlow::CallCfgNode {
|
||||
TornadoRequestHandlerRedirectCall() {
|
||||
this.getFunction() = TornadoModule::Web::RequestHandler::redirectMethod()
|
||||
@@ -486,7 +486,7 @@ private module Tornado {
|
||||
*
|
||||
* See https://www.tornadoweb.org/en/stable/web.html#tornado.web.RequestHandler.write
|
||||
*/
|
||||
private class TornadoRequestHandlerWriteCall extends HTTP::Server::HttpResponse::Range,
|
||||
private class TornadoRequestHandlerWriteCall extends Http::Server::HttpResponse::Range,
|
||||
DataFlow::CallCfgNode {
|
||||
TornadoRequestHandlerWriteCall() {
|
||||
this.getFunction() = TornadoModule::Web::RequestHandler::writeMethod()
|
||||
@@ -504,7 +504,7 @@ private module Tornado {
|
||||
*
|
||||
* See https://www.tornadoweb.org/en/stable/web.html#tornado.web.RequestHandler.set_cookie
|
||||
*/
|
||||
class TornadoRequestHandlerSetCookieCall extends HTTP::Server::CookieWrite::Range,
|
||||
class TornadoRequestHandlerSetCookieCall extends Http::Server::CookieWrite::Range,
|
||||
DataFlow::MethodCallNode {
|
||||
TornadoRequestHandlerSetCookieCall() {
|
||||
this.calls(TornadoModule::Web::RequestHandler::instance(), "set_cookie")
|
||||
|
||||
@@ -60,7 +60,7 @@ private module Twisted {
|
||||
}
|
||||
|
||||
/** A method that handles incoming requests, on a `twisted.web.resource.Resource` subclass. */
|
||||
class TwistedResourceRequestHandler extends HTTP::Server::RequestHandler::Range {
|
||||
class TwistedResourceRequestHandler extends Http::Server::RequestHandler::Range {
|
||||
TwistedResourceRequestHandler() { this = any(TwistedResourceSubclass cls).getARequestHandler() }
|
||||
|
||||
Parameter getRequestParameter() { result = this.getArg(getRequestParamIndex(this.getName())) }
|
||||
@@ -176,7 +176,7 @@ private module Twisted {
|
||||
/**
|
||||
* Implicit response from returns of render methods.
|
||||
*/
|
||||
private class TwistedResourceRenderMethodReturn extends HTTP::Server::HttpResponse::Range,
|
||||
private class TwistedResourceRenderMethodReturn extends Http::Server::HttpResponse::Range,
|
||||
DataFlow::CfgNode {
|
||||
TwistedResourceRenderMethodReturn() {
|
||||
this.asCfgNode() = any(TwistedResourceRenderMethod meth).getAReturnValueFlowNode()
|
||||
@@ -194,7 +194,7 @@ private module Twisted {
|
||||
*
|
||||
* See https://twistedmatrix.com/documents/21.2.0/api/twisted.web.server.Request.html#write
|
||||
*/
|
||||
class TwistedRequestWriteCall extends HTTP::Server::HttpResponse::Range, DataFlow::MethodCallNode {
|
||||
class TwistedRequestWriteCall extends Http::Server::HttpResponse::Range, DataFlow::MethodCallNode {
|
||||
TwistedRequestWriteCall() { this.calls(Request::instance(), "write") }
|
||||
|
||||
override DataFlow::Node getBody() {
|
||||
@@ -211,7 +211,7 @@ private module Twisted {
|
||||
*
|
||||
* See https://twistedmatrix.com/documents/21.2.0/api/twisted.web.http.Request.html#redirect
|
||||
*/
|
||||
class TwistedRequestRedirectCall extends HTTP::Server::HttpRedirectResponse::Range,
|
||||
class TwistedRequestRedirectCall extends Http::Server::HttpRedirectResponse::Range,
|
||||
DataFlow::MethodCallNode {
|
||||
TwistedRequestRedirectCall() { this.calls(Request::instance(), "redirect") }
|
||||
|
||||
@@ -231,7 +231,7 @@ private module Twisted {
|
||||
*
|
||||
* See https://twistedmatrix.com/documents/21.2.0/api/twisted.web.http.Request.html#addCookie
|
||||
*/
|
||||
class TwistedRequestAddCookieCall extends HTTP::Server::CookieWrite::Range,
|
||||
class TwistedRequestAddCookieCall extends Http::Server::CookieWrite::Range,
|
||||
DataFlow::MethodCallNode {
|
||||
TwistedRequestAddCookieCall() { this.calls(Twisted::Request::instance(), "addCookie") }
|
||||
|
||||
@@ -247,7 +247,7 @@ private module Twisted {
|
||||
*
|
||||
* See https://twistedmatrix.com/documents/21.2.0/api/twisted.web.http.Request.html#cookies
|
||||
*/
|
||||
class TwistedRequestCookiesAppendCall extends HTTP::Server::CookieWrite::Range,
|
||||
class TwistedRequestCookiesAppendCall extends Http::Server::CookieWrite::Range,
|
||||
DataFlow::MethodCallNode {
|
||||
TwistedRequestCookiesAppendCall() {
|
||||
exists(DataFlow::AttrRead cookiesLookup |
|
||||
|
||||
@@ -49,7 +49,7 @@ private module Urllib3 {
|
||||
* - https://urllib3.readthedocs.io/en/stable/reference/urllib3.request.html#urllib3.request.RequestMethods
|
||||
* - https://urllib3.readthedocs.io/en/stable/reference/urllib3.connectionpool.html#urllib3.HTTPConnectionPool.urlopen
|
||||
*/
|
||||
private class RequestCall extends HTTP::Client::Request::Range, API::CallNode {
|
||||
private class RequestCall extends Http::Client::Request::Range, API::CallNode {
|
||||
RequestCall() {
|
||||
this =
|
||||
classRef()
|
||||
|
||||
@@ -72,6 +72,8 @@ private class Unit = Specific::Unit;
|
||||
|
||||
private module API = Specific::API;
|
||||
|
||||
private module DataFlow = Specific::DataFlow;
|
||||
|
||||
private import Specific::AccessPathSyntax
|
||||
|
||||
/** Module containing hooks for providing input data to be interpreted as a model. */
|
||||
@@ -155,6 +157,38 @@ module ModelInput {
|
||||
*/
|
||||
abstract predicate row(string row);
|
||||
}
|
||||
|
||||
/**
|
||||
* A unit class for adding additional type model rows from CodeQL models.
|
||||
*/
|
||||
class TypeModel extends Unit {
|
||||
/**
|
||||
* Gets a data-flow node that is a source of the type `package;type`.
|
||||
*/
|
||||
DataFlow::Node getASource(string package, string type) { none() }
|
||||
|
||||
/**
|
||||
* Gets a data flow node that is a sink of the type `package;type`,
|
||||
* usually because it is an argument passed to a parameter of that type.
|
||||
*/
|
||||
DataFlow::Node getASink(string package, string type) { none() }
|
||||
}
|
||||
|
||||
/**
|
||||
* A unit class for adding additional type variable model rows.
|
||||
*/
|
||||
class TypeVariableModelCsv extends Unit {
|
||||
/**
|
||||
* Holds if `row` specifies a path through a type variable.
|
||||
*
|
||||
* A row of form,
|
||||
* ```
|
||||
* name;path
|
||||
* ```
|
||||
* means `path` can be substituted for a token `TypeVar[name]`.
|
||||
*/
|
||||
abstract predicate row(string row);
|
||||
}
|
||||
}
|
||||
|
||||
private import ModelInput
|
||||
@@ -182,6 +216,8 @@ private predicate summaryModel(string row) { any(SummaryModelCsv s).row(inverseP
|
||||
|
||||
private predicate typeModel(string row) { any(TypeModelCsv s).row(inversePad(row)) }
|
||||
|
||||
private predicate typeVariableModel(string row) { any(TypeVariableModelCsv s).row(inversePad(row)) }
|
||||
|
||||
/** Holds if a source model exists for the given parameters. */
|
||||
predicate sourceModel(string package, string type, string path, string kind) {
|
||||
exists(string row |
|
||||
@@ -219,7 +255,7 @@ private predicate summaryModel(
|
||||
)
|
||||
}
|
||||
|
||||
/** Holds if an type model exists for the given parameters. */
|
||||
/** Holds if a type model exists for the given parameters. */
|
||||
private predicate typeModel(
|
||||
string package1, string type1, string package2, string type2, string path
|
||||
) {
|
||||
@@ -233,6 +269,15 @@ private predicate typeModel(
|
||||
)
|
||||
}
|
||||
|
||||
/** Holds if a type variable model exists for the given parameters. */
|
||||
private predicate typeVariableModel(string name, string path) {
|
||||
exists(string row |
|
||||
typeVariableModel(row) and
|
||||
row.splitAt(";", 0) = name and
|
||||
row.splitAt(";", 1) = path
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a package that should be seen as an alias for the given other `package`,
|
||||
* or the `package` itself.
|
||||
@@ -253,7 +298,7 @@ private predicate isRelevantPackage(string package) {
|
||||
sourceModel(package, _, _, _) or
|
||||
sinkModel(package, _, _, _) or
|
||||
summaryModel(package, _, _, _, _, _) or
|
||||
typeModel(package, _, _, _, _)
|
||||
typeModel(_, _, package, _, _)
|
||||
) and
|
||||
(
|
||||
Specific::isPackageUsed(package)
|
||||
@@ -290,6 +335,8 @@ private class AccessPathRange extends AccessPath::Range {
|
||||
summaryModel(package, _, _, this, _, _) or
|
||||
summaryModel(package, _, _, _, this, _)
|
||||
)
|
||||
or
|
||||
typeVariableModel(_, this)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -339,6 +386,57 @@ private predicate invocationMatchesCallSiteFilter(Specific::InvokeNode invoke, A
|
||||
Specific::invocationMatchesExtraCallSiteFilter(invoke, token)
|
||||
}
|
||||
|
||||
private class TypeModelUseEntry extends API::EntryPoint {
|
||||
private string package;
|
||||
private string type;
|
||||
|
||||
TypeModelUseEntry() {
|
||||
exists(any(TypeModel tm).getASource(package, type)) and
|
||||
this = "TypeModelUseEntry;" + package + ";" + type
|
||||
}
|
||||
|
||||
override DataFlow::LocalSourceNode getASource() {
|
||||
result = any(TypeModel tm).getASource(package, type)
|
||||
}
|
||||
|
||||
API::Node getNodeForType(string package_, string type_) {
|
||||
package = package_ and type = type_ and result = this.getANode()
|
||||
}
|
||||
}
|
||||
|
||||
private class TypeModelDefEntry extends API::EntryPoint {
|
||||
private string package;
|
||||
private string type;
|
||||
|
||||
TypeModelDefEntry() {
|
||||
exists(any(TypeModel tm).getASink(package, type)) and
|
||||
this = "TypeModelDefEntry;" + package + ";" + type
|
||||
}
|
||||
|
||||
override DataFlow::Node getASink() { result = any(TypeModel tm).getASink(package, type) }
|
||||
|
||||
API::Node getNodeForType(string package_, string type_) {
|
||||
package = package_ and type = type_ and result = this.getANode()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets an API node identified by the given `(package,type)` pair.
|
||||
*/
|
||||
pragma[nomagic]
|
||||
private API::Node getNodeFromType(string package, string type) {
|
||||
exists(string package2, string type2, AccessPath path2 |
|
||||
typeModel(package, type, package2, type2, path2) and
|
||||
result = getNodeFromPath(package2, type2, path2)
|
||||
)
|
||||
or
|
||||
result = any(TypeModelUseEntry e).getNodeForType(package, type)
|
||||
or
|
||||
result = any(TypeModelDefEntry e).getNodeForType(package, type)
|
||||
or
|
||||
result = Specific::getExtraNodeFromType(package, type)
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the API node identified by the first `n` tokens of `path` in the given `(package, type, path)` tuple.
|
||||
*/
|
||||
@@ -347,12 +445,8 @@ private API::Node getNodeFromPath(string package, string type, AccessPath path,
|
||||
isRelevantFullPath(package, type, path) and
|
||||
(
|
||||
n = 0 and
|
||||
exists(string package2, string type2, AccessPath path2 |
|
||||
typeModel(package, type, package2, type2, path2) and
|
||||
result = getNodeFromPath(package2, type2, path2, path2.getNumToken())
|
||||
)
|
||||
result = getNodeFromType(package, type)
|
||||
or
|
||||
// Language-specific cases, such as handling of global variables
|
||||
result = Specific::getExtraNodeFromPath(package, type, path, n)
|
||||
)
|
||||
or
|
||||
@@ -361,6 +455,72 @@ private API::Node getNodeFromPath(string package, string type, AccessPath path,
|
||||
// Similar to the other recursive case, but where the path may have stepped through one or more call-site filters
|
||||
result =
|
||||
getSuccessorFromInvoke(getInvocationFromPath(package, type, path, n - 1), path.getToken(n - 1))
|
||||
or
|
||||
// Apply a subpath
|
||||
result =
|
||||
getNodeFromSubPath(getNodeFromPath(package, type, path, n - 1), getSubPathAt(path, n - 1))
|
||||
or
|
||||
// Apply a type step
|
||||
typeStep(getNodeFromPath(package, type, path, n), result)
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a subpath for the `TypeVar` token found at the `n`th token of `path`.
|
||||
*/
|
||||
pragma[nomagic]
|
||||
private AccessPath getSubPathAt(AccessPath path, int n) {
|
||||
exists(string typeVarName |
|
||||
path.getToken(n).getAnArgument("TypeVar") = typeVarName and
|
||||
typeVariableModel(typeVarName, result)
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a node that is found by evaluating the first `n` tokens of `subPath` starting at `base`.
|
||||
*/
|
||||
pragma[nomagic]
|
||||
private API::Node getNodeFromSubPath(API::Node base, AccessPath subPath, int n) {
|
||||
exists(AccessPath path, int k |
|
||||
base = [getNodeFromPath(_, _, path, k), getNodeFromSubPath(_, path, k)] and
|
||||
subPath = getSubPathAt(path, k) and
|
||||
result = base and
|
||||
n = 0
|
||||
)
|
||||
or
|
||||
exists(string package, string type, AccessPath basePath |
|
||||
typeStepModel(package, type, basePath, subPath) and
|
||||
base = getNodeFromPath(package, type, basePath) and
|
||||
result = base and
|
||||
n = 0
|
||||
)
|
||||
or
|
||||
result = getSuccessorFromNode(getNodeFromSubPath(base, subPath, n - 1), subPath.getToken(n - 1))
|
||||
or
|
||||
result =
|
||||
getSuccessorFromInvoke(getInvocationFromSubPath(base, subPath, n - 1), subPath.getToken(n - 1))
|
||||
or
|
||||
result =
|
||||
getNodeFromSubPath(getNodeFromSubPath(base, subPath, n - 1), getSubPathAt(subPath, n - 1))
|
||||
or
|
||||
typeStep(getNodeFromSubPath(base, subPath, n), result)
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a call site that is found by evaluating the first `n` tokens of `subPath` starting at `base`.
|
||||
*/
|
||||
private Specific::InvokeNode getInvocationFromSubPath(API::Node base, AccessPath subPath, int n) {
|
||||
result = Specific::getAnInvocationOf(getNodeFromSubPath(base, subPath, n))
|
||||
or
|
||||
result = getInvocationFromSubPath(base, subPath, n - 1) and
|
||||
invocationMatchesCallSiteFilter(result, subPath.getToken(n - 1))
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a node that is found by evaluating `subPath` starting at `base`.
|
||||
*/
|
||||
pragma[nomagic]
|
||||
private API::Node getNodeFromSubPath(API::Node base, AccessPath subPath) {
|
||||
result = getNodeFromSubPath(base, subPath, subPath.getNumToken())
|
||||
}
|
||||
|
||||
/** Gets the node identified by the given `(package, type, path)` tuple. */
|
||||
@@ -368,6 +528,20 @@ API::Node getNodeFromPath(string package, string type, AccessPath path) {
|
||||
result = getNodeFromPath(package, type, path, path.getNumToken())
|
||||
}
|
||||
|
||||
pragma[nomagic]
|
||||
private predicate typeStepModel(string package, string type, AccessPath basePath, AccessPath output) {
|
||||
summaryModel(package, type, basePath, "", output, "type")
|
||||
}
|
||||
|
||||
pragma[nomagic]
|
||||
private predicate typeStep(API::Node pred, API::Node succ) {
|
||||
exists(string package, string type, AccessPath basePath, AccessPath output |
|
||||
typeStepModel(package, type, basePath, output) and
|
||||
pred = getNodeFromPath(package, type, basePath) and
|
||||
succ = getNodeFromSubPath(pred, output)
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets an invocation identified by the given `(package, type, path)` tuple.
|
||||
*
|
||||
@@ -390,7 +564,7 @@ Specific::InvokeNode getInvocationFromPath(string package, string type, AccessPa
|
||||
*/
|
||||
bindingset[name]
|
||||
predicate isValidTokenNameInIdentifyingAccessPath(string name) {
|
||||
name = ["Argument", "Parameter", "ReturnValue", "WithArity"]
|
||||
name = ["Argument", "Parameter", "ReturnValue", "WithArity", "TypeVar"]
|
||||
or
|
||||
Specific::isExtraValidTokenNameInIdentifyingAccessPath(name)
|
||||
}
|
||||
@@ -418,6 +592,9 @@ predicate isValidTokenArgumentInIdentifyingAccessPath(string name, string argume
|
||||
name = "WithArity" and
|
||||
argument.regexpMatch("\\d+(\\.\\.(\\d+)?)?")
|
||||
or
|
||||
name = "TypeVar" and
|
||||
exists(argument)
|
||||
or
|
||||
Specific::isExtraValidTokenArgumentInIdentifyingAccessPath(name, argument)
|
||||
}
|
||||
|
||||
@@ -469,12 +646,7 @@ module ModelOutput {
|
||||
* Holds if `node` is seen as an instance of `(package,type)` due to a type definition
|
||||
* contributed by a CSV model.
|
||||
*/
|
||||
API::Node getATypeNode(string package, string type) {
|
||||
exists(string package2, string type2, AccessPath path |
|
||||
typeModel(package, type, package2, type2, path) and
|
||||
result = getNodeFromPath(package2, type2, path)
|
||||
)
|
||||
}
|
||||
API::Node getATypeNode(string package, string type) { result = getNodeFromType(package, type) }
|
||||
|
||||
/**
|
||||
* Gets an error message relating to an invalid CSV row in a model.
|
||||
@@ -489,6 +661,8 @@ module ModelOutput {
|
||||
any(SummaryModelCsv csv).row(row) and kind = "summary" and expectedArity = 6
|
||||
or
|
||||
any(TypeModelCsv csv).row(row) and kind = "type" and expectedArity = 5
|
||||
or
|
||||
any(TypeVariableModelCsv csv).row(row) and kind = "type-variable" and expectedArity = 2
|
||||
|
|
||||
actualArity = count(row.indexOf(";")) + 1 and
|
||||
actualArity != expectedArity and
|
||||
@@ -499,7 +673,7 @@ module ModelOutput {
|
||||
or
|
||||
// Check names and arguments of access path tokens
|
||||
exists(AccessPath path, AccessPathToken token |
|
||||
isRelevantFullPath(_, _, path) and
|
||||
(isRelevantFullPath(_, _, path) or typeVariableModel(_, path)) and
|
||||
token = path.getToken(_)
|
||||
|
|
||||
not isValidTokenNameInIdentifyingAccessPath(token.getName()) and
|
||||
|
||||
@@ -20,28 +20,29 @@
|
||||
*/
|
||||
|
||||
private import python as PY
|
||||
private import semmle.python.dataflow.new.DataFlow
|
||||
private import ApiGraphModels
|
||||
import semmle.python.ApiGraphs::API as API
|
||||
|
||||
class Unit = PY::Unit;
|
||||
|
||||
// Re-export libraries needed by ApiGraphModels.qll
|
||||
import semmle.python.frameworks.data.internal.AccessPathSyntax as AccessPathSyntax
|
||||
import semmle.python.dataflow.new.internal.AccessPathSyntax as AccessPathSyntax
|
||||
import semmle.python.dataflow.new.DataFlow::DataFlow as DataFlow
|
||||
private import AccessPathSyntax
|
||||
|
||||
/**
|
||||
* Holds if models describing `package` may be relevant for the analysis of this database.
|
||||
*/
|
||||
predicate isPackageUsed(string package) { exists(API::moduleImport(package)) }
|
||||
predicate isPackageUsed(string package) { API::moduleImportExists(package) }
|
||||
|
||||
/** Gets a Python-specific interpretation of the `(package, type, path)` tuple after resolving the first `n` access path tokens. */
|
||||
bindingset[package, type, path]
|
||||
API::Node getExtraNodeFromPath(string package, string type, AccessPath path, int n) {
|
||||
API::Node getExtraNodeFromPath(string package, string type, AccessPath path, int n) { none() }
|
||||
|
||||
/** Gets a Python-specific interpretation of the `(package, type)` tuple. */
|
||||
API::Node getExtraNodeFromType(string package, string type) {
|
||||
type = "" and
|
||||
n = 0 and
|
||||
result = API::moduleImport(package) and
|
||||
exists(path)
|
||||
result = API::moduleImport(package)
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -87,3 +87,70 @@ module Cryptography {
|
||||
predicate isWeak() { this = "ECB" }
|
||||
}
|
||||
}
|
||||
|
||||
/** Provides classes for modeling HTTP-related APIs. */
|
||||
module Http {
|
||||
/** Provides classes for modeling HTTP clients. */
|
||||
module Client {
|
||||
/**
|
||||
* A data-flow node that makes an outgoing HTTP request.
|
||||
*
|
||||
* Extend this class to refine existing API models. If you want to model new APIs,
|
||||
* extend `Http::Client::Request::Range` instead.
|
||||
*/
|
||||
class Request extends DataFlow::Node instanceof Request::Range {
|
||||
/**
|
||||
* Gets a data-flow node that contributes to the URL of the request.
|
||||
* Depending on the framework, a request may have multiple nodes which contribute to the URL.
|
||||
*/
|
||||
DataFlow::Node getAUrlPart() { result = super.getAUrlPart() }
|
||||
|
||||
/** Gets a string that identifies the framework used for this request. */
|
||||
string getFramework() { result = super.getFramework() }
|
||||
|
||||
/**
|
||||
* Holds if this request is made using a mode that disables SSL/TLS
|
||||
* certificate validation, where `disablingNode` represents the point at
|
||||
* which the validation was disabled, and `argumentOrigin` represents the origin
|
||||
* of the argument that disabled the validation (which could be the same node as
|
||||
* `disablingNode`).
|
||||
*/
|
||||
predicate disablesCertificateValidation(
|
||||
DataFlow::Node disablingNode, DataFlow::Node argumentOrigin
|
||||
) {
|
||||
super.disablesCertificateValidation(disablingNode, argumentOrigin)
|
||||
}
|
||||
}
|
||||
|
||||
/** Provides a class for modeling new HTTP requests. */
|
||||
module Request {
|
||||
/**
|
||||
* A data-flow node that makes an outgoing HTTP request.
|
||||
*
|
||||
* Extend this class to model new APIs. If you want to refine existing API models,
|
||||
* extend `Http::Client::Request` instead.
|
||||
*/
|
||||
abstract class Range extends DataFlow::Node {
|
||||
/**
|
||||
* Gets a data-flow node that contributes to the URL of the request.
|
||||
* Depending on the framework, a request may have multiple nodes which contribute to the URL.
|
||||
*/
|
||||
abstract DataFlow::Node getAUrlPart();
|
||||
|
||||
/** Gets a string that identifies the framework used for this request. */
|
||||
abstract string getFramework();
|
||||
|
||||
/**
|
||||
* Holds if this request is made using a mode that disables SSL/TLS
|
||||
* certificate validation, where `disablingNode` represents the point at
|
||||
* which the validation was disabled, and `argumentOrigin` represents the origin
|
||||
* of the argument that disabled the validation (which could be the same node as
|
||||
* `disablingNode`).
|
||||
*/
|
||||
abstract predicate disablesCertificateValidation(
|
||||
DataFlow::Node disablingNode, DataFlow::Node argumentOrigin
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -334,7 +334,7 @@ predicate call3(
|
||||
}
|
||||
|
||||
bindingset[self, function]
|
||||
predicate method_binding(
|
||||
deprecated predicate method_binding(
|
||||
AttrNode instantiation, ObjectInternal self, CallableObjectInternal function,
|
||||
PointsToContext context
|
||||
) {
|
||||
@@ -357,7 +357,9 @@ predicate method_binding(
|
||||
|
||||
/** Helper for method_binding */
|
||||
pragma[noinline]
|
||||
predicate receiver(AttrNode instantiation, PointsToContext context, ObjectInternal obj, string name) {
|
||||
deprecated predicate receiver(
|
||||
AttrNode instantiation, PointsToContext context, ObjectInternal obj, string name
|
||||
) {
|
||||
PointsToInternal::pointsTo(instantiation.getObject(name), context, obj, _)
|
||||
}
|
||||
|
||||
|
||||
@@ -13,7 +13,7 @@ import semmle.python.essa.SsaDefinitions
|
||||
private import semmle.python.types.Builtins
|
||||
private import semmle.python.internal.CachedStages
|
||||
|
||||
module BasePointsTo {
|
||||
deprecated module BasePointsTo {
|
||||
/** INTERNAL -- Use n.refersTo(value, _, origin) instead */
|
||||
pragma[noinline]
|
||||
predicate points_to(ControlFlowNode f, Object value, ControlFlowNode origin) {
|
||||
@@ -27,13 +27,13 @@ module BasePointsTo {
|
||||
}
|
||||
|
||||
/** Gets the kwargs parameter (`**kwargs`). In a function definition this is always a dict. */
|
||||
predicate kwargs_points_to(ControlFlowNode f, ClassObject cls) {
|
||||
deprecated predicate kwargs_points_to(ControlFlowNode f, ClassObject cls) {
|
||||
exists(Function func | func.getKwarg() = f.getNode()) and
|
||||
cls = theDictType()
|
||||
}
|
||||
|
||||
/** Gets the varargs parameter (`*varargs`). In a function definition this is always a tuple. */
|
||||
predicate varargs_points_to(ControlFlowNode f, ClassObject cls) {
|
||||
deprecated predicate varargs_points_to(ControlFlowNode f, ClassObject cls) {
|
||||
exists(Function func | func.getVararg() = f.getNode()) and
|
||||
cls = theTupleType()
|
||||
}
|
||||
@@ -45,7 +45,7 @@ predicate varargs_points_to(ControlFlowNode f, ClassObject cls) {
|
||||
* This exists primarily for internal use. Use getAnInferredType() instead.
|
||||
*/
|
||||
pragma[noinline]
|
||||
ClassObject simple_types(Object obj) {
|
||||
deprecated ClassObject simple_types(Object obj) {
|
||||
result = comprehension(obj.getOrigin())
|
||||
or
|
||||
result = collection_literal(obj.getOrigin())
|
||||
@@ -59,7 +59,7 @@ ClassObject simple_types(Object obj) {
|
||||
obj = unknownValue() and result = theUnknownType()
|
||||
}
|
||||
|
||||
private ClassObject comprehension(Expr e) {
|
||||
deprecated private ClassObject comprehension(Expr e) {
|
||||
e instanceof ListComp and result = theListType()
|
||||
or
|
||||
e instanceof SetComp and result = theSetType()
|
||||
@@ -69,7 +69,7 @@ private ClassObject comprehension(Expr e) {
|
||||
e instanceof GeneratorExp and result = theGeneratorType()
|
||||
}
|
||||
|
||||
private ClassObject collection_literal(Expr e) {
|
||||
deprecated private ClassObject collection_literal(Expr e) {
|
||||
e instanceof List and result = theListType()
|
||||
or
|
||||
e instanceof Set and result = theSetType()
|
||||
@@ -79,7 +79,7 @@ private ClassObject collection_literal(Expr e) {
|
||||
e instanceof Tuple and result = theTupleType()
|
||||
}
|
||||
|
||||
private int tuple_index_value(Object t, int i) {
|
||||
deprecated private int tuple_index_value(Object t, int i) {
|
||||
result = t.(TupleNode).getElement(i).getNode().(Num).getN().toInt()
|
||||
or
|
||||
exists(Object item |
|
||||
@@ -89,7 +89,7 @@ private int tuple_index_value(Object t, int i) {
|
||||
}
|
||||
|
||||
pragma[noinline]
|
||||
int version_tuple_value(Object t) {
|
||||
deprecated int version_tuple_value(Object t) {
|
||||
not exists(tuple_index_value(t, 1)) and result = tuple_index_value(t, 0) * 10
|
||||
or
|
||||
not exists(tuple_index_value(t, 2)) and
|
||||
@@ -102,7 +102,7 @@ int version_tuple_value(Object t) {
|
||||
}
|
||||
|
||||
/** Choose a version numbers that represent the extreme of supported versions. */
|
||||
private int major_minor() {
|
||||
deprecated private int major_minor() {
|
||||
if major_version() = 3
|
||||
then (
|
||||
result = 33 or result = 37
|
||||
@@ -113,7 +113,7 @@ private int major_minor() {
|
||||
}
|
||||
|
||||
/** Compares the given tuple object to both the maximum and minimum possible sys.version_info values */
|
||||
int version_tuple_compare(Object t) {
|
||||
deprecated int version_tuple_compare(Object t) {
|
||||
version_tuple_value(t) < major_minor() and result = -1
|
||||
or
|
||||
version_tuple_value(t) = major_minor() and result = 0
|
||||
@@ -122,7 +122,7 @@ int version_tuple_compare(Object t) {
|
||||
}
|
||||
|
||||
/** Holds if `cls` is a new-style class if it were to have no explicit base classes */
|
||||
predicate baseless_is_new_style(ClassObject cls) {
|
||||
deprecated predicate baseless_is_new_style(ClassObject cls) {
|
||||
cls.isBuiltin()
|
||||
or
|
||||
major_version() = 3 and exists(cls)
|
||||
@@ -160,7 +160,7 @@ private predicate class_defines_name(Class cls, string name) {
|
||||
}
|
||||
|
||||
/** Gets a return value CFG node, provided that is safe to track across returns */
|
||||
ControlFlowNode safe_return_node(PyFunctionObject func) {
|
||||
deprecated ControlFlowNode safe_return_node(PyFunctionObject func) {
|
||||
result = func.getAReturnedNode() and
|
||||
// Not a parameter
|
||||
not exists(Parameter p, SsaVariable pvar |
|
||||
@@ -172,7 +172,7 @@ ControlFlowNode safe_return_node(PyFunctionObject func) {
|
||||
}
|
||||
|
||||
/** Holds if it can be determined from the control flow graph alone that this function can never return */
|
||||
predicate function_can_never_return(FunctionObject func) {
|
||||
deprecated predicate function_can_never_return(FunctionObject func) {
|
||||
/*
|
||||
* A Python function never returns if it has no normal exits that are not dominated by a
|
||||
* call to a function which itself never returns.
|
||||
@@ -188,7 +188,9 @@ predicate function_can_never_return(FunctionObject func) {
|
||||
|
||||
/** Hold if outer contains inner, both are contained within a test and inner is a use is a plain use or an attribute lookup */
|
||||
pragma[noinline]
|
||||
predicate contains_interesting_expression_within_test(ControlFlowNode outer, ControlFlowNode inner) {
|
||||
deprecated predicate contains_interesting_expression_within_test(
|
||||
ControlFlowNode outer, ControlFlowNode inner
|
||||
) {
|
||||
inner.isLoad() and
|
||||
exists(ControlFlowNode test |
|
||||
outer.getAChild*() = inner and
|
||||
@@ -208,7 +210,7 @@ predicate test_contains(ControlFlowNode expr, ControlFlowNode use) {
|
||||
}
|
||||
|
||||
/** Holds if `test` is a test (a branch), `use` is within that test and `def` is an edge from that test with `sense` */
|
||||
predicate refinement_test(
|
||||
deprecated predicate refinement_test(
|
||||
ControlFlowNode test, ControlFlowNode use, boolean sense, PyEdgeRefinement def
|
||||
) {
|
||||
/*
|
||||
@@ -224,7 +226,7 @@ predicate refinement_test(
|
||||
|
||||
/** Holds if `f` is an import of the form `from .[...] import name` and the enclosing scope is an __init__ module */
|
||||
pragma[noinline]
|
||||
predicate live_import_from_dot_in_init(ImportMemberNode f, EssaVariable var) {
|
||||
deprecated predicate live_import_from_dot_in_init(ImportMemberNode f, EssaVariable var) {
|
||||
exists(string name |
|
||||
import_from_dot_in_init(f.getModule(name)) and
|
||||
var.getSourceVariable().getName() = name and
|
||||
@@ -249,13 +251,13 @@ Object undefinedVariable() { py_special_objects(result, "_semmle_undefined_value
|
||||
/** Gets the pseudo-object representing an unknown value */
|
||||
Object unknownValue() { result.asBuiltin() = Builtin::unknown() }
|
||||
|
||||
BuiltinCallable theTypeNewMethod() {
|
||||
deprecated BuiltinCallable theTypeNewMethod() {
|
||||
result.asBuiltin() = theTypeType().asBuiltin().getMember("__new__")
|
||||
}
|
||||
|
||||
/** Gets the `value, cls, origin` that `f` would refer to if it has not been assigned some other value */
|
||||
pragma[noinline]
|
||||
predicate potential_builtin_points_to(
|
||||
deprecated predicate potential_builtin_points_to(
|
||||
NameNode f, Object value, ClassObject cls, ControlFlowNode origin
|
||||
) {
|
||||
f.isGlobal() and
|
||||
@@ -269,7 +271,7 @@ predicate potential_builtin_points_to(
|
||||
}
|
||||
|
||||
pragma[noinline]
|
||||
predicate builtin_name_points_to(string name, Object value, ClassObject cls) {
|
||||
deprecated predicate builtin_name_points_to(string name, Object value, ClassObject cls) {
|
||||
value = Object::builtin(name) and cls.asBuiltin() = value.asBuiltin().getClass()
|
||||
}
|
||||
|
||||
@@ -331,7 +333,9 @@ module BaseFlow {
|
||||
}
|
||||
|
||||
/** Points-to for syntactic elements where context is not relevant */
|
||||
predicate simple_points_to(ControlFlowNode f, Object value, ClassObject cls, ControlFlowNode origin) {
|
||||
deprecated predicate simple_points_to(
|
||||
ControlFlowNode f, Object value, ClassObject cls, ControlFlowNode origin
|
||||
) {
|
||||
kwargs_points_to(f, cls) and value = f and origin = f
|
||||
or
|
||||
varargs_points_to(f, cls) and value = f and origin = f
|
||||
@@ -347,7 +351,9 @@ predicate simple_points_to(ControlFlowNode f, Object value, ClassObject cls, Con
|
||||
* Holds if `bit` is a binary expression node with a bitwise operator.
|
||||
* Helper for `this_binary_expr_points_to`.
|
||||
*/
|
||||
predicate bitwise_expression_node(BinaryExprNode bit, ControlFlowNode left, ControlFlowNode right) {
|
||||
deprecated predicate bitwise_expression_node(
|
||||
BinaryExprNode bit, ControlFlowNode left, ControlFlowNode right
|
||||
) {
|
||||
exists(Operator op | op = bit.getNode().getOp() |
|
||||
op instanceof BitAnd or
|
||||
op instanceof BitOr or
|
||||
@@ -357,13 +363,13 @@ predicate bitwise_expression_node(BinaryExprNode bit, ControlFlowNode left, Cont
|
||||
right = bit.getRight()
|
||||
}
|
||||
|
||||
private Module theCollectionsAbcModule() {
|
||||
deprecated private Module theCollectionsAbcModule() {
|
||||
result.getName() = "_abcoll"
|
||||
or
|
||||
result.getName() = "_collections_abc"
|
||||
}
|
||||
|
||||
ClassObject collectionsAbcClass(string name) {
|
||||
deprecated ClassObject collectionsAbcClass(string name) {
|
||||
exists(Class cls |
|
||||
result.getPyClass() = cls and
|
||||
cls.getName() = name and
|
||||
|
||||
@@ -13,7 +13,7 @@ predicate hasattr(CallNode c, ControlFlowNode obj, string attr) {
|
||||
}
|
||||
|
||||
/** Holds if `c` is a call to `callable(obj)`. */
|
||||
predicate is_callable(CallNode c, ControlFlowNode obj) {
|
||||
deprecated predicate is_callable(CallNode c, ControlFlowNode obj) {
|
||||
c.getFunction().(NameNode).getId() = "callable" and
|
||||
obj = c.getArg(0)
|
||||
}
|
||||
@@ -26,7 +26,7 @@ predicate isinstance(CallNode fc, ControlFlowNode cls, ControlFlowNode use) {
|
||||
}
|
||||
|
||||
/** Holds if `c` is a call to `issubclass(use, cls)`. */
|
||||
predicate issubclass(CallNode fc, ControlFlowNode cls, ControlFlowNode use) {
|
||||
deprecated predicate issubclass(CallNode fc, ControlFlowNode cls, ControlFlowNode use) {
|
||||
fc.getFunction().(NameNode).getId() = "issubclass" and
|
||||
fc.getArg(0) = use and
|
||||
cls = fc.getArg(1)
|
||||
|
||||
@@ -122,7 +122,7 @@ private newtype TPointsToContext =
|
||||
} or
|
||||
TObjectContext(SelfInstanceInternal object)
|
||||
|
||||
module Context {
|
||||
deprecated module Context {
|
||||
PointsToContext forObject(ObjectInternal object) { result = TObjectContext(object) }
|
||||
}
|
||||
|
||||
|
||||
@@ -1,19 +1,19 @@
|
||||
import python
|
||||
|
||||
/** Retained for backwards compatibility use ClassObject.isIterator() instead. */
|
||||
predicate is_iterator(ClassObject c) { c.isIterator() }
|
||||
deprecated predicate is_iterator(ClassObject c) { c.isIterator() }
|
||||
|
||||
/** Retained for backwards compatibility use ClassObject.isIterable() instead. */
|
||||
predicate is_iterable(ClassObject c) { c.isIterable() }
|
||||
deprecated predicate is_iterable(ClassObject c) { c.isIterable() }
|
||||
|
||||
/** Retained for backwards compatibility use ClassObject.isCollection() instead. */
|
||||
predicate is_collection(ClassObject c) { c.isCollection() }
|
||||
deprecated predicate is_collection(ClassObject c) { c.isCollection() }
|
||||
|
||||
/** Retained for backwards compatibility use ClassObject.isMapping() instead. */
|
||||
predicate is_mapping(ClassObject c) { c.isMapping() }
|
||||
deprecated predicate is_mapping(ClassObject c) { c.isMapping() }
|
||||
|
||||
/** Retained for backwards compatibility use ClassObject.isSequence() instead. */
|
||||
predicate is_sequence(ClassObject c) { c.isSequence() }
|
||||
deprecated predicate is_sequence(ClassObject c) { c.isSequence() }
|
||||
|
||||
/** Retained for backwards compatibility use ClassObject.isContextManager() instead. */
|
||||
predicate is_context_manager(ClassObject c) { c.isContextManager() }
|
||||
deprecated predicate is_context_manager(ClassObject c) { c.isContextManager() }
|
||||
|
||||
@@ -96,7 +96,10 @@ class OverlyWideRange extends RegExpCharacterRange {
|
||||
toCodePoint("A") <= high
|
||||
or
|
||||
// a non-alphanumeric char as part of the range boundaries
|
||||
exists(int bound | bound = [low, high] | not isAlphanumeric(bound.toUnicode()))
|
||||
exists(int bound | bound = [low, high] | not isAlphanumeric(bound.toUnicode())) and
|
||||
// while still being ascii
|
||||
low < 128 and
|
||||
high < 128
|
||||
) and
|
||||
// allowlist for known ranges
|
||||
not this = allowedWideRanges()
|
||||
@@ -173,7 +176,7 @@ module RangePrinter {
|
||||
}
|
||||
|
||||
/** Gets the number of parts we should print for a given `range`. */
|
||||
private int parts(OverlyWideRange range) { result = 1 + strictcount(cutoff(range, _)) }
|
||||
private int parts(OverlyWideRange range) { result = 1 + count(cutoff(range, _)) }
|
||||
|
||||
/** Holds if the given part of a range should span from `low` to `high`. */
|
||||
private predicate part(OverlyWideRange range, int part, string low, string high) {
|
||||
|
||||
@@ -56,7 +56,7 @@ module CleartextStorage {
|
||||
/** The data written to a cookie on a HTTP response, considered as a flow sink. */
|
||||
class CookieWriteAsSink extends Sink {
|
||||
CookieWriteAsSink() {
|
||||
exists(HTTP::Server::CookieWrite write |
|
||||
exists(Http::Server::CookieWrite write |
|
||||
this = write.getValueArg()
|
||||
or
|
||||
this = write.getHeaderArg()
|
||||
|
||||
@@ -64,14 +64,14 @@ module LdapInjection {
|
||||
* A logging operation, considered as a flow sink.
|
||||
*/
|
||||
class LdapExecutionAsDnSink extends DnSink {
|
||||
LdapExecutionAsDnSink() { this = any(LDAP::LdapExecution ldap).getBaseDn() }
|
||||
LdapExecutionAsDnSink() { this = any(Ldap::LdapExecution ldap).getBaseDn() }
|
||||
}
|
||||
|
||||
/**
|
||||
* A logging operation, considered as a flow sink.
|
||||
*/
|
||||
class LdapExecutionAsFilterSink extends FilterSink {
|
||||
LdapExecutionAsFilterSink() { this = any(LDAP::LdapExecution ldap).getFilter() }
|
||||
LdapExecutionAsFilterSink() { this = any(Ldap::LdapExecution ldap).getFilter() }
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -48,7 +48,7 @@ module ReflectedXss {
|
||||
*/
|
||||
class ServerHttpResponseBodyAsSink extends Sink {
|
||||
ServerHttpResponseBodyAsSink() {
|
||||
exists(HTTP::Server::HttpResponse response |
|
||||
exists(Http::Server::HttpResponse response |
|
||||
response.getMimetype().toLowerCase() = "text/html" and
|
||||
this = response.getBody()
|
||||
)
|
||||
|
||||
@@ -28,7 +28,7 @@ module ServerSideRequestForgery {
|
||||
/**
|
||||
* Gets the request this sink belongs to.
|
||||
*/
|
||||
abstract HTTP::Client::Request getRequest();
|
||||
abstract Http::Client::Request getRequest();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -57,7 +57,7 @@ module ServerSideRequestForgery {
|
||||
|
||||
/** The URL of an HTTP request, considered as a sink. */
|
||||
class HttpRequestUrlAsSink extends Sink {
|
||||
HTTP::Client::Request req;
|
||||
Http::Client::Request req;
|
||||
|
||||
HttpRequestUrlAsSink() {
|
||||
req.getAUrlPart() = this and
|
||||
@@ -74,7 +74,7 @@ module ServerSideRequestForgery {
|
||||
not req.getScope().getEnclosingModule().getName() in ["http.client", "httplib"]
|
||||
}
|
||||
|
||||
override HTTP::Client::Request getRequest() { result = req }
|
||||
override Http::Client::Request getRequest() { result = req }
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -42,7 +42,7 @@ class FullServerSideRequestForgeryConfiguration extends TaintTracking::Configura
|
||||
/**
|
||||
* Holds if all URL parts of `request` is fully user controlled.
|
||||
*/
|
||||
predicate fullyControlledRequest(HTTP::Client::Request request) {
|
||||
predicate fullyControlledRequest(Http::Client::Request request) {
|
||||
exists(FullServerSideRequestForgeryConfiguration fullConfig |
|
||||
forall(DataFlow::Node urlPart | urlPart = request.getAUrlPart() |
|
||||
fullConfig.hasFlow(_, urlPart)
|
||||
|
||||
@@ -49,6 +49,6 @@ module StackTraceExposure {
|
||||
* The body of a HTTP response that will be returned from a server, considered as a flow sink.
|
||||
*/
|
||||
class ServerHttpResponseBodyAsSink extends Sink {
|
||||
ServerHttpResponseBodyAsSink() { this = any(HTTP::Server::HttpResponse response).getBody() }
|
||||
ServerHttpResponseBodyAsSink() { this = any(Http::Server::HttpResponse response).getBody() }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -48,7 +48,7 @@ module UrlRedirect {
|
||||
*/
|
||||
class RedirectLocationAsSink extends Sink {
|
||||
RedirectLocationAsSink() {
|
||||
this = any(HTTP::Server::HttpRedirectResponse e).getRedirectLocation()
|
||||
this = any(Http::Server::HttpRedirectResponse e).getRedirectLocation()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -115,6 +115,7 @@ private newtype TStatePair =
|
||||
private int rankState(State state) {
|
||||
state =
|
||||
rank[result](State s, Location l |
|
||||
stateInsideBacktracking(s) and
|
||||
l = s.getRepr().getLocation()
|
||||
|
|
||||
s order by l.getStartLine(), l.getStartColumn(), s.toString()
|
||||
|
||||
@@ -93,8 +93,6 @@ class RegExpRoot extends RegExpTerm {
|
||||
* Holds if this root term is relevant to the ReDoS analysis.
|
||||
*/
|
||||
predicate isRelevant() {
|
||||
// there is at least one repetition
|
||||
getRoot(any(InfiniteRepetitionQuantifier q)) = this and
|
||||
// is actually used as a RegExp
|
||||
this.isUsedAsRegExp() and
|
||||
// not excluded for library specific reasons
|
||||
@@ -877,6 +875,107 @@ predicate isStartState(State state) {
|
||||
*/
|
||||
signature predicate isCandidateSig(State state, string pump);
|
||||
|
||||
/**
|
||||
* Holds if `state` is a candidate for ReDoS.
|
||||
*/
|
||||
signature predicate isCandidateSig(State state);
|
||||
|
||||
/**
|
||||
* Predicates for constructing a prefix string that leads to a given state.
|
||||
*/
|
||||
module PrefixConstruction<isCandidateSig/1 isCandidate> {
|
||||
/**
|
||||
* Holds if `state` is the textually last start state for the regular expression.
|
||||
*/
|
||||
private predicate lastStartState(RelevantState state) {
|
||||
exists(RegExpRoot root |
|
||||
state =
|
||||
max(RelevantState s, Location l |
|
||||
isStartState(s) and
|
||||
getRoot(s.getRepr()) = root and
|
||||
l = s.getRepr().getLocation()
|
||||
|
|
||||
s
|
||||
order by
|
||||
l.getStartLine(), l.getStartColumn(), s.getRepr().toString(), l.getEndColumn(),
|
||||
l.getEndLine()
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if there exists any transition (Epsilon() or other) from `a` to `b`.
|
||||
*/
|
||||
private predicate existsTransition(State a, State b) { delta(a, _, b) }
|
||||
|
||||
/**
|
||||
* Gets the minimum number of transitions it takes to reach `state` from the `start` state.
|
||||
*/
|
||||
int prefixLength(State start, State state) =
|
||||
shortestDistances(lastStartState/1, existsTransition/2)(start, state, result)
|
||||
|
||||
/**
|
||||
* Gets the minimum number of transitions it takes to reach `state` from the start state.
|
||||
*/
|
||||
private int lengthFromStart(State state) { result = prefixLength(_, state) }
|
||||
|
||||
/**
|
||||
* Gets a string for which the regular expression will reach `state`.
|
||||
*
|
||||
* Has at most one result for any given `state`.
|
||||
* This predicate will not always have a result even if there is a ReDoS issue in
|
||||
* the regular expression.
|
||||
*/
|
||||
string prefix(State state) {
|
||||
lastStartState(state) and
|
||||
result = ""
|
||||
or
|
||||
// the search stops past the last redos candidate state.
|
||||
lengthFromStart(state) <= max(lengthFromStart(any(State s | isCandidate(s)))) and
|
||||
exists(State prev |
|
||||
// select a unique predecessor (by an arbitrary measure)
|
||||
prev =
|
||||
min(State s, Location loc |
|
||||
lengthFromStart(s) = lengthFromStart(state) - 1 and
|
||||
loc = s.getRepr().getLocation() and
|
||||
delta(s, _, state)
|
||||
|
|
||||
s
|
||||
order by
|
||||
loc.getStartLine(), loc.getStartColumn(), loc.getEndLine(), loc.getEndColumn(),
|
||||
s.getRepr().toString()
|
||||
)
|
||||
|
|
||||
// greedy search for the shortest prefix
|
||||
result = prefix(prev) and delta(prev, Epsilon(), state)
|
||||
or
|
||||
not delta(prev, Epsilon(), state) and
|
||||
result = prefix(prev) + getCanonicalEdgeChar(prev, state)
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a canonical char for which there exists a transition from `prev` to `next` in the NFA.
|
||||
*/
|
||||
private string getCanonicalEdgeChar(State prev, State next) {
|
||||
result =
|
||||
min(string c | delta(prev, any(InputSymbol symbol | c = intersect(Any(), symbol)), next))
|
||||
}
|
||||
|
||||
/** A state within a regular expression that contains a candidate state. */
|
||||
class RelevantState instanceof State {
|
||||
RelevantState() {
|
||||
exists(State s | isCandidate(s) | getRoot(s.getRepr()) = getRoot(this.getRepr()))
|
||||
}
|
||||
|
||||
/** Gets a string representation for this state in a regular expression. */
|
||||
string toString() { result = State.super.toString() }
|
||||
|
||||
/** Gets the term represented by this state. */
|
||||
RegExpTerm getRepr() { result = State.super.getRepr() }
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A module for pruning candidate ReDoS states.
|
||||
* The candidates are specified by the `isCandidate` signature predicate.
|
||||
@@ -910,95 +1009,11 @@ module ReDoSPruning<isCandidateSig/2 isCandidate> {
|
||||
/** Gets a state that can reach the `accept-any` state using only epsilon steps. */
|
||||
private State acceptsAnySuffix() { epsilonSucc*(result) = AcceptAnySuffix(_) }
|
||||
|
||||
/**
|
||||
* Predicates for constructing a prefix string that leads to a given state.
|
||||
*/
|
||||
private module PrefixConstruction {
|
||||
/**
|
||||
* Holds if `state` is the textually last start state for the regular expression.
|
||||
*/
|
||||
private predicate lastStartState(State state) {
|
||||
exists(RegExpRoot root |
|
||||
state =
|
||||
max(State s, Location l |
|
||||
s = stateInPumpableRegexp() and
|
||||
isStartState(s) and
|
||||
getRoot(s.getRepr()) = root and
|
||||
l = s.getRepr().getLocation()
|
||||
|
|
||||
s
|
||||
order by
|
||||
l.getStartLine(), l.getStartColumn(), s.getRepr().toString(), l.getEndColumn(),
|
||||
l.getEndLine()
|
||||
)
|
||||
)
|
||||
}
|
||||
predicate isCandidateState(State s) { isReDoSCandidate(s, _) }
|
||||
|
||||
/**
|
||||
* Holds if there exists any transition (Epsilon() or other) from `a` to `b`.
|
||||
*/
|
||||
private predicate existsTransition(State a, State b) { delta(a, _, b) }
|
||||
import PrefixConstruction<isCandidateState/1> as Prefix
|
||||
|
||||
/**
|
||||
* Gets the minimum number of transitions it takes to reach `state` from the `start` state.
|
||||
*/
|
||||
int prefixLength(State start, State state) =
|
||||
shortestDistances(lastStartState/1, existsTransition/2)(start, state, result)
|
||||
|
||||
/**
|
||||
* Gets the minimum number of transitions it takes to reach `state` from the start state.
|
||||
*/
|
||||
private int lengthFromStart(State state) { result = prefixLength(_, state) }
|
||||
|
||||
/**
|
||||
* Gets a string for which the regular expression will reach `state`.
|
||||
*
|
||||
* Has at most one result for any given `state`.
|
||||
* This predicate will not always have a result even if there is a ReDoS issue in
|
||||
* the regular expression.
|
||||
*/
|
||||
string prefix(State state) {
|
||||
lastStartState(state) and
|
||||
result = ""
|
||||
or
|
||||
// the search stops past the last redos candidate state.
|
||||
lengthFromStart(state) <= max(lengthFromStart(any(State s | isReDoSCandidate(s, _)))) and
|
||||
exists(State prev |
|
||||
// select a unique predecessor (by an arbitrary measure)
|
||||
prev =
|
||||
min(State s, Location loc |
|
||||
lengthFromStart(s) = lengthFromStart(state) - 1 and
|
||||
loc = s.getRepr().getLocation() and
|
||||
delta(s, _, state)
|
||||
|
|
||||
s
|
||||
order by
|
||||
loc.getStartLine(), loc.getStartColumn(), loc.getEndLine(), loc.getEndColumn(),
|
||||
s.getRepr().toString()
|
||||
)
|
||||
|
|
||||
// greedy search for the shortest prefix
|
||||
result = prefix(prev) and delta(prev, Epsilon(), state)
|
||||
or
|
||||
not delta(prev, Epsilon(), state) and
|
||||
result = prefix(prev) + getCanonicalEdgeChar(prev, state)
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a canonical char for which there exists a transition from `prev` to `next` in the NFA.
|
||||
*/
|
||||
private string getCanonicalEdgeChar(State prev, State next) {
|
||||
result =
|
||||
min(string c | delta(prev, any(InputSymbol symbol | c = intersect(Any(), symbol)), next))
|
||||
}
|
||||
|
||||
/** Gets a state within a regular expression that has a pumpable state. */
|
||||
pragma[noinline]
|
||||
State stateInPumpableRegexp() {
|
||||
exists(State s | isReDoSCandidate(s, _) | getRoot(s.getRepr()) = getRoot(result.getRepr()))
|
||||
}
|
||||
}
|
||||
class RelevantState = Prefix::RelevantState;
|
||||
|
||||
/**
|
||||
* Predicates for testing the presence of a rejecting suffix.
|
||||
@@ -1018,8 +1033,6 @@ module ReDoSPruning<isCandidateSig/2 isCandidate> {
|
||||
* using epsilon transitions. But any attempt at repeating `w` will end in a state that accepts all suffixes.
|
||||
*/
|
||||
private module SuffixConstruction {
|
||||
import PrefixConstruction
|
||||
|
||||
/**
|
||||
* Holds if all states reachable from `fork` by repeating `w`
|
||||
* are likely rejectable by appending some suffix.
|
||||
@@ -1035,32 +1048,26 @@ module ReDoSPruning<isCandidateSig/2 isCandidate> {
|
||||
* This predicate might find impossible suffixes when searching for suffixes of length > 1, which can cause FPs.
|
||||
*/
|
||||
pragma[noinline]
|
||||
private predicate isLikelyRejectable(State s) {
|
||||
s = stateInPumpableRegexp() and
|
||||
(
|
||||
// exists a reject edge with some char.
|
||||
hasRejectEdge(s)
|
||||
or
|
||||
hasEdgeToLikelyRejectable(s)
|
||||
or
|
||||
// stopping here is rejection
|
||||
isRejectState(s)
|
||||
)
|
||||
private predicate isLikelyRejectable(RelevantState s) {
|
||||
// exists a reject edge with some char.
|
||||
hasRejectEdge(s)
|
||||
or
|
||||
hasEdgeToLikelyRejectable(s)
|
||||
or
|
||||
// stopping here is rejection
|
||||
isRejectState(s)
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if `s` is not an accept state, and there is no epsilon transition to an accept state.
|
||||
*/
|
||||
predicate isRejectState(State s) {
|
||||
s = stateInPumpableRegexp() and not epsilonSucc*(s) = Accept(_)
|
||||
}
|
||||
predicate isRejectState(RelevantState s) { not epsilonSucc*(s) = Accept(_) }
|
||||
|
||||
/**
|
||||
* Holds if there is likely a non-empty suffix leading to rejection starting in `s`.
|
||||
*/
|
||||
pragma[noopt]
|
||||
predicate hasEdgeToLikelyRejectable(State s) {
|
||||
s = stateInPumpableRegexp() and
|
||||
predicate hasEdgeToLikelyRejectable(RelevantState s) {
|
||||
// all edges (at least one) with some char leads to another state that is rejectable.
|
||||
// the `next` states might not share a common suffix, which can cause FPs.
|
||||
exists(string char | char = hasEdgeToLikelyRejectableHelper(s) |
|
||||
@@ -1075,8 +1082,7 @@ module ReDoSPruning<isCandidateSig/2 isCandidate> {
|
||||
* and `s` has not been found to be rejectable by `hasRejectEdge` or `isRejectState`.
|
||||
*/
|
||||
pragma[noinline]
|
||||
private string hasEdgeToLikelyRejectableHelper(State s) {
|
||||
s = stateInPumpableRegexp() and
|
||||
private string hasEdgeToLikelyRejectableHelper(RelevantState s) {
|
||||
not hasRejectEdge(s) and
|
||||
not isRejectState(s) and
|
||||
deltaClosedChar(s, result, _)
|
||||
@@ -1087,9 +1093,7 @@ module ReDoSPruning<isCandidateSig/2 isCandidate> {
|
||||
* along epsilon edges, such that there is a transition from
|
||||
* `prev` to `next` that the character symbol `char`.
|
||||
*/
|
||||
predicate deltaClosedChar(State prev, string char, State next) {
|
||||
prev = stateInPumpableRegexp() and
|
||||
next = stateInPumpableRegexp() and
|
||||
predicate deltaClosedChar(RelevantState prev, string char, RelevantState next) {
|
||||
deltaClosed(prev, getAnInputSymbolMatchingRelevant(char), next)
|
||||
}
|
||||
|
||||
@@ -1099,18 +1103,28 @@ module ReDoSPruning<isCandidateSig/2 isCandidate> {
|
||||
result = getAnInputSymbolMatching(char)
|
||||
}
|
||||
|
||||
pragma[noinline]
|
||||
RegExpRoot relevantRoot() {
|
||||
exists(RegExpTerm term, State s |
|
||||
s.getRepr() = term and isCandidateState(s) and result = term.getRootTerm()
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a char used for finding possible suffixes inside `root`.
|
||||
*/
|
||||
pragma[noinline]
|
||||
private string relevant(RegExpRoot root) {
|
||||
exists(ascii(result)) and exists(root)
|
||||
or
|
||||
exists(InputSymbol s | belongsTo(s, root) | result = intersect(s, _))
|
||||
or
|
||||
// The characters from `hasSimpleRejectEdge`. Only `\n` is really needed (as `\n` is not in the `ascii` relation).
|
||||
// The three chars must be kept in sync with `hasSimpleRejectEdge`.
|
||||
result = ["|", "\n", "Z"] and exists(root)
|
||||
root = relevantRoot() and
|
||||
(
|
||||
exists(ascii(result)) and exists(root)
|
||||
or
|
||||
exists(InputSymbol s | belongsTo(s, root) | result = intersect(s, _))
|
||||
or
|
||||
// The characters from `hasSimpleRejectEdge`. Only `\n` is really needed (as `\n` is not in the `ascii` relation).
|
||||
// The three chars must be kept in sync with `hasSimpleRejectEdge`.
|
||||
result = ["|", "\n", "Z"] and exists(root)
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1208,12 +1222,12 @@ module ReDoSPruning<isCandidateSig/2 isCandidate> {
|
||||
predicate hasReDoSResult(RegExpTerm t, string pump, State s, string prefixMsg) {
|
||||
isReDoSAttackable(t, pump, s) and
|
||||
(
|
||||
prefixMsg = "starting with '" + escape(PrefixConstruction::prefix(s)) + "' and " and
|
||||
not PrefixConstruction::prefix(s) = ""
|
||||
prefixMsg = "starting with '" + escape(Prefix::prefix(s)) + "' and " and
|
||||
not Prefix::prefix(s) = ""
|
||||
or
|
||||
PrefixConstruction::prefix(s) = "" and prefixMsg = ""
|
||||
Prefix::prefix(s) = "" and prefixMsg = ""
|
||||
or
|
||||
not exists(PrefixConstruction::prefix(s)) and prefixMsg = ""
|
||||
not exists(Prefix::prefix(s)) and prefixMsg = ""
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -13,11 +13,12 @@
|
||||
*/
|
||||
|
||||
import python
|
||||
import semmle.python.ApiGraphs
|
||||
|
||||
predicate doesnt_reraise(ExceptStmt ex) { ex.getAFlowNode().getBasicBlock().reachesExit() }
|
||||
|
||||
predicate catches_base_exception(ExceptStmt ex) {
|
||||
ex.getType().pointsTo(ClassValue::baseException())
|
||||
ex.getType() = API::builtin("BaseException").getAValueReachableFromSource().asExpr()
|
||||
or
|
||||
not exists(ex.getType())
|
||||
}
|
||||
|
||||
@@ -12,6 +12,7 @@
|
||||
*/
|
||||
|
||||
import python
|
||||
import semmle.python.ApiGraphs
|
||||
|
||||
predicate empty_except(ExceptStmt ex) {
|
||||
not exists(Stmt s | s = ex.getAStmt() and not s instanceof Pass)
|
||||
@@ -28,7 +29,7 @@ predicate no_comment(ExceptStmt ex) {
|
||||
}
|
||||
|
||||
predicate non_local_control_flow(ExceptStmt ex) {
|
||||
ex.getType().pointsTo(ClassValue::stopIteration())
|
||||
ex.getType() = API::builtin("StopIteration").getAValueReachableFromSource().asExpr()
|
||||
}
|
||||
|
||||
predicate try_has_normal_exit(Try try) {
|
||||
@@ -61,27 +62,32 @@ predicate subscript(Stmt s) {
|
||||
s.(Delete).getATarget() instanceof Subscript
|
||||
}
|
||||
|
||||
predicate encode_decode(Call ex, ClassValue type) {
|
||||
predicate encode_decode(Call ex, Expr type) {
|
||||
exists(string name | ex.getFunc().(Attribute).getName() = name |
|
||||
name = "encode" and type = ClassValue::unicodeEncodeError()
|
||||
name = "encode" and
|
||||
type = API::builtin("UnicodeEncodeError").getAValueReachableFromSource().asExpr()
|
||||
or
|
||||
name = "decode" and type = ClassValue::unicodeDecodeError()
|
||||
name = "decode" and
|
||||
type = API::builtin("UnicodeDecodeError").getAValueReachableFromSource().asExpr()
|
||||
)
|
||||
}
|
||||
|
||||
predicate small_handler(ExceptStmt ex, Stmt s, ClassValue type) {
|
||||
predicate small_handler(ExceptStmt ex, Stmt s, Expr type) {
|
||||
not exists(ex.getTry().getStmt(1)) and
|
||||
s = ex.getTry().getStmt(0) and
|
||||
ex.getType().pointsTo(type)
|
||||
ex.getType() = type
|
||||
}
|
||||
|
||||
predicate focussed_handler(ExceptStmt ex) {
|
||||
exists(Stmt s, ClassValue type | small_handler(ex, s, type) |
|
||||
subscript(s) and type.getASuperType() = ClassValue::lookupError()
|
||||
exists(Stmt s, Expr type | small_handler(ex, s, type) |
|
||||
subscript(s) and
|
||||
type = API::builtin("IndexError").getASubclass*().getAValueReachableFromSource().asExpr()
|
||||
or
|
||||
attribute_access(s) and type = ClassValue::attributeError()
|
||||
attribute_access(s) and
|
||||
type = API::builtin("AttributeError").getAValueReachableFromSource().asExpr()
|
||||
or
|
||||
s.(ExprStmt).getValue() instanceof Name and type = ClassValue::nameError()
|
||||
s.(ExprStmt).getValue() instanceof Name and
|
||||
type = API::builtin("NameError").getAValueReachableFromSource().asExpr()
|
||||
or
|
||||
encode_decode(s.(ExprStmt).getValue(), type)
|
||||
)
|
||||
|
||||
@@ -10,11 +10,14 @@
|
||||
*/
|
||||
|
||||
import python
|
||||
import semmle.python.dataflow.new.DataFlow
|
||||
|
||||
from Raise r, Value v, AstNode origin
|
||||
from Raise r, DataFlow::LocalSourceNode origin
|
||||
where
|
||||
r.getException().pointsTo(v, origin) and
|
||||
v.getClass() = ClassValue::tuple() and
|
||||
exists(DataFlow::Node exception | exception.asExpr() = r.getException() |
|
||||
origin.flowsTo(exception)
|
||||
) and
|
||||
origin.asExpr() instanceof Tuple and
|
||||
major_version() = 2
|
||||
/* Raising a tuple is a type error in Python 3, so is handled by the IllegalRaise query. */
|
||||
select r,
|
||||
|
||||
@@ -11,17 +11,22 @@
|
||||
*/
|
||||
|
||||
import python
|
||||
private import semmle.python.ApiGraphs
|
||||
|
||||
FunctionValue iter() { result = Value::named("iter") }
|
||||
API::Node iter() { result = API::builtin("iter") }
|
||||
|
||||
BuiltinFunctionValue next() { result = Value::named("next") }
|
||||
API::Node next() { result = API::builtin("next") }
|
||||
|
||||
API::Node stopIteration() { result = API::builtin("StopIteration") }
|
||||
|
||||
predicate call_to_iter(CallNode call, EssaVariable sequence) {
|
||||
sequence.getAUse() = iter().getArgumentForCall(call, 0)
|
||||
call = iter().getACall().asCfgNode() and
|
||||
call.getArg(0) = sequence.getAUse()
|
||||
}
|
||||
|
||||
predicate call_to_next(CallNode call, ControlFlowNode iter) {
|
||||
iter = next().getArgumentForCall(call, 0)
|
||||
call = next().getACall().asCfgNode() and
|
||||
call.getArg(0) = iter
|
||||
}
|
||||
|
||||
predicate call_to_next_has_default(CallNode call) {
|
||||
@@ -47,7 +52,7 @@ predicate iter_not_exhausted(EssaVariable iterator) {
|
||||
predicate stop_iteration_handled(CallNode call) {
|
||||
exists(Try t |
|
||||
t.containsInScope(call.getNode()) and
|
||||
t.getAHandler().getType().pointsTo(ClassValue::stopIteration())
|
||||
t.getAHandler().getType() = stopIteration().getAValueReachableFromSource().asExpr()
|
||||
)
|
||||
}
|
||||
|
||||
@@ -61,5 +66,11 @@ where
|
||||
) and
|
||||
call.getNode().getScope().(Function).isGenerator() and
|
||||
not exists(Comp comp | comp.contains(call.getNode())) and
|
||||
not stop_iteration_handled(call)
|
||||
not stop_iteration_handled(call) and
|
||||
// PEP 479 removes this concern from 3.7 onwards
|
||||
// see: https://peps.python.org/pep-0479/
|
||||
//
|
||||
// However, we do not know the minor version of the analyzed code (only of the extractor),
|
||||
// so we only alert on Python 2.
|
||||
major_version() = 2
|
||||
select call, "Call to next() in a generator"
|
||||
|
||||
@@ -19,5 +19,5 @@ from
|
||||
ModificationOfParameterWithDefault::Configuration config, DataFlow::PathNode source,
|
||||
DataFlow::PathNode sink
|
||||
where config.hasFlowPath(source, sink)
|
||||
select sink.getNode(), source, sink, "$@ flows to here and is mutated.", source.getNode(),
|
||||
"Default value"
|
||||
select sink.getNode(), source, sink, "This expression mutates $@.", source.getNode(),
|
||||
"a default value"
|
||||
|
||||
@@ -16,4 +16,4 @@ import Lexical.CommentedOutCode
|
||||
|
||||
from CommentedOutCodeBlock c
|
||||
where not c.maybeExampleCode()
|
||||
select c, "These comments appear to contain commented-out code."
|
||||
select c, "This comment appears to contain commented-out code."
|
||||
|
||||
@@ -65,13 +65,17 @@ private class DefaultSafeExternalApi extends SafeExternalApi {
|
||||
|
||||
/** A node representing data being passed to an external API through a call. */
|
||||
class ExternalApiDataNode extends DataFlow::Node {
|
||||
DataFlowPrivate::DataFlowCall call;
|
||||
DataFlowPrivate::DataFlowCallable callable;
|
||||
int i;
|
||||
|
||||
ExternalApiDataNode() {
|
||||
exists(call.getLocation().getFile().getRelativePath()) and
|
||||
callable = call.getCallable() and
|
||||
exists(DataFlowPrivate::DataFlowCall call |
|
||||
exists(call.getLocation().getFile().getRelativePath())
|
||||
|
|
||||
callable = call.getCallable() and
|
||||
// TODO: this ignores some complexity of keyword arguments (especially keyword-only args)
|
||||
this = call.getArg(i)
|
||||
) and
|
||||
not any(SafeExternalApi safe).getSafeCallable() = callable and
|
||||
exists(Value cv | cv = callable.getCallableValue() |
|
||||
cv.isAbsent()
|
||||
@@ -82,8 +86,6 @@ class ExternalApiDataNode extends DataFlow::Node {
|
||||
or
|
||||
not exists(cv.(CallableValue).getScope().getLocation().getFile().getRelativePath())
|
||||
) and
|
||||
// TODO: this ignores some complexity of keyword arguments (especially keyword-only args)
|
||||
this = call.getArg(i) and
|
||||
// Not already modeled as a taint step
|
||||
not exists(DataFlow::Node next | TaintTrackingPrivate::defaultAdditionalTaintStep(this, next)) and
|
||||
// for `list.append(x)`, we have a additional taint step from x -> [post] list.
|
||||
|
||||
@@ -18,5 +18,5 @@ import DataFlow::PathGraph
|
||||
|
||||
from Configuration config, DataFlow::PathNode source, DataFlow::PathNode sink
|
||||
where config.hasFlowPath(source, sink)
|
||||
select sink.getNode(), source, sink, "Extraction of tarfile from $@", source.getNode(),
|
||||
select sink.getNode(), source, sink, "This file extraction depends on $@", source.getNode(),
|
||||
"a potentially untrusted source"
|
||||
|
||||
@@ -20,5 +20,5 @@ import DataFlow::PathGraph
|
||||
|
||||
from Configuration config, DataFlow::PathNode source, DataFlow::PathNode sink
|
||||
where config.hasFlowPath(source, sink)
|
||||
select sink.getNode(), source, sink, "This command depends on $@.", source.getNode(),
|
||||
select sink.getNode(), source, sink, "This command line depends on $@.", source.getNode(),
|
||||
"a user-provided value"
|
||||
|
||||
@@ -23,6 +23,5 @@ where
|
||||
or
|
||||
any(FilterConfiguration filterConfig).hasFlowPath(source, sink) and
|
||||
parameterName = "filter"
|
||||
select sink.getNode(), source, sink,
|
||||
"$@ LDAP query parameter (" + parameterName + ") comes from $@.", sink.getNode(), "This",
|
||||
source.getNode(), "a user-provided value"
|
||||
select sink.getNode(), source, sink, "$@ depends on $@.", sink.getNode(),
|
||||
"LDAP query parameter (" + parameterName + ")", source.getNode(), "a user-provided value"
|
||||
|
||||
@@ -20,5 +20,5 @@ import DataFlow::PathGraph
|
||||
|
||||
from Configuration config, DataFlow::PathNode source, DataFlow::PathNode sink
|
||||
where config.hasFlowPath(source, sink)
|
||||
select sink.getNode(), source, sink, "$@ flows to here and is interpreted as code.",
|
||||
source.getNode(), "A user-provided value"
|
||||
select sink.getNode(), source, sink, "This code execution depends on $@.", source.getNode(),
|
||||
"a user-provided value"
|
||||
|
||||
@@ -17,5 +17,5 @@ import DataFlow::PathGraph
|
||||
|
||||
from Configuration config, DataFlow::PathNode source, DataFlow::PathNode sink
|
||||
where config.hasFlowPath(source, sink)
|
||||
select sink.getNode(), source, sink, "$@ flows to log entry.", source.getNode(),
|
||||
"User-provided value"
|
||||
select sink.getNode(), source, sink, "This log entry depends on $@.", source.getNode(),
|
||||
"a user-provided value"
|
||||
|
||||
@@ -19,5 +19,6 @@ import DataFlow::PathGraph
|
||||
|
||||
from Configuration config, DataFlow::PathNode source, DataFlow::PathNode sink
|
||||
where config.hasFlowPath(source, sink)
|
||||
select sink.getNode(), source, sink, "$@ may be exposed to an external user", source.getNode(),
|
||||
"Error information"
|
||||
select sink.getNode(), source, sink,
|
||||
"$@ flows to this location and may be exposed to an external user.", source.getNode(),
|
||||
"Stack trace information"
|
||||
|
||||
@@ -13,10 +13,9 @@
|
||||
import python
|
||||
private import semmle.python.dataflow.new.DataFlow
|
||||
private import semmle.python.Concepts
|
||||
private import semmle.python.ApiGraphs
|
||||
|
||||
from
|
||||
HTTP::Client::Request request, DataFlow::Node disablingNode, DataFlow::Node origin, string ending
|
||||
Http::Client::Request request, DataFlow::Node disablingNode, DataFlow::Node origin, string ending
|
||||
where
|
||||
request.disablesCertificateValidation(disablingNode, origin) and
|
||||
// Showing the origin is only useful when it's a different node than the one disabling
|
||||
|
||||
@@ -14,24 +14,24 @@
|
||||
import python
|
||||
import semmle.python.Concepts
|
||||
|
||||
predicate relevantSetting(HTTP::Server::CsrfProtectionSetting s) {
|
||||
predicate relevantSetting(Http::Server::CsrfProtectionSetting s) {
|
||||
// rule out test code as this is a common place to turn off CSRF protection.
|
||||
// We don't use normal `TestScope` to find test files, since we also want to match
|
||||
// a settings file such as `.../integration-tests/settings.py`
|
||||
not s.getLocation().getFile().getAbsolutePath().matches("%test%")
|
||||
}
|
||||
|
||||
predicate vulnerableSetting(HTTP::Server::CsrfProtectionSetting s) {
|
||||
predicate vulnerableSetting(Http::Server::CsrfProtectionSetting s) {
|
||||
s.getVerificationSetting() = false and
|
||||
not exists(HTTP::Server::CsrfLocalProtectionSetting p | p.csrfEnabled()) and
|
||||
not exists(Http::Server::CsrfLocalProtectionSetting p | p.csrfEnabled()) and
|
||||
relevantSetting(s)
|
||||
}
|
||||
|
||||
from HTTP::Server::CsrfProtectionSetting setting
|
||||
from Http::Server::CsrfProtectionSetting setting
|
||||
where
|
||||
vulnerableSetting(setting) and
|
||||
// We have seen examples of dummy projects with vulnerable settings alongside a main
|
||||
// project with a protecting settings file. We want to rule out this scenario, so we
|
||||
// require all non-test settings to be vulnerable.
|
||||
forall(HTTP::Server::CsrfProtectionSetting s | relevantSetting(s) | vulnerableSetting(s))
|
||||
forall(Http::Server::CsrfProtectionSetting s | relevantSetting(s) | vulnerableSetting(s))
|
||||
select setting, "Potential CSRF vulnerability due to forgery protection being disabled or weakened."
|
||||
|
||||
@@ -18,4 +18,5 @@ import DataFlow::PathGraph
|
||||
|
||||
from Configuration config, DataFlow::PathNode source, DataFlow::PathNode sink
|
||||
where config.hasFlowPath(source, sink)
|
||||
select sink.getNode(), source, sink, "Deserializing of $@.", source.getNode(), "untrusted input"
|
||||
select sink.getNode(), source, sink, "Unsafe deserialization depends on $@.", source.getNode(),
|
||||
"a user-provided value"
|
||||
|
||||
@@ -18,5 +18,5 @@ import DataFlow::PathGraph
|
||||
|
||||
from Configuration config, DataFlow::PathNode source, DataFlow::PathNode sink
|
||||
where config.hasFlowPath(source, sink)
|
||||
select sink.getNode(), source, sink, "Untrusted URL redirection due to $@.", source.getNode(),
|
||||
"A user-provided value"
|
||||
select sink.getNode(), source, sink, "Untrusted URL redirection depends on $@.", source.getNode(),
|
||||
"a user-provided value"
|
||||
|
||||
@@ -19,5 +19,5 @@ import DataFlow::PathGraph
|
||||
from Configuration cfg, DataFlow::PathNode source, DataFlow::PathNode sink
|
||||
where cfg.hasFlowPath(source, sink)
|
||||
select sink.getNode(), source, sink,
|
||||
"A $@ is parsed as XML without guarding against external entity expansion.", source.getNode(),
|
||||
"user-provided value"
|
||||
"XML parsing depends on $@ without guarding against external entity expansion.", source.getNode(),
|
||||
"a user-provided value"
|
||||
|
||||
@@ -17,4 +17,5 @@ import DataFlow::PathGraph
|
||||
|
||||
from Configuration config, DataFlow::PathNode source, DataFlow::PathNode sink
|
||||
where config.hasFlowPath(source, sink)
|
||||
select sink, source, sink, "This Xpath query depends on $@.", source, "a user-provided value"
|
||||
select sink.getNode(), source, sink, "XPath expression depends on $@.", source.getNode(),
|
||||
"a user-provided value"
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
* to match may be vulnerable to denial-of-service attacks.
|
||||
* @kind path-problem
|
||||
* @problem.severity warning
|
||||
* @security-severity 7.5
|
||||
* @precision high
|
||||
* @id py/polynomial-redos
|
||||
* @tags security
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
* attacks.
|
||||
* @kind problem
|
||||
* @problem.severity error
|
||||
* @security-severity 7.5
|
||||
* @precision high
|
||||
* @id py/redos
|
||||
* @tags security
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
* exponential time on certain inputs.
|
||||
* @kind path-problem
|
||||
* @problem.severity error
|
||||
* @security-severity 7.5
|
||||
* @precision high
|
||||
* @id py/regex-injection
|
||||
* @tags security
|
||||
@@ -23,6 +24,6 @@ from
|
||||
where
|
||||
config.hasFlowPath(source, sink) and
|
||||
regexExecution = sink.getNode().(Sink).getRegexExecution()
|
||||
select sink.getNode(), source, sink,
|
||||
"$@ regular expression is constructed from a $@ and executed by $@.", sink.getNode(), "This",
|
||||
source.getNode(), "user-provided value", regexExecution, regexExecution.getName()
|
||||
select sink.getNode(), source, sink, "$@ depends on $@ and executed by $@.", sink.getNode(),
|
||||
"This regular expression", source.getNode(), "a user-provided value", regexExecution,
|
||||
regexExecution.getName()
|
||||
|
||||
@@ -19,5 +19,5 @@ import DataFlow::PathGraph
|
||||
from Configuration cfg, DataFlow::PathNode source, DataFlow::PathNode sink
|
||||
where cfg.hasFlowPath(source, sink)
|
||||
select sink.getNode(), source, sink,
|
||||
"A $@ is parsed as XML without guarding against uncontrolled entity expansion.", source.getNode(),
|
||||
"user-provided value"
|
||||
"XML parsing depends on $@ without guarding against uncontrolled entity expansion.",
|
||||
source.getNode(), "a user-provided value"
|
||||
|
||||
@@ -16,7 +16,7 @@ import DataFlow::PathGraph
|
||||
|
||||
from
|
||||
FullServerSideRequestForgeryConfiguration fullConfig, DataFlow::PathNode source,
|
||||
DataFlow::PathNode sink, HTTP::Client::Request request
|
||||
DataFlow::PathNode sink, Http::Client::Request request
|
||||
where
|
||||
request = sink.getNode().(Sink).getRequest() and
|
||||
fullConfig.hasFlowPath(source, sink) and
|
||||
|
||||
@@ -16,7 +16,7 @@ import DataFlow::PathGraph
|
||||
|
||||
from
|
||||
PartialServerSideRequestForgeryConfiguration partialConfig, DataFlow::PathNode source,
|
||||
DataFlow::PathNode sink, HTTP::Client::Request request
|
||||
DataFlow::PathNode sink, Http::Client::Request request
|
||||
where
|
||||
request = sink.getNode().(Sink).getRequest() and
|
||||
partialConfig.hasFlowPath(source, sink) and
|
||||
|
||||
@@ -61,4 +61,4 @@ predicate reportable_unreachable(Stmt s) {
|
||||
|
||||
from Stmt s
|
||||
where reportable_unreachable(s)
|
||||
select s, "Unreachable statement."
|
||||
select s, "This statement is unreachable."
|
||||
|
||||
@@ -43,4 +43,4 @@ where
|
||||
unused_local(unused, v) and
|
||||
// If unused is part of a tuple, count it as unused if all elements of that tuple are unused.
|
||||
forall(Name el | el = unused.getParentNode().(Tuple).getAnElt() | unused_local(el, _))
|
||||
select unused, "The value assigned to local variable '" + v.getId() + "' is never used."
|
||||
select unused, "Variable " + v.getId() + " is not used"
|
||||
|
||||
4
python/ql/src/change-notes/2022-08-23-alert-messages.md
Normal file
4
python/ql/src/change-notes/2022-08-23-alert-messages.md
Normal file
@@ -0,0 +1,4 @@
|
||||
---
|
||||
category: minorAnalysis
|
||||
---
|
||||
* The alert message of many queries have been changed to make the message consistent with other languages.
|
||||
@@ -0,0 +1,4 @@
|
||||
---
|
||||
category: queryMetadata
|
||||
---
|
||||
* Added the `security-severity` tag the `py/redos`, `py/polynomial-redos`, and `py/regex-injection` queries.
|
||||
@@ -416,7 +416,7 @@ class CsvWriter extends DataFlow::Node {
|
||||
* Extend this class to refine existing API models. If you want to model new APIs,
|
||||
* extend `Cookie::Range` instead.
|
||||
*/
|
||||
class Cookie extends HTTP::Server::CookieWrite instanceof Cookie::Range {
|
||||
class Cookie extends Http::Server::CookieWrite instanceof Cookie::Range {
|
||||
/**
|
||||
* Holds if this cookie is secure.
|
||||
*/
|
||||
@@ -441,7 +441,7 @@ module Cookie {
|
||||
* Extend this class to model new APIs. If you want to refine existing API models,
|
||||
* extend `Cookie` instead.
|
||||
*/
|
||||
abstract class Range extends HTTP::Server::CookieWrite::Range {
|
||||
abstract class Range extends Http::Server::CookieWrite::Range {
|
||||
/**
|
||||
* Holds if this cookie is secure.
|
||||
*/
|
||||
|
||||
@@ -15,18 +15,21 @@ private module ExperimentalPrivateDjango {
|
||||
private module DjangoMod {
|
||||
API::Node http() { result = API::moduleImport("django").getMember("http") }
|
||||
|
||||
module Http {
|
||||
module DjangoHttp {
|
||||
API::Node response() { result = http().getMember("response") }
|
||||
|
||||
API::Node request() { result = http().getMember("request") }
|
||||
|
||||
module Request {
|
||||
module HttpRequest {
|
||||
class DjangoGETParameter extends DataFlow::Node, RemoteFlowSource::Range {
|
||||
DjangoGETParameter() { this = request().getMember("GET").getMember("get").getACall() }
|
||||
class DjangoGetParameter extends DataFlow::Node, RemoteFlowSource::Range {
|
||||
DjangoGetParameter() { this = request().getMember("GET").getMember("get").getACall() }
|
||||
|
||||
override string getSourceType() { result = "django.http.request.GET.get" }
|
||||
}
|
||||
|
||||
/** DEPRECATED: Alias for DjangoGetParameter */
|
||||
deprecated class DjangoGETParameter = DjangoGetParameter;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -51,7 +54,7 @@ private module ExperimentalPrivateDjango {
|
||||
*
|
||||
* Use the predicate `HttpResponse::instance()` to get references to instances of `django.http.response.HttpResponse`.
|
||||
*/
|
||||
abstract class InstanceSource extends HTTP::Server::HttpResponse::Range, DataFlow::Node {
|
||||
abstract class InstanceSource extends Http::Server::HttpResponse::Range, DataFlow::Node {
|
||||
}
|
||||
|
||||
/** A direct instantiation of `django.http.response.HttpResponse`. */
|
||||
@@ -153,7 +156,7 @@ private module ExperimentalPrivateDjango {
|
||||
*/
|
||||
class DjangoResponseSetCookieCall extends DataFlow::MethodCallNode, Cookie::Range {
|
||||
DjangoResponseSetCookieCall() {
|
||||
this.calls(PrivateDjango::DjangoImpl::Http::Response::HttpResponse::instance(),
|
||||
this.calls(PrivateDjango::DjangoImpl::DjangoHttp::Response::HttpResponse::instance(),
|
||||
"set_cookie")
|
||||
}
|
||||
|
||||
|
||||
@@ -126,9 +126,9 @@ private module Ldap {
|
||||
(
|
||||
// ldap_connection.start_tls_s()
|
||||
// see https://www.python-ldap.org/en/python-ldap-3.3.0/reference/ldap.html#ldap.LDAPObject.start_tls_s
|
||||
exists(DataFlow::MethodCallNode startTLS |
|
||||
startTLS.getObject().getALocalSource() = initialize and
|
||||
startTLS.getMethodName() = "start_tls_s"
|
||||
exists(DataFlow::MethodCallNode startTls |
|
||||
startTls.getObject().getALocalSource() = initialize and
|
||||
startTls.getMethodName() = "start_tls_s"
|
||||
)
|
||||
or
|
||||
// ldap_connection.set_option(ldap.OPT_X_TLS_%s, True)
|
||||
@@ -234,9 +234,9 @@ private module Ldap {
|
||||
or
|
||||
// ldap_connection.start_tls_s()
|
||||
// see https://www.python-ldap.org/en/python-ldap-3.3.0/reference/ldap.html#ldap.LDAPObject.start_tls_s
|
||||
exists(DataFlow::MethodCallNode startTLS |
|
||||
startTLS.getMethodName() = "start_tls_s" and
|
||||
startTLS.getObject().getALocalSource() = this
|
||||
exists(DataFlow::MethodCallNode startTls |
|
||||
startTls.getMethodName() = "start_tls_s" and
|
||||
startTls.getObject().getALocalSource() = this
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -31,8 +31,8 @@ module SmtpLib {
|
||||
* argument. Used because of the impossibility to get local source nodes from `_subparts`'
|
||||
* `(List|Tuple)` elements.
|
||||
*/
|
||||
private class SMTPMessageConfig extends TaintTracking2::Configuration {
|
||||
SMTPMessageConfig() { this = "SMTPMessageConfig" }
|
||||
private class SmtpMessageConfig extends TaintTracking2::Configuration {
|
||||
SmtpMessageConfig() { this = "SMTPMessageConfig" }
|
||||
|
||||
override predicate isSource(DataFlow::Node source) { source = mimeText(_) }
|
||||
|
||||
@@ -87,7 +87,7 @@ module SmtpLib {
|
||||
sink =
|
||||
[sendCall.getArg(2), sendCall.getArg(2).(DataFlow::MethodCallNode).getObject()]
|
||||
.getALocalSource() and
|
||||
any(SMTPMessageConfig a)
|
||||
any(SmtpMessageConfig a)
|
||||
.hasFlow(source, sink.(DataFlow::CallCfgNode).getArgByName("_subparts"))
|
||||
or
|
||||
// via .attach()
|
||||
@@ -117,7 +117,7 @@ module SmtpLib {
|
||||
* * `sub` would be `message["Subject"]` (`Subscript`)
|
||||
* * `result` would be `"multipart test"`
|
||||
*/
|
||||
private DataFlow::Node getSMTPSubscriptByIndex(DataFlow::CallCfgNode sendCall, string index) {
|
||||
private DataFlow::Node getSmtpSubscriptByIndex(DataFlow::CallCfgNode sendCall, string index) {
|
||||
exists(DefinitionNode def, Subscript sub |
|
||||
sub = def.getNode() and
|
||||
DataFlow::exprNode(sub.getObject()).getALocalSource() =
|
||||
@@ -163,15 +163,15 @@ module SmtpLib {
|
||||
override DataFlow::Node getHtmlBody() { result = getSmtpMessage(this, "html") }
|
||||
|
||||
override DataFlow::Node getTo() {
|
||||
result in [this.getArg(1), getSMTPSubscriptByIndex(this, "To")]
|
||||
result in [this.getArg(1), getSmtpSubscriptByIndex(this, "To")]
|
||||
}
|
||||
|
||||
override DataFlow::Node getFrom() {
|
||||
result in [this.getArg(0), getSMTPSubscriptByIndex(this, "From")]
|
||||
result in [this.getArg(0), getSmtpSubscriptByIndex(this, "From")]
|
||||
}
|
||||
|
||||
override DataFlow::Node getSubject() {
|
||||
result in [this.getArg(2), getSMTPSubscriptByIndex(this, "Subject")]
|
||||
result in [this.getArg(2), getSmtpSubscriptByIndex(this, "Subject")]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user