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

Skip to content

Commit edc5117

Browse files
author
Max Schaefer
committed
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.
1 parent 414ab8e commit edc5117

7 files changed

Lines changed: 92 additions & 1 deletion

File tree

javascript/ql/src/semmle/javascript/dataflow/Configuration.qll

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -441,7 +441,8 @@ private predicate exploratoryFlowStep(
441441
) {
442442
basicFlowStep(pred, succ, _, cfg) or
443443
basicStoreStep(pred, succ, _) or
444-
loadStep(pred, succ, _)
444+
loadStep(pred, succ, _) or
445+
approximateCallbackStep(pred, succ)
445446
}
446447

447448
/**
@@ -618,6 +619,26 @@ private predicate flowThroughProperty(
618619
)
619620
}
620621

622+
/**
623+
* Holds if `pred` is passed as an argument to a function `f` which also takes a
624+
* callback parameter `cb` and then invokes `cb`, passing `pred` into parameter `succ`
625+
* of `cb`.
626+
*/
627+
private predicate flowIntoHigherOrderCall(
628+
DataFlow::Node pred, DataFlow::Node succ, DataFlow::Configuration cfg, PathSummary summary
629+
) {
630+
exists(
631+
Function f, DataFlow::InvokeNode fCall, DataFlow::Node fArg, DataFlow::FunctionNode cb,
632+
DataFlow::InvokeNode cbCall, int i, PathSummary oldSummary
633+
|
634+
reachableFromInput(f, fCall, pred, cbCall.getArgument(i), cfg, oldSummary) and
635+
argumentPassing(fCall, fArg, f, cbCall.getCalleeNode().getALocalSource()) and
636+
cb = fArg.getALocalSource() and
637+
succ = cb.getParameter(i) and
638+
summary = oldSummary.append(PathSummary::call())
639+
)
640+
}
641+
621642
/**
622643
* Holds if there is a flow step from `pred` to `succ` described by `summary`
623644
* under configuration `cfg`.
@@ -634,6 +655,9 @@ private predicate flowStep(
634655
or
635656
// Flow through a property write/read pair
636657
flowThroughProperty(pred, succ, cfg, summary)
658+
or
659+
// Flow into higher-order call
660+
flowIntoHigherOrderCall(pred, succ, cfg, summary)
637661
) and
638662
not cfg.isBarrier(succ) and
639663
not cfg.isBarrier(pred, succ) and

javascript/ql/src/semmle/javascript/dataflow/TrackedNodes.qll

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -100,6 +100,8 @@ private module NodeTracking {
100100
basicStoreStep(mid, nd, _)
101101
or
102102
loadStep(mid, nd, _)
103+
or
104+
approximateCallbackStep(mid, nd)
103105
)
104106
}
105107

@@ -203,6 +205,26 @@ private module NodeTracking {
203205
)
204206
}
205207

208+
/**
209+
* Holds if `pred` is passed as an argument to a function `f` which also takes a
210+
* callback parameter `cb` and then invokes `cb`, passing `pred` into parameter `succ`
211+
* of `cb`.
212+
*/
213+
private predicate flowIntoHigherOrderCall(
214+
DataFlow::Node pred, DataFlow::Node succ, PathSummary summary
215+
) {
216+
exists(
217+
Function f, DataFlow::InvokeNode fCall, DataFlow::Node fArg, DataFlow::FunctionNode cb,
218+
DataFlow::InvokeNode cbCall, int i, PathSummary oldSummary
219+
|
220+
reachableFromInput(f, fCall, pred, cbCall.getArgument(i), oldSummary) and
221+
argumentPassing(fCall, fArg, f, cbCall.getCalleeNode().getALocalSource()) and
222+
cb = fArg.getALocalSource() and
223+
succ = cb.getParameter(i) and
224+
summary = oldSummary.append(PathSummary::call())
225+
)
226+
}
227+
206228
/**
207229
* Holds if there is a flow step from `pred` to `succ` described by `summary`.
208230
*/
@@ -216,6 +238,9 @@ private module NodeTracking {
216238
or
217239
// Flow through a property write/read pair
218240
flowThroughProperty(pred, succ, summary)
241+
or
242+
// Flow into higher-order call
243+
flowIntoHigherOrderCall(pred, succ, summary)
219244
}
220245

221246
/**

javascript/ql/src/semmle/javascript/dataflow/internal/FlowSteps.qll

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -235,6 +235,21 @@ predicate loadStep(DataFlow::Node pred, DataFlow::PropRead succ, string prop) {
235235
succ.accesses(pred, prop)
236236
}
237237

238+
/**
239+
* Holds if there is a call with arguments `cb` and `pred`, and `succ` is
240+
* a parameter of a function that may flow into `cb`.
241+
*
242+
* This is an over-approximation of a possible data flow step through a callback
243+
* invocation.
244+
*/
245+
predicate approximateCallbackStep(DataFlow::Node pred, DataFlow::Node succ) {
246+
exists (DataFlow::InvokeNode invk, DataFlow::FunctionNode cb |
247+
pred = invk.getAnArgument() and
248+
cb.flowsTo(invk.getAnArgument()) and
249+
succ = cb.getAParameter()
250+
)
251+
}
252+
238253
/**
239254
* A utility class that is equivalent to `boolean` but does not require type joining.
240255
*/

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
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+
| callback.js:16:14:16:21 | "source" | callback.js:13:14:13:14 | x |
45
| destructuring.js:2:16:2:24 | "tainted" | destructuring.js:9:15:9:22 | tainted2 |
56
| destructuring.js:19:15:19:23 | "tainted" | destructuring.js:14:15:14:15 | p |
67
| destructuring.js:20:15:20:28 | "also tainted" | destructuring.js:15:15:15:15 | r |

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
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+
| callback.js:16:14:16:21 | "source" | callback.js:13:14:13:14 | x |
45
| custom.js:1:14:1:26 | "verschmutzt" | custom.js:2:15:2:20 | quelle |
56
| destructuring.js:2:16:2:24 | "tainted" | destructuring.js:9:15:9:22 | tainted2 |
67
| destructuring.js:19:15:19:23 | "tainted" | destructuring.js:14:15:14:15 | p |

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

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
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+
| callback.js:16:14:16:21 | "source" | callback.js:13:14:13:14 | x |
5+
| callback.js:17:15:17:23 | "source2" | callback.js:13:14:13:14 | x |
46
| destructuring.js:2:16:2:24 | "tainted" | destructuring.js:5:14:5:20 | tainted |
57
| destructuring.js:2:16:2:24 | "tainted" | destructuring.js:9:15:9:22 | tainted2 |
68
| destructuring.js:19:15:19:23 | "tainted" | destructuring.js:14:15:14:15 | p |
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
function call(f, x) {
2+
return f(x);
3+
}
4+
5+
function map(f, xs) {
6+
const res = [];
7+
for (let i=0;i<xs.length;++i)
8+
res[i] = f(xs[i]);
9+
return res;
10+
}
11+
12+
function store(x) {
13+
let sink = x;
14+
}
15+
16+
let source = "source";
17+
let source2 = "source2";
18+
call(store, source);
19+
call(store, confounder); // call with different argument to make sure the call graph builder
20+
// doesn't resolve the call on line 2 for us
21+
map(store, [source2]);
22+
23+
// semmle-extractor-options: --source-type module

0 commit comments

Comments
 (0)