Skip to content

Commit 9160fbf

Browse files
authored
Merge pull request #2435 from asger-semmle/phi-edge-barrier-guards
JS: Phi edge barrier guards
2 parents f48e4bc + 79f8d02 commit 9160fbf

File tree

6 files changed

+89
-6
lines changed

6 files changed

+89
-6
lines changed

change-notes/1.24/analysis-javascript.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@
77
* Imports with the `.js` extension can now be resolved to a TypeScript file,
88
when the import refers to a file generated by TypeScript.
99

10+
- The analysis of sanitizer guards has improved, leading to fewer false-positive results from the security queries.
11+
1012
* Support for the following frameworks and libraries has been improved:
1113
- [react](https://www.npmjs.com/package/react)
1214
- [typeahead.js](https://www.npmjs.com/package/typeahead.js)

javascript/ql/src/semmle/javascript/SSA.qll

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -639,8 +639,16 @@ abstract class SsaPseudoDefinition extends SsaImplicitDefinition {
639639
* would be visible.
640640
*/
641641
class SsaPhiNode extends SsaPseudoDefinition, TPhi {
642+
/**
643+
* Gets the input to this phi node coming from the given predecessor block.
644+
*/
645+
SsaVariable getInputFromBlock(BasicBlock bb) {
646+
bb = getBasicBlock().getAPredecessor() and
647+
result = getDefReachingEndOf(bb, getSourceVariable())
648+
}
649+
642650
override SsaVariable getAnInput() {
643-
result = getDefReachingEndOf(getBasicBlock().getAPredecessor(), getSourceVariable())
651+
result = getInputFromBlock(_)
644652
}
645653

646654
override predicate definesAt(ReachableBasicBlock bb, int i, SsaSourceVariable v) {

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

Lines changed: 54 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -363,6 +363,51 @@ private predicate barrierGuardBlocksAccessPath(BarrierGuardNode guard, boolean o
363363
barrierGuardBlocksExpr(guard, outcome, ap.getAnInstance(), label)
364364
}
365365

366+
/**
367+
* Holds if `guard` should block flow along the edge `pred -> succ`.
368+
*
369+
* `label` is bound to the blocked label, or the empty string if all labels should be blocked.
370+
*/
371+
private predicate barrierGuardBlocksEdge(BarrierGuardNode guard, DataFlow::Node pred, DataFlow::Node succ, string label) {
372+
exists(SsaVariable input, SsaPhiNode phi, BasicBlock bb, ConditionGuardNode cond, boolean outcome |
373+
pred = DataFlow::ssaDefinitionNode(input) and
374+
succ = DataFlow::ssaDefinitionNode(phi) and
375+
input = phi.getInputFromBlock(bb) and
376+
guard.getEnclosingExpr() = cond.getTest() and
377+
outcome = cond.getOutcome() and
378+
barrierGuardBlocksExpr(guard, outcome, input.getAUse(), label) and
379+
cond.dominates(bb)
380+
)
381+
}
382+
383+
/**
384+
* Holds if there is a barrier edge `pred -> succ` in `cfg` either through an explicit barrier edge
385+
* or one implied by a barrier guard.
386+
*
387+
* Only holds for barriers that should apply to all flow labels.
388+
*/
389+
private predicate isBarrierEdge(Configuration cfg, DataFlow::Node pred, DataFlow::Node succ) {
390+
cfg.isBarrierEdge(pred, succ)
391+
or
392+
exists(DataFlow::BarrierGuardNode guard |
393+
cfg.isBarrierGuard(guard) and
394+
barrierGuardBlocksEdge(guard, pred, succ, "")
395+
)
396+
}
397+
398+
/**
399+
* Holds if there is a labeled barrier edge `pred -> succ` in `cfg` either through an explicit barrier edge
400+
* or one implied by a barrier guard.
401+
*/
402+
private predicate isLabeledBarrierEdge(Configuration cfg, DataFlow::Node pred, DataFlow::Node succ, DataFlow::FlowLabel label) {
403+
cfg.isBarrierEdge(pred, succ, label)
404+
or
405+
exists(DataFlow::BarrierGuardNode guard |
406+
cfg.isBarrierGuard(guard) and
407+
barrierGuardBlocksEdge(guard, pred, succ, label)
408+
)
409+
}
410+
366411
/**
367412
* A guard node that only blocks specific labels.
368413
*/
@@ -470,7 +515,8 @@ private predicate basicFlowStep(
470515
// Local flow
471516
exists(FlowLabel predlbl, FlowLabel succlbl |
472517
localFlowStep(pred, succ, cfg, predlbl, succlbl) and
473-
not cfg.isBarrierEdge(pred, succ, predlbl) and
518+
not isLabeledBarrierEdge(cfg, pred, succ, predlbl) and
519+
not isBarrierEdge(cfg, pred, succ) and
474520
summary = MkPathSummary(false, false, predlbl, succlbl)
475521
)
476522
or
@@ -584,7 +630,7 @@ private predicate callInputStep(
584630
)
585631
) and
586632
not cfg.isBarrier(succ) and
587-
not cfg.isBarrierEdge(pred, succ)
633+
not isBarrierEdge(cfg, pred, succ)
588634
}
589635

590636
/**
@@ -638,7 +684,8 @@ private predicate flowThroughCall(
638684
ret.asExpr() = f.getAReturnedExpr() and
639685
calls(output, f) and // Do not consider partial calls
640686
reachableFromInput(f, output, input, ret, cfg, summary) and
641-
not cfg.isBarrierEdge(ret, output) and
687+
not isBarrierEdge(cfg, ret, output) and
688+
not isLabeledBarrierEdge(cfg, ret, output, summary.getEndLabel()) and
642689
not cfg.isLabeledBarrier(output, summary.getEndLabel())
643690
)
644691
or
@@ -647,7 +694,8 @@ private predicate flowThroughCall(
647694
DataFlow::exceptionalInvocationReturnNode(output, invk.asExpr()) and
648695
calls(invk, f) and
649696
reachableFromInput(f, invk, input, ret, cfg, summary) and
650-
not cfg.isBarrierEdge(ret, output) and
697+
not isBarrierEdge(cfg, ret, output) and
698+
not isLabeledBarrierEdge(cfg, ret, output, summary.getEndLabel()) and
651699
not cfg.isLabeledBarrier(output, summary.getEndLabel())
652700
)
653701
}
@@ -886,7 +934,8 @@ private predicate flowStep(
886934
flowIntoHigherOrderCall(pred, succ, cfg, summary)
887935
) and
888936
not cfg.isBarrier(succ) and
889-
not cfg.isBarrierEdge(pred, succ) and
937+
not isBarrierEdge(cfg, pred, succ) and
938+
not isLabeledBarrierEdge(cfg, pred, succ, summary.getEndLabel()) and
890939
not cfg.isLabeledBarrier(succ, summary.getEndLabel())
891940
}
892941

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,7 @@ typeInferenceMismatch
7777
| sanitizer-guards.js:13:14:13:21 | source() | sanitizer-guards.js:26:9:26:14 | this.x |
7878
| sanitizer-guards.js:43:11:43:18 | source() | sanitizer-guards.js:45:8:45:8 | x |
7979
| sanitizer-guards.js:43:11:43:18 | source() | sanitizer-guards.js:48:10:48:10 | x |
80+
| sanitizer-guards.js:68:11:68:18 | source() | sanitizer-guards.js:75:8:75:8 | x |
8081
| spread.js:2:15:2:22 | source() | spread.js:4:8:4:19 | { ...taint } |
8182
| spread.js:2:15:2:22 | source() | spread.js:5:8:5:43 | { f: 'h ... orld' } |
8283
| spread.js:2:15:2:22 | source() | spread.js:7:8:7:19 | [ ...taint ] |

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,7 @@
5353
| sanitizer-guards.js:43:11:43:18 | source() | sanitizer-guards.js:45:8:45:8 | x |
5454
| sanitizer-guards.js:43:11:43:18 | source() | sanitizer-guards.js:48:10:48:10 | x |
5555
| sanitizer-guards.js:43:11:43:18 | source() | sanitizer-guards.js:52:10:52:10 | x |
56+
| sanitizer-guards.js:68:11:68:18 | source() | sanitizer-guards.js:75:8:75:8 | x |
5657
| thisAssignments.js:4:17:4:24 | source() | thisAssignments.js:5:10:5:18 | obj.field |
5758
| thisAssignments.js:7:19:7:26 | source() | thisAssignments.js:8:10:8:20 | this.field2 |
5859
| 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: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,3 +52,25 @@ function reflective() {
5252
sink(x); // OK
5353
}
5454
}
55+
56+
function phi() {
57+
let x = source();
58+
59+
if (something(x) && isSafe(x)) {
60+
// this input to the phi node for 'x' should be sanitized
61+
} else {
62+
x = null;
63+
}
64+
sink(x); // OK
65+
}
66+
67+
function phi2() {
68+
let x = source();
69+
70+
if (something(x) || isSafe(x)) {
71+
// this input to the phi node for 'x' is not fully sanitized
72+
} else {
73+
x = null;
74+
}
75+
sink(x); // NOT OK
76+
}

0 commit comments

Comments
 (0)