44 */
55
66import csharp
7+ private import codeql.util.Unit
78private import semmle.code.csharp.controlflow.Guards
89private import semmle.code.csharp.security.dataflow.flowsinks.FlowSinks
910private import semmle.code.csharp.security.dataflow.flowsources.FlowSources
@@ -24,23 +25,67 @@ abstract class Sink extends ApiSinkExprNode { }
2425/**
2526 * A sanitizer for uncontrolled data in path expression vulnerabilities.
2627 */
27- abstract class Sanitizer extends DataFlow:: ExprNode { }
28+ abstract class Sanitizer extends DataFlow:: ExprNode {
29+ /** Holds if this is a sanitizer when the flow state is `state`. */
30+ predicate isBarrier ( TaintedPathConfig:: FlowState state ) { any ( ) }
31+ }
32+
33+ /** A path normalization step. */
34+ private class PathNormalizationStep extends Unit {
35+ /**
36+ * Holds if the flow step from `n1` to `n2` transforms the path into an
37+ * absolute path.
38+ *
39+ * For example, the argument-to-return-value step through a call
40+ * to `System.IO.Path.GetFullPath` is a normalization step.
41+ */
42+ abstract predicate isAdditionalFlowStep ( DataFlow:: Node n1 , DataFlow:: Node n2 ) ;
43+ }
44+
45+ private class GetFullPathStep extends PathNormalizationStep {
46+ override predicate isAdditionalFlowStep ( DataFlow:: Node n1 , DataFlow:: Node n2 ) {
47+ exists ( Call call |
48+ call .getARuntimeTarget ( ) .hasFullyQualifiedName ( "System.IO.Path" , "GetFullPath" ) and
49+ n1 .asExpr ( ) = call .getArgument ( 0 ) and
50+ n2 .asExpr ( ) = call
51+ )
52+ }
53+ }
2854
2955/**
3056 * A taint-tracking configuration for uncontrolled data in path expression vulnerabilities.
3157 */
32- private module TaintedPathConfig implements DataFlow:: ConfigSig {
33- predicate isSource ( DataFlow:: Node source ) { source instanceof Source }
58+ private module TaintedPathConfig implements DataFlow:: StateConfigSig {
59+ newtype FlowState =
60+ additional NotNormalized ( ) or
61+ additional Normalized ( )
62+
63+ predicate isSource ( DataFlow:: Node source , FlowState state ) {
64+ source instanceof Source and state = NotNormalized ( )
65+ }
3466
35- predicate isSink ( DataFlow:: Node sink ) { sink instanceof Sink }
67+ predicate isSink ( DataFlow:: Node sink , FlowState state ) {
68+ sink instanceof Sink and
69+ exists ( state )
70+ }
71+
72+ predicate isAdditionalFlowStep ( DataFlow:: Node n1 , FlowState s1 , DataFlow:: Node n2 , FlowState s2 ) {
73+ any ( PathNormalizationStep step ) .isAdditionalFlowStep ( n1 , n2 ) and
74+ s1 = NotNormalized ( ) and
75+ s2 = Normalized ( )
76+ }
3677
37- predicate isBarrier ( DataFlow:: Node node ) { node instanceof Sanitizer }
78+ predicate isBarrier ( DataFlow:: Node node , FlowState state ) { node .( Sanitizer ) .isBarrier ( state ) }
79+
80+ predicate isBarrierOut ( DataFlow:: Node node , FlowState state ) {
81+ isAdditionalFlowStep ( node , state , _, _)
82+ }
3883}
3984
4085/**
4186 * A taint-tracking module for uncontrolled data in path expression vulnerabilities.
4287 */
43- module TaintedPath = TaintTracking:: Global < TaintedPathConfig > ;
88+ module TaintedPath = TaintTracking:: GlobalWithState < TaintedPathConfig > ;
4489
4590/**
4691 * DEPRECATED: Use `ThreatModelSource` instead.
@@ -99,7 +144,7 @@ class StreamWriterTaintedPathSink extends Sink {
99144}
100145
101146/**
102- * A weak guard that is insufficient to prevent path tampering.
147+ * A weak guard that may be insufficient to prevent path tampering.
103148 */
104149private class WeakGuard extends Guard {
105150 WeakGuard ( ) {
@@ -118,6 +163,14 @@ private class WeakGuard extends Guard {
118163 or
119164 this .( LogicalOperation ) .getAnOperand ( ) instanceof WeakGuard
120165 }
166+
167+ predicate isBarrier ( TaintedPathConfig:: FlowState state ) {
168+ state = TaintedPathConfig:: Normalized ( ) and
169+ exists ( Method m | this .( MethodCall ) .getTarget ( ) = m |
170+ m .getName ( ) = "StartsWith" or
171+ m .getName ( ) = "EndsWith"
172+ )
173+ }
121174}
122175
123176/**
@@ -126,12 +179,17 @@ private class WeakGuard extends Guard {
126179 * A weak check is one that is insufficient to prevent path tampering.
127180 */
128181class PathCheck extends Sanitizer {
182+ Guard g ;
183+
129184 PathCheck ( ) {
130- // This expression is structurally replicated in a dominating guard which is not a "weak" check
131- exists ( Guard g , AbstractValues:: BooleanValue v |
132- g = this .( GuardedDataFlowNode ) .getAGuard ( _, v ) and
133- not g instanceof WeakGuard
134- )
185+ // This expression is structurally replicated in a dominating guard
186+ exists ( AbstractValues:: BooleanValue v | g = this .( GuardedDataFlowNode ) .getAGuard ( _, v ) )
187+ }
188+
189+ override predicate isBarrier ( TaintedPathConfig:: FlowState state ) {
190+ g .( WeakGuard ) .isBarrier ( state )
191+ or
192+ not g instanceof WeakGuard
135193 }
136194}
137195
0 commit comments