Skip to content

Commit 89ddd67

Browse files
authored
Merge pull request #21002 from github/tausbn/python-add-models-for-zstd-compression
Python: Add modelling for `zstd.compression`
2 parents 1a29b32 + 8602a2d commit 89ddd67

File tree

4 files changed

+79
-24
lines changed

4 files changed

+79
-24
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+
* The `compression.zstd` library (added in Python 3.14) is now supported by the `py/decompression-bomb` query.

python/ql/src/experimental/semmle/python/security/DecompressionBomb.qll

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -364,6 +364,46 @@ module Lzma {
364364
}
365365
}
366366

367+
/** Provides sinks and additional taint steps related to the `zstd` library in Python 3.14+. */
368+
module Zstd {
369+
private API::Node zstdInstance() {
370+
result = API::moduleImport("compression").getMember("zstd").getMember(["ZstdFile", "open"])
371+
}
372+
373+
/**
374+
* The Decompression Sinks of `zstd` library
375+
*
376+
* `zstd.open(sink)`
377+
* `zstd.ZstdFile(sink)`
378+
*
379+
* only read mode is sink
380+
*/
381+
class DecompressionSink extends DecompressionBomb::Sink {
382+
DecompressionSink() {
383+
exists(API::CallNode zstdCall | zstdCall = zstdInstance().getACall() |
384+
this = zstdCall.getParameter(0, "file").asSink() and
385+
(
386+
not exists(
387+
zstdCall
388+
.getParameter(1, "mode")
389+
.getAValueReachingSink()
390+
.asExpr()
391+
.(StringLiteral)
392+
.getText()
393+
) or
394+
zstdCall
395+
.getParameter(1, "mode")
396+
.getAValueReachingSink()
397+
.asExpr()
398+
.(StringLiteral)
399+
.getText()
400+
.matches("%r%")
401+
)
402+
)
403+
}
404+
}
405+
}
406+
367407
/**
368408
* `io.TextIOWrapper(ip, encoding='utf-8')` like following:
369409
* ```python

python/ql/test/experimental/query-tests/Security/CWE-409/DecompressionBombs.expected

Lines changed: 30 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -36,15 +36,17 @@ edges
3636
| test.py:28:26:28:34 | ControlFlowNode for file_path | test.py:45:17:45:25 | ControlFlowNode for file_path | provenance | |
3737
| test.py:28:26:28:34 | ControlFlowNode for file_path | test.py:49:15:49:23 | ControlFlowNode for file_path | provenance | |
3838
| test.py:28:26:28:34 | ControlFlowNode for file_path | test.py:50:19:50:27 | ControlFlowNode for file_path | provenance | |
39-
| test.py:28:26:28:34 | ControlFlowNode for file_path | test.py:54:40:54:48 | ControlFlowNode for file_path | provenance | |
40-
| test.py:28:26:28:34 | ControlFlowNode for file_path | test.py:56:23:56:31 | ControlFlowNode for file_path | provenance | |
41-
| test.py:28:26:28:34 | ControlFlowNode for file_path | test.py:57:21:57:29 | ControlFlowNode for file_path | provenance | |
39+
| test.py:28:26:28:34 | ControlFlowNode for file_path | test.py:54:15:54:23 | ControlFlowNode for file_path | provenance | |
40+
| test.py:28:26:28:34 | ControlFlowNode for file_path | test.py:55:19:55:27 | ControlFlowNode for file_path | provenance | |
4241
| test.py:28:26:28:34 | ControlFlowNode for file_path | test.py:59:40:59:48 | ControlFlowNode for file_path | provenance | |
43-
| test.py:28:26:28:34 | ControlFlowNode for file_path | test.py:60:22:60:30 | ControlFlowNode for file_path | provenance | |
44-
| test.py:28:26:28:34 | ControlFlowNode for file_path | test.py:61:21:61:29 | ControlFlowNode for file_path | provenance | |
45-
| test.py:28:26:28:34 | ControlFlowNode for file_path | test.py:62:42:62:50 | ControlFlowNode for file_path | provenance | |
46-
| test.py:28:26:28:34 | ControlFlowNode for file_path | test.py:63:23:63:31 | ControlFlowNode for file_path | provenance | |
47-
| test.py:28:26:28:34 | ControlFlowNode for file_path | test.py:64:36:64:44 | ControlFlowNode for file_path | provenance | |
42+
| test.py:28:26:28:34 | ControlFlowNode for file_path | test.py:61:23:61:31 | ControlFlowNode for file_path | provenance | |
43+
| test.py:28:26:28:34 | ControlFlowNode for file_path | test.py:62:21:62:29 | ControlFlowNode for file_path | provenance | |
44+
| test.py:28:26:28:34 | ControlFlowNode for file_path | test.py:64:40:64:48 | ControlFlowNode for file_path | provenance | |
45+
| test.py:28:26:28:34 | ControlFlowNode for file_path | test.py:65:22:65:30 | ControlFlowNode for file_path | provenance | |
46+
| test.py:28:26:28:34 | ControlFlowNode for file_path | test.py:66:21:66:29 | ControlFlowNode for file_path | provenance | |
47+
| test.py:28:26:28:34 | ControlFlowNode for file_path | test.py:67:42:67:50 | ControlFlowNode for file_path | provenance | |
48+
| test.py:28:26:28:34 | ControlFlowNode for file_path | test.py:68:23:68:31 | ControlFlowNode for file_path | provenance | |
49+
| test.py:28:26:28:34 | ControlFlowNode for file_path | test.py:69:36:69:44 | ControlFlowNode for file_path | provenance | |
4850
nodes
4951
| test.py:10:16:10:24 | ControlFlowNode for file_path | semmle.label | ControlFlowNode for file_path |
5052
| test.py:11:5:11:35 | ControlFlowNode for Attribute() | semmle.label | ControlFlowNode for Attribute() |
@@ -79,15 +81,17 @@ nodes
7981
| test.py:45:17:45:25 | ControlFlowNode for file_path | semmle.label | ControlFlowNode for file_path |
8082
| test.py:49:15:49:23 | ControlFlowNode for file_path | semmle.label | ControlFlowNode for file_path |
8183
| test.py:50:19:50:27 | ControlFlowNode for file_path | semmle.label | ControlFlowNode for file_path |
82-
| test.py:54:40:54:48 | ControlFlowNode for file_path | semmle.label | ControlFlowNode for file_path |
83-
| test.py:56:23:56:31 | ControlFlowNode for file_path | semmle.label | ControlFlowNode for file_path |
84-
| test.py:57:21:57:29 | ControlFlowNode for file_path | semmle.label | ControlFlowNode for file_path |
84+
| test.py:54:15:54:23 | ControlFlowNode for file_path | semmle.label | ControlFlowNode for file_path |
85+
| test.py:55:19:55:27 | ControlFlowNode for file_path | semmle.label | ControlFlowNode for file_path |
8586
| test.py:59:40:59:48 | ControlFlowNode for file_path | semmle.label | ControlFlowNode for file_path |
86-
| test.py:60:22:60:30 | ControlFlowNode for file_path | semmle.label | ControlFlowNode for file_path |
87-
| test.py:61:21:61:29 | ControlFlowNode for file_path | semmle.label | ControlFlowNode for file_path |
88-
| test.py:62:42:62:50 | ControlFlowNode for file_path | semmle.label | ControlFlowNode for file_path |
89-
| test.py:63:23:63:31 | ControlFlowNode for file_path | semmle.label | ControlFlowNode for file_path |
90-
| test.py:64:36:64:44 | ControlFlowNode for file_path | semmle.label | ControlFlowNode for file_path |
87+
| test.py:61:23:61:31 | ControlFlowNode for file_path | semmle.label | ControlFlowNode for file_path |
88+
| test.py:62:21:62:29 | ControlFlowNode for file_path | semmle.label | ControlFlowNode for file_path |
89+
| test.py:64:40:64:48 | ControlFlowNode for file_path | semmle.label | ControlFlowNode for file_path |
90+
| test.py:65:22:65:30 | ControlFlowNode for file_path | semmle.label | ControlFlowNode for file_path |
91+
| test.py:66:21:66:29 | ControlFlowNode for file_path | semmle.label | ControlFlowNode for file_path |
92+
| test.py:67:42:67:50 | ControlFlowNode for file_path | semmle.label | ControlFlowNode for file_path |
93+
| test.py:68:23:68:31 | ControlFlowNode for file_path | semmle.label | ControlFlowNode for file_path |
94+
| test.py:69:36:69:44 | ControlFlowNode for file_path | semmle.label | ControlFlowNode for file_path |
9195
subpaths
9296
#select
9397
| test.py:11:5:11:52 | ControlFlowNode for Attribute() | test.py:10:16:10:24 | ControlFlowNode for file_path | test.py:11:5:11:52 | ControlFlowNode for Attribute() | This uncontrolled file extraction is $@. | test.py:10:16:10:24 | ControlFlowNode for file_path | depends on this user controlled data |
@@ -107,12 +111,14 @@ subpaths
107111
| test.py:45:17:45:25 | ControlFlowNode for file_path | test.py:10:16:10:24 | ControlFlowNode for file_path | test.py:45:17:45:25 | ControlFlowNode for file_path | This uncontrolled file extraction is $@. | test.py:10:16:10:24 | ControlFlowNode for file_path | depends on this user controlled data |
108112
| test.py:49:15:49:23 | ControlFlowNode for file_path | test.py:10:16:10:24 | ControlFlowNode for file_path | test.py:49:15:49:23 | ControlFlowNode for file_path | This uncontrolled file extraction is $@. | test.py:10:16:10:24 | ControlFlowNode for file_path | depends on this user controlled data |
109113
| test.py:50:19:50:27 | ControlFlowNode for file_path | test.py:10:16:10:24 | ControlFlowNode for file_path | test.py:50:19:50:27 | ControlFlowNode for file_path | This uncontrolled file extraction is $@. | test.py:10:16:10:24 | ControlFlowNode for file_path | depends on this user controlled data |
110-
| test.py:54:40:54:48 | ControlFlowNode for file_path | test.py:10:16:10:24 | ControlFlowNode for file_path | test.py:54:40:54:48 | ControlFlowNode for file_path | This uncontrolled file extraction is $@. | test.py:10:16:10:24 | ControlFlowNode for file_path | depends on this user controlled data |
111-
| test.py:56:23:56:31 | ControlFlowNode for file_path | test.py:10:16:10:24 | ControlFlowNode for file_path | test.py:56:23:56:31 | ControlFlowNode for file_path | This uncontrolled file extraction is $@. | test.py:10:16:10:24 | ControlFlowNode for file_path | depends on this user controlled data |
112-
| test.py:57:21:57:29 | ControlFlowNode for file_path | test.py:10:16:10:24 | ControlFlowNode for file_path | test.py:57:21:57:29 | ControlFlowNode for file_path | This uncontrolled file extraction is $@. | test.py:10:16:10:24 | ControlFlowNode for file_path | depends on this user controlled data |
114+
| test.py:54:15:54:23 | ControlFlowNode for file_path | test.py:10:16:10:24 | ControlFlowNode for file_path | test.py:54:15:54:23 | ControlFlowNode for file_path | This uncontrolled file extraction is $@. | test.py:10:16:10:24 | ControlFlowNode for file_path | depends on this user controlled data |
115+
| test.py:55:19:55:27 | ControlFlowNode for file_path | test.py:10:16:10:24 | ControlFlowNode for file_path | test.py:55:19:55:27 | ControlFlowNode for file_path | This uncontrolled file extraction is $@. | test.py:10:16:10:24 | ControlFlowNode for file_path | depends on this user controlled data |
113116
| test.py:59:40:59:48 | ControlFlowNode for file_path | test.py:10:16:10:24 | ControlFlowNode for file_path | test.py:59:40:59:48 | ControlFlowNode for file_path | This uncontrolled file extraction is $@. | test.py:10:16:10:24 | ControlFlowNode for file_path | depends on this user controlled data |
114-
| test.py:60:22:60:30 | ControlFlowNode for file_path | test.py:10:16:10:24 | ControlFlowNode for file_path | test.py:60:22:60:30 | ControlFlowNode for file_path | This uncontrolled file extraction is $@. | test.py:10:16:10:24 | ControlFlowNode for file_path | depends on this user controlled data |
115-
| test.py:61:21:61:29 | ControlFlowNode for file_path | test.py:10:16:10:24 | ControlFlowNode for file_path | test.py:61:21:61:29 | ControlFlowNode for file_path | This uncontrolled file extraction is $@. | test.py:10:16:10:24 | ControlFlowNode for file_path | depends on this user controlled data |
116-
| test.py:62:42:62:50 | ControlFlowNode for file_path | test.py:10:16:10:24 | ControlFlowNode for file_path | test.py:62:42:62:50 | ControlFlowNode for file_path | This uncontrolled file extraction is $@. | test.py:10:16:10:24 | ControlFlowNode for file_path | depends on this user controlled data |
117-
| test.py:63:23:63:31 | ControlFlowNode for file_path | test.py:10:16:10:24 | ControlFlowNode for file_path | test.py:63:23:63:31 | ControlFlowNode for file_path | This uncontrolled file extraction is $@. | test.py:10:16:10:24 | ControlFlowNode for file_path | depends on this user controlled data |
118-
| test.py:64:36:64:44 | ControlFlowNode for file_path | test.py:10:16:10:24 | ControlFlowNode for file_path | test.py:64:36:64:44 | ControlFlowNode for file_path | This uncontrolled file extraction is $@. | test.py:10:16:10:24 | ControlFlowNode for file_path | depends on this user controlled data |
117+
| test.py:61:23:61:31 | ControlFlowNode for file_path | test.py:10:16:10:24 | ControlFlowNode for file_path | test.py:61:23:61:31 | ControlFlowNode for file_path | This uncontrolled file extraction is $@. | test.py:10:16:10:24 | ControlFlowNode for file_path | depends on this user controlled data |
118+
| test.py:62:21:62:29 | ControlFlowNode for file_path | test.py:10:16:10:24 | ControlFlowNode for file_path | test.py:62:21:62:29 | ControlFlowNode for file_path | This uncontrolled file extraction is $@. | test.py:10:16:10:24 | ControlFlowNode for file_path | depends on this user controlled data |
119+
| test.py:64:40:64:48 | ControlFlowNode for file_path | test.py:10:16:10:24 | ControlFlowNode for file_path | test.py:64:40:64:48 | ControlFlowNode for file_path | This uncontrolled file extraction is $@. | test.py:10:16:10:24 | ControlFlowNode for file_path | depends on this user controlled data |
120+
| test.py:65:22:65:30 | ControlFlowNode for file_path | test.py:10:16:10:24 | ControlFlowNode for file_path | test.py:65:22:65:30 | ControlFlowNode for file_path | This uncontrolled file extraction is $@. | test.py:10:16:10:24 | ControlFlowNode for file_path | depends on this user controlled data |
121+
| test.py:66:21:66:29 | ControlFlowNode for file_path | test.py:10:16:10:24 | ControlFlowNode for file_path | test.py:66:21:66:29 | ControlFlowNode for file_path | This uncontrolled file extraction is $@. | test.py:10:16:10:24 | ControlFlowNode for file_path | depends on this user controlled data |
122+
| test.py:67:42:67:50 | ControlFlowNode for file_path | test.py:10:16:10:24 | ControlFlowNode for file_path | test.py:67:42:67:50 | ControlFlowNode for file_path | This uncontrolled file extraction is $@. | test.py:10:16:10:24 | ControlFlowNode for file_path | depends on this user controlled data |
123+
| test.py:68:23:68:31 | ControlFlowNode for file_path | test.py:10:16:10:24 | ControlFlowNode for file_path | test.py:68:23:68:31 | ControlFlowNode for file_path | This uncontrolled file extraction is $@. | test.py:10:16:10:24 | ControlFlowNode for file_path | depends on this user controlled data |
124+
| test.py:69:36:69:44 | ControlFlowNode for file_path | test.py:10:16:10:24 | ControlFlowNode for file_path | test.py:69:36:69:44 | ControlFlowNode for file_path | This uncontrolled file extraction is $@. | test.py:10:16:10:24 | ControlFlowNode for file_path | depends on this user controlled data |

python/ql/test/experimental/query-tests/Security/CWE-409/test.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,11 @@ async def bomb(file_path):
4949
gzip.open(file_path) # $ result=BAD
5050
gzip.GzipFile(file_path) # $ result=BAD
5151

52+
from compression import zstd
53+
54+
zstd.open(file_path) # $ result=BAD
55+
zstd.ZstdFile(file_path).read() # $ result=BAD
56+
5257
import pandas
5358

5459
pandas.read_csv(filepath_or_buffer=file_path) # $ result=BAD

0 commit comments

Comments
 (0)