From b3d5f9c4ddd4e0e0cab84fb9e455a271d1f53ffe Mon Sep 17 00:00:00 2001 From: Erik Krogh Kristensen Date: Fri, 16 Oct 2020 10:41:15 +0200 Subject: [PATCH 1/4] support throttle like calls as partial calls --- .../src/semmle/javascript/dataflow/Nodes.qll | 42 +++++++++++++++++++ .../InterProceduralFlow/DataFlow.expected | 8 ++++ .../InterProceduralFlow/GermanFlow.expected | 8 ++++ .../TaintTracking.expected | 8 ++++ .../InterProceduralFlow/partial.js | 26 ++++++++++++ 5 files changed, 92 insertions(+) diff --git a/javascript/ql/src/semmle/javascript/dataflow/Nodes.qll b/javascript/ql/src/semmle/javascript/dataflow/Nodes.qll index 867cc428529..5ecabbc6b26 100644 --- a/javascript/ql/src/semmle/javascript/dataflow/Nodes.qll +++ b/javascript/ql/src/semmle/javascript/dataflow/Nodes.qll @@ -1379,6 +1379,48 @@ module PartialInvokeNode { } } + /** + * A partial call that behaves like a throttle call, like `require("call-limit")(fs, limit)` or `_.memoize`. + * Seen as a partial invocation that binds no arguments. + */ + private class ThrottleLikePartialCall extends PartialInvokeNode::Range, DataFlow::CallNode { + int callbackIndex; + + ThrottleLikePartialCall() { + callbackIndex = 0 and + ( + this = LodashUnderscore::member(["throttle", "debounce", "once", "memoize"]).getACall() + or + this = + DataFlow::moduleImport(["call-limit", "lodash.debounce", "lodash.throttle", "debounce"]) + .getACall() + ) + or + callbackIndex = 1 and + ( + this = LodashUnderscore::member(["after", "before"]).getACall() + or + // not jQuery: https://github.com/cowboy/jquery-throttle-debounce + this = DataFlow::globalVarRef("$").getAMemberCall(["throttle", "debounce"]) + ) + or + callbackIndex = -1 and + this = DataFlow::moduleMember("throttle-debounce", ["debounce", "throttle"]).getACall() + } + + override DataFlow::SourceNode getBoundFunction(DataFlow::Node callback, int boundArgs) { + ( + callbackIndex >= 0 and + callback = getArgument(callbackIndex) + or + callbackIndex = -1 and + callback = getLastArgument() + ) and + boundArgs = 0 and + result = this + } + } + /** * A partial call through `ramda.partial`. */ diff --git a/javascript/ql/test/library-tests/InterProceduralFlow/DataFlow.expected b/javascript/ql/test/library-tests/InterProceduralFlow/DataFlow.expected index 664e6fada2b..e2aa29b1504 100644 --- a/javascript/ql/test/library-tests/InterProceduralFlow/DataFlow.expected +++ b/javascript/ql/test/library-tests/InterProceduralFlow/DataFlow.expected @@ -32,10 +32,18 @@ | partial.js:5:15:5:24 | "tainted1" | partial.js:15:15:15:15 | x | | partial.js:5:15:5:24 | "tainted1" | partial.js:21:15:21:15 | x | | partial.js:5:15:5:24 | "tainted1" | partial.js:27:15:27:15 | x | +| partial.js:5:15:5:24 | "tainted1" | partial.js:34:15:34:15 | x | +| partial.js:5:15:5:24 | "tainted1" | partial.js:41:15:41:15 | x | +| partial.js:5:15:5:24 | "tainted1" | partial.js:47:15:47:15 | x | +| partial.js:5:15:5:24 | "tainted1" | partial.js:53:15:53:15 | x | | partial.js:6:15:6:24 | "tainted2" | partial.js:10:15:10:15 | y | | partial.js:6:15:6:24 | "tainted2" | partial.js:16:15:16:15 | y | | partial.js:6:15:6:24 | "tainted2" | partial.js:22:15:22:15 | y | | partial.js:6:15:6:24 | "tainted2" | partial.js:28:15:28:15 | y | +| partial.js:6:15:6:24 | "tainted2" | partial.js:35:15:35:15 | y | +| partial.js:6:15:6:24 | "tainted2" | partial.js:42:15:42:15 | y | +| partial.js:6:15:6:24 | "tainted2" | partial.js:48:15:48:15 | y | +| partial.js:6:15:6:24 | "tainted2" | partial.js:54:15:54:15 | y | | promises.js:2:16:2:24 | "tainted" | promises.js:7:16:7:18 | val | | promises.js:2:16:2:24 | "tainted" | promises.js:38:32:38:32 | v | | promises.js:11:22:11:31 | "resolved" | promises.js:19:20:19:20 | v | diff --git a/javascript/ql/test/library-tests/InterProceduralFlow/GermanFlow.expected b/javascript/ql/test/library-tests/InterProceduralFlow/GermanFlow.expected index f5f0614794f..07737292c9c 100644 --- a/javascript/ql/test/library-tests/InterProceduralFlow/GermanFlow.expected +++ b/javascript/ql/test/library-tests/InterProceduralFlow/GermanFlow.expected @@ -33,10 +33,18 @@ | partial.js:5:15:5:24 | "tainted1" | partial.js:15:15:15:15 | x | | partial.js:5:15:5:24 | "tainted1" | partial.js:21:15:21:15 | x | | partial.js:5:15:5:24 | "tainted1" | partial.js:27:15:27:15 | x | +| partial.js:5:15:5:24 | "tainted1" | partial.js:34:15:34:15 | x | +| partial.js:5:15:5:24 | "tainted1" | partial.js:41:15:41:15 | x | +| partial.js:5:15:5:24 | "tainted1" | partial.js:47:15:47:15 | x | +| partial.js:5:15:5:24 | "tainted1" | partial.js:53:15:53:15 | x | | partial.js:6:15:6:24 | "tainted2" | partial.js:10:15:10:15 | y | | partial.js:6:15:6:24 | "tainted2" | partial.js:16:15:16:15 | y | | partial.js:6:15:6:24 | "tainted2" | partial.js:22:15:22:15 | y | | partial.js:6:15:6:24 | "tainted2" | partial.js:28:15:28:15 | y | +| partial.js:6:15:6:24 | "tainted2" | partial.js:35:15:35:15 | y | +| partial.js:6:15:6:24 | "tainted2" | partial.js:42:15:42:15 | y | +| partial.js:6:15:6:24 | "tainted2" | partial.js:48:15:48:15 | y | +| partial.js:6:15:6:24 | "tainted2" | partial.js:54:15:54:15 | y | | promises.js:2:16:2:24 | "tainted" | promises.js:7:16:7:18 | val | | promises.js:2:16:2:24 | "tainted" | promises.js:38:32:38:32 | v | | promises.js:11:22:11:31 | "resolved" | promises.js:19:20:19:20 | v | diff --git a/javascript/ql/test/library-tests/InterProceduralFlow/TaintTracking.expected b/javascript/ql/test/library-tests/InterProceduralFlow/TaintTracking.expected index 823db35f84d..e2997534c93 100644 --- a/javascript/ql/test/library-tests/InterProceduralFlow/TaintTracking.expected +++ b/javascript/ql/test/library-tests/InterProceduralFlow/TaintTracking.expected @@ -38,10 +38,18 @@ | partial.js:5:15:5:24 | "tainted1" | partial.js:15:15:15:15 | x | | partial.js:5:15:5:24 | "tainted1" | partial.js:21:15:21:15 | x | | partial.js:5:15:5:24 | "tainted1" | partial.js:27:15:27:15 | x | +| partial.js:5:15:5:24 | "tainted1" | partial.js:34:15:34:15 | x | +| partial.js:5:15:5:24 | "tainted1" | partial.js:41:15:41:15 | x | +| partial.js:5:15:5:24 | "tainted1" | partial.js:47:15:47:15 | x | +| partial.js:5:15:5:24 | "tainted1" | partial.js:53:15:53:15 | x | | partial.js:6:15:6:24 | "tainted2" | partial.js:10:15:10:15 | y | | partial.js:6:15:6:24 | "tainted2" | partial.js:16:15:16:15 | y | | partial.js:6:15:6:24 | "tainted2" | partial.js:22:15:22:15 | y | | partial.js:6:15:6:24 | "tainted2" | partial.js:28:15:28:15 | y | +| partial.js:6:15:6:24 | "tainted2" | partial.js:35:15:35:15 | y | +| partial.js:6:15:6:24 | "tainted2" | partial.js:42:15:42:15 | y | +| partial.js:6:15:6:24 | "tainted2" | partial.js:48:15:48:15 | y | +| partial.js:6:15:6:24 | "tainted2" | partial.js:54:15:54:15 | y | | promises.js:2:16:2:24 | "tainted" | promises.js:7:16:7:18 | val | | promises.js:2:16:2:24 | "tainted" | promises.js:38:32:38:32 | v | | promises.js:11:22:11:31 | "resolved" | promises.js:19:20:19:20 | v | diff --git a/javascript/ql/test/library-tests/InterProceduralFlow/partial.js b/javascript/ql/test/library-tests/InterProceduralFlow/partial.js index bd88efc7410..309765a6791 100644 --- a/javascript/ql/test/library-tests/InterProceduralFlow/partial.js +++ b/javascript/ql/test/library-tests/InterProceduralFlow/partial.js @@ -28,3 +28,29 @@ function f4(x, y) { let sink2 = y; } R.partial(f4, [source1])(source2); + +const limit = require('call-limit') +function f5(x, y) { + let sink1 = x; + let sink2 = y; +} +const limited = limit(f5, 5) +limited(source1, source2); + +function f6(x, y) { + let sink1 = x; + let sink2 = y; +} +_.throttle(f6, 100)(source1, source2); + +function f7(x, y) { + let sink1 = x; + let sink2 = y; +} +_.after(3, f7)(source1, source2); + +function f8(x, y) { + let sink1 = x; + let sink2 = y; +} +require("throttle-debounce").debounce(1000, false, f8)(source1, source2); \ No newline at end of file From 7598d31fc135106f9f1f31a40d9b3286408f8351 Mon Sep 17 00:00:00 2001 From: Erik Krogh Kristensen Date: Fri, 16 Oct 2020 13:35:31 +0200 Subject: [PATCH 2/4] add change note --- change-notes/1.26/analysis-javascript.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/change-notes/1.26/analysis-javascript.md b/change-notes/1.26/analysis-javascript.md index 01d35a12c2d..a3d5a906f86 100644 --- a/change-notes/1.26/analysis-javascript.md +++ b/change-notes/1.26/analysis-javascript.md @@ -5,7 +5,9 @@ * Support for the following frameworks and libraries has been improved: - [AWS Serverless](https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/sam-resource-function.html) - [Alibaba Serverless](https://www.alibabacloud.com/help/doc-detail/156876.htm) + - [debounce](https://www.npmjs.com/package/debounce) - [bluebird](https://www.npmjs.com/package/bluebird) + - [call-limit](https://www.npmjs.com/package/call-limit) - [express](https://www.npmjs.com/package/express) - [fast-json-stable-stringify](https://www.npmjs.com/package/fast-json-stable-stringify) - [fast-safe-stringify](https://www.npmjs.com/package/fast-safe-stringify) @@ -15,11 +17,15 @@ - [json-stable-stringify](https://www.npmjs.com/package/json-stable-stringify) - [json-stringify-safe](https://www.npmjs.com/package/json-stringify-safe) - [json3](https://www.npmjs.com/package/json3) + - [jQuery throttle / debounce](https://github.com/cowboy/jquery-throttle-debounce) - [lodash](https://www.npmjs.com/package/lodash) + - [lodash.debounce](https://www.npmjs.com/package/lodash.debounce) + - [lodash.throttle](https://www.npmjs.com/package/lodash.throttle) - [needle](https://www.npmjs.com/package/needle) - [object-inspect](https://www.npmjs.com/package/object-inspect) - [pretty-format](https://www.npmjs.com/package/pretty-format) - [stringify-object](https://www.npmjs.com/package/stringify-object) + - [throttle-debounce](https://www.npmjs.com/package/throttle-debounce) - [underscore](https://www.npmjs.com/package/underscore) * Analyzing files with the ".cjs" extension is now supported. From c2338b218f7e3accc06ddef93698cec20f484a4e Mon Sep 17 00:00:00 2001 From: Erik Krogh Kristensen Date: Fri, 16 Oct 2020 14:12:36 +0200 Subject: [PATCH 3/4] Update javascript/ql/src/semmle/javascript/dataflow/Nodes.qll Co-authored-by: Asger F --- javascript/ql/src/semmle/javascript/dataflow/Nodes.qll | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/javascript/ql/src/semmle/javascript/dataflow/Nodes.qll b/javascript/ql/src/semmle/javascript/dataflow/Nodes.qll index 5ecabbc6b26..11a3f1d7958 100644 --- a/javascript/ql/src/semmle/javascript/dataflow/Nodes.qll +++ b/javascript/ql/src/semmle/javascript/dataflow/Nodes.qll @@ -1392,7 +1392,7 @@ module PartialInvokeNode { this = LodashUnderscore::member(["throttle", "debounce", "once", "memoize"]).getACall() or this = - DataFlow::moduleImport(["call-limit", "lodash.debounce", "lodash.throttle", "debounce"]) + DataFlow::moduleImport(["call-limit", "debounce"]) .getACall() ) or From 8cf21e3b2ba6b5e6b066cf94156fcf24d011ef56 Mon Sep 17 00:00:00 2001 From: Erik Krogh Kristensen Date: Fri, 16 Oct 2020 16:56:35 +0200 Subject: [PATCH 4/4] autoformat --- javascript/ql/src/semmle/javascript/dataflow/Nodes.qll | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/javascript/ql/src/semmle/javascript/dataflow/Nodes.qll b/javascript/ql/src/semmle/javascript/dataflow/Nodes.qll index 11a3f1d7958..d995caabf2b 100644 --- a/javascript/ql/src/semmle/javascript/dataflow/Nodes.qll +++ b/javascript/ql/src/semmle/javascript/dataflow/Nodes.qll @@ -1391,9 +1391,7 @@ module PartialInvokeNode { ( this = LodashUnderscore::member(["throttle", "debounce", "once", "memoize"]).getACall() or - this = - DataFlow::moduleImport(["call-limit", "debounce"]) - .getACall() + this = DataFlow::moduleImport(["call-limit", "debounce"]).getACall() ) or callbackIndex = 1 and