Skip to content

Commit 0271a8f

Browse files
committed
Python: Add models for synchronised iterators
See https://docs.python.org/3.15/library/threading.html#iterator-synchronization We handle these as simple Models-as-Data models.
1 parent 74211cc commit 0271a8f

9 files changed

Lines changed: 167 additions & 0 deletions

File tree

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
---
2+
category: minorAnalysis
3+
---
4+
* Added data-flow modeling for `threading.serialize_iterator` and `threading.concurrent_tee`, introduced in Python 3.15.

‎python/ql/lib/semmle/python/frameworks/Stdlib.model.yml‎

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -138,6 +138,17 @@ extensions:
138138
- ["tempfile", "Member[mkstemp]", "Argument[0,suffix:,1,prefix:,2,dir:]", "ReturnValue.TupleElement[0,1]", "taint"]
139139
# See https://docs.python.org/3/library/textwrap.html#textwrap.dedent
140140
- ["textwrap", "Member[dedent]", "Argument[0,text:]", "ReturnValue", "taint"]
141+
# See https://docs.python.org/3/library/threading.html#threading.serialize_iterator
142+
- ["threading", "Member[serialize_iterator]", "Argument[0,iterable:].ListElement", "ReturnValue.ListElement", "value"]
143+
- ["threading", "Member[serialize_iterator]", "Argument[0,iterable:].SetElement", "ReturnValue.ListElement", "value"]
144+
- ["threading", "Member[serialize_iterator]", "Argument[0,iterable:].AnyTupleElement", "ReturnValue.ListElement", "value"]
145+
- ["threading", "Member[serialize_iterator]", "Argument[0,iterable:]", "ReturnValue", "taint"]
146+
# See https://docs.python.org/3/library/threading.html#threading.concurrent_tee
147+
# All returned iterators have the same contents, so the outer tuple is modeled as an index-insensitive sequence.
148+
- ["threading", "Member[concurrent_tee]", "Argument[0,iterable:].ListElement", "ReturnValue.ListElement.ListElement", "value"]
149+
- ["threading", "Member[concurrent_tee]", "Argument[0,iterable:].SetElement", "ReturnValue.ListElement.ListElement", "value"]
150+
- ["threading", "Member[concurrent_tee]", "Argument[0,iterable:].AnyTupleElement", "ReturnValue.ListElement.ListElement", "value"]
151+
- ["threading", "Member[concurrent_tee]", "Argument[0,iterable:]", "ReturnValue", "taint"]
141152
# See https://docs.python.org/3/library/traceback.html#traceback.StackSummary.from_list
142153
- ["traceback.StackSummary", "Member[from_list]", "Argument[0,a_list:]", "ReturnValue", "taint"]
143154
# See https://docs.python.org/3/library/typing.html#typing.cast
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
argumentToEnsureNotTaintedNotMarkedAsSpurious
2+
untaintedArgumentToEnsureTaintedNotMarkedAsMissing
3+
testFailures
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
import experimental.meta.InlineTaintTest
2+
import MakeInlineTaintTest<TestTaintTrackingConfig>
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
missingAnnotationOnSink
2+
testFailures
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
import python
2+
import utils.test.dataflow.NormalDataflowTest
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
semmle-extractor-options: --lang=3 --max-import-depth=1
Lines changed: 110 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,110 @@
1+
import threading
2+
from threading import concurrent_tee, serialize_iterator
3+
4+
5+
def serialized_list():
6+
iterator = threading.serialize_iterator([SOURCE])
7+
SINK(next(iterator)) # $ flow="SOURCE, l:-1 -> next(..)"
8+
SINK_F(iterator)
9+
10+
11+
def serialized_tuple():
12+
iterator = serialize_iterator(iterable=(SOURCE,))
13+
SINK(next(iterator)) # $ flow="SOURCE, l:-1 -> next(..)"
14+
15+
16+
def serialized_set():
17+
wrap = serialize_iterator
18+
iterator = wrap({SOURCE})
19+
SINK(next(iterator)) # $ flow="SOURCE, l:-1 -> next(..)"
20+
21+
22+
def serialized_generator():
23+
iterator = serialize_iterator(value for value in [SOURCE])
24+
for value in iterator:
25+
SINK(value) # $ flow="SOURCE, l:-2 -> value"
26+
27+
28+
def serialized_contents():
29+
iterator = serialize_iterator([{"tainted": SOURCE, "clean": NONSOURCE}])
30+
value = next(iterator)
31+
SINK(value["tainted"]) # $ flow="SOURCE, l:-2 -> value['tainted']"
32+
SINK_F(value["clean"])
33+
34+
35+
def serialized_callback():
36+
iterator = next(map(serialize_iterator, [[SOURCE]]))
37+
SINK(next(iterator)) # $ flow="SOURCE, l:-1 -> next(..)"
38+
39+
40+
def tee_list():
41+
first, second = threading.concurrent_tee([SOURCE])
42+
SINK(next(first)) # $ flow="SOURCE, l:-1 -> next(..)"
43+
SINK(next(second)) # $ flow="SOURCE, l:-2 -> next(..)"
44+
SINK_F(first)
45+
46+
47+
def tee_tuple():
48+
copies = concurrent_tee(iterable=(SOURCE,), n=3)
49+
SINK(next(copies[0])) # $ flow="SOURCE, l:-1 -> next(..)"
50+
SINK(next(copies[2])) # $ flow="SOURCE, l:-2 -> next(..)"
51+
52+
53+
def tee_set():
54+
split = concurrent_tee
55+
first, second = split({SOURCE}, 2)
56+
SINK(next(first)) # $ flow="SOURCE, l:-1 -> next(..)"
57+
SINK(next(second)) # $ flow="SOURCE, l:-2 -> next(..)"
58+
59+
60+
def tee_many():
61+
copies = concurrent_tee([SOURCE], n=10)
62+
SINK(next(copies[9])) # $ flow="SOURCE, l:-1 -> next(..)"
63+
SINK(next(copies[-1])) # $ flow="SOURCE, l:-2 -> next(..)"
64+
65+
66+
def tee_generator():
67+
copies = concurrent_tee(value for value in [SOURCE])
68+
for iterator in copies:
69+
SINK(next(iterator)) # $ flow="SOURCE, l:-2 -> next(..)"
70+
71+
72+
def tee_contents():
73+
first, second = concurrent_tee([{"tainted": SOURCE, "clean": NONSOURCE}])
74+
value = next(second)
75+
SINK(value["tainted"]) # $ flow="SOURCE, l:-2 -> value['tainted']"
76+
SINK_F(value["clean"])
77+
78+
79+
def tee_callback():
80+
copies = next(map(concurrent_tee, [[SOURCE]]))
81+
SINK(next(copies[0])) # $ flow="SOURCE, l:-1 -> next(..)"
82+
83+
84+
# The decorator form is already handled without a dedicated model.
85+
@threading.synchronized_iterator
86+
def decorated_generator(value):
87+
yield {"tainted": value, "clean": NONSOURCE}
88+
89+
90+
def synchronized_generator():
91+
value = next(decorated_generator(SOURCE))
92+
SINK(value["tainted"]) # $ flow="SOURCE, l:-1 -> value['tainted']"
93+
SINK_F(value["clean"])
94+
95+
96+
def clean_inputs():
97+
SINK_F(next(serialize_iterator([NONSOURCE])))
98+
SINK_F(next(concurrent_tee([NONSOURCE])[0]))
99+
SINK_F(next(concurrent_tee([NONSOURCE], n=SOURCE)[0]))
100+
101+
102+
def shadowed_functions():
103+
def serialize_iterator(iterable):
104+
return iter([NONSOURCE])
105+
106+
def concurrent_tee(iterable, n=2):
107+
return iter([NONSOURCE]), iter([NONSOURCE])
108+
109+
SINK_F(next(serialize_iterator([SOURCE])))
110+
SINK_F(next(concurrent_tee([SOURCE])[0]))
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
import threading
2+
3+
4+
def serialized_taint():
5+
iterator = threading.serialize_iterator(TAINTED_LIST)
6+
ensure_tainted(iterator) # $ tainted
7+
ensure_tainted(next(iterator)) # $ tainted
8+
9+
10+
def tee_taint():
11+
first, second = threading.concurrent_tee(TAINTED_LIST)
12+
ensure_tainted(first) # $ tainted
13+
ensure_tainted(second) # $ tainted
14+
ensure_tainted(next(first)) # $ tainted
15+
ensure_tainted(next(second)) # $ tainted
16+
17+
18+
def nested_contents():
19+
iterator = threading.serialize_iterator([{"tainted": TAINTED_STRING, "clean": "safe"}])
20+
value = next(iterator)
21+
ensure_tainted(value["tainted"]) # $ tainted
22+
ensure_not_tainted(value["clean"])
23+
24+
first, second = threading.concurrent_tee([{"tainted": TAINTED_STRING, "clean": "safe"}])
25+
value = next(second)
26+
ensure_tainted(value["tainted"]) # $ tainted
27+
ensure_not_tainted(value["clean"])
28+
29+
30+
def count_is_not_data():
31+
copies = threading.concurrent_tee(["clean"], n=taint(2))
32+
ensure_not_tainted(next(copies[0]))

0 commit comments

Comments
 (0)