mirror of
https://github.com/github/codeql.git
synced 2025-12-22 19:56:32 +01:00
Merge branch 'main' into import-refined
This commit is contained in:
@@ -1,3 +1,14 @@
|
||||
## 0.8.1
|
||||
|
||||
### Major Analysis Improvements
|
||||
|
||||
* We use a new analysis for the call-graph (determining which function is called). This can lead to changed results. In most cases this is much more accurate than the old call-graph that was based on points-to, but we do lose a few valid edges in the call-graph, especially around methods that are not defined inside its class.
|
||||
|
||||
### Minor Analysis Improvements
|
||||
|
||||
* Fixed module resolution so we properly recognize definitions made within if-then-else statements.
|
||||
* Added modeling of cryptographic operations in the `hmac` library.
|
||||
|
||||
## 0.8.0
|
||||
|
||||
### Breaking Changes
|
||||
|
||||
@@ -1,4 +0,0 @@
|
||||
---
|
||||
category: minorAnalysis
|
||||
---
|
||||
* Added modeling of cryptographic operations in the `hmac` library.
|
||||
@@ -1,4 +0,0 @@
|
||||
---
|
||||
category: minorAnalysis
|
||||
---
|
||||
* Fixed module resolution so we properly recognize definitions made within if-then-else statements.
|
||||
@@ -0,0 +1,9 @@
|
||||
---
|
||||
category: majorAnalysis
|
||||
---
|
||||
* The main data flow and taint tracking APIs have been changed. The old APIs
|
||||
remain in place for now and translate to the new through a
|
||||
backwards-compatible wrapper. If multiple configurations are in scope
|
||||
simultaneously, then this may affect results slightly. The new API is quite
|
||||
similar to the old, but makes use of a configuration module instead of a
|
||||
configuration class.
|
||||
4
python/ql/lib/change-notes/2023-03-03-delete-deps.md
Normal file
4
python/ql/lib/change-notes/2023-03-03-delete-deps.md
Normal file
@@ -0,0 +1,4 @@
|
||||
---
|
||||
category: minorAnalysis
|
||||
---
|
||||
* Deleted the deprecated `getPath` and `getFolder` predicates from the `XmlFile` class.
|
||||
4
python/ql/lib/change-notes/2023-03-13-mergepathgraph.md
Normal file
4
python/ql/lib/change-notes/2023-03-13-mergepathgraph.md
Normal file
@@ -0,0 +1,4 @@
|
||||
---
|
||||
category: feature
|
||||
---
|
||||
* Added support for merging two `PathGraph`s via disjoint union to allow results from multiple data flow computations in a single `path-problem` query.
|
||||
@@ -1,4 +1,10 @@
|
||||
---
|
||||
category: majorAnalysis
|
||||
---
|
||||
* We use a new analysis for the call-graph (determining which function is called). This can lead to changed results. In most cases this is much more accurate than the old call-graph that was based on points-to, but we do lose a few valid edges in the call-graph, especially around methods that are not defined inside its' class.
|
||||
## 0.8.1
|
||||
|
||||
### Major Analysis Improvements
|
||||
|
||||
* We use a new analysis for the call-graph (determining which function is called). This can lead to changed results. In most cases this is much more accurate than the old call-graph that was based on points-to, but we do lose a few valid edges in the call-graph, especially around methods that are not defined inside its class.
|
||||
|
||||
### Minor Analysis Improvements
|
||||
|
||||
* Fixed module resolution so we properly recognize definitions made within if-then-else statements.
|
||||
* Added modeling of cryptographic operations in the `hmac` library.
|
||||
@@ -1,2 +1,2 @@
|
||||
---
|
||||
lastReleaseVersion: 0.8.0
|
||||
lastReleaseVersion: 0.8.1
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
name: codeql/python-all
|
||||
version: 0.8.1-dev
|
||||
version: 0.8.2-dev
|
||||
groups: python
|
||||
dbscheme: semmlecode.python.dbscheme
|
||||
extractor: python
|
||||
|
||||
@@ -19,6 +19,9 @@ private import semmle.python.security.internal.EncryptionKeySizes
|
||||
* extend `SystemCommandExecution::Range` instead.
|
||||
*/
|
||||
class SystemCommandExecution extends DataFlow::Node instanceof SystemCommandExecution::Range {
|
||||
/** Holds if a shell interprets `arg`. */
|
||||
predicate isShellInterpreted(DataFlow::Node arg) { super.isShellInterpreted(arg) }
|
||||
|
||||
/** Gets the argument that specifies the command to be executed. */
|
||||
DataFlow::Node getCommand() { result = super.getCommand() }
|
||||
}
|
||||
@@ -35,6 +38,9 @@ module SystemCommandExecution {
|
||||
abstract class Range extends DataFlow::Node {
|
||||
/** Gets the argument that specifies the command to be executed. */
|
||||
abstract DataFlow::Node getCommand();
|
||||
|
||||
/** Holds if a shell interprets `arg`. */
|
||||
predicate isShellInterpreted(DataFlow::Node arg) { none() }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1019,7 +1025,8 @@ module Http {
|
||||
* Extend this class to refine existing API models. If you want to model new APIs,
|
||||
* extend `CsrfLocalProtectionSetting::Range` instead.
|
||||
*/
|
||||
class CsrfLocalProtectionSetting extends DataFlow::Node instanceof CsrfLocalProtectionSetting::Range {
|
||||
class CsrfLocalProtectionSetting extends DataFlow::Node instanceof CsrfLocalProtectionSetting::Range
|
||||
{
|
||||
/**
|
||||
* Gets a request handler whose CSRF protection is changed.
|
||||
*/
|
||||
|
||||
@@ -51,6 +51,7 @@ private import semmle.python.frameworks.Simplejson
|
||||
private import semmle.python.frameworks.SqlAlchemy
|
||||
private import semmle.python.frameworks.Starlette
|
||||
private import semmle.python.frameworks.Stdlib
|
||||
private import semmle.python.frameworks.Setuptools
|
||||
private import semmle.python.frameworks.Toml
|
||||
private import semmle.python.frameworks.Tornado
|
||||
private import semmle.python.frameworks.Twisted
|
||||
|
||||
@@ -22,5 +22,6 @@ private import python
|
||||
* global (inter-procedural) data flow analyses.
|
||||
*/
|
||||
module DataFlow {
|
||||
import internal.DataFlowImpl
|
||||
import internal.DataFlow
|
||||
import internal.DataFlowImpl1
|
||||
}
|
||||
|
||||
@@ -15,5 +15,6 @@ private import python
|
||||
* global (inter-procedural) taint-tracking analyses.
|
||||
*/
|
||||
module TaintTracking {
|
||||
import internal.tainttracking1.TaintTracking
|
||||
import internal.tainttracking1.TaintTrackingImpl
|
||||
}
|
||||
|
||||
353
python/ql/lib/semmle/python/dataflow/new/internal/DataFlow.qll
Normal file
353
python/ql/lib/semmle/python/dataflow/new/internal/DataFlow.qll
Normal file
@@ -0,0 +1,353 @@
|
||||
/**
|
||||
* Provides an implementation of global (interprocedural) data flow. This file
|
||||
* re-exports the local (intraprocedural) data flow analysis from
|
||||
* `DataFlowImplSpecific::Public` and adds a global analysis, mainly exposed
|
||||
* through the `Make` and `MakeWithState` modules.
|
||||
*/
|
||||
|
||||
private import DataFlowImplCommon
|
||||
private import DataFlowImplSpecific::Private
|
||||
import DataFlowImplSpecific::Public
|
||||
import DataFlowImplCommonPublic
|
||||
private import DataFlowImpl
|
||||
|
||||
/** An input configuration for data flow. */
|
||||
signature module ConfigSig {
|
||||
/**
|
||||
* Holds if `source` is a relevant data flow source.
|
||||
*/
|
||||
predicate isSource(Node source);
|
||||
|
||||
/**
|
||||
* Holds if `sink` is a relevant data flow sink.
|
||||
*/
|
||||
predicate isSink(Node sink);
|
||||
|
||||
/**
|
||||
* Holds if data flow through `node` is prohibited. This completely removes
|
||||
* `node` from the data flow graph.
|
||||
*/
|
||||
default predicate isBarrier(Node node) { none() }
|
||||
|
||||
/** Holds if data flow into `node` is prohibited. */
|
||||
default predicate isBarrierIn(Node node) { none() }
|
||||
|
||||
/** Holds if data flow out of `node` is prohibited. */
|
||||
default predicate isBarrierOut(Node node) { none() }
|
||||
|
||||
/**
|
||||
* Holds if data may flow from `node1` to `node2` in addition to the normal data-flow steps.
|
||||
*/
|
||||
default predicate isAdditionalFlowStep(Node node1, Node node2) { none() }
|
||||
|
||||
/**
|
||||
* Holds if an arbitrary number of implicit read steps of content `c` may be
|
||||
* taken at `node`.
|
||||
*/
|
||||
default predicate allowImplicitRead(Node node, ContentSet c) { none() }
|
||||
|
||||
/**
|
||||
* Gets the virtual dispatch branching limit when calculating field flow.
|
||||
* This can be overridden to a smaller value to improve performance (a
|
||||
* value of 0 disables field flow), or a larger value to get more results.
|
||||
*/
|
||||
default int fieldFlowBranchLimit() { result = 2 }
|
||||
|
||||
/**
|
||||
* Gets a data flow configuration feature to add restrictions to the set of
|
||||
* valid flow paths.
|
||||
*
|
||||
* - `FeatureHasSourceCallContext`:
|
||||
* Assume that sources have some existing call context to disallow
|
||||
* conflicting return-flow directly following the source.
|
||||
* - `FeatureHasSinkCallContext`:
|
||||
* Assume that sinks have some existing call context to disallow
|
||||
* conflicting argument-to-parameter flow directly preceding the sink.
|
||||
* - `FeatureEqualSourceSinkCallContext`:
|
||||
* Implies both of the above and additionally ensures that the entire flow
|
||||
* path preserves the call context.
|
||||
*
|
||||
* These features are generally not relevant for typical end-to-end data flow
|
||||
* queries, but should only be used for constructing paths that need to
|
||||
* somehow be pluggable in another path context.
|
||||
*/
|
||||
default FlowFeature getAFeature() { none() }
|
||||
|
||||
/** Holds if sources should be grouped in the result of `hasFlowPath`. */
|
||||
default predicate sourceGrouping(Node source, string sourceGroup) { none() }
|
||||
|
||||
/** Holds if sinks should be grouped in the result of `hasFlowPath`. */
|
||||
default predicate sinkGrouping(Node sink, string sinkGroup) { none() }
|
||||
|
||||
/**
|
||||
* Holds if hidden nodes should be included in the data flow graph.
|
||||
*
|
||||
* This feature should only be used for debugging or when the data flow graph
|
||||
* is not visualized (as it is in a `path-problem` query).
|
||||
*/
|
||||
default predicate includeHiddenNodes() { none() }
|
||||
}
|
||||
|
||||
/** An input configuration for data flow using flow state. */
|
||||
signature module StateConfigSig {
|
||||
bindingset[this]
|
||||
class FlowState;
|
||||
|
||||
/**
|
||||
* Holds if `source` is a relevant data flow source with the given initial
|
||||
* `state`.
|
||||
*/
|
||||
predicate isSource(Node source, FlowState state);
|
||||
|
||||
/**
|
||||
* Holds if `sink` is a relevant data flow sink accepting `state`.
|
||||
*/
|
||||
predicate isSink(Node sink, FlowState state);
|
||||
|
||||
/**
|
||||
* Holds if data flow through `node` is prohibited. This completely removes
|
||||
* `node` from the data flow graph.
|
||||
*/
|
||||
default predicate isBarrier(Node node) { none() }
|
||||
|
||||
/**
|
||||
* Holds if data flow through `node` is prohibited when the flow state is
|
||||
* `state`.
|
||||
*/
|
||||
predicate isBarrier(Node node, FlowState state);
|
||||
|
||||
/** Holds if data flow into `node` is prohibited. */
|
||||
default predicate isBarrierIn(Node node) { none() }
|
||||
|
||||
/** Holds if data flow out of `node` is prohibited. */
|
||||
default predicate isBarrierOut(Node node) { none() }
|
||||
|
||||
/**
|
||||
* Holds if data may flow from `node1` to `node2` in addition to the normal data-flow steps.
|
||||
*/
|
||||
default predicate isAdditionalFlowStep(Node node1, Node node2) { none() }
|
||||
|
||||
/**
|
||||
* Holds if data may flow from `node1` to `node2` in addition to the normal data-flow steps.
|
||||
* This step is only applicable in `state1` and updates the flow state to `state2`.
|
||||
*/
|
||||
predicate isAdditionalFlowStep(Node node1, FlowState state1, Node node2, FlowState state2);
|
||||
|
||||
/**
|
||||
* Holds if an arbitrary number of implicit read steps of content `c` may be
|
||||
* taken at `node`.
|
||||
*/
|
||||
default predicate allowImplicitRead(Node node, ContentSet c) { none() }
|
||||
|
||||
/**
|
||||
* Gets the virtual dispatch branching limit when calculating field flow.
|
||||
* This can be overridden to a smaller value to improve performance (a
|
||||
* value of 0 disables field flow), or a larger value to get more results.
|
||||
*/
|
||||
default int fieldFlowBranchLimit() { result = 2 }
|
||||
|
||||
/**
|
||||
* Gets a data flow configuration feature to add restrictions to the set of
|
||||
* valid flow paths.
|
||||
*
|
||||
* - `FeatureHasSourceCallContext`:
|
||||
* Assume that sources have some existing call context to disallow
|
||||
* conflicting return-flow directly following the source.
|
||||
* - `FeatureHasSinkCallContext`:
|
||||
* Assume that sinks have some existing call context to disallow
|
||||
* conflicting argument-to-parameter flow directly preceding the sink.
|
||||
* - `FeatureEqualSourceSinkCallContext`:
|
||||
* Implies both of the above and additionally ensures that the entire flow
|
||||
* path preserves the call context.
|
||||
*
|
||||
* These features are generally not relevant for typical end-to-end data flow
|
||||
* queries, but should only be used for constructing paths that need to
|
||||
* somehow be pluggable in another path context.
|
||||
*/
|
||||
default FlowFeature getAFeature() { none() }
|
||||
|
||||
/** Holds if sources should be grouped in the result of `hasFlowPath`. */
|
||||
default predicate sourceGrouping(Node source, string sourceGroup) { none() }
|
||||
|
||||
/** Holds if sinks should be grouped in the result of `hasFlowPath`. */
|
||||
default predicate sinkGrouping(Node sink, string sinkGroup) { none() }
|
||||
|
||||
/**
|
||||
* Holds if hidden nodes should be included in the data flow graph.
|
||||
*
|
||||
* This feature should only be used for debugging or when the data flow graph
|
||||
* is not visualized (as it is in a `path-problem` query).
|
||||
*/
|
||||
default predicate includeHiddenNodes() { none() }
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the exploration limit for `hasPartialFlow` and `hasPartialFlowRev`
|
||||
* measured in approximate number of interprocedural steps.
|
||||
*/
|
||||
signature int explorationLimitSig();
|
||||
|
||||
/**
|
||||
* The output of a data flow computation.
|
||||
*/
|
||||
signature module DataFlowSig {
|
||||
/**
|
||||
* A `Node` augmented with a call context (except for sinks) and an access path.
|
||||
* Only those `PathNode`s that are reachable from a source, and which can reach a sink, are generated.
|
||||
*/
|
||||
class PathNode;
|
||||
|
||||
/**
|
||||
* Holds if data can flow from `source` to `sink`.
|
||||
*
|
||||
* The corresponding paths are generated from the end-points and the graph
|
||||
* included in the module `PathGraph`.
|
||||
*/
|
||||
predicate hasFlowPath(PathNode source, PathNode sink);
|
||||
|
||||
/**
|
||||
* Holds if data can flow from `source` to `sink`.
|
||||
*/
|
||||
predicate hasFlow(Node source, Node sink);
|
||||
|
||||
/**
|
||||
* Holds if data can flow from some source to `sink`.
|
||||
*/
|
||||
predicate hasFlowTo(Node sink);
|
||||
|
||||
/**
|
||||
* Holds if data can flow from some source to `sink`.
|
||||
*/
|
||||
predicate hasFlowToExpr(DataFlowExpr sink);
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs a standard data flow computation.
|
||||
*/
|
||||
module Make<ConfigSig Config> implements DataFlowSig {
|
||||
private module C implements FullStateConfigSig {
|
||||
import DefaultState<Config>
|
||||
import Config
|
||||
}
|
||||
|
||||
import Impl<C>
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs a data flow computation using flow state.
|
||||
*/
|
||||
module MakeWithState<StateConfigSig Config> implements DataFlowSig {
|
||||
private module C implements FullStateConfigSig {
|
||||
import Config
|
||||
}
|
||||
|
||||
import Impl<C>
|
||||
}
|
||||
|
||||
signature class PathNodeSig {
|
||||
/** Gets a textual representation of this element. */
|
||||
string toString();
|
||||
|
||||
/**
|
||||
* 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
|
||||
);
|
||||
|
||||
/** Gets the underlying `Node`. */
|
||||
Node getNode();
|
||||
}
|
||||
|
||||
signature module PathGraphSig<PathNodeSig PathNode> {
|
||||
/** Holds if `(a,b)` is an edge in the graph of data flow path explanations. */
|
||||
predicate edges(PathNode a, PathNode b);
|
||||
|
||||
/** Holds if `n` is a node in the graph of data flow path explanations. */
|
||||
predicate nodes(PathNode n, string key, string val);
|
||||
|
||||
/**
|
||||
* Holds if `(arg, par, ret, out)` forms a subpath-tuple, that is, flow through
|
||||
* a subpath between `par` and `ret` with the connecting edges `arg -> par` and
|
||||
* `ret -> out` is summarized as the edge `arg -> out`.
|
||||
*/
|
||||
predicate subpaths(PathNode arg, PathNode par, PathNode ret, PathNode out);
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs a `PathGraph` from two `PathGraph`s by disjoint union.
|
||||
*/
|
||||
module MergePathGraph<
|
||||
PathNodeSig PathNode1, PathNodeSig PathNode2, PathGraphSig<PathNode1> Graph1,
|
||||
PathGraphSig<PathNode2> Graph2>
|
||||
{
|
||||
private newtype TPathNode =
|
||||
TPathNode1(PathNode1 p) or
|
||||
TPathNode2(PathNode2 p)
|
||||
|
||||
/** A node in a graph of path explanations that is formed by disjoint union of the two given graphs. */
|
||||
class PathNode extends TPathNode {
|
||||
/** Gets this as a projection on the first given `PathGraph`. */
|
||||
PathNode1 asPathNode1() { this = TPathNode1(result) }
|
||||
|
||||
/** Gets this as a projection on the second given `PathGraph`. */
|
||||
PathNode2 asPathNode2() { this = TPathNode2(result) }
|
||||
|
||||
/** Gets a textual representation of this element. */
|
||||
string toString() {
|
||||
result = this.asPathNode1().toString() or
|
||||
result = this.asPathNode2().toString()
|
||||
}
|
||||
|
||||
/**
|
||||
* 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.asPathNode1().hasLocationInfo(filepath, startline, startcolumn, endline, endcolumn) or
|
||||
this.asPathNode2().hasLocationInfo(filepath, startline, startcolumn, endline, endcolumn)
|
||||
}
|
||||
|
||||
/** Gets the underlying `Node`. */
|
||||
Node getNode() {
|
||||
result = this.asPathNode1().getNode() or
|
||||
result = this.asPathNode2().getNode()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Provides the query predicates needed to include a graph in a path-problem query.
|
||||
*/
|
||||
module PathGraph implements PathGraphSig<PathNode> {
|
||||
/** Holds if `(a,b)` is an edge in the graph of data flow path explanations. */
|
||||
query predicate edges(PathNode a, PathNode b) {
|
||||
Graph1::edges(a.asPathNode1(), b.asPathNode1()) or
|
||||
Graph2::edges(a.asPathNode2(), b.asPathNode2())
|
||||
}
|
||||
|
||||
/** Holds if `n` is a node in the graph of data flow path explanations. */
|
||||
query predicate nodes(PathNode n, string key, string val) {
|
||||
Graph1::nodes(n.asPathNode1(), key, val) or
|
||||
Graph2::nodes(n.asPathNode2(), key, val)
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if `(arg, par, ret, out)` forms a subpath-tuple, that is, flow through
|
||||
* a subpath between `par` and `ret` with the connecting edges `arg -> par` and
|
||||
* `ret -> out` is summarized as the edge `arg -> out`.
|
||||
*/
|
||||
query predicate subpaths(PathNode arg, PathNode par, PathNode ret, PathNode out) {
|
||||
Graph1::subpaths(arg.asPathNode1(), par.asPathNode1(), ret.asPathNode1(), out.asPathNode1()) or
|
||||
Graph2::subpaths(arg.asPathNode2(), par.asPathNode2(), ret.asPathNode2(), out.asPathNode2())
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -64,7 +64,14 @@ newtype TParameterPosition =
|
||||
index = any(Parameter p).getPosition() + 1
|
||||
} or
|
||||
TSynthStarArgsElementParameterPosition(int index) { exists(TStarArgsParameterPosition(index)) } or
|
||||
TDictSplatParameterPosition()
|
||||
TDictSplatParameterPosition() or
|
||||
// To get flow from a **kwargs argument to a keyword parameter, we add a read-step
|
||||
// from a synthetic **kwargs parameter. We need this separate synthetic ParameterNode,
|
||||
// since we clear content of the normal **kwargs parameter for the names that
|
||||
// correspond to normal keyword parameters. Since we cannot re-use the same parameter
|
||||
// position for multiple parameter nodes in the same callable, we introduce this
|
||||
// synthetic parameter position.
|
||||
TSynthDictSplatParameterPosition()
|
||||
|
||||
/** A parameter position. */
|
||||
class ParameterPosition extends TParameterPosition {
|
||||
@@ -92,6 +99,12 @@ class ParameterPosition extends TParameterPosition {
|
||||
/** Holds if this position represents a `**kwargs` parameter. */
|
||||
predicate isDictSplat() { this = TDictSplatParameterPosition() }
|
||||
|
||||
/**
|
||||
* Holds if this position represents a **synthetic** `**kwargs` parameter
|
||||
* (see comment for `TSynthDictSplatParameterPosition`).
|
||||
*/
|
||||
predicate isSynthDictSplat() { this = TSynthDictSplatParameterPosition() }
|
||||
|
||||
/** Gets a textual representation of this element. */
|
||||
string toString() {
|
||||
this.isSelf() and result = "self"
|
||||
@@ -108,6 +121,8 @@ class ParameterPosition extends TParameterPosition {
|
||||
)
|
||||
or
|
||||
this.isDictSplat() and result = "**"
|
||||
or
|
||||
this.isSynthDictSplat() and result = "synthetic **"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -179,6 +194,8 @@ predicate parameterMatch(ParameterPosition ppos, ArgumentPosition apos) {
|
||||
)
|
||||
or
|
||||
ppos.isDictSplat() and apos.isDictSplat()
|
||||
or
|
||||
ppos.isSynthDictSplat() and apos.isDictSplat()
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
@@ -324,16 +341,9 @@ abstract class DataFlowFunction extends DataFlowCallable, TFunction {
|
||||
)
|
||||
or
|
||||
// `**kwargs`
|
||||
// since the dataflow library has the restriction that we can only have ONE result per
|
||||
// parameter position, if there is both a synthetic **kwargs and a real **kwargs
|
||||
// parameter, we only give the result for the synthetic, and add local flow from the
|
||||
// synthetic to the real. It might seem more natural to do it in the other
|
||||
// direction, but since we have a clearStep on the real **kwargs parameter, we would have that
|
||||
// content-clearing would also affect the synthetic parameter, which we don't want.
|
||||
ppos.isDictSplat() and
|
||||
if exists(func.getArgByName(_))
|
||||
then result = TSynthDictSplatParameterNode(this)
|
||||
else result.getParameter() = func.getKwarg()
|
||||
ppos.isDictSplat() and result.getParameter() = func.getKwarg()
|
||||
or
|
||||
ppos.isSynthDictSplat() and result = TSynthDictSplatParameterNode(this)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1460,16 +1470,7 @@ class SummaryParameterNode extends ParameterNodeImpl, TSummaryParameterNode {
|
||||
override Parameter getParameter() { none() }
|
||||
|
||||
override predicate isParameterOf(DataFlowCallable c, ParameterPosition ppos) {
|
||||
sc = c.asLibraryCallable() and
|
||||
ppos = pos and
|
||||
// avoid overlap with `SynthDictSplatParameterNode`
|
||||
not (
|
||||
pos.isDictSplat() and
|
||||
exists(ParameterPosition keywordPos |
|
||||
FlowSummaryImpl::Private::summaryParameterNodeRange(sc, keywordPos) and
|
||||
keywordPos.isKeyword(_)
|
||||
)
|
||||
)
|
||||
sc = c.asLibraryCallable() and ppos = pos
|
||||
}
|
||||
|
||||
override DataFlowCallable getEnclosingCallable() { result.asLibraryCallable() = sc }
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,396 @@
|
||||
/**
|
||||
* DEPRECATED: Use `Make` and `MakeWithState` instead.
|
||||
*
|
||||
* Provides a `Configuration` class backwards-compatible interface to the data
|
||||
* flow library.
|
||||
*/
|
||||
|
||||
private import DataFlowImplCommon
|
||||
private import DataFlowImplSpecific::Private
|
||||
import DataFlowImplSpecific::Public
|
||||
private import DataFlowImpl
|
||||
import DataFlowImplCommonPublic
|
||||
import FlowStateString
|
||||
|
||||
/**
|
||||
* A configuration of interprocedural data flow analysis. This defines
|
||||
* sources, sinks, and any other configurable aspect of the analysis. Each
|
||||
* use of the global data flow library must define its own unique extension
|
||||
* of this abstract class. To create a configuration, extend this class with
|
||||
* a subclass whose characteristic predicate is a unique singleton string.
|
||||
* For example, write
|
||||
*
|
||||
* ```ql
|
||||
* class MyAnalysisConfiguration extends DataFlow::Configuration {
|
||||
* MyAnalysisConfiguration() { this = "MyAnalysisConfiguration" }
|
||||
* // Override `isSource` and `isSink`.
|
||||
* // Optionally override `isBarrier`.
|
||||
* // Optionally override `isAdditionalFlowStep`.
|
||||
* }
|
||||
* ```
|
||||
* Conceptually, this defines a graph where the nodes are `DataFlow::Node`s and
|
||||
* the edges are those data-flow steps that preserve the value of the node
|
||||
* along with any additional edges defined by `isAdditionalFlowStep`.
|
||||
* Specifying nodes in `isBarrier` will remove those nodes from the graph, and
|
||||
* specifying nodes in `isBarrierIn` and/or `isBarrierOut` will remove in-going
|
||||
* and/or out-going edges from those nodes, respectively.
|
||||
*
|
||||
* Then, to query whether there is flow between some `source` and `sink`,
|
||||
* write
|
||||
*
|
||||
* ```ql
|
||||
* exists(MyAnalysisConfiguration cfg | cfg.hasFlow(source, sink))
|
||||
* ```
|
||||
*
|
||||
* Multiple configurations can coexist, but two classes extending
|
||||
* `DataFlow::Configuration` should never depend on each other. One of them
|
||||
* should instead depend on a `DataFlow2::Configuration`, a
|
||||
* `DataFlow3::Configuration`, or a `DataFlow4::Configuration`.
|
||||
*/
|
||||
abstract class Configuration extends string {
|
||||
bindingset[this]
|
||||
Configuration() { any() }
|
||||
|
||||
/**
|
||||
* Holds if `source` is a relevant data flow source.
|
||||
*/
|
||||
predicate isSource(Node source) { none() }
|
||||
|
||||
/**
|
||||
* Holds if `source` is a relevant data flow source with the given initial
|
||||
* `state`.
|
||||
*/
|
||||
predicate isSource(Node source, FlowState state) { none() }
|
||||
|
||||
/**
|
||||
* Holds if `sink` is a relevant data flow sink.
|
||||
*/
|
||||
predicate isSink(Node sink) { none() }
|
||||
|
||||
/**
|
||||
* Holds if `sink` is a relevant data flow sink accepting `state`.
|
||||
*/
|
||||
predicate isSink(Node sink, FlowState state) { none() }
|
||||
|
||||
/**
|
||||
* Holds if data flow through `node` is prohibited. This completely removes
|
||||
* `node` from the data flow graph.
|
||||
*/
|
||||
predicate isBarrier(Node node) { none() }
|
||||
|
||||
/**
|
||||
* Holds if data flow through `node` is prohibited when the flow state is
|
||||
* `state`.
|
||||
*/
|
||||
predicate isBarrier(Node node, FlowState state) { none() }
|
||||
|
||||
/** Holds if data flow into `node` is prohibited. */
|
||||
predicate isBarrierIn(Node node) { none() }
|
||||
|
||||
/** Holds if data flow out of `node` is prohibited. */
|
||||
predicate isBarrierOut(Node node) { none() }
|
||||
|
||||
/**
|
||||
* DEPRECATED: Use `isBarrier` and `BarrierGuard` module instead.
|
||||
*
|
||||
* Holds if data flow through nodes guarded by `guard` is prohibited.
|
||||
*/
|
||||
deprecated predicate isBarrierGuard(BarrierGuard guard) { none() }
|
||||
|
||||
/**
|
||||
* DEPRECATED: Use `isBarrier` and `BarrierGuard` module instead.
|
||||
*
|
||||
* Holds if data flow through nodes guarded by `guard` is prohibited when
|
||||
* the flow state is `state`
|
||||
*/
|
||||
deprecated predicate isBarrierGuard(BarrierGuard guard, FlowState state) { none() }
|
||||
|
||||
/**
|
||||
* Holds if data may flow from `node1` to `node2` in addition to the normal data-flow steps.
|
||||
*/
|
||||
predicate isAdditionalFlowStep(Node node1, Node node2) { none() }
|
||||
|
||||
/**
|
||||
* Holds if data may flow from `node1` to `node2` in addition to the normal data-flow steps.
|
||||
* This step is only applicable in `state1` and updates the flow state to `state2`.
|
||||
*/
|
||||
predicate isAdditionalFlowStep(Node node1, FlowState state1, Node node2, FlowState state2) {
|
||||
none()
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if an arbitrary number of implicit read steps of content `c` may be
|
||||
* taken at `node`.
|
||||
*/
|
||||
predicate allowImplicitRead(Node node, ContentSet c) { none() }
|
||||
|
||||
/**
|
||||
* Gets the virtual dispatch branching limit when calculating field flow.
|
||||
* This can be overridden to a smaller value to improve performance (a
|
||||
* value of 0 disables field flow), or a larger value to get more results.
|
||||
*/
|
||||
int fieldFlowBranchLimit() { result = 2 }
|
||||
|
||||
/**
|
||||
* Gets a data flow configuration feature to add restrictions to the set of
|
||||
* valid flow paths.
|
||||
*
|
||||
* - `FeatureHasSourceCallContext`:
|
||||
* Assume that sources have some existing call context to disallow
|
||||
* conflicting return-flow directly following the source.
|
||||
* - `FeatureHasSinkCallContext`:
|
||||
* Assume that sinks have some existing call context to disallow
|
||||
* conflicting argument-to-parameter flow directly preceding the sink.
|
||||
* - `FeatureEqualSourceSinkCallContext`:
|
||||
* Implies both of the above and additionally ensures that the entire flow
|
||||
* path preserves the call context.
|
||||
*
|
||||
* These features are generally not relevant for typical end-to-end data flow
|
||||
* queries, but should only be used for constructing paths that need to
|
||||
* somehow be pluggable in another path context.
|
||||
*/
|
||||
FlowFeature getAFeature() { none() }
|
||||
|
||||
/** Holds if sources should be grouped in the result of `hasFlowPath`. */
|
||||
predicate sourceGrouping(Node source, string sourceGroup) { none() }
|
||||
|
||||
/** Holds if sinks should be grouped in the result of `hasFlowPath`. */
|
||||
predicate sinkGrouping(Node sink, string sinkGroup) { none() }
|
||||
|
||||
/**
|
||||
* Holds if data may flow from `source` to `sink` for this configuration.
|
||||
*/
|
||||
predicate hasFlow(Node source, Node sink) { hasFlow(source, sink, this) }
|
||||
|
||||
/**
|
||||
* Holds if data may flow from `source` to `sink` for this configuration.
|
||||
*
|
||||
* The corresponding paths are generated from the end-points and the graph
|
||||
* included in the module `PathGraph`.
|
||||
*/
|
||||
predicate hasFlowPath(PathNode source, PathNode sink) { hasFlowPath(source, sink, this) }
|
||||
|
||||
/**
|
||||
* Holds if data may flow from some source to `sink` for this configuration.
|
||||
*/
|
||||
predicate hasFlowTo(Node sink) { hasFlowTo(sink, this) }
|
||||
|
||||
/**
|
||||
* Holds if data may flow from some source to `sink` for this configuration.
|
||||
*/
|
||||
predicate hasFlowToExpr(DataFlowExpr sink) { this.hasFlowTo(exprNode(sink)) }
|
||||
|
||||
/**
|
||||
* DEPRECATED: Use `FlowExploration<explorationLimit>` instead.
|
||||
*
|
||||
* Gets the exploration limit for `hasPartialFlow` and `hasPartialFlowRev`
|
||||
* measured in approximate number of interprocedural steps.
|
||||
*/
|
||||
deprecated int explorationLimit() { none() }
|
||||
|
||||
/**
|
||||
* Holds if hidden nodes should be included in the data flow graph.
|
||||
*
|
||||
* This feature should only be used for debugging or when the data flow graph
|
||||
* is not visualized (for example in a `path-problem` query).
|
||||
*/
|
||||
predicate includeHiddenNodes() { none() }
|
||||
}
|
||||
|
||||
/**
|
||||
* This class exists to prevent mutual recursion between the user-overridden
|
||||
* member predicates of `Configuration` and the rest of the data-flow library.
|
||||
* Good performance cannot be guaranteed in the presence of such recursion, so
|
||||
* it should be replaced by using more than one copy of the data flow library.
|
||||
*/
|
||||
abstract private class ConfigurationRecursionPrevention extends Configuration {
|
||||
bindingset[this]
|
||||
ConfigurationRecursionPrevention() { any() }
|
||||
|
||||
override predicate hasFlow(Node source, Node sink) {
|
||||
strictcount(Node n | this.isSource(n)) < 0
|
||||
or
|
||||
strictcount(Node n | this.isSource(n, _)) < 0
|
||||
or
|
||||
strictcount(Node n | this.isSink(n)) < 0
|
||||
or
|
||||
strictcount(Node n | this.isSink(n, _)) < 0
|
||||
or
|
||||
strictcount(Node n1, Node n2 | this.isAdditionalFlowStep(n1, n2)) < 0
|
||||
or
|
||||
strictcount(Node n1, Node n2 | this.isAdditionalFlowStep(n1, _, n2, _)) < 0
|
||||
or
|
||||
super.hasFlow(source, sink)
|
||||
}
|
||||
}
|
||||
|
||||
/** A bridge class to access the deprecated `isBarrierGuard`. */
|
||||
private class BarrierGuardGuardedNodeBridge extends Unit {
|
||||
abstract predicate guardedNode(Node n, Configuration config);
|
||||
|
||||
abstract predicate guardedNode(Node n, FlowState state, Configuration config);
|
||||
}
|
||||
|
||||
private class BarrierGuardGuardedNode extends BarrierGuardGuardedNodeBridge {
|
||||
deprecated override predicate guardedNode(Node n, Configuration config) {
|
||||
exists(BarrierGuard g |
|
||||
config.isBarrierGuard(g) and
|
||||
n = g.getAGuardedNode()
|
||||
)
|
||||
}
|
||||
|
||||
deprecated override predicate guardedNode(Node n, FlowState state, Configuration config) {
|
||||
exists(BarrierGuard g |
|
||||
config.isBarrierGuard(g, state) and
|
||||
n = g.getAGuardedNode()
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private FlowState relevantState(Configuration config) {
|
||||
config.isSource(_, result) or
|
||||
config.isSink(_, result) or
|
||||
config.isBarrier(_, result) or
|
||||
config.isAdditionalFlowStep(_, result, _, _) or
|
||||
config.isAdditionalFlowStep(_, _, _, result)
|
||||
}
|
||||
|
||||
private newtype TConfigState =
|
||||
TMkConfigState(Configuration config, FlowState state) {
|
||||
state = relevantState(config) or state instanceof FlowStateEmpty
|
||||
}
|
||||
|
||||
private Configuration getConfig(TConfigState state) { state = TMkConfigState(result, _) }
|
||||
|
||||
private FlowState getState(TConfigState state) { state = TMkConfigState(_, result) }
|
||||
|
||||
private predicate singleConfiguration() { 1 = strictcount(Configuration c) }
|
||||
|
||||
private module Config implements FullStateConfigSig {
|
||||
class FlowState = TConfigState;
|
||||
|
||||
predicate isSource(Node source, FlowState state) {
|
||||
getConfig(state).isSource(source, getState(state))
|
||||
or
|
||||
getConfig(state).isSource(source) and getState(state) instanceof FlowStateEmpty
|
||||
}
|
||||
|
||||
predicate isSink(Node sink, FlowState state) {
|
||||
getConfig(state).isSink(sink, getState(state))
|
||||
or
|
||||
getConfig(state).isSink(sink) and getState(state) instanceof FlowStateEmpty
|
||||
}
|
||||
|
||||
predicate isBarrier(Node node) { none() }
|
||||
|
||||
predicate isBarrier(Node node, FlowState state) {
|
||||
getConfig(state).isBarrier(node, getState(state)) or
|
||||
getConfig(state).isBarrier(node) or
|
||||
any(BarrierGuardGuardedNodeBridge b).guardedNode(node, getState(state), getConfig(state)) or
|
||||
any(BarrierGuardGuardedNodeBridge b).guardedNode(node, getConfig(state))
|
||||
}
|
||||
|
||||
predicate isBarrierIn(Node node) { any(Configuration config).isBarrierIn(node) }
|
||||
|
||||
predicate isBarrierOut(Node node) { any(Configuration config).isBarrierOut(node) }
|
||||
|
||||
predicate isAdditionalFlowStep(Node node1, Node node2) {
|
||||
singleConfiguration() and
|
||||
any(Configuration config).isAdditionalFlowStep(node1, node2)
|
||||
}
|
||||
|
||||
predicate isAdditionalFlowStep(Node node1, FlowState state1, Node node2, FlowState state2) {
|
||||
getConfig(state1).isAdditionalFlowStep(node1, getState(state1), node2, getState(state2)) and
|
||||
getConfig(state2) = getConfig(state1)
|
||||
or
|
||||
not singleConfiguration() and
|
||||
getConfig(state1).isAdditionalFlowStep(node1, node2) and
|
||||
state2 = state1
|
||||
}
|
||||
|
||||
predicate allowImplicitRead(Node node, ContentSet c) {
|
||||
any(Configuration config).allowImplicitRead(node, c)
|
||||
}
|
||||
|
||||
int fieldFlowBranchLimit() { result = min(any(Configuration config).fieldFlowBranchLimit()) }
|
||||
|
||||
FlowFeature getAFeature() { result = any(Configuration config).getAFeature() }
|
||||
|
||||
predicate sourceGrouping(Node source, string sourceGroup) {
|
||||
any(Configuration config).sourceGrouping(source, sourceGroup)
|
||||
}
|
||||
|
||||
predicate sinkGrouping(Node sink, string sinkGroup) {
|
||||
any(Configuration config).sinkGrouping(sink, sinkGroup)
|
||||
}
|
||||
|
||||
predicate includeHiddenNodes() { any(Configuration config).includeHiddenNodes() }
|
||||
}
|
||||
|
||||
private import Impl<Config> as I
|
||||
import I
|
||||
|
||||
/**
|
||||
* A `Node` augmented with a call context (except for sinks), an access path, and a configuration.
|
||||
* Only those `PathNode`s that are reachable from a source, and which can reach a sink, are generated.
|
||||
*/
|
||||
class PathNode instanceof I::PathNode {
|
||||
/** Gets a textual representation of this element. */
|
||||
final string toString() { result = super.toString() }
|
||||
|
||||
/**
|
||||
* Gets a textual representation of this element, including a textual
|
||||
* representation of the call context.
|
||||
*/
|
||||
final string toStringWithContext() { result = super.toStringWithContext() }
|
||||
|
||||
/**
|
||||
* 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/).
|
||||
*/
|
||||
final predicate hasLocationInfo(
|
||||
string filepath, int startline, int startcolumn, int endline, int endcolumn
|
||||
) {
|
||||
super.hasLocationInfo(filepath, startline, startcolumn, endline, endcolumn)
|
||||
}
|
||||
|
||||
/** Gets the underlying `Node`. */
|
||||
final Node getNode() { result = super.getNode() }
|
||||
|
||||
/** Gets the `FlowState` of this node. */
|
||||
final FlowState getState() { result = getState(super.getState()) }
|
||||
|
||||
/** Gets the associated configuration. */
|
||||
final Configuration getConfiguration() { result = getConfig(super.getState()) }
|
||||
|
||||
/** Gets a successor of this node, if any. */
|
||||
final PathNode getASuccessor() { result = super.getASuccessor() }
|
||||
|
||||
/** Holds if this node is a source. */
|
||||
final predicate isSource() { super.isSource() }
|
||||
|
||||
/** Holds if this node is a grouping of source nodes. */
|
||||
final predicate isSourceGroup(string group) { super.isSourceGroup(group) }
|
||||
|
||||
/** Holds if this node is a grouping of sink nodes. */
|
||||
final predicate isSinkGroup(string group) { super.isSinkGroup(group) }
|
||||
}
|
||||
|
||||
private predicate hasFlow(Node source, Node sink, Configuration config) {
|
||||
exists(PathNode source0, PathNode sink0 |
|
||||
hasFlowPath(source0, sink0, config) and
|
||||
source0.getNode() = source and
|
||||
sink0.getNode() = sink
|
||||
)
|
||||
}
|
||||
|
||||
private predicate hasFlowPath(PathNode source, PathNode sink, Configuration config) {
|
||||
hasFlowPath(source, sink) and source.getConfiguration() = config
|
||||
}
|
||||
|
||||
private predicate hasFlowTo(Node sink, Configuration config) { hasFlow(_, sink, config) }
|
||||
|
||||
predicate flowsTo = hasFlow/3;
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -3,15 +3,18 @@ private import DataFlowImplSpecific::Public
|
||||
import Cached
|
||||
|
||||
module DataFlowImplCommonPublic {
|
||||
/** A state value to track during data flow. */
|
||||
class FlowState = string;
|
||||
/** Provides `FlowState = string`. */
|
||||
module FlowStateString {
|
||||
/** A state value to track during data flow. */
|
||||
class FlowState = string;
|
||||
|
||||
/**
|
||||
* The default state, which is used when the state is unspecified for a source
|
||||
* or a sink.
|
||||
*/
|
||||
class FlowStateEmpty extends FlowState {
|
||||
FlowStateEmpty() { this = "" }
|
||||
/**
|
||||
* The default state, which is used when the state is unspecified for a source
|
||||
* or a sink.
|
||||
*/
|
||||
class FlowStateEmpty extends FlowState {
|
||||
FlowStateEmpty() { this = "" }
|
||||
}
|
||||
}
|
||||
|
||||
private newtype TFlowFeature =
|
||||
@@ -179,6 +182,7 @@ private module LambdaFlow {
|
||||
boolean toJump, DataFlowCallOption lastCall
|
||||
) {
|
||||
revLambdaFlow0(lambdaCall, kind, node, t, toReturn, toJump, lastCall) and
|
||||
not expectsContent(node, _) and
|
||||
if castNode(node) or node instanceof ArgNode or node instanceof ReturnNode
|
||||
then compatibleTypes(t, getNodeDataFlowType(node))
|
||||
else any()
|
||||
|
||||
@@ -18,6 +18,9 @@ module Consistency {
|
||||
/** Holds if `n` should be excluded from the consistency test `uniqueEnclosingCallable`. */
|
||||
predicate uniqueEnclosingCallableExclude(Node n) { none() }
|
||||
|
||||
/** Holds if `call` should be excluded from the consistency test `uniqueCallEnclosingCallable`. */
|
||||
predicate uniqueCallEnclosingCallableExclude(DataFlowCall call) { none() }
|
||||
|
||||
/** Holds if `n` should be excluded from the consistency test `uniqueNodeLocation`. */
|
||||
predicate uniqueNodeLocationExclude(Node n) { none() }
|
||||
|
||||
@@ -86,6 +89,15 @@ module Consistency {
|
||||
)
|
||||
}
|
||||
|
||||
query predicate uniqueCallEnclosingCallable(DataFlowCall call, string msg) {
|
||||
exists(int c |
|
||||
c = count(call.getEnclosingCallable()) and
|
||||
c != 1 and
|
||||
not any(ConsistencyConfiguration conf).uniqueCallEnclosingCallableExclude(call) and
|
||||
msg = "Call should have one enclosing callable but has " + c + "."
|
||||
)
|
||||
}
|
||||
|
||||
query predicate uniqueType(Node n, string msg) {
|
||||
exists(int c |
|
||||
n instanceof RelevantNode and
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -110,7 +110,8 @@ class SyntheticPreUpdateNode extends Node, TSyntheticPreUpdateNode {
|
||||
* func(1, 2, 3)
|
||||
*/
|
||||
class SynthStarArgsElementParameterNode extends ParameterNodeImpl,
|
||||
TSynthStarArgsElementParameterNode {
|
||||
TSynthStarArgsElementParameterNode
|
||||
{
|
||||
DataFlowCallable callable;
|
||||
|
||||
SynthStarArgsElementParameterNode() { this = TSynthStarArgsElementParameterNode(callable) }
|
||||
@@ -181,11 +182,7 @@ private predicate synthDictSplatArgumentNodeStoreStep(
|
||||
private predicate dictSplatParameterNodeClearStep(ParameterNode n, DictionaryElementContent c) {
|
||||
exists(DataFlowCallable callable, ParameterPosition dictSplatPos, ParameterPosition keywordPos |
|
||||
dictSplatPos.isDictSplat() and
|
||||
(
|
||||
n.getParameter() = callable.(DataFlowFunction).getScope().getKwarg()
|
||||
or
|
||||
n = TSummaryParameterNode(callable.asLibraryCallable(), dictSplatPos)
|
||||
) and
|
||||
n = callable.getParameter(dictSplatPos) and
|
||||
exists(callable.getParameter(keywordPos)) and
|
||||
keywordPos.isKeyword(c.getKey())
|
||||
)
|
||||
@@ -236,28 +233,6 @@ class SynthDictSplatParameterNode extends ParameterNodeImpl, TSynthDictSplatPara
|
||||
override Parameter getParameter() { none() }
|
||||
}
|
||||
|
||||
/**
|
||||
* Flow step from the synthetic `**kwargs` parameter to the real `**kwargs` parameter.
|
||||
* Due to restriction in dataflow library, we can only give one of them as result for
|
||||
* `DataFlowCallable.getParameter`, so this is a workaround to ensure there is flow to
|
||||
* _both_ of them.
|
||||
*/
|
||||
private predicate dictSplatParameterNodeFlowStep(
|
||||
ParameterNodeImpl nodeFrom, ParameterNodeImpl nodeTo
|
||||
) {
|
||||
exists(DataFlowCallable callable |
|
||||
nodeFrom = TSynthDictSplatParameterNode(callable) and
|
||||
(
|
||||
nodeTo.getParameter() = callable.(DataFlowFunction).getScope().getKwarg()
|
||||
or
|
||||
exists(ParameterPosition pos |
|
||||
nodeTo = TSummaryParameterNode(callable.asLibraryCallable(), pos) and
|
||||
pos.isDictSplat()
|
||||
)
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads from the synthetic **kwargs parameter to each keyword parameter.
|
||||
*/
|
||||
@@ -403,8 +378,6 @@ predicate simpleLocalFlowStep(Node nodeFrom, Node nodeTo) {
|
||||
simpleLocalFlowStepForTypetracking(nodeFrom, nodeTo)
|
||||
or
|
||||
summaryFlowSteps(nodeFrom, nodeTo)
|
||||
or
|
||||
dictSplatParameterNodeFlowStep(nodeFrom, nodeTo)
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1008,3 +981,12 @@ class ContentApprox = Unit;
|
||||
/** Gets an approximated value for content `c`. */
|
||||
pragma[inline]
|
||||
ContentApprox getContentApprox(Content c) { any() }
|
||||
|
||||
/**
|
||||
* Gets an additional term that is added to the `join` and `branch` computations to reflect
|
||||
* an additional forward or backwards branching factor that is not taken into account
|
||||
* when calculating the (virtual) dispatch cost.
|
||||
*
|
||||
* Argument `arg` is part of a path from a source to a sink, and `p` is the target parameter.
|
||||
*/
|
||||
int getAdditionalFlowIntoCallNodeTerm(ArgumentNode arg, ParameterNode p) { none() }
|
||||
|
||||
@@ -301,8 +301,8 @@ module Private {
|
||||
TWithoutContentSummaryComponent(ContentSet c) or
|
||||
TWithContentSummaryComponent(ContentSet c)
|
||||
|
||||
private TParameterSummaryComponent thisParam() {
|
||||
result = TParameterSummaryComponent(instanceParameterPosition())
|
||||
private TParameterSummaryComponent callbackSelfParam() {
|
||||
result = TParameterSummaryComponent(callbackSelfParameterPosition())
|
||||
}
|
||||
|
||||
newtype TSummaryComponentStack =
|
||||
@@ -311,7 +311,7 @@ module Private {
|
||||
any(RequiredSummaryComponentStack x).required(head, tail)
|
||||
or
|
||||
any(RequiredSummaryComponentStack x).required(TParameterSummaryComponent(_), tail) and
|
||||
head = thisParam()
|
||||
head = callbackSelfParam()
|
||||
or
|
||||
derivedFluentFlowPush(_, _, _, head, tail, _)
|
||||
}
|
||||
@@ -336,7 +336,7 @@ module Private {
|
||||
callbackRef = s.drop(_) and
|
||||
(isCallbackParameter(callbackRef) or callbackRef.head() = TReturnSummaryComponent(_)) and
|
||||
input = callbackRef.tail() and
|
||||
output = TConsSummaryComponentStack(thisParam(), input) and
|
||||
output = TConsSummaryComponentStack(callbackSelfParam(), input) and
|
||||
preservesValue = true
|
||||
)
|
||||
or
|
||||
@@ -439,6 +439,9 @@ module Private {
|
||||
out.head() = TParameterSummaryComponent(_) and
|
||||
s = out.tail()
|
||||
)
|
||||
or
|
||||
// Add the post-update node corresponding to the requested argument node
|
||||
outputState(c, s) and isCallbackParameter(s)
|
||||
}
|
||||
|
||||
private newtype TSummaryNodeState =
|
||||
@@ -1012,7 +1015,7 @@ module Private {
|
||||
private predicate relevantSummaryElementGenerated(
|
||||
AccessPath inSpec, AccessPath outSpec, string kind
|
||||
) {
|
||||
summaryElement(this, inSpec, outSpec, kind, "generated") and
|
||||
summaryElement(this, inSpec, outSpec, kind, ["generated", "ai-generated"]) and
|
||||
not summaryElement(this, _, _, _, "manual")
|
||||
}
|
||||
|
||||
|
||||
@@ -45,7 +45,7 @@ class SummarizedCallableBase = string;
|
||||
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
|
||||
ArgumentPosition callbackSelfParameterPosition() { 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) }
|
||||
|
||||
@@ -0,0 +1,64 @@
|
||||
/**
|
||||
* Provides classes for performing local (intra-procedural) and
|
||||
* global (inter-procedural) taint-tracking analyses.
|
||||
*/
|
||||
|
||||
import TaintTrackingParameter::Public
|
||||
private import TaintTrackingParameter::Private
|
||||
|
||||
private module AddTaintDefaults<DataFlowInternal::FullStateConfigSig Config> implements
|
||||
DataFlowInternal::FullStateConfigSig
|
||||
{
|
||||
import Config
|
||||
|
||||
predicate isBarrier(DataFlow::Node node) {
|
||||
Config::isBarrier(node) or defaultTaintSanitizer(node)
|
||||
}
|
||||
|
||||
predicate isAdditionalFlowStep(DataFlow::Node node1, DataFlow::Node node2) {
|
||||
Config::isAdditionalFlowStep(node1, node2) or
|
||||
defaultAdditionalTaintStep(node1, node2)
|
||||
}
|
||||
|
||||
predicate allowImplicitRead(DataFlow::Node node, DataFlow::ContentSet c) {
|
||||
Config::allowImplicitRead(node, c)
|
||||
or
|
||||
(
|
||||
Config::isSink(node, _) or
|
||||
Config::isAdditionalFlowStep(node, _) or
|
||||
Config::isAdditionalFlowStep(node, _, _, _)
|
||||
) and
|
||||
defaultImplicitTaintRead(node, c)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs a standard taint tracking computation.
|
||||
*/
|
||||
module Make<DataFlow::ConfigSig Config> implements DataFlow::DataFlowSig {
|
||||
private module Config0 implements DataFlowInternal::FullStateConfigSig {
|
||||
import DataFlowInternal::DefaultState<Config>
|
||||
import Config
|
||||
}
|
||||
|
||||
private module C implements DataFlowInternal::FullStateConfigSig {
|
||||
import AddTaintDefaults<Config0>
|
||||
}
|
||||
|
||||
import DataFlowInternal::Impl<C>
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs a taint tracking computation using flow state.
|
||||
*/
|
||||
module MakeWithState<DataFlow::StateConfigSig Config> implements DataFlow::DataFlowSig {
|
||||
private module Config0 implements DataFlowInternal::FullStateConfigSig {
|
||||
import Config
|
||||
}
|
||||
|
||||
private module C implements DataFlowInternal::FullStateConfigSig {
|
||||
import AddTaintDefaults<Config0>
|
||||
}
|
||||
|
||||
import DataFlowInternal::Impl<C>
|
||||
}
|
||||
@@ -2,5 +2,6 @@ import semmle.python.dataflow.new.internal.TaintTrackingPublic as Public
|
||||
|
||||
module Private {
|
||||
import semmle.python.dataflow.new.DataFlow::DataFlow as DataFlow
|
||||
import semmle.python.dataflow.new.internal.DataFlowImpl as DataFlowInternal
|
||||
import semmle.python.dataflow.new.internal.TaintTrackingPrivate
|
||||
}
|
||||
|
||||
@@ -59,7 +59,8 @@ 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 instanceof AiohttpRouteSetup::Range {
|
||||
class AiohttpRouteSetup extends Http::Server::RouteSetup::Range instanceof AiohttpRouteSetup::Range
|
||||
{
|
||||
override Parameter getARoutedParameter() { none() }
|
||||
|
||||
override string getFramework() { result = "aiohttp.web" }
|
||||
@@ -252,7 +253,8 @@ 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)
|
||||
@@ -440,7 +442,8 @@ module AiohttpWebModel {
|
||||
* handler is invoked.
|
||||
*/
|
||||
class AiohttpRequestHandlerRequestParam extends Request::InstanceSource, RemoteFlowSource::Range,
|
||||
DataFlow::ParameterNode {
|
||||
DataFlow::ParameterNode
|
||||
{
|
||||
AiohttpRequestHandlerRequestParam() {
|
||||
exists(Function requestHandler |
|
||||
requestHandler = any(AiohttpCoroutineRouteSetup setup).getARequestHandler() and
|
||||
@@ -470,7 +473,8 @@ module AiohttpWebModel {
|
||||
* which is the request being processed currently.
|
||||
*/
|
||||
class AiohttpViewClassRequestAttributeRead extends Request::InstanceSource,
|
||||
RemoteFlowSource::Range, DataFlow::Node {
|
||||
RemoteFlowSource::Range, DataFlow::Node
|
||||
{
|
||||
AiohttpViewClassRequestAttributeRead() {
|
||||
this.(DataFlow::AttrRead).getObject() = any(AiohttpViewClass vc).getASelfRef() and
|
||||
this.(DataFlow::AttrRead).getAttributeName() = "request"
|
||||
@@ -494,7 +498,8 @@ module AiohttpWebModel {
|
||||
* - https://docs.aiohttp.org/en/stable/web_quickstart.html#aiohttp-web-exceptions
|
||||
*/
|
||||
class AiohttpWebResponseInstantiation extends Http::Server::HttpResponse::Range,
|
||||
Response::InstanceSource, DataFlow::CallCfgNode {
|
||||
Response::InstanceSource, DataFlow::CallCfgNode
|
||||
{
|
||||
API::Node apiNode;
|
||||
|
||||
AiohttpWebResponseInstantiation() {
|
||||
@@ -562,7 +567,8 @@ 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 +591,8 @@ 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 +607,8 @@ 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()
|
||||
}
|
||||
|
||||
@@ -23,7 +23,8 @@ private module CryptodomeModel {
|
||||
* See https://pycryptodome.readthedocs.io/en/latest/src/public_key/rsa.html#Crypto.PublicKey.RSA.generate
|
||||
*/
|
||||
class CryptodomePublicKeyRsaGenerateCall extends Cryptography::PublicKey::KeyGeneration::RsaRange,
|
||||
DataFlow::CallCfgNode {
|
||||
DataFlow::CallCfgNode
|
||||
{
|
||||
CryptodomePublicKeyRsaGenerateCall() {
|
||||
this =
|
||||
API::moduleImport(["Crypto", "Cryptodome"])
|
||||
@@ -44,7 +45,8 @@ private module CryptodomeModel {
|
||||
* See https://pycryptodome.readthedocs.io/en/latest/src/public_key/dsa.html#Crypto.PublicKey.DSA.generate
|
||||
*/
|
||||
class CryptodomePublicKeyDsaGenerateCall extends Cryptography::PublicKey::KeyGeneration::DsaRange,
|
||||
DataFlow::CallCfgNode {
|
||||
DataFlow::CallCfgNode
|
||||
{
|
||||
CryptodomePublicKeyDsaGenerateCall() {
|
||||
this =
|
||||
API::moduleImport(["Crypto", "Cryptodome"])
|
||||
@@ -65,7 +67,8 @@ private module CryptodomeModel {
|
||||
* See https://pycryptodome.readthedocs.io/en/latest/src/public_key/ecc.html#Crypto.PublicKey.ECC.generate
|
||||
*/
|
||||
class CryptodomePublicKeyEccGenerateCall extends Cryptography::PublicKey::KeyGeneration::EccRange,
|
||||
DataFlow::CallCfgNode {
|
||||
DataFlow::CallCfgNode
|
||||
{
|
||||
CryptodomePublicKeyEccGenerateCall() {
|
||||
this =
|
||||
API::moduleImport(["Crypto", "Cryptodome"])
|
||||
@@ -105,7 +108,8 @@ private module CryptodomeModel {
|
||||
* A cryptographic operation on an instance from the `Cipher` subpackage of `Cryptodome`/`Crypto`.
|
||||
*/
|
||||
class CryptodomeGenericCipherOperation extends Cryptography::CryptographicOperation::Range,
|
||||
DataFlow::CallCfgNode {
|
||||
DataFlow::CallCfgNode
|
||||
{
|
||||
string methodName;
|
||||
string cipherName;
|
||||
API::CallNode newCall;
|
||||
@@ -175,7 +179,8 @@ private module CryptodomeModel {
|
||||
* A cryptographic operation on an instance from the `Signature` subpackage of `Cryptodome`/`Crypto`.
|
||||
*/
|
||||
class CryptodomeGenericSignatureOperation extends Cryptography::CryptographicOperation::Range,
|
||||
DataFlow::CallCfgNode {
|
||||
DataFlow::CallCfgNode
|
||||
{
|
||||
string methodName;
|
||||
string signatureName;
|
||||
|
||||
@@ -214,7 +219,8 @@ private module CryptodomeModel {
|
||||
* A cryptographic operation on an instance from the `Hash` subpackage of `Cryptodome`/`Crypto`.
|
||||
*/
|
||||
class CryptodomeGenericHashOperation extends Cryptography::CryptographicOperation::Range,
|
||||
DataFlow::CallCfgNode {
|
||||
DataFlow::CallCfgNode
|
||||
{
|
||||
string hashName;
|
||||
|
||||
CryptodomeGenericHashOperation() {
|
||||
|
||||
@@ -82,7 +82,8 @@ private module CryptographyModel {
|
||||
* See https://cryptography.io/en/latest/hazmat/primitives/asymmetric/rsa.html#cryptography.hazmat.primitives.asymmetric.rsa.generate_private_key
|
||||
*/
|
||||
class CryptographyRsaGeneratePrivateKeyCall extends Cryptography::PublicKey::KeyGeneration::RsaRange,
|
||||
DataFlow::CallCfgNode {
|
||||
DataFlow::CallCfgNode
|
||||
{
|
||||
CryptographyRsaGeneratePrivateKeyCall() {
|
||||
this =
|
||||
API::moduleImport("cryptography")
|
||||
@@ -105,7 +106,8 @@ private module CryptographyModel {
|
||||
* See https://cryptography.io/en/latest/hazmat/primitives/asymmetric/dsa.html#cryptography.hazmat.primitives.asymmetric.dsa.generate_private_key
|
||||
*/
|
||||
class CryptographyDsaGeneratePrivateKeyCall extends Cryptography::PublicKey::KeyGeneration::DsaRange,
|
||||
DataFlow::CallCfgNode {
|
||||
DataFlow::CallCfgNode
|
||||
{
|
||||
CryptographyDsaGeneratePrivateKeyCall() {
|
||||
this =
|
||||
API::moduleImport("cryptography")
|
||||
@@ -128,7 +130,8 @@ private module CryptographyModel {
|
||||
* See https://cryptography.io/en/latest/hazmat/primitives/asymmetric/ec.html#cryptography.hazmat.primitives.asymmetric.ec.generate_private_key
|
||||
*/
|
||||
class CryptographyEcGeneratePrivateKeyCall extends Cryptography::PublicKey::KeyGeneration::EccRange,
|
||||
DataFlow::CallCfgNode {
|
||||
DataFlow::CallCfgNode
|
||||
{
|
||||
CryptographyEcGeneratePrivateKeyCall() {
|
||||
this =
|
||||
API::moduleImport("cryptography")
|
||||
@@ -204,7 +207,8 @@ private module CryptographyModel {
|
||||
* An encrypt or decrypt operation from `cryptography.hazmat.primitives.ciphers`.
|
||||
*/
|
||||
class CryptographyGenericCipherOperation extends Cryptography::CryptographicOperation::Range,
|
||||
DataFlow::MethodCallNode {
|
||||
DataFlow::MethodCallNode
|
||||
{
|
||||
string algorithmName;
|
||||
string modeName;
|
||||
|
||||
@@ -262,7 +266,8 @@ private module CryptographyModel {
|
||||
* An hashing operation from `cryptography.hazmat.primitives.hashes`.
|
||||
*/
|
||||
class CryptographyGenericHashOperation extends Cryptography::CryptographicOperation::Range,
|
||||
DataFlow::MethodCallNode {
|
||||
DataFlow::MethodCallNode
|
||||
{
|
||||
string algorithmName;
|
||||
|
||||
CryptographyGenericHashOperation() {
|
||||
|
||||
@@ -1271,7 +1271,8 @@ module PrivateDjango {
|
||||
}
|
||||
|
||||
/** An attribute read on an django request that is a `MultiValueDict` instance. */
|
||||
private class DjangoHttpRequestMultiValueDictInstances extends Django::MultiValueDict::InstanceSource {
|
||||
private class DjangoHttpRequestMultiValueDictInstances extends Django::MultiValueDict::InstanceSource
|
||||
{
|
||||
DjangoHttpRequestMultiValueDictInstances() {
|
||||
this.(DataFlow::AttrRead).getObject() = instance() and
|
||||
this.(DataFlow::AttrRead).getAttributeName() in ["GET", "POST", "FILES"]
|
||||
@@ -1279,7 +1280,8 @@ module PrivateDjango {
|
||||
}
|
||||
|
||||
/** An attribute read on an django request that is a `ResolverMatch` instance. */
|
||||
private class DjangoHttpRequestResolverMatchInstances extends Django::ResolverMatch::InstanceSource {
|
||||
private class DjangoHttpRequestResolverMatchInstances extends Django::ResolverMatch::InstanceSource
|
||||
{
|
||||
DjangoHttpRequestResolverMatchInstances() {
|
||||
this.(DataFlow::AttrRead).getObject() = instance() and
|
||||
this.(DataFlow::AttrRead).getAttributeName() = "resolver_match"
|
||||
@@ -1287,7 +1289,8 @@ module PrivateDjango {
|
||||
}
|
||||
|
||||
/** An `UploadedFile` instance that originates from a django request. */
|
||||
private class DjangoHttpRequestUploadedFileInstances extends Django::UploadedFile::InstanceSource {
|
||||
private class DjangoHttpRequestUploadedFileInstances extends Django::UploadedFile::InstanceSource
|
||||
{
|
||||
DjangoHttpRequestUploadedFileInstances() {
|
||||
// TODO: this currently only works in local-scope, since writing type-trackers for
|
||||
// this is a little too much effort. Once API-graphs are available for more
|
||||
@@ -1421,7 +1424,8 @@ 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 +1487,8 @@ 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 {
|
||||
@@ -2086,7 +2091,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 {
|
||||
class HttpResponseWriteCall extends Http::Server::HttpResponse::Range, DataFlow::CallCfgNode
|
||||
{
|
||||
DjangoImpl::DjangoHttp::Response::HttpResponse::InstanceSource instance;
|
||||
|
||||
HttpResponseWriteCall() { this.getFunction() = write(instance) }
|
||||
@@ -2106,7 +2112,8 @@ module PrivateDjango {
|
||||
* A call to `set_cookie` on a HTTP Response.
|
||||
*/
|
||||
class DjangoResponseSetCookieCall extends Http::Server::CookieWrite::Range,
|
||||
DataFlow::MethodCallNode {
|
||||
DataFlow::MethodCallNode
|
||||
{
|
||||
DjangoResponseSetCookieCall() {
|
||||
this.calls(DjangoImpl::DjangoHttp::Response::HttpResponse::instance(), "set_cookie")
|
||||
}
|
||||
@@ -2126,7 +2133,8 @@ module PrivateDjango {
|
||||
* A call to `delete_cookie` on a HTTP Response.
|
||||
*/
|
||||
class DjangoResponseDeleteCookieCall extends Http::Server::CookieWrite::Range,
|
||||
DataFlow::MethodCallNode {
|
||||
DataFlow::MethodCallNode
|
||||
{
|
||||
DjangoResponseDeleteCookieCall() {
|
||||
this.calls(DjangoImpl::DjangoHttp::Response::HttpResponse::instance(), "delete_cookie")
|
||||
}
|
||||
@@ -2429,7 +2437,8 @@ module PrivateDjango {
|
||||
|
||||
/** A request handler defined in a django view class, that has no known route. */
|
||||
private class DjangoViewClassHandlerWithoutKnownRoute extends Http::Server::RequestHandler::Range,
|
||||
DjangoRouteHandler {
|
||||
DjangoRouteHandler
|
||||
{
|
||||
DjangoViewClassHandlerWithoutKnownRoute() {
|
||||
exists(DjangoViewClass vc | vc.getARequestHandler() = this) and
|
||||
not exists(DjangoRouteSetup setup | setup.getARequestHandler() = this)
|
||||
@@ -2587,7 +2596,8 @@ module PrivateDjango {
|
||||
// ---------------------------------------------------------------------------
|
||||
/** A parameter that will receive the django `HttpRequest` instance when a request handler is invoked. */
|
||||
private class DjangoRequestHandlerRequestParam extends DjangoImpl::DjangoHttp::Request::HttpRequest::InstanceSource,
|
||||
RemoteFlowSource::Range, DataFlow::ParameterNode {
|
||||
RemoteFlowSource::Range, DataFlow::ParameterNode
|
||||
{
|
||||
DjangoRequestHandlerRequestParam() {
|
||||
this.getParameter() = any(DjangoRouteSetup setup).getARequestHandler().getRequestParam()
|
||||
or
|
||||
@@ -2604,7 +2614,8 @@ module PrivateDjango {
|
||||
* See https://docs.djangoproject.com/en/3.1/topics/class-based-views/generic-display/#dynamic-filtering
|
||||
*/
|
||||
private class DjangoViewClassRequestAttributeRead extends DjangoImpl::DjangoHttp::Request::HttpRequest::InstanceSource,
|
||||
RemoteFlowSource::Range, DataFlow::Node {
|
||||
RemoteFlowSource::Range, DataFlow::Node
|
||||
{
|
||||
DjangoViewClassRequestAttributeRead() {
|
||||
exists(DataFlow::AttrRead read | this = read |
|
||||
read.getObject() = any(DjangoViewClass vc).getASelfRef() and
|
||||
@@ -2624,7 +2635,8 @@ module PrivateDjango {
|
||||
* See https://docs.djangoproject.com/en/3.1/topics/class-based-views/generic-display/#dynamic-filtering
|
||||
*/
|
||||
private class DjangoViewClassRoutedParamsAttributeRead extends RemoteFlowSource::Range,
|
||||
DataFlow::Node {
|
||||
DataFlow::Node
|
||||
{
|
||||
DjangoViewClassRoutedParamsAttributeRead() {
|
||||
exists(DataFlow::AttrRead read | this = read |
|
||||
read.getObject() = any(DjangoViewClass vc).getASelfRef() and
|
||||
@@ -2652,7 +2664,8 @@ module PrivateDjango {
|
||||
* - https://docs.djangoproject.com/en/3.1/topics/http/file-uploads/#handling-uploaded-files-with-a-model
|
||||
*/
|
||||
private class DjangoFileFieldUploadToFunctionFilenameParam extends RemoteFlowSource::Range,
|
||||
DataFlow::ParameterNode {
|
||||
DataFlow::ParameterNode
|
||||
{
|
||||
DjangoFileFieldUploadToFunctionFilenameParam() {
|
||||
exists(DataFlow::CallCfgNode call, DataFlow::Node uploadToArg, Function func |
|
||||
this.getParameter() = func.getArg(1) and
|
||||
@@ -2679,7 +2692,8 @@ module PrivateDjango {
|
||||
* See https://docs.djangoproject.com/en/3.1/topics/http/shortcuts/#redirect
|
||||
*/
|
||||
private class DjangoShortcutsRedirectCall extends Http::Server::HttpRedirectResponse::Range,
|
||||
DataFlow::CallCfgNode {
|
||||
DataFlow::CallCfgNode
|
||||
{
|
||||
DjangoShortcutsRedirectCall() { this = DjangoImpl::Shortcuts::redirect().getACall() }
|
||||
|
||||
/**
|
||||
@@ -2713,7 +2727,8 @@ module PrivateDjango {
|
||||
* See https://docs.djangoproject.com/en/3.1/ref/class-based-views/base/#redirectview
|
||||
*/
|
||||
private class DjangoRedirectViewGetRedirectUrlReturn extends Http::Server::HttpRedirectResponse::Range,
|
||||
DataFlow::CfgNode {
|
||||
DataFlow::CfgNode
|
||||
{
|
||||
DjangoRedirectViewGetRedirectUrlReturn() {
|
||||
node = any(GetRedirectUrlFunction f).getAReturnValueFlowNode()
|
||||
}
|
||||
|
||||
@@ -43,13 +43,22 @@ private module FabricV1 {
|
||||
* - https://docs.fabfile.org/en/1.14/api/core/operations.html#fabric.operations.run
|
||||
* - https://docs.fabfile.org/en/1.14/api/core/operations.html#fabric.operations.sudo
|
||||
*/
|
||||
private class FabricApiLocalRunSudoCall extends SystemCommandExecution::Range,
|
||||
DataFlow::CallCfgNode {
|
||||
private class FabricApiLocalRunSudoCall extends SystemCommandExecution::Range, API::CallNode {
|
||||
FabricApiLocalRunSudoCall() { this = api().getMember(["local", "run", "sudo"]).getACall() }
|
||||
|
||||
override DataFlow::Node getCommand() {
|
||||
result = [this.getArg(0), this.getArgByName("command")]
|
||||
}
|
||||
|
||||
override predicate isShellInterpreted(DataFlow::Node arg) {
|
||||
arg = this.getCommand() and
|
||||
// defaults to running in a shell
|
||||
not this.getParameter(1, "shell")
|
||||
.getAValueReachingSink()
|
||||
.asExpr()
|
||||
.(ImmutableLiteral)
|
||||
.booleanValue() = false
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -153,7 +162,8 @@ private module FabricV2 {
|
||||
* - https://docs.fabfile.org/en/2.5/api/connection.html#fabric.connection.Connection.local
|
||||
*/
|
||||
private class FabricConnectionRunSudoLocalCall extends SystemCommandExecution::Range,
|
||||
DataFlow::CallCfgNode {
|
||||
DataFlow::CallCfgNode
|
||||
{
|
||||
FabricConnectionRunSudoLocalCall() {
|
||||
this.getFunction() = Fabric::Connection::ConnectionClass::instanceRunMethods()
|
||||
}
|
||||
@@ -161,6 +171,8 @@ private module FabricV2 {
|
||||
override DataFlow::Node getCommand() {
|
||||
result = [this.getArg(0), this.getArgByName("command")]
|
||||
}
|
||||
|
||||
override predicate isShellInterpreted(DataFlow::Node arg) { arg = this.getCommand() }
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
@@ -176,7 +188,8 @@ private module FabricV2 {
|
||||
}
|
||||
|
||||
class FabricTaskFirstParamConnectionInstance extends Fabric::Connection::ConnectionClass::InstanceSource,
|
||||
DataFlow::ParameterNode {
|
||||
DataFlow::ParameterNode
|
||||
{
|
||||
FabricTaskFirstParamConnectionInstance() {
|
||||
exists(Function func |
|
||||
func.getADecorator() = Fabric::Tasks::task().getAValueReachableFromSource().asExpr() and
|
||||
@@ -243,6 +256,8 @@ private module FabricV2 {
|
||||
override DataFlow::Node getCommand() {
|
||||
result = [this.getArg(0), this.getArgByName("command")]
|
||||
}
|
||||
|
||||
override predicate isShellInterpreted(DataFlow::Node arg) { arg = this.getCommand() }
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -88,7 +88,8 @@ private module FastApi {
|
||||
* Pydantic model.
|
||||
*/
|
||||
private class PydanticModelRequestHandlerParam extends Pydantic::BaseModel::InstanceSource,
|
||||
DataFlow::ParameterNode {
|
||||
DataFlow::ParameterNode
|
||||
{
|
||||
PydanticModelRequestHandlerParam() {
|
||||
this.getParameter().getAnnotation() =
|
||||
Pydantic::BaseModel::subclassRef().getAValueReachableFromSource().asExpr() and
|
||||
@@ -103,7 +104,8 @@ private module FastApi {
|
||||
* A parameter to a request handler that has a WebSocket type-annotation.
|
||||
*/
|
||||
private class WebSocketRequestHandlerParam extends Starlette::WebSocket::InstanceSource,
|
||||
DataFlow::ParameterNode {
|
||||
DataFlow::ParameterNode
|
||||
{
|
||||
WebSocketRequestHandlerParam() {
|
||||
this.getParameter().getAnnotation() =
|
||||
Starlette::WebSocket::classRef().getAValueReachableFromSource().asExpr() and
|
||||
@@ -196,7 +198,8 @@ private module FastApi {
|
||||
|
||||
/** A direct instantiation of a response class. */
|
||||
private class ResponseInstantiation extends InstanceSource, Http::Server::HttpResponse::Range,
|
||||
DataFlow::CallCfgNode {
|
||||
DataFlow::CallCfgNode
|
||||
{
|
||||
API::Node baseApiNode;
|
||||
API::Node responseClass;
|
||||
|
||||
@@ -223,7 +226,8 @@ 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() {
|
||||
@@ -246,7 +250,8 @@ private module FastApi {
|
||||
* An implicit response from a return of FastAPI request handler.
|
||||
*/
|
||||
private class FastApiRequestHandlerReturn extends Http::Server::HttpResponse::Range,
|
||||
DataFlow::CfgNode {
|
||||
DataFlow::CfgNode
|
||||
{
|
||||
FastApiRouteSetup routeSetup;
|
||||
|
||||
FastApiRequestHandlerReturn() {
|
||||
@@ -273,7 +278,8 @@ private module FastApi {
|
||||
* `response_class` set to a `FileResponse`.
|
||||
*/
|
||||
private class FastApiRequestHandlerFileResponseReturn extends FastApiRequestHandlerReturn,
|
||||
FileSystemAccess::Range {
|
||||
FileSystemAccess::Range
|
||||
{
|
||||
FastApiRequestHandlerFileResponseReturn() {
|
||||
exists(API::Node responseClass |
|
||||
responseClass.getAValueReachableFromSource() = routeSetup.getResponseClassArg() and
|
||||
@@ -291,7 +297,8 @@ 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
|
||||
@@ -349,7 +356,8 @@ private module FastApi {
|
||||
* header-key.
|
||||
*/
|
||||
private class HeadersAppendCookie extends Http::Server::CookieWrite::Range,
|
||||
DataFlow::MethodCallNode {
|
||||
DataFlow::MethodCallNode
|
||||
{
|
||||
HeadersAppendCookie() {
|
||||
exists(DataFlow::AttrRead headers, DataFlow::Node keyArg |
|
||||
headers.accesses(instance(), "headers") and
|
||||
|
||||
@@ -447,7 +447,8 @@ 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
|
||||
@@ -471,7 +472,8 @@ module Flask {
|
||||
* See https://flask.palletsprojects.com/en/1.1.x/api/#flask.redirect
|
||||
*/
|
||||
private class FlaskRedirectCall extends Http::Server::HttpRedirectResponse::Range,
|
||||
DataFlow::CallCfgNode {
|
||||
DataFlow::CallCfgNode
|
||||
{
|
||||
FlaskRedirectCall() { this = API::moduleImport("flask").getMember("redirect").getACall() }
|
||||
|
||||
override DataFlow::Node getRedirectLocation() {
|
||||
@@ -499,7 +501,8 @@ module Flask {
|
||||
* See https://flask.palletsprojects.com/en/2.0.x/api/#flask.Response.set_cookie
|
||||
*/
|
||||
class FlaskResponseSetCookieCall extends Http::Server::CookieWrite::Range,
|
||||
DataFlow::MethodCallNode {
|
||||
DataFlow::MethodCallNode
|
||||
{
|
||||
FlaskResponseSetCookieCall() { this.calls(Flask::Response::instance(), "set_cookie") }
|
||||
|
||||
override DataFlow::Node getHeaderArg() { none() }
|
||||
@@ -515,7 +518,8 @@ module Flask {
|
||||
* See https://flask.palletsprojects.com/en/2.0.x/api/#flask.Response.delete_cookie
|
||||
*/
|
||||
class FlaskResponseDeleteCookieCall extends Http::Server::CookieWrite::Range,
|
||||
DataFlow::MethodCallNode {
|
||||
DataFlow::MethodCallNode
|
||||
{
|
||||
FlaskResponseDeleteCookieCall() { this.calls(Flask::Response::instance(), "delete_cookie") }
|
||||
|
||||
override DataFlow::Node getHeaderArg() { none() }
|
||||
|
||||
@@ -81,5 +81,7 @@ private module Invoke {
|
||||
override DataFlow::Node getCommand() {
|
||||
result in [this.getArg(0), this.getArgByName("command")]
|
||||
}
|
||||
|
||||
override predicate isShellInterpreted(DataFlow::Node arg) { arg = this.getCommand() }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -307,7 +307,8 @@ private module Lxml {
|
||||
* - https://lxml.de/apidoc/lxml.etree.html?highlight=parseids#lxml.etree.iterparse
|
||||
*/
|
||||
private class LxmlIterparseCall extends API::CallNode, XML::XmlParsing::Range,
|
||||
FileSystemAccess::Range {
|
||||
FileSystemAccess::Range
|
||||
{
|
||||
LxmlIterparseCall() {
|
||||
this = API::moduleImport("lxml").getMember("etree").getMember("iterparse").getACall()
|
||||
}
|
||||
|
||||
@@ -101,7 +101,8 @@ private module MarkupSafeModel {
|
||||
|
||||
/** A call to any of the escaping functions in `markupsafe` */
|
||||
private class MarkupSafeEscapeCall extends Markup::InstanceSource, MarkupSafeEscape,
|
||||
DataFlow::CallCfgNode {
|
||||
DataFlow::CallCfgNode
|
||||
{
|
||||
MarkupSafeEscapeCall() {
|
||||
this = API::moduleImport("markupsafe").getMember(["escape", "escape_silent"]).getACall()
|
||||
or
|
||||
@@ -141,7 +142,8 @@ private module MarkupSafeModel {
|
||||
|
||||
/** A escape from %-style string format with `markupsafe.Markup` as the format string. */
|
||||
private class MarkupEscapeFromPercentStringFormat extends MarkupSafeEscape,
|
||||
Markup::PercentStringFormat {
|
||||
Markup::PercentStringFormat
|
||||
{
|
||||
override DataFlow::Node getAnInput() {
|
||||
result.asCfgNode() = node.getRight() and
|
||||
not result = Markup::instance()
|
||||
|
||||
@@ -164,7 +164,8 @@ private module Peewee {
|
||||
* https://docs.peewee-orm.com/en/latest/peewee/api.html#Database.connection.
|
||||
*/
|
||||
class PeeweeDatabaseConnectionCall extends PEP249::Connection::InstanceSource,
|
||||
DataFlow::CallCfgNode {
|
||||
DataFlow::CallCfgNode
|
||||
{
|
||||
PeeweeDatabaseConnectionCall() {
|
||||
this = Database::instance().getMember("connection").getACall()
|
||||
}
|
||||
|
||||
@@ -159,7 +159,8 @@ private module RestFramework {
|
||||
* known route setup.
|
||||
*/
|
||||
class RestFrameworkFunctionBasedViewWithoutKnownRoute extends Http::Server::RequestHandler::Range,
|
||||
PrivateDjango::DjangoRouteHandler instanceof RestFrameworkFunctionBasedView {
|
||||
PrivateDjango::DjangoRouteHandler instanceof RestFrameworkFunctionBasedView
|
||||
{
|
||||
RestFrameworkFunctionBasedViewWithoutKnownRoute() {
|
||||
not exists(PrivateDjango::DjangoRouteSetup setup | setup.getARequestHandler() = this)
|
||||
}
|
||||
@@ -183,7 +184,8 @@ private module RestFramework {
|
||||
* request handler is invoked.
|
||||
*/
|
||||
private class RestFrameworkRequestHandlerRequestParam extends Request::InstanceSource,
|
||||
RemoteFlowSource::Range, DataFlow::ParameterNode {
|
||||
RemoteFlowSource::Range, DataFlow::ParameterNode
|
||||
{
|
||||
RestFrameworkRequestHandlerRequestParam() {
|
||||
// rest_framework.views.APIView subclass
|
||||
exists(RestFrameworkApiViewClass vc |
|
||||
@@ -220,8 +222,8 @@ private module RestFramework {
|
||||
*
|
||||
* Use the predicate `Request::instance()` to get references to instances of `rest_framework.request.Request`.
|
||||
*/
|
||||
abstract class InstanceSource extends PrivateDjango::DjangoImpl::DjangoHttp::Request::HttpRequest::InstanceSource {
|
||||
}
|
||||
abstract class InstanceSource extends PrivateDjango::DjangoImpl::DjangoHttp::Request::HttpRequest::InstanceSource
|
||||
{ }
|
||||
|
||||
/** A direct instantiation of `rest_framework.request.Request`. */
|
||||
private class ClassInstantiation extends InstanceSource, DataFlow::CallCfgNode {
|
||||
@@ -297,7 +299,8 @@ private module RestFramework {
|
||||
|
||||
/** A direct instantiation of `rest_framework.response.Response`. */
|
||||
private class ClassInstantiation extends PrivateDjango::DjangoImpl::DjangoHttp::Response::HttpResponse::InstanceSource,
|
||||
DataFlow::CallCfgNode {
|
||||
DataFlow::CallCfgNode
|
||||
{
|
||||
ClassInstantiation() { this = classRef().getACall() }
|
||||
|
||||
override DataFlow::Node getBody() { result in [this.getArg(0), this.getArgByName("data")] }
|
||||
@@ -321,7 +324,8 @@ private module RestFramework {
|
||||
module ApiException {
|
||||
/** A direct instantiation of `rest_framework.exceptions.ApiException` or subclass. */
|
||||
private class ClassInstantiation extends Http::Server::HttpResponse::Range,
|
||||
DataFlow::CallCfgNode {
|
||||
DataFlow::CallCfgNode
|
||||
{
|
||||
string className;
|
||||
|
||||
ClassInstantiation() {
|
||||
|
||||
@@ -20,7 +20,8 @@ private module Rsa {
|
||||
* See https://stuvel.eu/python-rsa-doc/reference.html#rsa.newkeys
|
||||
*/
|
||||
class RsaNewkeysCall extends Cryptography::PublicKey::KeyGeneration::RsaRange,
|
||||
DataFlow::CallCfgNode {
|
||||
DataFlow::CallCfgNode
|
||||
{
|
||||
RsaNewkeysCall() { this = API::moduleImport("rsa").getMember("newkeys").getACall() }
|
||||
|
||||
override DataFlow::Node getKeySizeArg() {
|
||||
@@ -116,7 +117,8 @@ private module Rsa {
|
||||
* See https://stuvel.eu/python-rsa-doc/reference.html#rsa.compute_hash
|
||||
*/
|
||||
class RsaComputeHashCall extends Cryptography::CryptographicOperation::Range,
|
||||
DataFlow::CallCfgNode {
|
||||
DataFlow::CallCfgNode
|
||||
{
|
||||
RsaComputeHashCall() { this = API::moduleImport("rsa").getMember("compute_hash").getACall() }
|
||||
|
||||
override Cryptography::CryptographicAlgorithm getAlgorithm() {
|
||||
|
||||
74
python/ql/lib/semmle/python/frameworks/Setuptools.qll
Normal file
74
python/ql/lib/semmle/python/frameworks/Setuptools.qll
Normal file
@@ -0,0 +1,74 @@
|
||||
/**
|
||||
* Provides classes modeling package setup as defined by `setuptools`.
|
||||
*/
|
||||
|
||||
private import python
|
||||
private import semmle.python.dataflow.new.DataFlow
|
||||
|
||||
/** Provides models for the use of `setuptools` in setup scripts, and the APIs exported by the library defined using `setuptools`. */
|
||||
module Setuptools {
|
||||
/**
|
||||
* Gets a file that sets up a package using `setuptools` (or the deprecated `distutils`).
|
||||
*/
|
||||
private File setupFile() {
|
||||
// all of these might not be extracted, but the support is ready for when they are
|
||||
result.getBaseName() = ["setup.py", "setup.cfg", "pyproject.toml"]
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a file or folder that is exported by a library.
|
||||
*/
|
||||
private Container getALibraryExportedContainer() {
|
||||
// a child folder of the root that has a setup.py file
|
||||
result = setupFile().getParent().(Folder).getAFolder() and
|
||||
// where the folder has __init__.py file
|
||||
exists(result.(Folder).getFile("__init__.py")) and
|
||||
// and is not a test folder
|
||||
not result.(Folder).getBaseName() = ["test", "tests", "testing"]
|
||||
or
|
||||
// child of a library exported container
|
||||
result = getALibraryExportedContainer().getAChildContainer() and
|
||||
(
|
||||
// either any file
|
||||
not result instanceof Folder
|
||||
or
|
||||
// or a folder with an __init__.py file
|
||||
exists(result.(Folder).getFile("__init__.py"))
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets an AST node that is exported by a library.
|
||||
*/
|
||||
private AstNode getAnExportedLibraryFeature() {
|
||||
result.(Module).getFile() = getALibraryExportedContainer()
|
||||
or
|
||||
result = getAnExportedLibraryFeature().(Module).getAStmt()
|
||||
or
|
||||
result = getAnExportedLibraryFeature().(ClassDef).getDefinedClass().getAMethod()
|
||||
or
|
||||
result = getAnExportedLibraryFeature().(ClassDef).getDefinedClass().getInitMethod()
|
||||
or
|
||||
result = getAnExportedLibraryFeature().(FunctionDef).getDefinedFunction()
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a public function (or __init__) that is exported by a library.
|
||||
*/
|
||||
private Function getAnExportedFunction() {
|
||||
result = getAnExportedLibraryFeature() and
|
||||
(
|
||||
result.isPublic()
|
||||
or
|
||||
result.isInitMethod()
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a parameter to a public function that is exported by a library.
|
||||
*/
|
||||
DataFlow::ParameterNode getALibraryInput() {
|
||||
result.getParameter() = getAnExportedFunction().getAnArg() and
|
||||
not result.getParameter().isSelf()
|
||||
}
|
||||
}
|
||||
@@ -152,7 +152,8 @@ module Starlette {
|
||||
}
|
||||
|
||||
/** An attribute read on a `starlette.requests.URL` instance that is a `urllib.parse.SplitResult` instance. */
|
||||
private class UrlSplitInstances extends Stdlib::SplitResult::InstanceSource instanceof DataFlow::AttrRead {
|
||||
private class UrlSplitInstances extends Stdlib::SplitResult::InstanceSource instanceof DataFlow::AttrRead
|
||||
{
|
||||
UrlSplitInstances() {
|
||||
super.getObject() = instance() and
|
||||
super.getAttributeName() = "components"
|
||||
|
||||
@@ -1060,7 +1060,11 @@ private module StdlibPrivate {
|
||||
private class OsSystemCall extends SystemCommandExecution::Range, DataFlow::CallCfgNode {
|
||||
OsSystemCall() { this = os().getMember("system").getACall() }
|
||||
|
||||
override DataFlow::Node getCommand() { result = this.getArg(0) }
|
||||
override DataFlow::Node getCommand() {
|
||||
result in [this.getArg(0), this.getArgByName("command")]
|
||||
}
|
||||
|
||||
override predicate isShellInterpreted(DataFlow::Node arg) { arg = this.getCommand() }
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1071,7 +1075,7 @@ private module StdlibPrivate {
|
||||
* Although deprecated since version 2.6, they still work in 2.7.
|
||||
* See https://docs.python.org/2.7/library/os.html#os.popen2
|
||||
*/
|
||||
private class OsPopenCall extends SystemCommandExecution::Range, DataFlow::CallCfgNode {
|
||||
private class OsPopenCall extends SystemCommandExecution::Range, API::CallNode {
|
||||
string name;
|
||||
|
||||
OsPopenCall() {
|
||||
@@ -1085,6 +1089,8 @@ private module StdlibPrivate {
|
||||
not name = "popen" and
|
||||
result = this.getArgByName("cmd")
|
||||
}
|
||||
|
||||
override predicate isShellInterpreted(DataFlow::Node arg) { arg = this.getCommand() }
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1092,7 +1098,8 @@ private module StdlibPrivate {
|
||||
* See https://docs.python.org/3.8/library/os.html#os.execl
|
||||
*/
|
||||
private class OsExecCall extends SystemCommandExecution::Range, FileSystemAccess::Range,
|
||||
DataFlow::CallCfgNode {
|
||||
DataFlow::CallCfgNode
|
||||
{
|
||||
OsExecCall() {
|
||||
exists(string name |
|
||||
name in ["execl", "execle", "execlp", "execlpe", "execv", "execve", "execvp", "execvpe"] and
|
||||
@@ -1103,6 +1110,10 @@ private module StdlibPrivate {
|
||||
override DataFlow::Node getCommand() { result = this.getArg(0) }
|
||||
|
||||
override DataFlow::Node getAPathArgument() { result = this.getCommand() }
|
||||
|
||||
override predicate isShellInterpreted(DataFlow::Node arg) {
|
||||
none() // this is a safe API.
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1110,7 +1121,8 @@ private module StdlibPrivate {
|
||||
* See https://docs.python.org/3.8/library/os.html#os.spawnl
|
||||
*/
|
||||
private class OsSpawnCall extends SystemCommandExecution::Range, FileSystemAccess::Range,
|
||||
DataFlow::CallCfgNode {
|
||||
DataFlow::CallCfgNode
|
||||
{
|
||||
OsSpawnCall() {
|
||||
exists(string name |
|
||||
name in [
|
||||
@@ -1129,6 +1141,10 @@ private module StdlibPrivate {
|
||||
}
|
||||
|
||||
override DataFlow::Node getAPathArgument() { result = this.getCommand() }
|
||||
|
||||
override predicate isShellInterpreted(DataFlow::Node arg) {
|
||||
none() // this is a safe API.
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1136,12 +1152,17 @@ private module StdlibPrivate {
|
||||
* See https://docs.python.org/3.8/library/os.html#os.posix_spawn
|
||||
*/
|
||||
private class OsPosixSpawnCall extends SystemCommandExecution::Range, FileSystemAccess::Range,
|
||||
DataFlow::CallCfgNode {
|
||||
DataFlow::CallCfgNode
|
||||
{
|
||||
OsPosixSpawnCall() { this = os().getMember(["posix_spawn", "posix_spawnp"]).getACall() }
|
||||
|
||||
override DataFlow::Node getCommand() { result in [this.getArg(0), this.getArgByName("path")] }
|
||||
|
||||
override DataFlow::Node getAPathArgument() { result = this.getCommand() }
|
||||
|
||||
override predicate isShellInterpreted(DataFlow::Node arg) {
|
||||
none() // this is a safe API.
|
||||
}
|
||||
}
|
||||
|
||||
/** An additional taint step for calls to `os.path.join` */
|
||||
@@ -1167,7 +1188,7 @@ private module StdlibPrivate {
|
||||
* See https://docs.python.org/3.8/library/subprocess.html#subprocess.Popen
|
||||
* ref: https://docs.python.org/3/library/subprocess.html#legacy-shell-invocation-functions
|
||||
*/
|
||||
private class SubprocessPopenCall extends SystemCommandExecution::Range, DataFlow::CallCfgNode {
|
||||
private class SubprocessPopenCall extends SystemCommandExecution::Range, API::CallNode {
|
||||
SubprocessPopenCall() {
|
||||
exists(string name |
|
||||
name in [
|
||||
@@ -1177,43 +1198,33 @@ private module StdlibPrivate {
|
||||
)
|
||||
}
|
||||
|
||||
/** Gets the ControlFlowNode for the `args` argument, if any. */
|
||||
private DataFlow::Node get_args_arg() { result in [this.getArg(0), this.getArgByName("args")] }
|
||||
/** Gets the API-node for the `args` argument, if any. */
|
||||
private API::Node get_args_arg() { result = this.getParameter(0, "args") }
|
||||
|
||||
/** Gets the ControlFlowNode for the `shell` argument, if any. */
|
||||
private DataFlow::Node get_shell_arg() {
|
||||
result in [this.getArg(8), this.getArgByName("shell")]
|
||||
}
|
||||
/** Gets the API-node for the `shell` argument, if any. */
|
||||
private API::Node get_shell_arg() { result = this.getParameter(8, "shell") }
|
||||
|
||||
private boolean get_shell_arg_value() {
|
||||
not exists(this.get_shell_arg()) and
|
||||
result = false
|
||||
or
|
||||
exists(DataFlow::Node shell_arg | shell_arg = this.get_shell_arg() |
|
||||
result = shell_arg.asCfgNode().getNode().(ImmutableLiteral).booleanValue()
|
||||
or
|
||||
// TODO: Track the "shell" argument to determine possible values
|
||||
not shell_arg.asCfgNode().getNode() instanceof ImmutableLiteral and
|
||||
(
|
||||
result = true
|
||||
or
|
||||
result = false
|
||||
)
|
||||
)
|
||||
result =
|
||||
this.get_shell_arg().getAValueReachingSink().asExpr().(ImmutableLiteral).booleanValue()
|
||||
or
|
||||
not this.get_shell_arg().getAValueReachingSink().asExpr() instanceof ImmutableLiteral and
|
||||
result = false // defaults to `False`
|
||||
}
|
||||
|
||||
/** Gets the ControlFlowNode for the `executable` argument, if any. */
|
||||
private DataFlow::Node get_executable_arg() {
|
||||
result in [this.getArg(2), this.getArgByName("executable")]
|
||||
}
|
||||
/** Gets the API-node for the `executable` argument, if any. */
|
||||
private API::Node get_executable_arg() { result = this.getParameter(2, "executable") }
|
||||
|
||||
override DataFlow::Node getCommand() {
|
||||
// TODO: Track arguments ("args" and "shell")
|
||||
// TODO: Handle using `args=["sh", "-c", <user-input>]`
|
||||
result = this.get_executable_arg()
|
||||
result = this.get_executable_arg().asSink()
|
||||
or
|
||||
exists(DataFlow::Node arg_args, boolean shell |
|
||||
arg_args = this.get_args_arg() and
|
||||
arg_args = this.get_args_arg().asSink() and
|
||||
shell = this.get_shell_arg_value()
|
||||
|
|
||||
// When "executable" argument is set, and "shell" argument is `False`, the
|
||||
@@ -1239,6 +1250,11 @@ private module StdlibPrivate {
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
override predicate isShellInterpreted(DataFlow::Node arg) {
|
||||
arg = [this.get_executable_arg(), this.get_args_arg()].asSink() and
|
||||
this.get_shell_arg_value() = true
|
||||
}
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
@@ -1348,7 +1364,8 @@ private module StdlibPrivate {
|
||||
* argument as being deserialized...
|
||||
*/
|
||||
private class ShelveOpenCall extends Decoding::Range, FileSystemAccess::Range,
|
||||
DataFlow::CallCfgNode {
|
||||
DataFlow::CallCfgNode
|
||||
{
|
||||
ShelveOpenCall() { this = API::moduleImport("shelve").getMember("open").getACall() }
|
||||
|
||||
override predicate mayExecuteInput() { any() }
|
||||
@@ -1385,6 +1402,8 @@ private module StdlibPrivate {
|
||||
}
|
||||
|
||||
override DataFlow::Node getCommand() { result in [this.getArg(0), this.getArgByName("cmd")] }
|
||||
|
||||
override predicate isShellInterpreted(DataFlow::Node arg) { arg = this.getCommand() }
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
@@ -1401,6 +1420,8 @@ private module StdlibPrivate {
|
||||
PlatformPopenCall() { this = platform().getMember("popen").getACall() }
|
||||
|
||||
override DataFlow::Node getCommand() { result in [this.getArg(0), this.getArgByName("cmd")] }
|
||||
|
||||
override predicate isShellInterpreted(DataFlow::Node arg) { arg = this.getCommand() }
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
@@ -1452,7 +1473,8 @@ private module StdlibPrivate {
|
||||
* See https://docs.python.org/3/library/functions.html#open
|
||||
*/
|
||||
private class OpenCall extends FileSystemAccess::Range, Stdlib::FileLikeObject::InstanceSource,
|
||||
DataFlow::CallCfgNode {
|
||||
DataFlow::CallCfgNode
|
||||
{
|
||||
OpenCall() { this = getOpenFunctionRef().getACall() }
|
||||
|
||||
override DataFlow::Node getAPathArgument() {
|
||||
@@ -1712,7 +1734,8 @@ private module StdlibPrivate {
|
||||
* if it turns out to be a problem, we'll have to refine.
|
||||
*/
|
||||
private class ClassInstantiation extends InstanceSource, RemoteFlowSource::Range,
|
||||
DataFlow::CallCfgNode {
|
||||
DataFlow::CallCfgNode
|
||||
{
|
||||
ClassInstantiation() { this = classRef().getACall() }
|
||||
|
||||
override string getSourceType() { result = "cgi.FieldStorage" }
|
||||
@@ -1970,7 +1993,8 @@ private module StdlibPrivate {
|
||||
abstract class InstanceSource extends DataFlow::Node { }
|
||||
|
||||
/** The `self` parameter in a method on the `BaseHttpRequestHandler` class or any subclass. */
|
||||
private class SelfParam extends InstanceSource, RemoteFlowSource::Range, DataFlow::ParameterNode {
|
||||
private class SelfParam extends InstanceSource, RemoteFlowSource::Range, DataFlow::ParameterNode
|
||||
{
|
||||
SelfParam() {
|
||||
exists(HttpRequestHandlerClassDef cls | cls.getAMethod().getArg(0) = this.getParameter())
|
||||
}
|
||||
@@ -2008,14 +2032,16 @@ private module StdlibPrivate {
|
||||
}
|
||||
|
||||
/** An `HttpMessage` instance that originates from a `BaseHttpRequestHandler` instance. */
|
||||
private class BaseHttpRequestHandlerHeadersInstances extends Stdlib::HttpMessage::InstanceSource {
|
||||
private class BaseHttpRequestHandlerHeadersInstances extends Stdlib::HttpMessage::InstanceSource
|
||||
{
|
||||
BaseHttpRequestHandlerHeadersInstances() {
|
||||
this.(DataFlow::AttrRead).accesses(instance(), "headers")
|
||||
}
|
||||
}
|
||||
|
||||
/** A file-like object that originates from a `BaseHttpRequestHandler` instance. */
|
||||
private class BaseHttpRequestHandlerFileLikeObjectInstances extends Stdlib::FileLikeObject::InstanceSource {
|
||||
private class BaseHttpRequestHandlerFileLikeObjectInstances extends Stdlib::FileLikeObject::InstanceSource
|
||||
{
|
||||
BaseHttpRequestHandlerFileLikeObjectInstances() {
|
||||
this.(DataFlow::AttrRead).accesses(instance(), "rfile")
|
||||
}
|
||||
@@ -2167,7 +2193,8 @@ private module StdlibPrivate {
|
||||
* See https://github.com/python/cpython/blob/b567b9d74bd9e476a3027335873bb0508d6e450f/Lib/wsgiref/handlers.py#L276
|
||||
*/
|
||||
class WsgirefSimpleServerApplicationWriteCall extends Http::Server::HttpResponse::Range,
|
||||
DataFlow::CallCfgNode {
|
||||
DataFlow::CallCfgNode
|
||||
{
|
||||
WsgirefSimpleServerApplicationWriteCall() { this.getFunction() = writeFunction() }
|
||||
|
||||
override DataFlow::Node getBody() { result in [this.getArg(0), this.getArgByName("data")] }
|
||||
@@ -2181,7 +2208,8 @@ private module StdlibPrivate {
|
||||
* A return from a `WsgirefSimpleServerApplication`, which is included in the response body.
|
||||
*/
|
||||
class WsgirefSimpleServerApplicationReturn extends Http::Server::HttpResponse::Range,
|
||||
DataFlow::CfgNode {
|
||||
DataFlow::CfgNode
|
||||
{
|
||||
WsgirefSimpleServerApplicationReturn() {
|
||||
exists(WsgirefSimpleServerApplication requestHandler |
|
||||
node = requestHandler.getAReturnValueFlowNode()
|
||||
@@ -2292,7 +2320,8 @@ private module StdlibPrivate {
|
||||
|
||||
/** A call to the `getresponse` method. */
|
||||
private class HttpConnectionGetResponseCall extends DataFlow::MethodCallNode,
|
||||
HttpResponse::InstanceSource {
|
||||
HttpResponse::InstanceSource
|
||||
{
|
||||
HttpConnectionGetResponseCall() { this.calls(instance(_), "getresponse") }
|
||||
}
|
||||
|
||||
@@ -2351,7 +2380,8 @@ private module StdlibPrivate {
|
||||
* Use the predicate `HTTPResponse::instance()` to get references to instances of `http.client.HTTPResponse`.
|
||||
*/
|
||||
abstract class InstanceSource extends Stdlib::FileLikeObject::InstanceSource,
|
||||
DataFlow::LocalSourceNode { }
|
||||
DataFlow::LocalSourceNode
|
||||
{ }
|
||||
|
||||
/** A direct instantiation of `http.client.HttpResponse`. */
|
||||
private class ClassInstantiation extends InstanceSource, DataFlow::CallCfgNode {
|
||||
@@ -2722,7 +2752,8 @@ private module StdlibPrivate {
|
||||
* `HashlibNewCall` and `HashlibNewUpdateCall`.
|
||||
*/
|
||||
abstract class HashlibGenericHashOperation extends Cryptography::CryptographicOperation::Range,
|
||||
DataFlow::CallCfgNode {
|
||||
DataFlow::CallCfgNode
|
||||
{
|
||||
string hashName;
|
||||
API::Node hashClass;
|
||||
|
||||
@@ -2768,7 +2799,8 @@ private module StdlibPrivate {
|
||||
// hmac
|
||||
// ---------------------------------------------------------------------------
|
||||
abstract class HmacCryptographicOperation extends Cryptography::CryptographicOperation::Range,
|
||||
API::CallNode {
|
||||
API::CallNode
|
||||
{
|
||||
abstract API::Node getDigestArg();
|
||||
|
||||
override Cryptography::CryptographicAlgorithm getAlgorithm() {
|
||||
@@ -2996,7 +3028,8 @@ private module StdlibPrivate {
|
||||
}
|
||||
|
||||
/** Extra taint-step such that the result of `urllib.parse.urlsplit(tainted_string)` is tainted. */
|
||||
private class UrllibParseUrlsplitCallAdditionalTaintStep extends TaintTracking::AdditionalTaintStep {
|
||||
private class UrllibParseUrlsplitCallAdditionalTaintStep extends TaintTracking::AdditionalTaintStep
|
||||
{
|
||||
override predicate step(DataFlow::Node nodeFrom, DataFlow::Node nodeTo) {
|
||||
nodeTo.(UrllibParseUrlsplitCall).getUrl() = nodeFrom
|
||||
}
|
||||
@@ -3027,7 +3060,8 @@ private module StdlibPrivate {
|
||||
* See https://docs.python.org/3/library/tempfile.html#tempfile.NamedTemporaryFile
|
||||
*/
|
||||
private class TempfileNamedTemporaryFileCall extends FileSystemAccess::Range,
|
||||
DataFlow::CallCfgNode {
|
||||
DataFlow::CallCfgNode
|
||||
{
|
||||
TempfileNamedTemporaryFileCall() {
|
||||
this = API::moduleImport("tempfile").getMember("NamedTemporaryFile").getACall()
|
||||
}
|
||||
@@ -3064,7 +3098,8 @@ private module StdlibPrivate {
|
||||
* See https://docs.python.org/3/library/tempfile.html#tempfile.SpooledTemporaryFile
|
||||
*/
|
||||
private class TempfileSpooledTemporaryFileCall extends FileSystemAccess::Range,
|
||||
DataFlow::CallCfgNode {
|
||||
DataFlow::CallCfgNode
|
||||
{
|
||||
TempfileSpooledTemporaryFileCall() {
|
||||
this = API::moduleImport("tempfile").getMember("SpooledTemporaryFile").getACall()
|
||||
}
|
||||
@@ -3099,7 +3134,8 @@ private module StdlibPrivate {
|
||||
* See https://docs.python.org/3/library/tempfile.html#tempfile.TemporaryDirectory
|
||||
*/
|
||||
private class TempfileTemporaryDirectoryCall extends FileSystemAccess::Range,
|
||||
DataFlow::CallCfgNode {
|
||||
DataFlow::CallCfgNode
|
||||
{
|
||||
TempfileTemporaryDirectoryCall() {
|
||||
this = API::moduleImport("tempfile").getMember("TemporaryDirectory").getACall()
|
||||
}
|
||||
@@ -3556,7 +3592,8 @@ private module StdlibPrivate {
|
||||
* See https://docs.python.org/3/library/xml.sax.reader.html#xml.sax.xmlreader.XMLReader.parse
|
||||
*/
|
||||
private class XmlSaxInstanceParsing extends DataFlow::MethodCallNode, XML::XmlParsing::Range,
|
||||
FileSystemAccess::Range {
|
||||
FileSystemAccess::Range
|
||||
{
|
||||
XmlSaxInstanceParsing() {
|
||||
this =
|
||||
API::moduleImport("xml")
|
||||
|
||||
@@ -200,7 +200,8 @@ module Tornado {
|
||||
override string getAsyncMethodName() { none() }
|
||||
}
|
||||
|
||||
private class RequestAttrAccess extends TornadoModule::HttpUtil::HttpServerRequest::InstanceSource {
|
||||
private class RequestAttrAccess extends TornadoModule::HttpUtil::HttpServerRequest::InstanceSource
|
||||
{
|
||||
RequestAttrAccess() {
|
||||
this.(DataFlow::AttrRead).getObject() = instance() and
|
||||
this.(DataFlow::AttrRead).getAttributeName() = "request"
|
||||
@@ -463,7 +464,8 @@ module Tornado {
|
||||
* See https://www.tornadoweb.org/en/stable/web.html#tornado.web.RequestHandler.redirect
|
||||
*/
|
||||
private class TornadoRequestHandlerRedirectCall extends Http::Server::HttpRedirectResponse::Range,
|
||||
DataFlow::CallCfgNode {
|
||||
DataFlow::CallCfgNode
|
||||
{
|
||||
TornadoRequestHandlerRedirectCall() {
|
||||
this.getFunction() = TornadoModule::Web::RequestHandler::redirectMethod()
|
||||
}
|
||||
@@ -485,7 +487,8 @@ module Tornado {
|
||||
* See https://www.tornadoweb.org/en/stable/web.html#tornado.web.RequestHandler.write
|
||||
*/
|
||||
private class TornadoRequestHandlerWriteCall extends Http::Server::HttpResponse::Range,
|
||||
DataFlow::CallCfgNode {
|
||||
DataFlow::CallCfgNode
|
||||
{
|
||||
TornadoRequestHandlerWriteCall() {
|
||||
this.getFunction() = TornadoModule::Web::RequestHandler::writeMethod()
|
||||
}
|
||||
@@ -503,7 +506,8 @@ module Tornado {
|
||||
* See https://www.tornadoweb.org/en/stable/web.html#tornado.web.RequestHandler.set_cookie
|
||||
*/
|
||||
class TornadoRequestHandlerSetCookieCall extends Http::Server::CookieWrite::Range,
|
||||
DataFlow::MethodCallNode {
|
||||
DataFlow::MethodCallNode
|
||||
{
|
||||
TornadoRequestHandlerSetCookieCall() {
|
||||
this.calls(TornadoModule::Web::RequestHandler::instance(), "set_cookie")
|
||||
}
|
||||
|
||||
@@ -143,7 +143,8 @@ private module Twisted {
|
||||
* when a twisted request handler is called.
|
||||
*/
|
||||
class TwistedResourceRequestHandlerRequestParam extends RemoteFlowSource::Range,
|
||||
Request::InstanceSource, DataFlow::ParameterNode {
|
||||
Request::InstanceSource, DataFlow::ParameterNode
|
||||
{
|
||||
TwistedResourceRequestHandlerRequestParam() {
|
||||
this.getParameter() = any(TwistedResourceRequestHandler handler).getRequestParameter()
|
||||
}
|
||||
@@ -156,7 +157,8 @@ private module Twisted {
|
||||
* that is also given remote user input. (a bit like RoutedParameter).
|
||||
*/
|
||||
class TwistedResourceRequestHandlerExtraSources extends RemoteFlowSource::Range,
|
||||
DataFlow::ParameterNode {
|
||||
DataFlow::ParameterNode
|
||||
{
|
||||
TwistedResourceRequestHandlerExtraSources() {
|
||||
exists(TwistedResourceRequestHandler func, int i |
|
||||
func.getName() in ["getChild", "getChildWithDefault"] and i = 1
|
||||
@@ -177,7 +179,8 @@ private module Twisted {
|
||||
* Implicit response from returns of render methods.
|
||||
*/
|
||||
private class TwistedResourceRenderMethodReturn extends Http::Server::HttpResponse::Range,
|
||||
DataFlow::CfgNode {
|
||||
DataFlow::CfgNode
|
||||
{
|
||||
TwistedResourceRenderMethodReturn() {
|
||||
this.asCfgNode() = any(TwistedResourceRenderMethod meth).getAReturnValueFlowNode()
|
||||
}
|
||||
@@ -212,7 +215,8 @@ 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,
|
||||
DataFlow::MethodCallNode {
|
||||
DataFlow::MethodCallNode
|
||||
{
|
||||
TwistedRequestRedirectCall() { this.calls(Request::instance(), "redirect") }
|
||||
|
||||
override DataFlow::Node getBody() { none() }
|
||||
@@ -232,7 +236,8 @@ 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,
|
||||
DataFlow::MethodCallNode {
|
||||
DataFlow::MethodCallNode
|
||||
{
|
||||
TwistedRequestAddCookieCall() { this.calls(Twisted::Request::instance(), "addCookie") }
|
||||
|
||||
override DataFlow::Node getHeaderArg() { none() }
|
||||
@@ -248,7 +253,8 @@ 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,
|
||||
DataFlow::MethodCallNode {
|
||||
DataFlow::MethodCallNode
|
||||
{
|
||||
TwistedRequestCookiesAppendCall() {
|
||||
exists(DataFlow::AttrRead cookiesLookup |
|
||||
cookiesLookup.getObject() = Twisted::Request::instance() and
|
||||
|
||||
@@ -83,7 +83,8 @@ module Werkzeug {
|
||||
// possible to do storage.read() instead of the long form storage.stream.read(). So
|
||||
// that's why InstanceSource also extends `Stdlib::FileLikeObject::InstanceSource`
|
||||
abstract class InstanceSource extends Stdlib::FileLikeObject::InstanceSource,
|
||||
DataFlow::LocalSourceNode { }
|
||||
DataFlow::LocalSourceNode
|
||||
{ }
|
||||
|
||||
/** Gets a reference to an instance of `werkzeug.datastructures.FileStorage`. */
|
||||
private DataFlow::TypeTrackingNode instance(DataFlow::TypeTracker t) {
|
||||
|
||||
@@ -0,0 +1,159 @@
|
||||
/**
|
||||
* Provides default sources, sinks and sanitizers for reasoning about
|
||||
* shell command constructed from library input vulnerabilities, as
|
||||
* well as extension points for adding your own.
|
||||
*/
|
||||
|
||||
private import python
|
||||
private import semmle.python.dataflow.new.DataFlow
|
||||
private import semmle.python.dataflow.new.TaintTracking
|
||||
private import CommandInjectionCustomizations::CommandInjection as CommandInjection
|
||||
private import semmle.python.Concepts as Concepts
|
||||
|
||||
/**
|
||||
* Module containing sources, sinks, and sanitizers for shell command constructed from library input.
|
||||
*/
|
||||
module UnsafeShellCommandConstruction {
|
||||
/** A source for shell command constructed from library input vulnerabilities. */
|
||||
abstract class Source extends DataFlow::Node { }
|
||||
|
||||
private import semmle.python.frameworks.Setuptools
|
||||
|
||||
/** An input parameter to a gem seen as a source. */
|
||||
private class LibraryInputAsSource extends Source instanceof DataFlow::ParameterNode {
|
||||
LibraryInputAsSource() {
|
||||
this = Setuptools::getALibraryInput() and
|
||||
not this.getParameter().getName().matches(["cmd%", "command%", "%_command", "%_cmd"])
|
||||
}
|
||||
}
|
||||
|
||||
/** A sink for shell command constructed from library input vulnerabilities. */
|
||||
abstract class Sink extends DataFlow::Node {
|
||||
Sink() { not this.asExpr() instanceof StrConst } // filter out string constants, makes testing easier
|
||||
|
||||
/** Gets a description of how the string in this sink was constructed. */
|
||||
abstract string describe();
|
||||
|
||||
/** Gets the dataflow node where the string is constructed. */
|
||||
DataFlow::Node getStringConstruction() { result = this }
|
||||
|
||||
/** Gets the dataflow node that executed the string as a shell command. */
|
||||
abstract DataFlow::Node getCommandExecution();
|
||||
}
|
||||
|
||||
/** Holds if the string constructed at `source` is executed at `shellExec` */
|
||||
predicate isUsedAsShellCommand(DataFlow::Node source, Concepts::SystemCommandExecution shellExec) {
|
||||
source = backtrackShellExec(TypeTracker::TypeBackTracker::end(), shellExec)
|
||||
}
|
||||
|
||||
import semmle.python.dataflow.new.TypeTracker as TypeTracker
|
||||
|
||||
private DataFlow::LocalSourceNode backtrackShellExec(
|
||||
TypeTracker::TypeBackTracker t, Concepts::SystemCommandExecution shellExec
|
||||
) {
|
||||
t.start() and
|
||||
result = any(DataFlow::Node n | shellExec.isShellInterpreted(n)).getALocalSource()
|
||||
or
|
||||
exists(TypeTracker::TypeBackTracker t2 |
|
||||
result = backtrackShellExec(t2, shellExec).backtrack(t2, t)
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* A string constructed from a string-literal (e.g. `f'foo {sink}'`),
|
||||
* where the resulting string ends up being executed as a shell command.
|
||||
*/
|
||||
class StringInterpolationAsSink extends Sink {
|
||||
Concepts::SystemCommandExecution s;
|
||||
Fstring fstring;
|
||||
|
||||
StringInterpolationAsSink() {
|
||||
isUsedAsShellCommand(DataFlow::exprNode(fstring), s) and
|
||||
this.asExpr() = fstring.getASubExpression()
|
||||
}
|
||||
|
||||
override string describe() { result = "f-string" }
|
||||
|
||||
override DataFlow::Node getCommandExecution() { result = s }
|
||||
|
||||
override DataFlow::Node getStringConstruction() { result.asExpr() = fstring }
|
||||
}
|
||||
|
||||
/**
|
||||
* A component of a string-concatenation (e.g. `"foo " + sink`),
|
||||
* where the resulting string ends up being executed as a shell command.
|
||||
*/
|
||||
class StringConcatAsSink extends Sink {
|
||||
Concepts::SystemCommandExecution s;
|
||||
BinaryExpr add;
|
||||
|
||||
StringConcatAsSink() {
|
||||
add.getOp() instanceof Add and
|
||||
isUsedAsShellCommand(any(DataFlow::Node n | n.asExpr() = add), s) and
|
||||
this.asExpr() = add.getASubExpression()
|
||||
}
|
||||
|
||||
override DataFlow::Node getCommandExecution() { result = s }
|
||||
|
||||
override string describe() { result = "string concatenation" }
|
||||
|
||||
override DataFlow::Node getStringConstruction() { result.asExpr() = add }
|
||||
}
|
||||
|
||||
/**
|
||||
* A string constructed using a `" ".join(...)` call, where the resulting string ends up being executed as a shell command.
|
||||
*/
|
||||
class ArrayJoin extends Sink {
|
||||
Concepts::SystemCommandExecution s;
|
||||
DataFlow::MethodCallNode call;
|
||||
|
||||
ArrayJoin() {
|
||||
call.getMethodName() = "join" and
|
||||
unique( | | call.getArg(_)).asExpr().(Str).getText() = " " and
|
||||
isUsedAsShellCommand(call, s) and
|
||||
(
|
||||
this = call.getArg(0) and
|
||||
not call.getArg(0).asExpr() instanceof List
|
||||
or
|
||||
this.asExpr() = call.getArg(0).asExpr().(List).getASubExpression()
|
||||
)
|
||||
}
|
||||
|
||||
override string describe() { result = "array" }
|
||||
|
||||
override DataFlow::Node getCommandExecution() { result = s }
|
||||
|
||||
override DataFlow::Node getStringConstruction() { result = call }
|
||||
}
|
||||
|
||||
/**
|
||||
* A string constructed from a format call,
|
||||
* where the resulting string ends up being executed as a shell command.
|
||||
* Either a call to `.format(..)` or a string-interpolation with a `%` operator.
|
||||
*/
|
||||
class TaintedFormatStringAsSink extends Sink {
|
||||
Concepts::SystemCommandExecution s;
|
||||
DataFlow::Node formatCall;
|
||||
|
||||
TaintedFormatStringAsSink() {
|
||||
(
|
||||
formatCall.asExpr().(BinaryExpr).getOp() instanceof Mod and
|
||||
this.asExpr() = formatCall.asExpr().(BinaryExpr).getASubExpression()
|
||||
or
|
||||
formatCall.(DataFlow::MethodCallNode).getMethodName() = "format" and
|
||||
this =
|
||||
[
|
||||
formatCall.(DataFlow::MethodCallNode).getArg(_),
|
||||
formatCall.(DataFlow::MethodCallNode).getObject()
|
||||
]
|
||||
) and
|
||||
isUsedAsShellCommand(formatCall, s)
|
||||
}
|
||||
|
||||
override string describe() { result = "formatted string" }
|
||||
|
||||
override DataFlow::Node getCommandExecution() { result = s }
|
||||
|
||||
override DataFlow::Node getStringConstruction() { result = formatCall }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
/**
|
||||
* Provides a taint tracking configuration for reasoning about shell command
|
||||
* constructed from library input vulnerabilities
|
||||
*
|
||||
* Note, for performance reasons: only import this file if `Configuration` is needed,
|
||||
* otherwise `UnsafeShellCommandConstructionCustomizations` should be imported instead.
|
||||
*/
|
||||
|
||||
import python
|
||||
import semmle.python.dataflow.new.DataFlow
|
||||
import UnsafeShellCommandConstructionCustomizations::UnsafeShellCommandConstruction
|
||||
private import semmle.python.dataflow.new.TaintTracking
|
||||
private import CommandInjectionCustomizations::CommandInjection as CommandInjection
|
||||
private import semmle.python.dataflow.new.BarrierGuards
|
||||
|
||||
/**
|
||||
* A taint-tracking configuration for detecting shell command constructed from library input vulnerabilities.
|
||||
*/
|
||||
class Configuration extends TaintTracking::Configuration {
|
||||
Configuration() { this = "UnsafeShellCommandConstruction" }
|
||||
|
||||
override predicate isSource(DataFlow::Node source) { source instanceof Source }
|
||||
|
||||
override predicate isSink(DataFlow::Node sink) { sink instanceof Sink }
|
||||
|
||||
override predicate isSanitizer(DataFlow::Node node) {
|
||||
node instanceof CommandInjection::Sanitizer // using all sanitizers from `rb/command-injection`
|
||||
}
|
||||
|
||||
// override to require the path doesn't have unmatched return steps
|
||||
override DataFlow::FlowFeature getAFeature() {
|
||||
result instanceof DataFlow::FeatureHasSourceCallContext
|
||||
}
|
||||
}
|
||||
@@ -108,20 +108,6 @@ class XmlFile extends XmlParent, File {
|
||||
/** Gets the name of this XML file. */
|
||||
override string getName() { result = File.super.getAbsolutePath() }
|
||||
|
||||
/**
|
||||
* DEPRECATED: Use `getAbsolutePath()` instead.
|
||||
*
|
||||
* Gets the path of this XML file.
|
||||
*/
|
||||
deprecated string getPath() { result = this.getAbsolutePath() }
|
||||
|
||||
/**
|
||||
* DEPRECATED: Use `getParentContainer().getAbsolutePath()` instead.
|
||||
*
|
||||
* Gets the path of the folder that contains this XML file.
|
||||
*/
|
||||
deprecated string getFolder() { result = this.getParentContainer().getAbsolutePath() }
|
||||
|
||||
/** Gets the encoding of this XML file. */
|
||||
string getEncoding() { xmlEncoding(this, result) }
|
||||
|
||||
|
||||
@@ -1,3 +1,7 @@
|
||||
## 0.6.4
|
||||
|
||||
No user-facing changes.
|
||||
|
||||
## 0.6.3
|
||||
|
||||
No user-facing changes.
|
||||
|
||||
@@ -0,0 +1,73 @@
|
||||
<!DOCTYPE qhelp PUBLIC
|
||||
"-//Semmle//qhelp//EN"
|
||||
"qhelp.dtd">
|
||||
<qhelp>
|
||||
<overview>
|
||||
<p>
|
||||
Dynamically constructing a shell command with inputs from library
|
||||
functions may inadvertently change the meaning of the shell command.
|
||||
|
||||
Clients using the exported function may use inputs containing
|
||||
characters that the shell interprets in a special way, for instance
|
||||
quotes and spaces.
|
||||
|
||||
This can result in the shell command misbehaving, or even
|
||||
allowing a malicious user to execute arbitrary commands on the system.
|
||||
</p>
|
||||
|
||||
|
||||
</overview>
|
||||
<recommendation>
|
||||
|
||||
<p>
|
||||
If possible, provide the dynamic arguments to the shell as an array
|
||||
to APIs such as <code>subprocess.run</code> to avoid interpretation by the shell.
|
||||
</p>
|
||||
|
||||
<p>
|
||||
Alternatively, if the shell command must be constructed
|
||||
dynamically, then add code to ensure that special characters
|
||||
do not alter the shell command unexpectedly.
|
||||
</p>
|
||||
|
||||
</recommendation>
|
||||
<example>
|
||||
|
||||
<p>
|
||||
The following example shows a dynamically constructed shell
|
||||
command that downloads a file from a remote URL.
|
||||
</p>
|
||||
|
||||
<sample src="examples/unsafe-shell-command-construction.py" />
|
||||
|
||||
<p>
|
||||
The shell command will, however, fail to work as intended if the
|
||||
input contains spaces or other special characters interpreted in a
|
||||
special way by the shell.
|
||||
</p>
|
||||
|
||||
<p>
|
||||
Even worse, a client might pass in user-controlled
|
||||
data, not knowing that the input is interpreted as a shell command.
|
||||
This could allow a malicious user to provide the input <code>http://example.org; cat /etc/passwd</code>
|
||||
in order to execute the command <code>cat /etc/passwd</code>.
|
||||
</p>
|
||||
|
||||
<p>
|
||||
To avoid such potentially catastrophic behaviors, provide the
|
||||
input from library functions as an argument that does not
|
||||
get interpreted by a shell:
|
||||
</p>
|
||||
|
||||
<sample src="examples/unsafe-shell-command-construction_fixed.py" />
|
||||
|
||||
</example>
|
||||
<references>
|
||||
|
||||
<li>
|
||||
OWASP:
|
||||
<a href="https://www.owasp.org/index.php/Command_Injection">Command Injection</a>.
|
||||
</li>
|
||||
|
||||
</references>
|
||||
</qhelp>
|
||||
@@ -0,0 +1,27 @@
|
||||
/**
|
||||
* @name Unsafe shell command constructed from library input
|
||||
* @description Using externally controlled strings in a command line may allow a malicious
|
||||
* user to change the meaning of the command.
|
||||
* @kind path-problem
|
||||
* @problem.severity error
|
||||
* @security-severity 6.3
|
||||
* @precision medium
|
||||
* @id py/shell-command-constructed-from-input
|
||||
* @tags correctness
|
||||
* security
|
||||
* external/cwe/cwe-078
|
||||
* external/cwe/cwe-088
|
||||
* external/cwe/cwe-073
|
||||
*/
|
||||
|
||||
import python
|
||||
import semmle.python.security.dataflow.UnsafeShellCommandConstructionQuery
|
||||
import DataFlow::PathGraph
|
||||
|
||||
from Configuration config, DataFlow::PathNode source, DataFlow::PathNode sink, Sink sinkNode
|
||||
where
|
||||
config.hasFlowPath(source, sink) and
|
||||
sinkNode = sink.getNode()
|
||||
select sinkNode.getStringConstruction(), source, sink,
|
||||
"This " + sinkNode.describe() + " which depends on $@ is later used in a $@.", source.getNode(),
|
||||
"library input", sinkNode.getCommandExecution(), "shell command"
|
||||
@@ -0,0 +1,4 @@
|
||||
import os
|
||||
|
||||
def download(path):
|
||||
os.system("wget " + path) # NOT OK
|
||||
@@ -0,0 +1,4 @@
|
||||
import subprocess
|
||||
|
||||
def download(path):
|
||||
subprocess.run(["wget", path]) # OK
|
||||
@@ -20,48 +20,100 @@ import TlsLibraryModel
|
||||
* Since we really want "the last unrestriction, not nullified by a restriction",
|
||||
* we also disallow flow into restrictions.
|
||||
*/
|
||||
class InsecureContextConfiguration extends DataFlow::Configuration {
|
||||
TlsLibrary library;
|
||||
ProtocolVersion tracked_version;
|
||||
module InsecureContextConfiguration2 implements DataFlow::StateConfigSig {
|
||||
private newtype TFlowState =
|
||||
TMkFlowState(TlsLibrary library, int bits) {
|
||||
bits in [0 .. max(any(ProtocolVersion v).getBit()) * 2 - 1]
|
||||
}
|
||||
|
||||
InsecureContextConfiguration() {
|
||||
this = library + "Allows" + tracked_version and
|
||||
tracked_version.isInsecure()
|
||||
class FlowState extends TFlowState {
|
||||
int getBits() { this = TMkFlowState(_, result) }
|
||||
|
||||
TlsLibrary getLibrary() { this = TMkFlowState(result, _) }
|
||||
|
||||
predicate allowsInsecureVersion(ProtocolVersion v) {
|
||||
v.isInsecure() and this.getBits().bitAnd(v.getBit()) != 0
|
||||
}
|
||||
|
||||
string toString() {
|
||||
result =
|
||||
"FlowState(" + this.getLibrary().toString() + ", " +
|
||||
concat(ProtocolVersion v | this.allowsInsecureVersion(v) | v, ", ") + ")"
|
||||
}
|
||||
}
|
||||
|
||||
ProtocolVersion getTrackedVersion() { result = tracked_version }
|
||||
|
||||
override predicate isSource(DataFlow::Node source) { this.isUnrestriction(source) }
|
||||
|
||||
override predicate isSink(DataFlow::Node sink) {
|
||||
sink = library.connection_creation().getContext()
|
||||
}
|
||||
|
||||
override predicate isBarrierIn(DataFlow::Node node) {
|
||||
this.isRestriction(node)
|
||||
private predicate relevantState(FlowState state) {
|
||||
isSource(_, state)
|
||||
or
|
||||
this.isUnrestriction(node)
|
||||
}
|
||||
|
||||
private predicate isRestriction(DataFlow::Node node) {
|
||||
exists(ProtocolRestriction r |
|
||||
r = library.protocol_restriction() and
|
||||
r.getRestriction() = tracked_version
|
||||
|
|
||||
node = r.getContext()
|
||||
exists(FlowState state0 | relevantState(state0) |
|
||||
exists(ProtocolRestriction r |
|
||||
r = state0.getLibrary().protocol_restriction() and
|
||||
state.getBits() = state0.getBits().bitAnd(sum(r.getRestriction().getBit()).bitNot()) and
|
||||
state0.getLibrary() = state.getLibrary()
|
||||
)
|
||||
or
|
||||
exists(ProtocolUnrestriction pu |
|
||||
pu = state0.getLibrary().protocol_unrestriction() and
|
||||
state.getBits() = state0.getBits().bitOr(sum(pu.getUnrestriction().getBit())) and
|
||||
state0.getLibrary() = state.getLibrary()
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
private predicate isUnrestriction(DataFlow::Node node) {
|
||||
exists(ProtocolUnrestriction pu |
|
||||
pu = library.protocol_unrestriction() and
|
||||
pu.getUnrestriction() = tracked_version
|
||||
|
|
||||
node = pu.getContext()
|
||||
predicate isSource(DataFlow::Node source, FlowState state) {
|
||||
exists(ProtocolFamily family |
|
||||
source = state.getLibrary().unspecific_context_creation(family) and
|
||||
state.getBits() = family.getBits()
|
||||
)
|
||||
}
|
||||
|
||||
predicate isSink(DataFlow::Node sink, FlowState state) {
|
||||
sink = state.getLibrary().connection_creation().getContext() and
|
||||
state.allowsInsecureVersion(_)
|
||||
}
|
||||
|
||||
predicate isAdditionalFlowStep(
|
||||
DataFlow::Node node1, FlowState state1, DataFlow::Node node2, FlowState state2
|
||||
) {
|
||||
DataFlow::localFlowStep(node1, node2) and
|
||||
relevantState(state1) and
|
||||
(
|
||||
exists(ProtocolRestriction r |
|
||||
r = state1.getLibrary().protocol_restriction() and
|
||||
node2 = r.getContext() and
|
||||
state2.getBits() = state1.getBits().bitAnd(sum(r.getRestriction().getBit()).bitNot()) and
|
||||
state1.getLibrary() = state2.getLibrary()
|
||||
)
|
||||
or
|
||||
exists(ProtocolUnrestriction pu |
|
||||
pu = state1.getLibrary().protocol_unrestriction() and
|
||||
node2 = pu.getContext() and
|
||||
state2.getBits() = state1.getBits().bitOr(sum(pu.getUnrestriction().getBit())) and
|
||||
state1.getLibrary() = state2.getLibrary()
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
predicate isBarrier(DataFlow::Node node, FlowState state) {
|
||||
relevantState(state) and
|
||||
(
|
||||
exists(ProtocolRestriction r |
|
||||
r = state.getLibrary().protocol_restriction() and
|
||||
node = r.getContext() and
|
||||
state.allowsInsecureVersion(r.getRestriction())
|
||||
)
|
||||
or
|
||||
exists(ProtocolUnrestriction pu |
|
||||
pu = state.getLibrary().protocol_unrestriction() and
|
||||
node = pu.getContext() and
|
||||
not state.allowsInsecureVersion(pu.getUnrestriction())
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private module InsecureContextFlow = DataFlow::MakeWithState<InsecureContextConfiguration2>;
|
||||
|
||||
/**
|
||||
* Holds if `conectionCreation` marks the creation of a connection based on the contex
|
||||
* found at `contextOrigin` and allowing `insecure_version`.
|
||||
@@ -74,8 +126,11 @@ predicate unsafe_connection_creation_with_context(
|
||||
boolean specific
|
||||
) {
|
||||
// Connection created from a context allowing `insecure_version`.
|
||||
exists(InsecureContextConfiguration c | c.hasFlow(contextOrigin, connectionCreation) |
|
||||
insecure_version = c.getTrackedVersion() and
|
||||
exists(InsecureContextFlow::PathNode src, InsecureContextFlow::PathNode sink |
|
||||
InsecureContextFlow::hasFlowPath(src, sink) and
|
||||
src.getNode() = contextOrigin and
|
||||
sink.getNode() = connectionCreation and
|
||||
sink.getState().allowsInsecureVersion(insecure_version) and
|
||||
specific = false
|
||||
)
|
||||
or
|
||||
|
||||
@@ -51,8 +51,9 @@ class SetOptionsCall extends ProtocolRestriction, DataFlow::CallCfgNode {
|
||||
}
|
||||
}
|
||||
|
||||
class UnspecificPyOpenSslContextCreation extends PyOpenSslContextCreation, UnspecificContextCreation {
|
||||
UnspecificPyOpenSslContextCreation() { library instanceof PyOpenSsl }
|
||||
class UnspecificPyOpenSslContextCreation extends PyOpenSslContextCreation, UnspecificContextCreation
|
||||
{
|
||||
// UnspecificPyOpenSslContextCreation() { library instanceof PyOpenSsl }
|
||||
}
|
||||
|
||||
class PyOpenSsl extends TlsLibrary {
|
||||
|
||||
@@ -162,23 +162,21 @@ class ContextSetVersion extends ProtocolRestriction, ProtocolUnrestriction, Data
|
||||
}
|
||||
|
||||
class UnspecificSslContextCreation extends SslContextCreation, UnspecificContextCreation {
|
||||
UnspecificSslContextCreation() { library instanceof Ssl }
|
||||
|
||||
override ProtocolVersion getUnrestriction() {
|
||||
result = UnspecificContextCreation.super.getUnrestriction() and
|
||||
// These are turned off by default since Python 3.6
|
||||
// see https://docs.python.org/3.6/library/ssl.html#ssl.SSLContext
|
||||
not result in ["SSLv2", "SSLv3"]
|
||||
}
|
||||
// UnspecificSslContextCreation() { library instanceof Ssl }
|
||||
// override ProtocolVersion getUnrestriction() {
|
||||
// result = UnspecificContextCreation.super.getUnrestriction() and
|
||||
// // These are turned off by default since Python 3.6
|
||||
// // see https://docs.python.org/3.6/library/ssl.html#ssl.SSLContext
|
||||
// not result in ["SSLv2", "SSLv3"]
|
||||
// }
|
||||
}
|
||||
|
||||
class UnspecificSslDefaultContextCreation extends SslDefaultContextCreation, ProtocolUnrestriction {
|
||||
override DataFlow::Node getContext() { result = this }
|
||||
|
||||
// see https://docs.python.org/3/library/ssl.html#ssl.create_default_context
|
||||
override ProtocolVersion getUnrestriction() {
|
||||
result in ["TLSv1", "TLSv1_1", "TLSv1_2", "TLSv1_3"]
|
||||
}
|
||||
class UnspecificSslDefaultContextCreation extends SslDefaultContextCreation {
|
||||
// override DataFlow::Node getContext() { result = this }
|
||||
// // see https://docs.python.org/3/library/ssl.html#ssl.create_default_context
|
||||
// override ProtocolVersion getUnrestriction() {
|
||||
// result in ["TLSv1", "TLSv1_1", "TLSv1_2", "TLSv1_3"]
|
||||
// }
|
||||
}
|
||||
|
||||
class Ssl extends TlsLibrary {
|
||||
|
||||
@@ -15,18 +15,45 @@ class ProtocolVersion extends string {
|
||||
or
|
||||
this = "TLSv1" and version = ["TLSv1_1", "TLSv1_2", "TLSv1_3"]
|
||||
or
|
||||
this = ["TLSv1", "TLSv1_1"] and version = ["TLSv1_2", "TLSv1_3"]
|
||||
this = "TLSv1_1" and version = ["TLSv1_2", "TLSv1_3"]
|
||||
or
|
||||
this = ["TLSv1", "TLSv1_1", "TLSv1_2"] and version = "TLSv1_3"
|
||||
this = "TLSv1_2" and version = "TLSv1_3"
|
||||
}
|
||||
|
||||
/** Holds if this protocol version is known to be insecure. */
|
||||
predicate isInsecure() { this in ["SSLv2", "SSLv3", "TLSv1", "TLSv1_1"] }
|
||||
|
||||
/** Gets the bit mask for this protocol version. */
|
||||
int getBit() {
|
||||
this = "SSLv2" and result = 1
|
||||
or
|
||||
this = "SSLv3" and result = 2
|
||||
or
|
||||
this = "TLSv1" and result = 4
|
||||
or
|
||||
this = "TLSv1_1" and result = 8
|
||||
or
|
||||
this = "TLSv1_2" and result = 16
|
||||
or
|
||||
this = "TLSv1_3" and result = 32
|
||||
}
|
||||
|
||||
/** Gets the protocol family for this protocol version. */
|
||||
ProtocolFamily getFamily() {
|
||||
result = "SSLv23" and this in ["SSLv2", "SSLv3"]
|
||||
or
|
||||
result = "TLS" and this in ["TLSv1", "TLSv1_1", "TLSv1_2", "TLSv1_3"]
|
||||
}
|
||||
}
|
||||
|
||||
/** An unspecific protocol version */
|
||||
class ProtocolFamily extends string {
|
||||
ProtocolFamily() { this in ["SSLv23", "TLS"] }
|
||||
|
||||
/** Gets the bit mask for this protocol family. */
|
||||
int getBits() {
|
||||
result = sum(ProtocolVersion version | version.getFamily() = this | version.getBit())
|
||||
}
|
||||
}
|
||||
|
||||
/** The creation of a context. */
|
||||
@@ -63,21 +90,14 @@ abstract class ProtocolUnrestriction extends DataFlow::Node {
|
||||
* A context is being created with a range of allowed protocols.
|
||||
* This also serves as unrestricting these protocols.
|
||||
*/
|
||||
abstract class UnspecificContextCreation extends ContextCreation, ProtocolUnrestriction {
|
||||
TlsLibrary library;
|
||||
ProtocolFamily family;
|
||||
|
||||
UnspecificContextCreation() { this.getProtocol() = family }
|
||||
|
||||
override DataFlow::CfgNode getContext() { result = this }
|
||||
|
||||
override ProtocolVersion getUnrestriction() {
|
||||
// There is only one family, the two names are aliases in OpenSSL.
|
||||
// see https://github.com/openssl/openssl/blob/13888e797c5a3193e91d71e5f5a196a2d68d266f/include/openssl/ssl.h.in#L1953-L1955
|
||||
family in ["SSLv23", "TLS"] and
|
||||
// see https://docs.python.org/3/library/ssl.html#ssl-contexts
|
||||
result in ["SSLv2", "SSLv3", "TLSv1", "TLSv1_1", "TLSv1_2", "TLSv1_3"]
|
||||
}
|
||||
abstract class UnspecificContextCreation extends ContextCreation {
|
||||
// override ProtocolVersion getUnrestriction() {
|
||||
// // There is only one family, the two names are aliases in OpenSSL.
|
||||
// // see https://github.com/openssl/openssl/blob/13888e797c5a3193e91d71e5f5a196a2d68d266f/include/openssl/ssl.h.in#L1953-L1955
|
||||
// family in ["SSLv23", "TLS"] and
|
||||
// // see https://docs.python.org/3/library/ssl.html#ssl-contexts
|
||||
// result in ["SSLv2", "SSLv3", "TLSv1", "TLSv1_1", "TLSv1_2", "TLSv1_3"]
|
||||
// }
|
||||
}
|
||||
|
||||
/** A model of a SSL/TLS library. */
|
||||
|
||||
@@ -15,7 +15,9 @@ import Undefined
|
||||
import semmle.python.pointsto.PointsTo
|
||||
|
||||
predicate uninitialized_local(NameNode use) {
|
||||
exists(FastLocalVariable local | use.uses(local) or use.deletes(local) | not local.escapes()) and
|
||||
exists(FastLocalVariable local | use.uses(local) or use.deletes(local) |
|
||||
not local.escapes() and not local = any(Nonlocal nl).getAVariable()
|
||||
) and
|
||||
(
|
||||
any(Uninitialized uninit).taints(use) and
|
||||
PointsToInternal::reachableBlock(use.getBasicBlock(), _)
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
---
|
||||
category: newQuery
|
||||
---
|
||||
* Added a new query, `py/shell-command-constructed-from-input`, to detect libraries that unsafely construct shell commands from their inputs.
|
||||
@@ -0,0 +1,4 @@
|
||||
---
|
||||
category: fix
|
||||
---
|
||||
* Nonlocal variables are excluded from alerts.
|
||||
3
python/ql/src/change-notes/released/0.6.4.md
Normal file
3
python/ql/src/change-notes/released/0.6.4.md
Normal file
@@ -0,0 +1,3 @@
|
||||
## 0.6.4
|
||||
|
||||
No user-facing changes.
|
||||
@@ -1,2 +1,2 @@
|
||||
---
|
||||
lastReleaseVersion: 0.6.3
|
||||
lastReleaseVersion: 0.6.4
|
||||
|
||||
@@ -16,7 +16,8 @@ private import semmle.python.frameworks.Tornado
|
||||
abstract class ClientSuppliedIpUsedInSecurityCheck extends DataFlow::Node { }
|
||||
|
||||
private class FlaskClientSuppliedIpUsedInSecurityCheck extends ClientSuppliedIpUsedInSecurityCheck,
|
||||
DataFlow::MethodCallNode {
|
||||
DataFlow::MethodCallNode
|
||||
{
|
||||
FlaskClientSuppliedIpUsedInSecurityCheck() {
|
||||
this = Flask::request().getMember("headers").getMember(["get", "get_all", "getlist"]).getACall() and
|
||||
this.getArg(0).asExpr().(StrConst).getText().toLowerCase() = clientIpParameterName()
|
||||
@@ -24,7 +25,8 @@ private class FlaskClientSuppliedIpUsedInSecurityCheck extends ClientSuppliedIpU
|
||||
}
|
||||
|
||||
private class DjangoClientSuppliedIpUsedInSecurityCheck extends ClientSuppliedIpUsedInSecurityCheck,
|
||||
DataFlow::MethodCallNode {
|
||||
DataFlow::MethodCallNode
|
||||
{
|
||||
DjangoClientSuppliedIpUsedInSecurityCheck() {
|
||||
exists(DataFlow::Node req, DataFlow::AttrRead headers |
|
||||
// a call to request.headers.get or request.META.get
|
||||
@@ -38,7 +40,8 @@ private class DjangoClientSuppliedIpUsedInSecurityCheck extends ClientSuppliedIp
|
||||
}
|
||||
|
||||
private class TornadoClientSuppliedIpUsedInSecurityCheck extends ClientSuppliedIpUsedInSecurityCheck,
|
||||
DataFlow::MethodCallNode {
|
||||
DataFlow::MethodCallNode
|
||||
{
|
||||
TornadoClientSuppliedIpUsedInSecurityCheck() {
|
||||
// a call to self.request.headers.get or self.request.headers.get_list inside a tornado requesthandler
|
||||
exists(
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
name: codeql/python-queries
|
||||
version: 0.6.4-dev
|
||||
version: 0.6.5-dev
|
||||
groups:
|
||||
- python
|
||||
- queries
|
||||
|
||||
@@ -20,15 +20,6 @@ private class MyConsistencyConfiguration extends ConsistencyConfiguration {
|
||||
n instanceof SynthDictSplatParameterNode
|
||||
}
|
||||
|
||||
override predicate uniqueParameterNodeAtPositionExclude(
|
||||
DataFlowCallable c, ParameterPosition pos, Node p
|
||||
) {
|
||||
// TODO: This can be removed once we solve the overlap of dictionary splat parameters
|
||||
c.getParameter(pos) = p and
|
||||
pos.isDictSplat() and
|
||||
not exists(p.getLocation().getFile().getRelativePath())
|
||||
}
|
||||
|
||||
override predicate uniqueParameterNodePositionExclude(
|
||||
DataFlowCallable c, ParameterPosition pos, Node p
|
||||
) {
|
||||
@@ -44,4 +35,8 @@ private class MyConsistencyConfiguration extends ConsistencyConfiguration {
|
||||
param = func.getArgByName(_)
|
||||
)
|
||||
}
|
||||
|
||||
override predicate uniqueCallEnclosingCallableExclude(DataFlowCall call) {
|
||||
not exists(call.getLocation().getFile().getRelativePath())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
uniqueEnclosingCallable
|
||||
uniqueCallEnclosingCallable
|
||||
uniqueType
|
||||
uniqueNodeLocation
|
||||
missingLocation
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
uniqueEnclosingCallable
|
||||
uniqueCallEnclosingCallable
|
||||
uniqueType
|
||||
uniqueNodeLocation
|
||||
missingLocation
|
||||
|
||||
@@ -1,4 +1,9 @@
|
||||
uniqueEnclosingCallable
|
||||
uniqueCallEnclosingCallable
|
||||
| new_cls_param.py:14:6:14:16 | classmethod() | Call should have one enclosing callable but has 0. |
|
||||
| test.py:21:6:21:17 | staticmethod() | Call should have one enclosing callable but has 0. |
|
||||
| test.py:25:6:25:16 | classmethod() | Call should have one enclosing callable but has 0. |
|
||||
| test.py:29:6:29:16 | classmethod() | Call should have one enclosing callable but has 0. |
|
||||
uniqueType
|
||||
uniqueNodeLocation
|
||||
missingLocation
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
uniqueEnclosingCallable
|
||||
uniqueCallEnclosingCallable
|
||||
uniqueType
|
||||
uniqueNodeLocation
|
||||
missingLocation
|
||||
|
||||
@@ -204,6 +204,18 @@ def test_mixed():
|
||||
mixed(**args)
|
||||
|
||||
|
||||
def kwargs_same_name_as_positional_only(a, /, **kwargs):
|
||||
SINK1(a)
|
||||
SINK2(kwargs["a"])
|
||||
|
||||
@expects(2*2)
|
||||
def test_kwargs_same_name_as_positional_only():
|
||||
kwargs_same_name_as_positional_only(arg1, a=arg2) # $ arg1 SPURIOUS: bad1="arg2" MISSING: arg2
|
||||
|
||||
kwargs = {"a": arg2} # $ func=kwargs_same_name_as_positional_only SPURIOUS: bad1="arg2" MISSING: arg2
|
||||
kwargs_same_name_as_positional_only(arg1, **kwargs) # $ arg1
|
||||
|
||||
|
||||
def starargs_only(*args):
|
||||
SINK1(args[0])
|
||||
SINK2(args[1])
|
||||
|
||||
@@ -1,4 +1,7 @@
|
||||
uniqueEnclosingCallable
|
||||
uniqueCallEnclosingCallable
|
||||
| datamodel.py:71:6:71:16 | classmethod() | Call should have one enclosing callable but has 0. |
|
||||
| datamodel.py:76:6:76:17 | staticmethod() | Call should have one enclosing callable but has 0. |
|
||||
uniqueType
|
||||
uniqueNodeLocation
|
||||
missingLocation
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
uniqueEnclosingCallable
|
||||
uniqueCallEnclosingCallable
|
||||
uniqueType
|
||||
uniqueNodeLocation
|
||||
missingLocation
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
uniqueEnclosingCallable
|
||||
uniqueCallEnclosingCallable
|
||||
uniqueType
|
||||
uniqueNodeLocation
|
||||
missingLocation
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
uniqueEnclosingCallable
|
||||
uniqueCallEnclosingCallable
|
||||
uniqueType
|
||||
uniqueNodeLocation
|
||||
missingLocation
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
uniqueEnclosingCallable
|
||||
uniqueCallEnclosingCallable
|
||||
uniqueType
|
||||
uniqueNodeLocation
|
||||
missingLocation
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
uniqueEnclosingCallable
|
||||
uniqueCallEnclosingCallable
|
||||
uniqueType
|
||||
uniqueNodeLocation
|
||||
missingLocation
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
uniqueEnclosingCallable
|
||||
uniqueCallEnclosingCallable
|
||||
uniqueType
|
||||
uniqueNodeLocation
|
||||
missingLocation
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
uniqueEnclosingCallable
|
||||
uniqueCallEnclosingCallable
|
||||
uniqueType
|
||||
uniqueNodeLocation
|
||||
missingLocation
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
uniqueEnclosingCallable
|
||||
uniqueCallEnclosingCallable
|
||||
uniqueType
|
||||
uniqueNodeLocation
|
||||
missingLocation
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
uniqueEnclosingCallable
|
||||
uniqueCallEnclosingCallable
|
||||
uniqueType
|
||||
uniqueNodeLocation
|
||||
missingLocation
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
uniqueEnclosingCallable
|
||||
uniqueCallEnclosingCallable
|
||||
uniqueType
|
||||
uniqueNodeLocation
|
||||
missingLocation
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
uniqueEnclosingCallable
|
||||
uniqueCallEnclosingCallable
|
||||
uniqueType
|
||||
uniqueNodeLocation
|
||||
missingLocation
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
uniqueEnclosingCallable
|
||||
uniqueCallEnclosingCallable
|
||||
uniqueType
|
||||
uniqueNodeLocation
|
||||
missingLocation
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
uniqueEnclosingCallable
|
||||
uniqueCallEnclosingCallable
|
||||
uniqueType
|
||||
uniqueNodeLocation
|
||||
missingLocation
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
uniqueEnclosingCallable
|
||||
uniqueCallEnclosingCallable
|
||||
uniqueType
|
||||
uniqueNodeLocation
|
||||
missingLocation
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
uniqueEnclosingCallable
|
||||
uniqueCallEnclosingCallable
|
||||
uniqueType
|
||||
uniqueNodeLocation
|
||||
missingLocation
|
||||
|
||||
@@ -0,0 +1,64 @@
|
||||
# Here we test the case where a captured variable is being read.
|
||||
|
||||
# All functions starting with "test_" should run and execute `print("OK")` exactly once.
|
||||
# This can be checked by running validTest.py.
|
||||
|
||||
import sys
|
||||
import os
|
||||
|
||||
sys.path.append(os.path.dirname(os.path.dirname((__file__))))
|
||||
from testlib import expects
|
||||
|
||||
# These are defined so that we can evaluate the test code.
|
||||
NONSOURCE = "not a source"
|
||||
SOURCE = "source"
|
||||
|
||||
def is_source(x):
|
||||
return x == "source" or x == b"source" or x == 42 or x == 42.0 or x == 42j
|
||||
|
||||
|
||||
def SINK(x):
|
||||
if is_source(x):
|
||||
print("OK")
|
||||
else:
|
||||
print("Unexpected flow", x)
|
||||
|
||||
|
||||
def SINK_F(x):
|
||||
if is_source(x):
|
||||
print("Unexpected flow", x)
|
||||
else:
|
||||
print("OK")
|
||||
|
||||
l = [NONSOURCE]
|
||||
SINK_F(l_mod[0])
|
||||
|
||||
l_mod = [SOURCE for x in l]
|
||||
SINK(l_mod[0]) #$ captured
|
||||
|
||||
l_mod_lambda = [(lambda a : SOURCE)(x) for x in l]
|
||||
SINK(l_mod_lambda[0]) #$ captured
|
||||
|
||||
def mod(x):
|
||||
return SOURCE
|
||||
|
||||
l_mod_function = [mod(x) for x in l]
|
||||
SINK(l_mod_function[0]) #$ captured
|
||||
|
||||
def mod_list(l):
|
||||
def mod_local(x):
|
||||
return SOURCE
|
||||
|
||||
return [mod_local(x) for x in l]
|
||||
|
||||
l_modded = mod_list(l)
|
||||
SINK(l_modded[0]) #$ MISSING: captured
|
||||
|
||||
def mod_list_first(l):
|
||||
def mod_local(x):
|
||||
return SOURCE
|
||||
|
||||
return [mod_local(l[0])]
|
||||
|
||||
l_modded_first = mod_list_first(l)
|
||||
SINK(l_modded_first[0]) #$ captured
|
||||
@@ -1,4 +1,5 @@
|
||||
uniqueEnclosingCallable
|
||||
uniqueCallEnclosingCallable
|
||||
uniqueType
|
||||
uniqueNodeLocation
|
||||
missingLocation
|
||||
|
||||
@@ -1,4 +1,36 @@
|
||||
uniqueEnclosingCallable
|
||||
uniqueCallEnclosingCallable
|
||||
| code/bound_method_arg.py:5:6:5:16 | classmethod() | Call should have one enclosing callable but has 0. |
|
||||
| code/callable_as_argument.py:37:6:37:17 | staticmethod() | Call should have one enclosing callable but has 0. |
|
||||
| code/callable_as_argument.py:49:10:49:21 | staticmethod() | Call should have one enclosing callable but has 0. |
|
||||
| code/class_construction.py:13:6:13:16 | classmethod() | Call should have one enclosing callable but has 0. |
|
||||
| code/class_properties.py:9:6:9:13 | property() | Call should have one enclosing callable but has 0. |
|
||||
| code/class_properties.py:11:9:11:32 | print() | Call should have one enclosing callable but has 0. |
|
||||
| code/class_properties.py:14:6:14:15 | Attribute() | Call should have one enclosing callable but has 0. |
|
||||
| code/class_properties.py:19:6:19:16 | Attribute() | Call should have one enclosing callable but has 0. |
|
||||
| code/class_properties.py:36:12:36:62 | property() | Call should have one enclosing callable but has 0. |
|
||||
| code/class_properties.py:38:6:38:13 | property() | Call should have one enclosing callable but has 0. |
|
||||
| code/class_properties.py:40:9:40:38 | print() | Call should have one enclosing callable but has 0. |
|
||||
| code/class_subclass.py:10:6:10:17 | staticmethod() | Call should have one enclosing callable but has 0. |
|
||||
| code/class_subclass.py:14:6:14:16 | classmethod() | Call should have one enclosing callable but has 0. |
|
||||
| code/class_subclass.py:104:6:104:17 | staticmethod() | Call should have one enclosing callable but has 0. |
|
||||
| code/class_subclass.py:108:6:108:16 | classmethod() | Call should have one enclosing callable but has 0. |
|
||||
| code/class_subclass.py:112:6:112:17 | staticmethod() | Call should have one enclosing callable but has 0. |
|
||||
| code/class_subclass.py:116:6:116:16 | classmethod() | Call should have one enclosing callable but has 0. |
|
||||
| code/class_subclass.py:120:6:120:16 | classmethod() | Call should have one enclosing callable but has 0. |
|
||||
| code/class_subclass.py:149:6:149:17 | staticmethod() | Call should have one enclosing callable but has 0. |
|
||||
| code/class_subclass.py:153:6:153:16 | classmethod() | Call should have one enclosing callable but has 0. |
|
||||
| code/class_super.py:13:6:13:16 | classmethod() | Call should have one enclosing callable but has 0. |
|
||||
| code/class_super.py:28:6:28:17 | staticmethod() | Call should have one enclosing callable but has 0. |
|
||||
| code/class_super.py:36:6:36:16 | classmethod() | Call should have one enclosing callable but has 0. |
|
||||
| code/class_super.py:40:6:40:16 | classmethod() | Call should have one enclosing callable but has 0. |
|
||||
| code/func_defined_outside_class.py:17:18:17:41 | staticmethod() | Call should have one enclosing callable but has 0. |
|
||||
| code/func_defined_outside_class.py:18:18:18:40 | classmethod() | Call should have one enclosing callable but has 0. |
|
||||
| code/func_defined_outside_class.py:38:11:38:21 | _gen() | Call should have one enclosing callable but has 0. |
|
||||
| code/func_defined_outside_class.py:39:11:39:21 | _gen() | Call should have one enclosing callable but has 0. |
|
||||
| code/nested_class.py:3:10:3:21 | staticmethod() | Call should have one enclosing callable but has 0. |
|
||||
| code/nested_class.py:7:10:7:21 | staticmethod() | Call should have one enclosing callable but has 0. |
|
||||
| code/self_passing.py:60:6:60:16 | classmethod() | Call should have one enclosing callable but has 0. |
|
||||
uniqueType
|
||||
uniqueNodeLocation
|
||||
missingLocation
|
||||
|
||||
@@ -14,7 +14,7 @@ import experimental.meta.InlineTaintTest::Conf
|
||||
import DataFlow::PathGraph
|
||||
|
||||
class Conf extends TestTaintTrackingConfiguration {
|
||||
override int explorationLimit() { result = 5 }
|
||||
// override int explorationLimit() { result = 5 }
|
||||
}
|
||||
|
||||
// from Conf config, DataFlow::PartialPathNode source, DataFlow::PartialPathNode sink
|
||||
|
||||
@@ -1,4 +1,8 @@
|
||||
uniqueEnclosingCallable
|
||||
uniqueCallEnclosingCallable
|
||||
| test_captured.py:7:22:7:25 | p() | Call should have one enclosing callable but has 0. |
|
||||
| test_captured.py:7:22:7:25 | p() | Call should have one enclosing callable but has 0. |
|
||||
| test_captured.py:14:26:14:30 | pp() | Call should have one enclosing callable but has 0. |
|
||||
uniqueType
|
||||
uniqueNodeLocation
|
||||
missingLocation
|
||||
|
||||
14
python/ql/test/library-tests/ApiGraphs/py3/test_captured.py
Normal file
14
python/ql/test/library-tests/ApiGraphs/py3/test_captured.py
Normal file
@@ -0,0 +1,14 @@
|
||||
from html import escape
|
||||
|
||||
def p(x):
|
||||
return escape(x) #$ use=moduleImport("html").getMember("escape").getReturn()
|
||||
|
||||
def p_list(l):
|
||||
return ", ".join(p(x) for x in l) #$ use=moduleImport("html").getMember("escape").getReturn()
|
||||
|
||||
def pp_list(l):
|
||||
def pp(x):
|
||||
return escape(x) #$ use=moduleImport("html").getMember("escape").getReturn()
|
||||
|
||||
def pp_list_inner(l):
|
||||
return ", ".join(pp(x) for x in l) #$ MISSING: use=moduleImport("html").getMember("escape").getReturn()
|
||||
@@ -1,4 +1,40 @@
|
||||
uniqueEnclosingCallable
|
||||
uniqueCallEnclosingCallable
|
||||
| testapp/orm_form_test.py:7:12:7:43 | Attribute() | Call should have one enclosing callable but has 0. |
|
||||
| testapp/orm_inheritance.py:30:13:30:44 | Attribute() | Call should have one enclosing callable but has 0. |
|
||||
| testapp/orm_inheritance.py:34:25:34:56 | Attribute() | Call should have one enclosing callable but has 0. |
|
||||
| testapp/orm_inheritance.py:35:33:35:64 | Attribute() | Call should have one enclosing callable but has 0. |
|
||||
| testapp/orm_inheritance.py:39:21:39:52 | Attribute() | Call should have one enclosing callable but has 0. |
|
||||
| testapp/orm_inheritance.py:40:33:40:64 | Attribute() | Call should have one enclosing callable but has 0. |
|
||||
| testapp/orm_inheritance.py:118:13:118:44 | Attribute() | Call should have one enclosing callable but has 0. |
|
||||
| testapp/orm_inheritance.py:122:25:122:56 | Attribute() | Call should have one enclosing callable but has 0. |
|
||||
| testapp/orm_inheritance.py:123:33:123:64 | Attribute() | Call should have one enclosing callable but has 0. |
|
||||
| testapp/orm_inheritance.py:127:21:127:52 | Attribute() | Call should have one enclosing callable but has 0. |
|
||||
| testapp/orm_inheritance.py:128:33:128:64 | Attribute() | Call should have one enclosing callable but has 0. |
|
||||
| testapp/orm_security_tests.py:16:12:16:43 | Attribute() | Call should have one enclosing callable but has 0. |
|
||||
| testapp/orm_security_tests.py:17:11:17:31 | Attribute() | Call should have one enclosing callable but has 0. |
|
||||
| testapp/orm_security_tests.py:93:12:93:65 | Attribute() | Call should have one enclosing callable but has 0. |
|
||||
| testapp/orm_security_tests.py:112:12:112:65 | Attribute() | Call should have one enclosing callable but has 0. |
|
||||
| testapp/orm_tests.py:29:12:29:43 | Attribute() | Call should have one enclosing callable but has 0. |
|
||||
| testapp/orm_tests.py:43:12:43:43 | Attribute() | Call should have one enclosing callable but has 0. |
|
||||
| testapp/orm_tests.py:59:12:59:61 | Attribute() | Call should have one enclosing callable but has 0. |
|
||||
| testapp/orm_tests.py:74:12:74:43 | Attribute() | Call should have one enclosing callable but has 0. |
|
||||
| testapp/orm_tests.py:90:12:90:43 | Attribute() | Call should have one enclosing callable but has 0. |
|
||||
| testapp/orm_tests.py:111:12:111:43 | Attribute() | Call should have one enclosing callable but has 0. |
|
||||
| testapp/orm_tests.py:127:12:127:43 | Attribute() | Call should have one enclosing callable but has 0. |
|
||||
| testapp/orm_tests.py:128:13:128:44 | Attribute() | Call should have one enclosing callable but has 0. |
|
||||
| testapp/orm_tests.py:145:12:145:43 | Attribute() | Call should have one enclosing callable but has 0. |
|
||||
| testapp/orm_tests.py:146:13:146:44 | Attribute() | Call should have one enclosing callable but has 0. |
|
||||
| testapp/orm_tests.py:162:12:162:43 | Attribute() | Call should have one enclosing callable but has 0. |
|
||||
| testapp/orm_tests.py:178:12:178:43 | Attribute() | Call should have one enclosing callable but has 0. |
|
||||
| testapp/orm_tests.py:181:12:181:64 | Attribute() | Call should have one enclosing callable but has 0. |
|
||||
| testapp/orm_tests.py:207:12:207:43 | Attribute() | Call should have one enclosing callable but has 0. |
|
||||
| testapp/orm_tests.py:208:12:208:70 | Attribute() | Call should have one enclosing callable but has 0. |
|
||||
| testapp/orm_tests.py:234:12:234:43 | Attribute() | Call should have one enclosing callable but has 0. |
|
||||
| testapp/orm_tests.py:235:12:235:95 | Attribute() | Call should have one enclosing callable but has 0. |
|
||||
| testapp/orm_tests.py:256:12:256:43 | Attribute() | Call should have one enclosing callable but has 0. |
|
||||
| testapp/orm_tests.py:274:12:274:43 | Attribute() | Call should have one enclosing callable but has 0. |
|
||||
| testapp/orm_tests.py:295:12:295:43 | Attribute() | Call should have one enclosing callable but has 0. |
|
||||
uniqueType
|
||||
uniqueNodeLocation
|
||||
missingLocation
|
||||
|
||||
@@ -140,7 +140,7 @@ subprocess.Popen(args) # $getCommand=args
|
||||
args = "<progname>"
|
||||
use_shell = False
|
||||
exe = "executable"
|
||||
subprocess.Popen(args, shell=use_shell, executable=exe) # $getCommand=exe SPURIOUS: getCommand=args
|
||||
subprocess.Popen(args, shell=use_shell, executable=exe) # $getCommand=exe
|
||||
|
||||
|
||||
################################################################################
|
||||
|
||||
@@ -0,0 +1,2 @@
|
||||
missingAnnotationOnSink
|
||||
failures
|
||||
@@ -0,0 +1,3 @@
|
||||
import python
|
||||
import experimental.dataflow.TestUtil.DataflowQueryTest
|
||||
import semmle.python.security.dataflow.UnsafeShellCommandConstructionQuery
|
||||
@@ -0,0 +1,40 @@
|
||||
edges
|
||||
| src/unsafe_shell_test.py:4:22:4:25 | ControlFlowNode for name | src/unsafe_shell_test.py:5:25:5:28 | ControlFlowNode for name |
|
||||
| src/unsafe_shell_test.py:4:22:4:25 | ControlFlowNode for name | src/unsafe_shell_test.py:8:23:8:26 | ControlFlowNode for name |
|
||||
| src/unsafe_shell_test.py:4:22:4:25 | ControlFlowNode for name | src/unsafe_shell_test.py:11:25:11:38 | ControlFlowNode for Attribute() |
|
||||
| src/unsafe_shell_test.py:4:22:4:25 | ControlFlowNode for name | src/unsafe_shell_test.py:14:34:14:39 | ControlFlowNode for List |
|
||||
| src/unsafe_shell_test.py:4:22:4:25 | ControlFlowNode for name | src/unsafe_shell_test.py:17:32:17:35 | ControlFlowNode for name |
|
||||
| src/unsafe_shell_test.py:4:22:4:25 | ControlFlowNode for name | src/unsafe_shell_test.py:20:27:20:30 | ControlFlowNode for name |
|
||||
| src/unsafe_shell_test.py:14:34:14:39 | ControlFlowNode for List | src/unsafe_shell_test.py:14:25:14:40 | ControlFlowNode for Attribute() |
|
||||
| src/unsafe_shell_test.py:26:20:26:23 | ControlFlowNode for name | src/unsafe_shell_test.py:29:30:29:33 | ControlFlowNode for name |
|
||||
| src/unsafe_shell_test.py:36:22:36:25 | ControlFlowNode for name | src/unsafe_shell_test.py:39:30:39:33 | ControlFlowNode for name |
|
||||
| src/unsafe_shell_test.py:36:22:36:25 | ControlFlowNode for name | src/unsafe_shell_test.py:44:20:44:23 | ControlFlowNode for name |
|
||||
| src/unsafe_shell_test.py:41:24:41:24 | ControlFlowNode for x | src/unsafe_shell_test.py:42:34:42:34 | ControlFlowNode for x |
|
||||
| src/unsafe_shell_test.py:44:20:44:23 | ControlFlowNode for name | src/unsafe_shell_test.py:41:24:41:24 | ControlFlowNode for x |
|
||||
nodes
|
||||
| src/unsafe_shell_test.py:4:22:4:25 | ControlFlowNode for name | semmle.label | ControlFlowNode for name |
|
||||
| src/unsafe_shell_test.py:5:25:5:28 | ControlFlowNode for name | semmle.label | ControlFlowNode for name |
|
||||
| src/unsafe_shell_test.py:8:23:8:26 | ControlFlowNode for name | semmle.label | ControlFlowNode for name |
|
||||
| src/unsafe_shell_test.py:11:25:11:38 | ControlFlowNode for Attribute() | semmle.label | ControlFlowNode for Attribute() |
|
||||
| src/unsafe_shell_test.py:14:25:14:40 | ControlFlowNode for Attribute() | semmle.label | ControlFlowNode for Attribute() |
|
||||
| src/unsafe_shell_test.py:14:34:14:39 | ControlFlowNode for List | semmle.label | ControlFlowNode for List |
|
||||
| src/unsafe_shell_test.py:17:32:17:35 | ControlFlowNode for name | semmle.label | ControlFlowNode for name |
|
||||
| src/unsafe_shell_test.py:20:27:20:30 | ControlFlowNode for name | semmle.label | ControlFlowNode for name |
|
||||
| src/unsafe_shell_test.py:26:20:26:23 | ControlFlowNode for name | semmle.label | ControlFlowNode for name |
|
||||
| src/unsafe_shell_test.py:29:30:29:33 | ControlFlowNode for name | semmle.label | ControlFlowNode for name |
|
||||
| src/unsafe_shell_test.py:36:22:36:25 | ControlFlowNode for name | semmle.label | ControlFlowNode for name |
|
||||
| src/unsafe_shell_test.py:39:30:39:33 | ControlFlowNode for name | semmle.label | ControlFlowNode for name |
|
||||
| src/unsafe_shell_test.py:41:24:41:24 | ControlFlowNode for x | semmle.label | ControlFlowNode for x |
|
||||
| src/unsafe_shell_test.py:42:34:42:34 | ControlFlowNode for x | semmle.label | ControlFlowNode for x |
|
||||
| src/unsafe_shell_test.py:44:20:44:23 | ControlFlowNode for name | semmle.label | ControlFlowNode for name |
|
||||
subpaths
|
||||
#select
|
||||
| src/unsafe_shell_test.py:5:15:5:28 | ControlFlowNode for BinaryExpr | src/unsafe_shell_test.py:4:22:4:25 | ControlFlowNode for name | src/unsafe_shell_test.py:5:25:5:28 | ControlFlowNode for name | This string concatenation which depends on $@ is later used in a $@. | src/unsafe_shell_test.py:4:22:4:25 | ControlFlowNode for name | library input | src/unsafe_shell_test.py:5:5:5:29 | ControlFlowNode for Attribute() | shell command |
|
||||
| src/unsafe_shell_test.py:8:15:8:28 | ControlFlowNode for Fstring | src/unsafe_shell_test.py:4:22:4:25 | ControlFlowNode for name | src/unsafe_shell_test.py:8:23:8:26 | ControlFlowNode for name | This f-string which depends on $@ is later used in a $@. | src/unsafe_shell_test.py:4:22:4:25 | ControlFlowNode for name | library input | src/unsafe_shell_test.py:8:5:8:29 | ControlFlowNode for Attribute() | shell command |
|
||||
| src/unsafe_shell_test.py:11:15:11:38 | ControlFlowNode for BinaryExpr | src/unsafe_shell_test.py:4:22:4:25 | ControlFlowNode for name | src/unsafe_shell_test.py:11:25:11:38 | ControlFlowNode for Attribute() | This string concatenation which depends on $@ is later used in a $@. | src/unsafe_shell_test.py:4:22:4:25 | ControlFlowNode for name | library input | src/unsafe_shell_test.py:11:5:11:39 | ControlFlowNode for Attribute() | shell command |
|
||||
| src/unsafe_shell_test.py:14:15:14:40 | ControlFlowNode for BinaryExpr | src/unsafe_shell_test.py:4:22:4:25 | ControlFlowNode for name | src/unsafe_shell_test.py:14:25:14:40 | ControlFlowNode for Attribute() | This string concatenation which depends on $@ is later used in a $@. | src/unsafe_shell_test.py:4:22:4:25 | ControlFlowNode for name | library input | src/unsafe_shell_test.py:14:5:14:41 | ControlFlowNode for Attribute() | shell command |
|
||||
| src/unsafe_shell_test.py:17:15:17:36 | ControlFlowNode for Attribute() | src/unsafe_shell_test.py:4:22:4:25 | ControlFlowNode for name | src/unsafe_shell_test.py:17:32:17:35 | ControlFlowNode for name | This formatted string which depends on $@ is later used in a $@. | src/unsafe_shell_test.py:4:22:4:25 | ControlFlowNode for name | library input | src/unsafe_shell_test.py:17:5:17:37 | ControlFlowNode for Attribute() | shell command |
|
||||
| src/unsafe_shell_test.py:20:15:20:30 | ControlFlowNode for BinaryExpr | src/unsafe_shell_test.py:4:22:4:25 | ControlFlowNode for name | src/unsafe_shell_test.py:20:27:20:30 | ControlFlowNode for name | This formatted string which depends on $@ is later used in a $@. | src/unsafe_shell_test.py:4:22:4:25 | ControlFlowNode for name | library input | src/unsafe_shell_test.py:20:5:20:31 | ControlFlowNode for Attribute() | shell command |
|
||||
| src/unsafe_shell_test.py:29:20:29:33 | ControlFlowNode for BinaryExpr | src/unsafe_shell_test.py:26:20:26:23 | ControlFlowNode for name | src/unsafe_shell_test.py:29:30:29:33 | ControlFlowNode for name | This string concatenation which depends on $@ is later used in a $@. | src/unsafe_shell_test.py:26:20:26:23 | ControlFlowNode for name | library input | src/unsafe_shell_test.py:29:5:29:46 | ControlFlowNode for Attribute() | shell command |
|
||||
| src/unsafe_shell_test.py:39:20:39:33 | ControlFlowNode for BinaryExpr | src/unsafe_shell_test.py:36:22:36:25 | ControlFlowNode for name | src/unsafe_shell_test.py:39:30:39:33 | ControlFlowNode for name | This string concatenation which depends on $@ is later used in a $@. | src/unsafe_shell_test.py:36:22:36:25 | ControlFlowNode for name | library input | src/unsafe_shell_test.py:39:5:39:46 | ControlFlowNode for Attribute() | shell command |
|
||||
| src/unsafe_shell_test.py:42:24:42:34 | ControlFlowNode for BinaryExpr | src/unsafe_shell_test.py:36:22:36:25 | ControlFlowNode for name | src/unsafe_shell_test.py:42:34:42:34 | ControlFlowNode for x | This string concatenation which depends on $@ is later used in a $@. | src/unsafe_shell_test.py:36:22:36:25 | ControlFlowNode for name | library input | src/unsafe_shell_test.py:42:9:42:47 | ControlFlowNode for Attribute() | shell command |
|
||||
@@ -0,0 +1 @@
|
||||
Security/CWE-078/UnsafeShellCommandConstruction.ql
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user