Skip to content

Commit 3f70d91

Browse files
authored
Merge pull request #1288 from xiemaisi/js/fix-end-node-labels
Approved by asger-semmle
2 parents 2ede941 + 7faa4fd commit 3f70d91

File tree

7 files changed

+82
-12
lines changed

7 files changed

+82
-12
lines changed

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

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -557,7 +557,8 @@ private predicate callInputStep(
557557
/**
558558
* Holds if `input`, which is either an argument to `f` at `invk` or a definition
559559
* that is captured by `f`, may flow to `nd` under configuration `cfg` (possibly through
560-
* callees) along a path summarized by `summary`.
560+
* callees, but not containing any unmatched calls or returns) along a path summarized by
561+
* `summary`.
561562
*
562563
* Note that the summary does not take the initial step from argument to parameter
563564
* into account.
@@ -577,7 +578,7 @@ private predicate reachableFromInput(
577578
}
578579

579580
/**
580-
* Holds if there is a step from `pred` to `succ` under `cfg` that can be appended
581+
* Holds if there is a level step from `pred` to `succ` under `cfg` that can be appended
581582
* to a path represented by `oldSummary` yielding a path represented by `newSummary`.
582583
*/
583584
pragma[noinline]
@@ -587,6 +588,7 @@ private predicate appendStep(
587588
) {
588589
exists(PathSummary stepSummary |
589590
flowStep(pred, cfg, succ, stepSummary) and
591+
stepSummary.isLevel() and
590592
newSummary = oldSummary.append(stepSummary)
591593
)
592594
}
@@ -819,7 +821,7 @@ private predicate reachableFromSource(
819821
isSource(nd, cfg, lbl) and
820822
not cfg.isBarrier(nd) and
821823
not cfg.isLabeledBarrier(nd, lbl) and
822-
summary = MkPathSummary(false, false, lbl, lbl)
824+
summary = PathSummary::level(lbl)
823825
)
824826
or
825827
exists(DataFlow::Node pred, PathSummary oldSummary, PathSummary newSummary |
@@ -952,14 +954,19 @@ class PathNode extends TPathNode {
952954
* A path node corresponding to a flow source.
953955
*/
954956
class SourcePathNode extends PathNode {
955-
SourcePathNode() { isSource(nd, cfg, _) }
957+
SourcePathNode() {
958+
exists(FlowLabel lbl |
959+
summary = PathSummary::level(lbl) and
960+
isSource(nd, cfg, lbl)
961+
)
962+
}
956963
}
957964

958965
/**
959966
* A path node corresponding to a flow sink.
960967
*/
961968
class SinkPathNode extends PathNode {
962-
SinkPathNode() { isSink(nd, cfg, _) }
969+
SinkPathNode() { isSink(nd, cfg, summary.getEndLabel()) }
963970
}
964971

965972
/**

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

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -131,8 +131,8 @@ private module NodeTracking {
131131

132132
/**
133133
* Holds if `input`, which is either an argument to `f` at `invk` or a definition
134-
* that is captured by `f`, may flow to `nd` (possibly through callees) along
135-
* a path summarized by `summary`.
134+
* that is captured by `f`, may flow to `nd` (possibly through callees, but not containing
135+
* any unmatched calls or returns) along a path summarized by `summary`.
136136
*/
137137
private predicate reachableFromInput(
138138
Function f, DataFlow::Node invk, DataFlow::Node input, DataFlow::Node nd, PathSummary summary
@@ -143,6 +143,7 @@ private module NodeTracking {
143143
exists(DataFlow::Node mid, PathSummary oldSummary, PathSummary newSummary |
144144
reachableFromInput(f, invk, input, mid, oldSummary) and
145145
flowStep(mid, nd, newSummary) and
146+
newSummary.isLevel() and
146147
summary = oldSummary.append(newSummary)
147148
)
148149
}

javascript/ql/src/semmle/javascript/dataflow/internal/FlowSteps.qll

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -340,12 +340,17 @@ class PathSummary extends TPathSummary {
340340

341341
PathSummary() { this = MkPathSummary(hasReturn, hasCall, start, end) }
342342

343-
/** Indicates whether the path represented by this summary contains any return steps. */
343+
/** Indicates whether the path represented by this summary contains any unmatched return steps. */
344344
boolean hasReturn() { result = hasReturn }
345345

346-
/** Indicates whether the path represented by this summary contains any call steps. */
346+
/** Indicates whether the path represented by this summary contains any unmatched call steps. */
347347
boolean hasCall() { result = hasCall }
348348

349+
/** Holds if the path represented by this summary contains no unmatched call or return steps. */
350+
predicate isLevel() {
351+
hasReturn = false and hasCall = false
352+
}
353+
349354
/** Gets the flow label describing the value at the start of this flow path. */
350355
FlowLabel getStartLabel() { result = start }
351356

@@ -406,7 +411,13 @@ module PathSummary {
406411
/**
407412
* Gets a summary describing a path without any calls or returns.
408413
*/
409-
PathSummary level() { exists(FlowLabel lbl | result = MkPathSummary(false, false, lbl, lbl)) }
414+
PathSummary level() { result = level(_) }
415+
416+
/**
417+
* Gets a summary describing a path without any calls or returns, transforming `lbl` into
418+
* itself.
419+
*/
420+
PathSummary level(FlowLabel lbl) { result = MkPathSummary(false, false, lbl, lbl) }
410421

411422
/**
412423
* Gets a summary describing a path with one or more calls, but no returns.
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
| tst5.mjs:13:12:13:20 | source(0) | tst5.mjs:18:6:18:9 | flow |
2+
| tst5.mjs:15:8:15:19 | source(flow) | tst5.mjs:16:13:16:16 | flow |
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
import javascript
2+
3+
class Parity extends DataFlow::FlowLabel {
4+
Parity() { this = "even" or this = "odd" }
5+
6+
Parity flip() { result != this }
7+
}
8+
9+
class Config extends DataFlow::Configuration {
10+
Config() { this = "config" }
11+
12+
override predicate isSource(DataFlow::Node nd, DataFlow::FlowLabel lbl) {
13+
nd.(DataFlow::CallNode).getCalleeName() = "source" and
14+
lbl = "even"
15+
}
16+
17+
override predicate isSink(DataFlow::Node nd, DataFlow::FlowLabel lbl) {
18+
nd = any(DataFlow::CallNode c | c.getCalleeName() = "sink").getAnArgument() and
19+
lbl = "even"
20+
}
21+
22+
override predicate isAdditionalFlowStep(DataFlow::Node pred, DataFlow::Node succ, DataFlow::FlowLabel predLabel, DataFlow::FlowLabel succLabel) {
23+
exists(DataFlow::CallNode c | c = succ |
24+
c.getCalleeName() = "inc" and
25+
pred = c.getAnArgument() and
26+
succLabel = predLabel.(Parity).flip()
27+
)
28+
}
29+
}
30+
31+
from Config cfg, DataFlow::PathNode source, DataFlow::PathNode sink
32+
where cfg.hasFlowPath(source, sink)
33+
select source, sink
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
function source(x) {
2+
return x;
3+
}
4+
5+
function sink(x) {
6+
return x;
7+
}
8+
9+
function inc(x) {
10+
return x+1;
11+
}
12+
13+
var flow = source(0); // source
14+
flow = inc(flow);
15+
flow = source(flow); // source
16+
flow = sink(flow); // sink for line 15, but not for line 13 (wrong parity)
17+
flow = inc(flow);
18+
sink(flow); // sink for line 13, but not for line 15 (wrong parity)

javascript/ql/test/query-tests/Security/CWE-601/ClientSideUrlRedirect/ClientSideUrlRedirect.expected

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -78,14 +78,12 @@ edges
7878
| tst.js:6:34:6:55 | documen ... on.href | tst.js:6:20:6:56 | indirec ... n.href) |
7979
#select
8080
| tst2.js:4:21:4:55 | href.su ... '?')+1) | tst2.js:2:14:2:28 | window.location | tst2.js:4:21:4:55 | href.su ... '?')+1) | Untrusted URL redirection due to $@. | tst2.js:2:14:2:28 | window.location | user-provided value |
81-
| tst2.js:4:21:4:55 | href.su ... '?')+1) | tst2.js:2:14:2:28 | window.location | tst2.js:4:21:4:55 | href.su ... '?')+1) | Untrusted URL redirection due to $@. | tst2.js:2:14:2:28 | window.location | user-provided value |
8281
| tst6.js:4:21:4:28 | redirect | tst6.js:2:18:2:45 | $locati ... irect') | tst6.js:4:21:4:28 | redirect | Untrusted URL redirection due to $@. | tst6.js:2:18:2:45 | $locati ... irect') | user-provided value |
8382
| tst6.js:6:17:6:24 | redirect | tst6.js:2:18:2:45 | $locati ... irect') | tst6.js:6:17:6:24 | redirect | Untrusted URL redirection due to $@. | tst6.js:2:18:2:45 | $locati ... irect') | user-provided value |
8483
| tst6.js:8:21:8:56 | $locati ... + "foo" | tst6.js:8:21:8:48 | $locati ... irect') | tst6.js:8:21:8:56 | $locati ... + "foo" | Untrusted URL redirection due to $@. | tst6.js:8:21:8:48 | $locati ... irect') | user-provided value |
8584
| tst7.js:2:12:2:35 | documen ... .search | tst7.js:2:12:2:28 | document.location | tst7.js:2:12:2:35 | documen ... .search | Untrusted URL redirection due to $@. | tst7.js:2:12:2:28 | document.location | user-provided value |
8685
| tst7.js:5:27:5:50 | documen ... .search | tst7.js:5:27:5:43 | document.location | tst7.js:5:27:5:50 | documen ... .search | Untrusted URL redirection due to $@. | tst7.js:5:27:5:43 | document.location | user-provided value |
8786
| tst9.js:2:21:2:55 | documen ... ring(1) | tst9.js:2:21:2:37 | document.location | tst9.js:2:21:2:55 | documen ... ring(1) | Untrusted URL redirection due to $@. | tst9.js:2:21:2:37 | document.location | user-provided value |
88-
| tst9.js:2:21:2:55 | documen ... ring(1) | tst9.js:2:21:2:37 | document.location | tst9.js:2:21:2:55 | documen ... ring(1) | Untrusted URL redirection due to $@. | tst9.js:2:21:2:37 | document.location | user-provided value |
8987
| tst10.js:5:17:5:46 | '/' + d ... .search | tst10.js:5:23:5:39 | document.location | tst10.js:5:17:5:46 | '/' + d ... .search | Untrusted URL redirection due to $@. | tst10.js:5:23:5:39 | document.location | user-provided value |
9088
| tst10.js:8:17:8:47 | '//' + ... .search | tst10.js:8:24:8:40 | document.location | tst10.js:8:17:8:47 | '//' + ... .search | Untrusted URL redirection due to $@. | tst10.js:8:24:8:40 | document.location | user-provided value |
9189
| tst10.js:11:17:11:50 | '//foo' ... .search | tst10.js:11:27:11:43 | document.location | tst10.js:11:17:11:50 | '//foo' ... .search | Untrusted URL redirection due to $@. | tst10.js:11:27:11:43 | document.location | user-provided value |

0 commit comments

Comments
 (0)