Merge branch 'main' into import-refined

This commit is contained in:
Rasmus Wriedt Larsen
2023-03-20 15:42:01 +01:00
2604 changed files with 138727 additions and 220393 deletions

View File

@@ -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

View File

@@ -1,4 +0,0 @@
---
category: minorAnalysis
---
* Added modeling of cryptographic operations in the `hmac` library.

View File

@@ -1,4 +0,0 @@
---
category: minorAnalysis
---
* Fixed module resolution so we properly recognize definitions made within if-then-else statements.

View File

@@ -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.

View File

@@ -0,0 +1,4 @@
---
category: minorAnalysis
---
* Deleted the deprecated `getPath` and `getFolder` predicates from the `XmlFile` class.

View 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.

View File

@@ -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.

View File

@@ -1,2 +1,2 @@
---
lastReleaseVersion: 0.8.0
lastReleaseVersion: 0.8.1

View File

@@ -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

View File

@@ -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.
*/

View File

@@ -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

View File

@@ -22,5 +22,6 @@ private import python
* global (inter-procedural) data flow analyses.
*/
module DataFlow {
import internal.DataFlowImpl
import internal.DataFlow
import internal.DataFlowImpl1
}

View File

@@ -15,5 +15,6 @@ private import python
* global (inter-procedural) taint-tracking analyses.
*/
module TaintTracking {
import internal.tainttracking1.TaintTracking
import internal.tainttracking1.TaintTrackingImpl
}

View 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())
}
}
}

View File

@@ -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 }

View File

@@ -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;

View File

@@ -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()

View File

@@ -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

View File

@@ -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() }

View File

@@ -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")
}

View File

@@ -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) }

View File

@@ -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>
}

View File

@@ -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
}

View File

@@ -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()
}

View File

@@ -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() {

View File

@@ -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() {

View File

@@ -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()
}

View File

@@ -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() }
}
/**

View File

@@ -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

View File

@@ -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() }

View File

@@ -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() }
}
}

View File

@@ -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()
}

View File

@@ -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()

View File

@@ -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()
}

View File

@@ -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() {

View File

@@ -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() {

View 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()
}
}

View File

@@ -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"

View File

@@ -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")

View File

@@ -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")
}

View File

@@ -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

View File

@@ -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) {

View File

@@ -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 }
}
}

View File

@@ -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
}
}

View File

@@ -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) }

View File

@@ -1,3 +1,7 @@
## 0.6.4
No user-facing changes.
## 0.6.3
No user-facing changes.

View File

@@ -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>

View File

@@ -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"

View File

@@ -0,0 +1,4 @@
import os
def download(path):
os.system("wget " + path) # NOT OK

View File

@@ -0,0 +1,4 @@
import subprocess
def download(path):
subprocess.run(["wget", path]) # OK

View File

@@ -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

View File

@@ -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 {

View File

@@ -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 {

View File

@@ -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. */

View File

@@ -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(), _)

View File

@@ -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.

View File

@@ -0,0 +1,4 @@
---
category: fix
---
* Nonlocal variables are excluded from alerts.

View File

@@ -0,0 +1,3 @@
## 0.6.4
No user-facing changes.

View File

@@ -1,2 +1,2 @@
---
lastReleaseVersion: 0.6.3
lastReleaseVersion: 0.6.4

View File

@@ -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(

View File

@@ -1,5 +1,5 @@
name: codeql/python-queries
version: 0.6.4-dev
version: 0.6.5-dev
groups:
- python
- queries

View File

@@ -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())
}
}

View File

@@ -1,4 +1,5 @@
uniqueEnclosingCallable
uniqueCallEnclosingCallable
uniqueType
uniqueNodeLocation
missingLocation

View File

@@ -1,4 +1,5 @@
uniqueEnclosingCallable
uniqueCallEnclosingCallable
uniqueType
uniqueNodeLocation
missingLocation

View File

@@ -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

View File

@@ -1,4 +1,5 @@
uniqueEnclosingCallable
uniqueCallEnclosingCallable
uniqueType
uniqueNodeLocation
missingLocation

View File

@@ -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])

View File

@@ -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

View File

@@ -1,4 +1,5 @@
uniqueEnclosingCallable
uniqueCallEnclosingCallable
uniqueType
uniqueNodeLocation
missingLocation

View File

@@ -1,4 +1,5 @@
uniqueEnclosingCallable
uniqueCallEnclosingCallable
uniqueType
uniqueNodeLocation
missingLocation

View File

@@ -1,4 +1,5 @@
uniqueEnclosingCallable
uniqueCallEnclosingCallable
uniqueType
uniqueNodeLocation
missingLocation

View File

@@ -1,4 +1,5 @@
uniqueEnclosingCallable
uniqueCallEnclosingCallable
uniqueType
uniqueNodeLocation
missingLocation

View File

@@ -1,4 +1,5 @@
uniqueEnclosingCallable
uniqueCallEnclosingCallable
uniqueType
uniqueNodeLocation
missingLocation

View File

@@ -1,4 +1,5 @@
uniqueEnclosingCallable
uniqueCallEnclosingCallable
uniqueType
uniqueNodeLocation
missingLocation

View File

@@ -1,4 +1,5 @@
uniqueEnclosingCallable
uniqueCallEnclosingCallable
uniqueType
uniqueNodeLocation
missingLocation

View File

@@ -1,4 +1,5 @@
uniqueEnclosingCallable
uniqueCallEnclosingCallable
uniqueType
uniqueNodeLocation
missingLocation

View File

@@ -1,4 +1,5 @@
uniqueEnclosingCallable
uniqueCallEnclosingCallable
uniqueType
uniqueNodeLocation
missingLocation

View File

@@ -1,4 +1,5 @@
uniqueEnclosingCallable
uniqueCallEnclosingCallable
uniqueType
uniqueNodeLocation
missingLocation

View File

@@ -1,4 +1,5 @@
uniqueEnclosingCallable
uniqueCallEnclosingCallable
uniqueType
uniqueNodeLocation
missingLocation

View File

@@ -1,4 +1,5 @@
uniqueEnclosingCallable
uniqueCallEnclosingCallable
uniqueType
uniqueNodeLocation
missingLocation

View File

@@ -1,4 +1,5 @@
uniqueEnclosingCallable
uniqueCallEnclosingCallable
uniqueType
uniqueNodeLocation
missingLocation

View File

@@ -1,4 +1,5 @@
uniqueEnclosingCallable
uniqueCallEnclosingCallable
uniqueType
uniqueNodeLocation
missingLocation

View File

@@ -1,4 +1,5 @@
uniqueEnclosingCallable
uniqueCallEnclosingCallable
uniqueType
uniqueNodeLocation
missingLocation

View File

@@ -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

View File

@@ -1,4 +1,5 @@
uniqueEnclosingCallable
uniqueCallEnclosingCallable
uniqueType
uniqueNodeLocation
missingLocation

View File

@@ -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

View File

@@ -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

View File

@@ -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

View 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()

View File

@@ -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

View File

@@ -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
################################################################################

View File

@@ -0,0 +1,2 @@
missingAnnotationOnSink
failures

View File

@@ -0,0 +1,3 @@
import python
import experimental.dataflow.TestUtil.DataflowQueryTest
import semmle.python.security.dataflow.UnsafeShellCommandConstructionQuery

View File

@@ -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 |

View File

@@ -0,0 +1 @@
Security/CWE-078/UnsafeShellCommandConstruction.ql

Some files were not shown because too many files have changed in this diff Show More