Skip to content

Commit 027e5e7

Browse files
Merge pull request github#16300 from joefarebrother/python-pyramid
Python: Model the Pyramid framework
2 parents 904799b + fd55713 commit 027e5e7

File tree

8 files changed

+467
-0
lines changed

8 files changed

+467
-0
lines changed
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
---
2+
category: majorAnalysis
3+
---
4+
* Added modeling of the `pyramid` framework, leading to new remote flow sources and sinks.

python/ql/lib/semmle/python/Frameworks.qll

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,7 @@ private import semmle.python.frameworks.PyMongo
5656
private import semmle.python.frameworks.Pymssql
5757
private import semmle.python.frameworks.PyMySQL
5858
private import semmle.python.frameworks.Pyodbc
59+
private import semmle.python.frameworks.Pyramid
5960
private import semmle.python.frameworks.Requests
6061
private import semmle.python.frameworks.RestFramework
6162
private import semmle.python.frameworks.Rsa
Lines changed: 299 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,299 @@
1+
/**
2+
* Provides classes modeling security-relevant aspects of the `pyramid` PyPI package.
3+
* See https://docs.pylonsproject.org/projects/pyramid/.
4+
*/
5+
6+
private import python
7+
private import semmle.python.dataflow.new.RemoteFlowSources
8+
private import semmle.python.dataflow.new.TaintTracking
9+
private import semmle.python.Concepts
10+
private import semmle.python.ApiGraphs
11+
private import semmle.python.dataflow.new.FlowSummary
12+
private import semmle.python.frameworks.internal.PoorMansFunctionResolution
13+
private import semmle.python.frameworks.internal.InstanceTaintStepsHelper
14+
private import semmle.python.frameworks.data.ModelsAsData
15+
private import semmle.python.frameworks.Stdlib
16+
17+
/**
18+
* Provides models for the `pyramid` PyPI package.
19+
* See https://docs.pylonsproject.org/projects/pyramid/.
20+
*/
21+
module Pyramid {
22+
/** Provides models for pyramid View callables. */
23+
module View {
24+
/** A dataflow node that sets up a route on a server using the Pyramid framework. */
25+
abstract private class PyramidRouteSetup extends Http::Server::RouteSetup::Range {
26+
override string getFramework() { result = "Pyramid" }
27+
}
28+
29+
/**
30+
* A Pyramid view callable, that handles incoming requests.
31+
*/
32+
class ViewCallable extends Function {
33+
ViewCallable() { this = any(PyramidRouteSetup rs).getARequestHandler() }
34+
35+
/** Gets the `request` parameter of this callable. */
36+
Parameter getRequestParameter() {
37+
this.getPositionalParameterCount() = 1 and
38+
result = this.getArg(0)
39+
or
40+
this.getPositionalParameterCount() = 2 and
41+
result = this.getArg(1)
42+
}
43+
}
44+
45+
/** A pyramid route setup using the `pyramid.view.view_config` decorator. */
46+
private class DecoratorSetup extends PyramidRouteSetup {
47+
DecoratorSetup() {
48+
this = API::moduleImport("pyramid").getMember("view").getMember("view_config").getACall()
49+
}
50+
51+
override Function getARequestHandler() { result.getADecorator() = this.asExpr() }
52+
53+
override DataFlow::Node getUrlPatternArg() { none() } // there is a `route_name` arg, but that does not contain the url pattern
54+
55+
override Parameter getARoutedParameter() { none() }
56+
}
57+
58+
/** A pyramid route setup using a call to `pyramid.config.Configurator.add_view`. */
59+
private class ConfiguratorSetup extends PyramidRouteSetup instanceof Configurator::AddViewCall {
60+
override Function getARequestHandler() {
61+
this.(Configurator::AddViewCall).getViewArg() = poorMansFunctionTracker(result)
62+
}
63+
64+
override DataFlow::Node getUrlPatternArg() { none() } // there is a `route_name` arg, but that does not contain the url pattern
65+
66+
override Parameter getARoutedParameter() { none() }
67+
}
68+
}
69+
70+
/** Provides models for `pyramid.config.Configurator` */
71+
module Configurator {
72+
/** Gets a reference to the class `pyramid.config.Configurator`. */
73+
API::Node classRef() {
74+
result = API::moduleImport("pyramid").getMember("config").getMember("Configurator")
75+
}
76+
77+
/** Gets a reference to an instance of `pyramid.config.Configurator`. */
78+
private DataFlow::TypeTrackingNode instance(DataFlow::TypeTracker t) {
79+
t.start() and
80+
result = classRef().getACall()
81+
or
82+
exists(DataFlow::TypeTracker t2 | result = instance(t2).track(t2, t))
83+
}
84+
85+
/** Gets a reference to an instance of `pyramid.config.Configurator`. */
86+
DataFlow::Node instance() { instance(DataFlow::TypeTracker::end()).flowsTo(result) }
87+
88+
/** A call to the `add_view` method of an instance of `pyramid.config.Configurator`. */
89+
class AddViewCall extends DataFlow::MethodCallNode {
90+
AddViewCall() { this.calls(instance(), "add_view") }
91+
92+
/** Gets the `view` argument of this call. */
93+
DataFlow::Node getViewArg() { result = [this.getArg(0), this.getArgByName("view")] }
94+
}
95+
}
96+
97+
/** Provides modeling for pyramid requests. */
98+
module Request {
99+
/**
100+
* A source of instances of `pyramid.request.Request`, extend this class to model new instances.
101+
*
102+
* Use the predicate `Request::instance()` to get references to instances of `pyramid.request.Request`.
103+
*/
104+
abstract class InstanceSource extends DataFlow::LocalSourceNode { }
105+
106+
/** Gets a reference to an instance of `pyramid.request.Request`. */
107+
private DataFlow::TypeTrackingNode instance(DataFlow::TypeTracker t) {
108+
t.start() and
109+
result instanceof InstanceSource
110+
or
111+
exists(DataFlow::TypeTracker t2 | result = instance(t2).track(t2, t))
112+
}
113+
114+
/** Gets a reference to an instance of `pyramid.request.Request`. */
115+
DataFlow::Node instance() { instance(DataFlow::TypeTracker::end()).flowsTo(result) }
116+
117+
private class RequestParameter extends InstanceSource, RemoteFlowSource::Range instanceof DataFlow::ParameterNode
118+
{
119+
RequestParameter() { this.getParameter() = any(View::ViewCallable vc).getRequestParameter() }
120+
121+
override string getSourceType() { result = "Pyramid request parameter" }
122+
}
123+
124+
/** Taint steps for request instances. */
125+
private class InstanceTaintSteps extends InstanceTaintStepsHelper {
126+
InstanceTaintSteps() { this = "pyramid.request.Request" }
127+
128+
override DataFlow::Node getInstance() { result = instance() }
129+
130+
override string getAttributeName() {
131+
result in [
132+
"accept", "accept_charset", "accept_encoding", "accept_language", "application_url",
133+
"as_bytes", "authorization", "body", "body_file", "body_file_raw", "body_file_seekable",
134+
"cache_control", "client_addr", "content_type", "cookies", "domain", "headers", "host",
135+
"host_port", "host_url", "GET", "if_match", "if_none_match", "if_range",
136+
"if_none_match", "json", "json_body", "matchdict", "params", "path", "path_info",
137+
"path_qs", "path_url", "POST", "pragma", "query_string", "range", "referer", "referrer",
138+
"text", "url", "urlargs", "urlvars", "user_agent"
139+
]
140+
}
141+
142+
override string getMethodName() {
143+
result in ["as_bytes", "copy", "copy_get", "path_info_peek", "path_info_pop"]
144+
}
145+
146+
override string getAsyncMethodName() { none() }
147+
}
148+
149+
/** A call to a method of a `request` that copies the request. */
150+
private class RequestCopyCall extends InstanceSource, DataFlow::MethodCallNode {
151+
RequestCopyCall() { this.calls(instance(), ["copy", "copy_get"]) }
152+
}
153+
154+
/** A member of a request that is a file-like object. */
155+
private class RequestBodyFileLike extends Stdlib::FileLikeObject::InstanceSource instanceof DataFlow::AttrRead
156+
{
157+
RequestBodyFileLike() {
158+
this.getObject() = instance() and
159+
this.getAttributeName() = ["body_file", "body_file_raw", "body_file_seekable"]
160+
}
161+
}
162+
}
163+
164+
/** Provides modeling for pyramid responses. */
165+
module Response {
166+
/** A response returned by a view callable. */
167+
private class PyramidReturnResponse extends Http::Server::HttpResponse::Range {
168+
PyramidReturnResponse() {
169+
this.asCfgNode() = any(View::ViewCallable vc).getAReturnValueFlowNode() and
170+
not this = instance()
171+
}
172+
173+
override DataFlow::Node getBody() { result = this }
174+
175+
override DataFlow::Node getMimetypeOrContentTypeArg() { none() }
176+
177+
override string getMimetypeDefault() { result = "text/html" }
178+
}
179+
180+
/** Gets a reference to the class `pyramid.response.Response`. */
181+
API::Node classRef() {
182+
result = API::moduleImport("pyramid").getMember("response").getMember("Response")
183+
}
184+
185+
/**
186+
* A source of instances of `pyramid.response.Response`, extend this class to model new instances.
187+
*
188+
* This can include instantiations of the class, return values from function
189+
* calls, or a special parameter that will be set when functions are called by an external
190+
* library.
191+
*
192+
* Use the predicate `Response::instance()` to get references to instances of `pyramid.response.Response`.
193+
*/
194+
abstract class InstanceSource extends DataFlow::LocalSourceNode,
195+
Http::Server::HttpResponse::Range
196+
{ }
197+
198+
/** Gets a reference to an instance of `pyramid.response.Response`. */
199+
private DataFlow::TypeTrackingNode instance(DataFlow::TypeTracker t) {
200+
t.start() and
201+
result instanceof InstanceSource
202+
or
203+
exists(DataFlow::TypeTracker t2 | result = instance(t2).track(t2, t))
204+
}
205+
206+
/** Gets a reference to an instance of `pyramid.response.Response`. */
207+
DataFlow::Node instance() { instance(DataFlow::TypeTracker::end()).flowsTo(result) }
208+
209+
/** An instantiation of the class `pyramid.response.Response` or a subclass. */
210+
private class ClassInstantiation extends InstanceSource, DataFlow::CallCfgNode {
211+
ClassInstantiation() { this = classRef().getACall() }
212+
213+
override DataFlow::Node getBody() { result = [this.getArg(0), this.getArgByName("body")] }
214+
215+
override DataFlow::Node getMimetypeOrContentTypeArg() {
216+
result = [this.getArg(4), this.getArgByName("content_type")]
217+
}
218+
219+
override string getMimetypeDefault() { result = "text/html" }
220+
}
221+
222+
/** A write to a field that sets the body of a response. */
223+
private class ResponseBodySet extends Http::Server::HttpResponse::Range instanceof DataFlow::AttrWrite
224+
{
225+
string attrName;
226+
227+
ResponseBodySet() {
228+
this.getObject() = instance() and
229+
this.getAttributeName() = attrName and
230+
attrName in ["body", "body_file", "json", "json_body", "text", "ubody", "unicode_body"]
231+
}
232+
233+
override DataFlow::Node getBody() { result = this.(DataFlow::AttrWrite).getValue() }
234+
235+
override DataFlow::Node getMimetypeOrContentTypeArg() { none() }
236+
237+
override string getMimetypeDefault() {
238+
if attrName in ["json", "json_body"]
239+
then result = "application/json"
240+
else result = "text/html"
241+
}
242+
}
243+
244+
/** A use of the `response` attribute of a `Request`. */
245+
private class RequestResponseAttr extends InstanceSource instanceof DataFlow::AttrRead {
246+
RequestResponseAttr() {
247+
this.getObject() = Request::instance() and this.getAttributeName() = "response"
248+
}
249+
250+
override DataFlow::Node getBody() { none() }
251+
252+
override DataFlow::Node getMimetypeOrContentTypeArg() { none() }
253+
254+
override string getMimetypeDefault() { result = "text/html" }
255+
}
256+
257+
/** A call to `response.set_cookie`. */
258+
private class SetCookieCall extends Http::Server::CookieWrite::Range, DataFlow::MethodCallNode {
259+
SetCookieCall() { this.calls(instance(), "set_cookie") }
260+
261+
override DataFlow::Node getHeaderArg() { none() }
262+
263+
override DataFlow::Node getNameArg() { result = [this.getArg(0), this.getArgByName("name")] }
264+
265+
override DataFlow::Node getValueArg() {
266+
result = [this.getArg(1), this.getArgByName("value")]
267+
}
268+
}
269+
}
270+
271+
/** Provides models for pyramid http redirects. */
272+
module Redirect {
273+
/** Gets a reference to a class that represents an HTTP redirect response.. */
274+
API::Node classRef() {
275+
result =
276+
API::moduleImport("pyramid")
277+
.getMember("httpexceptions")
278+
.getMember([
279+
"HTTPMultipleChoices", "HTTPMovedPermanently", "HTTPFound", "HTTPSeeOther",
280+
"HTTPUseProxy", "HTTPTemporaryRedirect", "HTTPPermanentRedirect"
281+
])
282+
}
283+
284+
/** A call to a pyramid HTTP exception class that represents an HTTP redirect response. */
285+
class PyramidRedirect extends Http::Server::HttpRedirectResponse::Range, DataFlow::CallCfgNode {
286+
PyramidRedirect() { this = classRef().getACall() }
287+
288+
override DataFlow::Node getRedirectLocation() {
289+
result = [this.getArg(0), this.getArgByName("location")]
290+
}
291+
292+
override DataFlow::Node getBody() { none() }
293+
294+
override DataFlow::Node getMimetypeOrContentTypeArg() { none() }
295+
296+
override string getMimetypeDefault() { result = "text/html" }
297+
}
298+
}
299+
}
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
testFailures
2+
failures
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
import python
2+
import experimental.meta.ConceptsTest
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
argumentToEnsureNotTaintedNotMarkedAsSpurious
2+
untaintedArgumentToEnsureTaintedNotMarkedAsMissing
3+
testFailures
4+
failures
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>

0 commit comments

Comments
 (0)