Skip to content

Commit 9f443d4

Browse files
author
Max Schaefer
committed
Make Unexploitable*Characteristic more precise.
1 parent 6e9c90a commit 9f443d4

File tree

6 files changed

+73
-29
lines changed

6 files changed

+73
-29
lines changed

java/ql/automodel/src/AutomodelApplicationModeCharacteristics.qll

Lines changed: 24 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -416,41 +416,56 @@ class ApplicationModeMetadataExtractor extends string {
416416
*/
417417

418418
/**
419-
* A negative characteristic that indicates that an is-style boolean method is unexploitable even if it is a sink.
419+
* A negative characteristic that indicates that parameters of an is-style boolean method should not be considered sinks,
420+
* and its return value should not be considered a source.
420421
*
421422
* A sink is highly unlikely to be exploitable if its callable's name starts with `is` and the callable has a boolean return
422423
* type (e.g. `isDirectory`). These kinds of calls normally do only checks, and appear before the proper call that does
423424
* the dangerous/interesting thing, so we want the latter to be modeled as the sink.
424425
*
425426
* TODO: this might filter too much, it's possible that methods with more than one parameter contain interesting sinks
426427
*/
427-
private class UnexploitableIsCharacteristic extends CharacteristicsImpl::NotASinkCharacteristic {
428+
private class UnexploitableIsCharacteristic extends CharacteristicsImpl::NeitherSourceNorSinkCharacteristic
429+
{
428430
UnexploitableIsCharacteristic() { this = "unexploitable (is-style boolean method)" }
429431

430432
override predicate appliesToEndpoint(Endpoint e) {
431-
not ApplicationCandidatesImpl::isSink(e, _, _) and
432433
e.getCallable().getName().matches("is%") and
433-
e.getCallable().getReturnType() instanceof BooleanType
434+
e.getCallable().getReturnType() instanceof BooleanType and
435+
(
436+
e.getExtensibleType() = "sinkModel" and
437+
not ApplicationCandidatesImpl::isSink(e, _, _)
438+
or
439+
e.getExtensibleType() = "sourceModel" and
440+
not ApplicationCandidatesImpl::isSource(e, _, _) and
441+
e.getMaDOutput() = "ReturnValue"
442+
)
434443
}
435444
}
436445

437446
/**
438-
* A negative characteristic that indicates that an existence-checking boolean method is unexploitable even if it is a
439-
* sink.
447+
* A negative characteristic that indicates that parameters of an existence-checking boolean method should not be
448+
* considered sinks, and its return value should not be considered a source.
440449
*
441450
* A sink is highly unlikely to be exploitable if its callable's name is `exists` or `notExists` and the callable has a
442451
* boolean return type. These kinds of calls normally do only checks, and appear before the proper call that does the
443452
* dangerous/interesting thing, so we want the latter to be modeled as the sink.
444453
*/
445-
private class UnexploitableExistsCharacteristic extends CharacteristicsImpl::NotASinkCharacteristic {
454+
private class UnexploitableExistsCharacteristic extends CharacteristicsImpl::NeitherSourceNorSinkCharacteristic {
446455
UnexploitableExistsCharacteristic() { this = "unexploitable (existence-checking boolean method)" }
447456

448457
override predicate appliesToEndpoint(Endpoint e) {
449-
not ApplicationCandidatesImpl::isSink(e, _, _) and
450458
exists(Callable callable |
451-
callable = ApplicationModeGetCallable::getCallable(e) and
459+
callable = e.getCallable() and
452460
callable.getName().toLowerCase() = ["exists", "notexists"] and
453461
callable.getReturnType() instanceof BooleanType
462+
|
463+
e.getExtensibleType() = "sinkModel" and
464+
not ApplicationCandidatesImpl::isSink(e, _, _)
465+
or
466+
e.getExtensibleType() = "sourceModel" and
467+
not ApplicationCandidatesImpl::isSource(e, _, _) and
468+
e.getMaDOutput() = "ReturnValue"
454469
)
455470
}
456471
}

java/ql/automodel/src/AutomodelFrameworkModeCharacteristics.qll

Lines changed: 22 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -324,41 +324,55 @@ class FrameworkModeMetadataExtractor extends string {
324324
*/
325325

326326
/**
327-
* A negative characteristic that indicates that an is-style boolean method is unexploitable even if it is a sink.
327+
* A negative characteristic that indicates that parameters of an is-style boolean method should not be considered sinks,
328+
* and its return value should not be considered a source.
328329
*
329330
* A sink is highly unlikely to be exploitable if its callable's name starts with `is` and the callable has a boolean return
330331
* type (e.g. `isDirectory`). These kinds of calls normally do only checks, and appear before the proper call that does
331332
* the dangerous/interesting thing, so we want the latter to be modeled as the sink.
332333
*
333334
* TODO: this might filter too much, it's possible that methods with more than one parameter contain interesting sinks
334335
*/
335-
private class UnexploitableIsCharacteristic extends CharacteristicsImpl::NotASinkCharacteristic {
336+
private class UnexploitableIsCharacteristic extends CharacteristicsImpl::NeitherSourceNorSinkCharacteristic {
336337
UnexploitableIsCharacteristic() { this = "unexploitable (is-style boolean method)" }
337338

338339
override predicate appliesToEndpoint(Endpoint e) {
339-
not FrameworkCandidatesImpl::isSink(e, _, _) and
340340
e.getEnclosingCallable().getName().matches("is%") and
341-
e.getEnclosingCallable().getReturnType() instanceof BooleanType
341+
e.getEnclosingCallable().getReturnType() instanceof BooleanType and
342+
(
343+
e.getExtensibleType() = "sinkModel" and
344+
not FrameworkCandidatesImpl::isSink(e, _, _)
345+
or
346+
e.getExtensibleType() = "sourceModel" and
347+
not FrameworkCandidatesImpl::isSource(e, _, _) and
348+
e.getMaDOutput() = "ReturnValue"
349+
)
342350
}
343351
}
344352

345353
/**
346-
* A negative characteristic that indicates that an existence-checking boolean method is unexploitable even if it is a
347-
* sink.
354+
* A negative characteristic that indicates that parameters of an existence-checking boolean method should not be
355+
* considered sinks, and its return value should not be considered a source.
348356
*
349357
* A sink is highly unlikely to be exploitable if its callable's name is `exists` or `notExists` and the callable has a
350358
* boolean return type. These kinds of calls normally do only checks, and appear before the proper call that does the
351359
* dangerous/interesting thing, so we want the latter to be modeled as the sink.
352360
*/
353-
private class UnexploitableExistsCharacteristic extends CharacteristicsImpl::NotASinkCharacteristic {
361+
private class UnexploitableExistsCharacteristic extends CharacteristicsImpl::NeitherSourceNorSinkCharacteristic {
354362
UnexploitableExistsCharacteristic() { this = "unexploitable (existence-checking boolean method)" }
355363

356364
override predicate appliesToEndpoint(Endpoint e) {
357-
not FrameworkCandidatesImpl::isSink(e, _, _) and
358365
exists(Callable callable |
359366
callable = e.getEnclosingCallable() and
360367
callable.getName().toLowerCase() = ["exists", "notexists"] and
361368
callable.getReturnType() instanceof BooleanType
369+
|
370+
e.getExtensibleType() = "sourceModel" and
371+
not FrameworkCandidatesImpl::isSink(e, _, _)
372+
or
373+
e.getExtensibleType() = "sourceModel" and
374+
not FrameworkCandidatesImpl::isSource(e, _, _) and
375+
e.getMaDOutput() = "ReturnValue"
362376
)
363377
}
364378
}

java/ql/automodel/src/AutomodelSharedCharacteristics.qll

Lines changed: 17 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -269,6 +269,22 @@ module SharedCharacteristics<CandidateSig Candidate> {
269269
}
270270
}
271271

272+
/**
273+
* A high-confidence characteristic that indicates that an endpoint is neither a source nor a sink of any type.
274+
*/
275+
abstract class NeitherSourceNorSinkCharacteristic extends NotASinkCharacteristic, NotASourceCharacteristic {
276+
bindingset[this]
277+
NeitherSourceNorSinkCharacteristic() { any() }
278+
279+
final override predicate hasImplications(
280+
Candidate::EndpointType endpointType, boolean isPositiveIndicator, float confidence
281+
) {
282+
NotASinkCharacteristic.super.hasImplications(endpointType, isPositiveIndicator, confidence) or
283+
NotASourceCharacteristic.super
284+
.hasImplications(endpointType, isPositiveIndicator, confidence)
285+
}
286+
}
287+
272288
/**
273289
* A medium-confidence characteristic that indicates that an endpoint is unlikely to be a sink of any type. These
274290
* endpoints can be excluded from scoring at inference time, both to save time and to avoid false positives. They should
@@ -357,20 +373,10 @@ module SharedCharacteristics<CandidateSig Candidate> {
357373
/**
358374
* A negative characteristic that indicates that an endpoint was manually modeled as a neutral model.
359375
*/
360-
private class NeutralModelCharacteristic extends NotASinkCharacteristic,
361-
NotASourceCharacteristic
376+
private class NeutralModelCharacteristic extends NeitherSourceNorSinkCharacteristic
362377
{
363378
NeutralModelCharacteristic() { this = "known non-endpoint" }
364379

365-
// this is a negative characteristic for both sinks and sources
366-
override predicate hasImplications(
367-
Candidate::EndpointType endpointType, boolean isPositiveIndicator, float confidence
368-
) {
369-
NotASinkCharacteristic.super.hasImplications(endpointType, isPositiveIndicator, confidence) or
370-
NotASourceCharacteristic.super
371-
.hasImplications(endpointType, isPositiveIndicator, confidence)
372-
}
373-
374380
override predicate appliesToEndpoint(Candidate::Endpoint e) { Candidate::isNeutral(e) }
375381
}
376382

java/ql/automodel/test/AutomodelFrameworkModeExtraction/AutomodelFrameworkModeExtractCandidates.expected

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@
99
| com/github/codeql/test/PublicClass.java:13:33:13:42 | arg | Related locations: $@, $@.\nmetadata: $@, $@, $@, $@, $@, $@, $@, $@, $@, $@. | com/github/codeql/test/PublicClass.java:13:33:13:42 | arg | MethodDoc | com/github/codeql/test/PublicClass.java:13:33:13:42 | arg | ClassDoc | file://com.github.codeql.test:1:1:1:1 | com.github.codeql.test | package | file://PublicClass:1:1:1:1 | PublicClass | type | file://true:1:1:1:1 | true | subtypes | file://nonPublicStuff:1:1:1:1 | nonPublicStuff | name | file://(String):1:1:1:1 | (String) | signature | file://Argument[0]:1:1:1:1 | Argument[0] | input | file://:1:1:1:1 | | output | file://arg:1:1:1:1 | arg | parameterName | file://:1:1:1:1 | | alreadyAiModeled | file://sinkModel:1:1:1:1 | sinkModel | extensibleType |
1010
| com/github/codeql/test/PublicClass.java:22:10:22:20 | PublicClass | Related locations: $@, $@.\nmetadata: $@, $@, $@, $@, $@, $@, $@, $@, $@, $@. | com/github/codeql/test/PublicClass.java:22:10:22:20 | PublicClass | MethodDoc | com/github/codeql/test/PublicClass.java:22:10:22:20 | PublicClass | ClassDoc | file://com.github.codeql.test:1:1:1:1 | com.github.codeql.test | package | file://PublicClass:1:1:1:1 | PublicClass | type | file://true:1:1:1:1 | true | subtypes | file://PublicClass:1:1:1:1 | PublicClass | name | file://(Object):1:1:1:1 | (Object) | signature | file://:1:1:1:1 | | input | file://ReturnValue:1:1:1:1 | ReturnValue | output | file://:1:1:1:1 | | parameterName | file://:1:1:1:1 | | alreadyAiModeled | file://sourceModel:1:1:1:1 | sourceModel | extensibleType |
1111
| com/github/codeql/test/PublicClass.java:22:22:22:33 | input | Related locations: $@, $@.\nmetadata: $@, $@, $@, $@, $@, $@, $@, $@, $@, $@. | com/github/codeql/test/PublicClass.java:22:22:22:33 | input | MethodDoc | com/github/codeql/test/PublicClass.java:22:22:22:33 | input | ClassDoc | file://com.github.codeql.test:1:1:1:1 | com.github.codeql.test | package | file://PublicClass:1:1:1:1 | PublicClass | type | file://true:1:1:1:1 | true | subtypes | file://PublicClass:1:1:1:1 | PublicClass | name | file://(Object):1:1:1:1 | (Object) | signature | file://Argument[0]:1:1:1:1 | Argument[0] | input | file://:1:1:1:1 | | output | file://input:1:1:1:1 | input | parameterName | file://:1:1:1:1 | | alreadyAiModeled | file://sinkModel:1:1:1:1 | sinkModel | extensibleType |
12+
| com/github/codeql/test/PublicClass.java:26:18:26:26 | isIgnored | Related locations: $@, $@.\nmetadata: $@, $@, $@, $@, $@, $@, $@, $@, $@, $@. | com/github/codeql/test/PublicClass.java:26:18:26:26 | isIgnored | MethodDoc | com/github/codeql/test/PublicClass.java:26:18:26:26 | isIgnored | ClassDoc | file://com.github.codeql.test:1:1:1:1 | com.github.codeql.test | package | file://PublicClass:1:1:1:1 | PublicClass | type | file://true:1:1:1:1 | true | subtypes | file://isIgnored:1:1:1:1 | isIgnored | name | file://(Object):1:1:1:1 | (Object) | signature | file://:1:1:1:1 | | input | file://Parameter[this]:1:1:1:1 | Parameter[this] | output | file://this:1:1:1:1 | this | parameterName | file://:1:1:1:1 | | alreadyAiModeled | file://sourceModel:1:1:1:1 | sourceModel | extensibleType |
13+
| com/github/codeql/test/PublicClass.java:26:28:26:39 | input | Related locations: $@, $@.\nmetadata: $@, $@, $@, $@, $@, $@, $@, $@, $@, $@. | com/github/codeql/test/PublicClass.java:26:28:26:39 | input | MethodDoc | com/github/codeql/test/PublicClass.java:26:28:26:39 | input | ClassDoc | file://com.github.codeql.test:1:1:1:1 | com.github.codeql.test | package | file://PublicClass:1:1:1:1 | PublicClass | type | file://true:1:1:1:1 | true | subtypes | file://isIgnored:1:1:1:1 | isIgnored | name | file://(Object):1:1:1:1 | (Object) | signature | file://:1:1:1:1 | | input | file://Parameter[0]:1:1:1:1 | Parameter[0] | output | file://input:1:1:1:1 | input | parameterName | file://:1:1:1:1 | | alreadyAiModeled | file://sourceModel:1:1:1:1 | sourceModel | extensibleType |
1214
| com/github/codeql/test/PublicInterface.java:4:16:4:20 | stuff | Related locations: $@, $@.\nmetadata: $@, $@, $@, $@, $@, $@, $@, $@, $@, $@. | com/github/codeql/test/PublicInterface.java:4:16:4:20 | stuff | MethodDoc | com/github/codeql/test/PublicInterface.java:4:16:4:20 | stuff | ClassDoc | file://com.github.codeql.test:1:1:1:1 | com.github.codeql.test | package | file://PublicInterface:1:1:1:1 | PublicInterface | type | file://true:1:1:1:1 | true | subtypes | file://stuff:1:1:1:1 | stuff | name | file://(String):1:1:1:1 | (String) | signature | file://:1:1:1:1 | | input | file://Parameter[this]:1:1:1:1 | Parameter[this] | output | file://this:1:1:1:1 | this | parameterName | file://:1:1:1:1 | | alreadyAiModeled | file://sourceModel:1:1:1:1 | sourceModel | extensibleType |
1315
| com/github/codeql/test/PublicInterface.java:4:16:4:20 | stuff | Related locations: $@, $@.\nmetadata: $@, $@, $@, $@, $@, $@, $@, $@, $@, $@. | com/github/codeql/test/PublicInterface.java:4:16:4:20 | stuff | MethodDoc | com/github/codeql/test/PublicInterface.java:4:16:4:20 | stuff | ClassDoc | file://com.github.codeql.test:1:1:1:1 | com.github.codeql.test | package | file://PublicInterface:1:1:1:1 | PublicInterface | type | file://true:1:1:1:1 | true | subtypes | file://stuff:1:1:1:1 | stuff | name | file://(String):1:1:1:1 | (String) | signature | file://Argument[this]:1:1:1:1 | Argument[this] | input | file://:1:1:1:1 | | output | file://this:1:1:1:1 | this | parameterName | file://:1:1:1:1 | | alreadyAiModeled | file://sinkModel:1:1:1:1 | sinkModel | extensibleType |
1416
| com/github/codeql/test/PublicInterface.java:4:22:4:31 | arg | Related locations: $@, $@.\nmetadata: $@, $@, $@, $@, $@, $@, $@, $@, $@, $@. | com/github/codeql/test/PublicInterface.java:4:22:4:31 | arg | MethodDoc | com/github/codeql/test/PublicInterface.java:4:22:4:31 | arg | ClassDoc | file://com.github.codeql.test:1:1:1:1 | com.github.codeql.test | package | file://PublicInterface:1:1:1:1 | PublicInterface | type | file://true:1:1:1:1 | true | subtypes | file://stuff:1:1:1:1 | stuff | name | file://(String):1:1:1:1 | (String) | signature | file://:1:1:1:1 | | input | file://Parameter[0]:1:1:1:1 | Parameter[0] | output | file://arg:1:1:1:1 | arg | parameterName | file://:1:1:1:1 | | alreadyAiModeled | file://sourceModel:1:1:1:1 | sourceModel | extensibleType |

java/ql/automodel/test/AutomodelFrameworkModeExtraction/AutomodelFrameworkModeExtractNegativeExamples.expected

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,6 @@
1+
| com/github/codeql/test/PublicClass.java:26:18:26:26 | isIgnored | unexploitable (is-style boolean method)\nrelated locations: $@, $@.\nmetadata: $@, $@, $@, $@, $@, $@, $@, $@, $@. | com/github/codeql/test/PublicClass.java:26:18:26:26 | isIgnored | MethodDoc | com/github/codeql/test/PublicClass.java:26:18:26:26 | isIgnored | ClassDoc | file://com.github.codeql.test:1:1:1:1 | com.github.codeql.test | package | file://PublicClass:1:1:1:1 | PublicClass | type | file://true:1:1:1:1 | true | subtypes | file://isIgnored:1:1:1:1 | isIgnored | name | file://(Object):1:1:1:1 | (Object) | signature | file://:1:1:1:1 | | input | file://ReturnValue:1:1:1:1 | ReturnValue | output | file://:1:1:1:1 | | parameterName | file://sourceModel:1:1:1:1 | sourceModel | extensibleType |
2+
| com/github/codeql/test/PublicClass.java:26:18:26:26 | isIgnored | unexploitable (is-style boolean method)\nrelated locations: $@, $@.\nmetadata: $@, $@, $@, $@, $@, $@, $@, $@, $@. | com/github/codeql/test/PublicClass.java:26:18:26:26 | isIgnored | MethodDoc | com/github/codeql/test/PublicClass.java:26:18:26:26 | isIgnored | ClassDoc | file://com.github.codeql.test:1:1:1:1 | com.github.codeql.test | package | file://PublicClass:1:1:1:1 | PublicClass | type | file://true:1:1:1:1 | true | subtypes | file://isIgnored:1:1:1:1 | isIgnored | name | file://(Object):1:1:1:1 | (Object) | signature | file://Argument[this]:1:1:1:1 | Argument[this] | input | file://:1:1:1:1 | | output | file://this:1:1:1:1 | this | parameterName | file://sinkModel:1:1:1:1 | sinkModel | extensibleType |
3+
| com/github/codeql/test/PublicClass.java:26:28:26:39 | input | unexploitable (is-style boolean method)\nrelated locations: $@, $@.\nmetadata: $@, $@, $@, $@, $@, $@, $@, $@, $@. | com/github/codeql/test/PublicClass.java:26:28:26:39 | input | MethodDoc | com/github/codeql/test/PublicClass.java:26:28:26:39 | input | ClassDoc | file://com.github.codeql.test:1:1:1:1 | com.github.codeql.test | package | file://PublicClass:1:1:1:1 | PublicClass | type | file://true:1:1:1:1 | true | subtypes | file://isIgnored:1:1:1:1 | isIgnored | name | file://(Object):1:1:1:1 | (Object) | signature | file://Argument[0]:1:1:1:1 | Argument[0] | input | file://:1:1:1:1 | | output | file://input:1:1:1:1 | input | parameterName | file://sinkModel:1:1:1:1 | sinkModel | extensibleType |
14
| java/io/File.java:4:16:4:24 | compareTo | known non-endpoint\nrelated locations: $@, $@.\nmetadata: $@, $@, $@, $@, $@, $@, $@, $@, $@. | java/io/File.java:4:16:4:24 | compareTo | MethodDoc | java/io/File.java:4:16:4:24 | compareTo | ClassDoc | file://java.io:1:1:1:1 | java.io | package | file://File:1:1:1:1 | File | type | file://true:1:1:1:1 | true | subtypes | file://compareTo:1:1:1:1 | compareTo | name | file://(File):1:1:1:1 | (File) | signature | file://:1:1:1:1 | | input | file://Parameter[this]:1:1:1:1 | Parameter[this] | output | file://this:1:1:1:1 | this | parameterName | file://sourceModel:1:1:1:1 | sourceModel | extensibleType |
25
| java/io/File.java:4:16:4:24 | compareTo | known non-endpoint\nrelated locations: $@, $@.\nmetadata: $@, $@, $@, $@, $@, $@, $@, $@, $@. | java/io/File.java:4:16:4:24 | compareTo | MethodDoc | java/io/File.java:4:16:4:24 | compareTo | ClassDoc | file://java.io:1:1:1:1 | java.io | package | file://File:1:1:1:1 | File | type | file://true:1:1:1:1 | true | subtypes | file://compareTo:1:1:1:1 | compareTo | name | file://(File):1:1:1:1 | (File) | signature | file://Argument[this]:1:1:1:1 | Argument[this] | input | file://:1:1:1:1 | | output | file://this:1:1:1:1 | this | parameterName | file://sinkModel:1:1:1:1 | sinkModel | extensibleType |
36
| java/io/File.java:5:9:5:21 | pathname | known non-endpoint\nrelated locations: $@, $@.\nmetadata: $@, $@, $@, $@, $@, $@, $@, $@, $@. | java/io/File.java:5:9:5:21 | pathname | MethodDoc | java/io/File.java:5:9:5:21 | pathname | ClassDoc | file://java.io:1:1:1:1 | java.io | package | file://File:1:1:1:1 | File | type | file://true:1:1:1:1 | true | subtypes | file://compareTo:1:1:1:1 | compareTo | name | file://(File):1:1:1:1 | (File) | signature | file://:1:1:1:1 | | input | file://Parameter[0]:1:1:1:1 | Parameter[0] | output | file://pathname:1:1:1:1 | pathname | parameterName | file://sourceModel:1:1:1:1 | sourceModel | extensibleType |

java/ql/automodel/test/AutomodelFrameworkModeExtraction/com/github/codeql/test/PublicClass.java

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ public void stuff(String arg) { // `arg` is a sink candidate, `this` is a candid
55
System.out.println(arg);
66
}
77

8-
public static void staticStuff(String arg) { // `arg` is a candidate, `this` is not a candidate (static method), `arg` is not a source candidate (static methods can not be overloaded)
8+
public static void staticStuff(String arg) { // `arg` is a sink candidate, but not a source candidate (not overrideabe); `this` is not a candidate (static method)
99
System.out.println(arg);
1010
}
1111

@@ -22,4 +22,8 @@ void packagePrivateStuff(String arg) {
2222
public PublicClass(Object input) {
2323
// the `this` qualifier is not a candidate
2424
}
25+
26+
public Boolean isIgnored(Object input) { // `input` is a source candidate, but not a sink candidate (is-style method); `this` is not a candidate
27+
return false;
28+
}
2529
}

0 commit comments

Comments
 (0)