Skip to content

Commit f4f47bd

Browse files
authored
Merge pull request #4236 from RasmusWL/python-experimental-taint-sanitizers
Python: Expand on taint sanitizer tests
2 parents 17ccc13 + b8e057f commit f4f47bd

File tree

6 files changed

+288
-5
lines changed

6 files changed

+288
-5
lines changed
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
test_taint
2+
| test.py:22 | fail | test_custom_sanitizer | s |
3+
| test.py:36 | fail | test_custom_sanitizer_guard | s |
4+
| test.py:38 | ok | test_custom_sanitizer_guard | s |
5+
| test.py:49 | ok | test_escape | s2 |
6+
isSanitizer
7+
| TestTaintTrackingConfiguration | test.py:21:39:21:39 | ControlFlowNode for s |
8+
| TestTaintTrackingConfiguration | test.py:48:10:48:29 | ControlFlowNode for emulated_escaping() |
9+
isSanitizerGuard
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
import experimental.dataflow.tainttracking.TestTaintLib
2+
3+
class CustomSanitizerOverrides extends TestTaintTrackingConfiguration {
4+
override predicate isSanitizer(DataFlow::Node node) {
5+
exists(Call call |
6+
call.getFunc().(Name).getId() = "emulated_authentication_check" and
7+
call.getArg(0) = node.asExpr()
8+
)
9+
or
10+
node.asExpr().(Call).getFunc().(Name).getId() = "emulated_escaping"
11+
}
12+
13+
override predicate isSanitizerGuard(DataFlow::BarrierGuard guard) {
14+
// TODO: Future work for when BarrierGuard is implemented properly
15+
// exists(Call call |
16+
// call.getFunc().(Name).getId() = "emulated_is_safe" and
17+
// )
18+
none()
19+
}
20+
}
21+
22+
query predicate isSanitizer(TestTaintTrackingConfiguration conf, DataFlow::Node node) {
23+
exists(node.getLocation().getFile().getRelativePath()) and
24+
conf.isSanitizer(node)
25+
}
26+
27+
query predicate isSanitizerGuard(TestTaintTrackingConfiguration conf, DataFlow::BarrierGuard guard) {
28+
exists(guard.getLocation().getFile().getRelativePath()) and
29+
conf.isSanitizerGuard(guard)
30+
}
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
import sys; import os; sys.path.append(os.path.dirname(os.path.dirname((__file__))))
2+
from taintlib import *
3+
4+
# This has no runtime impact, but allows autocomplete to work
5+
from typing import TYPE_CHECKING
6+
if TYPE_CHECKING:
7+
from ..taintlib import *
8+
9+
10+
# Actual tests
11+
12+
def emulated_authentication_check(arg):
13+
if not arg == "safe":
14+
raise Exception("user unauthenticated")
15+
16+
17+
def test_custom_sanitizer():
18+
s = TAINTED_STRING
19+
20+
try:
21+
emulated_authentication_check(s)
22+
ensure_not_tainted(s)
23+
except:
24+
pass
25+
26+
27+
def emulated_is_safe(arg):
28+
# emulating something we won't be able to look at source code for
29+
return eval("False")
30+
31+
32+
def test_custom_sanitizer_guard():
33+
s = TAINTED_STRING
34+
35+
if emulated_is_safe(s):
36+
ensure_not_tainted(s)
37+
else:
38+
ensure_tainted(s)
39+
40+
41+
def emulated_escaping(arg):
42+
return arg.replace("<", "?").replace(">", "?").replace("'", "?").replace("\"", "?")
43+
44+
45+
def test_escape():
46+
s = TAINTED_STRING
47+
48+
s2 = emulated_escaping(s)
49+
ensure_not_tainted(s2)
50+
51+
52+
# Make tests runable
53+
54+
test_custom_sanitizer()
55+
test_custom_sanitizer_guard()
56+
test_escape()
Lines changed: 37 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,37 @@
1-
| test.py:16 | fail | const_eq_clears_taint | ts |
2-
| test.py:18 | ok | const_eq_clears_taint | ts |
3-
| test.py:24 | fail | const_eq_clears_taint2 | ts |
4-
| test.py:29 | ok | non_const_eq_preserves_taint | ts |
5-
| test.py:31 | ok | non_const_eq_preserves_taint | ts |
1+
| test_logical.py:30 | fail | test_basic | s |
2+
| test_logical.py:32 | ok | test_basic | s |
3+
| test_logical.py:35 | ok | test_basic | s |
4+
| test_logical.py:37 | fail | test_basic | s |
5+
| test_logical.py:45 | ok | test_or | s |
6+
| test_logical.py:47 | ok | test_or | s |
7+
| test_logical.py:51 | ok | test_or | s |
8+
| test_logical.py:53 | ok | test_or | s |
9+
| test_logical.py:57 | ok | test_or | s |
10+
| test_logical.py:59 | ok | test_or | s |
11+
| test_logical.py:67 | fail | test_and | s |
12+
| test_logical.py:69 | ok | test_and | s |
13+
| test_logical.py:73 | ok | test_and | s |
14+
| test_logical.py:75 | fail | test_and | s |
15+
| test_logical.py:79 | ok | test_and | s |
16+
| test_logical.py:81 | fail | test_and | s |
17+
| test_logical.py:89 | fail | test_tricky | s |
18+
| test_logical.py:93 | fail | test_tricky | s_ |
19+
| test_logical.py:100 | fail | test_nesting_not | s |
20+
| test_logical.py:102 | ok | test_nesting_not | s |
21+
| test_logical.py:105 | ok | test_nesting_not | s |
22+
| test_logical.py:107 | fail | test_nesting_not | s |
23+
| test_logical.py:116 | ok | test_nesting_not_with_and_true | s |
24+
| test_logical.py:118 | fail | test_nesting_not_with_and_true | s |
25+
| test_logical.py:121 | fail | test_nesting_not_with_and_true | s |
26+
| test_logical.py:123 | ok | test_nesting_not_with_and_true | s |
27+
| test_logical.py:126 | ok | test_nesting_not_with_and_true | s |
28+
| test_logical.py:128 | fail | test_nesting_not_with_and_true | s |
29+
| test_string_eq.py:16 | fail | const_eq_clears_taint | ts |
30+
| test_string_eq.py:18 | ok | const_eq_clears_taint | ts |
31+
| test_string_eq.py:20 | ok | const_eq_clears_taint | ts |
32+
| test_string_eq.py:27 | fail | const_eq_clears_taint2 | ts |
33+
| test_string_eq.py:33 | ok | non_const_eq_preserves_taint | ts |
34+
| test_string_eq.py:35 | ok | non_const_eq_preserves_taint | ts |
35+
| test_string_eq.py:45 | fail | const_eq_through_func | ts |
36+
| test_string_eq.py:47 | ok | const_eq_through_func | ts |
37+
| test_string_eq.py:49 | ok | const_eq_through_func | ts |
Lines changed: 138 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,138 @@
1+
import sys; import os; sys.path.append(os.path.dirname(os.path.dirname((__file__))))
2+
from taintlib import *
3+
4+
# This has no runtime impact, but allows autocomplete to work
5+
from typing import TYPE_CHECKING
6+
if TYPE_CHECKING:
7+
from ..taintlib import *
8+
9+
10+
# Actual tests
11+
12+
"""Testing logical constructs not/and/or works out of the box.
13+
"""
14+
15+
import random
16+
17+
18+
def random_choice():
19+
return bool(random.randint(0, 1))
20+
21+
22+
def is_safe(arg):
23+
return arg == "safe"
24+
25+
26+
def test_basic():
27+
s = TAINTED_STRING
28+
29+
if is_safe(s):
30+
ensure_not_tainted(s)
31+
else:
32+
ensure_tainted(s)
33+
34+
if not is_safe(s):
35+
ensure_tainted(s)
36+
else:
37+
ensure_not_tainted(s)
38+
39+
40+
def test_or():
41+
s = TAINTED_STRING
42+
43+
# x or y
44+
if is_safe(s) or random_choice():
45+
ensure_tainted(s) # might be tainted
46+
else:
47+
ensure_tainted(s) # must be tainted
48+
49+
# not (x or y)
50+
if not(is_safe(s) or random_choice()):
51+
ensure_tainted(s) # must be tainted
52+
else:
53+
ensure_tainted(s) # might be tainted
54+
55+
# not (x or y) == not x and not y [de Morgan's laws]
56+
if not is_safe(s) and not random_choice():
57+
ensure_tainted(s) # must be tainted
58+
else:
59+
ensure_tainted(s) # might be tainted
60+
61+
62+
def test_and():
63+
s = TAINTED_STRING
64+
65+
# x and y
66+
if is_safe(s) and random_choice():
67+
ensure_not_tainted(s) # must not be tainted
68+
else:
69+
ensure_tainted(s) # might be tainted
70+
71+
# not (x and y)
72+
if not(is_safe(s) and random_choice()):
73+
ensure_tainted(s) # might be tainted
74+
else:
75+
ensure_not_tainted(s)
76+
77+
# not (x and y) == not x or not y [de Morgan's laws]
78+
if not is_safe(s) or not random_choice():
79+
ensure_tainted(s) # might be tainted
80+
else:
81+
ensure_not_tainted(s)
82+
83+
84+
def test_tricky():
85+
s = TAINTED_STRING
86+
87+
x = is_safe(s)
88+
if x:
89+
ensure_not_tainted(s) # FP
90+
91+
s_ = s
92+
if is_safe(s):
93+
ensure_not_tainted(s_) # FP
94+
95+
96+
def test_nesting_not():
97+
s = TAINTED_STRING
98+
99+
if not(not(is_safe(s))):
100+
ensure_not_tainted(s)
101+
else:
102+
ensure_tainted(s)
103+
104+
if not(not(not(is_safe(s)))):
105+
ensure_tainted(s)
106+
else:
107+
ensure_not_tainted(s)
108+
109+
110+
# Adding `and True` makes the sanitizer trigger when it would otherwise not. See output in
111+
# SanitizedEdges.expected and compare with `test_nesting_not` and `test_basic`
112+
def test_nesting_not_with_and_true():
113+
s = TAINTED_STRING
114+
115+
if not(is_safe(s) and True):
116+
ensure_tainted(s)
117+
else:
118+
ensure_not_tainted(s)
119+
120+
if not(not(is_safe(s) and True)):
121+
ensure_not_tainted(s)
122+
else:
123+
ensure_tainted(s)
124+
125+
if not(not(not(is_safe(s) and True))):
126+
ensure_tainted(s)
127+
else:
128+
ensure_not_tainted(s)
129+
130+
131+
# Make tests runable
132+
133+
test_basic()
134+
test_or()
135+
test_and()
136+
test_tricky()
137+
test_nesting_not()
138+
test_nesting_not_with_and_true()

python/ql/test/experimental/dataflow/tainttracking/defaultSanitizer/test.py renamed to python/ql/test/experimental/dataflow/tainttracking/defaultSanitizer/test_string_eq.py

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,15 +14,19 @@ def const_eq_clears_taint():
1414
ts = TAINTED_STRING
1515
if ts == "safe":
1616
ensure_not_tainted(ts)
17+
else:
18+
ensure_tainted(ts)
1719
# ts should still be tainted after exiting the if block
1820
ensure_tainted(ts)
1921

22+
2023
def const_eq_clears_taint2():
2124
ts = TAINTED_STRING
2225
if ts != "safe":
2326
return
2427
ensure_not_tainted(ts)
2528

29+
2630
def non_const_eq_preserves_taint(x="foo"):
2731
ts = TAINTED_STRING
2832
if ts == ts:
@@ -31,6 +35,20 @@ def non_const_eq_preserves_taint(x="foo"):
3135
ensure_tainted(ts)
3236

3337

38+
def is_safe(x):
39+
return x == "safe"
40+
41+
42+
def const_eq_through_func():
43+
ts = TAINTED_STRING
44+
if is_safe(ts):
45+
ensure_not_tainted(ts)
46+
else:
47+
ensure_tainted(ts)
48+
# ts should still be tainted after exiting the if block
49+
ensure_tainted(ts)
50+
51+
3452
# Make tests runable
3553

3654
const_eq_clears_taint()

0 commit comments

Comments
 (0)