Skip to content

Commit 6246eb2

Browse files
committed
JS: Refactor LabeledSantizerGuard
1 parent fe920ec commit 6246eb2

File tree

5 files changed

+169
-18
lines changed

5 files changed

+169
-18
lines changed

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

Lines changed: 44 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -147,9 +147,8 @@ abstract class Configuration extends string {
147147
*/
148148
predicate isBarrier(DataFlow::Node node) {
149149
exists(BarrierGuardNode guard |
150-
not guard instanceof LabeledBarrierGuardNode and
151150
isBarrierGuard(guard) and
152-
guard.blocks(node)
151+
guard.internalBlocks(node, "")
153152
)
154153
}
155154

@@ -167,10 +166,9 @@ abstract class Configuration extends string {
167166
* Holds if flow with label `lbl` cannot flow into `node`.
168167
*/
169168
predicate isLabeledBarrier(DataFlow::Node node, FlowLabel lbl) {
170-
exists(LabeledBarrierGuardNode guard |
171-
lbl = guard.getALabel() and
169+
exists(BarrierGuardNode guard |
172170
isBarrierGuard(guard) and
173-
guard.blocks(node)
171+
guard.internalBlocks(node, lbl)
174172
)
175173
or
176174
none() // relax type inference to account for overriding
@@ -254,43 +252,74 @@ module FlowLabel {
254252
*/
255253
abstract class BarrierGuardNode extends DataFlow::Node {
256254
/**
257-
* Holds if data flow node `nd` acts as a barrier for data flow.
255+
* Holds if data flow node `nd` acts as a barrier for data flow, possibly due to aliasing
256+
* through an access path.
257+
*
258+
* `label` is bound to the blocked label, or the empty string if all labels should be blocked.
258259
*
259260
* INTERNAL: this predicate should only be used from within `blocks(boolean, Expr)`.
260261
*/
261-
predicate blocks(DataFlow::Node nd) {
262+
predicate internalBlocks(DataFlow::Node nd, string label) {
262263
// 1) `nd` is a use of a refinement node that blocks its input variable
263-
exists(SsaRefinementNode ref |
264+
exists(SsaRefinementNode ref, boolean outcome |
264265
nd = DataFlow::ssaDefinitionNode(ref) and
265266
forex(SsaVariable input | input = ref.getAnInput() |
266267
asExpr() = ref.getGuard().getTest() and
267-
blocks(ref.getGuard().(ConditionGuardNode).getOutcome(), input.getAUse())
268+
outcome = ref.getGuard().(ConditionGuardNode).getOutcome() and
269+
internalBlocksExpr(outcome, input.getAUse(), label)
268270
)
269271
)
270272
or
271273
// 2) `nd` is an instance of an access path `p`, and dominated by a barrier for `p`
272-
exists(AccessPath p, BasicBlock bb, ConditionGuardNode cond |
274+
exists(AccessPath p, BasicBlock bb, ConditionGuardNode cond, boolean outcome |
273275
nd = DataFlow::valueNode(p.getAnInstanceIn(bb)) and
274276
asExpr() = cond.getTest() and
275-
blocks(cond.getOutcome(), p.getAnInstance()) and
277+
outcome = cond.getOutcome() and
278+
internalBlocksAccessPath(outcome, p, label) and
276279
cond.dominates(bb)
277280
)
278281
}
279282

283+
/**
284+
* Holds if data flow node `nd` acts as a barrier for data flow.
285+
*
286+
* `label` is bound to the blocked label, or the empty string if all labels should be blocked.
287+
*/
288+
private predicate internalBlocksExpr(boolean outcome, Expr test, string label) {
289+
blocks(outcome, test) and label = ""
290+
or
291+
blocks(outcome, test, label)
292+
}
293+
294+
/**
295+
* Holds if data flow node `nd` acts as a barrier for data flow due to aliasing through
296+
* an access path.
297+
*
298+
* `label` is bound to the blocked label, or the empty string if all labels should be blocked.
299+
*/
300+
pragma[noinline]
301+
private predicate internalBlocksAccessPath(boolean outcome, AccessPath ap, string label) {
302+
internalBlocksExpr(outcome, ap.getAnInstance(), label)
303+
}
304+
280305
/**
281306
* Holds if this node blocks expression `e` provided it evaluates to `outcome`.
307+
*
308+
* This will block all flow labels.
282309
*/
283310
abstract predicate blocks(boolean outcome, Expr e);
311+
312+
/**
313+
* Holds if this node blocks expression `e` from flow of type `label`, provided it evaluates to `outcome`.
314+
*/
315+
predicate blocks(boolean outcome, Expr e, FlowLabel label) { none() }
284316
}
285317

286318
/**
287319
* A guard node that only blocks specific labels.
288320
*/
289321
abstract class LabeledBarrierGuardNode extends BarrierGuardNode {
290-
/**
291-
* Get a flow label blocked by this guard node.
292-
*/
293-
abstract FlowLabel getALabel();
322+
override predicate blocks(boolean outcome, Expr e) { none() }
294323
}
295324

296325
/**

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

Lines changed: 18 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -129,20 +129,35 @@ module TaintTracking {
129129
* A node that can act as a sanitizer when appearing in a condition.
130130
*/
131131
abstract class SanitizerGuardNode extends DataFlow::BarrierGuardNode {
132-
final override predicate blocks(boolean outcome, Expr e) { sanitizes(outcome, e) }
132+
override predicate blocks(boolean outcome, Expr e) { sanitizes(outcome, e) }
133133

134134
/**
135135
* Holds if this node sanitizes expression `e`, provided it evaluates
136136
* to `outcome`.
137137
*/
138138
abstract predicate sanitizes(boolean outcome, Expr e);
139+
140+
override predicate blocks(boolean outcome, Expr e, DataFlow::FlowLabel label) {
141+
sanitizes(outcome, e, label)
142+
}
143+
144+
/**
145+
* Holds if this node sanitizes expression `e`, provided it evaluates
146+
* to `outcome`.
147+
*/
148+
predicate sanitizes(boolean outcome, Expr e, DataFlow::FlowLabel label) { none() }
139149
}
140150

141151
/**
142152
* A sanitizer guard node that only blocks specific flow labels.
143153
*/
144-
abstract class LabeledSanitizerGuardNode extends SanitizerGuardNode,
145-
DataFlow::LabeledBarrierGuardNode { }
154+
abstract class LabeledSanitizerGuardNode extends SanitizerGuardNode, DataFlow::BarrierGuardNode {
155+
final override predicate blocks(boolean outcome, Expr e, DataFlow::FlowLabel label) {
156+
sanitizes(outcome, e, label)
157+
}
158+
159+
override predicate sanitizes(boolean outcome, Expr e) { none() }
160+
}
146161

147162
/**
148163
* A taint-propagating data flow edge that should be added to all taint tracking
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
| tst.js:2:11:2:18 | source() | tst.js:8:12:8:12 | x |
2+
| tst.js:2:11:2:18 | source() | tst.js:12:12:12:12 | x |
3+
| tst.js:2:11:2:18 | source() | tst.js:14:12:14:12 | x |
4+
| tst.js:2:11:2:18 | source() | tst.js:18:12:18:12 | x |
5+
| tst.js:2:11:2:18 | source() | tst.js:20:12:20:12 | x |
6+
| tst.js:2:11:2:18 | source() | tst.js:26:12:26:12 | x |
Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
import javascript
2+
3+
class CustomFlowLabel extends DataFlow::FlowLabel {
4+
CustomFlowLabel() {
5+
this = "A" or this = "B"
6+
}
7+
}
8+
9+
class Config extends TaintTracking::Configuration {
10+
Config() { this = "Config" }
11+
12+
override predicate isSource(DataFlow::Node node, DataFlow::FlowLabel lbl) {
13+
node.(DataFlow::CallNode).getCalleeName() = "source" and
14+
lbl instanceof CustomFlowLabel
15+
}
16+
17+
override predicate isSink(DataFlow::Node node, DataFlow::FlowLabel lbl) {
18+
exists(DataFlow::CallNode call |
19+
call.getCalleeName() = "sink" and
20+
node = call.getAnArgument() and
21+
lbl instanceof CustomFlowLabel
22+
)
23+
}
24+
25+
override predicate isSanitizerGuard(TaintTracking::SanitizerGuardNode node) {
26+
node instanceof IsTypeAGuard or
27+
node instanceof IsSanitizedGuard
28+
}
29+
}
30+
31+
/**
32+
* A condition that checks what kind of value the input is. Not enough to
33+
* sanitize the value, but later sanitizers only need to handle the relevant case.
34+
*/
35+
class IsTypeAGuard extends TaintTracking::LabeledSanitizerGuardNode, DataFlow::CallNode {
36+
IsTypeAGuard() {
37+
getCalleeName() = "isTypeA"
38+
}
39+
40+
override predicate sanitizes(boolean outcome, Expr e, DataFlow::FlowLabel lbl) {
41+
e = getArgument(0).asExpr() and
42+
(
43+
outcome = true and lbl = "B"
44+
or
45+
outcome = false and lbl = "A"
46+
)
47+
}
48+
}
49+
50+
class IsSanitizedGuard extends TaintTracking::LabeledSanitizerGuardNode, DataFlow::CallNode {
51+
IsSanitizedGuard() {
52+
getCalleeName() = "sanitizeA" or getCalleeName() = "sanitizeB"
53+
}
54+
55+
override predicate sanitizes(boolean outcome, Expr e, DataFlow::FlowLabel lbl) {
56+
e = getArgument(0).asExpr() and
57+
outcome = true and
58+
(
59+
getCalleeName() = "sanitizeA" and lbl = "A"
60+
or
61+
getCalleeName() = "sanitizeB" and lbl = "B"
62+
)
63+
}
64+
}
65+
66+
from Config cfg, DataFlow::Node source, DataFlow::Node sink
67+
where cfg.hasFlow(source, sink)
68+
select source, sink
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
function test() {
2+
let x = source();
3+
4+
if (isTypeA(x)) {
5+
if (sanitizeA(x)) {
6+
sink(x); // OK
7+
} else {
8+
sink(x); // NOT OK
9+
}
10+
11+
if (sanitizeB(x)) {
12+
sink(x); // NOT OK
13+
} else {
14+
sink(x); // NOT OK
15+
}
16+
} else {
17+
if (sanitizeA(x)) {
18+
sink(x); // NOT OK
19+
} else {
20+
sink(x); // NOT OK
21+
}
22+
23+
if (sanitizeB(x)) {
24+
sink(x); // OK
25+
} else {
26+
sink(x); // NOT OK
27+
}
28+
}
29+
30+
if (sanitizeA(x) && sanitizeB(x)) {
31+
sink(x); // OK
32+
}
33+
}

0 commit comments

Comments
 (0)