Skip to content

Commit 4f26b58

Browse files
authored
Merge pull request #1747 from markshannon/python-extend-taint-tracking-config
Python: Extend taint-tracking configuration to match API of Javascript implementation.
2 parents c642e72 + 22f55d2 commit 4f26b58

File tree

90 files changed

+4023
-2221
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

90 files changed

+4023
-2221
lines changed

python/ql/src/Security/CWE-022/TarSlip.ql

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -187,6 +187,18 @@ class TarSlipConfiguration extends TaintTracking::Configuration {
187187
sanitizer instanceof ExcludeTarFilePy
188188
}
189189

190+
override predicate isBarrier(DataFlow::Node node) {
191+
// Avoid flow into the tarfile module
192+
exists(ParameterDefinition def |
193+
node.asVariable().getDefinition() = def
194+
or
195+
node.asCfgNode() = def.getDefiningNode()
196+
|
197+
def.getScope() = Value::named("tarfile.open").(CallableValue).getScope()
198+
or
199+
def.isSelf() and def.getScope().getEnclosingModule().getName() = "tarfile"
200+
)
201+
}
190202
}
191203

192204

python/ql/src/Security/CWE-089/SqlInjection.ql

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,18 @@ class SQLInjectionConfiguration extends TaintTracking::Configuration {
3232

3333
}
3434

35+
/* Additional configuration to support tracking of DB objects. Connections, cursors, etc. */
36+
class DbConfiguration extends TaintTracking::Configuration {
37+
38+
DbConfiguration() { this = "DB configuration" }
39+
40+
override predicate isSource(TaintTracking::Source source) {
41+
source instanceof DjangoModelObjects or
42+
source instanceof DbConnectionSource
43+
}
44+
45+
}
46+
3547
from SQLInjectionConfiguration config, TaintedPathSource src, TaintedPathSink sink
3648
where config.hasFlowPath(src, sink)
3749
select sink.getSink(), src, sink, "This SQL query depends on $@.", src.getSource(), "a user-provided value"

python/ql/src/Variables/Undefined.qll

Lines changed: 66 additions & 81 deletions
Original file line numberDiff line numberDiff line change
@@ -9,44 +9,6 @@ class Uninitialized extends TaintKind {
99

1010
}
1111

12-
/** A source of an uninitialized variable.
13-
* Either the start of the scope or a deletion.
14-
*/
15-
class UninitializedSource extends TaintedDefinition {
16-
17-
UninitializedSource() {
18-
exists(FastLocalVariable var |
19-
this.getSourceVariable() = var and
20-
not var.escapes() |
21-
this instanceof ScopeEntryDefinition
22-
or
23-
this instanceof DeletionDefinition
24-
)
25-
}
26-
27-
override predicate isSourceOf(TaintKind kind) {
28-
kind instanceof Uninitialized
29-
}
30-
31-
}
32-
33-
/** A loop where we are guaranteed (or is at least likely) to execute the body at least once.
34-
*/
35-
class AtLeastOnceLoop extends DataFlowExtension::DataFlowVariable {
36-
37-
AtLeastOnceLoop() {
38-
loop_entry_variables(this, _)
39-
}
40-
41-
/* If we are guaranteed to iterate over a loop at least once, then we can prune any edges that
42-
* don't pass through the body.
43-
*/
44-
override predicate prunedSuccessor(EssaVariable succ) {
45-
loop_entry_variables(this, succ)
46-
}
47-
48-
}
49-
5012
private predicate loop_entry_variables(EssaVariable pred, EssaVariable succ) {
5113
exists(PhiFunction phi, BasicBlock pb |
5214
loop_entry_edge(pb, phi.getBasicBlock()) and
@@ -64,43 +26,6 @@ private predicate loop_entry_edge(BasicBlock pred, BasicBlock loop) {
6426
)
6527
}
6628

67-
class UnitializedSanitizer extends Sanitizer {
68-
69-
UnitializedSanitizer() { this = "use of variable" }
70-
71-
override
72-
predicate sanitizingDefinition(TaintKind taint, EssaDefinition def) {
73-
// An assignment cannot leave a variable uninitialized
74-
taint instanceof Uninitialized and
75-
(
76-
def instanceof AssignmentDefinition
77-
or
78-
def instanceof ExceptionCapture
79-
or
80-
def instanceof ParameterDefinition
81-
or
82-
/* A use is a "sanitizer" of "uninitialized", as any use of an undefined
83-
* variable will raise, making the subsequent code unreacahable.
84-
*/
85-
exists(def.(EssaNodeRefinement).getInput().getASourceUse())
86-
or
87-
exists(def.(PhiFunction).getAnInput().getASourceUse())
88-
or
89-
exists(def.(EssaEdgeRefinement).getInput().getASourceUse())
90-
)
91-
}
92-
93-
override
94-
predicate sanitizingNode(TaintKind taint, ControlFlowNode node) {
95-
taint instanceof Uninitialized and
96-
exists(EssaVariable v |
97-
v.getASourceUse() = node and
98-
not first_use(node, v)
99-
)
100-
}
101-
102-
}
103-
10429
/** Since any use of a local will raise if it is uninitialized, then
10530
* any use dominated by another use of the same variable must be defined, or is unreachable.
10631
*/
@@ -124,15 +49,75 @@ private predicate maybe_call_to_exiting_function(CallNode call) {
12449
)
12550
}
12651

127-
/** Prune edges where the predecessor block looks like it might contain a call to an exit function. */
128-
class ExitFunctionGuardedEdge extends DataFlowExtension::DataFlowVariable {
12952

130-
override predicate prunedSuccessor(EssaVariable succ) {
131-
exists(CallNode exit_call |
132-
succ.(PhiFunction).getInput(exit_call.getBasicBlock()) = this and
133-
maybe_call_to_exiting_function(exit_call)
53+
predicate exitFunctionGuardedEdge(EssaVariable pred, EssaVariable succ) {
54+
exists(CallNode exit_call |
55+
succ.(PhiFunction).getInput(exit_call.getBasicBlock()) = pred and
56+
maybe_call_to_exiting_function(exit_call)
57+
)
58+
}
59+
60+
class UninitializedConfig extends TaintTracking::Configuration {
61+
62+
UninitializedConfig() {
63+
this = "Unitialized local config"
64+
}
65+
66+
override predicate isSource(DataFlow::Node source, TaintKind kind) {
67+
kind instanceof Uninitialized and
68+
exists(EssaVariable var |
69+
source.asVariable() = var and
70+
var.getSourceVariable() instanceof FastLocalVariable and
71+
not var.getSourceVariable().(Variable).escapes() |
72+
var instanceof ScopeEntryDefinition
73+
or
74+
var instanceof DeletionDefinition
13475
)
13576
}
13677

78+
override predicate isBarrier(DataFlow::Node node, TaintKind kind) {
79+
kind instanceof Uninitialized and
80+
(
81+
definition(node.asVariable())
82+
or
83+
use(node.asVariable())
84+
or
85+
sanitizingNode(node.asCfgNode())
86+
)
87+
}
88+
89+
private predicate definition(EssaDefinition def) {
90+
def instanceof AssignmentDefinition
91+
or
92+
def instanceof ExceptionCapture
93+
or
94+
def instanceof ParameterDefinition
95+
}
96+
97+
private predicate use(EssaDefinition def) {
98+
exists(def.(EssaNodeRefinement).getInput().getASourceUse())
99+
or
100+
exists(def.(PhiFunction).getAnInput().getASourceUse())
101+
or
102+
exists(def.(EssaEdgeRefinement).getInput().getASourceUse())
103+
}
104+
105+
private predicate sanitizingNode(ControlFlowNode node) {
106+
exists(EssaVariable v |
107+
v.getASourceUse() = node and
108+
not first_use(node, v)
109+
)
110+
}
111+
112+
override predicate isBarrierEdge(DataFlow::Node src, DataFlow::Node dest) {
113+
/* If we are guaranteed to iterate over a loop at least once, then we can prune any edges that
114+
* don't pass through the body.
115+
*/
116+
loop_entry_variables(src.asVariable(), dest.asVariable())
117+
or
118+
exitFunctionGuardedEdge(src.asVariable(), dest.asVariable())
119+
}
120+
137121
}
138122

123+
Lines changed: 145 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,145 @@
1+
import python
2+
import semmle.python.security.TaintTracking
3+
private import semmle.python.objects.ObjectInternal
4+
private import semmle.python.dataflow.Implementation
5+
6+
module TaintTracking {
7+
8+
class Source = TaintSource;
9+
10+
class Sink = TaintSink;
11+
12+
class Extension = DataFlowExtension::DataFlowNode;
13+
14+
class PathSource = TaintTrackingNode;
15+
16+
class PathSink = TaintTrackingNode;
17+
18+
abstract class Configuration extends string {
19+
20+
/* Required to prevent compiler warning */
21+
bindingset[this]
22+
Configuration() { this = this }
23+
24+
/* Old implementation API */
25+
26+
predicate isSource(Source source) { none() }
27+
28+
predicate isSink(Sink sink) { none() }
29+
30+
predicate isSanitizer(Sanitizer sanitizer) { none() }
31+
32+
predicate isExtension(Extension extension) { none() }
33+
34+
/* New implementation API */
35+
36+
/**
37+
* Holds if `source` is a source of taint of `kind` that is relevant
38+
* for this configuration.
39+
*/
40+
predicate isSource(DataFlow::Node node, TaintKind kind) {
41+
exists(TaintSource source |
42+
this.isSource(source) and
43+
node.asCfgNode() = source and
44+
source.isSourceOf(kind)
45+
)
46+
}
47+
48+
/**
49+
* Holds if `sink` is a sink of taint of `kind` that is relevant
50+
* for this configuration.
51+
*/
52+
predicate isSink(DataFlow::Node node, TaintKind kind) {
53+
exists(TaintSink sink |
54+
node.asCfgNode() = sink and
55+
sink.sinks(kind)
56+
)
57+
}
58+
59+
/**
60+
* Holds if `src -> dest` should be considered as a flow edge
61+
* in addition to standard data flow edges.
62+
*/
63+
predicate isAdditionalFlowStep(DataFlow::Node src, DataFlow::Node dest) { none() }
64+
65+
/**
66+
* Holds if `src -> dest` is a flow edge converting taint from `srckind` to `destkind`.
67+
*/
68+
predicate isAdditionalFlowStep(DataFlow::Node src, DataFlow::Node dest, TaintKind srckind, TaintKind destkind) {
69+
none()
70+
}
71+
72+
/**
73+
* Holds if `node` should be considered as a barrier to flow of any kind.
74+
*/
75+
predicate isBarrier(DataFlow::Node node) { none() }
76+
77+
/**
78+
* Holds if `node` should be considered as a barrier to flow of `kind`.
79+
*/
80+
predicate isBarrier(DataFlow::Node node, TaintKind kind) {
81+
exists(Sanitizer sanitizer |
82+
this.isSanitizer(sanitizer)
83+
|
84+
sanitizer.sanitizingNode(kind, node.asCfgNode())
85+
or
86+
sanitizer.sanitizingEdge(kind, node.asVariable())
87+
or
88+
sanitizer.sanitizingSingleEdge(kind, node.asVariable())
89+
or
90+
sanitizer.sanitizingDefinition(kind, node.asVariable())
91+
or
92+
exists(MethodCallsiteRefinement call, FunctionObject callee |
93+
call = node.asVariable().getDefinition() and
94+
callee.getACall() = call.getCall() and
95+
sanitizer.sanitizingCall(kind, callee)
96+
)
97+
)
98+
}
99+
100+
/**
101+
* Holds if flow from `src` to `dest` is prohibited.
102+
*/
103+
predicate isBarrierEdge(DataFlow::Node src, DataFlow::Node dest) { none() }
104+
105+
/**
106+
* Holds if control flow from `test` along the `isTrue` edge is prohibited.
107+
*/
108+
predicate isBarrierTest(ControlFlowNode test, boolean isTrue) { none() }
109+
110+
/**
111+
* Holds if flow from `src` to `dest` is prohibited when the incoming taint is `srckind` and the outgoing taint is `destkind`.
112+
* Note that `srckind` and `destkind` can be the same.
113+
*/
114+
predicate isBarrierEdge(DataFlow::Node src, DataFlow::Node dest, TaintKind srckind, TaintKind destkind) { none() }
115+
116+
/* Common query API */
117+
118+
predicate hasFlowPath(PathSource source, PathSink sink) {
119+
this.(TaintTrackingImplementation).hasFlowPath(source, sink)
120+
}
121+
122+
/* Old query API */
123+
124+
/* deprecated */
125+
predicate hasFlow(Source source, Sink sink) {
126+
exists(PathSource psource, PathSink psink |
127+
this.hasFlowPath(psource, psink) and
128+
source = psource.getNode().asCfgNode() and
129+
sink = psink.getNode().asCfgNode()
130+
)
131+
}
132+
133+
/* New query API */
134+
135+
predicate hasSimpleFlow(DataFlow::Node source, DataFlow::Node sink) {
136+
exists(PathSource psource, PathSink psink |
137+
this.hasFlowPath(psource, psink) and
138+
source = psource.getNode() and
139+
sink = psink.getNode()
140+
)
141+
}
142+
143+
}
144+
145+
}

0 commit comments

Comments
 (0)