Skip to content

Commit d615842

Browse files
committed
JS: Generalize SanitizerFunction to data flow configs and flow labels
1 parent e2b0ec5 commit d615842

2 files changed

Lines changed: 145 additions & 113 deletions

File tree

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

Lines changed: 143 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -147,7 +147,7 @@ abstract class Configuration extends string {
147147
*/
148148
predicate isBarrier(DataFlow::Node node) {
149149
exists(BarrierGuardNode guard |
150-
isBarrierGuard(guard) and
150+
isBarrierGuardInternal(guard) and
151151
guard.internalBlocks(node, "")
152152
)
153153
}
@@ -181,7 +181,7 @@ abstract class Configuration extends string {
181181
*/
182182
predicate isLabeledBarrier(DataFlow::Node node, FlowLabel lbl) {
183183
exists(BarrierGuardNode guard |
184-
isBarrierGuard(guard) and
184+
isBarrierGuardInternal(guard) and
185185
guard.internalBlocks(node, lbl)
186186
)
187187
or
@@ -198,6 +198,12 @@ abstract class Configuration extends string {
198198
*/
199199
predicate isBarrierGuard(BarrierGuardNode guard) { none() }
200200

201+
private predicate isBarrierGuardInternal(BarrierGuardNode guard) {
202+
isBarrierGuard(guard)
203+
or
204+
guard.(AdditionalBarrierGuardNode).appliesTo(this)
205+
}
206+
201207
/**
202208
* Holds if data may flow from `source` to `sink` for this configuration.
203209
*/
@@ -304,7 +310,7 @@ abstract class BarrierGuardNode extends DataFlow::Node {
304310
forex(SsaVariable input | input = ref.getAnInput() |
305311
asExpr() = ref.getGuard().getTest() and
306312
outcome = ref.getGuard().(ConditionGuardNode).getOutcome() and
307-
internalBlocksExpr(outcome, input.getAUse(), label)
313+
barrierGuardBlocksExpr(this, outcome, input.getAUse(), label)
308314
)
309315
)
310316
or
@@ -313,33 +319,11 @@ abstract class BarrierGuardNode extends DataFlow::Node {
313319
nd = DataFlow::valueNode(p.getAnInstanceIn(bb)) and
314320
asExpr() = cond.getTest() and
315321
outcome = cond.getOutcome() and
316-
internalBlocksAccessPath(outcome, p, label) and
322+
barrierGuardBlocksAccessPath(this, outcome, p, label) and
317323
cond.dominates(bb)
318324
)
319325
}
320326

321-
/**
322-
* Holds if data flow node `nd` acts as a barrier for data flow.
323-
*
324-
* `label` is bound to the blocked label, or the empty string if all labels should be blocked.
325-
*/
326-
private predicate internalBlocksExpr(boolean outcome, Expr test, string label) {
327-
blocks(outcome, test) and label = ""
328-
or
329-
blocks(outcome, test, label)
330-
}
331-
332-
/**
333-
* Holds if data flow node `nd` acts as a barrier for data flow due to aliasing through
334-
* an access path.
335-
*
336-
* `label` is bound to the blocked label, or the empty string if all labels should be blocked.
337-
*/
338-
pragma[noinline]
339-
private predicate internalBlocksAccessPath(boolean outcome, AccessPath ap, string label) {
340-
internalBlocksExpr(outcome, ap.getAnInstance(), label)
341-
}
342-
343327
/**
344328
* Holds if this node blocks expression `e` provided it evaluates to `outcome`.
345329
*
@@ -353,6 +337,32 @@ abstract class BarrierGuardNode extends DataFlow::Node {
353337
predicate blocks(boolean outcome, Expr e, FlowLabel label) { none() }
354338
}
355339

340+
/**
341+
* Holds if data flow node `nd` acts as a barrier for data flow.
342+
*
343+
* `label` is bound to the blocked label, or the empty string if all labels should be blocked.
344+
*/
345+
private predicate barrierGuardBlocksExpr(BarrierGuardNode guard, boolean outcome, Expr test, string label) {
346+
guard.blocks(outcome, test) and label = ""
347+
or
348+
guard.blocks(outcome, test, label)
349+
or
350+
// Handle labelled barrier guard functions specially, to avoid negative recursion
351+
// through the non-abstract 3-argument version of blocks().
352+
guard.(AdditionalBarrierGuardCall).internalBlocksLabel(outcome, test, label)
353+
}
354+
355+
/**
356+
* Holds if data flow node `nd` acts as a barrier for data flow due to aliasing through
357+
* an access path.
358+
*
359+
* `label` is bound to the blocked label, or the empty string if all labels should be blocked.
360+
*/
361+
pragma[noinline]
362+
private predicate barrierGuardBlocksAccessPath(BarrierGuardNode guard, boolean outcome, AccessPath ap, string label) {
363+
barrierGuardBlocksExpr(guard, outcome, ap.getAnInstance(), label)
364+
}
365+
356366
/**
357367
* A guard node that only blocks specific labels.
358368
*/
@@ -1186,3 +1196,110 @@ module PathGraph {
11861196
not pred = finalMidNode(succ)
11871197
}
11881198
}
1199+
1200+
1201+
1202+
/**
1203+
* Gets an operand of the given `&&` operator.
1204+
*
1205+
* We use this to construct the transitive closure over a relation
1206+
* that does not include all of `BinaryExpr.getAnOperand`.
1207+
*/
1208+
private Expr getALogicalAndOperand(LogAndExpr e) {
1209+
result = e.getAnOperand()
1210+
}
1211+
1212+
/**
1213+
* Gets an operand of the given `||` operator.
1214+
*
1215+
* We use this to construct the transitive closure over a relation
1216+
* that does not include all of `BinaryExpr.getAnOperand`.
1217+
*/
1218+
private Expr getALogicalOrOperand(LogOrExpr e) {
1219+
result = e.getAnOperand()
1220+
}
1221+
1222+
/**
1223+
* A `BarrierGuardNode` that controls which data flow
1224+
* configurations it is used in.
1225+
*
1226+
* Note: For performance reasons, all subclasses of this class should be part
1227+
* of the standard library. Override `Configuration::isBarrierGuard`
1228+
* for analysis-specific barrier guards.
1229+
*/
1230+
abstract class AdditionalBarrierGuardNode extends BarrierGuardNode {
1231+
abstract predicate appliesTo(Configuration cfg);
1232+
}
1233+
1234+
/**
1235+
* A function that returns the result of a barrier guard.
1236+
*/
1237+
private class BarrierGuardFunction extends Function {
1238+
DataFlow::ParameterNode sanitizedParameter;
1239+
BarrierGuardNode guard;
1240+
boolean guardOutcome;
1241+
string label;
1242+
1243+
BarrierGuardFunction() {
1244+
exists(Expr e |
1245+
exists(Expr returnExpr |
1246+
returnExpr = guard.asExpr()
1247+
or
1248+
// ad hoc support for conjunctions:
1249+
getALogicalAndOperand+(returnExpr) = guard.asExpr() and guardOutcome = true
1250+
or
1251+
// ad hoc support for disjunctions:
1252+
getALogicalOrOperand+(returnExpr) = guard.asExpr() and guardOutcome = false
1253+
|
1254+
exists(SsaExplicitDefinition ssa |
1255+
ssa.getDef().getSource() = returnExpr and
1256+
ssa.getVariable().getAUse() = getAReturnedExpr()
1257+
)
1258+
or
1259+
returnExpr = getAReturnedExpr()
1260+
) and
1261+
sanitizedParameter.flowsToExpr(e) and
1262+
barrierGuardBlocksExpr(guard, guardOutcome, e, label)
1263+
) and
1264+
getNumParameter() = 1 and
1265+
sanitizedParameter.getParameter() = getParameter(0)
1266+
}
1267+
1268+
/**
1269+
* Holds if this function sanitizes argument `e` of call `call`, provided the call evaluates to `outcome`.
1270+
*/
1271+
predicate isBarrierCall(DataFlow::CallNode call, Expr e, boolean outcome, string lbl) {
1272+
exists(DataFlow::Node arg |
1273+
arg.asExpr() = e and
1274+
arg = call.getArgument(0) and
1275+
call.getNumArgument() = 1 and
1276+
argumentPassing(call, arg, this, sanitizedParameter) and
1277+
outcome = guardOutcome and
1278+
lbl = label
1279+
)
1280+
}
1281+
1282+
/**
1283+
* Holds if this function applies to the flow in `cfg`.
1284+
*/
1285+
predicate appliesTo(Configuration cfg) { cfg.isBarrierGuard(guard) }
1286+
}
1287+
1288+
/**
1289+
* A call that sanitizes an argument.
1290+
*/
1291+
private class AdditionalBarrierGuardCall extends AdditionalBarrierGuardNode, DataFlow::CallNode {
1292+
BarrierGuardFunction f;
1293+
1294+
AdditionalBarrierGuardCall() { f.isBarrierCall(this, _, _, _) }
1295+
1296+
override predicate blocks(boolean outcome, Expr e) {
1297+
f.isBarrierCall(this, e, outcome, "")
1298+
}
1299+
1300+
predicate internalBlocksLabel(boolean outcome, Expr e, DataFlow::FlowLabel label) {
1301+
f.isBarrierCall(this, e, outcome, label)
1302+
}
1303+
1304+
override predicate appliesTo(Configuration cfg) { f.appliesTo(cfg) }
1305+
}

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

Lines changed: 2 additions & 87 deletions
Original file line numberDiff line numberDiff line change
@@ -133,8 +133,8 @@ module TaintTracking {
133133
* configurations it is used in.
134134
*
135135
* Note: For performance reasons, all subclasses of this class should be part
136-
* of the standard library. Override `Configuration::isSanitizer`
137-
* for analysis-specific taint steps.
136+
* of the standard library. Override `Configuration::isSanitizerGuard`
137+
* for analysis-specific taint sanitizer guards.
138138
*/
139139
abstract class AdditionalSanitizerGuardNode extends SanitizerGuardNode {
140140
/**
@@ -846,91 +846,6 @@ module TaintTracking {
846846
override predicate appliesTo(Configuration cfg) { any() }
847847
}
848848

849-
/**
850-
* Gets an operand of the given `&&` operator.
851-
*
852-
* We use this to construct the transitive closure over a relation
853-
* that does not include all of `BinaryExpr.getAnOperand`.
854-
*/
855-
private Expr getALogicalAndOperand(LogAndExpr e) {
856-
result = e.getAnOperand()
857-
}
858-
859-
/**
860-
* Gets an operand of the given `||` operator.
861-
*
862-
* We use this to construct the transitive closure over a relation
863-
* that does not include all of `BinaryExpr.getAnOperand`.
864-
*/
865-
private Expr getALogicalOrOperand(LogOrExpr e) {
866-
result = e.getAnOperand()
867-
}
868-
869-
/**
870-
* A function that returns the result of a sanitizer check.
871-
*/
872-
private class SanitizingFunction extends Function {
873-
DataFlow::ParameterNode sanitizedParameter;
874-
SanitizerGuardNode sanitizer;
875-
boolean sanitizerOutcome;
876-
877-
SanitizingFunction() {
878-
exists(Expr e |
879-
exists(Expr returnExpr |
880-
returnExpr = sanitizer.asExpr()
881-
or
882-
// ad hoc support for conjunctions:
883-
getALogicalAndOperand+(returnExpr) = sanitizer.asExpr() and sanitizerOutcome = true
884-
or
885-
// ad hoc support for disjunctions:
886-
getALogicalOrOperand+(returnExpr) = sanitizer.asExpr() and sanitizerOutcome = false
887-
|
888-
exists(SsaExplicitDefinition ssa |
889-
ssa.getDef().getSource() = returnExpr and
890-
ssa.getVariable().getAUse() = getAReturnedExpr()
891-
)
892-
or
893-
returnExpr = getAReturnedExpr()
894-
) and
895-
sanitizedParameter.flowsToExpr(e) and
896-
sanitizer.sanitizes(sanitizerOutcome, e)
897-
) and
898-
getNumParameter() = 1 and
899-
sanitizedParameter.getParameter() = getParameter(0)
900-
}
901-
902-
/**
903-
* Holds if this function sanitizes argument `e` of call `call`, provided the call evaluates to `outcome`.
904-
*/
905-
predicate isSanitizingCall(DataFlow::CallNode call, Expr e, boolean outcome) {
906-
exists(DataFlow::Node arg |
907-
arg.asExpr() = e and
908-
arg = call.getArgument(0) and
909-
call.getNumArgument() = 1 and
910-
FlowSteps::argumentPassing(call, arg, this, sanitizedParameter) and
911-
outcome = sanitizerOutcome
912-
)
913-
}
914-
915-
/**
916-
* Holds if this function applies to the flow in `cfg`.
917-
*/
918-
predicate appliesTo(Configuration cfg) { cfg.isBarrierGuard(sanitizer) }
919-
}
920-
921-
/**
922-
* A call that sanitizes an argument.
923-
*/
924-
private class AdditionalSanitizingCall extends AdditionalSanitizerGuardNode, DataFlow::CallNode {
925-
SanitizingFunction f;
926-
927-
AdditionalSanitizingCall() { f.isSanitizingCall(this, _, _) }
928-
929-
override predicate sanitizes(boolean outcome, Expr e) { f.isSanitizingCall(this, e, outcome) }
930-
931-
override predicate appliesTo(Configuration cfg) { f.appliesTo(cfg) }
932-
}
933-
934849
/**
935850
* An equality test on `e.origin` or `e.source` where `e` is a `postMessage` event object,
936851
* considered as a sanitizer for `e`.

0 commit comments

Comments
 (0)