Skip to content

Commit c8a3729

Browse files
author
Max Schaefer
authored
Merge pull request #997 from asger-semmle/closure-promise
JS: model of closure Promises
2 parents a83f33b + 3d400cc commit c8a3729

File tree

6 files changed

+77
-0
lines changed

6 files changed

+77
-0
lines changed

javascript/ql/src/semmle/javascript/Promises.qll

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,3 +45,57 @@ module Q {
4545
override DataFlow::FunctionNode getExecutor() { result = getCallback(0) }
4646
}
4747
}
48+
49+
private module ClosurePromise {
50+
/**
51+
* A promise created by a call `new goog.Promise(executor)`.
52+
*/
53+
private class ClosurePromiseDefinition extends PromiseDefinition, DataFlow::NewNode {
54+
ClosurePromiseDefinition() { this = Closure::moduleImport("goog.Promise").getACall() }
55+
56+
override DataFlow::FunctionNode getExecutor() { result = getCallback(0) }
57+
}
58+
59+
/**
60+
* A promise created by a call `goog.Promise.resolve(value)`.
61+
*/
62+
private class ResolvedClosurePromiseDefinition extends ResolvedPromiseDefinition {
63+
ResolvedClosurePromiseDefinition() {
64+
this = Closure::moduleImport("goog.Promise.resolve").getACall()
65+
}
66+
67+
override DataFlow::Node getValue() { result = getArgument(0) }
68+
}
69+
70+
/**
71+
* Taint steps through closure promise methods.
72+
*/
73+
private class ClosurePromiseTaintStep extends TaintTracking::AdditionalTaintStep {
74+
DataFlow::Node pred;
75+
76+
ClosurePromiseTaintStep() {
77+
// static methods in goog.Promise
78+
exists (DataFlow::CallNode call, string name |
79+
call = Closure::moduleImport("goog.Promise." + name).getACall() and
80+
this = call and
81+
pred = call.getAnArgument()
82+
|
83+
name = "all" or
84+
name = "allSettled" or
85+
name = "firstFulfilled" or
86+
name = "race"
87+
)
88+
or
89+
// promise created through goog.promise.withResolver()
90+
exists (DataFlow::CallNode resolver |
91+
resolver = Closure::moduleImport("goog.Promise.withResolver").getACall() and
92+
this = resolver.getAPropertyRead("promise") and
93+
pred = resolver.getAMethodCall("resolve").getArgument(0)
94+
)
95+
}
96+
97+
override predicate step(DataFlow::Node src, DataFlow::Node dst) {
98+
src = pred and dst = this
99+
}
100+
}
101+
}

javascript/ql/test/library-tests/Promises/AdditionalPromises.expected

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,3 +5,5 @@
55
| promises.js:43:19:45:6 | Q.Promi ... \\n }) |
66
| promises.js:53:19:53:41 | Promise ... source) |
77
| promises.js:62:19:62:41 | Promise ... source) |
8+
| promises.js:71:5:71:27 | Promise ... source) |
9+
| promises.js:72:5:72:41 | new Pro ... ource)) |
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,3 @@
11
| promises.js:53:19:53:41 | Promise ... source) | promises.js:53:35:53:40 | source |
22
| promises.js:62:19:62:41 | Promise ... source) | promises.js:62:35:62:40 | source |
3+
| promises.js:71:5:71:27 | Promise ... source) | promises.js:71:21:71:26 | source |

javascript/ql/test/library-tests/Promises/promises.js

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,3 +64,13 @@
6464
var sink = val;
6565
});
6666
})();
67+
68+
(function() {
69+
var Promise = goog.require('goog.Promise');
70+
var source = "tainted";
71+
Promise.resolve(source).then(val => { var sink = val; });
72+
new Promise((res,rej) => res(source)).then(val => { var sink = val });
73+
let resolver = Promise.withResolver();
74+
resolver.resolve(source);
75+
resolver.promise.then(val => { var sink = val });
76+
})();

javascript/ql/test/library-tests/TaintTracking/BasicTaintTracking.expected

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,8 @@
2626
| partialCalls.js:4:17:4:24 | source() | partialCalls.js:51:14:51:14 | x |
2727
| promise.js:4:24:4:31 | source() | promise.js:4:8:4:32 | Promise ... urce()) |
2828
| promise.js:5:25:5:32 | source() | promise.js:5:8:5:33 | bluebir ... urce()) |
29+
| promise.js:10:24:10:31 | source() | promise.js:10:8:10:32 | Promise ... urce()) |
30+
| promise.js:12:20:12:27 | source() | promise.js:13:8:13:23 | resolver.promise |
2931
| sanitizer-guards.js:2:11:2:18 | source() | sanitizer-guards.js:4:8:4:8 | x |
3032
| sanitizer-guards.js:13:14:13:21 | source() | sanitizer-guards.js:15:10:15:15 | this.x |
3133
| sanitizer-guards.js:13:14:13:21 | source() | sanitizer-guards.js:21:14:21:19 | this.x |

javascript/ql/test/library-tests/TaintTracking/promise.js

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,3 +4,11 @@ function test() {
44
sink(Promise.resolve(source())); // NOT OK
55
sink(bluebird.resolve(source())); // NOT OK
66
}
7+
8+
function closure() {
9+
let Promise = goog.require('goog.Promise');
10+
sink(Promise.resolve(source())); // NOT OK
11+
let resolver = Promise.withResolver();
12+
resolver.resolve(source());
13+
sink(resolver.promise); // NOT OK
14+
}

0 commit comments

Comments
 (0)