Skip to content

Commit 23eed30

Browse files
authored
Merge pull request github#1157 from markshannon/python-taint-tracking-early-exit
Python taint-tracking improvements
2 parents 36c7a84 + 2f0bb82 commit 23eed30

File tree

11 files changed

+277
-2
lines changed

11 files changed

+277
-2
lines changed

python/ql/src/semmle/python/security/TaintTracking.qll

Lines changed: 34 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1237,7 +1237,7 @@ library module TaintFlowImplementation {
12371237
)
12381238
|
12391239
not Filters::isinstance(test.getTest(), _, var.getSourceVariable().getAUse()) and
1240-
not test.getTest() = var.getSourceVariable().getAUse()
1240+
not boolean_filter(test.getTest(), var.getSourceVariable().getAUse())
12411241
or
12421242
exists(ControlFlowNode c, ClassObject cls |
12431243
Filters::isinstance(test.getTest(), c, var.getSourceVariable().getAUse())
@@ -1248,10 +1248,42 @@ library module TaintFlowImplementation {
12481248
test.getSense() = false and not kind.getClass().getAnImproperSuperType() = cls
12491249
)
12501250
or
1251-
test.getTest() = var.getSourceVariable().getAUse() and kind.booleanValue() = test.getSense()
1251+
test.getSense() = test_evaluates(test.getTest(), var.getSourceVariable().getAUse(), kind)
12521252
)
12531253
}
12541254

1255+
/** Gets the operand of a unary `not` expression. */
1256+
private ControlFlowNode not_operand(ControlFlowNode expr) {
1257+
expr.(UnaryExprNode).getNode().getOp() instanceof Not and
1258+
result = expr.(UnaryExprNode).getOperand()
1259+
}
1260+
1261+
/** Holds if `test` is the test in a branch and `use` is that test
1262+
* with all the `not` prefixes removed.
1263+
*/
1264+
private predicate boolean_filter(ControlFlowNode test, ControlFlowNode use) {
1265+
any(PyEdgeRefinement ref).getTest() = test and
1266+
(
1267+
use = test
1268+
or
1269+
exists(ControlFlowNode notuse |
1270+
boolean_filter(test, notuse) and
1271+
use = not_operand(notuse)
1272+
)
1273+
)
1274+
}
1275+
1276+
/** Gets the boolean value that `test` evaluates to when `use` is tainted with `kind`
1277+
* and `test` and `use` are part of a test in a branch.
1278+
*/
1279+
private boolean test_evaluates(ControlFlowNode test, ControlFlowNode use, TaintKind kind) {
1280+
boolean_filter(_, use) and
1281+
kind.taints(use) and
1282+
test = use and result = kind.booleanValue()
1283+
or
1284+
result = test_evaluates(not_operand(test), use, kind).booleanNot()
1285+
}
1286+
12551287
pragma [noinline]
12561288
predicate tainted_argument(ArgumentRefinement def, CallContext context, TaintedNode origin) {
12571289
tainted_var(def.getInput(), context, origin)

python/ql/test/library-tests/taint/general/TaintLib.qll

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -334,3 +334,35 @@ class OsCommand extends TaintSink {
334334
}
335335

336336
}
337+
338+
339+
class Falsey extends TaintKind {
340+
341+
Falsey() { this = "falsey" }
342+
343+
override boolean booleanValue() {
344+
result = false
345+
}
346+
347+
}
348+
349+
class FalseySource extends TaintSource {
350+
351+
FalseySource() {
352+
this.(NameNode).getId() = "FALSEY"
353+
}
354+
355+
override predicate isSourceOf(TaintKind kind) {
356+
kind instanceof Falsey
357+
}
358+
359+
override string toString() {
360+
result = "falsey.source"
361+
}
362+
363+
}
364+
365+
366+
367+
368+

python/ql/test/library-tests/taint/general/TestDefn.expected

Lines changed: 184 additions & 0 deletions
Large diffs are not rendered by default.
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
import python
2+
import semmle.python.security.TaintTest
3+
import TaintLib
4+
5+
6+
from EssaDefinition defn, TaintedNode n
7+
where TaintFlowTest::tainted_def(defn, _, n)
8+
select
9+
defn.getLocation().toString(), defn.getRepresentation(), n.getLocation().toString(), n.getTrackedValue(), n.getNode().getNode().toString()

python/ql/test/library-tests/taint/general/TestNode.expected

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,8 @@
9494
| Taint explicit.carrier | carrier.py:33 | TAINT_CARRIER_SOURCE | |
9595
| Taint explicit.carrier | carrier.py:34 | Attribute | |
9696
| Taint explicit.carrier | carrier.py:35 | x | |
97+
| Taint falsey | test.py:189 | FALSEY | |
98+
| Taint falsey | test.py:190 | t | |
9799
| Taint paper | rockpaperscissors.py:6 | arg | rockpaperscissors.py:32 |
98100
| Taint paper | rockpaperscissors.py:9 | arg | rockpaperscissors.py:26 |
99101
| Taint paper | rockpaperscissors.py:25 | Attribute() | |

python/ql/test/library-tests/taint/general/TestSource.expected

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,3 +41,4 @@
4141
| test.py:168 | SOURCE | simple.test |
4242
| test.py:169 | SOURCE | simple.test |
4343
| test.py:178 | SOURCE | simple.test |
44+
| test.py:189 | FALSEY | falsey |

python/ql/test/library-tests/taint/general/TestStep.expected

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,7 @@
8282
| Taint explicit.carrier | carrier.py:33 | TAINT_CARRIER_SOURCE | | --> | Taint explicit.carrier | carrier.py:4 | arg | carrier.py:33 |
8383
| Taint explicit.carrier | carrier.py:34 | Attribute | | --> | Taint explicit.carrier | carrier.py:35 | x | |
8484
| Taint explicit.carrier | carrier.py:35 | x | | --> | Taint simple.test | carrier.py:35 | Attribute() | |
85+
| Taint falsey | test.py:189 | FALSEY | | --> | Taint falsey | test.py:190 | t | |
8586
| Taint paper | rockpaperscissors.py:25 | Attribute() | | --> | Taint paper | rockpaperscissors.py:26 | y | |
8687
| Taint paper | rockpaperscissors.py:26 | y | | --> | Taint paper | rockpaperscissors.py:9 | arg | rockpaperscissors.py:26 |
8788
| Taint paper | rockpaperscissors.py:30 | Attribute() | | --> | Taint paper | rockpaperscissors.py:32 | y | |

python/ql/test/library-tests/taint/general/TestVar.expected

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -182,3 +182,5 @@
182182
| test.py:180 | t_2 | test.py:178 | Taint simple.test | SOURCE |
183183
| test.py:183 | t_3 | test.py:178 | Taint simple.test | SOURCE |
184184
| test.py:186 | t_4 | test.py:178 | Taint simple.test | SOURCE |
185+
| test.py:189 | t_0 | test.py:189 | Taint falsey | FALSEY |
186+
| test.py:191 | t_1 | test.py:189 | Taint falsey | FALSEY |

python/ql/test/library-tests/taint/general/test.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -184,3 +184,9 @@ def test_truth():
184184
SINK(t)
185185
else:
186186
SINK(t)
187+
188+
def test_early_exit():
189+
t = FALSEY
190+
if not t:
191+
return
192+
t

python/ql/test/query-tests/Functions/general/ModificationOfParameterWithDefault.expected

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ edges
1010
| functions_test.py:298:21:298:21 | empty mutable value | functions_test.py:293:21:293:21 | empty mutable value |
1111
| functions_test.py:300:26:300:26 | empty mutable value | functions_test.py:301:8:301:8 | empty mutable value |
1212
| functions_test.py:300:26:300:26 | empty mutable value | functions_test.py:303:12:303:12 | empty mutable value |
13+
| functions_test.py:305:21:305:25 | empty mutable value | functions_test.py:306:12:306:16 | empty mutable value |
1314
parents
1415
| functions_test.py:290:25:290:25 | empty mutable value | functions_test.py:297:25:297:25 | empty mutable value |
1516
| functions_test.py:291:5:291:5 | empty mutable value | functions_test.py:297:25:297:25 | empty mutable value |

0 commit comments

Comments
 (0)