diff --git a/javascript/ql/src/semmle/javascript/Promises.qll b/javascript/ql/src/semmle/javascript/Promises.qll index 6d6bda892e41..2d7459373192 100644 --- a/javascript/ql/src/semmle/javascript/Promises.qll +++ b/javascript/ql/src/semmle/javascript/Promises.qll @@ -45,3 +45,57 @@ module Q { override DataFlow::FunctionNode getExecutor() { result = getCallback(0) } } } + +private module ClosurePromise { + /** + * A promise created by a call `new goog.Promise(executor)`. + */ + private class ClosurePromiseDefinition extends PromiseDefinition, DataFlow::NewNode { + ClosurePromiseDefinition() { this = Closure::moduleImport("goog.Promise").getACall() } + + override DataFlow::FunctionNode getExecutor() { result = getCallback(0) } + } + + /** + * A promise created by a call `goog.Promise.resolve(value)`. + */ + private class ResolvedClosurePromiseDefinition extends ResolvedPromiseDefinition { + ResolvedClosurePromiseDefinition() { + this = Closure::moduleImport("goog.Promise.resolve").getACall() + } + + override DataFlow::Node getValue() { result = getArgument(0) } + } + + /** + * Taint steps through closure promise methods. + */ + private class ClosurePromiseTaintStep extends TaintTracking::AdditionalTaintStep { + DataFlow::Node pred; + + ClosurePromiseTaintStep() { + // static methods in goog.Promise + exists (DataFlow::CallNode call, string name | + call = Closure::moduleImport("goog.Promise." + name).getACall() and + this = call and + pred = call.getAnArgument() + | + name = "all" or + name = "allSettled" or + name = "firstFulfilled" or + name = "race" + ) + or + // promise created through goog.promise.withResolver() + exists (DataFlow::CallNode resolver | + resolver = Closure::moduleImport("goog.Promise.withResolver").getACall() and + this = resolver.getAPropertyRead("promise") and + pred = resolver.getAMethodCall("resolve").getArgument(0) + ) + } + + override predicate step(DataFlow::Node src, DataFlow::Node dst) { + src = pred and dst = this + } + } +} diff --git a/javascript/ql/test/library-tests/Promises/AdditionalPromises.expected b/javascript/ql/test/library-tests/Promises/AdditionalPromises.expected index 1fcb4eacabe6..a3fa5795b303 100644 --- a/javascript/ql/test/library-tests/Promises/AdditionalPromises.expected +++ b/javascript/ql/test/library-tests/Promises/AdditionalPromises.expected @@ -5,3 +5,5 @@ | promises.js:43:19:45:6 | Q.Promi ... \\n }) | | promises.js:53:19:53:41 | Promise ... source) | | promises.js:62:19:62:41 | Promise ... source) | +| promises.js:71:5:71:27 | Promise ... source) | +| promises.js:72:5:72:41 | new Pro ... ource)) | diff --git a/javascript/ql/test/library-tests/Promises/ResolvedPromiseDefinition.expected b/javascript/ql/test/library-tests/Promises/ResolvedPromiseDefinition.expected index 8a2659e98450..955d64b6a237 100644 --- a/javascript/ql/test/library-tests/Promises/ResolvedPromiseDefinition.expected +++ b/javascript/ql/test/library-tests/Promises/ResolvedPromiseDefinition.expected @@ -1,2 +1,3 @@ | promises.js:53:19:53:41 | Promise ... source) | promises.js:53:35:53:40 | source | | promises.js:62:19:62:41 | Promise ... source) | promises.js:62:35:62:40 | source | +| promises.js:71:5:71:27 | Promise ... source) | promises.js:71:21:71:26 | source | diff --git a/javascript/ql/test/library-tests/Promises/promises.js b/javascript/ql/test/library-tests/Promises/promises.js index f447d5648a11..f053c81a9a94 100644 --- a/javascript/ql/test/library-tests/Promises/promises.js +++ b/javascript/ql/test/library-tests/Promises/promises.js @@ -64,3 +64,13 @@ var sink = val; }); })(); + +(function() { + var Promise = goog.require('goog.Promise'); + var source = "tainted"; + Promise.resolve(source).then(val => { var sink = val; }); + new Promise((res,rej) => res(source)).then(val => { var sink = val }); + let resolver = Promise.withResolver(); + resolver.resolve(source); + resolver.promise.then(val => { var sink = val }); +})(); diff --git a/javascript/ql/test/library-tests/TaintTracking/BasicTaintTracking.expected b/javascript/ql/test/library-tests/TaintTracking/BasicTaintTracking.expected index a9bcc2a5aac6..c29f3f82ce0c 100644 --- a/javascript/ql/test/library-tests/TaintTracking/BasicTaintTracking.expected +++ b/javascript/ql/test/library-tests/TaintTracking/BasicTaintTracking.expected @@ -23,6 +23,8 @@ | partialCalls.js:4:17:4:24 | source() | partialCalls.js:51:14:51:14 | x | | promise.js:4:24:4:31 | source() | promise.js:4:8:4:32 | Promise ... urce()) | | promise.js:5:25:5:32 | source() | promise.js:5:8:5:33 | bluebir ... urce()) | +| promise.js:10:24:10:31 | source() | promise.js:10:8:10:32 | Promise ... urce()) | +| promise.js:12:20:12:27 | source() | promise.js:13:8:13:23 | resolver.promise | | sanitizer-guards.js:2:11:2:18 | source() | sanitizer-guards.js:4:8:4:8 | x | | sanitizer-guards.js:13:14:13:21 | source() | sanitizer-guards.js:15:10:15:15 | this.x | | sanitizer-guards.js:13:14:13:21 | source() | sanitizer-guards.js:21:14:21:19 | this.x | diff --git a/javascript/ql/test/library-tests/TaintTracking/promise.js b/javascript/ql/test/library-tests/TaintTracking/promise.js index 55b28153d53b..cd5351720ba2 100644 --- a/javascript/ql/test/library-tests/TaintTracking/promise.js +++ b/javascript/ql/test/library-tests/TaintTracking/promise.js @@ -4,3 +4,11 @@ function test() { sink(Promise.resolve(source())); // NOT OK sink(bluebird.resolve(source())); // NOT OK } + +function closure() { + let Promise = goog.require('goog.Promise'); + sink(Promise.resolve(source())); // NOT OK + let resolver = Promise.withResolver(); + resolver.resolve(source()); + sink(resolver.promise); // NOT OK +}