6
6
import java
7
7
import semmle.code.java.dataflow.FlowSources
8
8
import semmle.code.java.dataflow.TaintTracking
9
- // SECLAB: Import the CSV utils library
10
- import semmle.code.java.dataflow.ExternalFlow as ExternalFlow
11
9
12
10
/**
13
11
* A `Method` that is considered a "safe" external API from a security perspective.
14
12
*/
15
13
abstract class SafeExternalApiMethod extends Method { }
16
14
17
- /** DEPRECATED: Alias for SafeExternalApiMethod */
18
- deprecated class SafeExternalAPIMethod = SafeExternalApiMethod ;
19
-
20
15
/** The default set of "safe" external APIs. */
21
16
private class DefaultSafeExternalApiMethod extends SafeExternalApiMethod {
22
17
DefaultSafeExternalApiMethod ( ) {
@@ -85,7 +80,7 @@ class ExternalApiDataNode extends DataFlow::Node {
85
80
) and
86
81
// Not already modeled as a taint step (we need both of these to handle `AdditionalTaintStep` subclasses as well)
87
82
not TaintTracking:: localTaintStep ( this , _) and
88
- not TaintTracking:: defaultAdditionalTaintStep ( this , _) and
83
+ not TaintTracking:: defaultAdditionalTaintStep ( this , _, _ ) and
89
84
// Not a call to a known safe external API
90
85
not call .getCallee ( ) instanceof SafeExternalApiMethod and
91
86
// SECLAB: Not in a test file
@@ -102,34 +97,41 @@ class ExternalApiDataNode extends DataFlow::Node {
102
97
string getMethodDescription ( ) { result = this .getMethod ( ) .getQualifiedName ( ) }
103
98
}
104
99
105
- /** DEPRECATED: Alias for ExternalApiDataNode */
106
- deprecated class ExternalAPIDataNode = ExternalApiDataNode ;
107
-
108
- /** A configuration for tracking flow from `RemoteFlowSource`s to `ExternalApiDataNode`s. */
109
- class UntrustedDataToExternalApiConfig extends TaintTracking:: Configuration {
100
+ /**
101
+ * DEPRECATED: Use `UntrustedDataToExternalApiFlow` instead.
102
+ *
103
+ * A configuration for tracking flow from `RemoteFlowSource`s to `ExternalApiDataNode`s.
104
+ */
105
+ deprecated class UntrustedDataToExternalApiConfig extends TaintTracking:: Configuration {
110
106
UntrustedDataToExternalApiConfig ( ) { this = "UntrustedDataToExternalAPIConfig" }
111
107
112
108
override predicate isSource ( DataFlow:: Node source ) { source instanceof RemoteFlowSource }
113
109
114
110
override predicate isSink ( DataFlow:: Node sink ) { sink instanceof ExternalApiDataNode }
115
111
}
116
112
117
- /** DEPRECATED: Alias for UntrustedDataToExternalApiConfig */
118
- deprecated class UntrustedDataToExternalAPIConfig = UntrustedDataToExternalApiConfig ;
113
+ /**
114
+ * Taint tracking configuration for flow from `ThreatModelFlowSource`s to `ExternalApiDataNode`s.
115
+ */
116
+ module UntrustedDataToExternalApiConfig implements DataFlow:: ConfigSig {
117
+ predicate isSource ( DataFlow:: Node source ) { source instanceof ThreatModelFlowSource }
118
+
119
+ predicate isSink ( DataFlow:: Node sink ) { sink instanceof ExternalApiDataNode }
120
+ }
121
+
122
+ /**
123
+ * Tracks flow from untrusted data to external APIs.
124
+ */
125
+ module UntrustedDataToExternalApiFlow = TaintTracking:: Global< UntrustedDataToExternalApiConfig > ;
119
126
120
127
/** A node representing untrusted data being passed to an external API. */
121
128
class UntrustedExternalApiDataNode extends ExternalApiDataNode {
122
- UntrustedExternalApiDataNode ( ) { any ( UntrustedDataToExternalApiConfig c ) . hasFlow ( _ , this ) }
129
+ UntrustedExternalApiDataNode ( ) { UntrustedDataToExternalApiFlow :: flowTo ( this ) }
123
130
124
131
/** Gets a source of untrusted data which is passed to this external API data node. */
125
- DataFlow:: Node getAnUntrustedSource ( ) {
126
- any ( UntrustedDataToExternalApiConfig c ) .hasFlow ( result , this )
127
- }
132
+ DataFlow:: Node getAnUntrustedSource ( ) { UntrustedDataToExternalApiFlow:: flow ( result , this ) }
128
133
}
129
134
130
- /** DEPRECATED: Alias for UntrustedExternalApiDataNode */
131
- deprecated class UntrustedExternalAPIDataNode = UntrustedExternalApiDataNode ;
132
-
133
135
/** An external API which is used with untrusted data. */
134
136
private newtype TExternalApi =
135
137
/** An untrusted API method `m` where untrusted data is passed at `index`. */
@@ -154,65 +156,189 @@ class ExternalApiUsedWithUntrustedData extends TExternalApi {
154
156
155
157
/** Gets a textual representation of this element. */
156
158
string toString ( ) {
157
- exists ( Method m , int index |
159
+ exists ( Method m , int index , string indexString |
160
+ if index = - 1 then indexString = "qualifier" else indexString = "param " + index
161
+ |
158
162
this = TExternalApiParameter ( m , index ) and
159
163
// SECLAB: use the CSV library to get the 6 first columns
160
164
result = asPartialModel ( m ) + index .toString ( )
161
165
)
162
166
}
163
167
}
164
168
165
- /** DEPRECATED: Alias for ExternalApiUsedWithUntrustedData */
166
- deprecated class ExternalAPIUsedWithUntrustedData = ExternalApiUsedWithUntrustedData ;
167
-
168
169
// SECLAB: predicates from https://github.com/github/codeql/blob/main/java/ql/src/utils/modelgenerator/internal/CaptureModelsSpecific.qll
169
170
// We cannot import them directly as they are based on TargetApiSpecific which checks for `fromSource()`
170
- private Method superImpl ( Method m ) {
171
+ private import java as J
172
+ private import semmle.code.java.dataflow.ExternalFlow as ExternalFlow
173
+ private import semmle.code.java.dataflow.internal.FlowSummaryImpl as FlowSummaryImpl
174
+
175
+ private predicate isInfrequentlyUsed ( J:: CompilationUnit cu ) {
176
+ cu .getPackage ( ) .getName ( ) .matches ( "javax.swing%" ) or
177
+ cu .getPackage ( ) .getName ( ) .matches ( "java.awt%" )
178
+ }
179
+
180
+ private predicate relevant ( Callable api ) {
181
+ api .isPublic ( ) and
182
+ api .getDeclaringType ( ) .isPublic ( ) and
183
+ api .fromSource ( ) and
184
+ not isUninterestingForModels ( api ) and
185
+ not isInfrequentlyUsed ( api .getCompilationUnit ( ) )
186
+ }
187
+
188
+ private J:: Method getARelevantOverride ( J:: Method m ) {
171
189
result = m .getAnOverride ( ) and
172
- not exists ( result .getAnOverride ( ) ) and
173
- not m instanceof ToStringMethod
190
+ relevant ( result ) and
191
+ // Other exclusions for overrides.
192
+ not m instanceof J:: ToStringMethod
174
193
}
175
194
176
- private string isExtensible ( RefType ref ) {
177
- if ref .isFinal ( ) then result = "false" else result = "true"
195
+ /**
196
+ * Gets the super implementation of `m` if it is relevant.
197
+ * If such a super implementations does not exist, returns `m` if it is relevant.
198
+ */
199
+ private J:: Callable liftedImpl ( J:: Callable m ) {
200
+ (
201
+ result = getARelevantOverride ( m )
202
+ or
203
+ result = m and relevant ( m )
204
+ ) and
205
+ not exists ( getARelevantOverride ( result ) )
178
206
}
179
207
180
- private string typeAsModel ( RefType type ) {
181
- result = type .getCompilationUnit ( ) .getPackage ( ) .getName ( ) + ";" + type .nestedName ( )
208
+ private predicate hasManualModel ( Callable api ) {
209
+ api = any ( FlowSummaryImpl:: Public:: SummarizedCallable sc | sc .applyManualModel ( ) ) .asCallable ( ) or
210
+ api = any ( FlowSummaryImpl:: Public:: NeutralSummaryCallable sc | sc .hasManualModel ( ) ) .asCallable ( )
211
+ }
212
+
213
+ /**
214
+ * A class of callables that are potentially relevant for generating summary, source, sink
215
+ * and neutral models.
216
+ *
217
+ * In the Standard library and 3rd party libraries it is the callables (or callables that have a
218
+ * super implementation) that can be called from outside the library itself.
219
+ */
220
+ class TargetApiSpecific extends Callable {
221
+ private Callable lift ;
222
+
223
+ TargetApiSpecific ( ) {
224
+ lift = liftedImpl ( this ) and
225
+ not hasManualModel ( lift )
226
+ }
227
+
228
+ /**
229
+ * Gets the callable that a model will be lifted to.
230
+ */
231
+ Callable lift ( ) { result = lift }
232
+
233
+ /**
234
+ * Holds if this callable is relevant in terms of generating models.
235
+ */
236
+ predicate isRelevant ( ) { relevant ( this ) }
182
237
}
183
238
184
- private RefType bestTypeForModel ( Method api ) {
185
- if exists ( superImpl ( api ) )
186
- then superImpl ( api ) .fromSource ( ) and result = superImpl ( api ) .getDeclaringType ( )
187
- else result = api .getDeclaringType ( )
239
+ private string isExtensible ( Callable c ) {
240
+ if c .getDeclaringType ( ) .isFinal ( ) then result = "false" else result = "true"
188
241
}
189
242
190
- private string typeAsSummaryModel ( Method api ) { result = typeAsModel ( bestTypeForModel ( api ) ) }
243
+ /**
244
+ * Returns the appropriate type name for the model.
245
+ */
246
+ private string typeAsModel ( Callable c ) {
247
+ exists ( RefType type | type = c .getDeclaringType ( ) |
248
+ result =
249
+ type .getCompilationUnit ( ) .getPackage ( ) .getName ( ) + ";" +
250
+ type .getErasure ( ) .( J:: RefType ) .nestedName ( )
251
+ )
252
+ }
191
253
192
- private predicate partialModel ( Method api , string type , string name , string parameters ) {
193
- type = typeAsSummaryModel ( api ) and
254
+ private predicate partialModel (
255
+ Callable api , string type , string extensible , string name , string parameters
256
+ ) {
257
+ type = typeAsModel ( api ) and
258
+ extensible = isExtensible ( api ) and
194
259
name = api .getName ( ) and
195
260
parameters = ExternalFlow:: paramsString ( api )
196
261
}
197
262
198
- string asPartialModel ( Method api ) {
199
- exists ( string type , string name , string parameters |
200
- partialModel ( api , type , name , parameters ) and
263
+ /**
264
+ * Computes the first 6 columns for MaD rows.
265
+ */
266
+ string asPartialModel ( TargetApiSpecific api ) {
267
+ exists ( string type , string extensible , string name , string parameters |
268
+ partialModel ( api .lift ( ) , type , extensible , name , parameters ) and
201
269
result =
202
270
type + ";" //
203
- + isExtensible ( bestTypeForModel ( api ) ) + ";" //
271
+ + extensible + ";" //
204
272
+ name + ";" //
205
273
+ parameters + ";" //
206
274
+ /* ext + */ ";" //
207
275
)
208
276
}
209
277
210
- private predicate isInTestFile ( File file ) {
211
- file .getAbsolutePath ( ) .matches ( "%src/test/%" ) or
212
- file .getAbsolutePath ( ) .matches ( "%/guava-tests/%" ) or
213
- file .getAbsolutePath ( ) .matches ( "%/guava-testlib/%" )
278
+ // SECLAB: check if the package is internal to the JDK
279
+ // https://github.com/github/codeql/blob/67e2ea195f092347f3d9b5f976c649d6e9fcc219/java/ql/lib/semmle/code/java/dataflow/internal/ModelExclusions.qll#L77
280
+ /** Holds if the given package `p` is a test package. */
281
+ pragma [ nomagic]
282
+ private predicate isTestPackage ( Package p ) {
283
+ p .getName ( )
284
+ .matches ( [
285
+ "org.junit%" , "junit.%" , "org.mockito%" , "org.assertj%" ,
286
+ "com.github.tomakehurst.wiremock%" , "org.hamcrest%" , "org.springframework.test.%" ,
287
+ "org.springframework.mock.%" , "org.springframework.boot.test.%" , "reactor.test%" ,
288
+ "org.xmlunit%" , "org.testcontainers.%" , "org.opentest4j%" , "org.mockserver%" ,
289
+ "org.powermock%" , "org.skyscreamer.jsonassert%" , "org.rnorth.visibleassertions" ,
290
+ "org.openqa.selenium%" , "com.gargoylesoftware.htmlunit%" , "org.jboss.arquillian.testng%" ,
291
+ "org.testng%"
292
+ ] )
293
+ }
294
+
295
+ /**
296
+ * A test library.
297
+ */
298
+ class TestLibrary extends RefType {
299
+ TestLibrary ( ) { isTestPackage ( this .getPackage ( ) ) }
300
+ }
301
+
302
+ /** Holds if the given compilation unit's package is internal. */
303
+ private predicate isInternal ( CompilationUnit cu ) {
304
+ isJdkInternal ( cu ) or
305
+ cu .getPackage ( ) .getName ( ) .matches ( "%internal%" )
306
+ }
307
+
308
+ /** A method relating to lambda flow. */
309
+ private class LambdaFlowMethod extends Method {
310
+ LambdaFlowMethod ( ) {
311
+ this .hasQualifiedName ( "java.lang" , "Runnable" , "run" ) or
312
+ this .hasQualifiedName ( "java.util" , "Comparator" ,
313
+ [ "comparing" , "comparingDouble" , "comparingInt" , "comparingLong" ] ) or
314
+ this .hasQualifiedName ( "java.util.function" , "BiConsumer" , "accept" ) or
315
+ this .hasQualifiedName ( "java.util.function" , "BiFunction" , "apply" ) or
316
+ this .hasQualifiedName ( "java.util.function" , "Consumer" , "accept" ) or
317
+ this .hasQualifiedName ( "java.util.function" , "Function" , "apply" ) or
318
+ this .hasQualifiedName ( "java.util.function" , "Supplier" , "get" )
319
+ }
320
+ }
321
+
322
+ /** Holds if the given callable is not worth modeling. */
323
+ predicate isUninterestingForModels ( Callable c ) {
324
+ isInTestFile ( c .getCompilationUnit ( ) .getFile ( ) ) or
325
+ isInternal ( c .getCompilationUnit ( ) ) or
326
+ c instanceof MainMethod or
327
+ c instanceof ToStringMethod or
328
+ c instanceof LambdaFlowMethod or
329
+ c instanceof StaticInitializer or
330
+ exists ( FunctionalExpr funcExpr | c = funcExpr .asMethod ( ) ) or
331
+ c .getDeclaringType ( ) instanceof TestLibrary or
332
+ c .( Constructor ) .isParameterless ( )
333
+ }
334
+
335
+ /** Holds if the given file is a test file. */
336
+ predicate isInTestFile ( File file ) {
337
+ file .getAbsolutePath ( ) .matches ( [ "%/test/%" , "%/guava-tests/%" , "%/guava-testlib/%" ] ) and
338
+ not file .getAbsolutePath ( ) .matches ( [ "%/ql/test/%" , "%/ql/automodel/test/%" ] ) // allows our test cases to work
214
339
}
215
340
341
+ /** Holds if the given compilation unit's package is a JDK internal. */
216
342
private predicate isJdkInternal ( CompilationUnit cu ) {
217
343
cu .getPackage ( ) .getName ( ) .matches ( "org.graalvm%" ) or
218
344
cu .getPackage ( ) .getName ( ) .matches ( "com.sun%" ) or
0 commit comments