Skip to content

Commit 8a16164

Browse files
authored
Merge pull request #878 from taus-semmle/python-mutable-default-with-flow
Python: Make "Modification of parameter with default" flow-sensitive.
2 parents 51e5a30 + 64e6974 commit 8a16164

File tree

4 files changed

+72
-27
lines changed

4 files changed

+72
-27
lines changed

change-notes/1.20/analysis-python.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ The API has been improved to declutter the global namespace and improve discover
2828
| **Query** | **Expected impact** | **Change** |
2929
|----------------------------|------------------------|------------------------------------------------------------------|
3030
| Comparison using is when operands support \_\_eq\_\_ (`py/comparison-using-is`) | Fewer false positive results | Results where one of the objects being compared is an enum member are no longer reported. |
31+
| Modification of parameter with default (`py/modification-of-default-value`) | More true positive results | Instances where the mutable default value is mutated inside other functions are now also reported. |
3132
| Mutation of descriptor in \_\_get\_\_ or \_\_set\_\_ method (`py/mutable-descriptor`) | Fewer false positive results | Results where the mutation does not occur when calling one of the `__get__`, `__set__` or `__delete__` methods are no longer reported. |
3233
| Unused import (`py/unused-import`) | Fewer false positive results | Results where the imported module is used in a `doctest` string are no longer reported. |
3334
| Unused import (`py/unused-import`) | Fewer false positive results | Results where the imported module is used in a type-hint comment are no longer reported. |

python/ql/src/Functions/ModificationOfParameterWithDefault.ql

Lines changed: 41 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
* @name Modification of parameter with default
33
* @description Modifying the default value of a parameter can lead to unexpected
44
* results.
5-
* @kind problem
5+
* @kind path-problem
66
* @tags reliability
77
* maintainability
88
* @problem.severity error
@@ -12,6 +12,7 @@
1212
*/
1313

1414
import python
15+
import semmle.python.security.Paths
1516

1617
predicate safe_method(string name) {
1718
name = "count" or name = "index" or name = "copy" or name = "get" or name = "has_key" or
@@ -23,27 +24,6 @@ predicate maybe_parameter(SsaVariable var, Function f, Parameter p) {
2324
f.getAnArg() = p
2425
}
2526

26-
Name use_of_parameter(Parameter p) {
27-
exists(SsaVariable var |
28-
p = var.getAnUltimateDefinition().getDefinition().getNode() and
29-
var.getAUse().getNode() = result
30-
)
31-
}
32-
33-
predicate modifying_call(Call c, Parameter p) {
34-
exists(Attribute a |
35-
c.getFunc() = a |
36-
a.getObject() = use_of_parameter(p) and
37-
not safe_method(a.getName())
38-
)
39-
}
40-
41-
predicate is_modification(AstNode a, Parameter p) {
42-
modifying_call(a, p)
43-
or
44-
a.(AugAssign).getTarget() = use_of_parameter(p)
45-
}
46-
4727
predicate has_mutable_default(Parameter p) {
4828
exists(SsaVariable v, FunctionExpr f | maybe_parameter(v, f.getInnerScope(), p) and
4929
exists(int i, int def_cnt, int arg_cnt |
@@ -56,6 +36,42 @@ predicate has_mutable_default(Parameter p) {
5636
)
5737
}
5838

59-
from AstNode a, Parameter p
60-
where has_mutable_default(p) and is_modification(a, p)
61-
select a, "Modification of parameter $@, which has mutable default value.", p, p.asName().getId()
39+
class MutableValue extends TaintKind {
40+
MutableValue() {
41+
this = "mutable value"
42+
}
43+
}
44+
45+
class MutableDefaultValue extends TaintSource {
46+
MutableDefaultValue() {
47+
has_mutable_default(this.(NameNode).getNode())
48+
}
49+
50+
override string toString() {
51+
result = "mutable default value"
52+
}
53+
54+
override predicate isSourceOf(TaintKind kind) {
55+
kind instanceof MutableValue
56+
}
57+
}
58+
59+
class Mutation extends TaintSink {
60+
Mutation() {
61+
exists(AugAssign a | a.getTarget().getAFlowNode() = this)
62+
or
63+
exists(Call c, Attribute a |
64+
c.getFunc() = a |
65+
a.getObject().getAFlowNode() = this and
66+
not safe_method(a.getName())
67+
)
68+
}
69+
70+
override predicate sinks(TaintKind kind) {
71+
kind instanceof MutableValue
72+
}
73+
}
74+
75+
from TaintedPathSource src, TaintedPathSink sink
76+
where src.flowsTo(sink)
77+
select sink.getSink(), src, sink, "$@ flows to here and is mutated.", src.getSource(), "Default value"
Lines changed: 20 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,20 @@
1-
| functions_test.py:40:5:40:17 | Attribute() | Modification of parameter $@, which has mutable default value. | functions_test.py:39:9:39:9 | Parameter | x |
2-
| functions_test.py:239:5:239:14 | AugAssign | Modification of parameter $@, which has mutable default value. | functions_test.py:238:15:238:15 | Parameter | x |
1+
edges
2+
| functions_test.py:36:9:36:9 | mutable value | functions_test.py:37:16:37:16 | mutable value |
3+
| functions_test.py:39:9:39:9 | mutable value | functions_test.py:40:5:40:5 | mutable value |
4+
| functions_test.py:238:15:238:15 | mutable value | functions_test.py:239:5:239:5 | mutable value |
5+
| functions_test.py:290:25:290:25 | mutable value | functions_test.py:291:5:291:5 | mutable value |
6+
| functions_test.py:293:21:293:21 | mutable value | functions_test.py:294:5:294:5 | mutable value |
7+
| functions_test.py:296:27:296:27 | mutable value | functions_test.py:297:25:297:25 | mutable value |
8+
| functions_test.py:296:27:296:27 | mutable value | functions_test.py:298:21:298:21 | mutable value |
9+
| functions_test.py:297:25:297:25 | mutable value | functions_test.py:290:25:290:25 | mutable value |
10+
| functions_test.py:298:21:298:21 | mutable value | functions_test.py:293:21:293:21 | mutable value |
11+
parents
12+
| functions_test.py:290:25:290:25 | mutable value | functions_test.py:297:25:297:25 | mutable value |
13+
| functions_test.py:291:5:291:5 | mutable value | functions_test.py:297:25:297:25 | mutable value |
14+
| functions_test.py:293:21:293:21 | mutable value | functions_test.py:298:21:298:21 | mutable value |
15+
| functions_test.py:294:5:294:5 | mutable value | functions_test.py:298:21:298:21 | mutable value |
16+
#select
17+
| functions_test.py:40:5:40:5 | Taint sink | functions_test.py:39:9:39:9 | mutable value | functions_test.py:40:5:40:5 | mutable value | $@ flows to here and is mutated. | functions_test.py:39:9:39:9 | mutable default value | Default value |
18+
| functions_test.py:239:5:239:5 | Taint sink | functions_test.py:238:15:238:15 | mutable value | functions_test.py:239:5:239:5 | mutable value | $@ flows to here and is mutated. | functions_test.py:238:15:238:15 | mutable default value | Default value |
19+
| functions_test.py:291:5:291:5 | Taint sink | functions_test.py:296:27:296:27 | mutable value | functions_test.py:291:5:291:5 | mutable value | $@ flows to here and is mutated. | functions_test.py:296:27:296:27 | mutable default value | Default value |
20+
| functions_test.py:294:5:294:5 | Taint sink | functions_test.py:296:27:296:27 | mutable value | functions_test.py:294:5:294:5 | mutable value | $@ flows to here and is mutated. | functions_test.py:296:27:296:27 | mutable default value | Default value |

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

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -286,3 +286,13 @@ def meth(arg):
286286

287287
Z().meth(0)
288288

289+
# indirect modification of parameter with default
290+
def aug_assign_argument(x):
291+
x += ['x']
292+
293+
def mutate_argument(x):
294+
x.append('x')
295+
296+
def indirect_modification(y = []):
297+
aug_assign_argument(y)
298+
mutate_argument(y)

0 commit comments

Comments
 (0)