Skip to content

Commit e784813

Browse files
committed
JS: Make barrier guards work with use-use flow
1 parent 67fdd86 commit e784813

File tree

5 files changed

+113
-5
lines changed

5 files changed

+113
-5
lines changed

javascript/ql/lib/semmle/javascript/dataflow/internal/BarrierGuards.qll

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@
66

77
private import javascript
88
private import semmle.javascript.dataflow.internal.AccessPaths
9+
private import semmle.javascript.dataflow.internal.DataFlowPrivate as DataFlowPrivate
10+
private import semmle.javascript.dataflow.internal.sharedlib.Ssa as Ssa2
911

1012
private signature class BarrierGuardSig extends DataFlow::Node {
1113
/**
@@ -282,6 +284,22 @@ module MakeStateBarrierGuard<
282284
)
283285
}
284286

287+
private predicate ssa2GuardChecks(
288+
Ssa2::SsaDataflowInput::Guard guard, Ssa2::SsaDataflowInput::Expr test, boolean branch,
289+
FlowState state
290+
) {
291+
exists(BarrierGuard g |
292+
g.asExpr() = guard and
293+
g.blocksExpr(branch, test, state)
294+
)
295+
}
296+
297+
private module Ssa2Barrier = Ssa2::BarrierGuardWithState<FlowState, ssa2GuardChecks/4>;
298+
299+
private predicate ssa2BlocksNode(DataFlow::Node node, FlowState state) {
300+
node = DataFlowPrivate::getNodeFromSsa2(Ssa2Barrier::getABarrierNode(state))
301+
}
302+
285303
/** Holds if a barrier guard blocks uses of `ap` in basic blocks dominated by `cond`. */
286304
pragma[nomagic]
287305
private predicate barrierGuardBlocksAccessPathIn(
@@ -323,6 +341,8 @@ module MakeStateBarrierGuard<
323341
barrierGuardBlocksAccessPathUse(use, state) and
324342
nd = DataFlow::valueNode(use)
325343
)
344+
or
345+
ssa2BlocksNode(nd, state)
326346
}
327347

328348
/**

javascript/ql/lib/semmle/javascript/dataflow/internal/DataFlowPrivate.qll

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1062,7 +1062,8 @@ private predicate samePhi(SsaPhiNode legacyPhi, Ssa2::PhiNode newPhi) {
10621062
)
10631063
}
10641064

1065-
private Node getNodeFromSsa2(Ssa2::Node node) {
1065+
cached
1066+
Node getNodeFromSsa2(Ssa2::Node node) {
10661067
result = TSsaUseNode(node.(Ssa2::ExprNode).getExpr())
10671068
or
10681069
result = TExprPostUpdateNode(node.(Ssa2::ExprPostUpdateNode).getExpr())

javascript/ql/lib/semmle/javascript/dataflow/internal/TaintTrackingPrivate.qll

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ private import semmle.javascript.dataflow.internal.Contents::Public
55
private import semmle.javascript.dataflow.internal.sharedlib.FlowSummaryImpl as FlowSummaryImpl
66
private import semmle.javascript.dataflow.internal.FlowSummaryPrivate as FlowSummaryPrivate
77
private import semmle.javascript.dataflow.internal.BarrierGuards
8+
private import semmle.javascript.dataflow.internal.sharedlib.Ssa as Ssa2
89

910
cached
1011
predicate defaultAdditionalTaintStep(DataFlow::Node node1, DataFlow::Node node2) {
@@ -44,13 +45,51 @@ private class SanitizerGuardAdapter extends DataFlow::Node instanceof TaintTrack
4445
predicate blocksExpr(boolean outcome, Expr e) { super.sanitizes(outcome, e) }
4546
}
4647

48+
bindingset[node]
49+
pragma[inline_late]
50+
private BasicBlock getBasicBlockFromSsa2(Ssa2::Node node) {
51+
result = node.(Ssa2::ExprNode).getExpr().getBasicBlock()
52+
or
53+
node.(Ssa2::SsaInputNode).isInputInto(_, result)
54+
}
55+
56+
/**
57+
* Holds if `node` should act as a taint barrier, as it occurs after a variable has been checked to be falsy.
58+
*
59+
* For example:
60+
* ```js
61+
* if (!x) {
62+
* use(x); // <-- 'x' is a varAccessBarrier
63+
* }
64+
* ```
65+
*
66+
* This is particularly important for ensuring that query-specific barrier guards work when they
67+
* occur after a truthiness-check:
68+
* ```js
69+
* if (x && !isSafe(x)) {
70+
* throw new Error()
71+
* }
72+
* use(x); // both inputs to the phi-read for 'x' are blocked (one by varAccessBarrier, one by isSafe(x))
73+
* ```
74+
*/
75+
private predicate varAccessBarrier(DataFlow::Node node) {
76+
exists(ConditionGuardNode guard, Ssa2::ExprNode nodeFrom, Ssa2::Node nodeTo |
77+
guard.getOutcome() = false and
78+
guard.getTest().(VarAccess) = nodeFrom.getExpr() and
79+
Ssa2::localFlowStep(_, nodeFrom, nodeTo, true) and
80+
guard.dominates(getBasicBlockFromSsa2(nodeTo)) and
81+
node = getNodeFromSsa2(nodeTo)
82+
)
83+
}
84+
4785
/**
4886
* Holds if `node` should be a sanitizer in all global taint flow configurations
4987
* but not in local taint.
5088
*/
5189
cached
5290
predicate defaultTaintSanitizer(DataFlow::Node node) {
5391
node instanceof DataFlow::VarAccessBarrier or
92+
varAccessBarrier(node) or
5493
node = MakeBarrierGuard<SanitizerGuardAdapter>::getABarrierNode()
5594
}
5695

javascript/ql/lib/semmle/javascript/dataflow/internal/sharedlib/Ssa.qll

Lines changed: 21 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,7 @@ module SsaConfig implements InputSig<js::DbLocation> {
5353

5454
import Make<js::DbLocation, SsaConfig>
5555

56-
private module SsaDataflowInput implements DataFlowIntegrationInputSig {
56+
module SsaDataflowInput implements DataFlowIntegrationInputSig {
5757
class Expr extends js::ControlFlowNode {
5858
Expr() { this = any(SsaConfig::SourceVariable v).getAUse() }
5959

@@ -66,11 +66,28 @@ private module SsaDataflowInput implements DataFlowIntegrationInputSig {
6666

6767
predicate ssaDefInitializesParam(WriteDefinition def, Parameter p) { none() } // Not handled here
6868

69-
abstract class Guard extends Expr { } // empty class
69+
class Guard extends js::ControlFlowNode {
70+
Guard() { this = any(js::ConditionGuardNode g).getTest() }
7071

71-
predicate guardControlsBlock(Guard guard, js::BasicBlock bb, boolean branch) { none() }
72+
predicate hasCfgNode(js::BasicBlock bb, int i) { this = bb.getNode(i) }
73+
}
74+
75+
pragma[inline]
76+
predicate guardControlsBlock(Guard guard, js::BasicBlock bb, boolean branch) {
77+
exists(js::ConditionGuardNode g |
78+
g.getTest() = guard and
79+
g.dominates(bb) and
80+
branch = g.getOutcome()
81+
)
82+
}
7283

73-
js::BasicBlock getAConditionalBasicBlockSuccessor(js::BasicBlock bb, boolean branch) { none() }
84+
js::BasicBlock getAConditionalBasicBlockSuccessor(js::BasicBlock bb, boolean branch) {
85+
exists(js::ConditionGuardNode g |
86+
bb = g.getTest().getBasicBlock() and
87+
result = g.getBasicBlock() and
88+
branch = g.getOutcome()
89+
)
90+
}
7491
}
7592

7693
import DataFlowIntegration<SsaDataflowInput>

javascript/ql/test/library-tests/TripleDot/useuse.js

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -111,3 +111,34 @@ function t7() {
111111
const c = new Sub(source('t7.1'));
112112
sink(c.field); // $ hasTaintFlow=t7.1
113113
}
114+
115+
function t8() {
116+
function foo(x) {
117+
const obj = {};
118+
obj.field = x;
119+
120+
sink(obj.field); // $ hasTaintFlow=t8.1
121+
122+
if (obj) {
123+
sink(obj.field); // $ hasTaintFlow=t8.1
124+
} else {
125+
sink(obj.field);
126+
}
127+
128+
if (!obj) {
129+
sink(obj.field);
130+
} else {
131+
sink(obj.field); // $ hasTaintFlow=t8.1
132+
}
133+
134+
if (!obj || !obj) {
135+
sink(obj.field);
136+
} else {
137+
sink(obj.field); // $ hasTaintFlow=t8.1
138+
}
139+
}
140+
141+
// The guards used above are specific to taint-tracking, to ensure only taint flows in
142+
const taint = source('t8.1') + ' taint';
143+
foo(taint);
144+
}

0 commit comments

Comments
 (0)