Skip to content

Commit cb43d27

Browse files
authored
Merge pull request #1472 from markshannon/python-taint-through-iterators
Python: Track taint through iteration and iterators including generators.
2 parents b43df74 + e8190d9 commit cb43d27

File tree

9 files changed

+132
-0
lines changed

9 files changed

+132
-0
lines changed

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

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -159,6 +159,12 @@ abstract class TaintKind extends string {
159159

160160
string repr() { result = this }
161161

162+
/** Gets the taint resulting from iterating over this kind of taint.
163+
* For example iterating over a text file produces lines. So iterating
164+
* over a tainted file would result in tainted strings
165+
*/
166+
TaintKind getTaintForIteration() { none() }
167+
162168
}
163169

164170
/** Taint kinds representing collections of other taint kind.
@@ -212,6 +218,10 @@ class SequenceKind extends CollectionKind {
212218
result = "sequence of " + itemKind
213219
}
214220

221+
override TaintKind getTaintForIteration() {
222+
result = itemKind
223+
}
224+
215225
}
216226

217227

@@ -863,6 +873,10 @@ library module TaintFlowImplementation {
863873
or
864874
call_taint_step(fromnode, totaint, tocontext, tonode)
865875
or
876+
iteration_step(fromnode, totaint, tocontext, tonode)
877+
or
878+
yield_step(fromnode, totaint, tocontext, tonode)
879+
or
866880
exists(DataFlowNode fromnodenode |
867881
fromnodenode = fromnode.getNode() and
868882
(
@@ -1053,6 +1067,26 @@ library module TaintFlowImplementation {
10531067
)
10541068
}
10551069

1070+
predicate yield_step(TaintedNode fromnode, TrackedValue totaint, CallContext tocontext, CallNode call) {
1071+
exists(PyFunctionObject func |
1072+
func.getFunction().isGenerator() and
1073+
func.getACall() = call and
1074+
(
1075+
fromnode.getContext() = tocontext.getCallee(call)
1076+
or
1077+
fromnode.getContext() = tocontext and tocontext = TTop()
1078+
) and
1079+
exists(Yield yield |
1080+
yield.getScope() = func.getFunction() and
1081+
yield.getValue() = fromnode.getNode().getNode()
1082+
) and
1083+
exists(SequenceKind seq |
1084+
seq.getItem() = fromnode.getTaintKind() and
1085+
totaint = fromnode.getTrackedValue().toKind(seq)
1086+
)
1087+
)
1088+
}
1089+
10561090
predicate call_taint_step(TaintedNode fromnode, TrackedValue totaint, CallContext tocontext, CallNode call) {
10571091
exists(string name |
10581092
call.getFunction().(AttrNode).getObject(name) = fromnode.getNode() and
@@ -1067,6 +1101,13 @@ library module TaintFlowImplementation {
10671101
)
10681102
}
10691103

1104+
/** Holds if `v` is defined by a `for` statement, the definition being `defn` */
1105+
cached predicate iteration_step(TaintedNode fromnode, TrackedValue totaint, CallContext tocontext, ControlFlowNode iter) {
1106+
exists(ForNode for | for.iterates(iter, fromnode.getNode())) and
1107+
totaint = TTrackedTaint(fromnode.getTaintKind().getTaintForIteration()) and
1108+
tocontext = fromnode.getContext()
1109+
}
1110+
10701111
predicate self_init_end_transfer(EssaVariable self, CallContext callee, CallNode call, CallContext caller) {
10711112
exists(ClassValue cls, Function init |
10721113
call.getFunction().pointsTo(cls) and
@@ -1161,6 +1202,9 @@ library module TaintFlowImplementation {
11611202
tainted_with(def, context, origin)
11621203
or
11631204
tainted_exception_capture(def, context, origin)
1205+
or
1206+
tainted_iteration(def, context, origin)
1207+
11641208
}
11651209

11661210
predicate tainted_scope_entry(ScopeEntryDefinition def, CallContext context, TaintedNode origin) {
@@ -1363,6 +1407,12 @@ library module TaintFlowImplementation {
13631407
context = fromnode.getContext()
13641408
}
13651409

1410+
pragma [noinline]
1411+
private predicate tainted_iteration(IterationDefinition def, CallContext context, TaintedNode fromnode) {
1412+
def.getDefiningNode() = fromnode.getNode() and
1413+
context = fromnode.getContext()
1414+
}
1415+
13661416
/* A call that returns a copy (or similar) of the argument */
13671417
predicate copyCall(ControlFlowNode fromnode, CallNode tonode) {
13681418
tonode.getFunction().(AttrNode).getObject("copy") = fromnode

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

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -362,7 +362,29 @@ class FalseySource extends TaintSource {
362362

363363
}
364364

365+
class TaintIterable extends TaintKind {
365366

367+
TaintIterable() {
368+
this = "iterable.simple"
369+
}
370+
371+
override TaintKind getTaintForIteration() {
372+
result instanceof SimpleTest
373+
}
374+
375+
}
376+
377+
class TaintIterableSource extends TaintSource {
378+
379+
TaintIterableSource() {
380+
this.(NameNode).getId() = "ITERABLE_SOURCE"
381+
}
382+
383+
override predicate isSourceOf(TaintKind kind) {
384+
kind instanceof TaintIterable
385+
}
386+
387+
}
366388

367389

368390

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

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -180,3 +180,12 @@
180180
| test.py:197 | Pi(t_0) [true] | test.py:195 | Taint simple.test | SOURCE |
181181
| test.py:199 | ArgumentRefinement(t_3) | test.py:195 | Taint simple.test | SOURCE |
182182
| test.py:199 | Pi(t_0) [false] | test.py:195 | Taint simple.test | SOURCE |
183+
| test.py:202 | ITERABLE_SOURCE | test.py:202 | Taint iterable.simple | ITERABLE_SOURCE |
184+
| test.py:203 | IterationDefinition | test.py:203 | Taint simple.test | i |
185+
| test.py:203 | phi(i_0, i_2) | test.py:203 | Taint simple.test | i |
186+
| test.py:208 | List | test.py:208 | Taint [simple.test] | List |
187+
| test.py:209 | IterationDefinition | test.py:209 | Taint simple.test | i |
188+
| test.py:209 | phi(i_0, i_2) | test.py:209 | Taint simple.test | i |
189+
| test.py:213 | IterationDefinition | test.py:213 | Taint simple.test | x |
190+
| test.py:213 | phi(x_2, x_3) | test.py:213 | Taint simple.test | x |
191+
| test.py:214 | ArgumentRefinement(x_1) | test.py:213 | Taint simple.test | x |

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

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,9 @@
6767
| Taint [simple.test] | test.py:172 | x | |
6868
| Taint [simple.test] | test.py:174 | l | |
6969
| Taint [simple.test] | test.py:174 | list() | |
70+
| Taint [simple.test] | test.py:208 | List | |
71+
| Taint [simple.test] | test.py:209 | seq | |
72+
| Taint [simple.test] | test.py:213 | flow_in_generator() | |
7073
| Taint basic.custom | test.py:72 | arg | test.py:121 |
7174
| Taint basic.custom | test.py:73 | arg | test.py:121 |
7275
| Taint basic.custom | test.py:120 | CUSTOM_SOURCE | |
@@ -96,6 +99,8 @@
9699
| Taint explicit.carrier | carrier.py:35 | x | |
97100
| Taint falsey | test.py:189 | FALSEY | |
98101
| Taint falsey | test.py:190 | t | |
102+
| Taint iterable.simple | test.py:202 | ITERABLE_SOURCE | |
103+
| Taint iterable.simple | test.py:203 | t | |
99104
| Taint paper | rockpaperscissors.py:6 | arg | rockpaperscissors.py:32 |
100105
| Taint paper | rockpaperscissors.py:9 | arg | rockpaperscissors.py:26 |
101106
| Taint paper | rockpaperscissors.py:25 | Attribute() | |
@@ -226,6 +231,14 @@
226231
| Taint simple.test | test.py:196 | t | |
227232
| Taint simple.test | test.py:197 | t | |
228233
| Taint simple.test | test.py:199 | t | |
234+
| Taint simple.test | test.py:203 | i | |
235+
| Taint simple.test | test.py:204 | i | |
236+
| Taint simple.test | test.py:205 | i | |
237+
| Taint simple.test | test.py:208 | SOURCE | |
238+
| Taint simple.test | test.py:209 | i | |
239+
| Taint simple.test | test.py:210 | i | |
240+
| Taint simple.test | test.py:213 | x | |
241+
| Taint simple.test | test.py:214 | x | |
229242
| Taint {simple.test} | test.py:169 | Dict | |
230243
| Taint {simple.test} | test.py:171 | d | |
231244
| Taint {simple.test} | test.py:173 | y | |

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,3 +36,4 @@
3636
| simple.test | test.py:178 | 186 | t | simple.test |
3737
| simple.test | test.py:195 | 197 | t | simple.test |
3838
| simple.test | test.py:195 | 199 | t | simple.test |
39+
| simple.test | test.py:208 | 214 | x | simple.test |

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

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,3 +43,5 @@
4343
| test.py:178 | SOURCE | simple.test |
4444
| test.py:189 | FALSEY | falsey |
4545
| test.py:195 | SOURCE | simple.test |
46+
| test.py:202 | ITERABLE_SOURCE | iterable.simple |
47+
| test.py:208 | SOURCE | simple.test |

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

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,9 @@
5959
| Taint [simple.test] | test.py:170 | l | | --> | Taint [simple.test] | test.py:172 | x | |
6060
| Taint [simple.test] | test.py:172 | x | | --> | Taint simple.test | test.py:172 | Subscript | |
6161
| Taint [simple.test] | test.py:174 | l | | --> | Taint [simple.test] | test.py:174 | list() | |
62+
| Taint [simple.test] | test.py:208 | List | | --> | Taint [simple.test] | test.py:209 | seq | |
63+
| Taint [simple.test] | test.py:209 | seq | | --> | Taint simple.test | test.py:209 | i | |
64+
| Taint [simple.test] | test.py:213 | flow_in_generator() | | --> | Taint simple.test | test.py:213 | x | |
6265
| Taint basic.custom | test.py:72 | arg | test.py:121 | --> | Taint basic.custom | test.py:73 | arg | test.py:121 |
6366
| Taint basic.custom | test.py:73 | arg | test.py:121 | --> | Taint basic.custom | test.py:121 | hub() | |
6467
| Taint basic.custom | test.py:120 | CUSTOM_SOURCE | | --> | Taint basic.custom | test.py:121 | t | |
@@ -83,6 +86,8 @@
8386
| Taint explicit.carrier | carrier.py:34 | Attribute | | --> | Taint explicit.carrier | carrier.py:35 | x | |
8487
| Taint explicit.carrier | carrier.py:35 | x | | --> | Taint simple.test | carrier.py:35 | Attribute() | |
8588
| Taint falsey | test.py:189 | FALSEY | | --> | Taint falsey | test.py:190 | t | |
89+
| Taint iterable.simple | test.py:202 | ITERABLE_SOURCE | | --> | Taint iterable.simple | test.py:203 | t | |
90+
| Taint iterable.simple | test.py:203 | t | | --> | Taint simple.test | test.py:203 | i | |
8691
| Taint paper | rockpaperscissors.py:25 | Attribute() | | --> | Taint paper | rockpaperscissors.py:26 | y | |
8792
| Taint paper | rockpaperscissors.py:26 | y | | --> | Taint paper | rockpaperscissors.py:9 | arg | rockpaperscissors.py:26 |
8893
| Taint paper | rockpaperscissors.py:30 | Attribute() | | --> | Taint paper | rockpaperscissors.py:32 | y | |
@@ -181,6 +186,12 @@
181186
| Taint simple.test | test.py:195 | SOURCE | | --> | Taint simple.test | test.py:196 | t | |
182187
| Taint simple.test | test.py:195 | SOURCE | | --> | Taint simple.test | test.py:197 | t | |
183188
| Taint simple.test | test.py:195 | SOURCE | | --> | Taint simple.test | test.py:199 | t | |
189+
| Taint simple.test | test.py:203 | i | | --> | Taint simple.test | test.py:204 | i | |
190+
| Taint simple.test | test.py:203 | i | | --> | Taint simple.test | test.py:205 | i | |
191+
| Taint simple.test | test.py:208 | SOURCE | | --> | Taint [simple.test] | test.py:208 | List | |
192+
| Taint simple.test | test.py:209 | i | | --> | Taint simple.test | test.py:210 | i | |
193+
| Taint simple.test | test.py:210 | i | | --> | Taint [simple.test] | test.py:213 | flow_in_generator() | |
194+
| Taint simple.test | test.py:213 | x | | --> | Taint simple.test | test.py:214 | x | |
184195
| Taint {simple.test} | test.py:169 | Dict | | --> | Taint {simple.test} | test.py:171 | d | |
185196
| Taint {simple.test} | test.py:169 | Dict | | --> | Taint {simple.test} | test.py:175 | d | |
186197
| Taint {simple.test} | test.py:171 | d | | --> | Taint {simple.test} | test.py:173 | y | |

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

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -182,3 +182,12 @@
182182
| test.py:197 | t_2 | test.py:195 | Taint simple.test | SOURCE |
183183
| test.py:199 | t_3 | test.py:195 | Taint simple.test | SOURCE |
184184
| test.py:199 | t_4 | test.py:195 | Taint simple.test | SOURCE |
185+
| test.py:202 | t_0 | test.py:202 | Taint iterable.simple | ITERABLE_SOURCE |
186+
| test.py:203 | i_1 | test.py:203 | Taint simple.test | i |
187+
| test.py:203 | i_2 | test.py:203 | Taint simple.test | i |
188+
| test.py:208 | seq_0 | test.py:208 | Taint [simple.test] | List |
189+
| test.py:209 | i_1 | test.py:209 | Taint simple.test | i |
190+
| test.py:209 | i_2 | test.py:209 | Taint simple.test | i |
191+
| test.py:213 | x_0 | test.py:213 | Taint simple.test | x |
192+
| test.py:213 | x_1 | test.py:213 | Taint simple.test | x |
193+
| test.py:214 | x_2 | test.py:213 | Taint simple.test | x |

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

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -198,3 +198,18 @@ def flow_through_type_test_if_no_class():
198198
else:
199199
SINK(t)
200200

201+
def flow_in_iteration():
202+
t = ITERABLE_SOURCE
203+
for i in t:
204+
i
205+
return i
206+
207+
def flow_in_generator():
208+
seq = [SOURCE]
209+
for i in seq:
210+
yield i
211+
212+
def flow_from_generator():
213+
for x in flow_in_generator():
214+
SINK(x)
215+

0 commit comments

Comments
 (0)