Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit 5990241

Browse files
committed
Python: Support django models (with some caveats)
1 parent d7308bd commit 5990241

2 files changed

Lines changed: 175 additions & 0 deletions

File tree

python/ql/src/experimental/semmle/python/frameworks/Django.qll

Lines changed: 162 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,136 @@ private module Django {
9090

9191
/** Gets a reference to the `django.db.connection.cursor.execute` function. */
9292
DataFlow::Node execute() { result = execute(DataFlow::TypeTracker::end()) }
93+
94+
/** Gets a reference to the `django.db.models` module. */
95+
private DataFlow::Node models(DataFlow::TypeTracker t) {
96+
t.start() and
97+
result = DataFlow::importNode("django.db.models")
98+
or
99+
t.startInAttr("models") and
100+
result = django()
101+
or
102+
exists(DataFlow::TypeTracker t2 | result = models(t2).track(t2, t))
103+
}
104+
105+
/** Gets a reference to the `django.db.models` module. */
106+
DataFlow::Node models() { result = models(DataFlow::TypeTracker::end()) }
107+
108+
/** Provides models for the `django.db.models` module. */
109+
module models {
110+
/** Gets a reference to the `django.db.models.Model` class. */
111+
private DataFlow::Node classModel(DataFlow::TypeTracker t) {
112+
t.start() and
113+
result = DataFlow::importNode("django.db.models.Model")
114+
or
115+
t.startInAttr("Model") and
116+
result = models()
117+
or
118+
exists(DataFlow::TypeTracker t2 | result = classModel(t2).track(t2, t))
119+
}
120+
121+
/** Gets a reference to the `django.db.models.Model` class. */
122+
DataFlow::Node classModel() { result = classModel(DataFlow::TypeTracker::end()) }
123+
124+
/** Gets a definition of a subclass the `django.db.models.Model` class. */
125+
class ClassModelSubclassDef extends ControlFlowNode {
126+
string name;
127+
128+
ClassModelSubclassDef() {
129+
exists(ClassExpr ce |
130+
this.getNode() = ce and
131+
ce.getABase() = classModel().asExpr() and
132+
ce.getName() = name
133+
)
134+
}
135+
136+
string getName() { result = name }
137+
}
138+
139+
/**
140+
* A reference to a class that is a subclass of the `django.db.models.Model` class.
141+
* This is quite an approximation, since it simply matches identifiers.
142+
*/
143+
class ClassModelSubclass extends DataFlow::CfgNode {
144+
override NameNode node;
145+
146+
ClassModelSubclass() { node.getId() = any(ClassModelSubclassDef cd).getName() }
147+
}
148+
149+
/** Gets a reference to the `objects` object of a model. */
150+
private DataFlow::Node objects(DataFlow::TypeTracker t) {
151+
t.startInAttr("objects") and
152+
result instanceof ClassModelSubclass
153+
or
154+
exists(DataFlow::TypeTracker t2 | result = objects(t2).track(t2, t))
155+
}
156+
157+
/** Gets a reference to the `objects` object of a model. */
158+
DataFlow::Node objects() { result = objects(DataFlow::TypeTracker::end()) }
159+
160+
/**
161+
* Gets a reference to the attribute `attr_name` of an `objects` object.
162+
* WARNING: Only holds for a few predefined attributes.
163+
*/
164+
private DataFlow::Node objects_attr(DataFlow::TypeTracker t, string attr_name) {
165+
attr_name in ["annotate", "extra", "raw"] and
166+
t.startInAttr(attr_name) and
167+
result = objects()
168+
or
169+
// Due to bad performance when using normal setup with `objects_attr(t2, attr_name).track(t2, t)`
170+
// we have inlined that code and forced a join
171+
exists(DataFlow::TypeTracker t2 |
172+
exists(DataFlow::StepSummary summary |
173+
objects_attr_first_join(t2, attr_name, result, summary) and
174+
t = t2.append(summary)
175+
)
176+
)
177+
}
178+
179+
pragma[nomagic]
180+
private predicate objects_attr_first_join(
181+
DataFlow::TypeTracker t2, string attr_name, DataFlow::Node res,
182+
DataFlow::StepSummary summary
183+
) {
184+
DataFlow::StepSummary::step(objects_attr(t2, attr_name), res, summary)
185+
}
186+
187+
/**
188+
* Gets a reference to the attribute `attr_name` of an `objects` object.
189+
* WARNING: Only holds for a few predefined attributes.
190+
*/
191+
DataFlow::Node objects_attr(string attr_name) {
192+
result = objects_attr(DataFlow::TypeTracker::end(), attr_name)
193+
}
194+
195+
/** Gets a reference to the `django.db.models.expressions` object. */
196+
private DataFlow::Node expressions(DataFlow::TypeTracker t) {
197+
t.start() and
198+
result = DataFlow::importNode("django.db.models.expressions")
199+
or
200+
t.startInAttr("expressions") and
201+
result = models()
202+
or
203+
exists(DataFlow::TypeTracker t2 | result = expressions(t2).track(t2, t))
204+
}
205+
206+
/** Gets a reference to the `django.db.models.expressions` object. */
207+
DataFlow::Node expressions() { result = expressions(DataFlow::TypeTracker::end()) }
208+
209+
/** Gets a reference to the `django.db.models.expressions.RawSQL` class. */
210+
private DataFlow::Node classRawSQL(DataFlow::TypeTracker t) {
211+
t.start() and
212+
result = DataFlow::importNode("django.db.models.expressions.RawSQL")
213+
or
214+
t.startInAttr("RawSQL") and
215+
result = expressions()
216+
or
217+
exists(DataFlow::TypeTracker t2 | result = classRawSQL(t2).track(t2, t))
218+
}
219+
220+
/** Gets a reference to the `django.db.models.expressions.RawSQL` class. */
221+
DataFlow::Node classRawSQL() { result = classRawSQL(DataFlow::TypeTracker::end()) }
222+
}
93223
}
94224
}
95225

@@ -101,4 +231,36 @@ private module Django {
101231

102232
override DataFlow::Node getSql() { result.asCfgNode() = node.getArg(0) }
103233
}
234+
235+
/** A call to the `annotate` function on a model using a `RawSQL` argument. */
236+
private class ObjectsAnnotate extends SqlExecution::Range, DataFlow::CfgNode {
237+
override CallNode node;
238+
CallNode raw;
239+
240+
ObjectsAnnotate() {
241+
node.getFunction() = django::db::models::objects_attr("annotate").asCfgNode() and
242+
raw = node.getArg(0) and
243+
raw.getFunction() = django::db::models::classRawSQL().asCfgNode()
244+
}
245+
246+
override DataFlow::Node getSql() { result.asCfgNode() = raw.getArg(0) }
247+
}
248+
249+
/** A call to the `raw` function on a model. */
250+
private class ObjectsRaw extends SqlExecution::Range, DataFlow::CfgNode {
251+
override CallNode node;
252+
253+
ObjectsRaw() { node.getFunction() = django::db::models::objects_attr("raw").asCfgNode() }
254+
255+
override DataFlow::Node getSql() { result.asCfgNode() = node.getArg(0) }
256+
}
257+
258+
/** A call to the `raw` function on a model. */
259+
private class ObjectsExtra extends SqlExecution::Range, DataFlow::CfgNode {
260+
override CallNode node;
261+
262+
ObjectsExtra() { node.getFunction() = django::db::models::objects_attr("extra").asCfgNode() }
263+
264+
override DataFlow::Node getSql() { result.asCfgNode() = node.getArg(0) }
265+
}
104266
}

python/ql/test/experimental/library-tests/frameworks/django/SqlExecution.py

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
from django.db import connection, models
2+
from django.db.models.expressions import RawSQL
23

34

45
def test_plain(username):
@@ -16,3 +17,15 @@ def test_context(username):
1617

1718
# BAD -- Using string formatting
1819
cursor.execute("SELECT * FROM users WHERE username = '%s'" % username) # $getSql=BinaryExpr
20+
21+
class User(models.Model):
22+
pass
23+
24+
def test_model(username):
25+
# GOOD -- Using parameters
26+
User.objects.raw("SELECT * FROM users WHERE username = %s", (username,)) # $getSql="SELECT * FROM users WHERE username = %s"
27+
28+
# BAD -- other ways of executing raw SQL code with string interpolation
29+
User.objects.annotate(RawSQL("insert into names_file ('name') values ('%s')" % username)) # $getSql=BinaryExpr
30+
User.objects.raw("insert into names_file ('name') values ('%s')" % username) # $getSql=BinaryExpr
31+
User.objects.extra("insert into names_file ('name') values ('%s')" % username) # $getSql=BinaryExpr

0 commit comments

Comments
 (0)