Skip to content

Commit f7771f1

Browse files
committed
JS: Type track mysql model
1 parent 3e9849b commit f7771f1

File tree

4 files changed

+54
-32
lines changed

4 files changed

+54
-32
lines changed

javascript/ql/src/semmle/javascript/frameworks/SQL.qll

Lines changed: 45 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -28,38 +28,61 @@ module SQL {
2828
* Provides classes modelling the (API compatible) `mysql` and `mysql2` packages.
2929
*/
3030
private module MySql {
31-
/** Gets the package name `mysql` or `mysql2`. */
32-
string mysql() { result = "mysql" or result = "mysql2" }
31+
private DataFlow::SourceNode mysql() {
32+
result = DataFlow::moduleImport(["mysql", "mysql2"])
33+
}
3334

34-
/** Gets a call to `mysql.createConnection`. */
35-
DataFlow::SourceNode createConnection() {
36-
result = DataFlow::moduleMember(mysql(), "createConnection").getACall()
35+
private DataFlow::CallNode createPool() {
36+
result = mysql().getAMemberCall("createPool")
3737
}
3838

3939
/** Gets a call to `mysql.createPool`. */
40-
DataFlow::SourceNode createPool() {
41-
result = DataFlow::moduleMember(mysql(), "createPool").getACall()
40+
private DataFlow::SourceNode pool(DataFlow::TypeTracker t) {
41+
t.start() and
42+
result = createPool()
43+
or
44+
exists(DataFlow::TypeTracker t2 |
45+
result = pool(t2).track(t2, t)
46+
)
47+
}
48+
49+
/** Gets a call to `mysql.createPool`. */
50+
private DataFlow::SourceNode pool() {
51+
result = pool(DataFlow::TypeTracker::end())
52+
}
53+
54+
/** Gets a call to `mysql.createConnection`. */
55+
DataFlow::CallNode createConnection() {
56+
result = mysql().getAMemberCall("createConnection")
4257
}
4358

4459
/** Gets a data flow node that contains a freshly created MySQL connection instance. */
45-
DataFlow::SourceNode connection() {
46-
result = createConnection()
60+
private DataFlow::SourceNode connection(DataFlow::TypeTracker t) {
61+
t.start() and
62+
(
63+
result = createConnection()
64+
or
65+
result = pool().getAMethodCall("getConnection").getABoundCallbackParameter(0, 1)
66+
)
4767
or
48-
result = createPool().getAMethodCall("getConnection").getCallback(0).getParameter(1)
68+
exists(DataFlow::TypeTracker t2 |
69+
result = connection(t2).track(t2, t)
70+
)
4971
}
5072

51-
/** A call to the MySql `query` method. */
52-
private class QueryCall extends DatabaseAccess, DataFlow::ValueNode {
53-
override MethodCallExpr astNode;
73+
/** Gets a data flow node that contains a freshly created MySQL connection instance. */
74+
DataFlow::SourceNode connection() {
75+
result = connection(DataFlow::TypeTracker::end())
76+
}
5477

78+
/** A call to the MySql `query` method. */
79+
private class QueryCall extends DatabaseAccess, DataFlow::MethodCallNode {
5580
QueryCall() {
56-
exists(DataFlow::SourceNode recv | recv = createPool() or recv = connection() |
57-
this = recv.getAMethodCall("query")
58-
)
81+
this = [pool(), connection()].getAMethodCall("query")
5982
}
6083

6184
override DataFlow::Node getAQueryArgument() {
62-
result = DataFlow::valueNode(astNode.getArgument(0))
85+
result = getArgument(0)
6386
}
6487
}
6588

@@ -71,18 +94,9 @@ private module MySql {
7194
/** A call to the `escape` or `escapeId` method that performs SQL sanitization. */
7295
class EscapingSanitizer extends SQL::SqlSanitizer, @callexpr {
7396
EscapingSanitizer() {
74-
exists(string esc | esc = "escape" or esc = "escapeId" |
75-
exists(DataFlow::SourceNode escape, MethodCallExpr mce |
76-
escape = DataFlow::moduleMember(mysql(), esc) or
77-
escape = connection().getAPropertyRead(esc) or
78-
escape = createPool().getAPropertyRead(esc)
79-
|
80-
this = mce and
81-
mce = escape.getACall().asExpr() and
82-
input = mce.getArgument(0) and
83-
output = mce
84-
)
85-
)
97+
this = [mysql(), pool(), connection()].getAMemberCall(["escape", "escapeId"]).asExpr() and
98+
input = this.(MethodCallExpr).getArgument(0) and
99+
output = this
86100
}
87101
}
88102

@@ -91,9 +105,8 @@ private module MySql {
91105
string kind;
92106

93107
Credentials() {
94-
exists(DataFlow::SourceNode call, string prop |
95-
(call = createConnection() or call = createPool()) and
96-
call.asExpr().(CallExpr).hasOptionArgument(0, prop, this) and
108+
exists(string prop |
109+
this = [createConnection(), createPool()].getOptionArgument(0, prop).asExpr() and
97110
(
98111
prop = "user" and kind = "user name"
99112
or

javascript/ql/test/library-tests/frameworks/SQL/SqlString.expected

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
| mysql2tst.js:23:3:23:56 | 'SELECT ... e` > ?' |
1010
| mysql3.js:14:20:14:52 | 'SELECT ... etable' |
1111
| mysql4.js:14:18:14:20 | sql |
12+
| mysqlImport.js:3:18:5:1 | {\\n s ... = ?',\\n} |
1213
| postgres1.js:37:21:37:24 | text |
1314
| postgres2.js:30:16:30:41 | 'SELECT ... number' |
1415
| postgres3.js:15:16:15:40 | 'SELECT ... s name' |

javascript/ql/test/library-tests/frameworks/SQL/mysql1.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,3 +26,5 @@ connection.query({
2626
});
2727

2828
connection.end();
29+
30+
exports.connection = connection;
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
const { connection } = require("./mysql1");
2+
3+
connection.query({
4+
sql: 'SELECT * FROM `books` WHERE `author` = ?',
5+
}, function (error, results, fields) {
6+
});

0 commit comments

Comments
 (0)