Skip to content

Commit e434f07

Browse files
committed
introduce, and use, API::APICallNode
1 parent 3801a15 commit e434f07

File tree

7 files changed

+144
-267
lines changed

7 files changed

+144
-267
lines changed

python/ql/lib/semmle/python/ApiGraphs.qll

Lines changed: 77 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,8 @@
66
* directed and labeled; they specify how the components represented by nodes relate to each other.
77
*/
88

9-
private import python
9+
// Importing python under the `py` namespace to avoid importing `CallNode` from `Flow.qll` and thereby having a naming conflict with `API::CallNode`.
10+
private import python as py
1011
import semmle.python.dataflow.new.DataFlow
1112

1213
/**
@@ -79,7 +80,7 @@ module API {
7980
/**
8081
* Gets a call to the function represented by this API component.
8182
*/
82-
DataFlow::CallCfgNode getACall() { result = this.getReturn().getAnImmediateUse() } // TODO: Make a API::CallNode. After I figure out named parameters
83+
CallNode getACall() { result = this.getReturn().getAnImmediateUse() }
8384

8485
/**
8586
* Gets a node representing member `m` of this API component.
@@ -289,6 +290,65 @@ module API {
289290
/** Gets a node corresponding to the built-in with the given name, if any. */
290291
Node builtin(string n) { result = moduleImport("builtins").getMember(n) }
291292

293+
/**
294+
* An `CallCfgNode` that is connected to the API graph.
295+
*
296+
* Can be used to reason about calls to an external API in which the correlation between
297+
* parameters and/or return values must be retained.
298+
*
299+
* The member predicates `getParameter`, `getNamedParameter`, `getReturn`, and `getInstance` mimic
300+
* the corresponding predicates from `API::Node`. These are guaranteed to exist and be unique to this call.
301+
*/
302+
class CallNode extends DataFlow::CallCfgNode {
303+
API::Node callee;
304+
305+
CallNode() { this = callee.getReturn().getAnImmediateUse() }
306+
307+
/** Gets the API node for the `i`th parameter of this invocation. */
308+
pragma[nomagic]
309+
Node getParameter(int i) {
310+
result = callee.getParameter(i) and
311+
result = this.getAParameterCandidate(i)
312+
}
313+
314+
/**
315+
* Gets an API node where a RHS of the node is the `i`th argument to this call.
316+
*/
317+
pragma[noinline]
318+
private Node getAParameterCandidate(int i) { result.getARhs() = this.getArg(i) }
319+
320+
/** Gets the API node for a parameter of this invocation. */
321+
Node getAParameter() { result = this.getParameter(_) }
322+
323+
/** Gets the API node for the last parameter of this invocation. */
324+
Node getLastParameter() { result = this.getParameter(max(int i | exists(this.getArg(i)))) }
325+
326+
/** Gets the API node for the parameter named `name` of this invocation. */
327+
Node getNamedParameter(string name) {
328+
result = callee.getNamedParameter(name) and
329+
result = this.getANamedParameterCandidate(name)
330+
}
331+
332+
/** Gets the API node for the parameter that has index `i` or is named `name`. */
333+
bindingset[i, name]
334+
Node getParameter(int i, string name) {
335+
result = this.getParameter(i)
336+
or
337+
result = this.getNamedParameter(name)
338+
}
339+
340+
pragma[noinline]
341+
private Node getANamedParameterCandidate(string name) {
342+
result.getARhs() = this.getArgByName(name)
343+
}
344+
345+
/** Gets the API node for the return value of this call. */
346+
Node getReturn() {
347+
result = callee.getReturn() and
348+
result.getAnImmediateUse() = this
349+
}
350+
}
351+
292352
/**
293353
* Provides the actual implementation of API graphs, cached for performance.
294354
*
@@ -376,13 +436,13 @@ module API {
376436
/** An abstract representative for imports of the module called `name`. */
377437
MkModuleImport(string name) {
378438
// Ignore the following module name for Python 2, as we alias `__builtin__` to `builtins` elsewhere
379-
(name != "__builtin__" or major_version() = 3) and
439+
(name != "__builtin__" or py::major_version() = 3) and
380440
(
381441
imports(_, name)
382442
or
383443
// When we `import foo.bar.baz` we want to create API graph nodes also for the prefixes
384444
// `foo` and `foo.bar`:
385-
name = any(ImportExpr e | not e.isRelative()).getAnImportedModuleName()
445+
name = any(py::ImportExpr e | not e.isRelative()).getAnImportedModuleName()
386446
)
387447
or
388448
// The `builtins` module should always be implicitly available
@@ -418,7 +478,7 @@ module API {
418478
* Ignores relative imports, such as `from ..foo.bar import baz`.
419479
*/
420480
private predicate imports(DataFlow::Node imp, string name) {
421-
exists(ImportExprNode iexpr |
481+
exists(py::ImportExprNode iexpr |
422482
imp.asCfgNode() = iexpr and
423483
not iexpr.getNode().isRelative() and
424484
name = iexpr.getNode().getImportedModuleName()
@@ -441,7 +501,7 @@ module API {
441501
*
442502
* `moduleImport("foo").getMember("bar")`
443503
*/
444-
private TApiNode potential_import_star_base(Scope s) {
504+
private TApiNode potential_import_star_base(py::Scope s) {
445505
exists(DataFlow::Node n |
446506
n.asCfgNode() = ImportStar::potentialImportStarBase(s) and
447507
use(result, n)
@@ -464,17 +524,17 @@ module API {
464524
)
465525
or
466526
// TODO: I had expected `DataFlow::AttrWrite` to contain the attribute writes from a dict, that's how JS works.
467-
exists(Dict dict, KeyValuePair item |
527+
exists(py::Dict dict, py::KeyValuePair item |
468528
dict = pred.asExpr() and
469529
dict.getItem(_) = item and
470-
lbl = Label::member(item.getKey().(StrConst).getS()) and
530+
lbl = Label::member(item.getKey().(py::StrConst).getS()) and
471531
rhs.asExpr() = item.getValue()
472532
)
473533
or
474-
exists(CallableExpr fn | fn = pred.asExpr() |
534+
exists(py::CallableExpr fn | fn = pred.asExpr() |
475535
not fn.getInnerScope().isAsync() and
476536
lbl = Label::return() and
477-
exists(Return ret |
537+
exists(py::Return ret |
478538
rhs.asExpr() = ret.getValue() and
479539
ret.getScope() = fn.getInnerScope()
480540
)
@@ -517,7 +577,7 @@ module API {
517577
// Subclassing a node
518578
lbl = Label::subclass() and
519579
exists(DataFlow::Node superclass | pred.flowsTo(superclass) |
520-
ref.asExpr().(ClassExpr).getABase() = superclass.asExpr()
580+
ref.asExpr().(py::ClassExpr).getABase() = superclass.asExpr()
521581
)
522582
or
523583
// awaiting
@@ -528,7 +588,7 @@ module API {
528588
)
529589
)
530590
or
531-
exists(DataFlow::Node def, CallableExpr fn |
591+
exists(DataFlow::Node def, py::CallableExpr fn |
532592
rhs(base, def) and fn = trackDefNode(def).asExpr()
533593
|
534594
exists(int i |
@@ -547,7 +607,7 @@ module API {
547607
lbl = Label::member(any(string name | ref = Builtins::likelyBuiltin(name)))
548608
or
549609
// Unknown variables that may belong to a module imported with `import *`
550-
exists(Scope s |
610+
exists(py::Scope s |
551611
base = potential_import_star_base(s) and
552612
lbl =
553613
Label::member(any(string name |
@@ -567,7 +627,7 @@ module API {
567627
)
568628
or
569629
// Ensure the Python 2 `__builtin__` module gets the name of the Python 3 `builtins` module.
570-
major_version() = 2 and
630+
py::major_version() = 2 and
571631
nd = MkModuleImport("builtins") and
572632
imports(ref, "__builtin__")
573633
or
@@ -710,18 +770,18 @@ module API {
710770
exists(Builtins::likelyBuiltin(member)) or
711771
ImportStar::namePossiblyDefinedInImportStar(_, member, _) or
712772
Impl::prefix_member(_, member, _) or
713-
member = any(Dict d).getAnItem().(KeyValuePair).getKey().(StrConst).getS()
773+
member = any(py::Dict d).getAnItem().(py::KeyValuePair).getKey().(py::StrConst).getS()
714774
} or
715775
MkLabelUnknownMember() or
716776
MkLabelParameter(int i) {
717777
exists(any(DataFlow::CallCfgNode c).getArg(i))
718778
or
719-
exists(any(Function f).getArg(i))
779+
exists(any(py::Function f).getArg(i))
720780
} or
721781
MkLabelNamedParameter(string name) {
722782
exists(any(DataFlow::CallCfgNode c).getArgByName(name))
723783
or
724-
exists(any(Function f).getArgByName(name))
784+
exists(any(py::Function f).getArgByName(name))
725785
} or
726786
MkLabelReturn() or
727787
MkLabelSubclass() or

python/ql/lib/semmle/python/frameworks/Aiomysql.qll

Lines changed: 10 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -50,38 +50,22 @@ private module Aiomysql {
5050
* Calling `execute` on a `Cursor` constructs a query.
5151
* See https://aiomysql.readthedocs.io/en/stable/cursors.html#Cursor.execute
5252
*/
53-
class CursorExecuteCall extends SqlConstruction::Range, DataFlow::CallCfgNode {
53+
class CursorExecuteCall extends SqlConstruction::Range, API::CallNode {
5454
CursorExecuteCall() { this = cursor().getMember("execute").getACall() }
5555

56-
override DataFlow::Node getSql() { result in [this.getArg(0), this.getArgByName("operation")] }
57-
}
58-
59-
/**
60-
* This is only needed to connect the argument to the execute call with the subsequnt awaiting.
61-
* It should be obsolete once we have `API::CallNode` available.
62-
*/
63-
private DataFlow::TypeTrackingNode cursorExecuteCall(DataFlow::TypeTracker t, DataFlow::Node sql) {
64-
// cursor created from connection
65-
t.start() and
66-
sql = result.(CursorExecuteCall).getSql()
67-
or
68-
exists(DataFlow::TypeTracker t2 | result = cursorExecuteCall(t2, sql).track(t2, t))
69-
}
70-
71-
DataFlow::Node cursorExecuteCall(DataFlow::Node sql) {
72-
cursorExecuteCall(DataFlow::TypeTracker::end(), sql).flowsTo(result)
56+
override DataFlow::Node getSql() { result = this.getParameter(0, "operation").getARhs() }
7357
}
7458

7559
/**
7660
* Awaiting the result of calling `execute` executes the query.
7761
* See https://aiomysql.readthedocs.io/en/stable/cursors.html#Cursor.execute
7862
*/
7963
class AwaitedCursorExecuteCall extends SqlExecution::Range {
80-
DataFlow::Node sql;
64+
CursorExecuteCall executeCall;
8165

82-
AwaitedCursorExecuteCall() { this = awaited(cursorExecuteCall(sql)) }
66+
AwaitedCursorExecuteCall() { this = executeCall.getReturn().getAwaited().getAnImmediateUse() }
8367

84-
override DataFlow::Node getSql() { result = sql }
68+
override DataFlow::Node getSql() { result = executeCall.getSql() }
8569
}
8670

8771
/**
@@ -107,39 +91,21 @@ private module Aiomysql {
10791
* Calling `execute` on a `SAConnection` constructs a query.
10892
* See https://aiomysql.readthedocs.io/en/stable/sa.html#aiomysql.sa.SAConnection.execute
10993
*/
110-
class SAConnectionExecuteCall extends SqlConstruction::Range, DataFlow::CallCfgNode {
94+
class SAConnectionExecuteCall extends SqlConstruction::Range, API::CallNode {
11195
SAConnectionExecuteCall() { this = saConnection().getMember("execute").getACall() }
11296

113-
override DataFlow::Node getSql() { result in [this.getArg(0), this.getArgByName("query")] }
114-
}
115-
116-
/**
117-
* This is only needed to connect the argument to the execute call with the subsequnt awaiting.
118-
* It should be obsolete once we have `API::CallNode` available.
119-
*/
120-
private DataFlow::TypeTrackingNode saConnectionExecuteCall(
121-
DataFlow::TypeTracker t, DataFlow::Node sql
122-
) {
123-
// saConnection created from engine
124-
t.start() and
125-
sql = result.(SAConnectionExecuteCall).getSql()
126-
or
127-
exists(DataFlow::TypeTracker t2 | result = saConnectionExecuteCall(t2, sql).track(t2, t))
128-
}
129-
130-
DataFlow::Node saConnectionExecuteCall(DataFlow::Node sql) {
131-
saConnectionExecuteCall(DataFlow::TypeTracker::end(), sql).flowsTo(result)
97+
override DataFlow::Node getSql() { result = this.getParameter(0, "query").getARhs() }
13298
}
13399

134100
/**
135101
* Awaiting the result of calling `execute` executes the query.
136102
* See https://aiomysql.readthedocs.io/en/stable/sa.html#aiomysql.sa.SAConnection.execute
137103
*/
138104
class AwaitedSAConnectionExecuteCall extends SqlExecution::Range {
139-
DataFlow::Node sql;
105+
SAConnectionExecuteCall execute;
140106

141-
AwaitedSAConnectionExecuteCall() { this = awaited(saConnectionExecuteCall(sql)) }
107+
AwaitedSAConnectionExecuteCall() { this = execute.getReturn().getAwaited().getAnImmediateUse() }
142108

143-
override DataFlow::Node getSql() { result = sql }
109+
override DataFlow::Node getSql() { result = execute.getSql() }
144110
}
145111
}

python/ql/lib/semmle/python/frameworks/Aiopg.qll

Lines changed: 10 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -50,38 +50,22 @@ private module Aiopg {
5050
* Calling `execute` on a `Cursor` constructs a query.
5151
* See https://aiopg.readthedocs.io/en/stable/core.html#aiopg.Cursor.execute
5252
*/
53-
class CursorExecuteCall extends SqlConstruction::Range, DataFlow::CallCfgNode {
53+
class CursorExecuteCall extends SqlConstruction::Range, API::CallNode {
5454
CursorExecuteCall() { this = cursor().getMember("execute").getACall() }
5555

56-
override DataFlow::Node getSql() { result in [this.getArg(0), this.getArgByName("operation")] }
57-
}
58-
59-
/**
60-
* This is only needed to connect the argument to the execute call with the subsequnt awaiting.
61-
* It should be obsolete once we have `API::CallNode` available.
62-
*/
63-
private DataFlow::TypeTrackingNode cursorExecuteCall(DataFlow::TypeTracker t, DataFlow::Node sql) {
64-
// cursor created from connection
65-
t.start() and
66-
sql = result.(CursorExecuteCall).getSql()
67-
or
68-
exists(DataFlow::TypeTracker t2 | result = cursorExecuteCall(t2, sql).track(t2, t))
69-
}
70-
71-
DataFlow::Node cursorExecuteCall(DataFlow::Node sql) {
72-
cursorExecuteCall(DataFlow::TypeTracker::end(), sql).flowsTo(result)
56+
override DataFlow::Node getSql() { result = this.getParameter(0, "operation").getARhs() }
7357
}
7458

7559
/**
7660
* Awaiting the result of calling `execute` executes the query.
7761
* See https://aiopg.readthedocs.io/en/stable/core.html#aiopg.Cursor.execute
7862
*/
7963
class AwaitedCursorExecuteCall extends SqlExecution::Range {
80-
DataFlow::Node sql;
64+
CursorExecuteCall execute;
8165

82-
AwaitedCursorExecuteCall() { this = awaited(cursorExecuteCall(sql)) }
66+
AwaitedCursorExecuteCall() { this = execute.getReturn().getAwaited().getAnImmediateUse() }
8367

84-
override DataFlow::Node getSql() { result = sql }
68+
override DataFlow::Node getSql() { result = execute.getSql() }
8569
}
8670

8771
/**
@@ -103,39 +87,21 @@ private module Aiopg {
10387
* Calling `execute` on a `SAConnection` constructs a query.
10488
* See https://aiopg.readthedocs.io/en/stable/sa.html#aiopg.sa.SAConnection.execute
10589
*/
106-
class SAConnectionExecuteCall extends SqlConstruction::Range, DataFlow::CallCfgNode {
90+
class SAConnectionExecuteCall extends SqlConstruction::Range, API::CallNode {
10791
SAConnectionExecuteCall() { this = saConnection().getMember("execute").getACall() }
10892

109-
override DataFlow::Node getSql() { result in [this.getArg(0), this.getArgByName("query")] }
110-
}
111-
112-
/**
113-
* This is only needed to connect the argument to the execute call with the subsequnt awaiting.
114-
* It should be obsolete once we have `API::CallNode` available.
115-
*/
116-
private DataFlow::TypeTrackingNode saConnectionExecuteCall(
117-
DataFlow::TypeTracker t, DataFlow::Node sql
118-
) {
119-
// saConnection created from engine
120-
t.start() and
121-
sql = result.(SAConnectionExecuteCall).getSql()
122-
or
123-
exists(DataFlow::TypeTracker t2 | result = saConnectionExecuteCall(t2, sql).track(t2, t))
124-
}
125-
126-
DataFlow::Node saConnectionExecuteCall(DataFlow::Node sql) {
127-
saConnectionExecuteCall(DataFlow::TypeTracker::end(), sql).flowsTo(result)
93+
override DataFlow::Node getSql() { result = this.getParameter(0, "query").getARhs() }
12894
}
12995

13096
/**
13197
* Awaiting the result of calling `execute` executes the query.
13298
* See https://aiopg.readthedocs.io/en/stable/sa.html#aiopg.sa.SAConnection.execute
13399
*/
134100
class AwaitedSAConnectionExecuteCall extends SqlExecution::Range {
135-
DataFlow::Node sql;
101+
SAConnectionExecuteCall excute;
136102

137-
AwaitedSAConnectionExecuteCall() { this = awaited(saConnectionExecuteCall(sql)) }
103+
AwaitedSAConnectionExecuteCall() { this = excute.getReturn().getAwaited().getAnImmediateUse() }
138104

139-
override DataFlow::Node getSql() { result = sql }
105+
override DataFlow::Node getSql() { result = excute.getSql() }
140106
}
141107
}

0 commit comments

Comments
 (0)