Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit f8d0b4e

Browse files
authored
Merge pull request #2618 from erik-krogh/ExceptionalPromise
Approved by asgerf
2 parents 7ca7bdf + aea365c commit f8d0b4e

18 files changed

Lines changed: 1083 additions & 275 deletions

File tree

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

Lines changed: 377 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,385 @@
11
/**
2-
* Provides classes for modelling promise libraries.
2+
* Provides classes for modelling promises and their data-flow.
33
*/
44

55
import javascript
66

7+
/**
8+
* A definition of a `Promise` object.
9+
*/
10+
abstract class PromiseDefinition extends DataFlow::SourceNode {
11+
/** Gets the executor function of this promise object. */
12+
abstract DataFlow::FunctionNode getExecutor();
13+
14+
/** Gets the `resolve` parameter of the executor function. */
15+
DataFlow::ParameterNode getResolveParameter() { result = getExecutor().getParameter(0) }
16+
17+
/** Gets the `reject` parameter of the executor function. */
18+
DataFlow::ParameterNode getRejectParameter() { result = getExecutor().getParameter(1) }
19+
20+
/** Gets the `i`th callback handler installed by method `m`. */
21+
private DataFlow::FunctionNode getAHandler(string m, int i) {
22+
result = getAMethodCall(m).getCallback(i)
23+
}
24+
25+
/**
26+
* Gets a function that handles promise resolution, including both
27+
* `then` handlers and `finally` handlers.
28+
*/
29+
DataFlow::FunctionNode getAResolveHandler() {
30+
result = getAHandler("then", 0) or
31+
result = getAFinallyHandler()
32+
}
33+
34+
/**
35+
* Gets a function that handles promise rejection, including
36+
* `then` handlers, `catch` handlers and `finally` handlers.
37+
*/
38+
DataFlow::FunctionNode getARejectHandler() {
39+
result = getAHandler("then", 1) or
40+
result = getACatchHandler() or
41+
result = getAFinallyHandler()
42+
}
43+
44+
/**
45+
* Gets a `catch` handler of this promise.
46+
*/
47+
DataFlow::FunctionNode getACatchHandler() { result = getAHandler("catch", 0) }
48+
49+
/**
50+
* Gets a `finally` handler of this promise.
51+
*/
52+
DataFlow::FunctionNode getAFinallyHandler() { result = getAHandler("finally", 0) }
53+
}
54+
55+
/** Holds if the `i`th callback handler is installed by method `m`. */
56+
private predicate hasHandler(DataFlow::InvokeNode promise, string m, int i) {
57+
exists(promise.getAMethodCall(m).getCallback(i))
58+
}
59+
60+
/**
61+
* A call that looks like a Promise.
62+
*
63+
* For example, this could be the call `promise(f).then(function(v){...})`
64+
*/
65+
class PromiseCandidate extends DataFlow::InvokeNode {
66+
PromiseCandidate() {
67+
hasHandler(this, "then", [0 .. 1]) or
68+
hasHandler(this, "catch", 0) or
69+
hasHandler(this, "finally", 0)
70+
}
71+
}
72+
73+
/**
74+
* A promise object created by the standard ECMAScript 2015 `Promise` constructor.
75+
*/
76+
private class ES2015PromiseDefinition extends PromiseDefinition, DataFlow::NewNode {
77+
ES2015PromiseDefinition() { this = DataFlow::globalVarRef("Promise").getAnInstantiation() }
78+
79+
override DataFlow::FunctionNode getExecutor() { result = getCallback(0) }
80+
}
81+
82+
/**
83+
* A promise that is created and resolved with one or more value.
84+
*/
85+
abstract class PromiseCreationCall extends DataFlow::CallNode {
86+
/**
87+
* Gets the value this promise is resolved with.
88+
*/
89+
abstract DataFlow::Node getValue();
90+
}
91+
92+
/**
93+
* A promise that is created using a `.resolve()` call.
94+
*/
95+
abstract class ResolvedPromiseDefinition extends PromiseCreationCall { }
96+
97+
/**
98+
* A resolved promise created by the standard ECMAScript 2015 `Promise.resolve` function.
99+
*/
100+
class ResolvedES2015PromiseDefinition extends ResolvedPromiseDefinition {
101+
ResolvedES2015PromiseDefinition() {
102+
this = DataFlow::globalVarRef("Promise").getAMemberCall("resolve")
103+
}
104+
105+
override DataFlow::Node getValue() { result = getArgument(0) }
106+
}
107+
108+
/**
109+
* An aggregated promise produced either by `Promise.all` or `Promise.race`.
110+
*/
111+
class AggregateES2015PromiseDefinition extends PromiseCreationCall {
112+
AggregateES2015PromiseDefinition() {
113+
exists(string m | m = "all" or m = "race" |
114+
this = DataFlow::globalVarRef("Promise").getAMemberCall(m)
115+
)
116+
}
117+
118+
override DataFlow::Node getValue() {
119+
result = getArgument(0).getALocalSource().(DataFlow::ArrayCreationNode).getAnElement()
120+
}
121+
}
122+
123+
/**
124+
* This module defines how data-flow propagates into and out of a Promise.
125+
* The data-flow is based on pseudo-properties rather than tainting the Promise object (which is what `PromiseTaintStep` does).
126+
*/
127+
private module PromiseFlow {
128+
/**
129+
* Gets the pseudo-field used to describe resolved values in a promise.
130+
*/
131+
string resolveField() {
132+
result = "$PromiseResolveField$"
133+
}
134+
135+
/**
136+
* Gets the pseudo-field used to describe rejected values in a promise.
137+
*/
138+
string rejectField() {
139+
result = "$PromiseRejectField$"
140+
}
141+
142+
/**
143+
* A flow step describing a promise definition.
144+
*
145+
* The resolved/rejected value is written to a pseudo-field on the promise.
146+
*/
147+
class PromiseDefitionStep extends DataFlow::AdditionalFlowStep {
148+
PromiseDefinition promise;
149+
PromiseDefitionStep() {
150+
this = promise
151+
}
152+
153+
override predicate storeStep(DataFlow::Node pred, DataFlow::Node succ, string prop) {
154+
prop = resolveField() and
155+
pred = promise.getResolveParameter().getACall().getArgument(0) and
156+
succ = this
157+
or
158+
prop = rejectField() and
159+
(
160+
pred = promise.getRejectParameter().getACall().getArgument(0) or
161+
pred = promise.getExecutor().getExceptionalReturn()
162+
) and
163+
succ = this
164+
}
165+
166+
override predicate loadStoreStep(DataFlow::Node pred, DataFlow::Node succ, string prop) {
167+
// Copy the value of a resolved promise to the value of this promise.
168+
prop = resolveField() and
169+
pred = promise.getResolveParameter().getACall().getArgument(0) and
170+
succ = this
171+
}
172+
}
173+
174+
/**
175+
* A flow step describing the a Promise.resolve (and similar) call.
176+
*/
177+
class CreationStep extends DataFlow::AdditionalFlowStep {
178+
PromiseCreationCall promise;
179+
CreationStep() {
180+
this = promise
181+
}
182+
183+
override predicate storeStep(DataFlow::Node pred, DataFlow::Node succ, string prop) {
184+
prop = resolveField() and
185+
pred = promise.getValue() and
186+
succ = this
187+
}
188+
189+
override predicate loadStoreStep(DataFlow::Node pred, DataFlow::Node succ, string prop) {
190+
// Copy the value of a resolved promise to the value of this promise.
191+
prop = resolveField() and
192+
pred = promise.getValue() and
193+
succ = this
194+
}
195+
}
196+
197+
198+
/**
199+
* A load step loading the pseudo-field describing that the promise is rejected.
200+
* The rejected value is thrown as a exception.
201+
*/
202+
class AwaitStep extends DataFlow::AdditionalFlowStep {
203+
DataFlow::Node operand;
204+
AwaitExpr await;
205+
AwaitStep() {
206+
this.getEnclosingExpr() = await and
207+
operand.getEnclosingExpr() = await.getOperand()
208+
}
209+
210+
override predicate loadStep(DataFlow::Node pred, DataFlow::Node succ, string prop) {
211+
prop = resolveField() and
212+
succ = this and
213+
pred = operand
214+
or
215+
prop = rejectField() and
216+
succ = await.getExceptionTarget() and
217+
pred = operand
218+
}
219+
}
220+
221+
/**
222+
* A flow step describing the data-flow related to the `.then` method of a promise.
223+
*/
224+
class ThenStep extends DataFlow::AdditionalFlowStep, DataFlow::MethodCallNode {
225+
ThenStep() {
226+
this.getMethodName() = "then"
227+
}
228+
229+
override predicate loadStep(DataFlow::Node pred, DataFlow::Node succ, string prop) {
230+
prop = resolveField() and
231+
pred = getReceiver() and
232+
succ = getCallback(0).getParameter(0)
233+
or
234+
prop = rejectField() and
235+
pred = getReceiver() and
236+
succ = getCallback(1).getParameter(0)
237+
}
238+
239+
override predicate loadStoreStep(DataFlow::Node pred, DataFlow::Node succ, string prop) {
240+
not exists(this.getArgument(1)) and
241+
prop = rejectField() and
242+
pred = getReceiver() and
243+
succ = this
244+
or
245+
// read the value of a resolved/rejected promise that is returned
246+
(prop = rejectField() or prop = resolveField()) and
247+
pred = getCallback([0..1]).getAReturn() and
248+
succ = this
249+
}
250+
251+
override predicate storeStep(DataFlow::Node pred, DataFlow::Node succ, string prop) {
252+
prop = resolveField() and
253+
pred = getCallback([0..1]).getAReturn() and
254+
succ = this
255+
or
256+
prop = rejectField() and
257+
pred = getCallback([0..1]).getExceptionalReturn() and
258+
succ = this
259+
}
260+
}
261+
262+
/**
263+
* A flow step describing the data-flow related to the `.catch` method of a promise.
264+
*/
265+
class CatchStep extends DataFlow::AdditionalFlowStep, DataFlow::MethodCallNode {
266+
CatchStep() {
267+
this.getMethodName() = "catch"
268+
}
269+
270+
override predicate loadStep(DataFlow::Node pred, DataFlow::Node succ, string prop) {
271+
prop = rejectField() and
272+
pred = getReceiver() and
273+
succ = getCallback(0).getParameter(0)
274+
}
275+
276+
override predicate loadStoreStep(DataFlow::Node pred, DataFlow::Node succ, string prop) {
277+
prop = resolveField() and
278+
pred = getReceiver().getALocalSource() and
279+
succ = this
280+
or
281+
// read the value of a resolved/rejected promise that is returned
282+
(prop = rejectField() or prop = resolveField()) and
283+
pred = getCallback(0).getAReturn() and
284+
succ = this
285+
}
286+
287+
override predicate storeStep(DataFlow::Node pred, DataFlow::Node succ, string prop) {
288+
prop = rejectField() and
289+
pred = getCallback(0).getExceptionalReturn() and
290+
succ = this
291+
or
292+
prop = resolveField() and
293+
pred = getCallback(0).getAReturn() and
294+
succ = this
295+
}
296+
}
297+
298+
/**
299+
* A flow step describing the data-flow related to the `.finally` method of a promise.
300+
*/
301+
class FinallyStep extends DataFlow::AdditionalFlowStep, DataFlow::MethodCallNode {
302+
FinallyStep() {
303+
this.getMethodName() = "finally"
304+
}
305+
306+
override predicate loadStoreStep(DataFlow::Node pred, DataFlow::Node succ, string prop) {
307+
(prop = resolveField() or prop = rejectField()) and
308+
pred = getReceiver() and
309+
succ = this
310+
or
311+
// read the value of a rejected promise that is returned
312+
prop = rejectField() and
313+
pred = getCallback(0).getAReturn() and
314+
succ = this
315+
}
316+
317+
override predicate storeStep(DataFlow::Node pred, DataFlow::Node succ, string prop) {
318+
prop = rejectField() and
319+
pred = getCallback(0).getExceptionalReturn() and
320+
succ = this
321+
}
322+
}
323+
}
324+
325+
/**
326+
* Holds if taint propagates from `pred` to `succ` through promises.
327+
*/
328+
predicate promiseTaintStep(DataFlow::Node pred, DataFlow::Node succ) {
329+
// from `x` to `new Promise((res, rej) => res(x))`
330+
pred = succ.(PromiseDefinition).getResolveParameter().getACall().getArgument(0)
331+
or
332+
// from `x` to `Promise.resolve(x)`
333+
pred = succ.(PromiseCreationCall).getValue()
334+
or
335+
exists(DataFlow::MethodCallNode thn |
336+
thn.getMethodName() = "then"
337+
|
338+
// from `p` to `x` in `p.then(x => ...)`
339+
pred = thn.getReceiver() and
340+
succ = thn.getCallback(0).getParameter(0)
341+
or
342+
// from `v` to `p.then(x => return v)`
343+
pred = thn.getCallback([0..1]).getAReturn() and
344+
succ = thn
345+
)
346+
or
347+
exists(DataFlow::MethodCallNode catch | catch.getMethodName() = "catch" |
348+
// from `p` to `p.catch(..)`
349+
pred = catch.getReceiver() and
350+
succ = catch
351+
or
352+
// from `v` to `p.catch(x => return v)`
353+
pred = catch.getCallback(0).getAReturn() and
354+
succ = catch
355+
)
356+
or
357+
// from `p` to `p.finally(..)`
358+
exists(DataFlow::MethodCallNode finally | finally.getMethodName() = "finally" |
359+
pred = finally.getReceiver() and
360+
succ = finally
361+
)
362+
or
363+
// from `x` to `await x`
364+
exists(AwaitExpr await |
365+
pred.getEnclosingExpr() = await.getOperand() and
366+
succ.getEnclosingExpr() = await
367+
)
368+
}
369+
370+
/**
371+
* An additional taint step that involves promises.
372+
*/
373+
private class PromiseTaintStep extends TaintTracking::AdditionalTaintStep {
374+
DataFlow::Node source;
375+
376+
PromiseTaintStep() { promiseTaintStep(source, this) }
377+
378+
override predicate step(DataFlow::Node pred, DataFlow::Node succ) {
379+
pred = source and succ = this
380+
}
381+
}
382+
7383
/**
8384
* Provides classes for working with the `bluebird` library (http://bluebirdjs.com).
9385
*/

0 commit comments

Comments
 (0)