Skip to content

Commit 47b2d48

Browse files
committed
python: add tests
- add `getACallSimple` to `SummarizedCallable` (by adding it to `LibraryCallable`)
1 parent 16bc584 commit 47b2d48

File tree

5 files changed

+288
-0
lines changed

5 files changed

+288
-0
lines changed

python/ql/lib/semmle/python/dataflow/new/internal/DataFlowDispatch.qll

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -250,6 +250,9 @@ abstract class LibraryCallable extends string {
250250
/** Gets a call to this library callable. */
251251
abstract CallCfgNode getACall();
252252

253+
/** Same as `getACall` but without referring to the call graph or API graph. */
254+
CallCfgNode getACallSimple() { none() }
255+
253256
/** Gets a data-flow node, where this library callable is used as a call-back. */
254257
abstract ArgumentNode getACallback();
255258
}
Lines changed: 189 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,189 @@
1+
private import python
2+
private import semmle.python.dataflow.new.FlowSummary
3+
private import semmle.python.ApiGraphs
4+
5+
/**
6+
* This module ensures that the `callStep` predicate in
7+
* our type tracker implelemtation does not refer to the
8+
* `getACall` predicate on `SummarizedCallable`.
9+
*/
10+
module RecursionGuard {
11+
private import semmle.python.dataflow.new.internal.TypeTrackerSpecific as TT
12+
13+
private class RecursionGuard extends SummarizedCallable {
14+
RecursionGuard() { this = "RecursionGuard" }
15+
16+
override DataFlow::CallCfgNode getACall() {
17+
result.getFunction().asCfgNode().(NameNode).getId() = this and
18+
(TT::callStep(_, _) implies any())
19+
}
20+
21+
override DataFlow::CallCfgNode getACallSimple() { none() }
22+
23+
override DataFlow::ArgumentNode getACallback() { result.asExpr().(Name).getId() = this }
24+
}
25+
26+
predicate test(DataFlow::Node nodeFrom, DataFlow::Node nodeTo) {
27+
TT::levelStepNoCall(nodeFrom, nodeTo)
28+
}
29+
}
30+
31+
private class SummarizedCallableIdentity extends SummarizedCallable {
32+
SummarizedCallableIdentity() { this = "identity" }
33+
34+
override DataFlow::CallCfgNode getACall() { none() }
35+
36+
override DataFlow::CallCfgNode getACallSimple() {
37+
result.getFunction().asCfgNode().(NameNode).getId() = this
38+
}
39+
40+
override DataFlow::ArgumentNode getACallback() { result.asExpr().(Name).getId() = this }
41+
42+
override predicate propagatesFlowExt(string input, string output, boolean preservesValue) {
43+
input = "Argument[0]" and
44+
output = "ReturnValue" and
45+
preservesValue = true
46+
}
47+
}
48+
49+
// For lambda flow to work, implement lambdaCall and lambdaCreation
50+
private class SummarizedCallableApplyLambda extends SummarizedCallable {
51+
SummarizedCallableApplyLambda() { this = "apply_lambda" }
52+
53+
override DataFlow::CallCfgNode getACall() { none() }
54+
55+
override DataFlow::CallCfgNode getACallSimple() {
56+
result.getFunction().asCfgNode().(NameNode).getId() = this
57+
}
58+
59+
override DataFlow::ArgumentNode getACallback() { result.asExpr().(Name).getId() = this }
60+
61+
override predicate propagatesFlowExt(string input, string output, boolean preservesValue) {
62+
input = "Argument[1]" and
63+
output = "Argument[0].Parameter[0]" and
64+
preservesValue = true
65+
or
66+
input = "Argument[0].ReturnValue" and
67+
output = "ReturnValue" and
68+
preservesValue = true
69+
}
70+
}
71+
72+
private class SummarizedCallableReversed extends SummarizedCallable {
73+
SummarizedCallableReversed() { this = "reversed" }
74+
75+
override DataFlow::CallCfgNode getACall() { none() }
76+
77+
override DataFlow::CallCfgNode getACallSimple() {
78+
result.getFunction().asCfgNode().(NameNode).getId() = this
79+
}
80+
81+
override DataFlow::ArgumentNode getACallback() { result.asExpr().(Name).getId() = this }
82+
83+
override predicate propagatesFlowExt(string input, string output, boolean preservesValue) {
84+
input = "Argument[0].ListElement" and
85+
output = "ReturnValue.ListElement" and
86+
preservesValue = true
87+
}
88+
}
89+
90+
private class SummarizedCallableMap extends SummarizedCallable {
91+
SummarizedCallableMap() { this = "list_map" }
92+
93+
override DataFlow::CallCfgNode getACall() { none() }
94+
95+
override DataFlow::CallCfgNode getACallSimple() {
96+
result.getFunction().asCfgNode().(NameNode).getId() = this
97+
}
98+
99+
override DataFlow::ArgumentNode getACallback() { result.asExpr().(Name).getId() = this }
100+
101+
override predicate propagatesFlowExt(string input, string output, boolean preservesValue) {
102+
input = "Argument[1].ListElement" and
103+
output = "Argument[0].Parameter[0]" and
104+
preservesValue = true
105+
or
106+
input = "Argument[0].ReturnValue" and
107+
output = "ReturnValue.ListElement" and
108+
preservesValue = true
109+
}
110+
}
111+
112+
private class SummarizedCallableAppend extends SummarizedCallable {
113+
SummarizedCallableAppend() { this = "append_to_list" }
114+
115+
override DataFlow::CallCfgNode getACall() { none() }
116+
117+
override DataFlow::CallCfgNode getACallSimple() {
118+
result.getFunction().asCfgNode().(NameNode).getId() = this
119+
}
120+
121+
override DataFlow::ArgumentNode getACallback() { result.asExpr().(Name).getId() = this }
122+
123+
override predicate propagatesFlowExt(string input, string output, boolean preservesValue) {
124+
input = "Argument[0]" and
125+
output = "ReturnValue" and
126+
preservesValue = false
127+
or
128+
input = "Argument[1]" and
129+
output = "ReturnValue.ListElement" and
130+
preservesValue = true
131+
}
132+
}
133+
134+
private class SummarizedCallableJsonLoads extends SummarizedCallable {
135+
SummarizedCallableJsonLoads() { this = "json.loads" }
136+
137+
override DataFlow::CallCfgNode getACall() {
138+
result = API::moduleImport("json").getMember("loads").getACall()
139+
}
140+
141+
override DataFlow::CallCfgNode getACallSimple() { none() }
142+
143+
override DataFlow::ArgumentNode getACallback() {
144+
result = API::moduleImport("json").getMember("loads").getAValueReachableFromSource()
145+
}
146+
147+
override predicate propagatesFlowExt(string input, string output, boolean preservesValue) {
148+
input = "Argument[0]" and
149+
output = "ReturnValue.ListElement" and
150+
preservesValue = true
151+
}
152+
}
153+
154+
// read and store
155+
private class SummarizedCallableReadSecret extends SummarizedCallable {
156+
SummarizedCallableReadSecret() { this = "read_secret" }
157+
158+
override DataFlow::CallCfgNode getACall() { none() }
159+
160+
override DataFlow::CallCfgNode getACallSimple() {
161+
result.getFunction().asCfgNode().(NameNode).getId() = this
162+
}
163+
164+
override DataFlow::ArgumentNode getACallback() { result.asExpr().(Name).getId() = this }
165+
166+
override predicate propagatesFlowExt(string input, string output, boolean preservesValue) {
167+
input = "Argument[0].Attribute[secret]" and
168+
output = "ReturnValue" and
169+
preservesValue = true
170+
}
171+
}
172+
173+
private class SummarizedCallableSetSecret extends SummarizedCallable {
174+
SummarizedCallableSetSecret() { this = "set_secret" }
175+
176+
override DataFlow::CallCfgNode getACall() { none() }
177+
178+
override DataFlow::CallCfgNode getACallSimple() {
179+
result.getFunction().asCfgNode().(NameNode).getId() = this
180+
}
181+
182+
override DataFlow::ArgumentNode getACallback() { result.asExpr().(Name).getId() = this }
183+
184+
override predicate propagatesFlowExt(string input, string output, boolean preservesValue) {
185+
input = "Argument[1]" and
186+
output = "Argument[0].Attribute[secret]" and
187+
preservesValue = true
188+
}
189+
}
Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
import sys
2+
import os
3+
4+
# Simple summary
5+
tainted = identity(tracked) # $ tracked
6+
tainted # $ MISSING: tracked
7+
8+
# Lambda summary
9+
# I think the missing result is expected because type tracking
10+
# is not allowed to flow back out of a call.
11+
tainted_lambda = apply_lambda(lambda x: x, tracked) # $ tracked
12+
tainted_lambda # $ MISSING: tracked
13+
14+
# A lambda that directly introduces taint
15+
bad_lambda = apply_lambda(lambda x: tracked, 1) # $ tracked
16+
bad_lambda # $ MISSING: tracked
17+
18+
# A lambda that breaks the flow
19+
untainted_lambda = apply_lambda(lambda x: 1, tracked) # $ tracked
20+
untainted_lambda
21+
22+
# Collection summaries
23+
tainted_list = reversed([tracked]) # $ tracked
24+
tl = tainted_list[0]
25+
tl # $ MISSING: tracked
26+
27+
# Complex summaries
28+
def add_colon(x):
29+
return x + ":"
30+
31+
tainted_mapped = list_map(add_colon, [tracked]) # $ tracked
32+
tm = tainted_mapped[0]
33+
tm # $ MISSING: tracked
34+
35+
def explicit_identity(x):
36+
return x
37+
38+
tainted_mapped_explicit = list_map(explicit_identity, [tracked]) # $ tracked
39+
tainted_mapped_explicit[0] # $ MISSING: tracked
40+
41+
tainted_mapped_summary = list_map(identity, [tracked]) # $ tracked
42+
tms = tainted_mapped_summary[0]
43+
tms # $ MISSING: tracked
44+
45+
another_tainted_list = append_to_list([], tracked) # $ tracked
46+
atl = another_tainted_list[0]
47+
atl # $ MISSING: tracked
48+
49+
from json import loads as json_loads
50+
tainted_resultlist = json_loads(tracked) # $ tracked
51+
tr = tainted_resultlist[0]
52+
tr # $ MISSING: tracked
53+
54+
x.secret = tracked # $ tracked=secret tracked
55+
r = read_secret(x) # $ tracked=secret MISSING: tracked
56+
r # $ MISSING: tracked
57+
58+
y # $ MISSING: tracked=secret
59+
set_secret(y, tracked) # $ tracked MISSING: tracked=secret
60+
y.secret # $ MISSING: tracked tracked=secret

python/ql/test/experimental/dataflow/typetracking-summaries/tracked.expected

Whitespace-only changes.
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
import python
2+
import semmle.python.dataflow.new.DataFlow
3+
import semmle.python.dataflow.new.TypeTracker
4+
import TestUtilities.InlineExpectationsTest
5+
import semmle.python.ApiGraphs
6+
import TestSummaries
7+
8+
// -----------------------------------------------------------------------------
9+
// tracked
10+
// -----------------------------------------------------------------------------
11+
private DataFlow::TypeTrackingNode tracked(TypeTracker t) {
12+
t.start() and
13+
result.asCfgNode() = any(NameNode n | n.getId() = "tracked")
14+
or
15+
exists(TypeTracker t2 | result = tracked(t2).track(t2, t))
16+
}
17+
18+
class TrackedTest extends InlineExpectationsTest {
19+
TrackedTest() { this = "TrackedTest" }
20+
21+
override string getARelevantTag() { result = "tracked" }
22+
23+
override predicate hasActualResult(Location location, string element, string tag, string value) {
24+
exists(DataFlow::Node e, TypeTracker t |
25+
exists(e.getLocation().getFile().getRelativePath()) and
26+
e.getLocation().getStartLine() > 0 and
27+
tracked(t).flowsTo(e) and
28+
// Module variables have no sensible location, and hence can't be annotated.
29+
not e instanceof DataFlow::ModuleVariableNode and
30+
tag = "tracked" and
31+
location = e.getLocation() and
32+
value = t.getAttr() and
33+
element = e.toString()
34+
)
35+
}
36+
}

0 commit comments

Comments
 (0)