Skip to content

Commit f48d16f

Browse files
committed
JS: Support barrier guards that are reflective calls
1 parent d615842 commit f48d16f

6 files changed

Lines changed: 42 additions & 3 deletions

File tree

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

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -308,7 +308,7 @@ abstract class BarrierGuardNode extends DataFlow::Node {
308308
exists(SsaRefinementNode ref, boolean outcome |
309309
nd = DataFlow::ssaDefinitionNode(ref) and
310310
forex(SsaVariable input | input = ref.getAnInput() |
311-
asExpr() = ref.getGuard().getTest() and
311+
getExpr() = ref.getGuard().getTest() and
312312
outcome = ref.getGuard().(ConditionGuardNode).getOutcome() and
313313
barrierGuardBlocksExpr(this, outcome, input.getAUse(), label)
314314
)
@@ -317,13 +317,20 @@ abstract class BarrierGuardNode extends DataFlow::Node {
317317
// 2) `nd` is an instance of an access path `p`, and dominated by a barrier for `p`
318318
exists(AccessPath p, BasicBlock bb, ConditionGuardNode cond, boolean outcome |
319319
nd = DataFlow::valueNode(p.getAnInstanceIn(bb)) and
320-
asExpr() = cond.getTest() and
320+
getExpr() = cond.getTest() and
321321
outcome = cond.getOutcome() and
322322
barrierGuardBlocksAccessPath(this, outcome, p, label) and
323323
cond.dominates(bb)
324324
)
325325
}
326326

327+
/** Gets the corresponding expression, including that of reflective calls. */
328+
private Expr getExpr() {
329+
result = asExpr()
330+
or
331+
this = DataFlow::reflectiveCallNode(result)
332+
}
333+
327334
/**
328335
* Holds if this node blocks expression `e` provided it evaluates to `outcome`.
329336
*

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

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -932,6 +932,15 @@ module DataFlow {
932932
*/
933933
DataFlow::Node globalAccessPathRootPseudoNode() { result instanceof TGlobalAccessPathRoot }
934934

935+
/**
936+
* Gets a data flow node representing the underlying call performed by the given
937+
* call to `Function.prototype.call` or `Function.prototype.apply`.
938+
*
939+
* For example, for an expression `fn.call(x, y)`, this gets a call node with `fn` as the
940+
* callee, `x` as the receiver, and `y` as the first argument.
941+
*/
942+
DataFlow::InvokeNode reflectiveCallNode(InvokeExpr expr) { result = TReflectiveCallNode(expr, _) }
943+
935944
/**
936945
* Provides classes representing various kinds of calls.
937946
*

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

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,8 @@ typeInferenceMismatch
7575
| sanitizer-guards.js:13:14:13:21 | source() | sanitizer-guards.js:15:10:15:15 | this.x |
7676
| sanitizer-guards.js:13:14:13:21 | source() | sanitizer-guards.js:21:14:21:19 | this.x |
7777
| sanitizer-guards.js:13:14:13:21 | source() | sanitizer-guards.js:26:9:26:14 | this.x |
78+
| sanitizer-guards.js:43:11:43:18 | source() | sanitizer-guards.js:45:8:45:8 | x |
79+
| sanitizer-guards.js:43:11:43:18 | source() | sanitizer-guards.js:48:10:48:10 | x |
7880
| spread.js:2:15:2:22 | source() | spread.js:4:8:4:19 | { ...taint } |
7981
| spread.js:2:15:2:22 | source() | spread.js:5:8:5:43 | { f: 'h ... orld' } |
8082
| spread.js:2:15:2:22 | source() | spread.js:7:8:7:19 | [ ...taint ] |

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

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,11 @@
11
import javascript
22
import semmle.javascript.dataflow.InferredTypes
33

4-
DataFlow::CallNode getACall(string name) { result.getCalleeName() = name }
4+
DataFlow::CallNode getACall(string name) {
5+
result.getCalleeName() = name
6+
or
7+
result.getCalleeNode().getALocalSource() = DataFlow::globalVarRef(name)
8+
}
59

610
class Sink extends DataFlow::Node {
711
Sink() { this = getACall("sink").getAnArgument() }

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

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,9 @@
5050
| sanitizer-guards.js:13:14:13:21 | source() | sanitizer-guards.js:15:10:15:15 | this.x |
5151
| sanitizer-guards.js:13:14:13:21 | source() | sanitizer-guards.js:21:14:21:19 | this.x |
5252
| sanitizer-guards.js:13:14:13:21 | source() | sanitizer-guards.js:26:9:26:14 | this.x |
53+
| sanitizer-guards.js:43:11:43:18 | source() | sanitizer-guards.js:45:8:45:8 | x |
54+
| sanitizer-guards.js:43:11:43:18 | source() | sanitizer-guards.js:48:10:48:10 | x |
55+
| sanitizer-guards.js:43:11:43:18 | source() | sanitizer-guards.js:52:10:52:10 | x |
5356
| thisAssignments.js:4:17:4:24 | source() | thisAssignments.js:5:10:5:18 | obj.field |
5457
| thisAssignments.js:7:19:7:26 | source() | thisAssignments.js:8:10:8:20 | this.field2 |
5558
| tst.js:2:13:2:20 | source() | tst.js:4:10:4:10 | x |

javascript/ql/test/library-tests/TaintTracking/sanitizer-guards.js

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,3 +38,17 @@ class C {
3838
});
3939
}
4040
}
41+
42+
function reflective() {
43+
let x = source();
44+
45+
sink(x); // NOT OK
46+
47+
if (isSafe.call(x)) {
48+
sink(x); // NOT OK - `isSafe` does not sanitize the receiver
49+
}
50+
51+
if (isSafe.call(null, x)) {
52+
sink(x); // OK
53+
}
54+
}

0 commit comments

Comments
 (0)