Skip to content

Commit 7d2d338

Browse files
author
Max Schaefer
committed
JavaScript: Track flow through forwarding higher-order calls.
1 parent 59bac82 commit 7d2d338

File tree

7 files changed

+49
-13
lines changed

7 files changed

+49
-13
lines changed

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

Lines changed: 16 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -442,7 +442,8 @@ private predicate exploratoryFlowStep(
442442
basicFlowStep(pred, succ, _, cfg) or
443443
basicStoreStep(pred, succ, _) or
444444
loadStep(pred, succ, _) or
445-
approximateCallbackStep(pred, succ)
445+
approximateCallbackStep(pred, succ) or
446+
succ = pred.(DataFlow::FunctionNode).getAParameter()
446447
}
447448

448449
/**
@@ -628,9 +629,20 @@ private predicate flowThroughProperty(
628629
private predicate higherOrderCall(
629630
DataFlow::Node arg, DataFlow::Node cb, int i, DataFlow::Configuration cfg, PathSummary summary
630631
) {
631-
exists (Function f, DataFlow::InvokeNode outer, DataFlow::InvokeNode inner |
632-
reachableFromInput(f, outer, arg, inner.getArgument(i), cfg, summary) and
633-
argumentPassing(outer, cb, f, inner.getCalleeNode().getALocalSource())
632+
exists (Function f, DataFlow::InvokeNode outer, DataFlow::InvokeNode inner, int j,
633+
DataFlow::Node innerArg, DataFlow::ParameterNode cbParm, PathSummary oldSummary |
634+
reachableFromInput(f, outer, arg, innerArg, cfg, oldSummary) and
635+
argumentPassing(outer, cb, f, cbParm) and
636+
innerArg = inner.getArgument(j) |
637+
cbParm.flowsTo(inner.getCalleeNode()) and
638+
i = j and
639+
summary = oldSummary
640+
or
641+
exists (DataFlow::Node cbArg, PathSummary newSummary |
642+
cbParm.flowsTo(cbArg) and
643+
higherOrderCall(innerArg, cbArg, i, cfg, newSummary) and
644+
summary = oldSummary.append(PathSummary::call()).append(newSummary)
645+
)
634646
)
635647
}
636648

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

Lines changed: 16 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -102,6 +102,8 @@ private module NodeTracking {
102102
loadStep(mid, nd, _)
103103
or
104104
approximateCallbackStep(mid, nd)
105+
or
106+
nd = mid.(DataFlow::FunctionNode).getAParameter()
105107
)
106108
}
107109

@@ -213,9 +215,20 @@ private module NodeTracking {
213215
private predicate higherOrderCall(
214216
DataFlow::Node arg, DataFlow::Node cb, int i, PathSummary summary
215217
) {
216-
exists (Function f, DataFlow::InvokeNode outer, DataFlow::InvokeNode inner |
217-
reachableFromInput(f, outer, arg, inner.getArgument(i), summary) and
218-
argumentPassing(outer, cb, f, inner.getCalleeNode().getALocalSource())
218+
exists (Function f, DataFlow::InvokeNode outer, DataFlow::InvokeNode inner, int j,
219+
DataFlow::Node innerArg, DataFlow::ParameterNode cbParm, PathSummary oldSummary |
220+
reachableFromInput(f, outer, arg, innerArg, oldSummary) and
221+
argumentPassing(outer, cb, f, cbParm) and
222+
innerArg = inner.getArgument(j) |
223+
cbParm.flowsTo(inner.getCalleeNode()) and
224+
i = j and
225+
summary = oldSummary
226+
or
227+
exists (DataFlow::Node cbArg, PathSummary newSummary |
228+
cbParm.flowsTo(cbArg) and
229+
higherOrderCall(innerArg, cbArg, i, newSummary) and
230+
summary = oldSummary.append(PathSummary::call()).append(newSummary)
231+
)
219232
)
220233
}
221234

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

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -236,17 +236,17 @@ predicate loadStep(DataFlow::Node pred, DataFlow::PropRead succ, string prop) {
236236
}
237237

238238
/**
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`.
239+
* Holds if there is a call with argument `pred`, and `succ` flows into the callee
240+
* position of that call.
241241
*
242242
* This is an over-approximation of a possible data flow step through a callback
243243
* invocation.
244244
*/
245-
predicate approximateCallbackStep(DataFlow::Node pred, DataFlow::Node succ) {
246-
exists (DataFlow::InvokeNode invk, DataFlow::FunctionNode cb |
245+
predicate approximateCallbackStep(DataFlow::Node pred, DataFlow::SourceNode succ) {
246+
exists (DataFlow::InvokeNode invk, DataFlow::ParameterNode cb |
247247
pred = invk.getAnArgument() and
248-
cb.flowsTo(invk.getAnArgument()) and
249-
succ = cb.getAParameter()
248+
cb.flowsTo(invk.getCalleeNode()) and
249+
callStep(any(DataFlow::Node nd | succ.flowsTo(nd)), cb)
250250
)
251251
}
252252

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

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

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
| a.js:2:15:2:28 | "also tainted" | b.js:5:13:5:29 | notTaintedTrustMe |
44
| callback.js:16:14:16:21 | "source" | callback.js:13:14:13:14 | x |
55
| callback.js:17:15:17:23 | "source2" | callback.js:13:14:13:14 | x |
6+
| callback.js:27:15:27:23 | "source3" | callback.js:13:14:13:14 | x |
67
| destructuring.js:2:16:2:24 | "tainted" | destructuring.js:5:14:5:20 | tainted |
78
| destructuring.js:2:16:2:24 | "tainted" | destructuring.js:9:15:9:22 | tainted2 |
89
| destructuring.js:19:15:19:23 | "tainted" | destructuring.js:14:15:14:15 | p |

javascript/ql/test/library-tests/InterProceduralFlow/callback.js

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,4 +20,12 @@ call(store, confounder); // call with different argument to make sure the call g
2020
// doesn't resolve the call on line 2 for us
2121
map(store, [source2]);
2222

23+
function call2(x, f) {
24+
call(f, x);
25+
}
26+
27+
let source3 = "source3";
28+
call2(source3, store);
29+
call2(source3, confounder);
30+
2331
// semmle-extractor-options: --source-type module

0 commit comments

Comments
 (0)