Skip to content

Commit 09b0c30

Browse files
committed
Python: Rewrite werkzeug to avoid InstanceSourceApiNode
InstanceSourceApiNode is a really good idea, but it just happened too soon. I can't do what I need if I have to supply an API-node. So to avoid confusion between deprecating to/from InstanceSource in those classes, I opted to do some major reorganizing as well 👍 Due to aliasing restrictions, I had to use a little trick with the `WerkzeugOld` module.
1 parent 04190ea commit 09b0c30

File tree

2 files changed

+140
-34
lines changed

2 files changed

+140
-34
lines changed

python/ql/src/semmle/python/frameworks/Flask.qll

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -401,15 +401,13 @@ module Flask {
401401
}
402402
}
403403

404-
private class RequestAttrMultiDict extends Werkzeug::werkzeug::datastructures::MultiDict::InstanceSourceApiNode {
404+
private class RequestAttrMultiDict extends Werkzeug::MultiDict::InstanceSource {
405405
string attr_name;
406406

407407
RequestAttrMultiDict() {
408408
attr_name in ["args", "values", "form", "files"] and
409-
this = request().getMember(attr_name)
409+
this.(DataFlow::AttrRead).accesses(request().getAUse(), attr_name)
410410
}
411-
412-
override string toString() { result = this.(API::Node).toString() }
413411
}
414412

415413
private class RequestAttrFiles extends RequestAttrMultiDict {

python/ql/src/semmle/python/frameworks/Werkzeug.qll

Lines changed: 138 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -18,20 +18,135 @@ private import semmle.python.frameworks.Stdlib
1818
* - https://werkzeug.palletsprojects.com/en/1.0.x/#werkzeug
1919
*/
2020
module Werkzeug {
21-
/** Provides models for the `werkzeug` module. */
22-
module werkzeug {
23-
/** Provides models for the `werkzeug.datastructures` module. */
24-
module datastructures {
21+
/**
22+
* Provides models for the `werkzeug.datastructures.MultiDict` class
23+
*
24+
* See https://werkzeug.palletsprojects.com/en/1.0.x/datastructures/#werkzeug.datastructures.MultiDict.
25+
*/
26+
module MultiDict {
27+
/**
28+
* A source of instances of `werkzeug.datastructures.MultiDict`, extend this class to model new instances.
29+
*
30+
* This can include instantiations of the class, return values from function
31+
* calls, or a special parameter that will be set when functions are called by an external
32+
* library.
33+
*
34+
* Use the predicate `MultiDict::instance()` to get references to instances of `werkzeug.datastructures.MultiDict`.
35+
*/
36+
abstract class InstanceSource extends DataFlow::LocalSourceNode { }
37+
38+
/** Gets a reference to an instance of `werkzeug.datastructures.MultiDict`. */
39+
private DataFlow::TypeTrackingNode instance(DataFlow::TypeTracker t) {
40+
t.start() and
41+
result instanceof InstanceSource
42+
or
43+
exists(DataFlow::TypeTracker t2 | result = instance(t2).track(t2, t))
44+
}
45+
46+
/** Gets a reference to an instance of `werkzeug.datastructures.MultiDict`. */
47+
DataFlow::Node instance() { instance(DataFlow::TypeTracker::end()).flowsTo(result) }
48+
49+
private class MultiDictAdditionalTaintStep extends TaintTracking::AdditionalTaintStep {
50+
override predicate step(DataFlow::Node nodeFrom, DataFlow::Node nodeTo) {
51+
// See https://werkzeug.palletsprojects.com/en/1.0.x/datastructures/#werkzeug.datastructures.Headers.getlist
52+
nodeFrom = instance() and
53+
nodeTo.(DataFlow::MethodCallNode).calls(nodeFrom, "getlist")
54+
}
55+
}
56+
}
57+
58+
/**
59+
* Provides models for the `werkzeug.datastructures.FileStorage` class
60+
*
61+
* See https://werkzeug.palletsprojects.com/en/1.0.x/datastructures/#werkzeug.datastructures.FileStorage.
62+
*/
63+
module FileStorage {
64+
/**
65+
* A source of instances of `werkzeug.datastructures.FileStorage`, extend this class to model new instances.
66+
*
67+
* This can include instantiations of the class, return values from function
68+
* calls, or a special parameter that will be set when functions are called by an external
69+
* library.
70+
*
71+
* Use the predicate `FileStorage::instance()` to get references to instances of `werkzeug.datastructures.FileStorage`.
72+
*/
73+
// All the attributes of the wrapper stream are proxied by the file storage so it’s
74+
// possible to do storage.read() instead of the long form storage.stream.read(). So
75+
// that's why InstanceSource also extends `Stdlib::FileLikeObject::InstanceSource`
76+
abstract class InstanceSource extends Stdlib::FileLikeObject::InstanceSource,
77+
DataFlow::LocalSourceNode { }
78+
79+
/** Gets a reference to an instance of `werkzeug.datastructures.FileStorage`. */
80+
private DataFlow::TypeTrackingNode instance(DataFlow::TypeTracker t) {
81+
t.start() and
82+
result instanceof InstanceSource
83+
or
84+
exists(DataFlow::TypeTracker t2 | result = instance(t2).track(t2, t))
85+
}
86+
87+
/** Gets a reference to an instance of `werkzeug.datastructures.FileStorage`. */
88+
DataFlow::Node instance() { instance(DataFlow::TypeTracker::end()).flowsTo(result) }
89+
90+
private class FileStorageAdditionalTaintStep extends TaintTracking::AdditionalTaintStep {
91+
override predicate step(DataFlow::Node nodeFrom, DataFlow::Node nodeTo) {
92+
nodeFrom = instance() and
93+
exists(DataFlow::AttrRead read | nodeTo = read |
94+
read.getAttributeName() in [
95+
// str
96+
"filename", "name", "content_type", "mimetype",
97+
// file-like
98+
"stream",
99+
// TODO: werkzeug.datastructures.Headers
100+
"headers",
101+
// dict[str, str]
102+
"mimetype_params"
103+
] and
104+
read.getObject() = nodeFrom
105+
)
106+
}
107+
}
108+
109+
/** A file-like object instance that originates from a `FileStorage`. */
110+
private class FileStorageFileLikeInstances extends Stdlib::FileLikeObject::InstanceSource {
111+
FileStorageFileLikeInstances() { this.(DataFlow::AttrRead).accesses(instance(), "stream") }
112+
}
113+
}
114+
115+
import WerkzeugOld
116+
}
117+
118+
/**
119+
* Old version that contains the deprecated modules.
120+
*/
121+
private module WerkzeugOld {
122+
/**
123+
* DEPRECATED: Use the modeling available directly in the `Werkzeug` module instead.
124+
*
125+
* Provides models for the `werkzeug` module.
126+
*/
127+
deprecated module werkzeug {
128+
/**
129+
* DEPRECATED: Use the modeling available directly in the `Werkzeug` module instead.
130+
*
131+
* Provides models for the `werkzeug.datastructures` module.
132+
*/
133+
deprecated module datastructures {
25134
/**
135+
* DEPRECATED: Use `Werkzeug::MultiDict` instead.
136+
*
26137
* Provides models for the `werkzeug.datastructures.MultiDict` class
27138
*
28139
* See https://werkzeug.palletsprojects.com/en/1.0.x/datastructures/#werkzeug.datastructures.MultiDict.
29140
*/
30-
module MultiDict {
31-
/** DEPRECATED. Use `InstanceSourceApiNode` instead. */
141+
deprecated module MultiDict {
142+
/**
143+
* DEPRECATED. Use `Werkzeug::MultiDict::InstanceSource` instead.
144+
*/
32145
abstract deprecated class InstanceSource extends DataFlow::Node { }
33146

34147
/**
148+
* DEPRECATED. Use `Werkzeug::MultiDict::InstanceSource` instead.
149+
*
35150
* A source of instances of `werkzeug.datastructures.MultiDict`, extend this class to model new instances.
36151
*
37152
* This can include instantiations of the class, return values from function
@@ -40,14 +155,16 @@ module Werkzeug {
40155
*
41156
* Use the predicate `MultiDict::instance()` to get references to instances of `werkzeug.datastructures.MultiDict`.
42157
*/
43-
abstract class InstanceSourceApiNode extends API::Node { }
158+
abstract deprecated class InstanceSourceApiNode extends API::Node { }
44159

45160
/**
161+
* DEPRECATED
162+
*
46163
* Gets a reference to the `getlist` method on an instance of `werkzeug.datastructures.MultiDict`.
47164
*
48165
* See https://werkzeug.palletsprojects.com/en/1.0.x/datastructures/#werkzeug.datastructures.Headers.getlist
49166
*/
50-
DataFlow::Node getlist() {
167+
deprecated DataFlow::Node getlist() {
51168
result = any(InstanceSourceApiNode a).getMember("getlist").getAUse()
52169
}
53170

@@ -57,26 +174,32 @@ module Werkzeug {
57174
exists(DataFlow::AttrRead read |
58175
read.getObject() = nodeFrom and
59176
nodeTo = read and
60-
nodeTo = werkzeug::datastructures::MultiDict::getlist()
177+
nodeTo = getlist()
61178
)
62179
or
63180
// getlist -> getlist()
64-
nodeFrom = werkzeug::datastructures::MultiDict::getlist() and
181+
nodeFrom = getlist() and
65182
nodeTo.(DataFlow::CallCfgNode).getFunction() = nodeFrom
66183
}
67184
}
68185
}
69186

70187
/**
188+
* DEPRECATED: Use `Werkzeug::FileStorage` instead.
189+
*
71190
* Provides models for the `werkzeug.datastructures.FileStorage` class
72191
*
73192
* See https://werkzeug.palletsprojects.com/en/1.0.x/datastructures/#werkzeug.datastructures.FileStorage.
74193
*/
75-
module FileStorage {
76-
/** DEPRECATED. Use `InstanceSourceApiNode` instead. */
194+
deprecated module FileStorage {
195+
/**
196+
* DEPRECATED. Use `Werkzeug::FileStorage::InstanceSource` instead.
197+
*/
77198
abstract deprecated class InstanceSource extends DataFlow::Node { }
78199

79200
/**
201+
* DEPRECATED. Use `Werkzeug::FileStorage::InstanceSource` instead.
202+
*
80203
* A source of instances of `werkzeug.datastructures.FileStorage`, extend this class to model new instances.
81204
*
82205
* This can include instantiations of the class, return values from function
@@ -85,14 +208,14 @@ module Werkzeug {
85208
*
86209
* Use the predicate `FileStorage::instance()` to get references to instances of `werkzeug.datastructures.FileStorage`.
87210
*/
88-
abstract class InstanceSourceApiNode extends API::Node { }
211+
abstract deprecated class InstanceSourceApiNode extends API::Node { }
89212

90213
/** Gets a reference to an instance of `werkzeug.datastructures.FileStorage`. */
91-
DataFlow::Node instance() { result = any(InstanceSourceApiNode a).getAUse() }
214+
deprecated DataFlow::Node instance() { result = any(InstanceSourceApiNode a).getAUse() }
92215

93216
private class FileStorageAdditionalTaintStep extends TaintTracking::AdditionalTaintStep {
94217
override predicate step(DataFlow::Node nodeFrom, DataFlow::Node nodeTo) {
95-
nodeFrom = werkzeug::datastructures::FileStorage::instance() and
218+
nodeFrom = instance() and
96219
exists(DataFlow::AttrRead read | nodeTo = read |
97220
read.getAttributeName() in [
98221
// str
@@ -108,21 +231,6 @@ module Werkzeug {
108231
)
109232
}
110233
}
111-
112-
/** A file-like object instance that originates from a `FileStorage`. */
113-
class FileStorageFileLikeInstances extends Stdlib::FileLikeObject::InstanceSource {
114-
FileStorageFileLikeInstances() {
115-
this.(DataFlow::AttrRead).accesses(instance(), "stream")
116-
or
117-
// All the attributes of the wrapper stream are proxied by the file storage
118-
// so it’s possible to do storage.read() instead of the long form
119-
// storage.stream.read().
120-
//
121-
// due to the `InstanceSourceApiNode` stuff, we can't just make
122-
// `InstanceSource` extend `Stdlib::FileLikeObject::InstanceSource`
123-
this = any(InstanceSourceApiNode api).getAnImmediateUse()
124-
}
125-
}
126234
}
127235
}
128236
}

0 commit comments

Comments
 (0)