add precise return-flow for async functions

This commit is contained in:
Erik Krogh Kristensen
2020-08-07 16:37:01 +02:00
parent cc94c5ec60
commit 26ef2f34da
5 changed files with 149 additions and 0 deletions

View File

@@ -455,6 +455,58 @@ private class PromiseTaintStep extends TaintTracking::AdditionalTaintStep {
}
}
/**
* Defines flow steps for return on async functions.
*/
private module AsyncReturnSteps {
private predicate valueProp = Promises::valueProp/0;
private predicate errorProp = Promises::errorProp/0;
private import semmle.javascript.dataflow.internal.FlowSteps
/**
* A data-flow step for ordinary and exceptional returns from async functions.
*/
private class AsyncReturn extends PreCallGraphStep {
// Note: partially copy-paste from FlowSteps::CachedSteps::returnStep/2
override predicate storeStep(DataFlow::Node pred, DataFlow::SourceNode succ, string prop) {
exists(DataFlow::FunctionNode f | f.getFunction().isAsync() |
// ordinary return
prop = valueProp() and
pred = f.getAReturn() and
succ = f.getReturnNode()
or
// exceptional return
prop = errorProp() and
exists(Expr expr |
expr = any(ThrowStmt throw).getExpr() and
pred = expr.flow()
or
DataFlow::exceptionalInvocationReturnNode(pred, expr)
|
f.getFunction() = expr.getContainer() and
succ = f.getReturnNode() // returns a rejected promise - therefore using the ordinary return node.
)
)
}
}
/**
* A data-flow step for ordinary return from an async function in a taint configuration.
*/
private class AsyncTaintReturn extends TaintTracking::AdditionalTaintStep, DataFlow::FunctionNode {
Function f;
AsyncTaintReturn() { this.getFunction() = f and f.isAsync() }
override predicate step(DataFlow::Node pred, DataFlow::Node succ) {
returnExpr(f, pred, _) and
succ.(DataFlow::FunctionReturnNode).getFunction() = f
}
}
}
/**
* Provides classes for working with the `bluebird` library (http://bluebirdjs.com).
*/

View File

@@ -1,6 +1,12 @@
| a.js:1:15:1:23 | "tainted" | b.js:4:13:4:40 | whoKnow ... Tainted |
| a.js:1:15:1:23 | "tainted" | b.js:6:13:6:13 | x |
| a.js:2:15:2:28 | "also tainted" | b.js:5:13:5:29 | notTaintedTrustMe |
| async.js:2:16:2:23 | "source" | async.js:8:15:8:27 | await async() |
| async.js:2:16:2:23 | "source" | async.js:13:15:13:20 | sync() |
| async.js:2:16:2:23 | "source" | async.js:27:17:27:17 | e |
| async.js:2:16:2:23 | "source" | async.js:36:17:36:17 | e |
| async.js:2:16:2:23 | "source" | async.js:41:17:41:17 | e |
| async.js:2:16:2:23 | "source" | async.js:54:17:54:36 | unpack(pack(source)) |
| callback.js:16:14:16:21 | "source" | callback.js:13:14:13:14 | x |
| callback.js:17:15:17:23 | "source2" | callback.js:13:14:13:14 | x |
| callback.js:27:15:27:23 | "source3" | callback.js:13:14:13:14 | x |

View File

@@ -1,6 +1,14 @@
| a.js:1:15:1:23 | "tainted" | b.js:4:13:4:40 | whoKnow ... Tainted |
| a.js:1:15:1:23 | "tainted" | b.js:6:13:6:13 | x |
| a.js:2:15:2:28 | "also tainted" | b.js:5:13:5:29 | notTaintedTrustMe |
| async.js:2:16:2:23 | "source" | async.js:7:14:7:20 | async() |
| async.js:2:16:2:23 | "source" | async.js:8:15:8:27 | await async() |
| async.js:2:16:2:23 | "source" | async.js:13:15:13:20 | sync() |
| async.js:2:16:2:23 | "source" | async.js:14:15:14:26 | await sync() |
| async.js:2:16:2:23 | "source" | async.js:27:17:27:17 | e |
| async.js:2:16:2:23 | "source" | async.js:36:17:36:17 | e |
| async.js:2:16:2:23 | "source" | async.js:41:17:41:17 | e |
| async.js:2:16:2:23 | "source" | async.js:54:17:54:36 | unpack(pack(source)) |
| callback.js:16:14:16:21 | "source" | callback.js:13:14:13:14 | x |
| callback.js:17:15:17:23 | "source2" | callback.js:13:14:13:14 | x |
| callback.js:27:15:27:23 | "source3" | callback.js:13:14:13:14 | x |

View File

@@ -1,3 +1,11 @@
| missing | async.js:1:2:1:2 | source | async.js:8:15:8:27 | await async() |
| missing | async.js:1:2:1:2 | source | async.js:26:12:26:12 | e |
| missing | async.js:1:2:1:2 | source | async.js:26:12:26:12 | e |
| missing | async.js:1:2:1:2 | source | async.js:27:17:27:17 | e |
| missing | async.js:2:16:2:23 | "source" | async.js:8:15:8:27 | await async() |
| missing | async.js:2:16:2:23 | "source" | async.js:26:12:26:12 | e |
| missing | async.js:2:16:2:23 | "source" | async.js:26:12:26:12 | e |
| missing | async.js:2:16:2:23 | "source" | async.js:27:17:27:17 | e |
| missing | callback.js:17:15:17:23 | "source2" | callback.js:8:16:8:20 | xs[i] |
| missing | callback.js:17:15:17:23 | "source2" | callback.js:12:16:12:16 | x |
| missing | callback.js:17:15:17:23 | "source2" | callback.js:12:16:12:16 | x |
@@ -53,3 +61,7 @@
| missing | tst.js:2:17:2:22 | "src1" | tst.js:27:22:27:24 | elt |
| missing | tst.js:2:17:2:22 | "src1" | tst.js:27:22:27:24 | elt |
| missing | tst.js:2:17:2:22 | "src1" | tst.js:28:20:28:22 | elt |
| spurious | async.js:1:2:1:2 | source | async.js:7:14:7:20 | async() |
| spurious | async.js:1:2:1:2 | source | async.js:8:21:8:27 | async() |
| spurious | async.js:2:16:2:23 | "source" | async.js:7:14:7:20 | async() |
| spurious | async.js:2:16:2:23 | "source" | async.js:8:21:8:27 | async() |

View File

@@ -0,0 +1,71 @@
(async function () {
let source = "source";
async function async() {
return source;
}
let sink = async(); // OK - wrapped in a promise. (NOT OK for taint-tracking configs)
let sink2 = await async(); // NOT OK
function sync() {
return source;
}
let sink3 = sync(); // NOT OK
let sink4 = await sync(); // OK
async function throwsAsync() {
throw source;
}
try {
throwsAsync();
} catch (e) {
let sink5 = e; // OK - throwsAsync just returns a promise.
}
try {
await throwsAsync();
} catch (e) {
let sink6 = e; // NOT OK
}
function throws() {
throw source;
}
try {
throws();
} catch (e) {
let sink5 = e; // NOT OK
}
try {
await throws();
} catch (e) {
let sink6 = e; // NOT OK
}
function syncTest() {
function pack(x) {
return {
x: x
}
};
function unpack(x) {
return x.x;
}
var sink7 = unpack(pack(source)); // NOT OK
}
function asyncTest() {
async function pack(x) {
return {
x: x
}
};
function unpack(x) {
return x.x;
}
var sink8 = unpack(pack(source)); // OK
let sink9 = unpack(await (pack(source))); // NOT OK - but not found
}
})();