JavaScript: Track flow into (simple) higher-order function calls.

The only case we support for now are functions that invoke one of their arguments, passing another argument as input.
This commit is contained in:
Max Schaefer
2019-01-11 08:05:54 +00:00
parent 414ab8ea8c
commit edc5117dfd
7 changed files with 92 additions and 1 deletions

View File

@@ -441,7 +441,8 @@ private predicate exploratoryFlowStep(
) {
basicFlowStep(pred, succ, _, cfg) or
basicStoreStep(pred, succ, _) or
loadStep(pred, succ, _)
loadStep(pred, succ, _) or
approximateCallbackStep(pred, succ)
}
/**
@@ -618,6 +619,26 @@ private predicate flowThroughProperty(
)
}
/**
* Holds if `pred` is passed as an argument to a function `f` which also takes a
* callback parameter `cb` and then invokes `cb`, passing `pred` into parameter `succ`
* of `cb`.
*/
private predicate flowIntoHigherOrderCall(
DataFlow::Node pred, DataFlow::Node succ, DataFlow::Configuration cfg, PathSummary summary
) {
exists(
Function f, DataFlow::InvokeNode fCall, DataFlow::Node fArg, DataFlow::FunctionNode cb,
DataFlow::InvokeNode cbCall, int i, PathSummary oldSummary
|
reachableFromInput(f, fCall, pred, cbCall.getArgument(i), cfg, oldSummary) and
argumentPassing(fCall, fArg, f, cbCall.getCalleeNode().getALocalSource()) and
cb = fArg.getALocalSource() and
succ = cb.getParameter(i) and
summary = oldSummary.append(PathSummary::call())
)
}
/**
* Holds if there is a flow step from `pred` to `succ` described by `summary`
* under configuration `cfg`.
@@ -634,6 +655,9 @@ private predicate flowStep(
or
// Flow through a property write/read pair
flowThroughProperty(pred, succ, cfg, summary)
or
// Flow into higher-order call
flowIntoHigherOrderCall(pred, succ, cfg, summary)
) and
not cfg.isBarrier(succ) and
not cfg.isBarrier(pred, succ) and

View File

@@ -100,6 +100,8 @@ private module NodeTracking {
basicStoreStep(mid, nd, _)
or
loadStep(mid, nd, _)
or
approximateCallbackStep(mid, nd)
)
}
@@ -203,6 +205,26 @@ private module NodeTracking {
)
}
/**
* Holds if `pred` is passed as an argument to a function `f` which also takes a
* callback parameter `cb` and then invokes `cb`, passing `pred` into parameter `succ`
* of `cb`.
*/
private predicate flowIntoHigherOrderCall(
DataFlow::Node pred, DataFlow::Node succ, PathSummary summary
) {
exists(
Function f, DataFlow::InvokeNode fCall, DataFlow::Node fArg, DataFlow::FunctionNode cb,
DataFlow::InvokeNode cbCall, int i, PathSummary oldSummary
|
reachableFromInput(f, fCall, pred, cbCall.getArgument(i), oldSummary) and
argumentPassing(fCall, fArg, f, cbCall.getCalleeNode().getALocalSource()) and
cb = fArg.getALocalSource() and
succ = cb.getParameter(i) and
summary = oldSummary.append(PathSummary::call())
)
}
/**
* Holds if there is a flow step from `pred` to `succ` described by `summary`.
*/
@@ -216,6 +238,9 @@ private module NodeTracking {
or
// Flow through a property write/read pair
flowThroughProperty(pred, succ, summary)
or
// Flow into higher-order call
flowIntoHigherOrderCall(pred, succ, summary)
}
/**

View File

@@ -235,6 +235,21 @@ predicate loadStep(DataFlow::Node pred, DataFlow::PropRead succ, string prop) {
succ.accesses(pred, prop)
}
/**
* Holds if there is a call with arguments `cb` and `pred`, and `succ` is
* a parameter of a function that may flow into `cb`.
*
* This is an over-approximation of a possible data flow step through a callback
* invocation.
*/
predicate approximateCallbackStep(DataFlow::Node pred, DataFlow::Node succ) {
exists (DataFlow::InvokeNode invk, DataFlow::FunctionNode cb |
pred = invk.getAnArgument() and
cb.flowsTo(invk.getAnArgument()) and
succ = cb.getAParameter()
)
}
/**
* A utility class that is equivalent to `boolean` but does not require type joining.
*/

View File

@@ -1,6 +1,7 @@
| 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 |
| callback.js:16:14:16:21 | "source" | callback.js:13:14:13:14 | x |
| destructuring.js:2:16:2:24 | "tainted" | destructuring.js:9:15:9:22 | tainted2 |
| destructuring.js:19:15:19:23 | "tainted" | destructuring.js:14:15:14:15 | p |
| destructuring.js:20:15:20:28 | "also tainted" | destructuring.js:15:15:15:15 | r |

View File

@@ -1,6 +1,7 @@
| 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 |
| callback.js:16:14:16:21 | "source" | callback.js:13:14:13:14 | x |
| custom.js:1:14:1:26 | "verschmutzt" | custom.js:2:15:2:20 | quelle |
| destructuring.js:2:16:2:24 | "tainted" | destructuring.js:9:15:9:22 | tainted2 |
| destructuring.js:19:15:19:23 | "tainted" | destructuring.js:14:15:14:15 | p |

View File

@@ -1,6 +1,8 @@
| 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 |
| 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 |
| destructuring.js:2:16:2:24 | "tainted" | destructuring.js:5:14:5:20 | tainted |
| destructuring.js:2:16:2:24 | "tainted" | destructuring.js:9:15:9:22 | tainted2 |
| destructuring.js:19:15:19:23 | "tainted" | destructuring.js:14:15:14:15 | p |

View File

@@ -0,0 +1,23 @@
function call(f, x) {
return f(x);
}
function map(f, xs) {
const res = [];
for (let i=0;i<xs.length;++i)
res[i] = f(xs[i]);
return res;
}
function store(x) {
let sink = x;
}
let source = "source";
let source2 = "source2";
call(store, source);
call(store, confounder); // call with different argument to make sure the call graph builder
// doesn't resolve the call on line 2 for us
map(store, [source2]);
// semmle-extractor-options: --source-type module