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

Skip to content

Commit 26ef2f3

Browse files
committed
add precise return-flow for async functions
1 parent cc94c5e commit 26ef2f3

5 files changed

Lines changed: 149 additions & 0 deletions

File tree

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

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -455,6 +455,58 @@ private class PromiseTaintStep extends TaintTracking::AdditionalTaintStep {
455455
}
456456
}
457457

458+
/**
459+
* Defines flow steps for return on async functions.
460+
*/
461+
private module AsyncReturnSteps {
462+
private predicate valueProp = Promises::valueProp/0;
463+
464+
private predicate errorProp = Promises::errorProp/0;
465+
466+
private import semmle.javascript.dataflow.internal.FlowSteps
467+
468+
/**
469+
* A data-flow step for ordinary and exceptional returns from async functions.
470+
*/
471+
private class AsyncReturn extends PreCallGraphStep {
472+
// Note: partially copy-paste from FlowSteps::CachedSteps::returnStep/2
473+
override predicate storeStep(DataFlow::Node pred, DataFlow::SourceNode succ, string prop) {
474+
exists(DataFlow::FunctionNode f | f.getFunction().isAsync() |
475+
// ordinary return
476+
prop = valueProp() and
477+
pred = f.getAReturn() and
478+
succ = f.getReturnNode()
479+
or
480+
// exceptional return
481+
prop = errorProp() and
482+
exists(Expr expr |
483+
expr = any(ThrowStmt throw).getExpr() and
484+
pred = expr.flow()
485+
or
486+
DataFlow::exceptionalInvocationReturnNode(pred, expr)
487+
|
488+
f.getFunction() = expr.getContainer() and
489+
succ = f.getReturnNode() // returns a rejected promise - therefore using the ordinary return node.
490+
)
491+
)
492+
}
493+
}
494+
495+
/**
496+
* A data-flow step for ordinary return from an async function in a taint configuration.
497+
*/
498+
private class AsyncTaintReturn extends TaintTracking::AdditionalTaintStep, DataFlow::FunctionNode {
499+
Function f;
500+
501+
AsyncTaintReturn() { this.getFunction() = f and f.isAsync() }
502+
503+
override predicate step(DataFlow::Node pred, DataFlow::Node succ) {
504+
returnExpr(f, pred, _) and
505+
succ.(DataFlow::FunctionReturnNode).getFunction() = f
506+
}
507+
}
508+
}
509+
458510
/**
459511
* Provides classes for working with the `bluebird` library (http://bluebirdjs.com).
460512
*/

javascript/ql/test/library-tests/InterProceduralFlow/GermanFlow.expected

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,12 @@
11
| a.js:1:15:1:23 | "tainted" | b.js:4:13:4:40 | whoKnow ... Tainted |
22
| a.js:1:15:1:23 | "tainted" | b.js:6:13:6:13 | x |
33
| a.js:2:15:2:28 | "also tainted" | b.js:5:13:5:29 | notTaintedTrustMe |
4+
| async.js:2:16:2:23 | "source" | async.js:8:15:8:27 | await async() |
5+
| async.js:2:16:2:23 | "source" | async.js:13:15:13:20 | sync() |
6+
| async.js:2:16:2:23 | "source" | async.js:27:17:27:17 | e |
7+
| async.js:2:16:2:23 | "source" | async.js:36:17:36:17 | e |
8+
| async.js:2:16:2:23 | "source" | async.js:41:17:41:17 | e |
9+
| async.js:2:16:2:23 | "source" | async.js:54:17:54:36 | unpack(pack(source)) |
410
| callback.js:16:14:16:21 | "source" | callback.js:13:14:13:14 | x |
511
| callback.js:17:15:17:23 | "source2" | callback.js:13:14:13:14 | x |
612
| callback.js:27:15:27:23 | "source3" | callback.js:13:14:13:14 | x |

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

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,14 @@
11
| a.js:1:15:1:23 | "tainted" | b.js:4:13:4:40 | whoKnow ... Tainted |
22
| a.js:1:15:1:23 | "tainted" | b.js:6:13:6:13 | x |
33
| a.js:2:15:2:28 | "also tainted" | b.js:5:13:5:29 | notTaintedTrustMe |
4+
| async.js:2:16:2:23 | "source" | async.js:7:14:7:20 | async() |
5+
| async.js:2:16:2:23 | "source" | async.js:8:15:8:27 | await async() |
6+
| async.js:2:16:2:23 | "source" | async.js:13:15:13:20 | sync() |
7+
| async.js:2:16:2:23 | "source" | async.js:14:15:14:26 | await sync() |
8+
| async.js:2:16:2:23 | "source" | async.js:27:17:27:17 | e |
9+
| async.js:2:16:2:23 | "source" | async.js:36:17:36:17 | e |
10+
| async.js:2:16:2:23 | "source" | async.js:41:17:41:17 | e |
11+
| async.js:2:16:2:23 | "source" | async.js:54:17:54:36 | unpack(pack(source)) |
412
| callback.js:16:14:16:21 | "source" | callback.js:13:14:13:14 | x |
513
| callback.js:17:15:17:23 | "source2" | callback.js:13:14:13:14 | x |
614
| callback.js:27:15:27:23 | "source3" | callback.js:13:14:13:14 | x |

javascript/ql/test/library-tests/InterProceduralFlow/TrackedNodes.expected

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,11 @@
1+
| missing | async.js:1:2:1:2 | source | async.js:8:15:8:27 | await async() |
2+
| missing | async.js:1:2:1:2 | source | async.js:26:12:26:12 | e |
3+
| missing | async.js:1:2:1:2 | source | async.js:26:12:26:12 | e |
4+
| missing | async.js:1:2:1:2 | source | async.js:27:17:27:17 | e |
5+
| missing | async.js:2:16:2:23 | "source" | async.js:8:15:8:27 | await async() |
6+
| missing | async.js:2:16:2:23 | "source" | async.js:26:12:26:12 | e |
7+
| missing | async.js:2:16:2:23 | "source" | async.js:26:12:26:12 | e |
8+
| missing | async.js:2:16:2:23 | "source" | async.js:27:17:27:17 | e |
19
| missing | callback.js:17:15:17:23 | "source2" | callback.js:8:16:8:20 | xs[i] |
210
| missing | callback.js:17:15:17:23 | "source2" | callback.js:12:16:12:16 | x |
311
| missing | callback.js:17:15:17:23 | "source2" | callback.js:12:16:12:16 | x |
@@ -53,3 +61,7 @@
5361
| missing | tst.js:2:17:2:22 | "src1" | tst.js:27:22:27:24 | elt |
5462
| missing | tst.js:2:17:2:22 | "src1" | tst.js:27:22:27:24 | elt |
5563
| missing | tst.js:2:17:2:22 | "src1" | tst.js:28:20:28:22 | elt |
64+
| spurious | async.js:1:2:1:2 | source | async.js:7:14:7:20 | async() |
65+
| spurious | async.js:1:2:1:2 | source | async.js:8:21:8:27 | async() |
66+
| spurious | async.js:2:16:2:23 | "source" | async.js:7:14:7:20 | async() |
67+
| spurious | async.js:2:16:2:23 | "source" | async.js:8:21:8:27 | async() |
Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
(async function () {
2+
let source = "source";
3+
4+
async function async() {
5+
return source;
6+
}
7+
let sink = async(); // OK - wrapped in a promise. (NOT OK for taint-tracking configs)
8+
let sink2 = await async(); // NOT OK
9+
10+
function sync() {
11+
return source;
12+
}
13+
let sink3 = sync(); // NOT OK
14+
let sink4 = await sync(); // OK
15+
16+
async function throwsAsync() {
17+
throw source;
18+
}
19+
try {
20+
throwsAsync();
21+
} catch (e) {
22+
let sink5 = e; // OK - throwsAsync just returns a promise.
23+
}
24+
try {
25+
await throwsAsync();
26+
} catch (e) {
27+
let sink6 = e; // NOT OK
28+
}
29+
30+
function throws() {
31+
throw source;
32+
}
33+
try {
34+
throws();
35+
} catch (e) {
36+
let sink5 = e; // NOT OK
37+
}
38+
try {
39+
await throws();
40+
} catch (e) {
41+
let sink6 = e; // NOT OK
42+
}
43+
44+
function syncTest() {
45+
function pack(x) {
46+
return {
47+
x: x
48+
}
49+
};
50+
function unpack(x) {
51+
return x.x;
52+
}
53+
54+
var sink7 = unpack(pack(source)); // NOT OK
55+
}
56+
57+
function asyncTest() {
58+
async function pack(x) {
59+
return {
60+
x: x
61+
}
62+
};
63+
function unpack(x) {
64+
return x.x;
65+
}
66+
67+
var sink8 = unpack(pack(source)); // OK
68+
let sink9 = unpack(await (pack(source))); // NOT OK - but not found
69+
}
70+
})();
71+

0 commit comments

Comments
 (0)