Skip to content

Commit d05954e

Browse files
authored
Merge pull request #4109 from RasmusWL/python-basic-taint-tracking
Python: Basic taint tracking with shared library
2 parents 86b91ce + 7fb8e0e commit d05954e

File tree

7 files changed

+106
-31
lines changed

7 files changed

+106
-31
lines changed
Lines changed: 13 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,14 @@
11
private import python
2-
private import TaintTrackingPublic
32
private import experimental.dataflow.DataFlow
43
private import experimental.dataflow.internal.DataFlowPrivate
4+
private import experimental.dataflow.internal.TaintTrackingPublic
5+
6+
/**
7+
* Holds if taint can flow in one local step from `nodeFrom` to `nodeTo` excluding
8+
* local data flow steps. That is, `nodeFrom` and `nodeTo` are likely to represent
9+
* different objects.
10+
*/
11+
predicate localAdditionalTaintStep(DataFlow::Node nodeFrom, DataFlow::Node nodeTo) { none() }
512

613
/**
714
* Holds if `node` should be a barrier in all global taint flow configurations
@@ -10,12 +17,11 @@ private import experimental.dataflow.internal.DataFlowPrivate
1017
predicate defaultTaintBarrier(DataFlow::Node node) { none() }
1118

1219
/**
13-
* Holds if the additional step from `pred` to `succ` should be included in all
20+
* Holds if the additional step from `nodeFrom` to `nodeTo` should be included in all
1421
* global taint flow configurations.
1522
*/
16-
predicate defaultAdditionalTaintStep(DataFlow::Node pred, DataFlow::Node succ) {
17-
none()
18-
// localAdditionalTaintStep(pred, succ)
19-
// or
20-
// succ = pred.(DataFlow::NonLocalJumpNode).getAJumpSuccessor(false)
23+
predicate defaultAdditionalTaintStep(DataFlow::Node nodeFrom, DataFlow::Node nodeTo) {
24+
localAdditionalTaintStep(nodeFrom, nodeTo)
25+
or
26+
any(AdditionalTaintStep a).step(nodeFrom, nodeTo)
2127
}

python/ql/src/experimental/dataflow/internal/TaintTrackingPublic.qll

Lines changed: 49 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -6,27 +6,52 @@
66
private import python
77
private import TaintTrackingPrivate
88
private import experimental.dataflow.DataFlow
9-
// /**
10-
// * Holds if taint propagates from `source` to `sink` in zero or more local
11-
// * (intra-procedural) steps.
12-
// */
13-
// predicate localTaint(DataFlow::Node source, DataFlow::Node sink) { localTaintStep*(source, sink) }
14-
// // /**
15-
// // * Holds if taint can flow from `e1` to `e2` in zero or more
16-
// // * local (intra-procedural) steps.
17-
// // */
18-
// // predicate localExprTaint(Expr e1, Expr e2) {
19-
// // localTaint(DataFlow::exprNode(e1), DataFlow::exprNode(e2))
20-
// // }
21-
// // /** A member (property or field) that is tainted if its containing object is tainted. */
22-
// // abstract class TaintedMember extends AssignableMember { }
23-
// /**
24-
// * Holds if taint propagates from `nodeFrom` to `nodeTo` in exactly one local
25-
// * (intra-procedural) step.
26-
// */
27-
// predicate localTaintStep(DataFlow::Node nodeFrom, DataFlow::Node nodeTo) {
28-
// // Ordinary data flow
29-
// DataFlow::localFlowStep(nodeFrom, nodeTo)
30-
// or
31-
// localAdditionalTaintStep(nodeFrom, nodeTo)
32-
// }
9+
10+
// Local taint flow and helpers
11+
/**
12+
* Holds if taint propagates from `source` to `sink` in zero or more local
13+
* (intra-procedural) steps.
14+
*/
15+
predicate localTaint(DataFlow::Node source, DataFlow::Node sink) { localTaintStep*(source, sink) }
16+
17+
/**
18+
* Holds if taint can flow from `e1` to `e2` in zero or more local (intra-procedural)
19+
* steps.
20+
*/
21+
predicate localExprTaint(Expr e1, Expr e2) {
22+
localTaint(DataFlow::exprNode(e1), DataFlow::exprNode(e2))
23+
}
24+
25+
/**
26+
* Holds if taint propagates from `nodeFrom` to `nodeTo` in exactly one local
27+
* (intra-procedural) step.
28+
*/
29+
predicate localTaintStep(DataFlow::Node nodeFrom, DataFlow::Node nodeTo) {
30+
// Ordinary data flow
31+
DataFlow::localFlowStep(nodeFrom, nodeTo)
32+
or
33+
localAdditionalTaintStep(nodeFrom, nodeTo)
34+
}
35+
36+
// AdditionalTaintStep for global taint flow
37+
private newtype TUnit = TMkUnit()
38+
39+
/** A singleton class containing a single dummy "unit" value. */
40+
private class Unit extends TUnit {
41+
/** Gets a textual representation of this element. */
42+
string toString() { result = "unit" }
43+
}
44+
45+
/**
46+
* A unit class for adding additional taint steps.
47+
*
48+
* Extend this class to add additional taint steps that should apply to all
49+
* taint configurations.
50+
*/
51+
class AdditionalTaintStep extends Unit {
52+
/**
53+
* Holds if the step from `nodeFrom` to `nodeTo` should be considered a taint
54+
* step for all configurations.
55+
*/
56+
abstract predicate step(DataFlow::Node nodeFrom, DataFlow::Node nodeTo);
57+
}
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
| test.py:3:11:3:16 | ControlFlowNode for SOURCE | test.py:4:6:4:12 | ControlFlowNode for tainted |
2+
| test.py:7:20:7:25 | ControlFlowNode for SOURCE | test.py:8:10:8:21 | ControlFlowNode for also_tainted |
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
import python
2+
import experimental.dataflow.TaintTracking
3+
import experimental.dataflow.DataFlow
4+
5+
class TestTaintTrackingConfiguration extends TaintTracking::Configuration {
6+
TestTaintTrackingConfiguration() { this = "TestTaintTrackingConfiguration" }
7+
8+
override predicate isSource(DataFlow::Node source) {
9+
source.(DataFlow::CfgNode).getNode().(NameNode).getId() = "SOURCE"
10+
}
11+
12+
override predicate isSink(DataFlow::Node sink) {
13+
exists(CallNode call |
14+
call.getFunction().(NameNode).getId() = "SINK" and
15+
sink.(DataFlow::CfgNode).getNode() = call.getAnArg()
16+
)
17+
}
18+
}
19+
20+
from TestTaintTrackingConfiguration config, DataFlow::Node source, DataFlow::Node sink
21+
where config.hasFlow(source, sink)
22+
select source, sink
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
| test.py:0:0:0:0 | SSA variable $ | test.py:0:0:0:0 | Exit node for Module test |
2+
| test.py:7:5:7:16 | SSA variable also_tainted | test.py:8:5:8:22 | SSA variable also_tainted |
3+
| test.py:7:5:7:16 | SSA variable also_tainted | test.py:8:10:8:21 | ControlFlowNode for also_tainted |
4+
| test.py:7:20:7:25 | ControlFlowNode for SOURCE | test.py:7:5:7:16 | SSA variable also_tainted |
5+
| test.py:8:5:8:22 | SSA variable also_tainted | test.py:6:1:6:11 | Exit node for Function func |
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
import python
2+
import experimental.dataflow.TaintTracking
3+
import experimental.dataflow.DataFlow
4+
5+
from DataFlow::Node nodeFrom, DataFlow::Node nodeTo
6+
where TaintTracking::localTaintStep(nodeFrom, nodeTo)
7+
select nodeFrom, nodeTo
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
# Module level taint is different from inside functions, since shared dataflow library
2+
# relies on `getEnclosingCallable`
3+
tainted = SOURCE
4+
SINK(tainted)
5+
6+
def func():
7+
also_tainted = SOURCE
8+
SINK(also_tainted)

0 commit comments

Comments
 (0)