Python: Expand on taint sanitizer tests

Most interesting to look at the custom sanitizers. Once we have use-use flow, we
should handle this case:

```
s = TAINTED_STRING
emulated_authentication_check(s)
ensure_not_tainted(s)
```
This commit is contained in:
Rasmus Wriedt Larsen
2020-09-09 13:57:25 +02:00
parent 22b3b0a5f1
commit ab8cc23ce7
6 changed files with 287 additions and 5 deletions

View File

@@ -0,0 +1,9 @@
test_taint
| test.py:22 | fail | test_custom_sanitizer | s |
| test.py:36 | fail | test_custom_sanitizer_guard | s |
| test.py:38 | ok | test_custom_sanitizer_guard | s |
| test.py:49 | ok | test_escape | s2 |
isSanitizer
| TestTaintTrackingConfiguration | test.py:21:39:21:39 | ControlFlowNode for s |
| TestTaintTrackingConfiguration | test.py:48:10:48:29 | ControlFlowNode for emulated_escaping() |
isSanitizerGuard

View File

@@ -0,0 +1,29 @@
import experimental.dataflow.tainttracking.TestTaintLib
class CustomSanitizerOverrides extends TestTaintTrackingConfiguration {
override predicate isSanitizer(DataFlow::Node node) {
exists(Call call |
call.getFunc().(Name).getId() = "emulated_authentication_check" and
call.getArg(0) = node.asExpr()
)
or
node.asExpr().(Call).getFunc().(Name).getId() = "emulated_escaping"
}
override predicate isSanitizerGuard(DataFlow::BarrierGuard guard) {
// exists(Call call |
// call.getFunc().(Name).getId() = "emulated_is_safe" and
// )
none()
}
}
query predicate isSanitizer(TestTaintTrackingConfiguration conf, DataFlow::Node node) {
exists(node.getLocation().getFile().getRelativePath()) and
conf.isSanitizer(node)
}
query predicate isSanitizerGuard(TestTaintTrackingConfiguration conf, DataFlow::BarrierGuard guard) {
exists(guard.getLocation().getFile().getRelativePath()) and
conf.isSanitizerGuard(guard)
}

View File

@@ -0,0 +1,56 @@
import sys; import os; sys.path.append(os.path.dirname(os.path.dirname((__file__))))
from taintlib import *
# This has no runtime impact, but allows autocomplete to work
from typing import TYPE_CHECKING
if TYPE_CHECKING:
from ..taintlib import *
# Actual tests
def emulated_authentication_check(arg):
if not arg == "safe":
raise Exception("user unauthenticated")
def test_custom_sanitizer():
s = TAINTED_STRING
try:
emulated_authentication_check(s)
ensure_not_tainted(s)
except:
pass
def emulated_is_safe(arg):
# emulating something we won't be able to look at source code for
return eval("False")
def test_custom_sanitizer_guard():
s = TAINTED_STRING
if emulated_is_safe(s):
ensure_not_tainted(s)
else:
ensure_tainted(s)
def emulated_escaping(arg):
return arg.replace("<", "?").replace(">", "?").replace("'", "?").replace("\"", "?")
def test_escape():
s = TAINTED_STRING
s2 = emulated_escaping(s)
ensure_not_tainted(s2)
# Make tests runable
test_custom_sanitizer()
test_custom_sanitizer_guard()
test_escape()

View File

@@ -1,5 +1,37 @@
| test.py:16 | fail | const_eq_clears_taint | ts |
| test.py:18 | ok | const_eq_clears_taint | ts |
| test.py:24 | fail | const_eq_clears_taint2 | ts |
| test.py:29 | ok | non_const_eq_preserves_taint | ts |
| test.py:31 | ok | non_const_eq_preserves_taint | ts |
| test_logical.py:30 | fail | test_basic | s |
| test_logical.py:32 | ok | test_basic | s |
| test_logical.py:35 | ok | test_basic | s |
| test_logical.py:37 | fail | test_basic | s |
| test_logical.py:45 | ok | test_or | s |
| test_logical.py:47 | ok | test_or | s |
| test_logical.py:51 | ok | test_or | s |
| test_logical.py:53 | ok | test_or | s |
| test_logical.py:57 | ok | test_or | s |
| test_logical.py:59 | ok | test_or | s |
| test_logical.py:67 | fail | test_and | s |
| test_logical.py:69 | ok | test_and | s |
| test_logical.py:73 | ok | test_and | s |
| test_logical.py:75 | fail | test_and | s |
| test_logical.py:79 | ok | test_and | s |
| test_logical.py:81 | fail | test_and | s |
| test_logical.py:89 | fail | test_tricky | s |
| test_logical.py:93 | fail | test_tricky | s_ |
| test_logical.py:100 | fail | test_nesting_not | s |
| test_logical.py:102 | ok | test_nesting_not | s |
| test_logical.py:105 | ok | test_nesting_not | s |
| test_logical.py:107 | fail | test_nesting_not | s |
| test_logical.py:116 | ok | test_nesting_not_with_and_true | s |
| test_logical.py:118 | fail | test_nesting_not_with_and_true | s |
| test_logical.py:121 | fail | test_nesting_not_with_and_true | s |
| test_logical.py:123 | ok | test_nesting_not_with_and_true | s |
| test_logical.py:126 | ok | test_nesting_not_with_and_true | s |
| test_logical.py:128 | fail | test_nesting_not_with_and_true | s |
| test_string_eq.py:16 | fail | const_eq_clears_taint | ts |
| test_string_eq.py:18 | ok | const_eq_clears_taint | ts |
| test_string_eq.py:20 | ok | const_eq_clears_taint | ts |
| test_string_eq.py:27 | fail | const_eq_clears_taint2 | ts |
| test_string_eq.py:33 | ok | non_const_eq_preserves_taint | ts |
| test_string_eq.py:35 | ok | non_const_eq_preserves_taint | ts |
| test_string_eq.py:45 | fail | const_eq_through_func | ts |
| test_string_eq.py:47 | ok | const_eq_through_func | ts |
| test_string_eq.py:49 | ok | const_eq_through_func | ts |

View File

@@ -0,0 +1,138 @@
import sys; import os; sys.path.append(os.path.dirname(os.path.dirname((__file__))))
from taintlib import *
# This has no runtime impact, but allows autocomplete to work
from typing import TYPE_CHECKING
if TYPE_CHECKING:
from ..taintlib import *
# Actual tests
"""Testing logical constructs not/and/or works out of the box.
"""
import random
def random_choice():
return bool(random.randint(0, 1))
def is_safe(arg):
return arg == "safe"
def test_basic():
s = TAINTED_STRING
if is_safe(s):
ensure_not_tainted(s)
else:
ensure_tainted(s)
if not is_safe(s):
ensure_tainted(s)
else:
ensure_not_tainted(s)
def test_or():
s = TAINTED_STRING
# x or y
if is_safe(s) or random_choice():
ensure_tainted(s) # might be tainted
else:
ensure_tainted(s) # must be tainted
# not (x or y)
if not(is_safe(s) or random_choice()):
ensure_tainted(s) # must be tainted
else:
ensure_tainted(s) # might be tainted
# not (x or y) == not x and not y [de Morgan's laws]
if not is_safe(s) and not random_choice():
ensure_tainted(s) # must be tainted
else:
ensure_tainted(s) # might be tainted
def test_and():
s = TAINTED_STRING
# x and y
if is_safe(s) and random_choice():
ensure_not_tainted(s) # must not be tainted
else:
ensure_tainted(s) # might be tainted
# not (x and y)
if not(is_safe(s) and random_choice()):
ensure_tainted(s) # might be tainted
else:
ensure_not_tainted(s)
# not (x and y) == not x or not y [de Morgan's laws]
if not is_safe(s) or not random_choice():
ensure_tainted(s) # might be tainted
else:
ensure_not_tainted(s)
def test_tricky():
s = TAINTED_STRING
x = is_safe(s)
if x:
ensure_not_tainted(s) # FP
s_ = s
if is_safe(s):
ensure_not_tainted(s_) # FP
def test_nesting_not():
s = TAINTED_STRING
if not(not(is_safe(s))):
ensure_not_tainted(s)
else:
ensure_tainted(s)
if not(not(not(is_safe(s)))):
ensure_tainted(s)
else:
ensure_not_tainted(s)
# Adding `and True` makes the sanitizer trigger when it would otherwise not. See output in
# SanitizedEdges.expected and compare with `test_nesting_not` and `test_basic`
def test_nesting_not_with_and_true():
s = TAINTED_STRING
if not(is_safe(s) and True):
ensure_tainted(s)
else:
ensure_not_tainted(s)
if not(not(is_safe(s) and True)):
ensure_not_tainted(s)
else:
ensure_tainted(s)
if not(not(not(is_safe(s) and True))):
ensure_tainted(s)
else:
ensure_not_tainted(s)
# Make tests runable
test_basic()
test_or()
test_and()
test_tricky()
test_nesting_not()
test_nesting_not_with_and_true()

View File

@@ -14,15 +14,19 @@ def const_eq_clears_taint():
ts = TAINTED_STRING
if ts == "safe":
ensure_not_tainted(ts)
else:
ensure_tainted(ts)
# ts should still be tainted after exiting the if block
ensure_tainted(ts)
def const_eq_clears_taint2():
ts = TAINTED_STRING
if ts != "safe":
return
ensure_not_tainted(ts)
def non_const_eq_preserves_taint(x="foo"):
ts = TAINTED_STRING
if ts == ts:
@@ -31,6 +35,20 @@ def non_const_eq_preserves_taint(x="foo"):
ensure_tainted(ts)
def is_safe(x):
return x == "safe"
def const_eq_through_func():
ts = TAINTED_STRING
if is_safe(ts):
ensure_not_tainted(ts)
else:
ensure_tainted(ts)
# ts should still be tainted after exiting the if block
ensure_tainted(ts)
# Make tests runable
const_eq_clears_taint()