Skip to content

Commit 081d66e

Browse files
committed
Python: Recognize taint for extended iterable unpacking
1 parent 1b67035 commit 081d66e

File tree

5 files changed

+34
-6
lines changed

5 files changed

+34
-6
lines changed

python/ql/src/semmle/python/Flow.qll

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1068,6 +1068,17 @@ class NameConstantNode extends NameNode {
10681068
*/
10691069
}
10701070

1071+
/** A control flow node correspoinding to a starred expression, `*a`. */
1072+
class StarredNode extends ControlFlowNode {
1073+
StarredNode() {
1074+
toAst(this) instanceof Starred
1075+
}
1076+
1077+
ControlFlowNode getValue() {
1078+
toAst(result) = toAst(this).(Starred).getValue()
1079+
}
1080+
}
1081+
10711082
private module Scopes {
10721083

10731084
private predicate fast_local(NameNode n) {

python/ql/src/semmle/python/dataflow/Implementation.qll

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -727,7 +727,10 @@ private class EssaTaintTracking extends string {
727727
SequenceNode left_parent, ControlFlowNode left_defn, CollectionKind parent_kind
728728
) {
729729
left_parent.getAnElement() = left_defn and
730-
result = parent_kind.getMember()
730+
// Handle `a, *b = some_iterable`
731+
if left_defn instanceof StarredNode
732+
then result = parent_kind
733+
else result = parent_kind.getMember()
731734
or
732735
result = iterable_unpacking_decent(left_parent.getAnElement(), left_defn,
733736
parent_kind.getMember())

python/ql/src/semmle/python/essa/SsaDefinitions.qll

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,11 @@ cached module SsaSource {
3939

4040
/** Holds if `v` is defined by multiple assignment at `defn`. */
4141
cached predicate multi_assignment_definition(Variable v, ControlFlowNode defn, int n, SequenceNode lhs) {
42-
defn.(NameNode).defines(v) and
42+
(
43+
defn.(NameNode).defines(v)
44+
or
45+
defn.(StarredNode).getValue().(NameNode).defines(v)
46+
) and
4347
not exists(defn.(DefinitionNode).getValue()) and
4448
lhs.getElement(n) = defn and
4549
lhs.getBasicBlock().dominates(defn.getBasicBlock())
Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
11
| test.py:11 | extended_unpacking | first | externally controlled string |
22
| test.py:11 | extended_unpacking | last | externally controlled string |
3-
| test.py:11 | extended_unpacking | rest | NO TAINT |
4-
| test.py:16 | also_allowed | a | NO TAINT |
3+
| test.py:11 | extended_unpacking | rest | [externally controlled string] |
4+
| test.py:16 | also_allowed | a | [externally controlled string] |
55
| test.py:24 | also_allowed | b | NO TAINT |
66
| test.py:24 | also_allowed | c | NO TAINT |
7+
| test.py:31 | nested | x | externally controlled string |
8+
| test.py:31 | nested | xs | [externally controlled string] |
9+
| test.py:31 | nested | ys | [externally controlled string] |

python/ql/test/3/library-tests/taint/unpacking/test.py

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,12 +8,12 @@ def test(*args):
88

99
def extended_unpacking():
1010
first, *rest, last = TAINTED_LIST
11-
test(first, rest, last) # TODO: mark `rest` as [taint]
11+
test(first, rest, last)
1212

1313

1414
def also_allowed():
1515
*a, = TAINTED_LIST
16-
test(a) # TODO: mark `a` as [taint]
16+
test(a)
1717

1818
# for b, *c in [(1, 2, 3), (4, 5, 6, 7)]:
1919
# print(c)
@@ -22,3 +22,10 @@ def also_allowed():
2222

2323
for b, *c in [TAINTED_LIST, TAINTED_LIST]:
2424
test(b, c) # TODO: mark `c` as [taint]
25+
26+
def nested():
27+
l = TAINTED_LIST
28+
ll = [l,l]
29+
30+
[[x, *xs], ys] = ll
31+
test(x, xs, ys)

0 commit comments

Comments
 (0)