Skip to content

Commit 2ffd79d

Browse files
authored
Merge pull request #7921 from erik-krogh/snapdragon
JS: add model for the snapdragon library
2 parents 987b11c + a4447ce commit 2ffd79d

File tree

7 files changed

+126
-1
lines changed

7 files changed

+126
-1
lines changed

javascript/ql/lib/javascript.qll

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -122,6 +122,7 @@ import semmle.javascript.frameworks.Request
122122
import semmle.javascript.frameworks.RxJS
123123
import semmle.javascript.frameworks.ServerLess
124124
import semmle.javascript.frameworks.ShellJS
125+
import semmle.javascript.frameworks.Snapdragon
125126
import semmle.javascript.frameworks.SystemCommandExecutors
126127
import semmle.javascript.frameworks.SQL
127128
import semmle.javascript.frameworks.SocketIO
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
/**
2+
* Provides classes for working with applications using [snapdragon](https://www.npmjs.com/package/snapdragon).
3+
*/
4+
5+
import javascript
6+
7+
/**
8+
* A module modeling taint steps for the [snapdragon](https://www.npmjs.com/package/snapdragon) library.
9+
*/
10+
private module Snapdragon {
11+
private API::Node getSetCall(API::Node base) { result = base.getMember("set").getReturn() }
12+
13+
/**
14+
* A taint step through the [snapdragon](https://www.npmjs.com/package/snapdragon) library.
15+
*
16+
* Models both parsing (converting a string to an AST) and compilation (converting an AST to a string).
17+
* For example:
18+
* ```JavaScript
19+
* var snapdragon = new (require("snapdragon"))();
20+
* snapdragon.parser.set("foo", function () {
21+
* sink(this); // <- sink
22+
* });
23+
* snapdragon.parse("source", opts); // <- source
24+
* ```
25+
*/
26+
private class SnapDragonStep extends DataFlow::SharedFlowStep {
27+
override predicate step(DataFlow::Node pred, DataFlow::Node succ) {
28+
exists(string methodName, API::CallNode set, API::CallNode call, API::Node base |
29+
// the handler, registered with a call to `.set`.
30+
set = getSetCall+(base.getMember(methodName + "r")).getAnImmediateUse() and
31+
// the snapdragon instance. The API is chaining, you can also use the instance directly.
32+
base = API::moduleImport("snapdragon").getInstance() and
33+
methodName = ["parse", "compile"] and
34+
(
35+
// snapdragon.parse(..)
36+
call = getSetCall*(base).getMember(methodName).getACall()
37+
or
38+
// snapdragon.parser.set().set().parse(..)
39+
call = getSetCall*(set.getReturn()).getMember(methodName).getACall()
40+
)
41+
|
42+
pred = call.getArgument(0) and
43+
(
44+
// for parsers handlers the input is the `this` pointer.
45+
methodName = "parse" and
46+
succ = DataFlow::thisNode(set.getCallback(1).getFunction())
47+
or
48+
// for compiler handlers the input is the first parameter.
49+
methodName = "compile" and
50+
succ = set.getParameter(1).getParameter(0).getAnImmediateUse()
51+
)
52+
)
53+
}
54+
}
55+
}
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
---
2+
category: minorAnalysis
3+
---
4+
* Added dataflow through the [`snapdragon`](https://npmjs.com/package/snapdragon) library.

javascript/ql/test/query-tests/Performance/ReDoS/PolynomialBackTracking.expected

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,9 @@
3434
| lib/lib.js:28:3:28:4 | f* | Strings with many repetitions of 'f' can start matching anywhere after the start of the preceeding f*g |
3535
| lib/moduleLib/moduleLib.js:2:3:2:4 | a* | Strings with many repetitions of 'a' can start matching anywhere after the start of the preceeding a*b |
3636
| lib/otherLib/js/src/index.js:2:3:2:4 | a* | Strings with many repetitions of 'a' can start matching anywhere after the start of the preceeding a*b |
37+
| lib/snapdragon.js:7:28:7:29 | a* | Strings starting with 'a' and with many repetitions of 'a' can start matching anywhere after the start of the preceeding aa*$ |
38+
| lib/snapdragon.js:15:26:15:27 | a* | Strings starting with 'a' and with many repetitions of 'a' can start matching anywhere after the start of the preceeding aa*$ |
39+
| lib/snapdragon.js:23:22:23:23 | a* | Strings starting with 'a' and with many repetitions of 'a' can start matching anywhere after the start of the preceeding aa*$ |
3740
| lib/sublib/factory.js:13:14:13:15 | f* | Strings with many repetitions of 'f' can start matching anywhere after the start of the preceeding f*g |
3841
| polynomial-redos.js:7:24:7:26 | \\s+ | Strings with many repetitions of ' ' can start matching anywhere after the start of the preceeding \\s+$ |
3942
| polynomial-redos.js:8:17:8:18 | * | Strings with many repetitions of ' ' can start matching anywhere after the start of the preceeding *, * |

javascript/ql/test/query-tests/Performance/ReDoS/PolynomialReDoS.expected

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,23 @@ nodes
3030
| lib/otherLib/js/src/index.js:1:28:1:31 | name |
3131
| lib/otherLib/js/src/index.js:2:13:2:16 | name |
3232
| lib/otherLib/js/src/index.js:2:13:2:16 | name |
33+
| lib/snapdragon.js:3:34:3:38 | input |
34+
| lib/snapdragon.js:3:34:3:38 | input |
35+
| lib/snapdragon.js:7:15:7:18 | this |
36+
| lib/snapdragon.js:7:15:7:18 | this |
37+
| lib/snapdragon.js:9:12:9:16 | input |
38+
| lib/snapdragon.js:12:34:12:38 | input |
39+
| lib/snapdragon.js:12:34:12:38 | input |
40+
| lib/snapdragon.js:15:13:15:16 | this |
41+
| lib/snapdragon.js:15:13:15:16 | this |
42+
| lib/snapdragon.js:17:20:17:24 | input |
43+
| lib/snapdragon.js:20:34:20:38 | input |
44+
| lib/snapdragon.js:20:34:20:38 | input |
45+
| lib/snapdragon.js:22:44:22:47 | node |
46+
| lib/snapdragon.js:23:5:23:8 | node |
47+
| lib/snapdragon.js:23:5:23:12 | node.val |
48+
| lib/snapdragon.js:23:5:23:12 | node.val |
49+
| lib/snapdragon.js:25:22:25:26 | input |
3350
| lib/sublib/factory.js:12:26:12:29 | name |
3451
| lib/sublib/factory.js:12:26:12:29 | name |
3552
| lib/sublib/factory.js:13:24:13:27 | name |
@@ -207,6 +224,20 @@ edges
207224
| lib/otherLib/js/src/index.js:1:28:1:31 | name | lib/otherLib/js/src/index.js:2:13:2:16 | name |
208225
| lib/otherLib/js/src/index.js:1:28:1:31 | name | lib/otherLib/js/src/index.js:2:13:2:16 | name |
209226
| lib/otherLib/js/src/index.js:1:28:1:31 | name | lib/otherLib/js/src/index.js:2:13:2:16 | name |
227+
| lib/snapdragon.js:3:34:3:38 | input | lib/snapdragon.js:9:12:9:16 | input |
228+
| lib/snapdragon.js:3:34:3:38 | input | lib/snapdragon.js:9:12:9:16 | input |
229+
| lib/snapdragon.js:9:12:9:16 | input | lib/snapdragon.js:7:15:7:18 | this |
230+
| lib/snapdragon.js:9:12:9:16 | input | lib/snapdragon.js:7:15:7:18 | this |
231+
| lib/snapdragon.js:12:34:12:38 | input | lib/snapdragon.js:17:20:17:24 | input |
232+
| lib/snapdragon.js:12:34:12:38 | input | lib/snapdragon.js:17:20:17:24 | input |
233+
| lib/snapdragon.js:17:20:17:24 | input | lib/snapdragon.js:15:13:15:16 | this |
234+
| lib/snapdragon.js:17:20:17:24 | input | lib/snapdragon.js:15:13:15:16 | this |
235+
| lib/snapdragon.js:20:34:20:38 | input | lib/snapdragon.js:25:22:25:26 | input |
236+
| lib/snapdragon.js:20:34:20:38 | input | lib/snapdragon.js:25:22:25:26 | input |
237+
| lib/snapdragon.js:22:44:22:47 | node | lib/snapdragon.js:23:5:23:8 | node |
238+
| lib/snapdragon.js:23:5:23:8 | node | lib/snapdragon.js:23:5:23:12 | node.val |
239+
| lib/snapdragon.js:23:5:23:8 | node | lib/snapdragon.js:23:5:23:12 | node.val |
240+
| lib/snapdragon.js:25:22:25:26 | input | lib/snapdragon.js:22:44:22:47 | node |
210241
| lib/sublib/factory.js:12:26:12:29 | name | lib/sublib/factory.js:13:24:13:27 | name |
211242
| lib/sublib/factory.js:12:26:12:29 | name | lib/sublib/factory.js:13:24:13:27 | name |
212243
| lib/sublib/factory.js:12:26:12:29 | name | lib/sublib/factory.js:13:24:13:27 | name |
@@ -355,6 +386,9 @@ edges
355386
| lib/lib.js:8:2:8:17 | /f*g/.test(name) | lib/lib.js:7:19:7:22 | name | lib/lib.js:8:13:8:16 | name | This $@ that depends on $@ may run slow on strings with many repetitions of 'f'. | lib/lib.js:8:3:8:4 | f* | regular expression | lib/lib.js:7:19:7:22 | name | library input |
356387
| lib/moduleLib/moduleLib.js:2:2:2:17 | /a*b/.test(name) | lib/moduleLib/moduleLib.js:1:28:1:31 | name | lib/moduleLib/moduleLib.js:2:13:2:16 | name | This $@ that depends on $@ may run slow on strings with many repetitions of 'a'. | lib/moduleLib/moduleLib.js:2:3:2:4 | a* | regular expression | lib/moduleLib/moduleLib.js:1:28:1:31 | name | library input |
357388
| lib/otherLib/js/src/index.js:2:2:2:17 | /a*b/.test(name) | lib/otherLib/js/src/index.js:1:28:1:31 | name | lib/otherLib/js/src/index.js:2:13:2:16 | name | This $@ that depends on $@ may run slow on strings with many repetitions of 'a'. | lib/otherLib/js/src/index.js:2:3:2:4 | a* | regular expression | lib/otherLib/js/src/index.js:1:28:1:31 | name | library input |
389+
| lib/snapdragon.js:7:15:7:32 | this.match(/aa*$/) | lib/snapdragon.js:3:34:3:38 | input | lib/snapdragon.js:7:15:7:18 | this | This $@ that depends on $@ may run slow on strings starting with 'a' and with many repetitions of 'a'. | lib/snapdragon.js:7:28:7:29 | a* | regular expression | lib/snapdragon.js:3:34:3:38 | input | library input |
390+
| lib/snapdragon.js:15:13:15:30 | this.match(/aa*$/) | lib/snapdragon.js:12:34:12:38 | input | lib/snapdragon.js:15:13:15:16 | this | This $@ that depends on $@ may run slow on strings starting with 'a' and with many repetitions of 'a'. | lib/snapdragon.js:15:26:15:27 | a* | regular expression | lib/snapdragon.js:12:34:12:38 | input | library input |
391+
| lib/snapdragon.js:23:5:23:26 | node.va ... /aa*$/) | lib/snapdragon.js:20:34:20:38 | input | lib/snapdragon.js:23:5:23:12 | node.val | This $@ that depends on $@ may run slow on strings starting with 'a' and with many repetitions of 'a'. | lib/snapdragon.js:23:22:23:23 | a* | regular expression | lib/snapdragon.js:20:34:20:38 | input | library input |
358392
| lib/sublib/factory.js:13:13:13:28 | /f*g/.test(name) | lib/sublib/factory.js:12:26:12:29 | name | lib/sublib/factory.js:13:24:13:27 | name | This $@ that depends on $@ may run slow on strings with many repetitions of 'f'. | lib/sublib/factory.js:13:14:13:15 | f* | regular expression | lib/sublib/factory.js:12:26:12:29 | name | library input |
359393
| polynomial-redos.js:7:2:7:34 | tainted ... /g, '') | polynomial-redos.js:5:16:5:32 | req.query.tainted | polynomial-redos.js:7:2:7:8 | tainted | This $@ that depends on $@ may run slow on strings with many repetitions of ' '. | polynomial-redos.js:7:24:7:26 | \\s+ | regular expression | polynomial-redos.js:5:16:5:32 | req.query.tainted | a user-provided value |
360394
| polynomial-redos.js:8:2:8:23 | tainted ... *, */) | polynomial-redos.js:5:16:5:32 | req.query.tainted | polynomial-redos.js:8:2:8:8 | tainted | This $@ that depends on $@ may run slow on strings with many repetitions of ' '. | polynomial-redos.js:8:17:8:18 | * | regular expression | polynomial-redos.js:5:16:5:32 | req.query.tainted | a user-provided value |

javascript/ql/test/query-tests/Performance/ReDoS/lib/lib.js

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,4 +26,6 @@ module.exports.id = id;
2626
module.exports.safe = function (x) {
2727
var y = id("safe");
2828
/f*g/.test(y); // OK
29-
}
29+
}
30+
31+
module.exports.snapdragon = require("./snapdragon")
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
var Snapdragon = require("snapdragon");
2+
3+
module.exports.test1 = function (input) {
4+
var snapdragon = new Snapdragon();
5+
var ast = snapdragon.parser
6+
.set("foo", function () {
7+
var m = this.match(/aa*$/); // NOT OK
8+
})
9+
.parse(input, options);
10+
};
11+
12+
module.exports.test2 = function (input) {
13+
var snapdragon = new Snapdragon();
14+
snapdragon.parser.set("foo", function () {
15+
var m = this.match(/aa*$/); // NOT OK
16+
});
17+
snapdragon.parse(input, options);
18+
};
19+
20+
module.exports.test3 = function (input) {
21+
var snapdragon = new Snapdragon();
22+
snapdragon.compiler.set("foo", function (node) {
23+
node.val.match(/aa*$/); // NOT OK
24+
});
25+
snapdragon.compile(input, options);
26+
};

0 commit comments

Comments
 (0)