Skip to content

Commit cac2618

Browse files
committed
Python: Don't report mutable parameters that are in fact immutable.
Fixes #1832. In the taint sink, we add an additional check that the given control-flow node can indeed point to a value that is mutable. This takes care of the guard on the type. If and when we get around to adding configurations for all of the taint analyses, we may want to implement this as a barrier instead, pruning any steps that go through a type test where the type is not mutable.
1 parent ed4657c commit cac2618

File tree

3 files changed

+42
-1
lines changed

3 files changed

+42
-1
lines changed

python/ql/src/Functions/ModificationOfParameterWithDefault.ql

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -73,14 +73,20 @@ class MutableDefaultValue extends TaintSource {
7373
}
7474
}
7575

76+
private ClassValue mutable_class() {
77+
result = Value::named("list") or
78+
result = Value::named("dict")
79+
}
80+
7681
class Mutation extends TaintSink {
7782
Mutation() {
7883
exists(AugAssign a | a.getTarget().getAFlowNode() = this)
7984
or
8085
exists(Call c, Attribute a |
8186
c.getFunc() = a |
8287
a.getObject().getAFlowNode() = this and
83-
not safe_method(a.getName())
88+
not safe_method(a.getName()) and
89+
this.(ControlFlowNode).pointsTo().getClass() = mutable_class()
8490
)
8591
}
8692

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

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,18 @@ edges
77
| functions_test.py:157:27:157:27 | empty mutable value | functions_test.py:159:21:159:21 | empty mutable value |
88
| functions_test.py:158:25:158:25 | empty mutable value | functions_test.py:151:25:151:25 | empty mutable value |
99
| functions_test.py:159:21:159:21 | empty mutable value | functions_test.py:154:21:154:21 | empty mutable value |
10+
| functions_test.py:175:28:175:28 | non-empty mutable value | functions_test.py:179:9:179:9 | non-empty mutable value |
11+
| functions_test.py:175:28:175:28 | non-empty mutable value | functions_test.py:181:9:181:9 | non-empty mutable value |
12+
| functions_test.py:188:18:188:18 | non-empty mutable value | functions_test.py:189:28:189:28 | non-empty mutable value |
13+
| functions_test.py:189:28:189:28 | non-empty mutable value | functions_test.py:175:28:175:28 | non-empty mutable value |
14+
| functions_test.py:191:18:191:18 | non-empty mutable value | functions_test.py:192:28:192:28 | non-empty mutable value |
15+
| functions_test.py:192:28:192:28 | non-empty mutable value | functions_test.py:175:28:175:28 | non-empty mutable value |
1016
#select
1117
| functions_test.py:40:5:40:5 | x | functions_test.py:39:9:39:9 | empty mutable value | functions_test.py:40:5:40:5 | empty mutable value | $@ flows to here and is mutated. | functions_test.py:39:9:39:9 | x | Default value |
1218
| functions_test.py:134:5:134:5 | x | functions_test.py:133:15:133:15 | empty mutable value | functions_test.py:134:5:134:5 | empty mutable value | $@ flows to here and is mutated. | functions_test.py:133:15:133:15 | x | Default value |
1319
| functions_test.py:152:5:152:5 | x | functions_test.py:157:27:157:27 | empty mutable value | functions_test.py:152:5:152:5 | empty mutable value | $@ flows to here and is mutated. | functions_test.py:157:27:157:27 | y | Default value |
1420
| functions_test.py:155:5:155:5 | x | functions_test.py:157:27:157:27 | empty mutable value | functions_test.py:155:5:155:5 | empty mutable value | $@ flows to here and is mutated. | functions_test.py:157:27:157:27 | y | Default value |
21+
| functions_test.py:179:9:179:9 | x | functions_test.py:188:18:188:18 | non-empty mutable value | functions_test.py:179:9:179:9 | non-empty mutable value | $@ flows to here and is mutated. | functions_test.py:188:18:188:18 | x | Default value |
22+
| functions_test.py:179:9:179:9 | x | functions_test.py:191:18:191:18 | non-empty mutable value | functions_test.py:179:9:179:9 | non-empty mutable value | $@ flows to here and is mutated. | functions_test.py:191:18:191:18 | x | Default value |
23+
| functions_test.py:181:9:181:9 | x | functions_test.py:188:18:188:18 | non-empty mutable value | functions_test.py:181:9:181:9 | non-empty mutable value | $@ flows to here and is mutated. | functions_test.py:188:18:188:18 | x | Default value |
24+
| functions_test.py:181:9:181:9 | x | functions_test.py:191:18:191:18 | non-empty mutable value | functions_test.py:181:9:181:9 | non-empty mutable value | $@ flows to here and is mutated. | functions_test.py:191:18:191:18 | x | Default value |

python/ql/test/query-tests/Functions/general/functions_test.py

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -168,3 +168,28 @@ def issue1143(expr, param=[]):
168168
return result
169169
for i in param:
170170
param.remove(i) # Mutation here
171+
172+
173+
# Type guarding of modification of parameter with default:
174+
175+
def do_stuff_based_on_type(x):
176+
if isinstance(x, str):
177+
x = x.split()
178+
elif isinstance(x, dict):
179+
x.setdefault('foo', 'bar')
180+
elif isinstance(x, list):
181+
x.append(5)
182+
elif isinstance(x, tuple):
183+
x = x.unknown_method()
184+
185+
def str_default(x="hello world"):
186+
do_stuff_based_on_type(x)
187+
188+
def dict_default(x={'baz':'quux'}):
189+
do_stuff_based_on_type(x)
190+
191+
def list_default(x=[1,2,3,4]):
192+
do_stuff_based_on_type(x)
193+
194+
def tuple_default(x=(1,2)):
195+
do_stuff_based_on_type(x)

0 commit comments

Comments
 (0)