Skip to content

Commit cca675a

Browse files
committed
Python: Add test for async taint
(which we belive we have just broken)
1 parent 06586a1 commit cca675a

File tree

4 files changed

+70
-1
lines changed

4 files changed

+70
-1
lines changed

python/ql/test/experimental/dataflow/ApiGraphs/async_test.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ async def bar():
1313

1414
async def test_async_with():
1515
async with pkg.async_func() as result: # $ use=moduleImport("pkg").getMember("async_func").getReturn().getAwaited() awaited=moduleImport("pkg").getMember("async_func").getReturn()
16-
return result # $ use=moduleImport("pkg").getMember("async_func").getReturn() awaited=moduleImport("pkg").getMember("async_func").getReturn()
16+
return result # $ awaited=moduleImport("pkg").getMember("async_func").getReturn()
1717

1818
async def test_async_for():
1919
async for _ in pkg.async_func(): # $ use=moduleImport("pkg").getMember("async_func").getReturn() awaited=moduleImport("pkg").getMember("async_func").getReturn()

python/ql/test/experimental/dataflow/tainttracking/TestTaintLib.qll

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,16 @@ class TestTaintTrackingConfiguration extends TaintTracking::Configuration {
77
TestTaintTrackingConfiguration() { this = "TestTaintTrackingConfiguration" }
88

99
override predicate isSource(DataFlow::Node source) {
10+
// Standard sources
1011
source.(DataFlow::CfgNode).getNode().(NameNode).getId() in [
1112
"TAINTED_STRING", "TAINTED_BYTES", "TAINTED_LIST", "TAINTED_DICT"
1213
]
14+
or
15+
// User defined sources
16+
exists(CallNode call |
17+
call.getFunction().(NameNode).getId() = "taint" and
18+
source.(DataFlow::CfgNode).getNode() = call.getAnArg()
19+
)
1320
}
1421

1522
override predicate isSink(DataFlow::Node sink) {
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
# Add taintlib to PATH so it can be imported during runtime without any hassle
2+
import sys; import os; sys.path.append(os.path.dirname(os.path.dirname((__file__))))
3+
from taintlib import *
4+
5+
# This has no runtime impact, but allows autocomplete to work
6+
from typing import TYPE_CHECKING
7+
if TYPE_CHECKING:
8+
from ..taintlib import *
9+
10+
11+
# Actual tests
12+
13+
async def tainted_coro():
14+
return TAINTED_STRING
15+
16+
async def test_await():
17+
coro = tainted_coro()
18+
taint(coro)
19+
s = await coro
20+
ensure_tainted(coro, s) # $ tainted
21+
22+
23+
class AsyncContext:
24+
async def __aenter__(self):
25+
return TAINTED_STRING
26+
27+
async def __aexit__(self, exc_type, exc, tb):
28+
pass
29+
30+
async def test_async_with():
31+
ctx = AsyncContext()
32+
taint(ctx)
33+
async with ctx as tainted:
34+
ensure_tainted(tainted) # $ MISSING: tainted
35+
36+
37+
class AsyncIter:
38+
def __aiter__(self):
39+
return self
40+
41+
async def __anext__(self):
42+
raise StopAsyncIteration
43+
44+
async def test_async_for():
45+
iter = AsyncIter()
46+
taint(iter)
47+
async for tainted in iter:
48+
ensure_tainted(tainted) # $ MISSING: tainted
49+
50+
51+
52+
# Make tests runable
53+
import asyncio
54+
55+
asyncio.run(test_await())
56+
asyncio.run(test_async_with())
57+
asyncio.run(test_async_for())

python/ql/test/experimental/dataflow/tainttracking/taintlib.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,11 @@
55

66
NOT_TAINTED = "NOT_TAINTED"
77

8+
# Use this to force expressions to be tainted
9+
def taint(*args):
10+
pass
11+
12+
813
def ensure_tainted(*args):
914
print("- ensure_tainted")
1015
for i, arg in enumerate(args):

0 commit comments

Comments
 (0)