Skip to content

Commit b67694b

Browse files
yoffowen-mc
authored andcommitted
Python: Remove imprecise container steps
- remove `tupleStoreStep` and `dictStoreStep` from `containerStep` These are imprecise compared to the content being precise. - add implicit reads to recover taint at sinks - add implicit read steps for decoders to supplement the `AdditionalTaintStep` that now only covers when the full container is tainted.
1 parent 7e6b10e commit b67694b

16 files changed

Lines changed: 182 additions & 55 deletions

File tree

python/ql/consistency-queries/DataFlowConsistency.ql

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,8 @@ private module Input implements InputSig<Location, PythonDataFlow> {
3636
// parameter, but dataflow-consistency queries should _not_ complain about there not
3737
// being a post-update node for the synthetic `**kwargs` parameter.
3838
n instanceof SynthDictSplatParameterNode
39+
or
40+
Private::Conversions::readStep(n, _, _)
3941
}
4042

4143
predicate uniqueParameterNodePositionExclude(DataFlowCallable c, ParameterPosition pos, Node p) {

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

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1009,6 +1009,8 @@ predicate readStep(Node nodeFrom, ContentSet c, Node nodeTo) {
10091009
synthDictSplatParameterNodeReadStep(nodeFrom, c, nodeTo)
10101010
or
10111011
VariableCapture::readStep(nodeFrom, c, nodeTo)
1012+
or
1013+
Conversions::readStep(nodeFrom, c, nodeTo)
10121014
}
10131015

10141016
/** Data flows from a sequence to a subscript of the sequence. */
@@ -1064,6 +1066,40 @@ predicate attributeReadStep(Node nodeFrom, AttributeContent c, AttrRead nodeTo)
10641066
nodeTo.accesses(nodeFrom, c.getAttribute())
10651067
}
10661068

1069+
module Conversions {
1070+
private import semmle.python.Concepts
1071+
1072+
predicate decoderReadStep(Node nodeFrom, ContentSet c, Node nodeTo) {
1073+
exists(Decoding decoding |
1074+
nodeFrom = decoding.getAnInput() and
1075+
nodeTo = decoding.getOutput()
1076+
) and
1077+
(
1078+
c instanceof TupleElementContent
1079+
or
1080+
c instanceof DictionaryElementContent
1081+
)
1082+
}
1083+
1084+
predicate encoderReadStep(Node nodeFrom, ContentSet c, Node nodeTo) {
1085+
exists(Encoding encoding |
1086+
nodeFrom = encoding.getAnInput() and
1087+
nodeTo = encoding.getOutput()
1088+
) and
1089+
(
1090+
c instanceof TupleElementContent
1091+
or
1092+
c instanceof DictionaryElementContent
1093+
)
1094+
}
1095+
1096+
predicate readStep(Node nodeFrom, ContentSet c, Node nodeTo) {
1097+
decoderReadStep(nodeFrom, c, nodeTo)
1098+
or
1099+
encoderReadStep(nodeFrom, c, nodeTo)
1100+
}
1101+
}
1102+
10671103
/**
10681104
* Holds if values stored inside content `c` are cleared at node `n`. For example,
10691105
* any value stored inside `f` is cleared at the pre-update node associated with `x`

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

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,16 @@ predicate defaultTaintSanitizer(DataFlow::Node node) { none() }
1616
* of `c` at sinks and inputs to additional taint steps.
1717
*/
1818
bindingset[node]
19-
predicate defaultImplicitTaintRead(DataFlow::Node node, DataFlow::ContentSet c) { none() }
19+
predicate defaultImplicitTaintRead(DataFlow::Node node, DataFlow::ContentSet c) {
20+
// We allow implicit reads of precise content
21+
// imprecise content has already bubled up.
22+
exists(node) and
23+
(
24+
c instanceof DataFlow::TupleElementContent
25+
or
26+
c instanceof DataFlow::DictionaryElementContent
27+
)
28+
}
2029

2130
private module Cached {
2231
/**
@@ -176,10 +185,6 @@ predicate containerStep(DataFlow::Node nodeFrom, DataFlow::Node nodeTo) {
176185
or
177186
DataFlowPrivate::setStoreStep(nodeFrom, _, nodeTo)
178187
or
179-
DataFlowPrivate::tupleStoreStep(nodeFrom, _, nodeTo)
180-
or
181-
DataFlowPrivate::dictStoreStep(nodeFrom, _, nodeTo)
182-
or
183188
// comprehension, so there is taint-flow from `x` in `[x for x in xs]` to the
184189
// resulting list of the list-comprehension.
185190
//

python/ql/test/library-tests/dataflow/sensitive-data/test.py

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -131,6 +131,5 @@ def call_wrapper(func):
131131
print(password) # $ SensitiveUse=password
132132
_config = {"sleep_timer": 5, "mysql_password": password}
133133

134-
# since we have taint-step from store of `password`, we will consider any item in the
135-
# dictionary to be a password :(
136-
print(_config["sleep_timer"]) # $ SPURIOUS: SensitiveUse=password
134+
# since we have precise dictionary content, other items of the config are not tainted
135+
print(_config["sleep_timer"])

python/ql/test/library-tests/dataflow/tainttracking/defaultAdditionalTaintStep-py3/test_string.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ def str_methods():
1717
ts.casefold(), # $ tainted
1818

1919
ts.format_map({}), # $ tainted
20-
"{unsafe}".format_map({"unsafe": ts}), # $ tainted
20+
"{unsafe}".format_map({"unsafe": ts}), # $ MISSING: tainted
2121
)
2222

2323

python/ql/test/library-tests/dataflow/tainttracking/defaultAdditionalTaintStep/test_collections.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -29,10 +29,10 @@ def test_construction():
2929

3030
ensure_tainted(
3131
list(tainted_list), # $ tainted
32-
list(tainted_tuple), # $ tainted
32+
list(tainted_tuple), # $ MISSING: tainted
3333
list(tainted_set), # $ tainted
34-
list(tainted_dict.values()), # $ tainted
35-
list(tainted_dict.items()), # $ tainted
34+
list(tainted_dict.values()), # $ MISSING: tainted
35+
list(tainted_dict.items()), # $ MISSING: tainted
3636

3737
tuple(tainted_list), # $ tainted
3838
set(tainted_list), # $ tainted

python/ql/test/library-tests/dataflow/tainttracking/defaultAdditionalTaintStep/test_string.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -115,7 +115,7 @@ def percent_fmt():
115115
ensure_tainted(
116116
tainted_fmt % (1, 2), # $ tainted
117117
"%s foo bar" % ts, # $ tainted
118-
"%s %s %s" % (1, 2, ts), # $ tainted
118+
"%s %s %s" % (1, 2, ts), # $ MISSING: tainted
119119
)
120120

121121

python/ql/test/library-tests/dataflow/tainttracking/defaultAdditionalTaintStep/test_unpacking.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,7 @@ def contrived_1():
5353

5454
(a, b, c), (d, e, f) = tainted_list, no_taint_list
5555
ensure_tainted(a, b, c) # $ tainted
56-
ensure_not_tainted(d, e, f) # $ SPURIOUS: tainted
56+
ensure_not_tainted(d, e, f)
5757

5858

5959
def contrived_2():

python/ql/test/library-tests/frameworks/stdlib/test_re.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -80,9 +80,9 @@
8080
)
8181

8282
ensure_not_tainted(
83-
re.subn(pat, repl="safe", string=ts),
8483
re.subn(pat, repl="safe", string=ts)[1], # // the number of substitutions made
8584
)
8685
ensure_tainted(
86+
re.subn(pat, repl="safe", string=ts), # $ tainted // implicit read at sink
8787
re.subn(pat, repl="safe", string=ts)[0], # $ tainted // the string
8888
)

python/ql/test/library-tests/frameworks/tornado/taint_test.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,7 @@ def get(self, name = "World!", number="0", foo="foo"): # $ requestHandler route
6363
request.headers["header-name"], # $ tainted
6464
request.headers.get_list("header-name"), # $ tainted
6565
request.headers.get_all(), # $ tainted
66-
[(k, v) for (k, v) in request.headers.get_all()], # $ tainted
66+
[(k, v) for (k, v) in request.headers.get_all()], # $ MISSING: tainted
6767

6868
# Dict[str, http.cookies.Morsel]
6969
request.cookies, # $ tainted

0 commit comments

Comments
 (0)