mirror of
https://github.com/github/codeql.git
synced 2026-07-20 18:58:36 +02:00
Merge branch 'main' into python/port-tarslip
This commit is contained in:
@@ -1,3 +1,11 @@
|
||||
## 0.4.1
|
||||
|
||||
## 0.4.0
|
||||
|
||||
### Breaking Changes
|
||||
|
||||
* `API::moduleImport` no longer has any results for dotted names, such as `API::moduleImport("foo.bar")`. Using `API::moduleImport("foo.bar").getMember("baz").getACall()` previously worked if the Python code was `from foo.bar import baz; baz()`, but not if the code was `import foo.bar; foo.bar.baz()` -- we are making this change to ensure the approach that can handle all cases is always used.
|
||||
|
||||
## 0.3.0
|
||||
|
||||
### Breaking Changes
|
||||
|
||||
@@ -1,4 +0,0 @@
|
||||
---
|
||||
category: breaking
|
||||
---
|
||||
`API::moduleImport` no longer has any results for dotted names, such as `API::moduleImport("foo.bar")`. Using `API::moduleImport("foo.bar").getMember("baz").getACall()` previously worked if the Python code was `from foo.bar import baz; baz()`, but not if the code was `import foo.bar; foo.bar.baz()` -- we are making this change to ensure the approach that can handle all cases is always used.
|
||||
@@ -0,0 +1,4 @@
|
||||
---
|
||||
category: deprecated
|
||||
---
|
||||
* The `BarrierGuard` class has been deprecated. Such barriers and sanitizers can now instead be created using the new `BarrierGuard` parameterized module.
|
||||
@@ -0,0 +1,4 @@
|
||||
---
|
||||
category: minorAnalysis
|
||||
---
|
||||
* Improved modeling of sensitive data sources, so common words like `certain` and `secretary` are no longer considered a certificate and a secret (respectively).
|
||||
5
python/ql/lib/change-notes/released/0.4.0.md
Normal file
5
python/ql/lib/change-notes/released/0.4.0.md
Normal file
@@ -0,0 +1,5 @@
|
||||
## 0.4.0
|
||||
|
||||
### Breaking Changes
|
||||
|
||||
* `API::moduleImport` no longer has any results for dotted names, such as `API::moduleImport("foo.bar")`. Using `API::moduleImport("foo.bar").getMember("baz").getACall()` previously worked if the Python code was `from foo.bar import baz; baz()`, but not if the code was `import foo.bar; foo.bar.baz()` -- we are making this change to ensure the approach that can handle all cases is always used.
|
||||
1
python/ql/lib/change-notes/released/0.4.1.md
Normal file
1
python/ql/lib/change-notes/released/0.4.1.md
Normal file
@@ -0,0 +1 @@
|
||||
## 0.4.1
|
||||
@@ -1,2 +1,2 @@
|
||||
---
|
||||
lastReleaseVersion: 0.3.0
|
||||
lastReleaseVersion: 0.4.1
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
name: codeql/python-all
|
||||
version: 0.3.1-dev
|
||||
version: 0.5.0-dev
|
||||
groups: python
|
||||
dbscheme: semmlecode.python.dbscheme
|
||||
extractor: python
|
||||
|
||||
@@ -136,6 +136,9 @@ module API {
|
||||
result = this.getASuccessor(Label::keywordParameter(name))
|
||||
}
|
||||
|
||||
/** Gets the node representing the self parameter */
|
||||
Node getSelfParameter() { result = this.getASuccessor(Label::selfParameter()) }
|
||||
|
||||
/**
|
||||
* Gets the number of parameters of the function represented by this node.
|
||||
*/
|
||||
@@ -321,6 +324,12 @@ module API {
|
||||
/** Gets the API node for a parameter of this invocation. */
|
||||
Node getAParameter() { result = this.getParameter(_) }
|
||||
|
||||
/** Gets the object that this method-call is being called on, if this is a method-call */
|
||||
Node getSelfParameter() {
|
||||
result.getARhs() = this.(DataFlow::MethodCallNode).getObject() and
|
||||
result = callee.getSelfParameter()
|
||||
}
|
||||
|
||||
/** Gets the API node for the keyword parameter `name` of this invocation. */
|
||||
Node getKeywordParameter(string name) {
|
||||
result = callee.getKeywordParameter(name) and
|
||||
@@ -345,6 +354,14 @@ module API {
|
||||
result = callee.getReturn() and
|
||||
result.getAnImmediateUse() = this
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the number of positional arguments of this call.
|
||||
*
|
||||
* Note: This is used for `WithArity[<n>]` in modeling-as-data, where we thought
|
||||
* including keyword arguments didn't make much sense.
|
||||
*/
|
||||
int getNumArgument() { result = count(this.getArg(_)) }
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -589,15 +606,24 @@ module API {
|
||||
exists(DataFlow::Node def, PY::CallableExpr fn |
|
||||
rhs(base, def) and fn = trackDefNode(def).asExpr()
|
||||
|
|
||||
exists(int i |
|
||||
lbl = Label::parameter(i) and
|
||||
exists(int i, int offset |
|
||||
if exists(PY::Parameter p | p = fn.getInnerScope().getAnArg() and p.isSelf())
|
||||
then offset = 1
|
||||
else offset = 0
|
||||
|
|
||||
lbl = Label::parameter(i - offset) and
|
||||
ref.asExpr() = fn.getInnerScope().getArg(i)
|
||||
)
|
||||
or
|
||||
exists(string name |
|
||||
exists(string name, PY::Parameter param |
|
||||
lbl = Label::keywordParameter(name) and
|
||||
ref.asExpr() = fn.getInnerScope().getArgByName(name)
|
||||
param = fn.getInnerScope().getArgByName(name) and
|
||||
not param.isSelf() and
|
||||
ref.asExpr() = param
|
||||
)
|
||||
or
|
||||
lbl = Label::selfParameter() and
|
||||
ref.asExpr() = any(PY::Parameter p | p = fn.getInnerScope().getAnArg() and p.isSelf())
|
||||
)
|
||||
or
|
||||
// Built-ins, treated as members of the module `builtins`
|
||||
@@ -664,6 +690,9 @@ module API {
|
||||
exists(string name | lbl = Label::keywordParameter(name) |
|
||||
arg = pred.getACall().getArgByName(name)
|
||||
)
|
||||
or
|
||||
lbl = Label::selfParameter() and
|
||||
arg = pred.getACall().(DataFlow::MethodCallNode).getObject()
|
||||
)
|
||||
}
|
||||
|
||||
@@ -780,6 +809,7 @@ module API {
|
||||
or
|
||||
exists(any(PY::Function f).getArgByName(name))
|
||||
} or
|
||||
MkLabelSelfParameter() or
|
||||
MkLabelReturn() or
|
||||
MkLabelSubclass() or
|
||||
MkLabelAwait()
|
||||
@@ -837,6 +867,11 @@ module API {
|
||||
string getName() { result = name }
|
||||
}
|
||||
|
||||
/** A label for the self parameter. */
|
||||
class LabelSelfParameter extends ApiLabel, MkLabelSelfParameter {
|
||||
override string toString() { result = "getSelfParameter()" }
|
||||
}
|
||||
|
||||
/** A label that gets the return value of a function. */
|
||||
class LabelReturn extends ApiLabel, MkLabelReturn {
|
||||
override string toString() { result = "getReturn()" }
|
||||
@@ -876,6 +911,9 @@ module API {
|
||||
/** Gets the `parameter` edge label for the keyword parameter `name`. */
|
||||
LabelKeywordParameter keywordParameter(string name) { result.getName() = name }
|
||||
|
||||
/** Gets the edge label for the self parameter. */
|
||||
LabelSelfParameter selfParameter() { any() }
|
||||
|
||||
/** Gets the `return` edge label. */
|
||||
LabelReturn return() { any() }
|
||||
|
||||
|
||||
@@ -119,16 +119,21 @@ module Path {
|
||||
}
|
||||
|
||||
/** A data-flow node that checks that a path is safe to access. */
|
||||
class SafeAccessCheck extends DataFlow::BarrierGuard instanceof SafeAccessCheck::Range {
|
||||
override predicate checks(ControlFlowNode node, boolean branch) {
|
||||
SafeAccessCheck::Range.super.checks(node, branch)
|
||||
}
|
||||
class SafeAccessCheck extends DataFlow::ExprNode {
|
||||
SafeAccessCheck() { this = DataFlow::BarrierGuard<safeAccessCheck/3>::getABarrierNode() }
|
||||
}
|
||||
|
||||
private predicate safeAccessCheck(DataFlow::GuardNode g, ControlFlowNode node, boolean branch) {
|
||||
g.(SafeAccessCheck::Range).checks(node, branch)
|
||||
}
|
||||
|
||||
/** Provides a class for modeling new path safety checks. */
|
||||
module SafeAccessCheck {
|
||||
/** A data-flow node that checks that a path is safe to access. */
|
||||
abstract class Range extends DataFlow::BarrierGuard { }
|
||||
abstract class Range extends DataFlow::GuardNode {
|
||||
/** Holds if this guard validates `node` upon evaluating to `branch`. */
|
||||
abstract predicate checks(ControlFlowNode node, boolean branch);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1211,38 +1216,5 @@ module Cryptography {
|
||||
}
|
||||
}
|
||||
|
||||
import semmle.python.concepts.CryptoAlgorithms
|
||||
|
||||
/**
|
||||
* A data-flow node that is an application of a cryptographic algorithm. For example,
|
||||
* encryption, decryption, signature-validation.
|
||||
*
|
||||
* Extend this class to refine existing API models. If you want to model new APIs,
|
||||
* extend `CryptographicOperation::Range` instead.
|
||||
*/
|
||||
class CryptographicOperation extends DataFlow::Node instanceof CryptographicOperation::Range {
|
||||
/** Gets the algorithm used, if it matches a known `CryptographicAlgorithm`. */
|
||||
CryptographicAlgorithm getAlgorithm() { result = super.getAlgorithm() }
|
||||
|
||||
/** Gets an input the algorithm is used on, for example the plain text input to be encrypted. */
|
||||
DataFlow::Node getAnInput() { result = super.getAnInput() }
|
||||
}
|
||||
|
||||
/** Provides classes for modeling new applications of a cryptographic algorithms. */
|
||||
module CryptographicOperation {
|
||||
/**
|
||||
* A data-flow node that is an application of a cryptographic algorithm. For example,
|
||||
* encryption, decryption, signature-validation.
|
||||
*
|
||||
* Extend this class to model new APIs. If you want to refine existing API models,
|
||||
* extend `CryptographicOperation` instead.
|
||||
*/
|
||||
abstract class Range extends DataFlow::Node {
|
||||
/** Gets the algorithm used, if it matches a known `CryptographicAlgorithm`. */
|
||||
abstract CryptographicAlgorithm getAlgorithm();
|
||||
|
||||
/** Gets an input the algorithm is used on, for example the plain text input to be encrypted. */
|
||||
abstract DataFlow::Node getAnInput();
|
||||
}
|
||||
}
|
||||
import semmle.python.internal.ConceptsShared::Cryptography
|
||||
}
|
||||
|
||||
@@ -12,6 +12,7 @@ private import semmle.python.frameworks.Asyncpg
|
||||
private import semmle.python.frameworks.ClickhouseDriver
|
||||
private import semmle.python.frameworks.Cryptodome
|
||||
private import semmle.python.frameworks.Cryptography
|
||||
private import semmle.python.frameworks.data.ModelsAsData
|
||||
private import semmle.python.frameworks.Dill
|
||||
private import semmle.python.frameworks.Django
|
||||
private import semmle.python.frameworks.Fabric
|
||||
|
||||
@@ -81,6 +81,9 @@ class EncryptionAlgorithm extends MkEncryptionAlgorithm, CryptographicAlgorithm
|
||||
override string getName() { result = name }
|
||||
|
||||
override predicate isWeak() { isWeak = true }
|
||||
|
||||
/** Holds if this algorithm is a stream cipher. */
|
||||
predicate isStreamCipher() { isStreamCipher(name) }
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -67,6 +67,6 @@ predicate isStrongPasswordHashingAlgorithm(string name) {
|
||||
predicate isWeakPasswordHashingAlgorithm(string name) { name = "EVPKDF" }
|
||||
|
||||
/**
|
||||
* Holds if `name` corresponds to a weak block cipher mode of operation.
|
||||
* Holds if `name` corresponds to a stream cipher.
|
||||
*/
|
||||
predicate isWeakBlockMode(string name) { name = "ECB" }
|
||||
predicate isStreamCipher(string name) { name = ["CHACHA", "RC4", "ARC4", "ARCFOUR", "RABBIT"] }
|
||||
|
||||
@@ -3,8 +3,44 @@
|
||||
private import python
|
||||
private import semmle.python.dataflow.new.DataFlow
|
||||
|
||||
private predicate stringConstCompare(DataFlow::GuardNode g, ControlFlowNode node, boolean branch) {
|
||||
exists(CompareNode cn | cn = g |
|
||||
exists(StrConst str_const, Cmpop op |
|
||||
op = any(Eq eq) and branch = true
|
||||
or
|
||||
op = any(NotEq ne) and branch = false
|
||||
|
|
||||
cn.operands(str_const.getAFlowNode(), op, node)
|
||||
or
|
||||
cn.operands(node, op, str_const.getAFlowNode())
|
||||
)
|
||||
or
|
||||
exists(IterableNode str_const_iterable, Cmpop op |
|
||||
op = any(In in_) and branch = true
|
||||
or
|
||||
op = any(NotIn ni) and branch = false
|
||||
|
|
||||
forall(ControlFlowNode elem | elem = str_const_iterable.getAnElement() |
|
||||
elem.getNode() instanceof StrConst
|
||||
) and
|
||||
cn.operands(node, op, str_const_iterable)
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
/** A validation of unknown node by comparing with a constant string value. */
|
||||
class StringConstCompare extends DataFlow::BarrierGuard, CompareNode {
|
||||
class StringConstCompareBarrier extends DataFlow::Node {
|
||||
StringConstCompareBarrier() {
|
||||
this = DataFlow::BarrierGuard<stringConstCompare/3>::getABarrierNode()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* DEPRECATED: Use `StringConstCompareBarrier` instead.
|
||||
*
|
||||
* A validation of unknown node by comparing with a constant string value.
|
||||
*/
|
||||
deprecated class StringConstCompare extends DataFlow::BarrierGuard, CompareNode {
|
||||
ControlFlowNode checked_node;
|
||||
boolean safe_branch;
|
||||
|
||||
|
||||
@@ -90,14 +90,20 @@ abstract class Configuration extends string {
|
||||
/** Holds if data flow out of `node` is prohibited. */
|
||||
predicate isBarrierOut(Node node) { none() }
|
||||
|
||||
/** Holds if data flow through nodes guarded by `guard` is prohibited. */
|
||||
predicate isBarrierGuard(BarrierGuard guard) { 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`
|
||||
*/
|
||||
predicate isBarrierGuard(BarrierGuard guard, FlowState state) { none() }
|
||||
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.
|
||||
@@ -335,6 +341,29 @@ private predicate outBarrier(NodeEx node, Configuration config) {
|
||||
)
|
||||
}
|
||||
|
||||
/** 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()
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
pragma[nomagic]
|
||||
private predicate fullBarrier(NodeEx node, Configuration config) {
|
||||
exists(Node n | node.asNode() = n |
|
||||
@@ -348,10 +377,7 @@ private predicate fullBarrier(NodeEx node, Configuration config) {
|
||||
not config.isSink(n) and
|
||||
not config.isSink(n, _)
|
||||
or
|
||||
exists(BarrierGuard g |
|
||||
config.isBarrierGuard(g) and
|
||||
n = g.getAGuardedNode()
|
||||
)
|
||||
any(BarrierGuardGuardedNodeBridge b).guardedNode(n, config)
|
||||
)
|
||||
}
|
||||
|
||||
@@ -360,10 +386,7 @@ private predicate stateBarrier(NodeEx node, FlowState state, Configuration confi
|
||||
exists(Node n | node.asNode() = n |
|
||||
config.isBarrier(n, state)
|
||||
or
|
||||
exists(BarrierGuard g |
|
||||
config.isBarrierGuard(g, state) and
|
||||
n = g.getAGuardedNode()
|
||||
)
|
||||
any(BarrierGuardGuardedNodeBridge b).guardedNode(n, state, config)
|
||||
)
|
||||
}
|
||||
|
||||
@@ -3854,16 +3877,11 @@ class PathNode extends TPathNode {
|
||||
/** Gets the associated configuration. */
|
||||
Configuration getConfiguration() { none() }
|
||||
|
||||
private PathNode getASuccessorIfHidden() {
|
||||
this.(PathNodeImpl).isHidden() and
|
||||
result = this.(PathNodeImpl).getASuccessorImpl()
|
||||
}
|
||||
|
||||
/** Gets a successor of this node, if any. */
|
||||
final PathNode getASuccessor() {
|
||||
result = this.(PathNodeImpl).getASuccessorImpl().getASuccessorIfHidden*() and
|
||||
not this.(PathNodeImpl).isHidden() and
|
||||
not result.(PathNodeImpl).isHidden()
|
||||
result = this.(PathNodeImpl).getANonHiddenSuccessor() and
|
||||
reach(this) and
|
||||
reach(result)
|
||||
}
|
||||
|
||||
/** Holds if this node is a source. */
|
||||
@@ -3871,7 +3889,18 @@ class PathNode extends TPathNode {
|
||||
}
|
||||
|
||||
abstract private class PathNodeImpl extends PathNode {
|
||||
abstract PathNode getASuccessorImpl();
|
||||
abstract PathNodeImpl getASuccessorImpl();
|
||||
|
||||
private PathNodeImpl getASuccessorIfHidden() {
|
||||
this.isHidden() and
|
||||
result = this.getASuccessorImpl()
|
||||
}
|
||||
|
||||
final PathNodeImpl getANonHiddenSuccessor() {
|
||||
result = this.getASuccessorImpl().getASuccessorIfHidden*() and
|
||||
not this.isHidden() and
|
||||
not result.isHidden()
|
||||
}
|
||||
|
||||
abstract NodeEx getNodeEx();
|
||||
|
||||
@@ -3914,15 +3943,17 @@ abstract private class PathNodeImpl extends PathNode {
|
||||
}
|
||||
|
||||
/** Holds if `n` can reach a sink. */
|
||||
private predicate directReach(PathNode n) {
|
||||
n instanceof PathNodeSink or directReach(n.getASuccessor())
|
||||
private predicate directReach(PathNodeImpl n) {
|
||||
n instanceof PathNodeSink or directReach(n.getANonHiddenSuccessor())
|
||||
}
|
||||
|
||||
/** Holds if `n` can reach a sink or is used in a subpath that can reach a sink. */
|
||||
private predicate reach(PathNode n) { directReach(n) or Subpaths::retReach(n) }
|
||||
|
||||
/** Holds if `n1.getASuccessor() = n2` and `n2` can reach a sink. */
|
||||
private predicate pathSucc(PathNode n1, PathNode n2) { n1.getASuccessor() = n2 and directReach(n2) }
|
||||
private predicate pathSucc(PathNodeImpl n1, PathNode n2) {
|
||||
n1.getANonHiddenSuccessor() = n2 and directReach(n2)
|
||||
}
|
||||
|
||||
private predicate pathSuccPlus(PathNode n1, PathNode n2) = fastTC(pathSucc/2)(n1, n2)
|
||||
|
||||
@@ -3931,7 +3962,7 @@ private predicate pathSuccPlus(PathNode n1, PathNode n2) = fastTC(pathSucc/2)(n1
|
||||
*/
|
||||
module PathGraph {
|
||||
/** Holds if `(a,b)` is an edge in the graph of data flow path explanations. */
|
||||
query predicate edges(PathNode a, PathNode b) { a.getASuccessor() = b and reach(a) and reach(b) }
|
||||
query predicate edges(PathNode a, PathNode b) { a.getASuccessor() = b }
|
||||
|
||||
/** Holds if `n` is a node in the graph of data flow path explanations. */
|
||||
query predicate nodes(PathNode n, string key, string val) {
|
||||
@@ -4049,7 +4080,7 @@ private class PathNodeSink extends PathNodeImpl, TPathNodeSink {
|
||||
|
||||
override Configuration getConfiguration() { result = config }
|
||||
|
||||
override PathNode getASuccessorImpl() { none() }
|
||||
override PathNodeImpl getASuccessorImpl() { none() }
|
||||
|
||||
override predicate isSource() { sourceNode(node, state, config) }
|
||||
}
|
||||
@@ -4365,8 +4396,8 @@ private module Subpaths {
|
||||
}
|
||||
|
||||
pragma[nomagic]
|
||||
private predicate hasSuccessor(PathNode pred, PathNodeMid succ, NodeEx succNode) {
|
||||
succ = pred.getASuccessor() and
|
||||
private predicate hasSuccessor(PathNodeImpl pred, PathNodeMid succ, NodeEx succNode) {
|
||||
succ = pred.getANonHiddenSuccessor() and
|
||||
succNode = succ.getNodeEx()
|
||||
}
|
||||
|
||||
@@ -4375,9 +4406,9 @@ private module Subpaths {
|
||||
* 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, PathNodeImpl par, PathNodeImpl ret, PathNode out) {
|
||||
predicate subpaths(PathNodeImpl arg, PathNodeImpl par, PathNodeImpl ret, PathNode out) {
|
||||
exists(ParamNodeEx p, NodeEx o, FlowState sout, AccessPath apout, PathNodeMid out0 |
|
||||
pragma[only_bind_into](arg).getASuccessor() = pragma[only_bind_into](out0) and
|
||||
pragma[only_bind_into](arg).getANonHiddenSuccessor() = pragma[only_bind_into](out0) and
|
||||
subpaths03(pragma[only_bind_into](arg), p, localStepToHidden*(ret), o, sout, apout) and
|
||||
hasSuccessor(pragma[only_bind_into](arg), par, p) and
|
||||
not ret.isHidden() and
|
||||
@@ -4390,12 +4421,12 @@ private module Subpaths {
|
||||
/**
|
||||
* Holds if `n` can reach a return node in a summarized subpath that can reach a sink.
|
||||
*/
|
||||
predicate retReach(PathNode n) {
|
||||
predicate retReach(PathNodeImpl n) {
|
||||
exists(PathNode out | subpaths(_, _, n, out) | directReach(out) or retReach(out))
|
||||
or
|
||||
exists(PathNode mid |
|
||||
exists(PathNodeImpl mid |
|
||||
retReach(mid) and
|
||||
n.getASuccessor() = mid and
|
||||
n.getANonHiddenSuccessor() = mid and
|
||||
not subpaths(_, mid, _, _)
|
||||
)
|
||||
}
|
||||
|
||||
@@ -90,14 +90,20 @@ abstract class Configuration extends string {
|
||||
/** Holds if data flow out of `node` is prohibited. */
|
||||
predicate isBarrierOut(Node node) { none() }
|
||||
|
||||
/** Holds if data flow through nodes guarded by `guard` is prohibited. */
|
||||
predicate isBarrierGuard(BarrierGuard guard) { 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`
|
||||
*/
|
||||
predicate isBarrierGuard(BarrierGuard guard, FlowState state) { none() }
|
||||
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.
|
||||
@@ -335,6 +341,29 @@ private predicate outBarrier(NodeEx node, Configuration config) {
|
||||
)
|
||||
}
|
||||
|
||||
/** 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()
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
pragma[nomagic]
|
||||
private predicate fullBarrier(NodeEx node, Configuration config) {
|
||||
exists(Node n | node.asNode() = n |
|
||||
@@ -348,10 +377,7 @@ private predicate fullBarrier(NodeEx node, Configuration config) {
|
||||
not config.isSink(n) and
|
||||
not config.isSink(n, _)
|
||||
or
|
||||
exists(BarrierGuard g |
|
||||
config.isBarrierGuard(g) and
|
||||
n = g.getAGuardedNode()
|
||||
)
|
||||
any(BarrierGuardGuardedNodeBridge b).guardedNode(n, config)
|
||||
)
|
||||
}
|
||||
|
||||
@@ -360,10 +386,7 @@ private predicate stateBarrier(NodeEx node, FlowState state, Configuration confi
|
||||
exists(Node n | node.asNode() = n |
|
||||
config.isBarrier(n, state)
|
||||
or
|
||||
exists(BarrierGuard g |
|
||||
config.isBarrierGuard(g, state) and
|
||||
n = g.getAGuardedNode()
|
||||
)
|
||||
any(BarrierGuardGuardedNodeBridge b).guardedNode(n, state, config)
|
||||
)
|
||||
}
|
||||
|
||||
@@ -3854,16 +3877,11 @@ class PathNode extends TPathNode {
|
||||
/** Gets the associated configuration. */
|
||||
Configuration getConfiguration() { none() }
|
||||
|
||||
private PathNode getASuccessorIfHidden() {
|
||||
this.(PathNodeImpl).isHidden() and
|
||||
result = this.(PathNodeImpl).getASuccessorImpl()
|
||||
}
|
||||
|
||||
/** Gets a successor of this node, if any. */
|
||||
final PathNode getASuccessor() {
|
||||
result = this.(PathNodeImpl).getASuccessorImpl().getASuccessorIfHidden*() and
|
||||
not this.(PathNodeImpl).isHidden() and
|
||||
not result.(PathNodeImpl).isHidden()
|
||||
result = this.(PathNodeImpl).getANonHiddenSuccessor() and
|
||||
reach(this) and
|
||||
reach(result)
|
||||
}
|
||||
|
||||
/** Holds if this node is a source. */
|
||||
@@ -3871,7 +3889,18 @@ class PathNode extends TPathNode {
|
||||
}
|
||||
|
||||
abstract private class PathNodeImpl extends PathNode {
|
||||
abstract PathNode getASuccessorImpl();
|
||||
abstract PathNodeImpl getASuccessorImpl();
|
||||
|
||||
private PathNodeImpl getASuccessorIfHidden() {
|
||||
this.isHidden() and
|
||||
result = this.getASuccessorImpl()
|
||||
}
|
||||
|
||||
final PathNodeImpl getANonHiddenSuccessor() {
|
||||
result = this.getASuccessorImpl().getASuccessorIfHidden*() and
|
||||
not this.isHidden() and
|
||||
not result.isHidden()
|
||||
}
|
||||
|
||||
abstract NodeEx getNodeEx();
|
||||
|
||||
@@ -3914,15 +3943,17 @@ abstract private class PathNodeImpl extends PathNode {
|
||||
}
|
||||
|
||||
/** Holds if `n` can reach a sink. */
|
||||
private predicate directReach(PathNode n) {
|
||||
n instanceof PathNodeSink or directReach(n.getASuccessor())
|
||||
private predicate directReach(PathNodeImpl n) {
|
||||
n instanceof PathNodeSink or directReach(n.getANonHiddenSuccessor())
|
||||
}
|
||||
|
||||
/** Holds if `n` can reach a sink or is used in a subpath that can reach a sink. */
|
||||
private predicate reach(PathNode n) { directReach(n) or Subpaths::retReach(n) }
|
||||
|
||||
/** Holds if `n1.getASuccessor() = n2` and `n2` can reach a sink. */
|
||||
private predicate pathSucc(PathNode n1, PathNode n2) { n1.getASuccessor() = n2 and directReach(n2) }
|
||||
private predicate pathSucc(PathNodeImpl n1, PathNode n2) {
|
||||
n1.getANonHiddenSuccessor() = n2 and directReach(n2)
|
||||
}
|
||||
|
||||
private predicate pathSuccPlus(PathNode n1, PathNode n2) = fastTC(pathSucc/2)(n1, n2)
|
||||
|
||||
@@ -3931,7 +3962,7 @@ private predicate pathSuccPlus(PathNode n1, PathNode n2) = fastTC(pathSucc/2)(n1
|
||||
*/
|
||||
module PathGraph {
|
||||
/** Holds if `(a,b)` is an edge in the graph of data flow path explanations. */
|
||||
query predicate edges(PathNode a, PathNode b) { a.getASuccessor() = b and reach(a) and reach(b) }
|
||||
query predicate edges(PathNode a, PathNode b) { a.getASuccessor() = b }
|
||||
|
||||
/** Holds if `n` is a node in the graph of data flow path explanations. */
|
||||
query predicate nodes(PathNode n, string key, string val) {
|
||||
@@ -4049,7 +4080,7 @@ private class PathNodeSink extends PathNodeImpl, TPathNodeSink {
|
||||
|
||||
override Configuration getConfiguration() { result = config }
|
||||
|
||||
override PathNode getASuccessorImpl() { none() }
|
||||
override PathNodeImpl getASuccessorImpl() { none() }
|
||||
|
||||
override predicate isSource() { sourceNode(node, state, config) }
|
||||
}
|
||||
@@ -4365,8 +4396,8 @@ private module Subpaths {
|
||||
}
|
||||
|
||||
pragma[nomagic]
|
||||
private predicate hasSuccessor(PathNode pred, PathNodeMid succ, NodeEx succNode) {
|
||||
succ = pred.getASuccessor() and
|
||||
private predicate hasSuccessor(PathNodeImpl pred, PathNodeMid succ, NodeEx succNode) {
|
||||
succ = pred.getANonHiddenSuccessor() and
|
||||
succNode = succ.getNodeEx()
|
||||
}
|
||||
|
||||
@@ -4375,9 +4406,9 @@ private module Subpaths {
|
||||
* 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, PathNodeImpl par, PathNodeImpl ret, PathNode out) {
|
||||
predicate subpaths(PathNodeImpl arg, PathNodeImpl par, PathNodeImpl ret, PathNode out) {
|
||||
exists(ParamNodeEx p, NodeEx o, FlowState sout, AccessPath apout, PathNodeMid out0 |
|
||||
pragma[only_bind_into](arg).getASuccessor() = pragma[only_bind_into](out0) and
|
||||
pragma[only_bind_into](arg).getANonHiddenSuccessor() = pragma[only_bind_into](out0) and
|
||||
subpaths03(pragma[only_bind_into](arg), p, localStepToHidden*(ret), o, sout, apout) and
|
||||
hasSuccessor(pragma[only_bind_into](arg), par, p) and
|
||||
not ret.isHidden() and
|
||||
@@ -4390,12 +4421,12 @@ private module Subpaths {
|
||||
/**
|
||||
* Holds if `n` can reach a return node in a summarized subpath that can reach a sink.
|
||||
*/
|
||||
predicate retReach(PathNode n) {
|
||||
predicate retReach(PathNodeImpl n) {
|
||||
exists(PathNode out | subpaths(_, _, n, out) | directReach(out) or retReach(out))
|
||||
or
|
||||
exists(PathNode mid |
|
||||
exists(PathNodeImpl mid |
|
||||
retReach(mid) and
|
||||
n.getASuccessor() = mid and
|
||||
n.getANonHiddenSuccessor() = mid and
|
||||
not subpaths(_, mid, _, _)
|
||||
)
|
||||
}
|
||||
|
||||
@@ -90,14 +90,20 @@ abstract class Configuration extends string {
|
||||
/** Holds if data flow out of `node` is prohibited. */
|
||||
predicate isBarrierOut(Node node) { none() }
|
||||
|
||||
/** Holds if data flow through nodes guarded by `guard` is prohibited. */
|
||||
predicate isBarrierGuard(BarrierGuard guard) { 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`
|
||||
*/
|
||||
predicate isBarrierGuard(BarrierGuard guard, FlowState state) { none() }
|
||||
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.
|
||||
@@ -335,6 +341,29 @@ private predicate outBarrier(NodeEx node, Configuration config) {
|
||||
)
|
||||
}
|
||||
|
||||
/** 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()
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
pragma[nomagic]
|
||||
private predicate fullBarrier(NodeEx node, Configuration config) {
|
||||
exists(Node n | node.asNode() = n |
|
||||
@@ -348,10 +377,7 @@ private predicate fullBarrier(NodeEx node, Configuration config) {
|
||||
not config.isSink(n) and
|
||||
not config.isSink(n, _)
|
||||
or
|
||||
exists(BarrierGuard g |
|
||||
config.isBarrierGuard(g) and
|
||||
n = g.getAGuardedNode()
|
||||
)
|
||||
any(BarrierGuardGuardedNodeBridge b).guardedNode(n, config)
|
||||
)
|
||||
}
|
||||
|
||||
@@ -360,10 +386,7 @@ private predicate stateBarrier(NodeEx node, FlowState state, Configuration confi
|
||||
exists(Node n | node.asNode() = n |
|
||||
config.isBarrier(n, state)
|
||||
or
|
||||
exists(BarrierGuard g |
|
||||
config.isBarrierGuard(g, state) and
|
||||
n = g.getAGuardedNode()
|
||||
)
|
||||
any(BarrierGuardGuardedNodeBridge b).guardedNode(n, state, config)
|
||||
)
|
||||
}
|
||||
|
||||
@@ -3854,16 +3877,11 @@ class PathNode extends TPathNode {
|
||||
/** Gets the associated configuration. */
|
||||
Configuration getConfiguration() { none() }
|
||||
|
||||
private PathNode getASuccessorIfHidden() {
|
||||
this.(PathNodeImpl).isHidden() and
|
||||
result = this.(PathNodeImpl).getASuccessorImpl()
|
||||
}
|
||||
|
||||
/** Gets a successor of this node, if any. */
|
||||
final PathNode getASuccessor() {
|
||||
result = this.(PathNodeImpl).getASuccessorImpl().getASuccessorIfHidden*() and
|
||||
not this.(PathNodeImpl).isHidden() and
|
||||
not result.(PathNodeImpl).isHidden()
|
||||
result = this.(PathNodeImpl).getANonHiddenSuccessor() and
|
||||
reach(this) and
|
||||
reach(result)
|
||||
}
|
||||
|
||||
/** Holds if this node is a source. */
|
||||
@@ -3871,7 +3889,18 @@ class PathNode extends TPathNode {
|
||||
}
|
||||
|
||||
abstract private class PathNodeImpl extends PathNode {
|
||||
abstract PathNode getASuccessorImpl();
|
||||
abstract PathNodeImpl getASuccessorImpl();
|
||||
|
||||
private PathNodeImpl getASuccessorIfHidden() {
|
||||
this.isHidden() and
|
||||
result = this.getASuccessorImpl()
|
||||
}
|
||||
|
||||
final PathNodeImpl getANonHiddenSuccessor() {
|
||||
result = this.getASuccessorImpl().getASuccessorIfHidden*() and
|
||||
not this.isHidden() and
|
||||
not result.isHidden()
|
||||
}
|
||||
|
||||
abstract NodeEx getNodeEx();
|
||||
|
||||
@@ -3914,15 +3943,17 @@ abstract private class PathNodeImpl extends PathNode {
|
||||
}
|
||||
|
||||
/** Holds if `n` can reach a sink. */
|
||||
private predicate directReach(PathNode n) {
|
||||
n instanceof PathNodeSink or directReach(n.getASuccessor())
|
||||
private predicate directReach(PathNodeImpl n) {
|
||||
n instanceof PathNodeSink or directReach(n.getANonHiddenSuccessor())
|
||||
}
|
||||
|
||||
/** Holds if `n` can reach a sink or is used in a subpath that can reach a sink. */
|
||||
private predicate reach(PathNode n) { directReach(n) or Subpaths::retReach(n) }
|
||||
|
||||
/** Holds if `n1.getASuccessor() = n2` and `n2` can reach a sink. */
|
||||
private predicate pathSucc(PathNode n1, PathNode n2) { n1.getASuccessor() = n2 and directReach(n2) }
|
||||
private predicate pathSucc(PathNodeImpl n1, PathNode n2) {
|
||||
n1.getANonHiddenSuccessor() = n2 and directReach(n2)
|
||||
}
|
||||
|
||||
private predicate pathSuccPlus(PathNode n1, PathNode n2) = fastTC(pathSucc/2)(n1, n2)
|
||||
|
||||
@@ -3931,7 +3962,7 @@ private predicate pathSuccPlus(PathNode n1, PathNode n2) = fastTC(pathSucc/2)(n1
|
||||
*/
|
||||
module PathGraph {
|
||||
/** Holds if `(a,b)` is an edge in the graph of data flow path explanations. */
|
||||
query predicate edges(PathNode a, PathNode b) { a.getASuccessor() = b and reach(a) and reach(b) }
|
||||
query predicate edges(PathNode a, PathNode b) { a.getASuccessor() = b }
|
||||
|
||||
/** Holds if `n` is a node in the graph of data flow path explanations. */
|
||||
query predicate nodes(PathNode n, string key, string val) {
|
||||
@@ -4049,7 +4080,7 @@ private class PathNodeSink extends PathNodeImpl, TPathNodeSink {
|
||||
|
||||
override Configuration getConfiguration() { result = config }
|
||||
|
||||
override PathNode getASuccessorImpl() { none() }
|
||||
override PathNodeImpl getASuccessorImpl() { none() }
|
||||
|
||||
override predicate isSource() { sourceNode(node, state, config) }
|
||||
}
|
||||
@@ -4365,8 +4396,8 @@ private module Subpaths {
|
||||
}
|
||||
|
||||
pragma[nomagic]
|
||||
private predicate hasSuccessor(PathNode pred, PathNodeMid succ, NodeEx succNode) {
|
||||
succ = pred.getASuccessor() and
|
||||
private predicate hasSuccessor(PathNodeImpl pred, PathNodeMid succ, NodeEx succNode) {
|
||||
succ = pred.getANonHiddenSuccessor() and
|
||||
succNode = succ.getNodeEx()
|
||||
}
|
||||
|
||||
@@ -4375,9 +4406,9 @@ private module Subpaths {
|
||||
* 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, PathNodeImpl par, PathNodeImpl ret, PathNode out) {
|
||||
predicate subpaths(PathNodeImpl arg, PathNodeImpl par, PathNodeImpl ret, PathNode out) {
|
||||
exists(ParamNodeEx p, NodeEx o, FlowState sout, AccessPath apout, PathNodeMid out0 |
|
||||
pragma[only_bind_into](arg).getASuccessor() = pragma[only_bind_into](out0) and
|
||||
pragma[only_bind_into](arg).getANonHiddenSuccessor() = pragma[only_bind_into](out0) and
|
||||
subpaths03(pragma[only_bind_into](arg), p, localStepToHidden*(ret), o, sout, apout) and
|
||||
hasSuccessor(pragma[only_bind_into](arg), par, p) and
|
||||
not ret.isHidden() and
|
||||
@@ -4390,12 +4421,12 @@ private module Subpaths {
|
||||
/**
|
||||
* Holds if `n` can reach a return node in a summarized subpath that can reach a sink.
|
||||
*/
|
||||
predicate retReach(PathNode n) {
|
||||
predicate retReach(PathNodeImpl n) {
|
||||
exists(PathNode out | subpaths(_, _, n, out) | directReach(out) or retReach(out))
|
||||
or
|
||||
exists(PathNode mid |
|
||||
exists(PathNodeImpl mid |
|
||||
retReach(mid) and
|
||||
n.getASuccessor() = mid and
|
||||
n.getANonHiddenSuccessor() = mid and
|
||||
not subpaths(_, mid, _, _)
|
||||
)
|
||||
}
|
||||
|
||||
@@ -90,14 +90,20 @@ abstract class Configuration extends string {
|
||||
/** Holds if data flow out of `node` is prohibited. */
|
||||
predicate isBarrierOut(Node node) { none() }
|
||||
|
||||
/** Holds if data flow through nodes guarded by `guard` is prohibited. */
|
||||
predicate isBarrierGuard(BarrierGuard guard) { 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`
|
||||
*/
|
||||
predicate isBarrierGuard(BarrierGuard guard, FlowState state) { none() }
|
||||
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.
|
||||
@@ -335,6 +341,29 @@ private predicate outBarrier(NodeEx node, Configuration config) {
|
||||
)
|
||||
}
|
||||
|
||||
/** 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()
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
pragma[nomagic]
|
||||
private predicate fullBarrier(NodeEx node, Configuration config) {
|
||||
exists(Node n | node.asNode() = n |
|
||||
@@ -348,10 +377,7 @@ private predicate fullBarrier(NodeEx node, Configuration config) {
|
||||
not config.isSink(n) and
|
||||
not config.isSink(n, _)
|
||||
or
|
||||
exists(BarrierGuard g |
|
||||
config.isBarrierGuard(g) and
|
||||
n = g.getAGuardedNode()
|
||||
)
|
||||
any(BarrierGuardGuardedNodeBridge b).guardedNode(n, config)
|
||||
)
|
||||
}
|
||||
|
||||
@@ -360,10 +386,7 @@ private predicate stateBarrier(NodeEx node, FlowState state, Configuration confi
|
||||
exists(Node n | node.asNode() = n |
|
||||
config.isBarrier(n, state)
|
||||
or
|
||||
exists(BarrierGuard g |
|
||||
config.isBarrierGuard(g, state) and
|
||||
n = g.getAGuardedNode()
|
||||
)
|
||||
any(BarrierGuardGuardedNodeBridge b).guardedNode(n, state, config)
|
||||
)
|
||||
}
|
||||
|
||||
@@ -3854,16 +3877,11 @@ class PathNode extends TPathNode {
|
||||
/** Gets the associated configuration. */
|
||||
Configuration getConfiguration() { none() }
|
||||
|
||||
private PathNode getASuccessorIfHidden() {
|
||||
this.(PathNodeImpl).isHidden() and
|
||||
result = this.(PathNodeImpl).getASuccessorImpl()
|
||||
}
|
||||
|
||||
/** Gets a successor of this node, if any. */
|
||||
final PathNode getASuccessor() {
|
||||
result = this.(PathNodeImpl).getASuccessorImpl().getASuccessorIfHidden*() and
|
||||
not this.(PathNodeImpl).isHidden() and
|
||||
not result.(PathNodeImpl).isHidden()
|
||||
result = this.(PathNodeImpl).getANonHiddenSuccessor() and
|
||||
reach(this) and
|
||||
reach(result)
|
||||
}
|
||||
|
||||
/** Holds if this node is a source. */
|
||||
@@ -3871,7 +3889,18 @@ class PathNode extends TPathNode {
|
||||
}
|
||||
|
||||
abstract private class PathNodeImpl extends PathNode {
|
||||
abstract PathNode getASuccessorImpl();
|
||||
abstract PathNodeImpl getASuccessorImpl();
|
||||
|
||||
private PathNodeImpl getASuccessorIfHidden() {
|
||||
this.isHidden() and
|
||||
result = this.getASuccessorImpl()
|
||||
}
|
||||
|
||||
final PathNodeImpl getANonHiddenSuccessor() {
|
||||
result = this.getASuccessorImpl().getASuccessorIfHidden*() and
|
||||
not this.isHidden() and
|
||||
not result.isHidden()
|
||||
}
|
||||
|
||||
abstract NodeEx getNodeEx();
|
||||
|
||||
@@ -3914,15 +3943,17 @@ abstract private class PathNodeImpl extends PathNode {
|
||||
}
|
||||
|
||||
/** Holds if `n` can reach a sink. */
|
||||
private predicate directReach(PathNode n) {
|
||||
n instanceof PathNodeSink or directReach(n.getASuccessor())
|
||||
private predicate directReach(PathNodeImpl n) {
|
||||
n instanceof PathNodeSink or directReach(n.getANonHiddenSuccessor())
|
||||
}
|
||||
|
||||
/** Holds if `n` can reach a sink or is used in a subpath that can reach a sink. */
|
||||
private predicate reach(PathNode n) { directReach(n) or Subpaths::retReach(n) }
|
||||
|
||||
/** Holds if `n1.getASuccessor() = n2` and `n2` can reach a sink. */
|
||||
private predicate pathSucc(PathNode n1, PathNode n2) { n1.getASuccessor() = n2 and directReach(n2) }
|
||||
private predicate pathSucc(PathNodeImpl n1, PathNode n2) {
|
||||
n1.getANonHiddenSuccessor() = n2 and directReach(n2)
|
||||
}
|
||||
|
||||
private predicate pathSuccPlus(PathNode n1, PathNode n2) = fastTC(pathSucc/2)(n1, n2)
|
||||
|
||||
@@ -3931,7 +3962,7 @@ private predicate pathSuccPlus(PathNode n1, PathNode n2) = fastTC(pathSucc/2)(n1
|
||||
*/
|
||||
module PathGraph {
|
||||
/** Holds if `(a,b)` is an edge in the graph of data flow path explanations. */
|
||||
query predicate edges(PathNode a, PathNode b) { a.getASuccessor() = b and reach(a) and reach(b) }
|
||||
query predicate edges(PathNode a, PathNode b) { a.getASuccessor() = b }
|
||||
|
||||
/** Holds if `n` is a node in the graph of data flow path explanations. */
|
||||
query predicate nodes(PathNode n, string key, string val) {
|
||||
@@ -4049,7 +4080,7 @@ private class PathNodeSink extends PathNodeImpl, TPathNodeSink {
|
||||
|
||||
override Configuration getConfiguration() { result = config }
|
||||
|
||||
override PathNode getASuccessorImpl() { none() }
|
||||
override PathNodeImpl getASuccessorImpl() { none() }
|
||||
|
||||
override predicate isSource() { sourceNode(node, state, config) }
|
||||
}
|
||||
@@ -4365,8 +4396,8 @@ private module Subpaths {
|
||||
}
|
||||
|
||||
pragma[nomagic]
|
||||
private predicate hasSuccessor(PathNode pred, PathNodeMid succ, NodeEx succNode) {
|
||||
succ = pred.getASuccessor() and
|
||||
private predicate hasSuccessor(PathNodeImpl pred, PathNodeMid succ, NodeEx succNode) {
|
||||
succ = pred.getANonHiddenSuccessor() and
|
||||
succNode = succ.getNodeEx()
|
||||
}
|
||||
|
||||
@@ -4375,9 +4406,9 @@ private module Subpaths {
|
||||
* 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, PathNodeImpl par, PathNodeImpl ret, PathNode out) {
|
||||
predicate subpaths(PathNodeImpl arg, PathNodeImpl par, PathNodeImpl ret, PathNode out) {
|
||||
exists(ParamNodeEx p, NodeEx o, FlowState sout, AccessPath apout, PathNodeMid out0 |
|
||||
pragma[only_bind_into](arg).getASuccessor() = pragma[only_bind_into](out0) and
|
||||
pragma[only_bind_into](arg).getANonHiddenSuccessor() = pragma[only_bind_into](out0) and
|
||||
subpaths03(pragma[only_bind_into](arg), p, localStepToHidden*(ret), o, sout, apout) and
|
||||
hasSuccessor(pragma[only_bind_into](arg), par, p) and
|
||||
not ret.isHidden() and
|
||||
@@ -4390,12 +4421,12 @@ private module Subpaths {
|
||||
/**
|
||||
* Holds if `n` can reach a return node in a summarized subpath that can reach a sink.
|
||||
*/
|
||||
predicate retReach(PathNode n) {
|
||||
predicate retReach(PathNodeImpl n) {
|
||||
exists(PathNode out | subpaths(_, _, n, out) | directReach(out) or retReach(out))
|
||||
or
|
||||
exists(PathNode mid |
|
||||
exists(PathNodeImpl mid |
|
||||
retReach(mid) and
|
||||
n.getASuccessor() = mid and
|
||||
n.getANonHiddenSuccessor() = mid and
|
||||
not subpaths(_, mid, _, _)
|
||||
)
|
||||
}
|
||||
|
||||
@@ -216,10 +216,9 @@ private module LambdaFlow {
|
||||
or
|
||||
// jump step
|
||||
exists(Node mid, DataFlowType t0 |
|
||||
revLambdaFlow(lambdaCall, kind, mid, t0, _, _, _) and
|
||||
revLambdaFlow(lambdaCall, kind, mid, t0, _, _, lastCall) and
|
||||
toReturn = false and
|
||||
toJump = true and
|
||||
lastCall = TDataFlowCallNone()
|
||||
toJump = true
|
||||
|
|
||||
jumpStepCached(node, mid) and
|
||||
t = t0
|
||||
@@ -789,24 +788,31 @@ private module Cached {
|
||||
cached
|
||||
predicate readSet(Node node1, ContentSet c, Node node2) { readStep(node1, c, node2) }
|
||||
|
||||
cached
|
||||
predicate storeSet(
|
||||
Node node1, ContentSet c, Node node2, DataFlowType contentType, DataFlowType containerType
|
||||
) {
|
||||
storeStep(node1, c, node2) and
|
||||
contentType = getNodeDataFlowType(node1) and
|
||||
containerType = getNodeDataFlowType(node2)
|
||||
or
|
||||
exists(Node n1, Node n2 |
|
||||
n1 = node1.(PostUpdateNode).getPreUpdateNode() and
|
||||
n2 = node2.(PostUpdateNode).getPreUpdateNode()
|
||||
|
|
||||
argumentValueFlowsThrough(n2, TReadStepTypesSome(containerType, c, contentType), n1)
|
||||
or
|
||||
readSet(n2, c, n1) and
|
||||
contentType = getNodeDataFlowType(n1) and
|
||||
containerType = getNodeDataFlowType(n2)
|
||||
)
|
||||
}
|
||||
|
||||
private predicate store(
|
||||
Node node1, Content c, Node node2, DataFlowType contentType, DataFlowType containerType
|
||||
) {
|
||||
exists(ContentSet cs | c = cs.getAStoreContent() |
|
||||
storeStep(node1, cs, node2) and
|
||||
contentType = getNodeDataFlowType(node1) and
|
||||
containerType = getNodeDataFlowType(node2)
|
||||
or
|
||||
exists(Node n1, Node n2 |
|
||||
n1 = node1.(PostUpdateNode).getPreUpdateNode() and
|
||||
n2 = node2.(PostUpdateNode).getPreUpdateNode()
|
||||
|
|
||||
argumentValueFlowsThrough(n2, TReadStepTypesSome(containerType, cs, contentType), n1)
|
||||
or
|
||||
readSet(n2, cs, n1) and
|
||||
contentType = getNodeDataFlowType(n1) and
|
||||
containerType = getNodeDataFlowType(n2)
|
||||
)
|
||||
exists(ContentSet cs |
|
||||
c = cs.getAStoreContent() and storeSet(node1, cs, node2, contentType, containerType)
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -540,6 +540,35 @@ class GuardNode extends ControlFlowNode {
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if the guard `g` validates `node` upon evaluating to `branch`.
|
||||
*
|
||||
* The expression `e` is expected to be a syntactic part of the guard `g`.
|
||||
* For example, the guard `g` might be a call `isSafe(x)` and the expression `e`
|
||||
* the argument `x`.
|
||||
*/
|
||||
signature predicate guardChecksSig(GuardNode g, ControlFlowNode node, boolean branch);
|
||||
|
||||
/**
|
||||
* Provides a set of barrier nodes for a guard that validates a node.
|
||||
*
|
||||
* This is expected to be used in `isBarrier`/`isSanitizer` definitions
|
||||
* in data flow and taint tracking.
|
||||
*/
|
||||
module BarrierGuard<guardChecksSig/3 guardChecks> {
|
||||
/** Gets a node that is safely guarded by the given guard check. */
|
||||
ExprNode getABarrierNode() {
|
||||
exists(GuardNode g, EssaDefinition def, ControlFlowNode node, boolean branch |
|
||||
AdjacentUses::useOfDef(def, node) and
|
||||
guardChecks(g, node, branch) and
|
||||
AdjacentUses::useOfDef(def, result.asCfgNode()) and
|
||||
g.controlsBlock(result.asCfgNode().getBasicBlock(), branch)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* DEPRECATED: Use `BarrierGuard` module instead.
|
||||
*
|
||||
* A guard that validates some expression.
|
||||
*
|
||||
* To use this in a configuration, extend the class and provide a
|
||||
@@ -548,7 +577,7 @@ class GuardNode extends ControlFlowNode {
|
||||
*
|
||||
* It is important that all extending classes in scope are disjoint.
|
||||
*/
|
||||
class BarrierGuard extends GuardNode {
|
||||
deprecated class BarrierGuard extends GuardNode {
|
||||
/** Holds if this guard validates `node` upon evaluating to `branch`. */
|
||||
abstract predicate checks(ControlFlowNode node, boolean branch);
|
||||
|
||||
|
||||
@@ -10,12 +10,6 @@ private import semmle.python.ApiGraphs
|
||||
*/
|
||||
predicate defaultTaintSanitizer(DataFlow::Node node) { none() }
|
||||
|
||||
/**
|
||||
* Holds if `guard` should be a sanitizer guard in all global taint flow configurations
|
||||
* but not in local taint.
|
||||
*/
|
||||
predicate defaultTaintSanitizerGuard(DataFlow::BarrierGuard guard) { none() }
|
||||
|
||||
/**
|
||||
* Holds if default `TaintTracking::Configuration`s should allow implicit reads
|
||||
* of `c` at sinks and inputs to additional taint steps.
|
||||
|
||||
@@ -116,20 +116,30 @@ abstract class Configuration extends DataFlow::Configuration {
|
||||
|
||||
final override predicate isBarrierOut(DataFlow::Node node) { this.isSanitizerOut(node) }
|
||||
|
||||
/** Holds if taint propagation through nodes guarded by `guard` is prohibited. */
|
||||
predicate isSanitizerGuard(DataFlow::BarrierGuard guard) { none() }
|
||||
/**
|
||||
* DEPRECATED: Use `isSanitizer` and `BarrierGuard` module instead.
|
||||
*
|
||||
* Holds if taint propagation through nodes guarded by `guard` is prohibited.
|
||||
*/
|
||||
deprecated predicate isSanitizerGuard(DataFlow::BarrierGuard guard) { none() }
|
||||
|
||||
final override predicate isBarrierGuard(DataFlow::BarrierGuard guard) {
|
||||
this.isSanitizerGuard(guard) or defaultTaintSanitizerGuard(guard)
|
||||
deprecated final override predicate isBarrierGuard(DataFlow::BarrierGuard guard) {
|
||||
this.isSanitizerGuard(guard)
|
||||
}
|
||||
|
||||
/**
|
||||
* DEPRECATED: Use `isSanitizer` and `BarrierGuard` module instead.
|
||||
*
|
||||
* Holds if taint propagation through nodes guarded by `guard` is prohibited
|
||||
* when the flow state is `state`.
|
||||
*/
|
||||
predicate isSanitizerGuard(DataFlow::BarrierGuard guard, DataFlow::FlowState state) { none() }
|
||||
deprecated predicate isSanitizerGuard(DataFlow::BarrierGuard guard, DataFlow::FlowState state) {
|
||||
none()
|
||||
}
|
||||
|
||||
final override predicate isBarrierGuard(DataFlow::BarrierGuard guard, DataFlow::FlowState state) {
|
||||
deprecated final override predicate isBarrierGuard(
|
||||
DataFlow::BarrierGuard guard, DataFlow::FlowState state
|
||||
) {
|
||||
this.isSanitizerGuard(guard, state)
|
||||
}
|
||||
|
||||
|
||||
@@ -116,20 +116,30 @@ abstract class Configuration extends DataFlow::Configuration {
|
||||
|
||||
final override predicate isBarrierOut(DataFlow::Node node) { this.isSanitizerOut(node) }
|
||||
|
||||
/** Holds if taint propagation through nodes guarded by `guard` is prohibited. */
|
||||
predicate isSanitizerGuard(DataFlow::BarrierGuard guard) { none() }
|
||||
/**
|
||||
* DEPRECATED: Use `isSanitizer` and `BarrierGuard` module instead.
|
||||
*
|
||||
* Holds if taint propagation through nodes guarded by `guard` is prohibited.
|
||||
*/
|
||||
deprecated predicate isSanitizerGuard(DataFlow::BarrierGuard guard) { none() }
|
||||
|
||||
final override predicate isBarrierGuard(DataFlow::BarrierGuard guard) {
|
||||
this.isSanitizerGuard(guard) or defaultTaintSanitizerGuard(guard)
|
||||
deprecated final override predicate isBarrierGuard(DataFlow::BarrierGuard guard) {
|
||||
this.isSanitizerGuard(guard)
|
||||
}
|
||||
|
||||
/**
|
||||
* DEPRECATED: Use `isSanitizer` and `BarrierGuard` module instead.
|
||||
*
|
||||
* Holds if taint propagation through nodes guarded by `guard` is prohibited
|
||||
* when the flow state is `state`.
|
||||
*/
|
||||
predicate isSanitizerGuard(DataFlow::BarrierGuard guard, DataFlow::FlowState state) { none() }
|
||||
deprecated predicate isSanitizerGuard(DataFlow::BarrierGuard guard, DataFlow::FlowState state) {
|
||||
none()
|
||||
}
|
||||
|
||||
final override predicate isBarrierGuard(DataFlow::BarrierGuard guard, DataFlow::FlowState state) {
|
||||
deprecated final override predicate isBarrierGuard(
|
||||
DataFlow::BarrierGuard guard, DataFlow::FlowState state
|
||||
) {
|
||||
this.isSanitizerGuard(guard, state)
|
||||
}
|
||||
|
||||
|
||||
@@ -116,20 +116,30 @@ abstract class Configuration extends DataFlow::Configuration {
|
||||
|
||||
final override predicate isBarrierOut(DataFlow::Node node) { this.isSanitizerOut(node) }
|
||||
|
||||
/** Holds if taint propagation through nodes guarded by `guard` is prohibited. */
|
||||
predicate isSanitizerGuard(DataFlow::BarrierGuard guard) { none() }
|
||||
/**
|
||||
* DEPRECATED: Use `isSanitizer` and `BarrierGuard` module instead.
|
||||
*
|
||||
* Holds if taint propagation through nodes guarded by `guard` is prohibited.
|
||||
*/
|
||||
deprecated predicate isSanitizerGuard(DataFlow::BarrierGuard guard) { none() }
|
||||
|
||||
final override predicate isBarrierGuard(DataFlow::BarrierGuard guard) {
|
||||
this.isSanitizerGuard(guard) or defaultTaintSanitizerGuard(guard)
|
||||
deprecated final override predicate isBarrierGuard(DataFlow::BarrierGuard guard) {
|
||||
this.isSanitizerGuard(guard)
|
||||
}
|
||||
|
||||
/**
|
||||
* DEPRECATED: Use `isSanitizer` and `BarrierGuard` module instead.
|
||||
*
|
||||
* Holds if taint propagation through nodes guarded by `guard` is prohibited
|
||||
* when the flow state is `state`.
|
||||
*/
|
||||
predicate isSanitizerGuard(DataFlow::BarrierGuard guard, DataFlow::FlowState state) { none() }
|
||||
deprecated predicate isSanitizerGuard(DataFlow::BarrierGuard guard, DataFlow::FlowState state) {
|
||||
none()
|
||||
}
|
||||
|
||||
final override predicate isBarrierGuard(DataFlow::BarrierGuard guard, DataFlow::FlowState state) {
|
||||
deprecated final override predicate isBarrierGuard(
|
||||
DataFlow::BarrierGuard guard, DataFlow::FlowState state
|
||||
) {
|
||||
this.isSanitizerGuard(guard, state)
|
||||
}
|
||||
|
||||
|
||||
@@ -116,20 +116,30 @@ abstract class Configuration extends DataFlow::Configuration {
|
||||
|
||||
final override predicate isBarrierOut(DataFlow::Node node) { this.isSanitizerOut(node) }
|
||||
|
||||
/** Holds if taint propagation through nodes guarded by `guard` is prohibited. */
|
||||
predicate isSanitizerGuard(DataFlow::BarrierGuard guard) { none() }
|
||||
/**
|
||||
* DEPRECATED: Use `isSanitizer` and `BarrierGuard` module instead.
|
||||
*
|
||||
* Holds if taint propagation through nodes guarded by `guard` is prohibited.
|
||||
*/
|
||||
deprecated predicate isSanitizerGuard(DataFlow::BarrierGuard guard) { none() }
|
||||
|
||||
final override predicate isBarrierGuard(DataFlow::BarrierGuard guard) {
|
||||
this.isSanitizerGuard(guard) or defaultTaintSanitizerGuard(guard)
|
||||
deprecated final override predicate isBarrierGuard(DataFlow::BarrierGuard guard) {
|
||||
this.isSanitizerGuard(guard)
|
||||
}
|
||||
|
||||
/**
|
||||
* DEPRECATED: Use `isSanitizer` and `BarrierGuard` module instead.
|
||||
*
|
||||
* Holds if taint propagation through nodes guarded by `guard` is prohibited
|
||||
* when the flow state is `state`.
|
||||
*/
|
||||
predicate isSanitizerGuard(DataFlow::BarrierGuard guard, DataFlow::FlowState state) { none() }
|
||||
deprecated predicate isSanitizerGuard(DataFlow::BarrierGuard guard, DataFlow::FlowState state) {
|
||||
none()
|
||||
}
|
||||
|
||||
final override predicate isBarrierGuard(DataFlow::BarrierGuard guard, DataFlow::FlowState state) {
|
||||
deprecated final override predicate isBarrierGuard(
|
||||
DataFlow::BarrierGuard guard, DataFlow::FlowState state
|
||||
) {
|
||||
this.isSanitizerGuard(guard, state)
|
||||
}
|
||||
|
||||
|
||||
@@ -1,14 +1,15 @@
|
||||
import python
|
||||
private import semmle.python.ApiGraphs
|
||||
|
||||
abstract class TestScope extends Scope { }
|
||||
|
||||
// don't extend Class directly to avoid ambiguous method warnings
|
||||
class UnitTestClass extends TestScope {
|
||||
class UnitTestClass extends TestScope, Class {
|
||||
UnitTestClass() {
|
||||
exists(ClassValue cls | this = cls.getScope() |
|
||||
cls.getABaseType+() = Module::named("unittest").attr(_)
|
||||
or
|
||||
cls.getABaseType+().getName().toLowerCase() = "testcase"
|
||||
exists(API::Node testCaseClass, string testCaseString |
|
||||
testCaseString.matches("%TestCase") and
|
||||
testCaseClass = any(API::Node mod).getMember(testCaseString)
|
||||
|
|
||||
this.getParent() = testCaseClass.getASubclass*().getAnImmediateUse().asExpr()
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -662,7 +662,7 @@ private module AiohttpClientModel {
|
||||
private API::Node instance() { result = classRef().getReturn() }
|
||||
|
||||
/** A method call on a ClientSession that sends off a request */
|
||||
private class OutgoingRequestCall extends HTTP::Client::Request::Range, DataFlow::CallCfgNode {
|
||||
private class OutgoingRequestCall extends HTTP::Client::Request::Range, API::CallNode {
|
||||
string methodName;
|
||||
|
||||
OutgoingRequestCall() {
|
||||
@@ -685,8 +685,14 @@ private module AiohttpClientModel {
|
||||
override predicate disablesCertificateValidation(
|
||||
DataFlow::Node disablingNode, DataFlow::Node argumentOrigin
|
||||
) {
|
||||
// TODO: Look into disabling certificate validation
|
||||
none()
|
||||
exists(API::Node param | param = this.getKeywordParameter(["ssl", "verify_ssl"]) |
|
||||
disablingNode = param.getARhs() and
|
||||
argumentOrigin = param.getAValueReachingRhs() and
|
||||
// aiohttp.client treats `None` as the default and all other "falsey" values as `False`.
|
||||
argumentOrigin.asExpr().(ImmutableLiteral).booleanValue() = false and
|
||||
not argumentOrigin.asExpr() instanceof None
|
||||
)
|
||||
// TODO: Handling of SSLContext passed as ssl/ssl_context arguments
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,91 +7,42 @@ private import python
|
||||
private import semmle.python.dataflow.new.DataFlow
|
||||
private import semmle.python.Concepts
|
||||
private import semmle.python.ApiGraphs
|
||||
private import semmle.python.frameworks.data.ModelsAsData
|
||||
|
||||
/** Provides models for the `asyncpg` PyPI package. */
|
||||
private module Asyncpg {
|
||||
private import semmle.python.internal.Awaited
|
||||
|
||||
/** Gets a `ConnectionPool` that is created when the result of `asyncpg.create_pool()` is awaited. */
|
||||
API::Node connectionPool() {
|
||||
result = API::moduleImport("asyncpg").getMember("create_pool").getReturn().getAwaited()
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a `Connection` that is created when
|
||||
* - the result of `asyncpg.connect()` is awaited.
|
||||
* - the result of calling `acquire` on a `ConnectionPool` is awaited.
|
||||
*/
|
||||
API::Node connection() {
|
||||
result = API::moduleImport("asyncpg").getMember("connect").getReturn().getAwaited()
|
||||
or
|
||||
result = connectionPool().getMember("acquire").getReturn().getAwaited()
|
||||
}
|
||||
|
||||
/** `Connection`s and `ConnectionPool`s provide some methods that execute SQL. */
|
||||
class SqlExecutionOnConnection extends SqlExecution::Range, DataFlow::MethodCallNode {
|
||||
string methodName;
|
||||
|
||||
SqlExecutionOnConnection() {
|
||||
this = [connectionPool(), connection()].getMember(methodName).getACall() and
|
||||
methodName in ["copy_from_query", "execute", "fetch", "fetchrow", "fetchval", "executemany"]
|
||||
}
|
||||
|
||||
override DataFlow::Node getSql() {
|
||||
methodName in ["copy_from_query", "execute", "fetch", "fetchrow", "fetchval"] and
|
||||
result in [this.getArg(0), this.getArgByName("query")]
|
||||
or
|
||||
methodName = "executemany" and
|
||||
result in [this.getArg(0), this.getArgByName("command")]
|
||||
class AsyncpgModel extends ModelInput::TypeModelCsv {
|
||||
override predicate row(string row) {
|
||||
// package1;type1;package2;type2;path
|
||||
row =
|
||||
[
|
||||
// a `ConnectionPool` that is created when the result of `asyncpg.create_pool()` is awaited.
|
||||
"asyncpg;ConnectionPool;asyncpg;;Member[create_pool].ReturnValue.Awaited",
|
||||
// a `Connection` that is created when
|
||||
// * - the result of `asyncpg.connect()` is awaited.
|
||||
// * - the result of calling `acquire` on a `ConnectionPool` is awaited.
|
||||
"asyncpg;Connection;asyncpg;;Member[connect].ReturnValue.Awaited",
|
||||
"asyncpg;Connection;asyncpg;ConnectionPool;Member[acquire].ReturnValue.Awaited",
|
||||
// Creating an internal `~Connection` type that contains both `Connection` and `ConnectionPool`.
|
||||
"asyncpg;~Connection;asyncpg;Connection;", "asyncpg;~Connection;asyncpg;ConnectionPool;"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
/** A model of `Connection` and `ConnectionPool`, which provide some methods that access the file system. */
|
||||
class FileAccessOnConnection extends FileSystemAccess::Range, DataFlow::MethodCallNode {
|
||||
string methodName;
|
||||
|
||||
FileAccessOnConnection() {
|
||||
this = [connectionPool(), connection()].getMember(methodName).getACall() and
|
||||
methodName in ["copy_from_query", "copy_from_table", "copy_to_table"]
|
||||
}
|
||||
|
||||
// The path argument is keyword only.
|
||||
override DataFlow::Node getAPathArgument() {
|
||||
methodName in ["copy_from_query", "copy_from_table"] and
|
||||
result = this.getArgByName("output")
|
||||
or
|
||||
methodName = "copy_to_table" and
|
||||
result = this.getArgByName("source")
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Provides models of the `PreparedStatement` class in `asyncpg`.
|
||||
* `PreparedStatement`s are created when the result of calling `prepare(query)` on a connection is awaited.
|
||||
* The result of calling `prepare(query)` is a `PreparedStatementFactory` and the argument, `query` needs to
|
||||
* be tracked to the place where a `PreparedStatement` is created and then further to any executing methods.
|
||||
* Hence the two type trackers.
|
||||
*/
|
||||
module PreparedStatement {
|
||||
class PreparedStatementConstruction extends SqlConstruction::Range, API::CallNode {
|
||||
PreparedStatementConstruction() { this = connection().getMember("prepare").getACall() }
|
||||
|
||||
override DataFlow::Node getSql() { result = this.getParameter(0, "query").getARhs() }
|
||||
}
|
||||
|
||||
class PreparedStatementExecution extends SqlExecution::Range, API::CallNode {
|
||||
PreparedStatementConstruction prepareCall;
|
||||
|
||||
PreparedStatementExecution() {
|
||||
this =
|
||||
prepareCall
|
||||
.getReturn()
|
||||
.getAwaited()
|
||||
.getMember(["executemany", "fetch", "fetchrow", "fetchval"])
|
||||
.getACall()
|
||||
}
|
||||
|
||||
override DataFlow::Node getSql() { result = prepareCall.getSql() }
|
||||
class AsyncpgSink extends ModelInput::SinkModelCsv {
|
||||
// package;type;path;kind
|
||||
override predicate row(string row) {
|
||||
row =
|
||||
[
|
||||
// `Connection`s and `ConnectionPool`s provide some methods that execute SQL.
|
||||
"asyncpg;~Connection;Member[copy_from_query,execute,fetch,fetchrow,fetchval].Argument[0,query:];sql-injection",
|
||||
"asyncpg;~Connection;Member[executemany].Argument[0,command:];sql-injection",
|
||||
// A model of `Connection` and `ConnectionPool`, which provide some methods that access the file system.
|
||||
"asyncpg;~Connection;Member[copy_from_query,copy_from_table].Argument[output:];path-injection",
|
||||
"asyncpg;~Connection;Member[copy_to_table].Argument[source:];path-injection",
|
||||
// the `PreparedStatement` class in `asyncpg`.
|
||||
"asyncpg;Connection;Member[prepare].Argument[0,query:];sql-injection",
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
@@ -106,7 +57,9 @@ private module Asyncpg {
|
||||
*/
|
||||
module Cursor {
|
||||
class CursorConstruction extends SqlConstruction::Range, API::CallNode {
|
||||
CursorConstruction() { this = connection().getMember("cursor").getACall() }
|
||||
CursorConstruction() {
|
||||
this = ModelOutput::getATypeNode("asyncpg", "Connection").getMember("cursor").getACall()
|
||||
}
|
||||
|
||||
override DataFlow::Node getSql() { result = this.getParameter(0, "query").getARhs() }
|
||||
}
|
||||
@@ -121,8 +74,11 @@ private module Asyncpg {
|
||||
this = c.getReturn().getAwaited().getAnImmediateUse()
|
||||
)
|
||||
or
|
||||
exists(PreparedStatement::PreparedStatementConstruction prepareCall |
|
||||
sql = prepareCall.getSql() and
|
||||
exists(API::CallNode prepareCall |
|
||||
prepareCall =
|
||||
ModelOutput::getATypeNode("asyncpg", "Connection").getMember("prepare").getACall()
|
||||
|
|
||||
sql = prepareCall.getParameter(0, "query").getARhs() and
|
||||
this =
|
||||
prepareCall
|
||||
.getReturn()
|
||||
|
||||
@@ -108,20 +108,20 @@ private module CryptodomeModel {
|
||||
DataFlow::CallCfgNode {
|
||||
string methodName;
|
||||
string cipherName;
|
||||
API::CallNode newCall;
|
||||
|
||||
CryptodomeGenericCipherOperation() {
|
||||
methodName in [
|
||||
"encrypt", "decrypt", "verify", "update", "hexverify", "encrypt_and_digest",
|
||||
"decrypt_and_verify"
|
||||
] and
|
||||
this =
|
||||
newCall =
|
||||
API::moduleImport(["Crypto", "Cryptodome"])
|
||||
.getMember("Cipher")
|
||||
.getMember(cipherName)
|
||||
.getMember("new")
|
||||
.getReturn()
|
||||
.getMember(methodName)
|
||||
.getACall()
|
||||
.getACall() and
|
||||
this = newCall.getReturn().getMember(methodName).getACall()
|
||||
}
|
||||
|
||||
override Cryptography::CryptographicAlgorithm getAlgorithm() { result.matchesName(cipherName) }
|
||||
@@ -155,6 +155,20 @@ private module CryptodomeModel {
|
||||
this.getArgByName("mac_tag")
|
||||
]
|
||||
}
|
||||
|
||||
override Cryptography::BlockMode getBlockMode() {
|
||||
// `modeName` is of the form "MODE_<BlockMode>"
|
||||
exists(string modeName |
|
||||
newCall.getArg(1) =
|
||||
API::moduleImport(["Crypto", "Cryptodome"])
|
||||
.getMember("Cipher")
|
||||
.getMember(cipherName)
|
||||
.getMember(modeName)
|
||||
.getAUse()
|
||||
|
|
||||
result = modeName.splitAt("_", 1)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -192,6 +206,8 @@ private module CryptodomeModel {
|
||||
result in [this.getArg(1), this.getArgByName("signature")]
|
||||
)
|
||||
}
|
||||
|
||||
override Cryptography::BlockMode getBlockMode() { none() }
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -215,5 +231,7 @@ private module CryptodomeModel {
|
||||
override Cryptography::CryptographicAlgorithm getAlgorithm() { result.matchesName(hashName) }
|
||||
|
||||
override DataFlow::Node getAnInput() { result in [this.getArg(0), this.getArgByName("data")] }
|
||||
|
||||
override Cryptography::BlockMode getBlockMode() { none() }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -170,8 +170,19 @@ private module CryptographyModel {
|
||||
.getMember(algorithmName)
|
||||
}
|
||||
|
||||
/** Gets a reference to a `cryptography.hazmat.primitives.ciphers.modes` Class */
|
||||
API::Node modeClassRef(string modeName) {
|
||||
result =
|
||||
API::moduleImport("cryptography")
|
||||
.getMember("hazmat")
|
||||
.getMember("primitives")
|
||||
.getMember("ciphers")
|
||||
.getMember("modes")
|
||||
.getMember(modeName)
|
||||
}
|
||||
|
||||
/** Gets a reference to a Cipher instance using algorithm with `algorithmName`. */
|
||||
API::Node cipherInstance(string algorithmName) {
|
||||
API::Node cipherInstance(string algorithmName, string modeName) {
|
||||
exists(API::CallNode call | result = call.getReturn() |
|
||||
call =
|
||||
API::moduleImport("cryptography")
|
||||
@@ -182,7 +193,12 @@ private module CryptographyModel {
|
||||
.getACall() and
|
||||
algorithmClassRef(algorithmName).getReturn().getAUse() in [
|
||||
call.getArg(0), call.getArgByName("algorithm")
|
||||
]
|
||||
] and
|
||||
exists(DataFlow::Node modeArg | modeArg in [call.getArg(1), call.getArgByName("mode")] |
|
||||
if modeArg = modeClassRef(_).getReturn().getAUse()
|
||||
then modeArg = modeClassRef(modeName).getReturn().getAUse()
|
||||
else modeName = "<None or unknown>"
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
@@ -192,10 +208,11 @@ private module CryptographyModel {
|
||||
class CryptographyGenericCipherOperation extends Cryptography::CryptographicOperation::Range,
|
||||
DataFlow::MethodCallNode {
|
||||
string algorithmName;
|
||||
string modeName;
|
||||
|
||||
CryptographyGenericCipherOperation() {
|
||||
this =
|
||||
cipherInstance(algorithmName)
|
||||
cipherInstance(algorithmName, modeName)
|
||||
.getMember(["decryptor", "encryptor"])
|
||||
.getReturn()
|
||||
.getMember(["update", "update_into"])
|
||||
@@ -207,6 +224,8 @@ private module CryptographyModel {
|
||||
}
|
||||
|
||||
override DataFlow::Node getAnInput() { result in [this.getArg(0), this.getArgByName("data")] }
|
||||
|
||||
override Cryptography::BlockMode getBlockMode() { result = modeName }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -257,6 +276,8 @@ private module CryptographyModel {
|
||||
}
|
||||
|
||||
override DataFlow::Node getAnInput() { result in [this.getArg(0), this.getArgByName("data")] }
|
||||
|
||||
override Cryptography::BlockMode getBlockMode() { none() }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -464,7 +464,15 @@ module Django {
|
||||
|
||||
/** A file-like object instance that originates from a `UploadedFile`. */
|
||||
class UploadedFileFileLikeInstances extends Stdlib::FileLikeObject::InstanceSource {
|
||||
UploadedFileFileLikeInstances() { this.(DataFlow::AttrRead).accesses(instance(), "file") }
|
||||
UploadedFileFileLikeInstances() {
|
||||
// in the bottom of
|
||||
// https://docs.djangoproject.com/en/4.0/ref/files/file/#django.core.files.File
|
||||
// it's mentioned that the File object itself has proxy methods for
|
||||
// `read`/`write`/... that forwards to the underlying file object.
|
||||
this = instance()
|
||||
or
|
||||
this.(DataFlow::AttrRead).accesses(instance(), "file")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -18,7 +18,12 @@ private import semmle.python.ApiGraphs
|
||||
* - https://www.python-httpx.org/
|
||||
*/
|
||||
private module HttpxModel {
|
||||
private class RequestCall extends HTTP::Client::Request::Range, DataFlow::CallCfgNode {
|
||||
/**
|
||||
* An outgoing HTTP request, from the `httpx` library.
|
||||
*
|
||||
* See https://www.python-httpx.org/api/
|
||||
*/
|
||||
private class RequestCall extends HTTP::Client::Request::Range, API::CallNode {
|
||||
string methodName;
|
||||
|
||||
RequestCall() {
|
||||
@@ -39,15 +44,18 @@ private module HttpxModel {
|
||||
override predicate disablesCertificateValidation(
|
||||
DataFlow::Node disablingNode, DataFlow::Node argumentOrigin
|
||||
) {
|
||||
// TODO: Look into disabling certificate validation
|
||||
none()
|
||||
disablingNode = this.getKeywordParameter("verify").getARhs() and
|
||||
argumentOrigin = this.getKeywordParameter("verify").getAValueReachingRhs() and
|
||||
// unlike `requests`, httpx treats `None` as turning off verify (and not as the default)
|
||||
argumentOrigin.asExpr().(ImmutableLiteral).booleanValue() = false
|
||||
// TODO: Handling of insecure SSLContext passed to verify argument
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Provides models for the `httpx.[Async]Client` class
|
||||
*
|
||||
* See https://www.python-httpx.org/async/
|
||||
* See https://www.python-httpx.org/api/#client
|
||||
*/
|
||||
module Client {
|
||||
/** Get a reference to the `httpx.Client` or `httpx.AsyncClient` class. */
|
||||
@@ -55,16 +63,13 @@ private module HttpxModel {
|
||||
result = API::moduleImport("httpx").getMember(["Client", "AsyncClient"])
|
||||
}
|
||||
|
||||
/** Get a reference to an `httpx.Client` or `httpx.AsyncClient` instance. */
|
||||
private API::Node instance() { result = classRef().getReturn() }
|
||||
|
||||
/** A method call on a Client that sends off a request */
|
||||
private class OutgoingRequestCall extends HTTP::Client::Request::Range, DataFlow::CallCfgNode {
|
||||
string methodName;
|
||||
|
||||
OutgoingRequestCall() {
|
||||
methodName in [HTTP::httpVerbLower(), "request", "stream"] and
|
||||
this = instance().getMember(methodName).getACall()
|
||||
this = classRef().getReturn().getMember(methodName).getACall()
|
||||
}
|
||||
|
||||
override DataFlow::Node getAUrlPart() {
|
||||
@@ -80,8 +85,16 @@ private module HttpxModel {
|
||||
override predicate disablesCertificateValidation(
|
||||
DataFlow::Node disablingNode, DataFlow::Node argumentOrigin
|
||||
) {
|
||||
// TODO: Look into disabling certificate validation
|
||||
none()
|
||||
exists(API::CallNode constructor |
|
||||
constructor = classRef().getACall() and
|
||||
this = constructor.getReturn().getMember(methodName).getACall()
|
||||
|
|
||||
disablingNode = constructor.getKeywordParameter("verify").getARhs() and
|
||||
argumentOrigin = constructor.getKeywordParameter("verify").getAValueReachingRhs() and
|
||||
// unlike `requests`, httpx treats `None` as turning off verify (and not as the default)
|
||||
argumentOrigin.asExpr().(ImmutableLiteral).booleanValue() = false
|
||||
// TODO: Handling of insecure SSLContext passed to verify argument
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -20,9 +20,14 @@ private import semmle.python.frameworks.Stdlib
|
||||
*
|
||||
* See
|
||||
* - https://pypi.org/project/requests/
|
||||
* - https://docs.python-requests.org/en/latest/
|
||||
* - https://requests.readthedocs.io/en/latest/
|
||||
*/
|
||||
private module Requests {
|
||||
/**
|
||||
* An outgoing HTTP request, from the `requests` library.
|
||||
*
|
||||
* See https://requests.readthedocs.io/en/latest/api/#requests.request
|
||||
*/
|
||||
private class OutgoingRequestCall extends HTTP::Client::Request::Range, API::CallNode {
|
||||
string methodName;
|
||||
|
||||
@@ -58,6 +63,7 @@ private module Requests {
|
||||
) {
|
||||
disablingNode = this.getKeywordParameter("verify").getARhs() and
|
||||
argumentOrigin = this.getKeywordParameter("verify").getAValueReachingRhs() and
|
||||
// requests treats `None` as the default and all other "falsey" values as `False`.
|
||||
argumentOrigin.asExpr().(ImmutableLiteral).booleanValue() = false and
|
||||
not argumentOrigin.asExpr() instanceof None
|
||||
}
|
||||
@@ -81,7 +87,7 @@ private module Requests {
|
||||
/**
|
||||
* Provides models for the `requests.models.Response` class
|
||||
*
|
||||
* See https://docs.python-requests.org/en/latest/api/#requests.Response.
|
||||
* See https://requests.readthedocs.io/en/latest/api/#requests.Response.
|
||||
*/
|
||||
module Response {
|
||||
/** Gets a reference to the `requests.models.Response` class. */
|
||||
|
||||
@@ -41,6 +41,8 @@ private module Rsa {
|
||||
override DataFlow::Node getAnInput() {
|
||||
result in [this.getArg(0), this.getArgByName("message")]
|
||||
}
|
||||
|
||||
override Cryptography::BlockMode getBlockMode() { none() }
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -54,6 +56,8 @@ private module Rsa {
|
||||
override Cryptography::CryptographicAlgorithm getAlgorithm() { result.getName() = "RSA" }
|
||||
|
||||
override DataFlow::Node getAnInput() { result in [this.getArg(0), this.getArgByName("crypto")] }
|
||||
|
||||
override Cryptography::BlockMode getBlockMode() { none() }
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -79,6 +83,8 @@ private module Rsa {
|
||||
override DataFlow::Node getAnInput() {
|
||||
result in [this.getArg(0), this.getArgByName("message")]
|
||||
}
|
||||
|
||||
override Cryptography::BlockMode getBlockMode() { none() }
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -100,6 +106,8 @@ private module Rsa {
|
||||
or
|
||||
result in [this.getArg(1), this.getArgByName("signature")]
|
||||
}
|
||||
|
||||
override Cryptography::BlockMode getBlockMode() { none() }
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -122,6 +130,8 @@ private module Rsa {
|
||||
override DataFlow::Node getAnInput() {
|
||||
result in [this.getArg(0), this.getArgByName("message")]
|
||||
}
|
||||
|
||||
override Cryptography::BlockMode getBlockMode() { none() }
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -137,5 +147,7 @@ private module Rsa {
|
||||
override DataFlow::Node getAnInput() {
|
||||
result in [this.getArg(0), this.getArgByName("hash_value")]
|
||||
}
|
||||
|
||||
override Cryptography::BlockMode getBlockMode() { none() }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2671,6 +2671,8 @@ private module StdlibPrivate {
|
||||
override Cryptography::CryptographicAlgorithm getAlgorithm() { result.matchesName(hashName) }
|
||||
|
||||
override DataFlow::Node getAnInput() { result = this.getParameter(1, "data").getARhs() }
|
||||
|
||||
override Cryptography::BlockMode getBlockMode() { none() }
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -2686,6 +2688,8 @@ private module StdlibPrivate {
|
||||
override Cryptography::CryptographicAlgorithm getAlgorithm() { result.matchesName(hashName) }
|
||||
|
||||
override DataFlow::Node getAnInput() { result = this.getArg(0) }
|
||||
|
||||
override Cryptography::BlockMode getBlockMode() { none() }
|
||||
}
|
||||
|
||||
/** Helper predicate for the `HashLibGenericHashOperation` charpred, to prevent a bad join order. */
|
||||
@@ -2709,6 +2713,8 @@ private module StdlibPrivate {
|
||||
HashlibGenericHashOperation() { hashClass = hashlibMember(hashName) }
|
||||
|
||||
override Cryptography::CryptographicAlgorithm getAlgorithm() { result.matchesName(hashName) }
|
||||
|
||||
override Cryptography::BlockMode getBlockMode() { none() }
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -42,7 +42,8 @@ private module Urllib {
|
||||
override predicate disablesCertificateValidation(
|
||||
DataFlow::Node disablingNode, DataFlow::Node argumentOrigin
|
||||
) {
|
||||
// TODO: Look into disabling certificate validation
|
||||
// cannot enable/disable certificate validation on this object, only when used
|
||||
// with `urlopen`, which is modeled below
|
||||
none()
|
||||
}
|
||||
}
|
||||
@@ -63,7 +64,8 @@ private module Urllib {
|
||||
override predicate disablesCertificateValidation(
|
||||
DataFlow::Node disablingNode, DataFlow::Node argumentOrigin
|
||||
) {
|
||||
// TODO: Look into disabling certificate validation
|
||||
// will validate certificate by default, see https://github.com/python/cpython/blob/243ed5439c32e8517aa745bc2ca9774d99c99d0f/Lib/http/client.py#L1420-L1421
|
||||
// TODO: Handling of insecure SSLContext passed to context argument
|
||||
none()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -30,7 +30,8 @@ private module Urllib2 {
|
||||
override predicate disablesCertificateValidation(
|
||||
DataFlow::Node disablingNode, DataFlow::Node argumentOrigin
|
||||
) {
|
||||
// TODO: Look into disabling certificate validation
|
||||
// cannot enable/disable certificate validation on this object, only when used
|
||||
// with `urlopen`, which is modeled below
|
||||
none()
|
||||
}
|
||||
}
|
||||
@@ -49,7 +50,8 @@ private module Urllib2 {
|
||||
override predicate disablesCertificateValidation(
|
||||
DataFlow::Node disablingNode, DataFlow::Node argumentOrigin
|
||||
) {
|
||||
// TODO: Look into disabling certificate validation
|
||||
// will validate certificate by default
|
||||
// TODO: Handling of insecure SSLContext passed to context argument
|
||||
none()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -42,9 +42,6 @@ private module Urllib3 {
|
||||
.getASubclass+()
|
||||
}
|
||||
|
||||
/** Gets a reference to an instance of a `urllib3.request.RequestMethods` subclass. */
|
||||
private API::Node instance() { result = classRef().getReturn() }
|
||||
|
||||
/**
|
||||
* A call to a method making an outgoing request.
|
||||
*
|
||||
@@ -52,10 +49,11 @@ private module Urllib3 {
|
||||
* - https://urllib3.readthedocs.io/en/stable/reference/urllib3.request.html#urllib3.request.RequestMethods
|
||||
* - https://urllib3.readthedocs.io/en/stable/reference/urllib3.connectionpool.html#urllib3.HTTPConnectionPool.urlopen
|
||||
*/
|
||||
private class RequestCall extends HTTP::Client::Request::Range, DataFlow::CallCfgNode {
|
||||
private class RequestCall extends HTTP::Client::Request::Range, API::CallNode {
|
||||
RequestCall() {
|
||||
this =
|
||||
instance()
|
||||
classRef()
|
||||
.getReturn()
|
||||
.getMember(["request", "request_encode_url", "request_encode_body", "urlopen"])
|
||||
.getACall()
|
||||
}
|
||||
@@ -67,8 +65,22 @@ private module Urllib3 {
|
||||
override predicate disablesCertificateValidation(
|
||||
DataFlow::Node disablingNode, DataFlow::Node argumentOrigin
|
||||
) {
|
||||
// TODO: Look into disabling certificate validation
|
||||
none()
|
||||
exists(API::CallNode constructor |
|
||||
constructor = classRef().getACall() and
|
||||
this = constructor.getReturn().getAMember().getACall()
|
||||
|
|
||||
// cert_reqs
|
||||
// see https://urllib3.readthedocs.io/en/stable/user-guide.html?highlight=cert_reqs#certificate-verification
|
||||
disablingNode = constructor.getKeywordParameter("cert_reqs").getARhs() and
|
||||
argumentOrigin = constructor.getKeywordParameter("cert_reqs").getAValueReachingRhs() and
|
||||
argumentOrigin.asExpr().(StrConst).getText() = "CERT_NONE"
|
||||
or
|
||||
// assert_hostname
|
||||
// see https://urllib3.readthedocs.io/en/stable/reference/urllib3.connectionpool.html?highlight=assert_hostname#urllib3.HTTPSConnectionPool
|
||||
disablingNode = constructor.getKeywordParameter("assert_hostname").getARhs() and
|
||||
argumentOrigin = constructor.getKeywordParameter("assert_hostname").getAValueReachingRhs() and
|
||||
argumentOrigin.asExpr().(BooleanLiteral).booleanValue() = false
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
47
python/ql/lib/semmle/python/frameworks/data/ModelsAsData.qll
Normal file
47
python/ql/lib/semmle/python/frameworks/data/ModelsAsData.qll
Normal file
@@ -0,0 +1,47 @@
|
||||
/**
|
||||
* Provides classes for contributing a model, or using the interpreted results
|
||||
* of a model represented as data.
|
||||
*
|
||||
* - Use the `ModelInput` module to contribute new models.
|
||||
* - Use the `ModelOutput` module to access the model results in terms of API nodes.
|
||||
*
|
||||
* The package name refers to the top-level module the import comes from, and not a PyPI package.
|
||||
* So for `from foo.bar import baz`, the package will be `foo`.
|
||||
*/
|
||||
|
||||
private import python
|
||||
private import internal.ApiGraphModels as Shared
|
||||
private import internal.ApiGraphModelsSpecific as Specific
|
||||
import Shared::ModelInput as ModelInput
|
||||
import Shared::ModelOutput as ModelOutput
|
||||
private import semmle.python.dataflow.new.RemoteFlowSources
|
||||
private import semmle.python.dataflow.new.DataFlow
|
||||
private import semmle.python.ApiGraphs
|
||||
private import semmle.python.dataflow.new.TaintTracking
|
||||
|
||||
/**
|
||||
* A remote flow source originating from a CSV source row.
|
||||
*/
|
||||
private class RemoteFlowSourceFromCsv extends RemoteFlowSource {
|
||||
RemoteFlowSourceFromCsv() { this = ModelOutput::getASourceNode("remote").getAnImmediateUse() }
|
||||
|
||||
override string getSourceType() { result = "Remote flow (from model)" }
|
||||
}
|
||||
|
||||
/**
|
||||
* Like `ModelOutput::summaryStep` but with API nodes mapped to data-flow nodes.
|
||||
*/
|
||||
private predicate summaryStepNodes(DataFlow::Node pred, DataFlow::Node succ, string kind) {
|
||||
exists(API::Node predNode, API::Node succNode |
|
||||
Specific::summaryStep(predNode, succNode, kind) and
|
||||
pred = predNode.getARhs() and
|
||||
succ = succNode.getAnImmediateUse()
|
||||
)
|
||||
}
|
||||
|
||||
/** Taint steps induced by summary models of kind `taint`. */
|
||||
private class TaintStepFromSummary extends TaintTracking::AdditionalTaintStep {
|
||||
override predicate step(DataFlow::Node pred, DataFlow::Node succ) {
|
||||
summaryStepNodes(pred, succ, "taint")
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,182 @@
|
||||
/**
|
||||
* Module for parsing access paths from CSV models, both the identifying access path used
|
||||
* by dynamic languages, and the input/output specifications for summary steps.
|
||||
*
|
||||
* This file is used by the shared data flow library and by the JavaScript libraries
|
||||
* (which does not use the shared data flow libraries).
|
||||
*/
|
||||
|
||||
/**
|
||||
* Convenience-predicate for extracting two capture groups at once.
|
||||
*/
|
||||
bindingset[input, regexp]
|
||||
private predicate regexpCaptureTwo(string input, string regexp, string capture1, string capture2) {
|
||||
capture1 = input.regexpCapture(regexp, 1) and
|
||||
capture2 = input.regexpCapture(regexp, 2)
|
||||
}
|
||||
|
||||
/** Companion module to the `AccessPath` class. */
|
||||
module AccessPath {
|
||||
/** A string that should be parsed as an access path. */
|
||||
abstract class Range extends string {
|
||||
bindingset[this]
|
||||
Range() { any() }
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses an integer constant `n` or interval `n1..n2` (inclusive) and gets the value
|
||||
* of the constant or any value contained in the interval.
|
||||
*/
|
||||
bindingset[arg]
|
||||
int parseInt(string arg) {
|
||||
result = arg.toInt()
|
||||
or
|
||||
// Match "n1..n2"
|
||||
exists(string lo, string hi |
|
||||
regexpCaptureTwo(arg, "(-?\\d+)\\.\\.(-?\\d+)", lo, hi) and
|
||||
result = [lo.toInt() .. hi.toInt()]
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses a lower-bounded interval `n..` and gets the lower bound.
|
||||
*/
|
||||
bindingset[arg]
|
||||
int parseLowerBound(string arg) { result = arg.regexpCapture("(-?\\d+)\\.\\.", 1).toInt() }
|
||||
|
||||
/**
|
||||
* Parses an integer constant or interval (bounded or unbounded) that explicitly
|
||||
* references the arity, such as `N-1` or `N-3..N-1`.
|
||||
*
|
||||
* Note that expressions of form `N-x` will never resolve to a negative index,
|
||||
* even if `N` is zero (it will have no result in that case).
|
||||
*/
|
||||
bindingset[arg, arity]
|
||||
private int parseIntWithExplicitArity(string arg, int arity) {
|
||||
result >= 0 and // do not allow N-1 to resolve to a negative index
|
||||
exists(string lo |
|
||||
// N-x
|
||||
lo = arg.regexpCapture("N-(\\d+)", 1) and
|
||||
result = arity - lo.toInt()
|
||||
or
|
||||
// N-x..
|
||||
lo = arg.regexpCapture("N-(\\d+)\\.\\.", 1) and
|
||||
result = [arity - lo.toInt(), arity - 1]
|
||||
)
|
||||
or
|
||||
exists(string lo, string hi |
|
||||
// x..N-y
|
||||
regexpCaptureTwo(arg, "(-?\\d+)\\.\\.N-(\\d+)", lo, hi) and
|
||||
result = [lo.toInt() .. arity - hi.toInt()]
|
||||
or
|
||||
// N-x..N-y
|
||||
regexpCaptureTwo(arg, "N-(\\d+)\\.\\.N-(\\d+)", lo, hi) and
|
||||
result = [arity - lo.toInt() .. arity - hi.toInt()] and
|
||||
result >= 0
|
||||
or
|
||||
// N-x..y
|
||||
regexpCaptureTwo(arg, "N-(\\d+)\\.\\.(\\d+)", lo, hi) and
|
||||
result = [arity - lo.toInt() .. hi.toInt()] and
|
||||
result >= 0
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses an integer constant or interval (bounded or unbounded) and gets any
|
||||
* of the integers contained within (of which there may be infinitely many).
|
||||
*
|
||||
* Has no result for arguments involving an explicit arity, such as `N-1`.
|
||||
*/
|
||||
bindingset[arg, result]
|
||||
int parseIntUnbounded(string arg) {
|
||||
result = parseInt(arg)
|
||||
or
|
||||
result >= parseLowerBound(arg)
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses an integer constant or interval (bounded or unbounded) that
|
||||
* may reference the arity of a call, such as `N-1` or `N-3..N-1`.
|
||||
*
|
||||
* Note that expressions of form `N-x` will never resolve to a negative index,
|
||||
* even if `N` is zero (it will have no result in that case).
|
||||
*/
|
||||
bindingset[arg, arity]
|
||||
int parseIntWithArity(string arg, int arity) {
|
||||
result = parseInt(arg)
|
||||
or
|
||||
result in [parseLowerBound(arg) .. arity - 1]
|
||||
or
|
||||
result = parseIntWithExplicitArity(arg, arity)
|
||||
}
|
||||
}
|
||||
|
||||
/** Gets the `n`th token on the access path as a string. */
|
||||
private string getRawToken(AccessPath path, int n) {
|
||||
// Avoid splitting by '.' since tokens may contain dots, e.g. `Field[foo.Bar.x]`.
|
||||
// Instead use regexpFind to match valid tokens, and supplement with a final length
|
||||
// check (in `AccessPath.hasSyntaxError`) to ensure all characters were included in a token.
|
||||
result = path.regexpFind("\\w+(?:\\[[^\\]]*\\])?(?=\\.|$)", n, _)
|
||||
}
|
||||
|
||||
/**
|
||||
* A string that occurs as an access path (either identifying or input/output spec)
|
||||
* which might be relevant for this database.
|
||||
*/
|
||||
class AccessPath extends string instanceof AccessPath::Range {
|
||||
/** Holds if this string is not a syntactically valid access path. */
|
||||
predicate hasSyntaxError() {
|
||||
// If the lengths match, all characters must haven been included in a token
|
||||
// or seen by the `.` lookahead pattern.
|
||||
this != "" and
|
||||
not this.length() = sum(int n | | getRawToken(this, n).length() + 1) - 1
|
||||
}
|
||||
|
||||
/** Gets the `n`th token on the access path (if there are no syntax errors). */
|
||||
AccessPathToken getToken(int n) {
|
||||
result = getRawToken(this, n) and
|
||||
not this.hasSyntaxError()
|
||||
}
|
||||
|
||||
/** Gets the number of tokens on the path (if there are no syntax errors). */
|
||||
int getNumToken() {
|
||||
result = count(int n | exists(getRawToken(this, n))) and
|
||||
not this.hasSyntaxError()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* An access part token such as `Argument[1]` or `ReturnValue`, appearing in one or more access paths.
|
||||
*/
|
||||
class AccessPathToken extends string {
|
||||
AccessPathToken() { this = getRawToken(_, _) }
|
||||
|
||||
private string getPart(int part) {
|
||||
result = this.regexpCapture("([^\\[]+)(?:\\[([^\\]]*)\\])?", part)
|
||||
}
|
||||
|
||||
/** Gets the name of the token, such as `Member` from `Member[x]` */
|
||||
string getName() { result = this.getPart(1) }
|
||||
|
||||
/**
|
||||
* Gets the argument list, such as `1,2` from `Member[1,2]`,
|
||||
* or has no result if there are no arguments.
|
||||
*/
|
||||
string getArgumentList() { result = this.getPart(2) }
|
||||
|
||||
/** Gets the `n`th argument to this token, such as `x` or `y` from `Member[x,y]`. */
|
||||
string getArgument(int n) { result = this.getArgumentList().splitAt(",", n).trim() }
|
||||
|
||||
/** Gets the `n`th argument to this `name` token, such as `x` or `y` from `Member[x,y]`. */
|
||||
pragma[nomagic]
|
||||
string getArgument(string name, int n) { name = this.getName() and result = this.getArgument(n) }
|
||||
|
||||
/** Gets an argument to this token, such as `x` or `y` from `Member[x,y]`. */
|
||||
string getAnArgument() { result = this.getArgument(_) }
|
||||
|
||||
/** Gets an argument to this `name` token, such as `x` or `y` from `Member[x,y]`. */
|
||||
string getAnArgument(string name) { result = this.getArgument(name, _) }
|
||||
|
||||
/** Gets the number of arguments to this token, such as 2 for `Member[x,y]` or zero for `ReturnValue`. */
|
||||
int getNumArgument() { result = count(int n | exists(this.getArgument(n))) }
|
||||
}
|
||||
@@ -0,0 +1,522 @@
|
||||
/**
|
||||
* INTERNAL use only. This is an experimental API subject to change without notice.
|
||||
*
|
||||
* Provides classes and predicates for dealing with flow models specified in CSV format.
|
||||
*
|
||||
* The CSV specification has the following columns:
|
||||
* - Sources:
|
||||
* `package; type; path; kind`
|
||||
* - Sinks:
|
||||
* `package; type; path; kind`
|
||||
* - Summaries:
|
||||
* `package; type; path; input; output; kind`
|
||||
* - Types:
|
||||
* `package1; type1; package2; type2; path`
|
||||
*
|
||||
* The interpretation of a row is similar to API-graphs with a left-to-right
|
||||
* reading.
|
||||
* 1. The `package` column selects a package name, as it would be referenced in the source code,
|
||||
* such as an NPM package, PIP package, or Ruby gem. (See `ModelsAsData.qll` for language-specific details).
|
||||
* It may also be a synthetic package used for a type definition (see type definitions below).
|
||||
* 2. The `type` column selects all instances of a named type originating from that package,
|
||||
* or the empty string if referring to the package itself.
|
||||
* It can also be a synthetic type name defined by a type definition (see type definitions below).
|
||||
* 3. The `path` column is a `.`-separated list of "access path tokens" to resolve, starting at the node selected by `package` and `type`.
|
||||
*
|
||||
* Every language supports the following tokens:
|
||||
* - Argument[n]: the n-th argument to a call. May be a range of form `x..y` (inclusive) and/or a comma-separated list.
|
||||
* Additionally, `N-1` refers to the last argument, `N-2` refers to the second-last, and so on.
|
||||
* - Parameter[n]: the n-th parameter of a callback. May be a range of form `x..y` (inclusive) and/or a comma-separated list.
|
||||
* - ReturnValue: the value returned by a function call
|
||||
* - WithArity[n]: match a call with the given arity. May be a range of form `x..y` (inclusive) and/or a comma-separated list.
|
||||
*
|
||||
* The following tokens are common and should be implemented for languages where it makes sense:
|
||||
* - Member[x]: a member named `x`; exactly what a "member" is depends on the language. May be a comma-separated list of names.
|
||||
* - Instance: an instance of a class
|
||||
* - Subclass: a subclass of a class
|
||||
* - ArrayElement: an element of array
|
||||
* - Element: an element of a collection-like object
|
||||
* - MapKey: a key in map-like object
|
||||
* - MapValue: a value in a map-like object
|
||||
* - Awaited: the value from a resolved promise/future-like object
|
||||
*
|
||||
* For the time being, please consult `ApiGraphModelsSpecific.qll` to see which language-specific tokens are currently supported.
|
||||
*
|
||||
* 4. The `input` and `output` columns specify how data enters and leaves the element selected by the
|
||||
* first `(package, type, path)` tuple. Both strings are `.`-separated access paths
|
||||
* of the same syntax as the `path` column.
|
||||
* 5. The `kind` column is a tag that can be referenced from QL to determine to
|
||||
* which classes the interpreted elements should be added. For example, for
|
||||
* sources `"remote"` indicates a default remote flow source, and for summaries
|
||||
* `"taint"` indicates a default additional taint step and `"value"` indicates a
|
||||
* globally applicable value-preserving step.
|
||||
*
|
||||
* ### Types
|
||||
*
|
||||
* A type row of form `package1; type1; package2; type2; path` indicates that `package2; type2; path`
|
||||
* should be seen as an instance of the type `package1; type1`.
|
||||
*
|
||||
* A `(package,type)` pair may refer to a static type or a synthetic type name used internally in the model.
|
||||
* Synthetic type names can be used to reuse intermediate sub-paths, when there are multiple ways to access the same
|
||||
* element.
|
||||
* See `ModelsAsData.qll` for the language-specific interpretation of packages and static type names.
|
||||
*
|
||||
* By convention, if one wants to avoid clashes with static types from the package, the type name
|
||||
* should be prefixed with a tilde character (`~`). For example, `(foo, ~Bar)` can be used to indicate that
|
||||
* the type is related to the `foo` package but is not intended to match a static type.
|
||||
*/
|
||||
|
||||
private import ApiGraphModelsSpecific as Specific
|
||||
|
||||
private class Unit = Specific::Unit;
|
||||
|
||||
private module API = Specific::API;
|
||||
|
||||
private import Specific::AccessPathSyntax
|
||||
|
||||
/** Module containing hooks for providing input data to be interpreted as a model. */
|
||||
module ModelInput {
|
||||
/**
|
||||
* A unit class for adding additional source model rows.
|
||||
*
|
||||
* Extend this class to add additional source definitions.
|
||||
*/
|
||||
class SourceModelCsv extends Unit {
|
||||
/**
|
||||
* Holds if `row` specifies a source definition.
|
||||
*
|
||||
* A row of form
|
||||
* ```
|
||||
* package;type;path;kind
|
||||
* ```
|
||||
* indicates that the value at `(package, type, path)` should be seen as a flow
|
||||
* source of the given `kind`.
|
||||
*
|
||||
* The kind `remote` represents a general remote flow source.
|
||||
*/
|
||||
abstract predicate row(string row);
|
||||
}
|
||||
|
||||
/**
|
||||
* A unit class for adding additional sink model rows.
|
||||
*
|
||||
* Extend this class to add additional sink definitions.
|
||||
*/
|
||||
class SinkModelCsv extends Unit {
|
||||
/**
|
||||
* Holds if `row` specifies a sink definition.
|
||||
*
|
||||
* A row of form
|
||||
* ```
|
||||
* package;type;path;kind
|
||||
* ```
|
||||
* indicates that the value at `(package, type, path)` should be seen as a sink
|
||||
* of the given `kind`.
|
||||
*/
|
||||
abstract predicate row(string row);
|
||||
}
|
||||
|
||||
/**
|
||||
* A unit class for adding additional summary model rows.
|
||||
*
|
||||
* Extend this class to add additional flow summary definitions.
|
||||
*/
|
||||
class SummaryModelCsv extends Unit {
|
||||
/**
|
||||
* Holds if `row` specifies a summary definition.
|
||||
*
|
||||
* A row of form
|
||||
* ```
|
||||
* package;type;path;input;output;kind
|
||||
* ```
|
||||
* indicates that for each call to `(package, type, path)`, the value referred to by `input`
|
||||
* can flow to the value referred to by `output`.
|
||||
*
|
||||
* `kind` should be either `value` or `taint`, for value-preserving or taint-preserving steps,
|
||||
* respectively.
|
||||
*/
|
||||
abstract predicate row(string row);
|
||||
}
|
||||
|
||||
/**
|
||||
* A unit class for adding additional type model rows.
|
||||
*
|
||||
* Extend this class to add additional type definitions.
|
||||
*/
|
||||
class TypeModelCsv extends Unit {
|
||||
/**
|
||||
* Holds if `row` specifies a type definition.
|
||||
*
|
||||
* A row of form,
|
||||
* ```
|
||||
* package1;type1;package2;type2;path
|
||||
* ```
|
||||
* indicates that `(package2, type2, path)` should be seen as an instance of `(package1, type1)`.
|
||||
*/
|
||||
abstract predicate row(string row);
|
||||
}
|
||||
}
|
||||
|
||||
private import ModelInput
|
||||
|
||||
/**
|
||||
* An empty class, except in specific tests.
|
||||
*
|
||||
* If this is non-empty, all models are parsed even if the package is not
|
||||
* considered relevant for the current database.
|
||||
*/
|
||||
abstract class TestAllModels extends Unit { }
|
||||
|
||||
/**
|
||||
* Append `;dummy` to the value of `s` to work around the fact that `string.split(delim,n)`
|
||||
* does not preserve empty trailing substrings.
|
||||
*/
|
||||
bindingset[result]
|
||||
private string inversePad(string s) { s = result + ";dummy" }
|
||||
|
||||
private predicate sourceModel(string row) { any(SourceModelCsv s).row(inversePad(row)) }
|
||||
|
||||
private predicate sinkModel(string row) { any(SinkModelCsv s).row(inversePad(row)) }
|
||||
|
||||
private predicate summaryModel(string row) { any(SummaryModelCsv s).row(inversePad(row)) }
|
||||
|
||||
private predicate typeModel(string row) { any(TypeModelCsv s).row(inversePad(row)) }
|
||||
|
||||
/** Holds if a source model exists for the given parameters. */
|
||||
predicate sourceModel(string package, string type, string path, string kind) {
|
||||
exists(string row |
|
||||
sourceModel(row) and
|
||||
row.splitAt(";", 0) = package and
|
||||
row.splitAt(";", 1) = type and
|
||||
row.splitAt(";", 2) = path and
|
||||
row.splitAt(";", 3) = kind
|
||||
)
|
||||
}
|
||||
|
||||
/** Holds if a sink model exists for the given parameters. */
|
||||
private predicate sinkModel(string package, string type, string path, string kind) {
|
||||
exists(string row |
|
||||
sinkModel(row) and
|
||||
row.splitAt(";", 0) = package and
|
||||
row.splitAt(";", 1) = type and
|
||||
row.splitAt(";", 2) = path and
|
||||
row.splitAt(";", 3) = kind
|
||||
)
|
||||
}
|
||||
|
||||
/** Holds if a summary model `row` exists for the given parameters. */
|
||||
private predicate summaryModel(
|
||||
string package, string type, string path, string input, string output, string kind
|
||||
) {
|
||||
exists(string row |
|
||||
summaryModel(row) and
|
||||
row.splitAt(";", 0) = package and
|
||||
row.splitAt(";", 1) = type and
|
||||
row.splitAt(";", 2) = path and
|
||||
row.splitAt(";", 3) = input and
|
||||
row.splitAt(";", 4) = output and
|
||||
row.splitAt(";", 5) = kind
|
||||
)
|
||||
}
|
||||
|
||||
/** Holds if an type model exists for the given parameters. */
|
||||
private predicate typeModel(
|
||||
string package1, string type1, string package2, string type2, string path
|
||||
) {
|
||||
exists(string row |
|
||||
typeModel(row) and
|
||||
row.splitAt(";", 0) = package1 and
|
||||
row.splitAt(";", 1) = type1 and
|
||||
row.splitAt(";", 2) = package2 and
|
||||
row.splitAt(";", 3) = type2 and
|
||||
row.splitAt(";", 4) = path
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a package that should be seen as an alias for the given other `package`,
|
||||
* or the `package` itself.
|
||||
*/
|
||||
bindingset[package]
|
||||
bindingset[result]
|
||||
string getAPackageAlias(string package) {
|
||||
typeModel(package, "", result, "", "")
|
||||
or
|
||||
result = package
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if CSV rows involving `package` might be relevant for the analysis of this database.
|
||||
*/
|
||||
private predicate isRelevantPackage(string package) {
|
||||
(
|
||||
sourceModel(package, _, _, _) or
|
||||
sinkModel(package, _, _, _) or
|
||||
summaryModel(package, _, _, _, _, _) or
|
||||
typeModel(package, _, _, _, _)
|
||||
) and
|
||||
(
|
||||
Specific::isPackageUsed(package)
|
||||
or
|
||||
exists(TestAllModels t)
|
||||
)
|
||||
or
|
||||
exists(string other |
|
||||
isRelevantPackage(other) and
|
||||
typeModel(package, _, other, _, _)
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if `package,type,path` is used in some CSV row.
|
||||
*/
|
||||
pragma[nomagic]
|
||||
predicate isRelevantFullPath(string package, string type, string path) {
|
||||
isRelevantPackage(package) and
|
||||
(
|
||||
sourceModel(package, type, path, _) or
|
||||
sinkModel(package, type, path, _) or
|
||||
summaryModel(package, type, path, _, _, _) or
|
||||
typeModel(_, _, package, type, path)
|
||||
)
|
||||
}
|
||||
|
||||
/** A string from a CSV row that should be parsed as an access path. */
|
||||
private class AccessPathRange extends AccessPath::Range {
|
||||
AccessPathRange() {
|
||||
isRelevantFullPath(_, _, this)
|
||||
or
|
||||
exists(string package | isRelevantPackage(package) |
|
||||
summaryModel(package, _, _, this, _, _) or
|
||||
summaryModel(package, _, _, _, this, _)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a successor of `node` in the API graph.
|
||||
*/
|
||||
bindingset[token]
|
||||
API::Node getSuccessorFromNode(API::Node node, AccessPathToken token) {
|
||||
// API graphs use the same label for arguments and parameters. An edge originating from a
|
||||
// use-node represents an argument, and an edge originating from a def-node represents a parameter.
|
||||
// We just map both to the same thing.
|
||||
token.getName() = ["Argument", "Parameter"] and
|
||||
result = node.getParameter(AccessPath::parseIntUnbounded(token.getAnArgument()))
|
||||
or
|
||||
token.getName() = "ReturnValue" and
|
||||
result = node.getReturn()
|
||||
or
|
||||
// Language-specific tokens
|
||||
result = Specific::getExtraSuccessorFromNode(node, token)
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets an API-graph successor for the given invocation.
|
||||
*/
|
||||
bindingset[token]
|
||||
API::Node getSuccessorFromInvoke(Specific::InvokeNode invoke, AccessPathToken token) {
|
||||
token.getName() = "Argument" and
|
||||
result =
|
||||
invoke
|
||||
.getParameter(AccessPath::parseIntWithArity(token.getAnArgument(), invoke.getNumArgument()))
|
||||
or
|
||||
token.getName() = "ReturnValue" and
|
||||
result = invoke.getReturn()
|
||||
or
|
||||
// Language-specific tokens
|
||||
result = Specific::getExtraSuccessorFromInvoke(invoke, token)
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if `invoke` invokes a call-site filter given by `token`.
|
||||
*/
|
||||
pragma[inline]
|
||||
private predicate invocationMatchesCallSiteFilter(Specific::InvokeNode invoke, AccessPathToken token) {
|
||||
token.getName() = "WithArity" and
|
||||
invoke.getNumArgument() = AccessPath::parseIntUnbounded(token.getAnArgument())
|
||||
or
|
||||
Specific::invocationMatchesExtraCallSiteFilter(invoke, token)
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the API node identified by the first `n` tokens of `path` in the given `(package, type, path)` tuple.
|
||||
*/
|
||||
pragma[nomagic]
|
||||
private API::Node getNodeFromPath(string package, string type, AccessPath path, int n) {
|
||||
isRelevantFullPath(package, type, path) and
|
||||
(
|
||||
n = 0 and
|
||||
exists(string package2, string type2, AccessPath path2 |
|
||||
typeModel(package, type, package2, type2, path2) and
|
||||
result = getNodeFromPath(package2, type2, path2, path2.getNumToken())
|
||||
)
|
||||
or
|
||||
// Language-specific cases, such as handling of global variables
|
||||
result = Specific::getExtraNodeFromPath(package, type, path, n)
|
||||
)
|
||||
or
|
||||
result = getSuccessorFromNode(getNodeFromPath(package, type, path, n - 1), path.getToken(n - 1))
|
||||
or
|
||||
// Similar to the other recursive case, but where the path may have stepped through one or more call-site filters
|
||||
result =
|
||||
getSuccessorFromInvoke(getInvocationFromPath(package, type, path, n - 1), path.getToken(n - 1))
|
||||
}
|
||||
|
||||
/** Gets the node identified by the given `(package, type, path)` tuple. */
|
||||
API::Node getNodeFromPath(string package, string type, AccessPath path) {
|
||||
result = getNodeFromPath(package, type, path, path.getNumToken())
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets an invocation identified by the given `(package, type, path)` tuple.
|
||||
*
|
||||
* Unlike `getNodeFromPath`, the `path` may end with one or more call-site filters.
|
||||
*/
|
||||
Specific::InvokeNode getInvocationFromPath(string package, string type, AccessPath path, int n) {
|
||||
result = Specific::getAnInvocationOf(getNodeFromPath(package, type, path, n))
|
||||
or
|
||||
result = getInvocationFromPath(package, type, path, n - 1) and
|
||||
invocationMatchesCallSiteFilter(result, path.getToken(n - 1))
|
||||
}
|
||||
|
||||
/** Gets an invocation identified by the given `(package, type, path)` tuple. */
|
||||
Specific::InvokeNode getInvocationFromPath(string package, string type, AccessPath path) {
|
||||
result = getInvocationFromPath(package, type, path, path.getNumToken())
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if `name` is a valid name for an access path token in the identifying access path.
|
||||
*/
|
||||
bindingset[name]
|
||||
predicate isValidTokenNameInIdentifyingAccessPath(string name) {
|
||||
name = ["Argument", "Parameter", "ReturnValue", "WithArity"]
|
||||
or
|
||||
Specific::isExtraValidTokenNameInIdentifyingAccessPath(name)
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if `name` is a valid name for an access path token with no arguments, occurring
|
||||
* in an identifying access path.
|
||||
*/
|
||||
bindingset[name]
|
||||
predicate isValidNoArgumentTokenInIdentifyingAccessPath(string name) {
|
||||
name = "ReturnValue"
|
||||
or
|
||||
Specific::isExtraValidNoArgumentTokenInIdentifyingAccessPath(name)
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if `argument` is a valid argument to an access path token with the given `name`, occurring
|
||||
* in an identifying access path.
|
||||
*/
|
||||
bindingset[name, argument]
|
||||
predicate isValidTokenArgumentInIdentifyingAccessPath(string name, string argument) {
|
||||
name = ["Argument", "Parameter"] and
|
||||
argument.regexpMatch("(N-|-)?\\d+(\\.\\.((N-|-)?\\d+)?)?")
|
||||
or
|
||||
name = "WithArity" and
|
||||
argument.regexpMatch("\\d+(\\.\\.(\\d+)?)?")
|
||||
or
|
||||
Specific::isExtraValidTokenArgumentInIdentifyingAccessPath(name, argument)
|
||||
}
|
||||
|
||||
/**
|
||||
* Module providing access to the imported models in terms of API graph nodes.
|
||||
*/
|
||||
module ModelOutput {
|
||||
/**
|
||||
* Holds if a CSV source model contributed `source` with the given `kind`.
|
||||
*/
|
||||
API::Node getASourceNode(string kind) {
|
||||
exists(string package, string type, string path |
|
||||
sourceModel(package, type, path, kind) and
|
||||
result = getNodeFromPath(package, type, path)
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if a CSV sink model contributed `sink` with the given `kind`.
|
||||
*/
|
||||
API::Node getASinkNode(string kind) {
|
||||
exists(string package, string type, string path |
|
||||
sinkModel(package, type, path, kind) and
|
||||
result = getNodeFromPath(package, type, path)
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if a relevant CSV summary exists for these parameters.
|
||||
*/
|
||||
predicate relevantSummaryModel(
|
||||
string package, string type, string path, string input, string output, string kind
|
||||
) {
|
||||
isRelevantPackage(package) and
|
||||
summaryModel(package, type, path, input, output, kind)
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if a `baseNode` is an invocation identified by the `package,type,path` part of a summary row.
|
||||
*/
|
||||
predicate resolvedSummaryBase(
|
||||
string package, string type, string path, Specific::InvokeNode baseNode
|
||||
) {
|
||||
summaryModel(package, type, path, _, _, _) and
|
||||
baseNode = getInvocationFromPath(package, type, path)
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if `node` is seen as an instance of `(package,type)` due to a type definition
|
||||
* contributed by a CSV model.
|
||||
*/
|
||||
API::Node getATypeNode(string package, string type) {
|
||||
exists(string package2, string type2, AccessPath path |
|
||||
typeModel(package, type, package2, type2, path) and
|
||||
result = getNodeFromPath(package2, type2, path)
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets an error message relating to an invalid CSV row in a model.
|
||||
*/
|
||||
string getAWarning() {
|
||||
// Check number of columns
|
||||
exists(string row, string kind, int expectedArity, int actualArity |
|
||||
any(SourceModelCsv csv).row(row) and kind = "source" and expectedArity = 4
|
||||
or
|
||||
any(SinkModelCsv csv).row(row) and kind = "sink" and expectedArity = 4
|
||||
or
|
||||
any(SummaryModelCsv csv).row(row) and kind = "summary" and expectedArity = 6
|
||||
or
|
||||
any(TypeModelCsv csv).row(row) and kind = "type" and expectedArity = 5
|
||||
|
|
||||
actualArity = count(row.indexOf(";")) + 1 and
|
||||
actualArity != expectedArity and
|
||||
result =
|
||||
"CSV " + kind + " row should have " + expectedArity + " columns but has " + actualArity +
|
||||
": " + row
|
||||
)
|
||||
or
|
||||
// Check names and arguments of access path tokens
|
||||
exists(AccessPath path, AccessPathToken token |
|
||||
isRelevantFullPath(_, _, path) and
|
||||
token = path.getToken(_)
|
||||
|
|
||||
not isValidTokenNameInIdentifyingAccessPath(token.getName()) and
|
||||
result = "Invalid token name '" + token.getName() + "' in access path: " + path
|
||||
or
|
||||
isValidTokenNameInIdentifyingAccessPath(token.getName()) and
|
||||
exists(string argument |
|
||||
argument = token.getAnArgument() and
|
||||
not isValidTokenArgumentInIdentifyingAccessPath(token.getName(), argument) and
|
||||
result =
|
||||
"Invalid argument '" + argument + "' in token '" + token + "' in access path: " + path
|
||||
)
|
||||
or
|
||||
isValidTokenNameInIdentifyingAccessPath(token.getName()) and
|
||||
token.getNumArgument() = 0 and
|
||||
not isValidNoArgumentTokenInIdentifyingAccessPath(token.getName()) and
|
||||
result = "Invalid token '" + token + "' is missing its arguments, in access path: " + path
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,202 @@
|
||||
/**
|
||||
* Contains the language-specific part of the models-as-data implementation found in `ApiGraphModels.qll`.
|
||||
*
|
||||
* It must export the following members:
|
||||
* ```ql
|
||||
* class Unit // a unit type
|
||||
* module AccessPathSyntax // a re-export of the AccessPathSyntax module
|
||||
* class InvokeNode // a type representing an invocation connected to the API graph
|
||||
* module API // the API graph module
|
||||
* predicate isPackageUsed(string package)
|
||||
* API::Node getExtraNodeFromPath(string package, string type, string path, int n)
|
||||
* API::Node getExtraSuccessorFromNode(API::Node node, AccessPathToken token)
|
||||
* API::Node getExtraSuccessorFromInvoke(API::InvokeNode node, AccessPathToken token)
|
||||
* predicate invocationMatchesExtraCallSiteFilter(API::InvokeNode invoke, AccessPathToken token)
|
||||
* InvokeNode getAnInvocationOf(API::Node node)
|
||||
* predicate isExtraValidTokenNameInIdentifyingAccessPath(string name)
|
||||
* predicate isExtraValidNoArgumentTokenInIdentifyingAccessPath(string name)
|
||||
* predicate isExtraValidTokenArgumentInIdentifyingAccessPath(string name, string argument)
|
||||
* ```
|
||||
*/
|
||||
|
||||
private import python as PY
|
||||
private import semmle.python.dataflow.new.DataFlow
|
||||
private import ApiGraphModels
|
||||
import semmle.python.ApiGraphs::API as API
|
||||
|
||||
class Unit = PY::Unit;
|
||||
|
||||
// Re-export libraries needed by ApiGraphModels.qll
|
||||
import semmle.python.frameworks.data.internal.AccessPathSyntax as AccessPathSyntax
|
||||
private import AccessPathSyntax
|
||||
|
||||
/**
|
||||
* Holds if models describing `package` may be relevant for the analysis of this database.
|
||||
*/
|
||||
predicate isPackageUsed(string package) { exists(API::moduleImport(package)) }
|
||||
|
||||
/** Gets a Python-specific interpretation of the `(package, type, path)` tuple after resolving the first `n` access path tokens. */
|
||||
bindingset[package, type, path]
|
||||
API::Node getExtraNodeFromPath(string package, string type, AccessPath path, int n) {
|
||||
type = "" and
|
||||
n = 0 and
|
||||
result = API::moduleImport(package) and
|
||||
exists(path)
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a Python-specific API graph successor of `node` reachable by resolving `token`.
|
||||
*/
|
||||
bindingset[token]
|
||||
API::Node getExtraSuccessorFromNode(API::Node node, AccessPathToken token) {
|
||||
token.getName() = "Member" and
|
||||
result = node.getMember(token.getAnArgument())
|
||||
or
|
||||
token.getName() = "Instance" and
|
||||
result = node.getReturn() // In Python `Instance` is just an alias for `ReturnValue`
|
||||
or
|
||||
token.getName() = "Awaited" and
|
||||
result = node.getAwaited()
|
||||
or
|
||||
token.getName() = "Subclass" and
|
||||
result = node.getASubclass*()
|
||||
or
|
||||
token.getName() = "Method" and
|
||||
result = node.getMember(token.getAnArgument()).getReturn()
|
||||
or
|
||||
token.getName() = ["Argument", "Parameter"] and
|
||||
(
|
||||
token.getAnArgument() = "self" and
|
||||
result = node.getSelfParameter()
|
||||
or
|
||||
exists(string name | token.getAnArgument() = name + ":" |
|
||||
result = node.getKeywordParameter(name)
|
||||
)
|
||||
or
|
||||
token.getAnArgument() = "any" and
|
||||
result = [node.getParameter(_), node.getKeywordParameter(_)]
|
||||
or
|
||||
token.getAnArgument() = "any-named" and
|
||||
result = node.getKeywordParameter(_)
|
||||
)
|
||||
// Some features don't have MaD tokens yet, they would need to be added to API-graphs first.
|
||||
// - decorators ("DecoratedClass", "DecoratedMember", "DecoratedParameter")
|
||||
// - Array/Map elements ("ArrayElement", "Element", "MapKey", "MapValue")
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a Python-specific API graph successor of `node` reachable by resolving `token`.
|
||||
*/
|
||||
bindingset[token]
|
||||
API::Node getExtraSuccessorFromInvoke(API::CallNode node, AccessPathToken token) {
|
||||
token.getName() = "Instance" and
|
||||
result = node.getReturn()
|
||||
or
|
||||
token.getName() = ["Argument", "Parameter"] and
|
||||
(
|
||||
token.getAnArgument() = "self" and
|
||||
result = node.getSelfParameter()
|
||||
or
|
||||
token.getAnArgument() = "any" and
|
||||
result = [node.getParameter(_), node.getKeywordParameter(_)]
|
||||
or
|
||||
token.getAnArgument() = "any-named" and
|
||||
result = node.getKeywordParameter(_)
|
||||
or
|
||||
exists(string arg | arg + ":" = token.getAnArgument() | result = node.getKeywordParameter(arg))
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if `invoke` matches the PY-specific call site filter in `token`.
|
||||
*/
|
||||
bindingset[token]
|
||||
predicate invocationMatchesExtraCallSiteFilter(API::CallNode invoke, AccessPathToken token) {
|
||||
token.getName() = "Call" and exists(invoke) // there is only one kind of call in Python.
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if `path` is an input or output spec for a summary with the given `base` node.
|
||||
*/
|
||||
pragma[nomagic]
|
||||
private predicate relevantInputOutputPath(API::CallNode base, AccessPath inputOrOutput) {
|
||||
exists(string package, string type, string input, string output, string path |
|
||||
ModelOutput::relevantSummaryModel(package, type, path, input, output, _) and
|
||||
ModelOutput::resolvedSummaryBase(package, type, path, base) and
|
||||
inputOrOutput = [input, output]
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the API node for the first `n` tokens of the given input/output path, evaluated relative to `baseNode`.
|
||||
*/
|
||||
private API::Node getNodeFromInputOutputPath(API::CallNode baseNode, AccessPath path, int n) {
|
||||
relevantInputOutputPath(baseNode, path) and
|
||||
(
|
||||
n = 1 and
|
||||
result = getSuccessorFromInvoke(baseNode, path.getToken(0))
|
||||
or
|
||||
result =
|
||||
getSuccessorFromNode(getNodeFromInputOutputPath(baseNode, path, n - 1), path.getToken(n - 1))
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the API node for the given input/output path, evaluated relative to `baseNode`.
|
||||
*/
|
||||
private API::Node getNodeFromInputOutputPath(API::CallNode baseNode, AccessPath path) {
|
||||
result = getNodeFromInputOutputPath(baseNode, path, path.getNumToken())
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if a CSV summary contributed the step `pred -> succ` of the given `kind`.
|
||||
*/
|
||||
predicate summaryStep(API::Node pred, API::Node succ, string kind) {
|
||||
exists(
|
||||
string package, string type, string path, API::CallNode base, AccessPath input,
|
||||
AccessPath output
|
||||
|
|
||||
ModelOutput::relevantSummaryModel(package, type, path, input, output, kind) and
|
||||
ModelOutput::resolvedSummaryBase(package, type, path, base) and
|
||||
pred = getNodeFromInputOutputPath(base, input) and
|
||||
succ = getNodeFromInputOutputPath(base, output)
|
||||
)
|
||||
}
|
||||
|
||||
class InvokeNode = API::CallNode;
|
||||
|
||||
/** Gets an `InvokeNode` corresponding to an invocation of `node`. */
|
||||
InvokeNode getAnInvocationOf(API::Node node) { result = node.getACall() }
|
||||
|
||||
/**
|
||||
* Holds if `name` is a valid name for an access path token in the identifying access path.
|
||||
*/
|
||||
bindingset[name]
|
||||
predicate isExtraValidTokenNameInIdentifyingAccessPath(string name) {
|
||||
name = ["Member", "Instance", "Awaited", "Call", "Method", "Subclass"]
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if `name` is a valid name for an access path token with no arguments, occurring
|
||||
* in an identifying access path.
|
||||
*/
|
||||
predicate isExtraValidNoArgumentTokenInIdentifyingAccessPath(string name) {
|
||||
name = ["Instance", "Awaited", "Call", "Subclass"]
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if `argument` is a valid argument to an access path token with the given `name`, occurring
|
||||
* in an identifying access path.
|
||||
*/
|
||||
bindingset[name, argument]
|
||||
predicate isExtraValidTokenArgumentInIdentifyingAccessPath(string name, string argument) {
|
||||
name = ["Member", "Method"] and
|
||||
exists(argument)
|
||||
or
|
||||
name = ["Argument", "Parameter"] and
|
||||
(
|
||||
argument = ["self", "any", "any-named"]
|
||||
or
|
||||
argument.regexpMatch("\\w+:") // keyword argument
|
||||
)
|
||||
}
|
||||
@@ -4,3 +4,4 @@
|
||||
*/
|
||||
|
||||
import semmle.python.dataflow.new.DataFlow
|
||||
import semmle.python.concepts.CryptoAlgorithms as CryptoAlgorithms
|
||||
|
||||
@@ -11,3 +11,79 @@
|
||||
*/
|
||||
|
||||
private import ConceptsImports
|
||||
|
||||
/**
|
||||
* Provides models for cryptographic concepts.
|
||||
*
|
||||
* Note: The `CryptographicAlgorithm` class currently doesn't take weak keys into
|
||||
* consideration for the `isWeak` member predicate. So RSA is always considered
|
||||
* secure, although using a low number of bits will actually make it insecure. We plan
|
||||
* to improve our libraries in the future to more precisely capture this aspect.
|
||||
*/
|
||||
module Cryptography {
|
||||
class CryptographicAlgorithm = CryptoAlgorithms::CryptographicAlgorithm;
|
||||
|
||||
class EncryptionAlgorithm = CryptoAlgorithms::EncryptionAlgorithm;
|
||||
|
||||
class HashingAlgorithm = CryptoAlgorithms::HashingAlgorithm;
|
||||
|
||||
class PasswordHashingAlgorithm = CryptoAlgorithms::PasswordHashingAlgorithm;
|
||||
|
||||
/**
|
||||
* A data-flow node that is an application of a cryptographic algorithm. For example,
|
||||
* encryption, decryption, signature-validation.
|
||||
*
|
||||
* Extend this class to refine existing API models. If you want to model new APIs,
|
||||
* extend `CryptographicOperation::Range` instead.
|
||||
*/
|
||||
class CryptographicOperation extends DataFlow::Node instanceof CryptographicOperation::Range {
|
||||
/** Gets the algorithm used, if it matches a known `CryptographicAlgorithm`. */
|
||||
CryptographicAlgorithm getAlgorithm() { result = super.getAlgorithm() }
|
||||
|
||||
/** Gets an input the algorithm is used on, for example the plain text input to be encrypted. */
|
||||
DataFlow::Node getAnInput() { result = super.getAnInput() }
|
||||
|
||||
/**
|
||||
* Gets the block mode used to perform this cryptographic operation.
|
||||
* This may have no result - for example if the `CryptographicAlgorithm` used
|
||||
* is a stream cipher rather than a block cipher.
|
||||
*/
|
||||
BlockMode getBlockMode() { result = super.getBlockMode() }
|
||||
}
|
||||
|
||||
/** Provides classes for modeling new applications of a cryptographic algorithms. */
|
||||
module CryptographicOperation {
|
||||
/**
|
||||
* A data-flow node that is an application of a cryptographic algorithm. For example,
|
||||
* encryption, decryption, signature-validation.
|
||||
*
|
||||
* Extend this class to model new APIs. If you want to refine existing API models,
|
||||
* extend `CryptographicOperation` instead.
|
||||
*/
|
||||
abstract class Range extends DataFlow::Node {
|
||||
/** Gets the algorithm used, if it matches a known `CryptographicAlgorithm`. */
|
||||
abstract CryptographicAlgorithm getAlgorithm();
|
||||
|
||||
/** Gets an input the algorithm is used on, for example the plain text input to be encrypted. */
|
||||
abstract DataFlow::Node getAnInput();
|
||||
|
||||
/**
|
||||
* Gets the block mode used to perform this cryptographic operation.
|
||||
* This may have no result - for example if the `CryptographicAlgorithm` used
|
||||
* is a stream cipher rather than a block cipher.
|
||||
*/
|
||||
abstract BlockMode getBlockMode();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A cryptographic block cipher mode of operation. This can be used to encrypt
|
||||
* data of arbitrary length using a block encryption algorithm.
|
||||
*/
|
||||
class BlockMode extends string {
|
||||
BlockMode() { this = ["ECB", "CBC", "GCM", "CCM", "CFB", "OFB", "CTR", "OPENPGP"] }
|
||||
|
||||
/** Holds if this block mode is considered to be insecure. */
|
||||
predicate isWeak() { this = "ECB" }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -32,9 +32,11 @@ module CodeInjection {
|
||||
abstract class Sanitizer extends DataFlow::Node { }
|
||||
|
||||
/**
|
||||
* DEPRECATED: Use `Sanitizer` instead.
|
||||
*
|
||||
* A sanitizer guard for "code injection" vulnerabilities.
|
||||
*/
|
||||
abstract class SanitizerGuard extends DataFlow::BarrierGuard { }
|
||||
abstract deprecated class SanitizerGuard extends DataFlow::BarrierGuard { }
|
||||
|
||||
/**
|
||||
* A source of remote user input, considered as a flow source.
|
||||
@@ -51,5 +53,5 @@ module CodeInjection {
|
||||
/**
|
||||
* A comparison with a constant string, considered as a sanitizer-guard.
|
||||
*/
|
||||
class StringConstCompareAsSanitizerGuard extends SanitizerGuard, StringConstCompare { }
|
||||
class StringConstCompareAsSanitizer extends Sanitizer, StringConstCompareBarrier { }
|
||||
}
|
||||
|
||||
@@ -23,7 +23,7 @@ class Configuration extends TaintTracking::Configuration {
|
||||
|
||||
override predicate isSanitizer(DataFlow::Node node) { node instanceof Sanitizer }
|
||||
|
||||
override predicate isSanitizerGuard(DataFlow::BarrierGuard guard) {
|
||||
deprecated override predicate isSanitizerGuard(DataFlow::BarrierGuard guard) {
|
||||
guard instanceof SanitizerGuard
|
||||
}
|
||||
}
|
||||
|
||||
@@ -32,9 +32,11 @@ module CommandInjection {
|
||||
abstract class Sanitizer extends DataFlow::Node { }
|
||||
|
||||
/**
|
||||
* DEPRECATED: Use `Sanitizer` instead.
|
||||
*
|
||||
* A sanitizer guard for "command injection" vulnerabilities.
|
||||
*/
|
||||
abstract class SanitizerGuard extends DataFlow::BarrierGuard { }
|
||||
abstract deprecated class SanitizerGuard extends DataFlow::BarrierGuard { }
|
||||
|
||||
/**
|
||||
* A source of remote user input, considered as a flow source.
|
||||
@@ -83,5 +85,5 @@ module CommandInjection {
|
||||
/**
|
||||
* A comparison with a constant string, considered as a sanitizer-guard.
|
||||
*/
|
||||
class StringConstCompareAsSanitizerGuard extends SanitizerGuard, StringConstCompare { }
|
||||
class StringConstCompareAsSanitizerGuard extends Sanitizer, StringConstCompareBarrier { }
|
||||
}
|
||||
|
||||
@@ -23,7 +23,7 @@ class Configuration extends TaintTracking::Configuration {
|
||||
|
||||
override predicate isSanitizer(DataFlow::Node node) { node instanceof Sanitizer }
|
||||
|
||||
override predicate isSanitizerGuard(DataFlow::BarrierGuard guard) {
|
||||
deprecated override predicate isSanitizerGuard(DataFlow::BarrierGuard guard) {
|
||||
guard instanceof SanitizerGuard
|
||||
}
|
||||
}
|
||||
|
||||
@@ -42,14 +42,18 @@ module LdapInjection {
|
||||
abstract class FilterSanitizer extends DataFlow::Node { }
|
||||
|
||||
/**
|
||||
* DEPRECATED: Use `DnSanitizer` instead.
|
||||
*
|
||||
* A sanitizer guard for "ldap injection" vulnerabilities.
|
||||
*/
|
||||
abstract class DnSanitizerGuard extends DataFlow::BarrierGuard { }
|
||||
abstract deprecated class DnSanitizerGuard extends DataFlow::BarrierGuard { }
|
||||
|
||||
/**
|
||||
* DEPRECATED: Use `FilterSanitizer` instead.
|
||||
*
|
||||
* A sanitizer guard for "ldap injection" vulnerabilities.
|
||||
*/
|
||||
abstract class FilterSanitizerGuard extends DataFlow::BarrierGuard { }
|
||||
abstract deprecated class FilterSanitizerGuard extends DataFlow::BarrierGuard { }
|
||||
|
||||
/**
|
||||
* A source of remote user input, considered as a flow source.
|
||||
@@ -73,12 +77,12 @@ module LdapInjection {
|
||||
/**
|
||||
* A comparison with a constant string, considered as a sanitizer-guard.
|
||||
*/
|
||||
class StringConstCompareAsDnSanitizerGuard extends DnSanitizerGuard, StringConstCompare { }
|
||||
class StringConstCompareAsDnSanitizerGuard extends DnSanitizer, StringConstCompareBarrier { }
|
||||
|
||||
/**
|
||||
* A comparison with a constant string, considered as a sanitizer-guard.
|
||||
*/
|
||||
class StringConstCompareAsFilterSanitizerGuard extends FilterSanitizerGuard, StringConstCompare {
|
||||
class StringConstCompareAsFilterSanitizerGuard extends FilterSanitizer, StringConstCompareBarrier {
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -26,7 +26,7 @@ class DnConfiguration extends TaintTracking::Configuration {
|
||||
|
||||
override predicate isSanitizer(DataFlow::Node node) { node instanceof DnSanitizer }
|
||||
|
||||
override predicate isSanitizerGuard(DataFlow::BarrierGuard guard) {
|
||||
deprecated override predicate isSanitizerGuard(DataFlow::BarrierGuard guard) {
|
||||
guard instanceof DnSanitizerGuard
|
||||
}
|
||||
}
|
||||
@@ -44,7 +44,7 @@ class FilterConfiguration extends TaintTracking::Configuration {
|
||||
|
||||
override predicate isSanitizer(DataFlow::Node node) { node instanceof FilterSanitizer }
|
||||
|
||||
override predicate isSanitizerGuard(DataFlow::BarrierGuard guard) {
|
||||
deprecated override predicate isSanitizerGuard(DataFlow::BarrierGuard guard) {
|
||||
guard instanceof FilterSanitizerGuard
|
||||
}
|
||||
}
|
||||
|
||||
@@ -32,9 +32,11 @@ module LogInjection {
|
||||
abstract class Sanitizer extends DataFlow::Node { }
|
||||
|
||||
/**
|
||||
* DEPRECATED: Use `Sanitizer` instead.
|
||||
*
|
||||
* A sanitizer guard for "log injection" vulnerabilities.
|
||||
*/
|
||||
abstract class SanitizerGuard extends DataFlow::BarrierGuard { }
|
||||
abstract deprecated class SanitizerGuard extends DataFlow::BarrierGuard { }
|
||||
|
||||
/**
|
||||
* A source of remote user input, considered as a flow source.
|
||||
@@ -51,7 +53,7 @@ module LogInjection {
|
||||
/**
|
||||
* A comparison with a constant string, considered as a sanitizer-guard.
|
||||
*/
|
||||
class StringConstCompareAsSanitizerGuard extends SanitizerGuard, StringConstCompare { }
|
||||
class StringConstCompareAsSanitizerGuard extends Sanitizer, StringConstCompareBarrier { }
|
||||
|
||||
/**
|
||||
* A call to replace line breaks, considered as a sanitizer.
|
||||
|
||||
@@ -23,7 +23,7 @@ class Configuration extends TaintTracking::Configuration {
|
||||
|
||||
override predicate isSanitizer(DataFlow::Node node) { node instanceof Sanitizer }
|
||||
|
||||
override predicate isSanitizerGuard(DataFlow::BarrierGuard guard) {
|
||||
deprecated override predicate isSanitizerGuard(DataFlow::BarrierGuard guard) {
|
||||
guard instanceof SanitizerGuard
|
||||
}
|
||||
}
|
||||
|
||||
@@ -90,11 +90,13 @@ deprecated class NormalizedPathNotCheckedConfiguration extends TaintTracking2::C
|
||||
|
||||
override predicate isSink(DataFlow::Node sink) { sink instanceof Sink }
|
||||
|
||||
override predicate isSanitizer(DataFlow::Node node) { node instanceof Sanitizer }
|
||||
override predicate isSanitizer(DataFlow::Node node) {
|
||||
node instanceof Path::SafeAccessCheck
|
||||
or
|
||||
node instanceof Sanitizer
|
||||
}
|
||||
|
||||
override predicate isSanitizerGuard(DataFlow::BarrierGuard guard) {
|
||||
guard instanceof Path::SafeAccessCheck
|
||||
or
|
||||
guard instanceof SanitizerGuard
|
||||
}
|
||||
}
|
||||
|
||||
@@ -43,9 +43,11 @@ module PathInjection {
|
||||
abstract class Sanitizer extends DataFlow::Node { }
|
||||
|
||||
/**
|
||||
* DEPRECATED: Use `Sanitizer` instead.
|
||||
*
|
||||
* A sanitizer guard for "path injection" vulnerabilities.
|
||||
*/
|
||||
abstract class SanitizerGuard extends DataFlow::BarrierGuard { }
|
||||
abstract deprecated class SanitizerGuard extends DataFlow::BarrierGuard { }
|
||||
|
||||
/**
|
||||
* A source of remote user input, considered as a flow source.
|
||||
@@ -59,8 +61,14 @@ module PathInjection {
|
||||
FileSystemAccessAsSink() { this = any(FileSystemAccess e).getAPathArgument() }
|
||||
}
|
||||
|
||||
private import semmle.python.frameworks.data.ModelsAsData
|
||||
|
||||
private class DataAsFileSink extends Sink {
|
||||
DataAsFileSink() { this = ModelOutput::getASinkNode("path-injection").getARhs() }
|
||||
}
|
||||
|
||||
/**
|
||||
* A comparison with a constant string, considered as a sanitizer-guard.
|
||||
*/
|
||||
class StringConstCompareAsSanitizerGuard extends SanitizerGuard, StringConstCompare { }
|
||||
class StringConstCompareAsSanitizerGuard extends Sanitizer, StringConstCompareBarrier { }
|
||||
}
|
||||
|
||||
@@ -47,11 +47,11 @@ class Configuration extends TaintTracking::Configuration {
|
||||
node instanceof Path::PathNormalization and
|
||||
state instanceof NotNormalized
|
||||
or
|
||||
node = any(Path::SafeAccessCheck c).getAGuardedNode() and
|
||||
node instanceof Path::SafeAccessCheck and
|
||||
state instanceof NormalizedUnchecked
|
||||
}
|
||||
|
||||
override predicate isSanitizerGuard(DataFlow::BarrierGuard guard) {
|
||||
deprecated override predicate isSanitizerGuard(DataFlow::BarrierGuard guard) {
|
||||
guard instanceof SanitizerGuard
|
||||
}
|
||||
|
||||
|
||||
@@ -44,9 +44,11 @@ module PolynomialReDoS {
|
||||
abstract class Sanitizer extends DataFlow::Node { }
|
||||
|
||||
/**
|
||||
* DEPRECATED: Use `Sanitizer` instead.
|
||||
*
|
||||
* A sanitizer guard for "polynomial regular expression denial of service (ReDoS)" vulnerabilities.
|
||||
*/
|
||||
abstract class SanitizerGuard extends DataFlow::BarrierGuard { }
|
||||
abstract deprecated class SanitizerGuard extends DataFlow::BarrierGuard { }
|
||||
|
||||
/**
|
||||
* A source of remote user input, considered as a flow source.
|
||||
@@ -74,5 +76,5 @@ module PolynomialReDoS {
|
||||
/**
|
||||
* A comparison with a constant string, considered as a sanitizer-guard.
|
||||
*/
|
||||
class StringConstCompareAsSanitizerGuard extends SanitizerGuard, StringConstCompare { }
|
||||
class StringConstCompareAsSanitizerGuard extends Sanitizer, StringConstCompareBarrier { }
|
||||
}
|
||||
|
||||
@@ -23,7 +23,7 @@ class Configuration extends TaintTracking::Configuration {
|
||||
|
||||
override predicate isSanitizer(DataFlow::Node node) { node instanceof Sanitizer }
|
||||
|
||||
override predicate isSanitizerGuard(DataFlow::BarrierGuard guard) {
|
||||
deprecated override predicate isSanitizerGuard(DataFlow::BarrierGuard guard) {
|
||||
guard instanceof SanitizerGuard
|
||||
}
|
||||
}
|
||||
|
||||
@@ -32,9 +32,11 @@ module ReflectedXss {
|
||||
abstract class Sanitizer extends DataFlow::Node { }
|
||||
|
||||
/**
|
||||
* DEPRECATED: Use `Sanitizer` instead.
|
||||
*
|
||||
* A sanitizer guard for "reflected server-side cross-site scripting" vulnerabilities.
|
||||
*/
|
||||
abstract class SanitizerGuard extends DataFlow::BarrierGuard { }
|
||||
abstract deprecated class SanitizerGuard extends DataFlow::BarrierGuard { }
|
||||
|
||||
/**
|
||||
* A source of remote user input, considered as a flow source.
|
||||
@@ -72,7 +74,7 @@ module ReflectedXss {
|
||||
/**
|
||||
* A comparison with a constant string, considered as a sanitizer-guard.
|
||||
*/
|
||||
class StringConstCompareAsSanitizerGuard extends SanitizerGuard, StringConstCompare { }
|
||||
class StringConstCompareAsSanitizerGuard extends Sanitizer, StringConstCompareBarrier { }
|
||||
}
|
||||
|
||||
/** DEPRECATED: Alias for ReflectedXss */
|
||||
|
||||
@@ -23,7 +23,7 @@ class Configuration extends TaintTracking::Configuration {
|
||||
|
||||
override predicate isSanitizer(DataFlow::Node node) { node instanceof Sanitizer }
|
||||
|
||||
override predicate isSanitizerGuard(DataFlow::BarrierGuard guard) {
|
||||
deprecated override predicate isSanitizerGuard(DataFlow::BarrierGuard guard) {
|
||||
guard instanceof SanitizerGuard
|
||||
}
|
||||
}
|
||||
|
||||
@@ -40,9 +40,11 @@ module RegexInjection {
|
||||
abstract class Sanitizer extends DataFlow::Node { }
|
||||
|
||||
/**
|
||||
* DEPRECATED: Use `Sanitizer` instead.
|
||||
*
|
||||
* A sanitizer guard for "regular expression injection" vulnerabilities.
|
||||
*/
|
||||
abstract class SanitizerGuard extends DataFlow::BarrierGuard { }
|
||||
abstract deprecated class SanitizerGuard extends DataFlow::BarrierGuard { }
|
||||
|
||||
/**
|
||||
* A source of remote user input, considered as a flow source.
|
||||
|
||||
@@ -24,7 +24,7 @@ class Configuration extends TaintTracking::Configuration {
|
||||
|
||||
override predicate isSanitizer(DataFlow::Node node) { node instanceof Sanitizer }
|
||||
|
||||
override predicate isSanitizerGuard(DataFlow::BarrierGuard guard) {
|
||||
deprecated override predicate isSanitizerGuard(DataFlow::BarrierGuard guard) {
|
||||
guard instanceof SanitizerGuard
|
||||
}
|
||||
}
|
||||
|
||||
@@ -44,9 +44,11 @@ module ServerSideRequestForgery {
|
||||
abstract class FullUrlControlSanitizer extends DataFlow::Node { }
|
||||
|
||||
/**
|
||||
* DEPRECATED: Use `Sanitizer` instead.
|
||||
*
|
||||
* A sanitizer guard for "Server-side request forgery" vulnerabilities.
|
||||
*/
|
||||
abstract class SanitizerGuard extends DataFlow::BarrierGuard { }
|
||||
abstract deprecated class SanitizerGuard extends DataFlow::BarrierGuard { }
|
||||
|
||||
/**
|
||||
* A source of remote user input, considered as a flow source.
|
||||
@@ -78,7 +80,7 @@ module ServerSideRequestForgery {
|
||||
/**
|
||||
* A comparison with a constant string, considered as a sanitizer-guard.
|
||||
*/
|
||||
class StringConstCompareAsSanitizerGuard extends SanitizerGuard, StringConstCompare { }
|
||||
class StringConstCompareAsSanitizerGuard extends Sanitizer, StringConstCompareBarrier { }
|
||||
|
||||
/**
|
||||
* A string construction (concat, format, f-string) where the left side is not
|
||||
|
||||
@@ -34,7 +34,7 @@ class FullServerSideRequestForgeryConfiguration extends TaintTracking::Configura
|
||||
node instanceof FullUrlControlSanitizer
|
||||
}
|
||||
|
||||
override predicate isSanitizerGuard(DataFlow::BarrierGuard guard) {
|
||||
deprecated override predicate isSanitizerGuard(DataFlow::BarrierGuard guard) {
|
||||
guard instanceof SanitizerGuard
|
||||
}
|
||||
}
|
||||
@@ -65,7 +65,7 @@ class PartialServerSideRequestForgeryConfiguration extends TaintTracking::Config
|
||||
|
||||
override predicate isSanitizer(DataFlow::Node node) { node instanceof Sanitizer }
|
||||
|
||||
override predicate isSanitizerGuard(DataFlow::BarrierGuard guard) {
|
||||
deprecated override predicate isSanitizerGuard(DataFlow::BarrierGuard guard) {
|
||||
guard instanceof SanitizerGuard
|
||||
}
|
||||
}
|
||||
|
||||
@@ -33,9 +33,11 @@ module SqlInjection {
|
||||
abstract class Sanitizer extends DataFlow::Node { }
|
||||
|
||||
/**
|
||||
* DEPRECATED: Use `Sanitizer` instead.
|
||||
*
|
||||
* A sanitizer guard for "SQL injection" vulnerabilities.
|
||||
*/
|
||||
abstract class SanitizerGuard extends DataFlow::BarrierGuard { }
|
||||
abstract deprecated class SanitizerGuard extends DataFlow::BarrierGuard { }
|
||||
|
||||
/**
|
||||
* A source of remote user input, considered as a flow source.
|
||||
@@ -59,5 +61,12 @@ module SqlInjection {
|
||||
/**
|
||||
* A comparison with a constant string, considered as a sanitizer-guard.
|
||||
*/
|
||||
class StringConstCompareAsSanitizerGuard extends SanitizerGuard, StringConstCompare { }
|
||||
class StringConstCompareAsSanitizerGuard extends Sanitizer, StringConstCompareBarrier { }
|
||||
|
||||
private import semmle.python.frameworks.data.ModelsAsData
|
||||
|
||||
/** A sink for sql-injection from model data. */
|
||||
private class DataAsSqlSink extends Sink {
|
||||
DataAsSqlSink() { this = ModelOutput::getASinkNode("sql-injection").getARhs() }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -23,7 +23,7 @@ class Configuration extends TaintTracking::Configuration {
|
||||
|
||||
override predicate isSanitizer(DataFlow::Node node) { node instanceof Sanitizer }
|
||||
|
||||
override predicate isSanitizerGuard(DataFlow::BarrierGuard guard) {
|
||||
deprecated override predicate isSanitizerGuard(DataFlow::BarrierGuard guard) {
|
||||
guard instanceof SanitizerGuard
|
||||
}
|
||||
}
|
||||
|
||||
@@ -32,9 +32,11 @@ module StackTraceExposure {
|
||||
abstract class Sanitizer extends DataFlow::Node { }
|
||||
|
||||
/**
|
||||
* DEPRECATED: Use `Sanitizer` instead.
|
||||
*
|
||||
* A sanitizer guard for "stack trace exposure" vulnerabilities.
|
||||
*/
|
||||
abstract class SanitizerGuard extends DataFlow::BarrierGuard { }
|
||||
abstract deprecated class SanitizerGuard extends DataFlow::BarrierGuard { }
|
||||
|
||||
/**
|
||||
* A source of exception info, considered as a flow source.
|
||||
|
||||
@@ -23,7 +23,7 @@ class Configuration extends TaintTracking::Configuration {
|
||||
|
||||
override predicate isSanitizer(DataFlow::Node node) { node instanceof Sanitizer }
|
||||
|
||||
override predicate isSanitizerGuard(DataFlow::BarrierGuard guard) {
|
||||
deprecated override predicate isSanitizerGuard(DataFlow::BarrierGuard guard) {
|
||||
guard instanceof SanitizerGuard
|
||||
}
|
||||
|
||||
|
||||
@@ -32,9 +32,11 @@ module UnsafeDeserialization {
|
||||
abstract class Sanitizer extends DataFlow::Node { }
|
||||
|
||||
/**
|
||||
* DEPRECATED: Use `Sanitizer` instead.
|
||||
*
|
||||
* A sanitizer guard for "code execution from deserialization" vulnerabilities.
|
||||
*/
|
||||
abstract class SanitizerGuard extends DataFlow::BarrierGuard { }
|
||||
abstract deprecated class SanitizerGuard extends DataFlow::BarrierGuard { }
|
||||
|
||||
/**
|
||||
* A source of remote user input, considered as a flow source.
|
||||
@@ -56,5 +58,5 @@ module UnsafeDeserialization {
|
||||
/**
|
||||
* A comparison with a constant string, considered as a sanitizer-guard.
|
||||
*/
|
||||
class StringConstCompareAsSanitizerGuard extends SanitizerGuard, StringConstCompare { }
|
||||
class StringConstCompareAsSanitizerGuard extends Sanitizer, StringConstCompareBarrier { }
|
||||
}
|
||||
|
||||
@@ -23,7 +23,7 @@ class Configuration extends TaintTracking::Configuration {
|
||||
|
||||
override predicate isSanitizer(DataFlow::Node node) { node instanceof Sanitizer }
|
||||
|
||||
override predicate isSanitizerGuard(DataFlow::BarrierGuard guard) {
|
||||
deprecated override predicate isSanitizerGuard(DataFlow::BarrierGuard guard) {
|
||||
guard instanceof SanitizerGuard
|
||||
}
|
||||
}
|
||||
|
||||
@@ -32,9 +32,11 @@ module UrlRedirect {
|
||||
abstract class Sanitizer extends DataFlow::Node { }
|
||||
|
||||
/**
|
||||
* DEPRECATED: Use `Sanitizer` instead.
|
||||
*
|
||||
* A sanitizer guard for "URL redirection" vulnerabilities.
|
||||
*/
|
||||
abstract class SanitizerGuard extends DataFlow::BarrierGuard { }
|
||||
abstract deprecated class SanitizerGuard extends DataFlow::BarrierGuard { }
|
||||
|
||||
/**
|
||||
* A source of remote user input, considered as a flow source.
|
||||
@@ -67,5 +69,5 @@ module UrlRedirect {
|
||||
/**
|
||||
* A comparison with a constant string, considered as a sanitizer-guard.
|
||||
*/
|
||||
class StringConstCompareAsSanitizerGuard extends SanitizerGuard, StringConstCompare { }
|
||||
class StringConstCompareAsSanitizerGuard extends Sanitizer, StringConstCompareBarrier { }
|
||||
}
|
||||
|
||||
@@ -23,7 +23,7 @@ class Configuration extends TaintTracking::Configuration {
|
||||
|
||||
override predicate isSanitizer(DataFlow::Node node) { node instanceof Sanitizer }
|
||||
|
||||
override predicate isSanitizerGuard(DataFlow::BarrierGuard guard) {
|
||||
deprecated override predicate isSanitizerGuard(DataFlow::BarrierGuard guard) {
|
||||
guard instanceof SanitizerGuard
|
||||
}
|
||||
}
|
||||
|
||||
@@ -30,9 +30,11 @@ module XpathInjection {
|
||||
abstract class Sanitizer extends DataFlow::Node { }
|
||||
|
||||
/**
|
||||
* DEPRECATED: Use `Sanitizer` instead.
|
||||
*
|
||||
* A sanitizer guard for "XPath injection" vulnerabilities.
|
||||
*/
|
||||
abstract class SanitizerGuard extends DataFlow::BarrierGuard { }
|
||||
abstract deprecated class SanitizerGuard extends DataFlow::BarrierGuard { }
|
||||
|
||||
/**
|
||||
* A source of remote user input, considered as a flow source.
|
||||
|
||||
@@ -23,7 +23,7 @@ class Configuration extends TaintTracking::Configuration {
|
||||
|
||||
override predicate isSanitizer(DataFlow::Node node) { node instanceof Sanitizer }
|
||||
|
||||
override predicate isSanitizerGuard(DataFlow::BarrierGuard guard) {
|
||||
deprecated override predicate isSanitizerGuard(DataFlow::BarrierGuard guard) {
|
||||
guard instanceof SanitizerGuard
|
||||
}
|
||||
}
|
||||
|
||||
@@ -50,7 +50,7 @@ module HeuristicNames {
|
||||
* Gets a regular expression that identifies strings that may indicate the presence of secret
|
||||
* or trusted data.
|
||||
*/
|
||||
string maybeSecret() { result = "(?is).*((?<!is)secret|(?<!un|is)trusted).*" }
|
||||
string maybeSecret() { result = "(?is).*((?<!is|is_)secret|(?<!un|un_|is|is_)trusted).*" }
|
||||
|
||||
/**
|
||||
* Gets a regular expression that identifies strings that may indicate the presence of
|
||||
@@ -96,10 +96,14 @@ module HeuristicNames {
|
||||
* Gets a regular expression that identifies strings that may indicate the presence of data
|
||||
* that is hashed or encrypted, and hence rendered non-sensitive, or contains special characters
|
||||
* suggesting nouns within the string do not represent the meaning of the whole string (e.g. a URL or a SQL query).
|
||||
*
|
||||
* We also filter out common words like `certain` and `concert`, since otherwise these could
|
||||
* be matched by the certificate regular expressions. Same for `accountable` (account), or
|
||||
* `secretarial` (secret).
|
||||
*/
|
||||
string notSensitiveRegexp() {
|
||||
result =
|
||||
"(?is).*([^\\w$.-]|redact|censor|obfuscate|hash|md5|sha|random|((?<!un)(en))?(crypt|code)).*"
|
||||
"(?is).*([^\\w$.-]|redact|censor|obfuscate|hash|md5|sha|random|((?<!un)(en))?(crypt|code)|certain|concert|secretar|accountant|accountab).*"
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -141,26 +141,28 @@ private class StatePair extends TStatePair {
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds for all constructed state pairs.
|
||||
* Holds for `(fork, fork)` state pairs when `isFork(fork, _, _, _, _)` holds.
|
||||
*
|
||||
* Used in `statePairDist`
|
||||
* Used in `statePairDistToFork`
|
||||
*/
|
||||
private predicate isStatePair(StatePair p) { any() }
|
||||
private predicate isStatePairFork(StatePair p) {
|
||||
exists(State fork | p = MkStatePair(fork, fork) and isFork(fork, _, _, _, _))
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if there are transitions from the components of `q` to the corresponding
|
||||
* components of `r`.
|
||||
*
|
||||
* Used in `statePairDist`
|
||||
* Used in `statePairDistToFork`
|
||||
*/
|
||||
private predicate delta2(StatePair q, StatePair r) { step(q, _, _, r) }
|
||||
private predicate reverseStep(StatePair r, StatePair q) { step(q, _, _, r) }
|
||||
|
||||
/**
|
||||
* Gets the minimum length of a path from `q` to `r` in the
|
||||
* product automaton.
|
||||
*/
|
||||
private int statePairDist(StatePair q, StatePair r) =
|
||||
shortestDistances(isStatePair/1, delta2/2)(q, r, result)
|
||||
private int statePairDistToFork(StatePair q, StatePair r) =
|
||||
shortestDistances(isStatePairFork/1, reverseStep/2)(r, q, result)
|
||||
|
||||
/**
|
||||
* Holds if there are transitions from `q` to `r1` and from `q` to `r2`
|
||||
@@ -255,14 +257,7 @@ private predicate step(StatePair q, InputSymbol s1, InputSymbol s2, State r1, St
|
||||
|
||||
private newtype TTrace =
|
||||
Nil() or
|
||||
Step(InputSymbol s1, InputSymbol s2, TTrace t) {
|
||||
exists(StatePair p |
|
||||
isReachableFromFork(_, p, t, _) and
|
||||
step(p, s1, s2, _)
|
||||
)
|
||||
or
|
||||
t = Nil() and isFork(_, s1, s2, _, _)
|
||||
}
|
||||
Step(InputSymbol s1, InputSymbol s2, TTrace t) { isReachableFromFork(_, _, s1, s2, t, _) }
|
||||
|
||||
/**
|
||||
* A list of pairs of input symbols that describe a path in the product automaton
|
||||
@@ -284,20 +279,28 @@ private class Trace extends TTrace {
|
||||
* a path from `r` back to `(fork, fork)` with `rem` steps.
|
||||
*/
|
||||
private predicate isReachableFromFork(State fork, StatePair r, Trace w, int rem) {
|
||||
exists(InputSymbol s1, InputSymbol s2, Trace v |
|
||||
isReachableFromFork(fork, r, s1, s2, v, rem) and
|
||||
w = Step(s1, s2, v)
|
||||
)
|
||||
}
|
||||
|
||||
private predicate isReachableFromFork(
|
||||
State fork, StatePair r, InputSymbol s1, InputSymbol s2, Trace v, int rem
|
||||
) {
|
||||
// base case
|
||||
exists(InputSymbol s1, InputSymbol s2, State q1, State q2 |
|
||||
exists(State q1, State q2 |
|
||||
isFork(fork, s1, s2, q1, q2) and
|
||||
r = MkStatePair(q1, q2) and
|
||||
w = Step(s1, s2, Nil()) and
|
||||
rem = statePairDist(r, MkStatePair(fork, fork))
|
||||
v = Nil() and
|
||||
rem = statePairDistToFork(r, MkStatePair(fork, fork))
|
||||
)
|
||||
or
|
||||
// recursive case
|
||||
exists(StatePair p, Trace v, InputSymbol s1, InputSymbol s2 |
|
||||
exists(StatePair p |
|
||||
isReachableFromFork(fork, p, v, rem + 1) and
|
||||
step(p, s1, s2, r) and
|
||||
w = Step(s1, s2, v) and
|
||||
rem >= statePairDist(r, MkStatePair(fork, fork))
|
||||
rem = statePairDistToFork(r, MkStatePair(fork, fork))
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -1,3 +1,11 @@
|
||||
## 0.1.4
|
||||
|
||||
## 0.1.3
|
||||
|
||||
### New Queries
|
||||
|
||||
* The query "PAM authorization bypass due to incorrect usage" (`py/pam-auth-bypass`) has been promoted from experimental to the main query pack. Its results will now appear by default. This query was originally [submitted as an experimental query by @porcupineyhairs](https://github.com/github/codeql/pull/8595).
|
||||
|
||||
## 0.1.2
|
||||
|
||||
### New Queries
|
||||
|
||||
@@ -12,6 +12,8 @@
|
||||
*/
|
||||
|
||||
import python
|
||||
import semmle.python.dataflow.new.DataFlow
|
||||
import semmle.python.ApiGraphs
|
||||
|
||||
/*
|
||||
* Jinja 2 Docs:
|
||||
@@ -25,25 +27,24 @@ import python
|
||||
* safe1_tmpl = Template('Hello {{ name }}!', autoescape=True)
|
||||
*/
|
||||
|
||||
ClassValue jinja2EnvironmentOrTemplate() {
|
||||
result = Value::named("jinja2.Environment")
|
||||
private API::Node jinja2EnvironmentOrTemplate() {
|
||||
result = API::moduleImport("jinja2").getMember("Environment")
|
||||
or
|
||||
result = Value::named("jinja2.Template")
|
||||
result = API::moduleImport("jinja2").getMember("Template")
|
||||
}
|
||||
|
||||
ControlFlowNode getAutoEscapeParameter(CallNode call) { result = call.getArgByName("autoescape") }
|
||||
|
||||
from CallNode call
|
||||
from API::CallNode call
|
||||
where
|
||||
call.getFunction().pointsTo(jinja2EnvironmentOrTemplate()) and
|
||||
not exists(call.getNode().getStarargs()) and
|
||||
not exists(call.getNode().getKwargs()) and
|
||||
call = jinja2EnvironmentOrTemplate().getACall() and
|
||||
not exists(call.asCfgNode().(CallNode).getNode().getStarargs()) and
|
||||
not exists(call.asCfgNode().(CallNode).getNode().getKwargs()) and
|
||||
(
|
||||
not exists(getAutoEscapeParameter(call))
|
||||
not exists(call.getArgByName("autoescape"))
|
||||
or
|
||||
exists(Value isFalse |
|
||||
getAutoEscapeParameter(call).pointsTo(isFalse) and
|
||||
isFalse.getDefiniteBooleanValue() = false
|
||||
)
|
||||
call.getKeywordParameter("autoescape")
|
||||
.getAValueReachingRhs()
|
||||
.asExpr()
|
||||
.(ImmutableLiteral)
|
||||
.booleanValue() = false
|
||||
)
|
||||
select call, "Using jinja2 templates with autoescape=False can potentially allow XSS attacks."
|
||||
|
||||
@@ -9,7 +9,9 @@
|
||||
<p>
|
||||
A
|
||||
<code>pam_authenticate</code>
|
||||
only verifies the credentials of a user. It does not check if a user has an appropriate authorization to actually login. This means a user with a expired login or a password can still access the system.
|
||||
only verifies the credentials of a user. It does not check if a user has an
|
||||
appropriate authorization to actually login. This means a user with an expired
|
||||
login or a password can still access the system.
|
||||
</p>
|
||||
|
||||
</overview>
|
||||
@@ -26,7 +28,9 @@
|
||||
|
||||
<example>
|
||||
<p>
|
||||
In the following example, the code only checks the credentials of a user. Hence, in this case, a user expired with expired creds can still login. This can be verified by creating a new user account, expiring it with
|
||||
In the following example, the code only checks the credentials of a user. Hence,
|
||||
in this case, a user with expired credentials can still login. This can be
|
||||
verified by creating a new user account, expiring it with
|
||||
<code>chage -E0 `username` </code>
|
||||
and then trying to log in.
|
||||
</p>
|
||||
@@ -46,4 +50,4 @@
|
||||
<a href="https://man7.org/linux/man-pages/man3/pam_acct_mgmt.3.html">pam_acct_mgmt</a>
|
||||
</li>
|
||||
</references>
|
||||
</qhelp>
|
||||
</qhelp>
|
||||
@@ -1,8 +1,9 @@
|
||||
/**
|
||||
* @name Authorization bypass due to incorrect usage of PAM
|
||||
* @description Using only the `pam_authenticate` call to check the validity of a login can lead to a authorization bypass.
|
||||
* @name PAM authorization bypass due to incorrect usage
|
||||
* @description Not using `pam_acct_mgmt` after `pam_authenticate` to check the validity of a login can lead to authorization bypass.
|
||||
* @kind problem
|
||||
* @problem.severity warning
|
||||
* @security-severity 8.1
|
||||
* @precision high
|
||||
* @id py/pam-auth-bypass
|
||||
* @tags security
|
||||
@@ -33,4 +34,5 @@ where
|
||||
acctMgmtCall = libPam().getMember("pam_acct_mgmt").getACall() and
|
||||
DataFlow::localFlow(handle, acctMgmtCall.getArg(0))
|
||||
)
|
||||
select authenticateCall, "This PAM authentication call may be lead to an authorization bypass."
|
||||
select authenticateCall,
|
||||
"This PAM authentication call may lead to an authorization bypass, since 'pam_acct_mgmt' is not called afterwards."
|
||||
19
python/ql/src/Security/CWE-285/PamAuthorizationBad.py
Normal file
19
python/ql/src/Security/CWE-285/PamAuthorizationBad.py
Normal file
@@ -0,0 +1,19 @@
|
||||
libpam = CDLL(find_library("pam"))
|
||||
|
||||
pam_authenticate = libpam.pam_authenticate
|
||||
pam_authenticate.restype = c_int
|
||||
pam_authenticate.argtypes = [PamHandle, c_int]
|
||||
|
||||
def authenticate(username, password, service='login'):
|
||||
def my_conv(n_messages, messages, p_response, app_data):
|
||||
"""
|
||||
Simple conversation function that responds to any prompt where the echo is off with the supplied password
|
||||
"""
|
||||
...
|
||||
|
||||
handle = PamHandle()
|
||||
conv = PamConv(my_conv, 0)
|
||||
retval = pam_start(service, username, byref(conv), byref(handle))
|
||||
|
||||
retval = pam_authenticate(handle, 0)
|
||||
return retval == 0
|
||||
25
python/ql/src/Security/CWE-285/PamAuthorizationGood.py
Normal file
25
python/ql/src/Security/CWE-285/PamAuthorizationGood.py
Normal file
@@ -0,0 +1,25 @@
|
||||
libpam = CDLL(find_library("pam"))
|
||||
|
||||
pam_authenticate = libpam.pam_authenticate
|
||||
pam_authenticate.restype = c_int
|
||||
pam_authenticate.argtypes = [PamHandle, c_int]
|
||||
|
||||
pam_acct_mgmt = libpam.pam_acct_mgmt
|
||||
pam_acct_mgmt.restype = c_int
|
||||
pam_acct_mgmt.argtypes = [PamHandle, c_int]
|
||||
|
||||
def authenticate(username, password, service='login'):
|
||||
def my_conv(n_messages, messages, p_response, app_data):
|
||||
"""
|
||||
Simple conversation function that responds to any prompt where the echo is off with the supplied password
|
||||
"""
|
||||
...
|
||||
|
||||
handle = PamHandle()
|
||||
conv = PamConv(my_conv, 0)
|
||||
retval = pam_start(service, username, byref(conv), byref(handle))
|
||||
|
||||
retval = pam_authenticate(handle, 0)
|
||||
if retval == 0:
|
||||
retval = pam_acct_mgmt(handle, 0)
|
||||
return retval == 0
|
||||
@@ -15,12 +15,14 @@ private import semmle.python.dataflow.new.DataFlow
|
||||
private import semmle.python.Concepts
|
||||
private import semmle.python.ApiGraphs
|
||||
|
||||
from API::CallNode call, DataFlow::Node falseyOrigin, string verb
|
||||
from
|
||||
HTTP::Client::Request request, DataFlow::Node disablingNode, DataFlow::Node origin, string ending
|
||||
where
|
||||
verb = HTTP::httpVerbLower() and
|
||||
call = API::moduleImport("requests").getMember(verb).getACall() and
|
||||
falseyOrigin = call.getKeywordParameter("verify").getAValueReachingRhs() and
|
||||
// requests treats `None` as the default and all other "falsey" values as `False`.
|
||||
falseyOrigin.asExpr().(ImmutableLiteral).booleanValue() = false and
|
||||
not falseyOrigin.asExpr() instanceof None
|
||||
select call, "Call to requests." + verb + " with verify=$@", falseyOrigin, "False"
|
||||
request.disablesCertificateValidation(disablingNode, origin) and
|
||||
// Showing the origin is only useful when it's a different node than the one disabling
|
||||
// certificate validation, for example in `requests.get(..., verify=arg)`, `arg` would
|
||||
// be the `disablingNode`, and the `origin` would be the place were `arg` got its
|
||||
// value from.
|
||||
if disablingNode = origin then ending = "." else ending = " by the value from $@."
|
||||
select request, "This request may run without certificate validation because it is $@" + ending,
|
||||
disablingNode, "disabled here", origin, "here"
|
||||
|
||||
@@ -13,13 +13,18 @@
|
||||
import python
|
||||
import semmle.python.Concepts
|
||||
|
||||
from Cryptography::CryptographicOperation operation, Cryptography::CryptographicAlgorithm algorithm
|
||||
from
|
||||
Cryptography::CryptographicOperation operation, Cryptography::CryptographicAlgorithm algorithm,
|
||||
string msgPrefix
|
||||
where
|
||||
algorithm = operation.getAlgorithm() and
|
||||
algorithm.isWeak() and
|
||||
// `Cryptography::HashingAlgorithm` and `Cryptography::PasswordHashingAlgorithm` are
|
||||
// handled by `py/weak-sensitive-data-hashing`
|
||||
algorithm instanceof Cryptography::EncryptionAlgorithm
|
||||
select operation,
|
||||
"The cryptographic algorithm " + algorithm.getName() +
|
||||
" is broken or weak, and should not be used."
|
||||
algorithm instanceof Cryptography::EncryptionAlgorithm and
|
||||
(
|
||||
algorithm.isWeak() and
|
||||
msgPrefix = "The cryptographic algorithm " + operation.getAlgorithm().getName()
|
||||
)
|
||||
or
|
||||
operation.getBlockMode().isWeak() and msgPrefix = "The block mode " + operation.getBlockMode()
|
||||
select operation, msgPrefix + " is broken or weak, and should not be used."
|
||||
|
||||
@@ -12,6 +12,7 @@
|
||||
*/
|
||||
|
||||
import python
|
||||
import semmle.python.ApiGraphs
|
||||
|
||||
bindingset[p]
|
||||
int world_permission(int p) { result = p % 8 }
|
||||
@@ -33,20 +34,20 @@ string permissive_permission(int p) {
|
||||
world_permission(p) = 0 and result = "group " + access(group_permission(p))
|
||||
}
|
||||
|
||||
predicate chmod_call(CallNode call, FunctionValue chmod, NumericValue num) {
|
||||
Value::named("os.chmod") = chmod and
|
||||
chmod.getACall() = call and
|
||||
call.getArg(1).pointsTo(num)
|
||||
predicate chmod_call(API::CallNode call, string name, int mode) {
|
||||
call = API::moduleImport("os").getMember("chmod").getACall() and
|
||||
mode = call.getParameter(1, "mode").getAValueReachingRhs().asExpr().(IntegerLiteral).getValue() and
|
||||
name = "chmod"
|
||||
}
|
||||
|
||||
predicate open_call(CallNode call, FunctionValue open, NumericValue num) {
|
||||
Value::named("os.open") = open and
|
||||
open.getACall() = call and
|
||||
call.getArg(2).pointsTo(num)
|
||||
predicate open_call(API::CallNode call, string name, int mode) {
|
||||
call = API::moduleImport("os").getMember("open").getACall() and
|
||||
mode = call.getParameter(2, "mode").getAValueReachingRhs().asExpr().(IntegerLiteral).getValue() and
|
||||
name = "open"
|
||||
}
|
||||
|
||||
from CallNode call, FunctionValue func, NumericValue num, string permission
|
||||
from API::CallNode call, string name, int mode, string permission
|
||||
where
|
||||
(chmod_call(call, func, num) or open_call(call, func, num)) and
|
||||
permission = permissive_permission(num.getIntValue())
|
||||
select call, "Overly permissive mask in " + func.getName() + " sets file to " + permission + "."
|
||||
(chmod_call(call, name, mode) or open_call(call, name, mode)) and
|
||||
permission = permissive_permission(mode)
|
||||
select call, "Overly permissive mask in " + name + " sets file to " + permission + "."
|
||||
|
||||
@@ -72,7 +72,7 @@ class LgtmSuppressionComment extends LineSuppressionComment {
|
||||
* A noqa suppression comment. Both pylint and pyflakes respect this, so lgtm ought to too.
|
||||
*/
|
||||
class NoqaSuppressionComment extends LineSuppressionComment {
|
||||
NoqaSuppressionComment() { this.getContents().toLowerCase().regexpMatch("\\s*noqa\\s*") }
|
||||
NoqaSuppressionComment() { this.getContents().toLowerCase().regexpMatch("\\s*noqa\\s*([^:].*)?") }
|
||||
|
||||
override string getAnnotation() { result = "lgtm" }
|
||||
}
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
---
|
||||
category: minorAnalysis
|
||||
---
|
||||
* The query "Use of a broken or weak cryptographic algorithm" (`py/weak-cryptographic-algorithm`) now report if a cryptographic operation is potentially insecure due to use of a weak block mode.
|
||||
@@ -0,0 +1,4 @@
|
||||
---
|
||||
category: majorAnalysis
|
||||
---
|
||||
* Improved library modeling for the query "Request without certificate validation" (`py/request-without-cert-validation`), so it now also covers `httpx`, `aiohttp.client`, and `urllib3`.
|
||||
5
python/ql/src/change-notes/released/0.1.3.md
Normal file
5
python/ql/src/change-notes/released/0.1.3.md
Normal file
@@ -0,0 +1,5 @@
|
||||
## 0.1.3
|
||||
|
||||
### New Queries
|
||||
|
||||
* The query "PAM authorization bypass due to incorrect usage" (`py/pam-auth-bypass`) has been promoted from experimental to the main query pack. Its results will now appear by default. This query was originally [submitted as an experimental query by @porcupineyhairs](https://github.com/github/codeql/pull/8595).
|
||||
1
python/ql/src/change-notes/released/0.1.4.md
Normal file
1
python/ql/src/change-notes/released/0.1.4.md
Normal file
@@ -0,0 +1 @@
|
||||
## 0.1.4
|
||||
@@ -1,2 +1,2 @@
|
||||
---
|
||||
lastReleaseVersion: 0.1.2
|
||||
lastReleaseVersion: 0.1.4
|
||||
|
||||
23
python/ql/src/experimental/Security/CWE-079/ReflectedXSS.ql
Normal file
23
python/ql/src/experimental/Security/CWE-079/ReflectedXSS.ql
Normal file
@@ -0,0 +1,23 @@
|
||||
/**
|
||||
* @name Reflected server-side cross-site scripting
|
||||
* @description Writing user input directly to a web page
|
||||
* allows for a cross-site scripting vulnerability.
|
||||
* @kind path-problem
|
||||
* @problem.severity error
|
||||
* @security-severity 2.9
|
||||
* @sub-severity high
|
||||
* @id py/reflective-xss
|
||||
* @tags security
|
||||
* external/cwe/cwe-079
|
||||
* external/cwe/cwe-116
|
||||
*/
|
||||
|
||||
// determine precision above
|
||||
import python
|
||||
import experimental.semmle.python.security.dataflow.ReflectedXSS
|
||||
import DataFlow::PathGraph
|
||||
|
||||
from ReflectedXssConfiguration config, DataFlow::PathNode source, DataFlow::PathNode sink
|
||||
where config.hasFlowPath(source, sink)
|
||||
select sink.getNode(), source, sink, "Cross-site scripting vulnerability due to $@.",
|
||||
source.getNode(), "a user-provided value"
|
||||
56
python/ql/src/experimental/Security/CWE-1236/CsvInjection.py
Normal file
56
python/ql/src/experimental/Security/CWE-1236/CsvInjection.py
Normal file
@@ -0,0 +1,56 @@
|
||||
#!/usr/bin/env python
|
||||
# -*- coding: UTF-8 -*-
|
||||
"""
|
||||
@Desc :csv injection
|
||||
"""
|
||||
import copy
|
||||
import csv
|
||||
from flask import Flask
|
||||
from flask import request
|
||||
from typing import List
|
||||
|
||||
app = Flask(__name__)
|
||||
|
||||
@app.route('/bad1')
|
||||
def bad1():
|
||||
csv_data = request.args.get('csv')
|
||||
csvWriter = csv.writer(open("test.csv", "wt"))
|
||||
csvWriter.writerow(csv_data)
|
||||
return "bad1"
|
||||
|
||||
@app.route('/good1')
|
||||
def good1():
|
||||
csv_data = request.args.get('csv')
|
||||
csvWriter = csv.writer(open("test.csv", "wt"))
|
||||
csvWriter.writerow(santize_for_csv(csv_data))
|
||||
return "good1"
|
||||
|
||||
def santize_for_csv(data: str| List[str] | List[List[str]]):
|
||||
def sanitize(item):
|
||||
return "'" + item
|
||||
|
||||
unsafe_prefixes = ("+", "=", "-", "@")
|
||||
if isinstance(data, str):
|
||||
if data.startswith(unsafe_prefixes):
|
||||
return sanitize(data)
|
||||
return data
|
||||
elif isinstance(data, list) and isinstance(data[0], str):
|
||||
sanitized_data = copy.deepcopy(data)
|
||||
for index, item in enumerate(data):
|
||||
if item.startswith(unsafe_prefixes):
|
||||
sanitized_data[index] = sanitize(item)
|
||||
return sanitized_data
|
||||
elif isinstance(data[0], list) and isinstance(data[0][0], str):
|
||||
sanitized_data = copy.deepcopy(data)
|
||||
for outer_index, sublist in enumerate(data):
|
||||
for inner_index, item in enumerate(sublist):
|
||||
if item.startswith(unsafe_prefixes):
|
||||
sanitized_data[outer_index][inner_index] = sanitize(item)
|
||||
return sanitized_data
|
||||
else:
|
||||
raise ValueError("Unsupported data type: " + str(type(data)))
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
app.debug = True
|
||||
app.run()
|
||||
@@ -0,0 +1,29 @@
|
||||
<!DOCTYPE qhelp PUBLIC
|
||||
"-//Semmle//qhelp//EN"
|
||||
"qhelp.dtd">
|
||||
<qhelp>
|
||||
<overview>
|
||||
<p>CSV Injection, also known as Formula Injection, occurs when websites embed untrusted input inside CSV files.</p>
|
||||
<p>When a CSV format file is opened with a spreadsheet program such as Microsoft Excel or LibreOffice Calc.
|
||||
this software interprets entries beginning with <code>=</code> as formulas, which may attempt information exfiltration
|
||||
or other malicious activity when automatically executed by the spreadsheet software.</p>
|
||||
</overview>
|
||||
<recommendation>
|
||||
|
||||
<p>When generating CSV output, ensure that formula-sensitive metacharacters are effectively escaped or removed from all data before storage in the resultant CSV.
|
||||
Risky characters include <code>=</code>(equal), <code>+</code>(plus), <code>-</code>(minus), and <code>@</code>(at).</p>
|
||||
|
||||
</recommendation>
|
||||
<example>
|
||||
|
||||
<p>The following examples show the bad case and the good case respectively.
|
||||
In <code>bad1</code> method, the data provided by the user is directly stored in the CSV file, which may be attacked.
|
||||
But in the <code>good1</code> method, the program will check the data provided by the user, and process the data starting with <code>=</code>(equal), <code>+</code>(plus), <code>-</code>(minus), and <code>@</code>(at) characters safely.</p>
|
||||
|
||||
<sample src="CsvInjection.py" />
|
||||
|
||||
</example>
|
||||
<references>
|
||||
<li>OWASP: <a href="https://owasp.org/www-community/attacks/CSV_Injection">CSV Injection</a>.</li>
|
||||
</references>
|
||||
</qhelp>
|
||||
20
python/ql/src/experimental/Security/CWE-1236/CsvInjection.ql
Normal file
20
python/ql/src/experimental/Security/CWE-1236/CsvInjection.ql
Normal file
@@ -0,0 +1,20 @@
|
||||
/**
|
||||
* @name Csv Injection
|
||||
* @description From user-controlled data saved in CSV files, it is easy to attempt information disclosure
|
||||
* or other malicious activities when automated by spreadsheet software
|
||||
* @kind path-problem
|
||||
* @problem.severity error
|
||||
* @id py/csv-injection
|
||||
* @tags security
|
||||
* external/cwe/cwe-1236
|
||||
*/
|
||||
|
||||
import python
|
||||
import DataFlow::PathGraph
|
||||
import semmle.python.dataflow.new.DataFlow
|
||||
import experimental.semmle.python.security.injection.CsvInjection
|
||||
|
||||
from CsvInjectionFlowConfig config, DataFlow::PathNode source, DataFlow::PathNode sink
|
||||
where config.hasFlowPath(source, sink)
|
||||
select sink.getNode(), source, sink, "Csv injection might include code from $@.", source.getNode(),
|
||||
"this user input"
|
||||
@@ -1,13 +0,0 @@
|
||||
def authenticate(self, username, password, service='login', encoding='utf-8', resetcreds=True):
|
||||
libpam = CDLL(find_library("pam"))
|
||||
pam_authenticate = libpam.pam_authenticate
|
||||
pam_authenticate.restype = c_int
|
||||
pam_authenticate.argtypes = [PamHandle, c_int]
|
||||
|
||||
|
||||
handle = PamHandle()
|
||||
conv = PamConv(my_conv, 0)
|
||||
retval = pam_start(service, username, byref(conv), byref(handle))
|
||||
|
||||
retval = pam_authenticate(handle, 0)
|
||||
return retval == 0
|
||||
@@ -1,17 +0,0 @@
|
||||
def authenticate(self, username, password, service='login', encoding='utf-8', resetcreds=True):
|
||||
libpam = CDLL(find_library("pam"))
|
||||
pam_authenticate = libpam.pam_authenticate
|
||||
pam_acct_mgmt = libpam.pam_acct_mgmt
|
||||
pam_authenticate.restype = c_int
|
||||
pam_authenticate.argtypes = [PamHandle, c_int]
|
||||
pam_acct_mgmt.restype = c_int
|
||||
pam_acct_mgmt.argtypes = [PamHandle, c_int]
|
||||
|
||||
handle = PamHandle()
|
||||
conv = PamConv(my_conv, 0)
|
||||
retval = pam_start(service, username, byref(conv), byref(handle))
|
||||
|
||||
retval = pam_authenticate(handle, 0)
|
||||
if retval == 0:
|
||||
retval = pam_acct_mgmt(handle, 0)
|
||||
return retval == 0
|
||||
@@ -371,6 +371,39 @@ class HeaderDeclaration extends DataFlow::Node {
|
||||
DataFlow::Node getValueArg() { result = range.getValueArg() }
|
||||
}
|
||||
|
||||
/** Provides classes for modeling Csv writer APIs. */
|
||||
module CsvWriter {
|
||||
/**
|
||||
* A data flow node for csv writer.
|
||||
*
|
||||
* Extend this class to model new APIs. If you want to refine existing API models,
|
||||
* extend `CsvWriter` instead.
|
||||
*/
|
||||
abstract class Range extends DataFlow::Node {
|
||||
/**
|
||||
* Get the parameter value of the csv writer function.
|
||||
*/
|
||||
abstract DataFlow::Node getAnInput();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A data flow node for csv writer.
|
||||
*
|
||||
* Extend this class to refine existing API models. If you want to model new APIs,
|
||||
* extend `CsvWriter::Range` instead.
|
||||
*/
|
||||
class CsvWriter extends DataFlow::Node {
|
||||
CsvWriter::Range range;
|
||||
|
||||
CsvWriter() { this = range }
|
||||
|
||||
/**
|
||||
* Get the parameter value of the csv writer function.
|
||||
*/
|
||||
DataFlow::Node getAnInput() { result = range.getAnInput() }
|
||||
}
|
||||
|
||||
/**
|
||||
* A data-flow node that sets a cookie in an HTTP response.
|
||||
*
|
||||
@@ -569,3 +602,77 @@ class JwtDecoding extends DataFlow::Node instanceof JwtDecoding::Range {
|
||||
|
||||
/** DEPRECATED: Alias for JwtDecoding */
|
||||
deprecated class JWTDecoding = JwtDecoding;
|
||||
|
||||
/** Provides classes for modeling Email APIs. */
|
||||
module EmailSender {
|
||||
/**
|
||||
* A data-flow node that sends an email.
|
||||
*
|
||||
* Extend this class to model new APIs. If you want to refine existing API models,
|
||||
* extend `EmailSender` instead.
|
||||
*/
|
||||
abstract class Range extends DataFlow::Node {
|
||||
/**
|
||||
* Gets a data flow node holding the plaintext version of the email body.
|
||||
*/
|
||||
abstract DataFlow::Node getPlainTextBody();
|
||||
|
||||
/**
|
||||
* Gets a data flow node holding the html version of the email body.
|
||||
*/
|
||||
abstract DataFlow::Node getHtmlBody();
|
||||
|
||||
/**
|
||||
* Gets a data flow node holding the recipients of the email.
|
||||
*/
|
||||
abstract DataFlow::Node getTo();
|
||||
|
||||
/**
|
||||
* Gets a data flow node holding the senders of the email.
|
||||
*/
|
||||
abstract DataFlow::Node getFrom();
|
||||
|
||||
/**
|
||||
* Gets a data flow node holding the subject of the email.
|
||||
*/
|
||||
abstract DataFlow::Node getSubject();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A data-flow node that sends an email.
|
||||
*
|
||||
* Extend this class to refine existing API models. If you want to model new APIs,
|
||||
* extend `EmailSender::Range` instead.
|
||||
*/
|
||||
class EmailSender extends DataFlow::Node instanceof EmailSender::Range {
|
||||
/**
|
||||
* Gets a data flow node holding the plaintext version of the email body.
|
||||
*/
|
||||
DataFlow::Node getPlainTextBody() { result = super.getPlainTextBody() }
|
||||
|
||||
/**
|
||||
* Gets a data flow node holding the html version of the email body.
|
||||
*/
|
||||
DataFlow::Node getHtmlBody() { result = super.getHtmlBody() }
|
||||
|
||||
/**
|
||||
* Gets a data flow node holding the recipients of the email.
|
||||
*/
|
||||
DataFlow::Node getTo() { result = super.getTo() }
|
||||
|
||||
/**
|
||||
* Gets a data flow node holding the senders of the email.
|
||||
*/
|
||||
DataFlow::Node getFrom() { result = super.getFrom() }
|
||||
|
||||
/**
|
||||
* Gets a data flow node holding the subject of the email.
|
||||
*/
|
||||
DataFlow::Node getSubject() { result = super.getSubject() }
|
||||
|
||||
/**
|
||||
* Gets a data flow node that refers to the HTML body or plaintext body of the email.
|
||||
*/
|
||||
DataFlow::Node getABody() { result in [super.getPlainTextBody(), super.getHtmlBody()] }
|
||||
}
|
||||
|
||||
@@ -9,8 +9,12 @@ private import experimental.semmle.python.frameworks.Werkzeug
|
||||
private import experimental.semmle.python.frameworks.LDAP
|
||||
private import experimental.semmle.python.frameworks.NoSQL
|
||||
private import experimental.semmle.python.frameworks.JWT
|
||||
private import experimental.semmle.python.frameworks.Csv
|
||||
private import experimental.semmle.python.libraries.PyJWT
|
||||
private import experimental.semmle.python.libraries.Python_JWT
|
||||
private import experimental.semmle.python.libraries.Authlib
|
||||
private import experimental.semmle.python.libraries.PythonJose
|
||||
private import experimental.semmle.python.frameworks.CopyFile
|
||||
private import experimental.semmle.python.frameworks.Sendgrid
|
||||
private import experimental.semmle.python.libraries.FlaskMail
|
||||
private import experimental.semmle.python.libraries.SmtpLib
|
||||
|
||||
73
python/ql/src/experimental/semmle/python/frameworks/Csv.qll
Normal file
73
python/ql/src/experimental/semmle/python/frameworks/Csv.qll
Normal file
@@ -0,0 +1,73 @@
|
||||
/**
|
||||
* Provides classes modeling security-relevant aspects of the `csv` PyPI package.
|
||||
* See https://docs.python.org/3/library/csv.html
|
||||
*/
|
||||
|
||||
private import python
|
||||
private import semmle.python.ApiGraphs
|
||||
private import experimental.semmle.python.Concepts
|
||||
|
||||
/**
|
||||
* Provides models for the `csv` PyPI package.
|
||||
*
|
||||
* See
|
||||
* - https://docs.python.org/3/library/csv.html
|
||||
*/
|
||||
private module Csv {
|
||||
private module Writer {
|
||||
abstract class InstanceSource extends DataFlow::LocalSourceNode { }
|
||||
|
||||
/** A direct instantiation of `csv.writer` or `csv.DictWriter`. */
|
||||
private class ClassInstantiation extends InstanceSource, DataFlow::CfgNode {
|
||||
ClassInstantiation() {
|
||||
this = API::moduleImport("csv").getMember(["writer", "DictWriter"]).getACall()
|
||||
}
|
||||
}
|
||||
|
||||
/** Gets a reference to an `csv.writer` or `csv.DictWriter` instance. */
|
||||
private DataFlow::TypeTrackingNode instance(DataFlow::TypeTracker t) {
|
||||
t.start() and
|
||||
result instanceof InstanceSource
|
||||
or
|
||||
exists(DataFlow::TypeTracker t2 | result = instance(t2).track(t2, t))
|
||||
}
|
||||
|
||||
/** Gets a reference to an `csv.writer` or `csv.DictWriter` instance. */
|
||||
DataFlow::Node instance() { instance(DataFlow::TypeTracker::end()).flowsTo(result) }
|
||||
|
||||
/**
|
||||
* See:
|
||||
* - https://docs.python.org/3/library/csv.html#csvwriter.writerow
|
||||
* - https://docs.python.org/3/library/csv.html#csvwriter.writerows
|
||||
*/
|
||||
private class CsvWriteCall extends CsvWriter::Range, DataFlow::CallCfgNode {
|
||||
string methodName;
|
||||
|
||||
CsvWriteCall() {
|
||||
methodName in ["writerow", "writerows"] and
|
||||
this.(DataFlow::MethodCallNode).calls(Writer::instance(), methodName)
|
||||
}
|
||||
|
||||
override DataFlow::Node getAnInput() {
|
||||
result = this.getArg(0)
|
||||
or
|
||||
methodName = "writerow" and
|
||||
result = this.getArgByName("row")
|
||||
or
|
||||
methodName = "writerows" and
|
||||
result = this.getArgByName("rows")
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* See: https://docs.python.org/3/library/csv.html#csv.DictWriter
|
||||
*/
|
||||
private class DictWriterInstance extends CsvWriter::Range, DataFlow::CallCfgNode {
|
||||
DictWriterInstance() { this = API::moduleImport("csv").getMember("DictWriter").getACall() }
|
||||
|
||||
override DataFlow::Node getAnInput() {
|
||||
result in [this.getArg(1), this.getArgByName("fieldnames")]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -8,8 +8,8 @@ private import semmle.python.frameworks.Django
|
||||
private import semmle.python.dataflow.new.DataFlow
|
||||
private import experimental.semmle.python.Concepts
|
||||
private import semmle.python.ApiGraphs
|
||||
import semmle.python.dataflow.new.RemoteFlowSources
|
||||
private import semmle.python.Concepts
|
||||
import semmle.python.dataflow.new.RemoteFlowSources
|
||||
|
||||
private module ExperimentalPrivateDjango {
|
||||
private module DjangoMod {
|
||||
@@ -189,5 +189,90 @@ private module ExperimentalPrivateDjango {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
module Email {
|
||||
/** https://docs.djangoproject.com/en/3.2/topics/email/ */
|
||||
private API::Node djangoMail() {
|
||||
result = API::moduleImport("django").getMember("core").getMember("mail")
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a call to `django.core.mail.send_mail()`.
|
||||
*
|
||||
* Given the following example:
|
||||
*
|
||||
* ```py
|
||||
* send_mail("Subject", "plain-text body", "from@example.com", ["to@example.com"], html_message=django.http.request.GET.get("html"))
|
||||
* ```
|
||||
*
|
||||
* * `this` would be `send_mail("Subject", "plain-text body", "from@example.com", ["to@example.com"], html_message=django.http.request.GET.get("html"))`.
|
||||
* * `getPlainTextBody()`'s result would be `"plain-text body"`.
|
||||
* * `getHtmlBody()`'s result would be `django.http.request.GET.get("html")`.
|
||||
* * `getTo()`'s result would be `["to@example.com"]`.
|
||||
* * `getFrom()`'s result would be `"from@example.com"`.
|
||||
* * `getSubject()`'s result would be `"Subject"`.
|
||||
*/
|
||||
private class DjangoSendMail extends DataFlow::CallCfgNode, EmailSender::Range {
|
||||
DjangoSendMail() { this = djangoMail().getMember("send_mail").getACall() }
|
||||
|
||||
override DataFlow::Node getPlainTextBody() {
|
||||
result in [this.getArg(1), this.getArgByName("message")]
|
||||
}
|
||||
|
||||
override DataFlow::Node getHtmlBody() {
|
||||
result in [this.getArg(8), this.getArgByName("html_message")]
|
||||
}
|
||||
|
||||
override DataFlow::Node getTo() {
|
||||
result in [this.getArg(3), this.getArgByName("recipient_list")]
|
||||
}
|
||||
|
||||
override DataFlow::Node getFrom() {
|
||||
result in [this.getArg(2), this.getArgByName("from_email")]
|
||||
}
|
||||
|
||||
override DataFlow::Node getSubject() {
|
||||
result in [this.getArg(0), this.getArgByName("subject")]
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a call to `django.core.mail.mail_admins()` or `django.core.mail.mail_managers()`.
|
||||
*
|
||||
* Given the following example:
|
||||
*
|
||||
* ```py
|
||||
* mail_admins("Subject", "plain-text body", html_message=django.http.request.GET.get("html"))
|
||||
* ```
|
||||
*
|
||||
* * `this` would be `mail_admins("Subject", "plain-text body", html_message=django.http.request.GET.get("html"))`.
|
||||
* * `getPlainTextBody()`'s result would be `"plain-text body"`.
|
||||
* * `getHtmlBody()`'s result would be `django.http.request.GET.get("html")`.
|
||||
* * `getTo()`'s result would be `none`.
|
||||
* * `getFrom()`'s result would be `none`.
|
||||
* * `getSubject()`'s result would be `"Subject"`.
|
||||
*/
|
||||
private class DjangoMailInternal extends DataFlow::CallCfgNode, EmailSender::Range {
|
||||
DjangoMailInternal() {
|
||||
this = djangoMail().getMember(["mail_admins", "mail_managers"]).getACall()
|
||||
}
|
||||
|
||||
override DataFlow::Node getPlainTextBody() {
|
||||
result in [this.getArg(1), this.getArgByName("message")]
|
||||
}
|
||||
|
||||
override DataFlow::Node getHtmlBody() {
|
||||
result in [this.getArg(4), this.getArgByName("html_message")]
|
||||
}
|
||||
|
||||
override DataFlow::Node getTo() { none() }
|
||||
|
||||
override DataFlow::Node getFrom() { none() }
|
||||
|
||||
override DataFlow::Node getSubject() {
|
||||
result in [this.getArg(0), this.getArgByName("subject")]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user