Skip to content

Commit 28bedda

Browse files
authored
Merge pull request github#14513 from RasmusWL/yield-modeling
Python: Improve `yield` modeling
2 parents 9d719aa + 80506f1 commit 28bedda

File tree

4 files changed

+87
-0
lines changed

4 files changed

+87
-0
lines changed
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 support for functions decorated with `contextlib.contextmanager`.

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

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -240,6 +240,19 @@ predicate hasPropertyDecorator(Function func) {
240240
)
241241
}
242242

243+
/**
244+
* Holds if the function `func` has a `contextlib.contextmanager`.
245+
*/
246+
predicate hasContextmanagerDecorator(Function func) {
247+
exists(ControlFlowNode contextmanager |
248+
contextmanager.(NameNode).getId() = "contextmanager" and contextmanager.(NameNode).isGlobal()
249+
or
250+
contextmanager.(AttrNode).getObject("contextmanager").(NameNode).getId() = "contextlib"
251+
|
252+
func.getADecorator() = contextmanager.getNode()
253+
)
254+
}
255+
243256
// =============================================================================
244257
// Callables
245258
// =============================================================================
@@ -1604,6 +1617,24 @@ class ExtractedReturnNode extends ReturnNode, CfgNode {
16041617
override ReturnKind getKind() { any() }
16051618
}
16061619

1620+
/**
1621+
* A data flow node that represents the value yielded by a callable with a
1622+
* `contextlib.contextmanager` decorator. We treat this as a normal return, which makes
1623+
* things just work when used in a `with` statement -- technically calling the function
1624+
* directly will give you a `contextlib._GeneratorContextManager` instance, so it's a
1625+
* slight workaround solution.
1626+
*
1627+
* See https://docs.python.org/3/library/contextlib.html#contextlib.contextmanager
1628+
*/
1629+
class YieldNodeInContextManagerFunction extends ReturnNode, CfgNode {
1630+
YieldNodeInContextManagerFunction() {
1631+
hasContextmanagerDecorator(node.getScope()) and
1632+
node = any(Yield yield).getValue().getAFlowNode()
1633+
}
1634+
1635+
override ReturnKind getKind() { any() }
1636+
}
1637+
16071638
/** A data-flow node that represents the output of a call. */
16081639
abstract class OutNode extends Node {
16091640
/** Gets the underlying call, where this node is a corresponding output of kind `kind`. */

python/ql/test/experimental/dataflow/typetracking/test.py

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -195,3 +195,42 @@ def other_name(self):
195195
def with_global_modifier(self):
196196
global some_value
197197
print(some_value) # $ tracked
198+
199+
# ------------------------------------------------------------------------------
200+
# yield
201+
# ------------------------------------------------------------------------------
202+
203+
def yielding_function():
204+
x = tracked # $ tracked
205+
yield x # $ tracked
206+
207+
def test_yield():
208+
for x in yielding_function(): # $ MISSING: tracked
209+
print(x) # $ MISSING: tracked
210+
211+
gen = yielding_function()
212+
y = next(gen) # $ MISSING: tracked
213+
print(y) # $ MISSING: tracked
214+
215+
216+
# see https://docs.python.org/3.11/library/contextlib.html#contextlib.contextmanager
217+
from contextlib import contextmanager
218+
import contextlib
219+
220+
@contextmanager
221+
def managed_resource():
222+
x = tracked # $ tracked
223+
yield x # $ tracked
224+
225+
def test_context_manager():
226+
with managed_resource() as x: # $ tracked
227+
print(x) # $ tracked
228+
229+
@contextlib.contextmanager
230+
def managed_resource2():
231+
x = tracked # $ tracked
232+
yield x # $ tracked
233+
234+
def test_context_manager2():
235+
with managed_resource2() as x: # $ tracked
236+
print(x) # $ tracked

python/ql/test/experimental/query-tests/Security/CWE-022-TarSlip/TarSlip.expected

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,12 @@ edges
3232
| TarSlipImprov.py:141:6:141:29 | ControlFlowNode for Attribute() | TarSlipImprov.py:141:34:141:36 | GSSA Variable tar |
3333
| TarSlipImprov.py:141:34:141:36 | GSSA Variable tar | TarSlipImprov.py:142:9:142:13 | GSSA Variable entry |
3434
| TarSlipImprov.py:142:9:142:13 | GSSA Variable entry | TarSlipImprov.py:143:36:143:40 | ControlFlowNode for entry |
35+
| TarSlipImprov.py:151:14:151:50 | ControlFlowNode for closing() | TarSlipImprov.py:151:55:151:56 | SSA variable tf |
36+
| TarSlipImprov.py:151:22:151:49 | ControlFlowNode for Attribute() | TarSlipImprov.py:151:14:151:50 | ControlFlowNode for closing() |
37+
| TarSlipImprov.py:151:55:151:56 | SSA variable tf | TarSlipImprov.py:152:19:152:20 | ControlFlowNode for tf |
38+
| TarSlipImprov.py:152:19:152:20 | ControlFlowNode for tf | TarSlipImprov.py:157:18:157:40 | ControlFlowNode for py2_tarxz() |
39+
| TarSlipImprov.py:157:9:157:14 | SSA variable tar_cm | TarSlipImprov.py:162:20:162:23 | SSA variable tarc |
40+
| TarSlipImprov.py:157:18:157:40 | ControlFlowNode for py2_tarxz() | TarSlipImprov.py:157:9:157:14 | SSA variable tar_cm |
3541
| TarSlipImprov.py:159:9:159:14 | SSA variable tar_cm | TarSlipImprov.py:162:20:162:23 | SSA variable tarc |
3642
| TarSlipImprov.py:159:18:159:52 | ControlFlowNode for closing() | TarSlipImprov.py:159:9:159:14 | SSA variable tar_cm |
3743
| TarSlipImprov.py:159:26:159:51 | ControlFlowNode for Attribute() | TarSlipImprov.py:159:18:159:52 | ControlFlowNode for closing() |
@@ -122,6 +128,12 @@ nodes
122128
| TarSlipImprov.py:141:34:141:36 | GSSA Variable tar | semmle.label | GSSA Variable tar |
123129
| TarSlipImprov.py:142:9:142:13 | GSSA Variable entry | semmle.label | GSSA Variable entry |
124130
| TarSlipImprov.py:143:36:143:40 | ControlFlowNode for entry | semmle.label | ControlFlowNode for entry |
131+
| TarSlipImprov.py:151:14:151:50 | ControlFlowNode for closing() | semmle.label | ControlFlowNode for closing() |
132+
| TarSlipImprov.py:151:22:151:49 | ControlFlowNode for Attribute() | semmle.label | ControlFlowNode for Attribute() |
133+
| TarSlipImprov.py:151:55:151:56 | SSA variable tf | semmle.label | SSA variable tf |
134+
| TarSlipImprov.py:152:19:152:20 | ControlFlowNode for tf | semmle.label | ControlFlowNode for tf |
135+
| TarSlipImprov.py:157:9:157:14 | SSA variable tar_cm | semmle.label | SSA variable tar_cm |
136+
| TarSlipImprov.py:157:18:157:40 | ControlFlowNode for py2_tarxz() | semmle.label | ControlFlowNode for py2_tarxz() |
125137
| TarSlipImprov.py:159:9:159:14 | SSA variable tar_cm | semmle.label | SSA variable tar_cm |
126138
| TarSlipImprov.py:159:18:159:52 | ControlFlowNode for closing() | semmle.label | ControlFlowNode for closing() |
127139
| TarSlipImprov.py:159:26:159:51 | ControlFlowNode for Attribute() | semmle.label | ControlFlowNode for Attribute() |
@@ -199,6 +211,7 @@ subpaths
199211
| TarSlipImprov.py:130:5:130:7 | ControlFlowNode for tar | TarSlipImprov.py:129:6:129:26 | ControlFlowNode for Attribute() | TarSlipImprov.py:130:5:130:7 | ControlFlowNode for tar | Extraction of tarfile from $@ to a potentially untrusted source $@. | TarSlipImprov.py:129:6:129:26 | ControlFlowNode for Attribute() | ControlFlowNode for Attribute() | TarSlipImprov.py:130:5:130:7 | ControlFlowNode for tar | ControlFlowNode for tar |
200212
| TarSlipImprov.py:134:1:134:3 | ControlFlowNode for tar | TarSlipImprov.py:133:7:133:39 | ControlFlowNode for Attribute() | TarSlipImprov.py:134:1:134:3 | ControlFlowNode for tar | Extraction of tarfile from $@ to a potentially untrusted source $@. | TarSlipImprov.py:133:7:133:39 | ControlFlowNode for Attribute() | ControlFlowNode for Attribute() | TarSlipImprov.py:134:1:134:3 | ControlFlowNode for tar | ControlFlowNode for tar |
201213
| TarSlipImprov.py:143:36:143:40 | ControlFlowNode for entry | TarSlipImprov.py:141:6:141:29 | ControlFlowNode for Attribute() | TarSlipImprov.py:143:36:143:40 | ControlFlowNode for entry | Extraction of tarfile from $@ to a potentially untrusted source $@. | TarSlipImprov.py:141:6:141:29 | ControlFlowNode for Attribute() | ControlFlowNode for Attribute() | TarSlipImprov.py:143:36:143:40 | ControlFlowNode for entry | ControlFlowNode for entry |
214+
| TarSlipImprov.py:169:9:169:12 | ControlFlowNode for tarc | TarSlipImprov.py:151:22:151:49 | ControlFlowNode for Attribute() | TarSlipImprov.py:169:9:169:12 | ControlFlowNode for tarc | Extraction of tarfile from $@ to a potentially untrusted source $@. | TarSlipImprov.py:151:22:151:49 | ControlFlowNode for Attribute() | ControlFlowNode for Attribute() | TarSlipImprov.py:169:9:169:12 | ControlFlowNode for tarc | ControlFlowNode for tarc |
202215
| TarSlipImprov.py:169:9:169:12 | ControlFlowNode for tarc | TarSlipImprov.py:159:26:159:51 | ControlFlowNode for Attribute() | TarSlipImprov.py:169:9:169:12 | ControlFlowNode for tarc | Extraction of tarfile from $@ to a potentially untrusted source $@. | TarSlipImprov.py:159:26:159:51 | ControlFlowNode for Attribute() | ControlFlowNode for Attribute() | TarSlipImprov.py:169:9:169:12 | ControlFlowNode for tarc | ControlFlowNode for tarc |
203216
| TarSlipImprov.py:178:36:178:40 | ControlFlowNode for entry | TarSlipImprov.py:176:6:176:31 | ControlFlowNode for Attribute() | TarSlipImprov.py:178:36:178:40 | ControlFlowNode for entry | Extraction of tarfile from $@ to a potentially untrusted source $@. | TarSlipImprov.py:176:6:176:31 | ControlFlowNode for Attribute() | ControlFlowNode for Attribute() | TarSlipImprov.py:178:36:178:40 | ControlFlowNode for entry | ControlFlowNode for entry |
204217
| TarSlipImprov.py:184:21:184:25 | ControlFlowNode for entry | TarSlipImprov.py:182:6:182:31 | ControlFlowNode for Attribute() | TarSlipImprov.py:184:21:184:25 | ControlFlowNode for entry | Extraction of tarfile from $@ to a potentially untrusted source $@. | TarSlipImprov.py:182:6:182:31 | ControlFlowNode for Attribute() | ControlFlowNode for Attribute() | TarSlipImprov.py:184:21:184:25 | ControlFlowNode for entry | ControlFlowNode for entry |

0 commit comments

Comments
 (0)