Skip to content

Commit fa08258

Browse files
authored
Merge pull request github#3036 from erik-krogh/CustomTrack
Approved by asgerf
2 parents ea46873 + 095d4d7 commit fa08258

File tree

10 files changed

+426
-251
lines changed

10 files changed

+426
-251
lines changed

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

Lines changed: 166 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
*/
44

55
import javascript
6+
private import dataflow.internal.StepSummary
67

78
/**
89
* A definition of a `Promise` object.
@@ -121,46 +122,166 @@ class AggregateES2015PromiseDefinition extends PromiseCreationCall {
121122
}
122123

123124
/**
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).
125+
* Common predicates shared between type-tracking and data-flow for promises.
126126
*/
127-
private module PromiseFlow {
127+
module Promises {
128128
/**
129129
* Gets the pseudo-field used to describe resolved values in a promise.
130130
*/
131-
string resolveField() { result = "$PromiseResolveField$" }
131+
string valueProp() { result = "$PromiseResolveField$" }
132132

133133
/**
134134
* Gets the pseudo-field used to describe rejected values in a promise.
135135
*/
136-
string rejectField() { result = "$PromiseRejectField$" }
136+
string errorProp() { result = "$PromiseRejectField$" }
137+
}
138+
139+
/**
140+
* A module for supporting promises in type-tracking predicates.
141+
* The `PromiseTypeTracking::promiseStep` predicate is used for type tracking in and out of promises,
142+
* and is included in the standard type-tracking steps (`SourceNode::track`).
143+
* The `TypeTracker::startInPromise()` predicate can be used to initiate a type-tracker
144+
* where the tracked value is a promise.
145+
*
146+
* The below is an example of a type-tracking predicate where the initial value is a promise:
147+
* ```
148+
* DataFlow::SourceNode myType(DataFlow::TypeTracker t) {
149+
* t.startInPromise() and
150+
* result = <the promise value> and
151+
* or
152+
* exists(DataFlow::TypeTracker t2 | result = myType(t2).track(t2, t))
153+
* }
154+
* ```
155+
*
156+
* The type-tracking predicate above will only end (`t = DataFlow::TypeTracker::end()`) after the tracked value has been
157+
* extracted from the promise.
158+
*
159+
* The `PromiseTypeTracking::promiseStep` predicate can be used instead of `SourceNode::track`
160+
* to get type-tracking only for promise steps.
161+
*
162+
* Replace `t.startInPromise()` in the above example with `t.start()` to create a type-tracking predicate
163+
* where the value is not initially inside a promise.
164+
*/
165+
module PromiseTypeTracking {
166+
/**
167+
* Gets the result from a single step through a promise, from `pred` to `result` summarized by `summary`.
168+
* This can be loading a resolved value from a promise, storing a value in a promise, or copying a resolved value from one promise to another.
169+
*/
170+
DataFlow::SourceNode promiseStep(DataFlow::SourceNode pred, StepSummary summary) {
171+
exists(PromiseFlowStep step, string field | field = Promises::valueProp() |
172+
summary = LoadStep(field) and
173+
step.load(pred, result, field)
174+
or
175+
summary = StoreStep(field) and
176+
step.store(pred, result, field)
177+
or
178+
summary = LevelStep() and
179+
step.loadStore(pred, result, field)
180+
)
181+
}
182+
183+
/**
184+
* Gets the result from a single step through a promise, from `pred` with tracker `t2` to `result` with tracker `t`.
185+
* This can be loading a resolved value from a promise, storing a value in a promise, or copying a resolved value from one promise to another.
186+
*/
187+
DataFlow::SourceNode promiseStep(
188+
DataFlow::SourceNode pred, DataFlow::TypeTracker t, DataFlow::TypeTracker t2
189+
) {
190+
exists(StepSummary summary |
191+
result = PromiseTypeTracking::promiseStep(pred, summary) and
192+
t = t2.append(summary)
193+
)
194+
}
195+
196+
/**
197+
* A class enabling the use of the `resolveField` as a pseudo-property in type-tracking predicates.
198+
*/
199+
private class ResolveFieldAsTypeTrackingProperty extends TypeTrackingPseudoProperty {
200+
ResolveFieldAsTypeTrackingProperty() { this = Promises::valueProp() }
201+
}
202+
}
203+
204+
/**
205+
* An `AdditionalFlowStep` used to model a data-flow step related to promises.
206+
*
207+
* The `loadStep`/`storeStep`/`loadStoreStep` methods are overloaded such that the new predicates
208+
* `load`/`store`/`loadStore` can be used in the `PromiseTypeTracking` module.
209+
* (Thereby avoiding conflicts with a "cousin" `AdditionalFlowStep` implementation.)
210+
*
211+
* The class is private and is only intended to be used inside the `PromiseTypeTracking` and `PromiseFlow` modules.
212+
*/
213+
abstract private class PromiseFlowStep extends DataFlow::AdditionalFlowStep {
214+
final override predicate step(DataFlow::Node pred, DataFlow::Node succ) { none() }
215+
216+
final override predicate step(
217+
DataFlow::Node p, DataFlow::Node s, DataFlow::FlowLabel pl, DataFlow::FlowLabel sl
218+
) {
219+
none()
220+
}
221+
222+
/**
223+
* Holds if the property `prop` of the object `pred` should be loaded into `succ`.
224+
*/
225+
predicate load(DataFlow::Node pred, DataFlow::Node succ, string prop) { none() }
226+
227+
final override predicate loadStep(DataFlow::Node pred, DataFlow::Node succ, string prop) {
228+
this.load(pred, succ, prop)
229+
}
230+
231+
/**
232+
* Holds if `pred` should be stored in the object `succ` under the property `prop`.
233+
*/
234+
predicate store(DataFlow::Node pred, DataFlow::Node succ, string prop) { none() }
235+
236+
final override predicate storeStep(DataFlow::Node pred, DataFlow::Node succ, string prop) {
237+
this.store(pred, succ, prop)
238+
}
239+
240+
/**
241+
* Holds if the property `prop` should be copied from the object `pred` to the object `succ`.
242+
*/
243+
predicate loadStore(DataFlow::Node pred, DataFlow::Node succ, string prop) { none() }
244+
245+
final override predicate loadStoreStep(DataFlow::Node pred, DataFlow::Node succ, string prop) {
246+
this.loadStore(pred, succ, prop)
247+
}
248+
}
249+
250+
/**
251+
* This module defines how data-flow propagates into and out of a Promise.
252+
* The data-flow is based on pseudo-properties rather than tainting the Promise object (which is what `PromiseTaintStep` does).
253+
*/
254+
private module PromiseFlow {
255+
private predicate valueProp = Promises::valueProp/0;
256+
257+
private predicate errorProp = Promises::errorProp/0;
137258

138259
/**
139260
* A flow step describing a promise definition.
140261
*
141262
* The resolved/rejected value is written to a pseudo-field on the promise.
142263
*/
143-
class PromiseDefitionStep extends DataFlow::AdditionalFlowStep {
264+
class PromiseDefitionStep extends PromiseFlowStep {
144265
PromiseDefinition promise;
145266

146267
PromiseDefitionStep() { this = promise }
147268

148-
override predicate storeStep(DataFlow::Node pred, DataFlow::Node succ, string prop) {
149-
prop = resolveField() and
269+
override predicate store(DataFlow::Node pred, DataFlow::Node succ, string prop) {
270+
prop = valueProp() and
150271
pred = promise.getResolveParameter().getACall().getArgument(0) and
151272
succ = this
152273
or
153-
prop = rejectField() and
274+
prop = errorProp() and
154275
(
155276
pred = promise.getRejectParameter().getACall().getArgument(0) or
156277
pred = promise.getExecutor().getExceptionalReturn()
157278
) and
158279
succ = this
159280
}
160281

161-
override predicate loadStoreStep(DataFlow::Node pred, DataFlow::Node succ, string prop) {
282+
override predicate loadStore(DataFlow::Node pred, DataFlow::Node succ, string prop) {
162283
// Copy the value of a resolved promise to the value of this promise.
163-
prop = resolveField() and
284+
prop = valueProp() and
164285
pred = promise.getResolveParameter().getACall().getArgument(0) and
165286
succ = this
166287
}
@@ -169,20 +290,20 @@ private module PromiseFlow {
169290
/**
170291
* A flow step describing the a Promise.resolve (and similar) call.
171292
*/
172-
class CreationStep extends DataFlow::AdditionalFlowStep {
293+
class CreationStep extends PromiseFlowStep {
173294
PromiseCreationCall promise;
174295

175296
CreationStep() { this = promise }
176297

177-
override predicate storeStep(DataFlow::Node pred, DataFlow::Node succ, string prop) {
178-
prop = resolveField() and
298+
override predicate store(DataFlow::Node pred, DataFlow::Node succ, string prop) {
299+
prop = valueProp() and
179300
pred = promise.getValue() and
180301
succ = this
181302
}
182303

183-
override predicate loadStoreStep(DataFlow::Node pred, DataFlow::Node succ, string prop) {
304+
override predicate loadStore(DataFlow::Node pred, DataFlow::Node succ, string prop) {
184305
// Copy the value of a resolved promise to the value of this promise.
185-
prop = resolveField() and
306+
prop = valueProp() and
186307
pred = promise.getValue() and
187308
succ = this
188309
}
@@ -192,7 +313,7 @@ private module PromiseFlow {
192313
* A load step loading the pseudo-field describing that the promise is rejected.
193314
* The rejected value is thrown as a exception.
194315
*/
195-
class AwaitStep extends DataFlow::AdditionalFlowStep {
316+
class AwaitStep extends PromiseFlowStep {
196317
DataFlow::Node operand;
197318
AwaitExpr await;
198319

@@ -201,12 +322,12 @@ private module PromiseFlow {
201322
operand.getEnclosingExpr() = await.getOperand()
202323
}
203324

204-
override predicate loadStep(DataFlow::Node pred, DataFlow::Node succ, string prop) {
205-
prop = resolveField() and
325+
override predicate load(DataFlow::Node pred, DataFlow::Node succ, string prop) {
326+
prop = valueProp() and
206327
succ = this and
207328
pred = operand
208329
or
209-
prop = rejectField() and
330+
prop = errorProp() and
210331
succ = await.getExceptionTarget() and
211332
pred = operand
212333
}
@@ -215,37 +336,37 @@ private module PromiseFlow {
215336
/**
216337
* A flow step describing the data-flow related to the `.then` method of a promise.
217338
*/
218-
class ThenStep extends DataFlow::AdditionalFlowStep, DataFlow::MethodCallNode {
339+
class ThenStep extends PromiseFlowStep, DataFlow::MethodCallNode {
219340
ThenStep() { this.getMethodName() = "then" }
220341

221-
override predicate loadStep(DataFlow::Node pred, DataFlow::Node succ, string prop) {
222-
prop = resolveField() and
342+
override predicate load(DataFlow::Node pred, DataFlow::Node succ, string prop) {
343+
prop = valueProp() and
223344
pred = getReceiver() and
224345
succ = getCallback(0).getParameter(0)
225346
or
226-
prop = rejectField() and
347+
prop = errorProp() and
227348
pred = getReceiver() and
228349
succ = getCallback(1).getParameter(0)
229350
}
230351

231-
override predicate loadStoreStep(DataFlow::Node pred, DataFlow::Node succ, string prop) {
352+
override predicate loadStore(DataFlow::Node pred, DataFlow::Node succ, string prop) {
232353
not exists(this.getArgument(1)) and
233-
prop = rejectField() and
354+
prop = errorProp() and
234355
pred = getReceiver() and
235356
succ = this
236357
or
237358
// read the value of a resolved/rejected promise that is returned
238-
(prop = rejectField() or prop = resolveField()) and
359+
(prop = errorProp() or prop = valueProp()) and
239360
pred = getCallback([0 .. 1]).getAReturn() and
240361
succ = this
241362
}
242363

243-
override predicate storeStep(DataFlow::Node pred, DataFlow::Node succ, string prop) {
244-
prop = resolveField() and
364+
override predicate store(DataFlow::Node pred, DataFlow::Node succ, string prop) {
365+
prop = valueProp() and
245366
pred = getCallback([0 .. 1]).getAReturn() and
246367
succ = this
247368
or
248-
prop = rejectField() and
369+
prop = errorProp() and
249370
pred = getCallback([0 .. 1]).getExceptionalReturn() and
250371
succ = this
251372
}
@@ -254,32 +375,32 @@ private module PromiseFlow {
254375
/**
255376
* A flow step describing the data-flow related to the `.catch` method of a promise.
256377
*/
257-
class CatchStep extends DataFlow::AdditionalFlowStep, DataFlow::MethodCallNode {
378+
class CatchStep extends PromiseFlowStep, DataFlow::MethodCallNode {
258379
CatchStep() { this.getMethodName() = "catch" }
259380

260-
override predicate loadStep(DataFlow::Node pred, DataFlow::Node succ, string prop) {
261-
prop = rejectField() and
381+
override predicate load(DataFlow::Node pred, DataFlow::Node succ, string prop) {
382+
prop = errorProp() and
262383
pred = getReceiver() and
263384
succ = getCallback(0).getParameter(0)
264385
}
265386

266-
override predicate loadStoreStep(DataFlow::Node pred, DataFlow::Node succ, string prop) {
267-
prop = resolveField() and
387+
override predicate loadStore(DataFlow::Node pred, DataFlow::Node succ, string prop) {
388+
prop = valueProp() and
268389
pred = getReceiver().getALocalSource() and
269390
succ = this
270391
or
271392
// read the value of a resolved/rejected promise that is returned
272-
(prop = rejectField() or prop = resolveField()) and
393+
(prop = errorProp() or prop = valueProp()) and
273394
pred = getCallback(0).getAReturn() and
274395
succ = this
275396
}
276397

277-
override predicate storeStep(DataFlow::Node pred, DataFlow::Node succ, string prop) {
278-
prop = rejectField() and
398+
override predicate store(DataFlow::Node pred, DataFlow::Node succ, string prop) {
399+
prop = errorProp() and
279400
pred = getCallback(0).getExceptionalReturn() and
280401
succ = this
281402
or
282-
prop = resolveField() and
403+
prop = valueProp() and
283404
pred = getCallback(0).getAReturn() and
284405
succ = this
285406
}
@@ -288,22 +409,22 @@ private module PromiseFlow {
288409
/**
289410
* A flow step describing the data-flow related to the `.finally` method of a promise.
290411
*/
291-
class FinallyStep extends DataFlow::AdditionalFlowStep, DataFlow::MethodCallNode {
412+
class FinallyStep extends PromiseFlowStep, DataFlow::MethodCallNode {
292413
FinallyStep() { this.getMethodName() = "finally" }
293414

294-
override predicate loadStoreStep(DataFlow::Node pred, DataFlow::Node succ, string prop) {
295-
(prop = resolveField() or prop = rejectField()) and
415+
override predicate loadStore(DataFlow::Node pred, DataFlow::Node succ, string prop) {
416+
(prop = valueProp() or prop = errorProp()) and
296417
pred = getReceiver() and
297418
succ = this
298419
or
299420
// read the value of a rejected promise that is returned
300-
prop = rejectField() and
421+
prop = errorProp() and
301422
pred = getCallback(0).getAReturn() and
302423
succ = this
303424
}
304425

305-
override predicate storeStep(DataFlow::Node pred, DataFlow::Node succ, string prop) {
306-
prop = rejectField() and
426+
override predicate store(DataFlow::Node pred, DataFlow::Node succ, string prop) {
427+
prop = errorProp() and
307428
pred = getCallback(0).getExceptionalReturn() and
308429
succ = this
309430
}

0 commit comments

Comments
 (0)