Merge pull request #7921 from erik-krogh/snapdragon

JS: add model for the snapdragon library
This commit is contained in:
Erik Krogh Kristensen
2022-02-11 10:10:55 +01:00
committed by GitHub
7 changed files with 126 additions and 1 deletions

View File

@@ -122,6 +122,7 @@ import semmle.javascript.frameworks.Request
import semmle.javascript.frameworks.RxJS
import semmle.javascript.frameworks.ServerLess
import semmle.javascript.frameworks.ShellJS
import semmle.javascript.frameworks.Snapdragon
import semmle.javascript.frameworks.SystemCommandExecutors
import semmle.javascript.frameworks.SQL
import semmle.javascript.frameworks.SocketIO

View File

@@ -0,0 +1,55 @@
/**
* Provides classes for working with applications using [snapdragon](https://www.npmjs.com/package/snapdragon).
*/
import javascript
/**
* A module modeling taint steps for the [snapdragon](https://www.npmjs.com/package/snapdragon) library.
*/
private module Snapdragon {
private API::Node getSetCall(API::Node base) { result = base.getMember("set").getReturn() }
/**
* A taint step through the [snapdragon](https://www.npmjs.com/package/snapdragon) library.
*
* Models both parsing (converting a string to an AST) and compilation (converting an AST to a string).
* For example:
* ```JavaScript
* var snapdragon = new (require("snapdragon"))();
* snapdragon.parser.set("foo", function () {
* sink(this); // <- sink
* });
* snapdragon.parse("source", opts); // <- source
* ```
*/
private class SnapDragonStep extends DataFlow::SharedFlowStep {
override predicate step(DataFlow::Node pred, DataFlow::Node succ) {
exists(string methodName, API::CallNode set, API::CallNode call, API::Node base |
// the handler, registered with a call to `.set`.
set = getSetCall+(base.getMember(methodName + "r")).getAnImmediateUse() and
// the snapdragon instance. The API is chaining, you can also use the instance directly.
base = API::moduleImport("snapdragon").getInstance() and
methodName = ["parse", "compile"] and
(
// snapdragon.parse(..)
call = getSetCall*(base).getMember(methodName).getACall()
or
// snapdragon.parser.set().set().parse(..)
call = getSetCall*(set.getReturn()).getMember(methodName).getACall()
)
|
pred = call.getArgument(0) and
(
// for parsers handlers the input is the `this` pointer.
methodName = "parse" and
succ = DataFlow::thisNode(set.getCallback(1).getFunction())
or
// for compiler handlers the input is the first parameter.
methodName = "compile" and
succ = set.getParameter(1).getParameter(0).getAnImmediateUse()
)
)
}
}
}

View File

@@ -0,0 +1,4 @@
---
category: minorAnalysis
---
* Added dataflow through the [`snapdragon`](https://npmjs.com/package/snapdragon) library.

View File

@@ -34,6 +34,9 @@
| 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 |
| 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 |
| 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 |
| 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*$ |
| 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*$ |
| 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*$ |
| 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 |
| polynomial-redos.js:7:24:7:26 | \\s+ | Strings with many repetitions of ' ' can start matching anywhere after the start of the preceeding \\s+$ |
| polynomial-redos.js:8:17:8:18 | * | Strings with many repetitions of ' ' can start matching anywhere after the start of the preceeding *, * |

View File

@@ -30,6 +30,23 @@ nodes
| lib/otherLib/js/src/index.js:1:28:1:31 | name |
| lib/otherLib/js/src/index.js:2:13:2:16 | name |
| lib/otherLib/js/src/index.js:2:13:2:16 | name |
| lib/snapdragon.js:3:34:3:38 | input |
| lib/snapdragon.js:3:34:3:38 | input |
| lib/snapdragon.js:7:15:7:18 | this |
| lib/snapdragon.js:7:15:7:18 | this |
| lib/snapdragon.js:9:12:9:16 | input |
| lib/snapdragon.js:12:34:12:38 | input |
| lib/snapdragon.js:12:34:12:38 | input |
| lib/snapdragon.js:15:13:15:16 | this |
| lib/snapdragon.js:15:13:15:16 | this |
| lib/snapdragon.js:17:20:17:24 | input |
| lib/snapdragon.js:20:34:20:38 | input |
| lib/snapdragon.js:20:34:20:38 | input |
| lib/snapdragon.js:22:44:22:47 | node |
| lib/snapdragon.js:23:5:23:8 | node |
| lib/snapdragon.js:23:5:23:12 | node.val |
| lib/snapdragon.js:23:5:23:12 | node.val |
| lib/snapdragon.js:25:22:25:26 | input |
| lib/sublib/factory.js:12:26:12:29 | name |
| lib/sublib/factory.js:12:26:12:29 | name |
| lib/sublib/factory.js:13:24:13:27 | name |
@@ -207,6 +224,20 @@ edges
| lib/otherLib/js/src/index.js:1:28:1:31 | name | lib/otherLib/js/src/index.js:2:13:2:16 | name |
| lib/otherLib/js/src/index.js:1:28:1:31 | name | lib/otherLib/js/src/index.js:2:13:2:16 | name |
| lib/otherLib/js/src/index.js:1:28:1:31 | name | lib/otherLib/js/src/index.js:2:13:2:16 | name |
| lib/snapdragon.js:3:34:3:38 | input | lib/snapdragon.js:9:12:9:16 | input |
| lib/snapdragon.js:3:34:3:38 | input | lib/snapdragon.js:9:12:9:16 | input |
| lib/snapdragon.js:9:12:9:16 | input | lib/snapdragon.js:7:15:7:18 | this |
| lib/snapdragon.js:9:12:9:16 | input | lib/snapdragon.js:7:15:7:18 | this |
| lib/snapdragon.js:12:34:12:38 | input | lib/snapdragon.js:17:20:17:24 | input |
| lib/snapdragon.js:12:34:12:38 | input | lib/snapdragon.js:17:20:17:24 | input |
| lib/snapdragon.js:17:20:17:24 | input | lib/snapdragon.js:15:13:15:16 | this |
| lib/snapdragon.js:17:20:17:24 | input | lib/snapdragon.js:15:13:15:16 | this |
| lib/snapdragon.js:20:34:20:38 | input | lib/snapdragon.js:25:22:25:26 | input |
| lib/snapdragon.js:20:34:20:38 | input | lib/snapdragon.js:25:22:25:26 | input |
| lib/snapdragon.js:22:44:22:47 | node | lib/snapdragon.js:23:5:23:8 | node |
| lib/snapdragon.js:23:5:23:8 | node | lib/snapdragon.js:23:5:23:12 | node.val |
| lib/snapdragon.js:23:5:23:8 | node | lib/snapdragon.js:23:5:23:12 | node.val |
| lib/snapdragon.js:25:22:25:26 | input | lib/snapdragon.js:22:44:22:47 | node |
| lib/sublib/factory.js:12:26:12:29 | name | lib/sublib/factory.js:13:24:13:27 | name |
| lib/sublib/factory.js:12:26:12:29 | name | lib/sublib/factory.js:13:24:13:27 | name |
| lib/sublib/factory.js:12:26:12:29 | name | lib/sublib/factory.js:13:24:13:27 | name |
@@ -355,6 +386,9 @@ edges
| 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 |
| 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 |
| 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 |
| 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 |
| 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 |
| 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 |
| 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 |
| 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 |
| 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 |

View File

@@ -26,4 +26,6 @@ module.exports.id = id;
module.exports.safe = function (x) {
var y = id("safe");
/f*g/.test(y); // OK
}
}
module.exports.snapdragon = require("./snapdragon")

View File

@@ -0,0 +1,26 @@
var Snapdragon = require("snapdragon");
module.exports.test1 = function (input) {
var snapdragon = new Snapdragon();
var ast = snapdragon.parser
.set("foo", function () {
var m = this.match(/aa*$/); // NOT OK
})
.parse(input, options);
};
module.exports.test2 = function (input) {
var snapdragon = new Snapdragon();
snapdragon.parser.set("foo", function () {
var m = this.match(/aa*$/); // NOT OK
});
snapdragon.parse(input, options);
};
module.exports.test3 = function (input) {
var snapdragon = new Snapdragon();
snapdragon.compiler.set("foo", function (node) {
node.val.match(/aa*$/); // NOT OK
});
snapdragon.compile(input, options);
};