Skip to content

Commit 3be4a86

Browse files
committed
make ReDoSPruning into a parameterized module
1 parent dc06e9d commit 3be4a86

File tree

16 files changed

+296
-328
lines changed

16 files changed

+296
-328
lines changed

java/ql/lib/semmle/code/java/security/performance/ExponentialBackTracking.qll

Lines changed: 2 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -364,11 +364,5 @@ private predicate isPumpable(State fork, string w) {
364364
)
365365
}
366366

367-
/**
368-
* An instantiation of `ReDoSConfiguration` for exponential backtracking.
369-
*/
370-
class ExponentialReDoSConfiguration extends ReDoSConfiguration {
371-
ExponentialReDoSConfiguration() { this = "ExponentialReDoSConfiguration" }
372-
373-
override predicate isReDoSCandidate(State state, string pump) { isPumpable(state, pump) }
374-
}
367+
/** Holds if `state` has exponential ReDoS */
368+
predicate hasReDoSResult = ReDoSPruning<isPumpable/2>::hasReDoSResult/4;

java/ql/lib/semmle/code/java/security/performance/ReDoSUtil.qll

Lines changed: 65 additions & 63 deletions
Original file line numberDiff line numberDiff line change
@@ -838,38 +838,32 @@ predicate isStartState(State state) {
838838
}
839839

840840
/**
841-
* A configuration for which parts of a regular expression should be considered relevant for
842-
* the different predicates in `ReDoS.qll`.
843-
* Used to adjust the computations for either superlinear or exponential backtracking.
841+
* Holds if `state` is a candidate for ReDoS with string `pump`.
844842
*/
845-
abstract class ReDoSConfiguration extends string {
846-
bindingset[this]
847-
ReDoSConfiguration() { any() }
843+
signature predicate isCandidateSig(State state, string pump);
848844

849-
/**
850-
* Holds if `state` with the pump string `pump` is a candidate for a
851-
* ReDoS vulnerable state.
852-
* This is used to determine which states are considered for the prefix/suffix construction.
853-
*/
854-
abstract predicate isReDoSCandidate(State state, string pump);
855-
}
856-
857-
module ReDoSPruning {
845+
/**
846+
* A module for pruning candidate ReDoS states.
847+
* The candidates are specified by the `isCandidate` signature predicate.
848+
* The candidates are checked for rejecting suffixes and deduplicated,
849+
* and the resulting ReDoS states are read by the `hasReDoSResult` predicate.
850+
*/
851+
module ReDoSPruning<isCandidateSig/2 isCandidate> {
858852
/**
859853
* Holds if repeating `pump` starting at `state` is a candidate for causing backtracking.
860854
* No check whether a rejected suffix exists has been made.
861855
*/
862856
private predicate isReDoSCandidate(State state, string pump) {
863-
any(ReDoSConfiguration conf).isReDoSCandidate(state, pump) and
857+
isCandidate(state, pump) and
864858
(
865-
not any(ReDoSConfiguration conf).isReDoSCandidate(epsilonSucc+(state), _)
859+
not isCandidate(epsilonSucc+(state), _)
866860
or
867861
epsilonSucc+(state) = state and
868862
state =
869863
max(State s, Location l |
870864
s = epsilonSucc+(state) and
871865
l = s.getRepr().getLocation() and
872-
any(ReDoSConfiguration conf).isReDoSCandidate(s, _) and
866+
isCandidate(s, _) and
873867
s.getRepr() instanceof InfiniteRepetitionQuantifier
874868
|
875869
s order by l.getStartLine(), l.getStartColumn(), l.getEndColumn(), l.getEndLine()
@@ -887,8 +881,11 @@ module ReDoSPruning {
887881
private predicate lastStartState(State state) {
888882
exists(RegExpRoot root |
889883
state =
890-
max(StateInPumpableRegexp s, Location l |
891-
isStartState(s) and getRoot(s.getRepr()) = root and l = s.getRepr().getLocation()
884+
max(State s, Location l |
885+
s = stateInPumpableRegexp() and
886+
isStartState(s) and
887+
getRoot(s.getRepr()) = root and
888+
l = s.getRepr().getLocation()
892889
|
893890
s
894891
order by
@@ -957,14 +954,10 @@ module ReDoSPruning {
957954
min(string c | delta(prev, any(InputSymbol symbol | c = intersect(Any(), symbol)), next))
958955
}
959956

960-
/**
961-
* A state within a regular expression that has a pumpable state.
962-
*/
963-
class StateInPumpableRegexp extends State {
964-
pragma[noinline]
965-
StateInPumpableRegexp() {
966-
exists(State s | isReDoSCandidate(s, _) | getRoot(s.getRepr()) = getRoot(this.getRepr()))
967-
}
957+
/** Gets a state within a regular expression that has a pumpable state. */
958+
pragma[noinline]
959+
State stateInPumpableRegexp() {
960+
exists(State s | isReDoSCandidate(s, _) | getRoot(s.getRepr()) = getRoot(result.getRepr()))
968961
}
969962
}
970963

@@ -1002,26 +995,32 @@ module ReDoSPruning {
1002995
* This predicate might find impossible suffixes when searching for suffixes of length > 1, which can cause FPs.
1003996
*/
1004997
pragma[noinline]
1005-
private predicate isLikelyRejectable(StateInPumpableRegexp s) {
1006-
// exists a reject edge with some char.
1007-
hasRejectEdge(s)
1008-
or
1009-
hasEdgeToLikelyRejectable(s)
1010-
or
1011-
// stopping here is rejection
1012-
isRejectState(s)
998+
private predicate isLikelyRejectable(State s) {
999+
s = stateInPumpableRegexp() and
1000+
(
1001+
// exists a reject edge with some char.
1002+
hasRejectEdge(s)
1003+
or
1004+
hasEdgeToLikelyRejectable(s)
1005+
or
1006+
// stopping here is rejection
1007+
isRejectState(s)
1008+
)
10131009
}
10141010

10151011
/**
10161012
* Holds if `s` is not an accept state, and there is no epsilon transition to an accept state.
10171013
*/
1018-
predicate isRejectState(StateInPumpableRegexp s) { not epsilonSucc*(s) = Accept(_) }
1014+
predicate isRejectState(State s) {
1015+
s = stateInPumpableRegexp() and not epsilonSucc*(s) = Accept(_)
1016+
}
10191017

10201018
/**
10211019
* Holds if there is likely a non-empty suffix leading to rejection starting in `s`.
10221020
*/
10231021
pragma[noopt]
1024-
predicate hasEdgeToLikelyRejectable(StateInPumpableRegexp s) {
1022+
predicate hasEdgeToLikelyRejectable(State s) {
1023+
s = stateInPumpableRegexp() and
10251024
// all edges (at least one) with some char leads to another state that is rejectable.
10261025
// the `next` states might not share a common suffix, which can cause FPs.
10271026
exists(string char | char = hasEdgeToLikelyRejectableHelper(s) |
@@ -1036,7 +1035,8 @@ module ReDoSPruning {
10361035
* and `s` has not been found to be rejectable by `hasRejectEdge` or `isRejectState`.
10371036
*/
10381037
pragma[noinline]
1039-
private string hasEdgeToLikelyRejectableHelper(StateInPumpableRegexp s) {
1038+
private string hasEdgeToLikelyRejectableHelper(State s) {
1039+
s = stateInPumpableRegexp() and
10401040
not hasRejectEdge(s) and
10411041
not isRejectState(s) and
10421042
deltaClosedChar(s, result, _)
@@ -1047,7 +1047,9 @@ module ReDoSPruning {
10471047
* along epsilon edges, such that there is a transition from
10481048
* `prev` to `next` that the character symbol `char`.
10491049
*/
1050-
predicate deltaClosedChar(StateInPumpableRegexp prev, string char, StateInPumpableRegexp next) {
1050+
predicate deltaClosedChar(State prev, string char, State next) {
1051+
prev = stateInPumpableRegexp() and
1052+
next = stateInPumpableRegexp() and
10511053
deltaClosed(prev, getAnInputSymbolMatchingRelevant(char), next)
10521054
}
10531055

@@ -1148,7 +1150,7 @@ module ReDoSPruning {
11481150
exists(int i, string c | s = Match(term, i) |
11491151
c =
11501152
min(string w |
1151-
any(ReDoSConfiguration conf).isReDoSCandidate(s, w) and
1153+
isCandidate(s, w) and
11521154
SuffixConstruction::reachesOnlyRejectableSuffixes(s, w)
11531155
|
11541156
w order by w.length(), w
@@ -1173,28 +1175,28 @@ module ReDoSPruning {
11731175
not exists(PrefixConstruction::prefix(s)) and prefixMsg = ""
11741176
)
11751177
}
1176-
}
11771178

1178-
/**
1179-
* Gets the result of backslash-escaping newlines, carriage-returns and
1180-
* backslashes in `s`.
1181-
*/
1182-
bindingset[s]
1183-
private string escape(string s) {
1184-
result =
1185-
s.replaceAll("\\", "\\\\")
1186-
.replaceAll("\n", "\\n")
1187-
.replaceAll("\r", "\\r")
1188-
.replaceAll("\t", "\\t")
1189-
}
1179+
/**
1180+
* Gets the result of backslash-escaping newlines, carriage-returns and
1181+
* backslashes in `s`.
1182+
*/
1183+
bindingset[s]
1184+
private string escape(string s) {
1185+
result =
1186+
s.replaceAll("\\", "\\\\")
1187+
.replaceAll("\n", "\\n")
1188+
.replaceAll("\r", "\\r")
1189+
.replaceAll("\t", "\\t")
1190+
}
11901191

1191-
/**
1192-
* Gets `str` with the last `i` characters moved to the front.
1193-
*
1194-
* We use this to adjust the pump string to match with the beginning of
1195-
* a RegExpTerm, so it doesn't start in the middle of a constant.
1196-
*/
1197-
bindingset[str, i]
1198-
private string rotate(string str, int i) {
1199-
result = str.suffix(str.length() - i) + str.prefix(str.length() - i)
1192+
/**
1193+
* Gets `str` with the last `i` characters moved to the front.
1194+
*
1195+
* We use this to adjust the pump string to match with the beginning of
1196+
* a RegExpTerm, so it doesn't start in the middle of a constant.
1197+
*/
1198+
bindingset[str, i]
1199+
private string rotate(string str, int i) {
1200+
result = str.suffix(str.length() - i) + str.prefix(str.length() - i)
1201+
}
12001202
}

java/ql/lib/semmle/code/java/security/performance/SuperlinearBackTracking.qll

Lines changed: 6 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -42,15 +42,6 @@ import ReDoSUtil
4242
* It also doesn't find all transitions in the product automaton, which can cause false negatives.
4343
*/
4444

45-
/**
46-
* An instantiaion of `ReDoSConfiguration` for superlinear ReDoS.
47-
*/
48-
class SuperLinearReDoSConfiguration extends ReDoSConfiguration {
49-
SuperLinearReDoSConfiguration() { this = "SuperLinearReDoSConfiguration" }
50-
51-
override predicate isReDoSCandidate(State state, string pump) { isPumpable(_, state, pump) }
52-
}
53-
5445
/**
5546
* Gets any root (start) state of a regular expression.
5647
*/
@@ -386,12 +377,17 @@ predicate isPumpable(State pivot, State succ, string pump) {
386377
)
387378
}
388379

380+
/**
381+
* Holds if states starting in `state` can have polynomial backtracking with the string `pump`.
382+
*/
383+
predicate isReDoSCandidate(State state, string pump) { isPumpable(_, state, pump) }
384+
389385
/**
390386
* Holds if repetitions of `pump` at `t` will cause polynomial backtracking.
391387
*/
392388
predicate polynimalReDoS(RegExpTerm t, string pump, string prefixMsg, RegExpTerm prev) {
393389
exists(State s, State pivot |
394-
ReDoSPruning::hasReDoSResult(t, pump, s, prefixMsg) and
390+
ReDoSPruning<isReDoSCandidate/2>::hasReDoSResult(t, pump, s, prefixMsg) and
395391
isPumpable(pivot, s, _) and
396392
prev = pivot.getRepr()
397393
)

java/ql/test/query-tests/security/CWE-730/ReDoS.ql

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ class HasExpRedos extends InlineExpectationsTest {
1111
override predicate hasActualResult(Location location, string element, string tag, string value) {
1212
tag = "hasExpRedos" and
1313
exists(RegExpTerm t, string pump, State s, string prefixMsg |
14-
ReDoSPruning::hasReDoSResult(t, pump, s, prefixMsg) and
14+
hasReDoSResult(t, pump, s, prefixMsg) and
1515
not t.getRegex().getAMode() = "VERBOSE" and
1616
value = "" and
1717
location = t.getLocation() and

javascript/ql/lib/semmle/javascript/security/performance/ExponentialBackTracking.qll

Lines changed: 2 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -364,11 +364,5 @@ private predicate isPumpable(State fork, string w) {
364364
)
365365
}
366366

367-
/**
368-
* An instantiation of `ReDoSConfiguration` for exponential backtracking.
369-
*/
370-
class ExponentialReDoSConfiguration extends ReDoSConfiguration {
371-
ExponentialReDoSConfiguration() { this = "ExponentialReDoSConfiguration" }
372-
373-
override predicate isReDoSCandidate(State state, string pump) { isPumpable(state, pump) }
374-
}
367+
/** Holds if `state` has exponential ReDoS */
368+
predicate hasReDoSResult = ReDoSPruning<isPumpable/2>::hasReDoSResult/4;

0 commit comments

Comments
 (0)