Skip to content

Commit f9b539e

Browse files
authored
Merge pull request github#6253 from asgerf/js/more-precise-capture-steps
Approved by erik-krogh
2 parents 6aec7f2 + d8927e5 commit f9b539e

File tree

6 files changed

+46
-2
lines changed

6 files changed

+46
-2
lines changed
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
lgtm,codescanning
2+
* Fixed a bug that could occur when data was tracked through a function whose parameter
3+
flows through a captured variable before reaching the return.
4+
This can lead to fewer false-positive results and more true-positive results.

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

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1183,6 +1183,13 @@ private predicate flowThroughCall(
11831183
not cfg.isLabeledBarrier(output, summary.getEndLabel())
11841184
)
11851185
or
1186+
exists(Function f, LocalVariable variable |
1187+
reachableFromInput(f, _, input, output, cfg, summary) and
1188+
output = DataFlow::capturedVariableNode(variable) and
1189+
getCapturedVariableDepth(variable) < getContainerDepth(f) and // Only step outwards
1190+
not cfg.isLabeledBarrier(output, summary.getEndLabel())
1191+
)
1192+
or
11861193
exists(Function f, DataFlow::Node invk, DataFlow::Node ret |
11871194
DataFlow::exceptionalFunctionReturnNode(ret, f) and
11881195
DataFlow::exceptionalInvocationReturnNode(output, invk.asExpr()) and

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

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -109,13 +109,30 @@ DataFlow::Node getThrowTarget(DataFlow::Node thrower) {
109109
*/
110110
cached
111111
private module CachedSteps {
112+
/** Gets the nesting depth of the given container, starting with the top-level at 0. */
113+
cached
114+
int getContainerDepth(StmtContainer container) {
115+
not exists(container.getEnclosingContainer()) and
116+
result = 0
117+
or
118+
result = 1 + getContainerDepth(container.getEnclosingContainer())
119+
}
120+
121+
/** Gets the nesting depth of the container declaring the given captured variable. */
122+
cached
123+
int getCapturedVariableDepth(LocalVariable v) {
124+
v.isCaptured() and
125+
result = getContainerDepth(v.getDeclaringContainer())
126+
}
127+
112128
/**
113129
* Holds if `f` captures the given `variable` in `cap`.
114130
*/
115131
cached
116132
predicate captures(Function f, LocalVariable variable, SsaVariableCapture cap) {
117133
variable = cap.getSourceVariable() and
118-
f = cap.getContainer()
134+
f = cap.getContainer() and
135+
not f = variable.getDeclaringContainer()
119136
}
120137

121138
/**

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

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,8 @@ typeInferenceMismatch
3434
| callbacks.js:51:18:51:25 | source() | callbacks.js:30:29:30:29 | y |
3535
| callbacks.js:53:23:53:30 | source() | callbacks.js:58:10:58:10 | x |
3636
| capture-flow.js:9:11:9:18 | source() | capture-flow.js:14:10:14:16 | outer() |
37+
| capture-flow.js:9:11:9:18 | source() | capture-flow.js:19:6:19:16 | outerMost() |
38+
| capture-flow.js:31:14:31:21 | source() | capture-flow.js:31:6:31:22 | confuse(source()) |
3739
| captured-sanitizer.js:25:3:25:10 | source() | captured-sanitizer.js:15:10:15:10 | x |
3840
| case.js:2:16:2:23 | source() | case.js:5:8:5:35 | changeC ... source) |
3941
| case.js:2:16:2:23 | source() | case.js:8:8:8:24 | camelCase(source) |

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

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,8 @@
2424
| callbacks.js:51:18:51:25 | source() | callbacks.js:30:29:30:29 | y |
2525
| callbacks.js:53:23:53:30 | source() | callbacks.js:58:10:58:10 | x |
2626
| capture-flow.js:9:11:9:18 | source() | capture-flow.js:14:10:14:16 | outer() |
27+
| capture-flow.js:9:11:9:18 | source() | capture-flow.js:19:6:19:16 | outerMost() |
28+
| capture-flow.js:31:14:31:21 | source() | capture-flow.js:31:6:31:22 | confuse(source()) |
2729
| captured-sanitizer.js:25:3:25:10 | source() | captured-sanitizer.js:15:10:15:10 | x |
2830
| constructor-calls.js:4:18:4:25 | source() | constructor-calls.js:18:8:18:14 | c.taint |
2931
| constructor-calls.js:4:18:4:25 | source() | constructor-calls.js:22:8:22:19 | c_safe.taint |

javascript/ql/test/library-tests/TaintTracking/capture-flow.js

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,4 +16,16 @@ function outerMost() {
1616
return outer();
1717
}
1818

19-
sink(outerMost()); // NOT OK - but missed
19+
sink(outerMost()); // NOT OK
20+
21+
function confuse(x) {
22+
let captured;
23+
function f() {
24+
captured = x;
25+
}
26+
f();
27+
return captured;
28+
}
29+
30+
sink(confuse('safe')); // OK
31+
sink(confuse(source())); // NOT OK

0 commit comments

Comments
 (0)