Skip to content

Commit a812e4a

Browse files
committed
Refactor creating barriers with annotations
1 parent e5bd62d commit a812e4a

5 files changed

Lines changed: 25 additions & 30 deletions

File tree

java/ql/lib/semmle/code/java/Concepts.qll

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,12 @@ private module Frameworks {
2525
*
2626
* These are either method calls, which return `true` when there is a match, or
2727
* annotations, which are considered to match if they are present.
28+
*
29+
* To use a `RegexMatch` as a barrier or sanitizer, instantiate
30+
* `DataFlow::BarrierGuard`. Method-call matches act as ordinary control-flow
31+
* barrier guards, while annotation matches are treated as direct barriers,
32+
* since an annotation does not dominate the expression it constrains;
33+
* `DataFlow::BarrierGuard` handles both, so callers need not distinguish the two.
2834
*/
2935
class RegexMatch extends Expr instanceof RegexMatch::Range {
3036
/** Gets the expression for the regex being executed by this node. */

java/ql/lib/semmle/code/java/dataflow/internal/DataFlowUtil.qll

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -418,7 +418,17 @@ module BarrierGuard<guardChecksSig/3 guardChecks> {
418418
}
419419

420420
/** Gets a node that is safely guarded by the given guard check. */
421-
Node getABarrierNode() { result = BarrierGuardValue<guardChecks0/3>::getABarrierNode() }
421+
Node getABarrierNode() {
422+
result = BarrierGuardValue<guardChecks0/3>::getABarrierNode()
423+
or
424+
// An annotation doesn't dominate the expression it constrains, so the
425+
// barrier-guard machinery above never yields a node for it; treat it as a
426+
// direct barrier instead. Annotations are always present, so we only
427+
// consider the `true` branch.
428+
exists(Guard g, Expr e |
429+
g instanceof Annotation and guardChecks(g, e, true) and result.asExpr() = e
430+
)
431+
}
422432
}
423433

424434
bindingset[this]

java/ql/lib/semmle/code/java/security/LogInjection.qll

Lines changed: 0 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -90,13 +90,6 @@ private predicate logInjectionSanitizer(Expr e) {
9090
target.getStringValue() = ["\n", "\r", "\\n", "\\r", "\\R"]
9191
)
9292
)
93-
or
94-
exists(RegexMatch rm, CompileTimeConstantExpr target |
95-
rm instanceof Annotation and
96-
e = rm.getASanitizedExpr() and
97-
target = rm.getRegex() and
98-
regexPreventsLogInjection(target.getStringValue(), true)
99-
)
10093
}
10194

10295
/**
@@ -113,7 +106,6 @@ private predicate logInjectionGuard(Guard g, Expr e, boolean branch) {
113106
or
114107
exists(RegexMatch rm, CompileTimeConstantExpr target |
115108
rm = g and
116-
not rm instanceof Annotation and
117109
target = rm.getRegex() and
118110
e = rm.getASanitizedExpr()
119111
|

java/ql/lib/semmle/code/java/security/PathSanitizer.qll

Lines changed: 2 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ private import semmle.code.java.dataflow.SSA
1010
private import semmle.code.java.frameworks.kotlin.IO
1111
private import semmle.code.java.frameworks.kotlin.Text
1212
private import semmle.code.java.dataflow.Nullness
13+
private import semmle.code.java.security.Sanitizers
1314

1415
/** A sanitizer that protects against path injection vulnerabilities. */
1516
abstract class PathInjectionSanitizer extends DataFlow::Node { }
@@ -470,12 +471,7 @@ private class DirectoryCharactersGuard extends PathGuard {
470471
Expr checkedExpr;
471472
boolean branch;
472473

473-
DirectoryCharactersGuard() {
474-
// Annotations are handled directly as barriers in `DirectoryCharactersSanitizer`,
475-
// since they don't dominate the sanitized expression and so can't act as barrier guards.
476-
not this instanceof Annotation and
477-
isMatchesCall(this, checkedExpr, branch)
478-
}
474+
DirectoryCharactersGuard() { isMatchesCall(this, checkedExpr, branch) }
479475

480476
override Expr getCheckedExpr() { result = checkedExpr }
481477

@@ -501,12 +497,5 @@ private class DirectoryCharactersSanitizer extends PathInjectionSanitizer {
501497
this.asExpr() instanceof ReplaceDirectoryCharactersSanitizer
502498
or
503499
this = DataFlow::BarrierGuard<directoryCharactersGuard/3>::getABarrierNode()
504-
or
505-
// Annotations don't fit into the model of barrier guards because the
506-
// annotation doesn't dominate the sanitized expression, so we instead
507-
// treat them as barriers directly.
508-
exists(RegexMatch rm | rm instanceof Annotation and isMatchesCall(rm, _, true) |
509-
this.asExpr() = rm.getString()
510-
)
511500
}
512501
}

java/ql/lib/semmle/code/java/security/Sanitizers.qll

Lines changed: 6 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -37,11 +37,14 @@ class SimpleTypeSanitizer extends DataFlow::Node {
3737
*
3838
* This is overapproximate: we do not attempt to reason about the correctness of the regexp.
3939
*
40-
* Use this if you want to define a derived `DataFlow::BarrierGuard` without
41-
* make the type recursive. Otherwise use `RegexpCheckBarrier`.
40+
* This holds for both method-call and annotation regular-expression matches.
41+
* Method-call matches yield barrier nodes via ordinary control-flow dominance,
42+
* while annotation matches are treated as direct barriers by
43+
* `DataFlow::BarrierGuard`, since an annotation does not dominate the
44+
* expression it constrains.
4245
*/
4346
predicate regexpMatchGuardChecks(Guard guard, Expr e, boolean branch) {
44-
exists(RegexMatch rm | not rm instanceof Annotation |
47+
exists(RegexMatch rm |
4548
guard = rm and
4649
e = rm.getASanitizedExpr() and
4750
branch = true
@@ -56,11 +59,6 @@ predicate regexpMatchGuardChecks(Guard guard, Expr e, boolean branch) {
5659
class RegexpCheckBarrier extends DataFlow::Node {
5760
RegexpCheckBarrier() {
5861
this = DataFlow::BarrierGuard<regexpMatchGuardChecks/3>::getABarrierNode()
59-
or
60-
// Annotations don't fit into the model of barrier guards because the
61-
// annotation doesn't dominate the sanitized expression, so we instead
62-
// treat them as barriers directly.
63-
exists(RegexMatch rm | rm instanceof Annotation | this.asExpr() = rm.getString())
6462
}
6563
}
6664

0 commit comments

Comments
 (0)