Skip to content

Commit 5efc19c

Browse files
authored
Merge pull request #7806 from erik-krogh/pyDef
Python: Add def nodes to API graphs
2 parents 8faabb8 + 7691807 commit 5efc19c

38 files changed

+550
-355
lines changed

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

Lines changed: 269 additions & 20 deletions
Large diffs are not rendered by default.

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
* A query. 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
* An awaited query. 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
* A query. 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
* An awaited query. 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
* A query. 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
* An awaited query. 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
* A query. 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
* An awaited query. 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
}

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

Lines changed: 31 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -71,48 +71,27 @@ private module Asyncpg {
7171
* The result of calling `prepare(query)` is a `PreparedStatementFactory` and the argument, `query` needs to
7272
* be tracked to the place where a `PreparedStatement` is created and then futher to any executing methods.
7373
* Hence the two type trackers.
74-
*
75-
* TODO: Rewrite this, once we have `API::CallNode` available.
7674
*/
7775
module PreparedStatement {
78-
class PreparedStatementConstruction extends SqlConstruction::Range, DataFlow::CallCfgNode {
76+
class PreparedStatementConstruction extends SqlConstruction::Range, API::CallNode {
7977
PreparedStatementConstruction() { this = connection().getMember("prepare").getACall() }
8078

81-
override DataFlow::Node getSql() { result in [this.getArg(0), this.getArgByName("query")] }
79+
override DataFlow::Node getSql() { result = this.getParameter(0, "query").getARhs() }
8280
}
8381

84-
private DataFlow::TypeTrackingNode preparedStatementFactory(
85-
DataFlow::TypeTracker t, DataFlow::Node sql
86-
) {
87-
t.start() and
88-
sql = result.(PreparedStatementConstruction).getSql()
89-
or
90-
exists(DataFlow::TypeTracker t2 | result = preparedStatementFactory(t2, sql).track(t2, t))
91-
}
92-
93-
DataFlow::Node preparedStatementFactory(DataFlow::Node sql) {
94-
preparedStatementFactory(DataFlow::TypeTracker::end(), sql).flowsTo(result)
95-
}
96-
97-
private DataFlow::TypeTrackingNode preparedStatement(DataFlow::TypeTracker t, DataFlow::Node sql) {
98-
t.start() and
99-
result = awaited(preparedStatementFactory(sql))
100-
or
101-
exists(DataFlow::TypeTracker t2 | result = preparedStatement(t2, sql).track(t2, t))
102-
}
103-
104-
DataFlow::Node preparedStatement(DataFlow::Node sql) {
105-
preparedStatement(DataFlow::TypeTracker::end(), sql).flowsTo(result)
106-
}
107-
108-
class PreparedStatementExecution extends SqlExecution::Range, DataFlow::MethodCallNode {
109-
DataFlow::Node sql;
82+
class PreparedStatementExecution extends SqlExecution::Range, API::CallNode {
83+
PreparedStatementConstruction prepareCall;
11084

11185
PreparedStatementExecution() {
112-
this.calls(preparedStatement(sql), ["executemany", "fetch", "fetchrow", "fetchval"])
86+
this =
87+
prepareCall
88+
.getReturn()
89+
.getAwaited()
90+
.getMember(["executemany", "fetch", "fetchrow", "fetchval"])
91+
.getACall()
11392
}
11493

115-
override DataFlow::Node getSql() { result = sql }
94+
override DataFlow::Node getSql() { result = prepareCall.getSql() }
11695
}
11796
}
11897

@@ -124,37 +103,36 @@ private module Asyncpg {
124103
* The result of calling `cursor` in either case is a `CursorFactory` and the argument, `query` needs to
125104
* be tracked to the place where a `Cursor` is created, hence the type tracker.
126105
* The creation of the `Cursor` executes the query.
127-
*
128-
* TODO: Rewrite this, once we have `API::CallNode` available.
129106
*/
130107
module Cursor {
131-
class CursorConstruction extends SqlConstruction::Range, DataFlow::CallCfgNode {
108+
class CursorConstruction extends SqlConstruction::Range, API::CallNode {
132109
CursorConstruction() { this = connection().getMember("cursor").getACall() }
133110

134-
override DataFlow::Node getSql() { result in [this.getArg(0), this.getArgByName("query")] }
135-
}
136-
137-
private DataFlow::TypeTrackingNode cursorFactory(DataFlow::TypeTracker t, DataFlow::Node sql) {
138-
// cursor created from connection
139-
t.start() and
140-
sql = result.(CursorConstruction).getSql()
141-
or
142-
// cursor created from prepared statement
143-
t.start() and
144-
result.(DataFlow::MethodCallNode).calls(PreparedStatement::preparedStatement(sql), "cursor")
145-
or
146-
exists(DataFlow::TypeTracker t2 | result = cursorFactory(t2, sql).track(t2, t))
147-
}
148-
149-
DataFlow::Node cursorFactory(DataFlow::Node sql) {
150-
cursorFactory(DataFlow::TypeTracker::end(), sql).flowsTo(result)
111+
override DataFlow::Node getSql() { result = this.getParameter(0, "query").getARhs() }
151112
}
152113

153114
/** The creation of a `Cursor` executes the associated query. */
154115
class CursorCreation extends SqlExecution::Range {
155116
DataFlow::Node sql;
156117

157-
CursorCreation() { this = awaited(cursorFactory(sql)) }
118+
CursorCreation() {
119+
exists(CursorConstruction c |
120+
sql = c.getSql() and
121+
this = c.getReturn().getAwaited().getAnImmediateUse()
122+
)
123+
or
124+
exists(PreparedStatement::PreparedStatementConstruction prepareCall |
125+
sql = prepareCall.getSql() and
126+
this =
127+
prepareCall
128+
.getReturn()
129+
.getAwaited()
130+
.getMember("cursor")
131+
.getReturn()
132+
.getAwaited()
133+
.getAnImmediateUse()
134+
)
135+
}
158136

159137
override DataFlow::Node getSql() { result = sql }
160138
}

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

Lines changed: 3 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ private import semmle.python.frameworks.Stdlib
2424
* - https://docs.python-requests.org/en/latest/
2525
*/
2626
private module Requests {
27-
private class OutgoingRequestCall extends HTTP::Client::Request::Range, DataFlow::CallCfgNode {
27+
private class OutgoingRequestCall extends HTTP::Client::Request::Range, API::CallNode {
2828
string methodName;
2929

3030
OutgoingRequestCall() {
@@ -54,14 +54,11 @@ private module Requests {
5454
result = this.getArg(1)
5555
}
5656

57-
/** Gets the `verify` argument to this outgoing requests call. */
58-
DataFlow::Node getVerifyArg() { result = this.getArgByName("verify") }
59-
6057
override predicate disablesCertificateValidation(
6158
DataFlow::Node disablingNode, DataFlow::Node argumentOrigin
6259
) {
63-
disablingNode = this.getVerifyArg() and
64-
argumentOrigin = verifyArgBacktracker(disablingNode) and
60+
disablingNode = this.getKeywordParameter("verify").getARhs() and
61+
argumentOrigin = this.getKeywordParameter("verify").getAValueReachingRhs() and
6562
argumentOrigin.asExpr().(ImmutableLiteral).booleanValue() = false and
6663
not argumentOrigin.asExpr() instanceof None
6764
}
@@ -79,22 +76,6 @@ private module Requests {
7976
}
8077
}
8178

82-
/** Gets a back-reference to the verify argument `arg`. */
83-
private DataFlow::TypeTrackingNode verifyArgBacktracker(
84-
DataFlow::TypeBackTracker t, DataFlow::Node arg
85-
) {
86-
t.start() and
87-
arg = any(OutgoingRequestCall c).getVerifyArg() and
88-
result = arg.getALocalSource()
89-
or
90-
exists(DataFlow::TypeBackTracker t2 | result = verifyArgBacktracker(t2, arg).backtrack(t2, t))
91-
}
92-
93-
/** Gets a back-reference to the verify argument `arg`. */
94-
private DataFlow::LocalSourceNode verifyArgBacktracker(DataFlow::Node arg) {
95-
result = verifyArgBacktracker(DataFlow::TypeBackTracker::end(), arg)
96-
}
97-
9879
// ---------------------------------------------------------------------------
9980
// Response
10081
// ---------------------------------------------------------------------------

0 commit comments

Comments
 (0)