@@ -4,6 +4,8 @@ import java
4
4
private import semmle.code.java.controlflow.Guards
5
5
private import semmle.code.java.dataflow.FlowSources
6
6
private import semmle.code.java.dataflow.SSA
7
+ private import semmle.code.java.frameworks.kotlin.IO
8
+ private import semmle.code.java.frameworks.kotlin.Text
7
9
8
10
/** A sanitizer that protects against path injection vulnerabilities. */
9
11
abstract class PathInjectionSanitizer extends DataFlow:: Node { }
@@ -50,12 +52,14 @@ private predicate exactPathMatchGuard(Guard g, Expr e, boolean branch) {
50
52
t instanceof TypeUri or
51
53
t instanceof TypePath or
52
54
t instanceof TypeFile or
53
- t .hasQualifiedName ( "android.net" , "Uri" )
55
+ t .hasQualifiedName ( "android.net" , "Uri" ) or
56
+ t instanceof StringsKt or
57
+ t instanceof FilesKt
54
58
|
59
+ e = getVisualQualifier ( ma ) .getUnderlyingExpr ( ) and
55
60
ma .getMethod ( ) .getDeclaringType ( ) = t and
56
61
ma = g and
57
- ma .getMethod ( ) .getName ( ) = [ "equals" , "equalsIgnoreCase" ] and
58
- e = ma .getQualifier ( ) and
62
+ getSourceMethod ( ma .getMethod ( ) ) .hasName ( [ "equals" , "equalsIgnoreCase" ] ) and
59
63
branch = true
60
64
)
61
65
}
@@ -86,7 +90,7 @@ private class AllowedPrefixGuard extends PathGuard instanceof MethodAccess {
86
90
not isDisallowedWord ( super .getAnArgument ( ) )
87
91
}
88
92
89
- override Expr getCheckedExpr ( ) { result = super . getQualifier ( ) }
93
+ override Expr getCheckedExpr ( ) { result = getVisualQualifier ( this ) . getUnderlyingExpr ( ) }
90
94
}
91
95
92
96
/**
@@ -153,7 +157,7 @@ private class BlockListGuard extends PathGuard instanceof MethodAccess {
153
157
isDisallowedWord ( super .getAnArgument ( ) )
154
158
}
155
159
156
- override Expr getCheckedExpr ( ) { result = super . getQualifier ( ) }
160
+ override Expr getCheckedExpr ( ) { result = getVisualQualifier ( this ) . getUnderlyingExpr ( ) }
157
161
}
158
162
159
163
/**
@@ -187,15 +191,31 @@ private class BlockListSanitizer extends PathInjectionSanitizer {
187
191
}
188
192
}
189
193
194
+ private class ConstantOrRegex extends Expr {
195
+ ConstantOrRegex ( ) {
196
+ this instanceof CompileTimeConstantExpr or
197
+ this instanceof KtToRegex
198
+ }
199
+
200
+ string getStringValue ( ) {
201
+ result = this .( CompileTimeConstantExpr ) .getStringValue ( ) or
202
+ result = this .( KtToRegex ) .getExpressionString ( )
203
+ }
204
+ }
205
+
190
206
private predicate isStringPrefixMatch ( MethodAccess ma ) {
191
- exists ( Method m | m = ma .getMethod ( ) and m .getDeclaringType ( ) instanceof TypeString |
192
- m .hasName ( "startsWith" )
207
+ exists ( Method m , RefType t |
208
+ m .getDeclaringType ( ) = t and
209
+ ( t instanceof TypeString or t instanceof StringsKt ) and
210
+ m = ma .getMethod ( )
211
+ |
212
+ getSourceMethod ( m ) .hasName ( "startsWith" )
193
213
or
194
- m .hasName ( "regionMatches" ) and
195
- ma . getArgument ( 0 ) .( CompileTimeConstantExpr ) .getIntValue ( ) = 0
214
+ getSourceMethod ( m ) .hasName ( "regionMatches" ) and
215
+ getVisualArgument ( ma , 0 ) .( CompileTimeConstantExpr ) .getIntValue ( ) = 0
196
216
or
197
217
m .hasName ( "matches" ) and
198
- not ma . getArgument ( 0 ) .( CompileTimeConstantExpr ) .getStringValue ( ) .matches ( ".*%" )
218
+ not getVisualArgument ( ma , 0 ) .( ConstantOrRegex ) .getStringValue ( ) .matches ( ".*%" )
199
219
)
200
220
}
201
221
@@ -205,52 +225,52 @@ private predicate isStringPrefixMatch(MethodAccess ma) {
205
225
private predicate isStringPartialMatch ( MethodAccess ma ) {
206
226
isStringPrefixMatch ( ma )
207
227
or
208
- ma .getMethod ( ) .getDeclaringType ( ) instanceof TypeString and
209
- ma .getMethod ( ) .hasName ( [ "contains" , "matches" , "regionMatches" , "indexOf" , "lastIndexOf" ] )
228
+ exists ( RefType t | t = ma .getMethod ( ) .getDeclaringType ( ) |
229
+ t instanceof TypeString or t instanceof StringsKt
230
+ ) and
231
+ getSourceMethod ( ma .getMethod ( ) )
232
+ .hasName ( [ "contains" , "matches" , "regionMatches" , "indexOf" , "lastIndexOf" ] )
210
233
}
211
234
212
235
/**
213
236
* Holds if `ma` is a call to a method that checks whether a path starts with a prefix.
214
237
*/
215
238
private predicate isPathPrefixMatch ( MethodAccess ma ) {
216
- exists ( RefType t |
217
- t instanceof TypePath
218
- or
219
- t .hasQualifiedName ( "kotlin.io" , "FilesKt" )
220
- |
221
- t = ma .getMethod ( ) .getDeclaringType ( ) and
222
- ma .getMethod ( ) .hasName ( "startsWith" )
223
- )
239
+ exists ( RefType t | t = ma .getMethod ( ) .getDeclaringType ( ) |
240
+ t instanceof TypePath or t instanceof FilesKt
241
+ ) and
242
+ getSourceMethod ( ma .getMethod ( ) ) .hasName ( "startsWith" )
224
243
}
225
244
226
- private predicate isDisallowedWord ( CompileTimeConstantExpr word ) {
245
+ private predicate isDisallowedWord ( ConstantOrRegex word ) {
227
246
word .getStringValue ( ) .matches ( [ "/" , "\\" , "%WEB-INF%" , "%/data%" ] )
228
247
}
229
248
230
249
/** A complementary guard that protects against path traversal, by looking for the literal `..`. */
231
250
private class PathTraversalGuard extends PathGuard {
251
+ Expr checkedExpr ;
252
+
232
253
PathTraversalGuard ( ) {
233
- exists ( MethodAccess ma |
234
- ma .getMethod ( ) .getDeclaringType ( ) instanceof TypeString and
254
+ exists ( MethodAccess ma , Method m , RefType t |
255
+ m = ma .getMethod ( ) and
256
+ t = m .getDeclaringType ( ) and
257
+ ( t instanceof TypeString or t instanceof StringsKt ) and
258
+ checkedExpr = getVisualQualifier ( ma ) .getUnderlyingExpr ( ) and
235
259
ma .getAnArgument ( ) .( CompileTimeConstantExpr ) .getStringValue ( ) = ".."
236
260
|
237
261
this = ma and
238
- ma . getMethod ( ) .hasName ( "contains" )
262
+ getSourceMethod ( m ) .hasName ( "contains" )
239
263
or
240
264
exists ( EqualityTest eq |
241
265
this = eq and
242
- ma . getMethod ( ) .hasName ( [ "indexOf" , "lastIndexOf" ] ) and
266
+ getSourceMethod ( m ) .hasName ( [ "indexOf" , "lastIndexOf" ] ) and
243
267
eq .getAnOperand ( ) = ma and
244
268
eq .getAnOperand ( ) .( CompileTimeConstantExpr ) .getIntValue ( ) = - 1
245
269
)
246
270
)
247
271
}
248
272
249
- override Expr getCheckedExpr ( ) {
250
- exists ( MethodAccess ma | ma = this .( EqualityTest ) .getAnOperand ( ) or ma = this |
251
- result = ma .getQualifier ( )
252
- )
253
- }
273
+ override Expr getCheckedExpr ( ) { result = checkedExpr }
254
274
255
275
boolean getBranch ( ) {
256
276
this instanceof MethodAccess and result = false
@@ -262,15 +282,46 @@ private class PathTraversalGuard extends PathGuard {
262
282
/** A complementary sanitizer that protects against path traversal using path normalization. */
263
283
private class PathNormalizeSanitizer extends MethodAccess {
264
284
PathNormalizeSanitizer ( ) {
265
- exists ( RefType t |
266
- t instanceof TypePath or
267
- t .hasQualifiedName ( "kotlin.io" , "FilesKt" )
268
- |
269
- this .getMethod ( ) .getDeclaringType ( ) = t and
285
+ exists ( RefType t | this .getMethod ( ) .getDeclaringType ( ) = t |
286
+ ( t instanceof TypePath or t instanceof FilesKt ) and
270
287
this .getMethod ( ) .hasName ( "normalize" )
288
+ or
289
+ t instanceof TypeFile and
290
+ this .getMethod ( ) .hasName ( [ "getCanonicalPath" , "getCanonicalFile" ] )
271
291
)
272
- or
273
- this .getMethod ( ) .getDeclaringType ( ) instanceof TypeFile and
274
- this .getMethod ( ) .hasName ( [ "getCanonicalPath" , "getCanonicalFile" ] )
275
292
}
276
293
}
294
+
295
+ /**
296
+ * Gets the qualifier of `ma` as seen in the source code.
297
+ * This is a helper predicate to solve discrepancies between
298
+ * what `getQualifier` actually gets in Java and Kotlin.
299
+ */
300
+ private Expr getVisualQualifier ( MethodAccess ma ) {
301
+ if getSourceMethod ( ma .getMethod ( ) ) instanceof ExtensionMethod
302
+ then result = ma .getArgument ( 0 )
303
+ else result = ma .getQualifier ( )
304
+ }
305
+
306
+ /**
307
+ * Gets the argument of `ma` at position `argPos` as seen in the source code.
308
+ * This is a helper predicate to solve discrepancies between
309
+ * what `getArgument` actually gets in Java and Kotlin.
310
+ */
311
+ bindingset [ argPos]
312
+ private Argument getVisualArgument ( MethodAccess ma , int argPos ) {
313
+ if getSourceMethod ( ma .getMethod ( ) ) instanceof ExtensionMethod
314
+ then result = ma .getArgument ( argPos + 1 )
315
+ else result = ma .getArgument ( argPos )
316
+ }
317
+
318
+ /**
319
+ * Gets the proxied method if `m` is a Kotlin proxy that supplies default parameter values.
320
+ * Otherwise, just gets `m`.
321
+ */
322
+ private Method getSourceMethod ( Method m ) {
323
+ m = result .getKotlinParameterDefaultsProxy ( )
324
+ or
325
+ not exists ( Method src | m = src .getKotlinParameterDefaultsProxy ( ) ) and
326
+ result = m
327
+ }
0 commit comments