Skip to content

Commit 0802107

Browse files
committed
JS: Flow label -> flow state in TaintedPath
1 parent b8d652c commit 0802107

File tree

2 files changed

+174
-60
lines changed

2 files changed

+174
-60
lines changed

javascript/ql/lib/semmle/javascript/security/dataflow/TaintedPathCustomizations.qll

Lines changed: 162 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -11,16 +11,22 @@ module TaintedPath {
1111
* A data flow source for tainted-path vulnerabilities.
1212
*/
1313
abstract class Source extends DataFlow::Node {
14-
/** Gets a flow label denoting the type of value for which this is a source. */
15-
DataFlow::FlowLabel getAFlowLabel() { result instanceof Label::PosixPath }
14+
/** Gets a flow state denoting the type of value for which this is a source. */
15+
FlowState getAFlowState() { result instanceof FlowState::PosixPath }
16+
17+
/** DEPRECATED. Use `getAFlowState()` instead. */
18+
deprecated DataFlow::FlowLabel getAFlowLabel() { result = this.getAFlowState().toFlowLabel() }
1619
}
1720

1821
/**
1922
* A data flow sink for tainted-path vulnerabilities.
2023
*/
2124
abstract class Sink extends DataFlow::Node {
22-
/** Gets a flow label denoting the type of value for which this is a sink. */
23-
DataFlow::FlowLabel getAFlowLabel() { result instanceof Label::PosixPath }
25+
/** Gets a flow state denoting the type of value for which this is a sink. */
26+
FlowState getAFlowState() { result instanceof FlowState::PosixPath }
27+
28+
/** DEPRECATED. Use `getAFlowState()` instead. */
29+
deprecated DataFlow::FlowLabel getAFlowLabel() { result = this.getAFlowState().toFlowLabel() }
2430
}
2531

2632
/**
@@ -38,16 +44,16 @@ module TaintedPath {
3844
predicate blocksExpr(boolean outcome, Expr e) { none() }
3945

4046
/**
41-
* Holds if this node acts as a barrier for `label`, blocking further flow from `e` if `this` evaluates to `outcome`.
47+
* Holds if this node acts as a barrier for `state`, blocking further flow from `e` if `this` evaluates to `outcome`.
4248
*/
43-
predicate blocksExpr(boolean outcome, Expr e, DataFlow::FlowLabel label) { none() }
49+
predicate blocksExpr(boolean outcome, Expr e, FlowState state) { none() }
4450

4551
/** DEPRECATED. Use `blocksExpr` instead. */
4652
deprecated predicate sanitizes(boolean outcome, Expr e) { this.blocksExpr(outcome, e) }
4753

4854
/** DEPRECATED. Use `blocksExpr` instead. */
4955
deprecated predicate sanitizes(boolean outcome, Expr e, DataFlow::FlowLabel label) {
50-
this.blocksExpr(outcome, e, label)
56+
this.blocksExpr(outcome, e, Label::toFlowState(label))
5157
}
5258
}
5359

@@ -65,7 +71,23 @@ module TaintedPath {
6571

6672
deprecated class BarrierGuardNode = BarrierGuard;
6773

68-
module Label {
74+
private newtype TFlowState =
75+
TPosixPath(FlowState::Normalization normalization, FlowState::Relativeness relativeness) or
76+
TSplitPath()
77+
78+
private class FlowStateImpl extends TFlowState {
79+
/** Gets a string representation of this flow state. */
80+
abstract string toString();
81+
82+
/** DEPRECATED. Gets the corresponding flow label, for backwards compatibility. */
83+
abstract deprecated DataFlow::FlowLabel toFlowLabel();
84+
}
85+
86+
/** The flow state to associate with a tainted value. See also `FlowState::PosixPath`. */
87+
final class FlowState = FlowStateImpl;
88+
89+
/** Module containing details of individual flow states. */
90+
module FlowState {
6991
/**
7092
* A string indicating if a path is normalized, that is, whether internal `../` components
7193
* have been removed.
@@ -81,6 +103,90 @@ module TaintedPath {
81103
Relativeness() { this = "relative" or this = "absolute" }
82104
}
83105

106+
/**
107+
* A flow state representing a Posix path.
108+
*
109+
* There are currently four flow states, representing the different combinations of
110+
* normalization and absoluteness.
111+
*/
112+
class PosixPath extends FlowStateImpl, TPosixPath {
113+
Normalization normalization;
114+
Relativeness relativeness;
115+
116+
PosixPath() { this = TPosixPath(normalization, relativeness) }
117+
118+
/** Gets a string indicating whether this path is normalized. */
119+
Normalization getNormalization() { result = normalization }
120+
121+
/** Gets a string indicating whether this path is relative. */
122+
Relativeness getRelativeness() { result = relativeness }
123+
124+
/** Holds if this path is normalized. */
125+
predicate isNormalized() { normalization = "normalized" }
126+
127+
/** Holds if this path is not normalized. */
128+
predicate isNonNormalized() { normalization = "raw" }
129+
130+
/** Holds if this path is relative. */
131+
predicate isRelative() { relativeness = "relative" }
132+
133+
/** Holds if this path is relative. */
134+
predicate isAbsolute() { relativeness = "absolute" }
135+
136+
/** Gets the path label with normalized flag set to true. */
137+
PosixPath toNormalized() {
138+
result.isNormalized() and
139+
result.getRelativeness() = this.getRelativeness()
140+
}
141+
142+
/** Gets the path label with normalized flag set to true. */
143+
PosixPath toNonNormalized() {
144+
result.isNonNormalized() and
145+
result.getRelativeness() = this.getRelativeness()
146+
}
147+
148+
/** Gets the path label with absolute flag set to true. */
149+
PosixPath toAbsolute() {
150+
result.isAbsolute() and
151+
result.getNormalization() = this.getNormalization()
152+
}
153+
154+
/** Gets the path label with absolute flag set to true. */
155+
PosixPath toRelative() {
156+
result.isRelative() and
157+
result.getNormalization() = this.getNormalization()
158+
}
159+
160+
/** Holds if this path may contain `../` components. */
161+
predicate canContainDotDotSlash() {
162+
// Absolute normalized path is the only combination that cannot contain `../`.
163+
not (this.isNormalized() and this.isAbsolute())
164+
}
165+
166+
override string toString() { result = normalization + "-" + relativeness + "-posix-path" }
167+
168+
deprecated override Label::PosixPath toFlowLabel() {
169+
result.getNormalization() = normalization and result.getRelativeness() = relativeness
170+
}
171+
}
172+
173+
/**
174+
* A flow label representing an array of path elements that may include "..".
175+
*/
176+
class SplitPath extends FlowStateImpl, TSplitPath {
177+
override string toString() { result = "splitPath" }
178+
179+
deprecated override Label::SplitPath toFlowLabel() { any() }
180+
}
181+
}
182+
183+
deprecated module Label {
184+
FlowState toFlowState(DataFlow::FlowLabel label) { result.toFlowLabel() = label }
185+
186+
class Normalization = FlowState::Normalization;
187+
188+
class Relativeness = FlowState::Relativeness;
189+
84190
/**
85191
* A flow label representing a Posix path.
86192
*
@@ -380,14 +486,14 @@ module TaintedPath {
380486
class StartsWithDotDotSanitizer extends BarrierGuard instanceof StringOps::StartsWith {
381487
StartsWithDotDotSanitizer() { isDotDotSlashPrefix(super.getSubstring()) }
382488

383-
override predicate blocksExpr(boolean outcome, Expr e, DataFlow::FlowLabel label) {
489+
override predicate blocksExpr(boolean outcome, Expr e, FlowState state) {
384490
// Sanitize in the false case for:
385491
// .startsWith(".")
386492
// .startsWith("..")
387493
// .startsWith("../")
388494
outcome = super.getPolarity().booleanNot() and
389495
e = super.getBaseString().asExpr() and
390-
exists(Label::PosixPath posixPath | posixPath = label |
496+
exists(FlowState::PosixPath posixPath | posixPath = state |
391497
posixPath.isNormalized() and
392498
posixPath.isRelative()
393499
)
@@ -422,10 +528,10 @@ module TaintedPath {
422528
not startsWith.getSubstring().getStringValue() = "/"
423529
}
424530

425-
override predicate blocksExpr(boolean outcome, Expr e, DataFlow::FlowLabel label) {
531+
override predicate blocksExpr(boolean outcome, Expr e, FlowState state) {
426532
outcome = startsWith.getPolarity() and
427533
e = startsWith.getBaseString().asExpr() and
428-
exists(Label::PosixPath posixPath | posixPath = label |
534+
exists(FlowState::PosixPath posixPath | posixPath = state |
429535
posixPath.isAbsolute() and
430536
posixPath.isNormalized()
431537
)
@@ -457,9 +563,9 @@ module TaintedPath {
457563
) // !x.startsWith("/home") does not guarantee that x is not absolute
458564
}
459565

460-
override predicate blocksExpr(boolean outcome, Expr e, DataFlow::FlowLabel label) {
566+
override predicate blocksExpr(boolean outcome, Expr e, FlowState state) {
461567
e = operand.asExpr() and
462-
exists(Label::PosixPath posixPath | posixPath = label |
568+
exists(FlowState::PosixPath posixPath | posixPath = state |
463569
outcome = polarity and posixPath.isRelative()
464570
or
465571
negatable = true and
@@ -475,10 +581,10 @@ module TaintedPath {
475581
class ContainsDotDotSanitizer extends BarrierGuard instanceof StringOps::Includes {
476582
ContainsDotDotSanitizer() { isDotDotSlashPrefix(super.getSubstring()) }
477583

478-
override predicate blocksExpr(boolean outcome, Expr e, DataFlow::FlowLabel label) {
584+
override predicate blocksExpr(boolean outcome, Expr e, FlowState state) {
479585
e = super.getBaseString().asExpr() and
480586
outcome = super.getPolarity().booleanNot() and
481-
label.(Label::PosixPath).canContainDotDotSlash() // can still be bypassed by normalized absolute path
587+
state.(FlowState::PosixPath).canContainDotDotSlash() // can still be bypassed by normalized absolute path
482588
}
483589
}
484590

@@ -488,10 +594,10 @@ module TaintedPath {
488594
class ContainsDotDotRegExpSanitizer extends BarrierGuard instanceof StringOps::RegExpTest {
489595
ContainsDotDotRegExpSanitizer() { super.getRegExp().getAMatchedString() = [".", "..", "../"] }
490596

491-
override predicate blocksExpr(boolean outcome, Expr e, DataFlow::FlowLabel label) {
597+
override predicate blocksExpr(boolean outcome, Expr e, FlowState state) {
492598
e = super.getStringOperand().asExpr() and
493599
outcome = super.getPolarity().booleanNot() and
494-
label.(Label::PosixPath).canContainDotDotSlash() // can still be bypassed by normalized absolute path
600+
state.(FlowState::PosixPath).canContainDotDotSlash() // can still be bypassed by normalized absolute path
495601
}
496602
}
497603

@@ -590,11 +696,11 @@ module TaintedPath {
590696
)
591697
}
592698

593-
override predicate blocksExpr(boolean outcome, Expr e, DataFlow::FlowLabel label) {
699+
override predicate blocksExpr(boolean outcome, Expr e, FlowState state) {
594700
(
595701
onlyNormalizedAbsolutePaths = true and
596-
label.(Label::PosixPath).isNormalized() and
597-
label.(Label::PosixPath).isAbsolute()
702+
state.(FlowState::PosixPath).isNormalized() and
703+
state.(FlowState::PosixPath).isAbsolute()
598704
or
599705
onlyNormalizedAbsolutePaths = false
600706
) and
@@ -661,12 +767,12 @@ module TaintedPath {
661767
private class FsPathSinkWithoutUpwardNavigation extends FsPathSink {
662768
FsPathSinkWithoutUpwardNavigation() { fileSystemAccess.isUpwardNavigationRejected(this) }
663769

664-
override DataFlow::FlowLabel getAFlowLabel() {
770+
override FlowState getAFlowState() {
665771
// The protection is ineffective if the ../ segments have already
666772
// cancelled out against the intended root dir using path.join or similar.
667773
// Only flag normalized paths, as this corresponds to the output
668774
// of a normalizing call that had a malicious input.
669-
result.(Label::PosixPath).isNormalized()
775+
result.(FlowState::PosixPath).isNormalized()
670776
}
671777
}
672778

@@ -760,17 +866,26 @@ module TaintedPath {
760866
}
761867

762868
/**
763-
* Holds if there is a step `src -> dst` mapping `srclabel` to `dstlabel` relevant for path traversal vulnerabilities.
869+
* DEPRECATED. Use `isAdditionalFlowStep` instead.
764870
*/
765-
predicate isAdditionalTaintedPathFlowStep(
871+
deprecated predicate isAdditionalTaintedPathFlowStep(
766872
DataFlow::Node src, DataFlow::Node dst, DataFlow::FlowLabel srclabel,
767873
DataFlow::FlowLabel dstlabel
768874
) {
769-
isPosixPathStep(src, dst, srclabel, dstlabel)
875+
isAdditionalFlowStep(src, Label::toFlowState(srclabel), dst, Label::toFlowState(dstlabel))
876+
}
877+
878+
/**
879+
* Holds if there is a step `src -> dst` mapping `srclabel` to `dstlabel` relevant for path traversal vulnerabilities.
880+
*/
881+
predicate isAdditionalFlowStep(
882+
DataFlow::Node src, FlowState srclabel, DataFlow::Node dst, FlowState dstlabel
883+
) {
884+
isPosixPathStep(src, srclabel, dst, dstlabel)
770885
or
771886
// Ignore all preliminary sanitization after decoding URI components
772-
srclabel instanceof Label::PosixPath and
773-
dstlabel instanceof Label::PosixPath and
887+
srclabel instanceof FlowState::PosixPath and
888+
dstlabel instanceof FlowState::PosixPath and
774889
(
775890
TaintTracking::uriStep(src, dst)
776891
or
@@ -814,8 +929,8 @@ module TaintedPath {
814929
exists(StringSplitCall mcn | dst = mcn and mcn.getBaseString() = src |
815930
if mcn.getSeparator() = "/"
816931
then
817-
srclabel.(Label::PosixPath).canContainDotDotSlash() and
818-
dstlabel instanceof Label::SplitPath
932+
srclabel.(FlowState::PosixPath).canContainDotDotSlash() and
933+
dstlabel instanceof FlowState::SplitPath
819934
else srclabel = dstlabel
820935
)
821936
or
@@ -825,30 +940,30 @@ module TaintedPath {
825940
name = "pop" or
826941
name = "shift"
827942
) and
828-
srclabel instanceof Label::SplitPath and
829-
dstlabel.(Label::PosixPath).canContainDotDotSlash()
943+
srclabel instanceof FlowState::SplitPath and
944+
dstlabel.(FlowState::PosixPath).canContainDotDotSlash()
830945
or
831946
(
832947
name = "slice" or
833948
name = "splice" or
834949
name = "concat"
835950
) and
836-
dstlabel instanceof Label::SplitPath and
837-
srclabel instanceof Label::SplitPath
951+
dstlabel instanceof FlowState::SplitPath and
952+
srclabel instanceof FlowState::SplitPath
838953
or
839954
name = "join" and
840955
mcn.getArgument(0).mayHaveStringValue("/") and
841-
srclabel instanceof Label::SplitPath and
842-
dstlabel.(Label::PosixPath).canContainDotDotSlash()
956+
srclabel instanceof FlowState::SplitPath and
957+
dstlabel.(FlowState::PosixPath).canContainDotDotSlash()
843958
)
844959
or
845960
// prefix.concat(path)
846961
exists(DataFlow::MethodCallNode mcn |
847962
mcn.getMethodName() = "concat" and mcn.getAnArgument() = src
848963
|
849964
dst = mcn and
850-
dstlabel instanceof Label::SplitPath and
851-
srclabel instanceof Label::SplitPath
965+
dstlabel instanceof FlowState::SplitPath and
966+
srclabel instanceof FlowState::SplitPath
852967
)
853968
or
854969
// reading unknown property of split path
@@ -862,8 +977,8 @@ module TaintedPath {
862977
binop.getAnOperand().getIntValue() = 1 and
863978
binop.getAnOperand().(PropAccess).getPropertyName() = "length"
864979
) and
865-
srclabel instanceof Label::SplitPath and
866-
dstlabel.(Label::PosixPath).canContainDotDotSlash()
980+
srclabel instanceof FlowState::SplitPath and
981+
dstlabel.(FlowState::PosixPath).canContainDotDotSlash()
867982
)
868983
or
869984
exists(API::CallNode call | call = API::moduleImport("slash").getACall() |
@@ -884,14 +999,14 @@ module TaintedPath {
884999
src = join.getASpreadArgument() and
8851000
dst = join and
8861001
(
887-
srclabel.(Label::PosixPath).canContainDotDotSlash()
1002+
srclabel.(FlowState::PosixPath).canContainDotDotSlash()
8881003
or
889-
srclabel instanceof Label::SplitPath
1004+
srclabel instanceof FlowState::SplitPath
8901005
) and
891-
dstlabel.(Label::PosixPath).isNormalized() and
1006+
dstlabel.(FlowState::PosixPath).isNormalized() and
8921007
if isRelative(join.getArgument(0).getStringValue())
893-
then dstlabel.(Label::PosixPath).isRelative()
894-
else dstlabel.(Label::PosixPath).isAbsolute()
1008+
then dstlabel.(FlowState::PosixPath).isRelative()
1009+
else dstlabel.(FlowState::PosixPath).isAbsolute()
8951010
)
8961011
}
8971012

@@ -900,7 +1015,8 @@ module TaintedPath {
9001015
* standard taint step `src -> dst` should be suppressed.
9011016
*/
9021017
private predicate isPosixPathStep(
903-
DataFlow::Node src, DataFlow::Node dst, Label::PosixPath srclabel, Label::PosixPath dstlabel
1018+
DataFlow::Node src, FlowState::PosixPath srclabel, DataFlow::Node dst,
1019+
FlowState::PosixPath dstlabel
9041020
) {
9051021
// path.normalize() and similar
9061022
exists(NormalizingPathCall call |

0 commit comments

Comments
 (0)