Skip to content

Commit cf22bd3

Browse files
Alvaro MuñozGeekMasher
authored andcommitted
Update APIs for Java
1 parent 58bcdb3 commit cf22bd3

File tree

1 file changed

+172
-46
lines changed

1 file changed

+172
-46
lines changed

java/src/library_sources/ExternalAPIs.qll

Lines changed: 172 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -6,17 +6,12 @@
66
import java
77
import semmle.code.java.dataflow.FlowSources
88
import semmle.code.java.dataflow.TaintTracking
9-
// SECLAB: Import the CSV utils library
10-
import semmle.code.java.dataflow.ExternalFlow as ExternalFlow
119

1210
/**
1311
* A `Method` that is considered a "safe" external API from a security perspective.
1412
*/
1513
abstract class SafeExternalApiMethod extends Method { }
1614

17-
/** DEPRECATED: Alias for SafeExternalApiMethod */
18-
deprecated class SafeExternalAPIMethod = SafeExternalApiMethod;
19-
2015
/** The default set of "safe" external APIs. */
2116
private class DefaultSafeExternalApiMethod extends SafeExternalApiMethod {
2217
DefaultSafeExternalApiMethod() {
@@ -85,7 +80,7 @@ class ExternalApiDataNode extends DataFlow::Node {
8580
) and
8681
// Not already modeled as a taint step (we need both of these to handle `AdditionalTaintStep` subclasses as well)
8782
not TaintTracking::localTaintStep(this, _) and
88-
not TaintTracking::defaultAdditionalTaintStep(this, _) and
83+
not TaintTracking::defaultAdditionalTaintStep(this, _, _) and
8984
// Not a call to a known safe external API
9085
not call.getCallee() instanceof SafeExternalApiMethod and
9186
// SECLAB: Not in a test file
@@ -102,34 +97,41 @@ class ExternalApiDataNode extends DataFlow::Node {
10297
string getMethodDescription() { result = this.getMethod().getQualifiedName() }
10398
}
10499

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 {
110106
UntrustedDataToExternalApiConfig() { this = "UntrustedDataToExternalAPIConfig" }
111107

112108
override predicate isSource(DataFlow::Node source) { source instanceof RemoteFlowSource }
113109

114110
override predicate isSink(DataFlow::Node sink) { sink instanceof ExternalApiDataNode }
115111
}
116112

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>;
119126

120127
/** A node representing untrusted data being passed to an external API. */
121128
class UntrustedExternalApiDataNode extends ExternalApiDataNode {
122-
UntrustedExternalApiDataNode() { any(UntrustedDataToExternalApiConfig c).hasFlow(_, this) }
129+
UntrustedExternalApiDataNode() { UntrustedDataToExternalApiFlow::flowTo(this) }
123130

124131
/** 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) }
128133
}
129134

130-
/** DEPRECATED: Alias for UntrustedExternalApiDataNode */
131-
deprecated class UntrustedExternalAPIDataNode = UntrustedExternalApiDataNode;
132-
133135
/** An external API which is used with untrusted data. */
134136
private newtype TExternalApi =
135137
/** An untrusted API method `m` where untrusted data is passed at `index`. */
@@ -154,65 +156,189 @@ class ExternalApiUsedWithUntrustedData extends TExternalApi {
154156

155157
/** Gets a textual representation of this element. */
156158
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+
|
158162
this = TExternalApiParameter(m, index) and
159163
// SECLAB: use the CSV library to get the 6 first columns
160164
result = asPartialModel(m) + index.toString()
161165
)
162166
}
163167
}
164168

165-
/** DEPRECATED: Alias for ExternalApiUsedWithUntrustedData */
166-
deprecated class ExternalAPIUsedWithUntrustedData = ExternalApiUsedWithUntrustedData;
167-
168169
// SECLAB: predicates from https://github.com/github/codeql/blob/main/java/ql/src/utils/modelgenerator/internal/CaptureModelsSpecific.qll
169170
// 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) {
171189
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
174193
}
175194

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))
178206
}
179207

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) }
182237
}
183238

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"
188241
}
189242

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+
}
191253

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
194259
name = api.getName() and
195260
parameters = ExternalFlow::paramsString(api)
196261
}
197262

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
201269
result =
202270
type + ";" //
203-
+ isExtensible(bestTypeForModel(api)) + ";" //
271+
+ extensible + ";" //
204272
+ name + ";" //
205273
+ parameters + ";" //
206274
+ /* ext + */ ";" //
207275
)
208276
}
209277

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
214339
}
215340

341+
/** Holds if the given compilation unit's package is a JDK internal. */
216342
private predicate isJdkInternal(CompilationUnit cu) {
217343
cu.getPackage().getName().matches("org.graalvm%") or
218344
cu.getPackage().getName().matches("com.sun%") or

0 commit comments

Comments
 (0)