move predicates that depend on isReDoSCandidate into a ReDoSPruning module

This commit is contained in:
Erik Krogh Kristensen
2022-02-14 13:11:49 +01:00
parent 3248f7b423
commit dc06e9df02
12 changed files with 1272 additions and 1264 deletions

View File

@@ -14,45 +14,6 @@
import ReDoSUtilSpecific
/**
* A configuration for which parts of a regular expression should be considered relevant for
* the different predicates in `ReDoS.qll`.
* Used to adjust the computations for either superlinear or exponential backtracking.
*/
abstract class ReDoSConfiguration extends string {
bindingset[this]
ReDoSConfiguration() { any() }
/**
* Holds if `state` with the pump string `pump` is a candidate for a
* ReDoS vulnerable state.
* This is used to determine which states are considered for the prefix/suffix construction.
*/
abstract predicate isReDoSCandidate(State state, string pump);
}
/**
* Holds if repeating `pump` starting at `state` is a candidate for causing backtracking.
* No check whether a rejected suffix exists has been made.
*/
private predicate isReDoSCandidate(State state, string pump) {
any(ReDoSConfiguration conf).isReDoSCandidate(state, pump) and
(
not any(ReDoSConfiguration conf).isReDoSCandidate(epsilonSucc+(state), _)
or
epsilonSucc+(state) = state and
state =
max(State s, Location l |
s = epsilonSucc+(state) and
l = s.getRepr().getLocation() and
any(ReDoSConfiguration conf).isReDoSCandidate(s, _) and
s.getRepr() instanceof InfiniteRepetitionQuantifier
|
s order by l.getStartLine(), l.getStartColumn(), l.getEndColumn(), l.getEndLine()
)
)
}
/**
* Gets the char after `c` (from a simplified ASCII table).
*/
@@ -876,6 +837,46 @@ predicate isStartState(State state) {
exists(RegExpCaret car | state = after(car))
}
/**
* A configuration for which parts of a regular expression should be considered relevant for
* the different predicates in `ReDoS.qll`.
* Used to adjust the computations for either superlinear or exponential backtracking.
*/
abstract class ReDoSConfiguration extends string {
bindingset[this]
ReDoSConfiguration() { any() }
/**
* Holds if `state` with the pump string `pump` is a candidate for a
* ReDoS vulnerable state.
* This is used to determine which states are considered for the prefix/suffix construction.
*/
abstract predicate isReDoSCandidate(State state, string pump);
}
module ReDoSPruning {
/**
* Holds if repeating `pump` starting at `state` is a candidate for causing backtracking.
* No check whether a rejected suffix exists has been made.
*/
private predicate isReDoSCandidate(State state, string pump) {
any(ReDoSConfiguration conf).isReDoSCandidate(state, pump) and
(
not any(ReDoSConfiguration conf).isReDoSCandidate(epsilonSucc+(state), _)
or
epsilonSucc+(state) = state and
state =
max(State s, Location l |
s = epsilonSucc+(state) and
l = s.getRepr().getLocation() and
any(ReDoSConfiguration conf).isReDoSCandidate(s, _) and
s.getRepr() instanceof InfiniteRepetitionQuantifier
|
s order by l.getStartLine(), l.getStartColumn(), l.getEndColumn(), l.getEndLine()
)
)
}
/**
* Predicates for constructing a prefix string that leads to a given state.
*/
@@ -1139,30 +1140,6 @@ private module SuffixConstruction {
private string getAProcessChar() { result = any(string s | isReDoSCandidate(_, s)).charAt(_) }
}
/**
* Gets the result of backslash-escaping newlines, carriage-returns and
* backslashes in `s`.
*/
bindingset[s]
private string escape(string s) {
result =
s.replaceAll("\\", "\\\\")
.replaceAll("\n", "\\n")
.replaceAll("\r", "\\r")
.replaceAll("\t", "\\t")
}
/**
* Gets `str` with the last `i` characters moved to the front.
*
* We use this to adjust the pump string to match with the beginning of
* a RegExpTerm, so it doesn't start in the middle of a constant.
*/
bindingset[str, i]
private string rotate(string str, int i) {
result = str.suffix(str.length() - i) + str.prefix(str.length() - i)
}
/**
* Holds if `term` may cause superlinear backtracking on strings containing many repetitions of `pump`.
* Gets the shortest string that causes superlinear backtracking.
@@ -1196,3 +1173,28 @@ predicate hasReDoSResult(RegExpTerm t, string pump, State s, string prefixMsg) {
not exists(PrefixConstruction::prefix(s)) and prefixMsg = ""
)
}
}
/**
* Gets the result of backslash-escaping newlines, carriage-returns and
* backslashes in `s`.
*/
bindingset[s]
private string escape(string s) {
result =
s.replaceAll("\\", "\\\\")
.replaceAll("\n", "\\n")
.replaceAll("\r", "\\r")
.replaceAll("\t", "\\t")
}
/**
* Gets `str` with the last `i` characters moved to the front.
*
* We use this to adjust the pump string to match with the beginning of
* a RegExpTerm, so it doesn't start in the middle of a constant.
*/
bindingset[str, i]
private string rotate(string str, int i) {
result = str.suffix(str.length() - i) + str.prefix(str.length() - i)
}

View File

@@ -391,7 +391,7 @@ predicate isPumpable(State pivot, State succ, string pump) {
*/
predicate polynimalReDoS(RegExpTerm t, string pump, string prefixMsg, RegExpTerm prev) {
exists(State s, State pivot |
hasReDoSResult(t, pump, s, prefixMsg) and
ReDoSPruning::hasReDoSResult(t, pump, s, prefixMsg) and
isPumpable(pivot, s, _) and
prev = pivot.getRepr()
)

View File

@@ -11,7 +11,7 @@ class HasExpRedos extends InlineExpectationsTest {
override predicate hasActualResult(Location location, string element, string tag, string value) {
tag = "hasExpRedos" and
exists(RegExpTerm t, string pump, State s, string prefixMsg |
hasReDoSResult(t, pump, s, prefixMsg) and
ReDoSPruning::hasReDoSResult(t, pump, s, prefixMsg) and
not t.getRegex().getAMode() = "VERBOSE" and
value = "" and
location = t.getLocation() and

View File

@@ -14,45 +14,6 @@
import ReDoSUtilSpecific
/**
* A configuration for which parts of a regular expression should be considered relevant for
* the different predicates in `ReDoS.qll`.
* Used to adjust the computations for either superlinear or exponential backtracking.
*/
abstract class ReDoSConfiguration extends string {
bindingset[this]
ReDoSConfiguration() { any() }
/**
* Holds if `state` with the pump string `pump` is a candidate for a
* ReDoS vulnerable state.
* This is used to determine which states are considered for the prefix/suffix construction.
*/
abstract predicate isReDoSCandidate(State state, string pump);
}
/**
* Holds if repeating `pump` starting at `state` is a candidate for causing backtracking.
* No check whether a rejected suffix exists has been made.
*/
private predicate isReDoSCandidate(State state, string pump) {
any(ReDoSConfiguration conf).isReDoSCandidate(state, pump) and
(
not any(ReDoSConfiguration conf).isReDoSCandidate(epsilonSucc+(state), _)
or
epsilonSucc+(state) = state and
state =
max(State s, Location l |
s = epsilonSucc+(state) and
l = s.getRepr().getLocation() and
any(ReDoSConfiguration conf).isReDoSCandidate(s, _) and
s.getRepr() instanceof InfiniteRepetitionQuantifier
|
s order by l.getStartLine(), l.getStartColumn(), l.getEndColumn(), l.getEndLine()
)
)
}
/**
* Gets the char after `c` (from a simplified ASCII table).
*/
@@ -876,6 +837,46 @@ predicate isStartState(State state) {
exists(RegExpCaret car | state = after(car))
}
/**
* A configuration for which parts of a regular expression should be considered relevant for
* the different predicates in `ReDoS.qll`.
* Used to adjust the computations for either superlinear or exponential backtracking.
*/
abstract class ReDoSConfiguration extends string {
bindingset[this]
ReDoSConfiguration() { any() }
/**
* Holds if `state` with the pump string `pump` is a candidate for a
* ReDoS vulnerable state.
* This is used to determine which states are considered for the prefix/suffix construction.
*/
abstract predicate isReDoSCandidate(State state, string pump);
}
module ReDoSPruning {
/**
* Holds if repeating `pump` starting at `state` is a candidate for causing backtracking.
* No check whether a rejected suffix exists has been made.
*/
private predicate isReDoSCandidate(State state, string pump) {
any(ReDoSConfiguration conf).isReDoSCandidate(state, pump) and
(
not any(ReDoSConfiguration conf).isReDoSCandidate(epsilonSucc+(state), _)
or
epsilonSucc+(state) = state and
state =
max(State s, Location l |
s = epsilonSucc+(state) and
l = s.getRepr().getLocation() and
any(ReDoSConfiguration conf).isReDoSCandidate(s, _) and
s.getRepr() instanceof InfiniteRepetitionQuantifier
|
s order by l.getStartLine(), l.getStartColumn(), l.getEndColumn(), l.getEndLine()
)
)
}
/**
* Predicates for constructing a prefix string that leads to a given state.
*/
@@ -1139,30 +1140,6 @@ private module SuffixConstruction {
private string getAProcessChar() { result = any(string s | isReDoSCandidate(_, s)).charAt(_) }
}
/**
* Gets the result of backslash-escaping newlines, carriage-returns and
* backslashes in `s`.
*/
bindingset[s]
private string escape(string s) {
result =
s.replaceAll("\\", "\\\\")
.replaceAll("\n", "\\n")
.replaceAll("\r", "\\r")
.replaceAll("\t", "\\t")
}
/**
* Gets `str` with the last `i` characters moved to the front.
*
* We use this to adjust the pump string to match with the beginning of
* a RegExpTerm, so it doesn't start in the middle of a constant.
*/
bindingset[str, i]
private string rotate(string str, int i) {
result = str.suffix(str.length() - i) + str.prefix(str.length() - i)
}
/**
* Holds if `term` may cause superlinear backtracking on strings containing many repetitions of `pump`.
* Gets the shortest string that causes superlinear backtracking.
@@ -1196,3 +1173,28 @@ predicate hasReDoSResult(RegExpTerm t, string pump, State s, string prefixMsg) {
not exists(PrefixConstruction::prefix(s)) and prefixMsg = ""
)
}
}
/**
* Gets the result of backslash-escaping newlines, carriage-returns and
* backslashes in `s`.
*/
bindingset[s]
private string escape(string s) {
result =
s.replaceAll("\\", "\\\\")
.replaceAll("\n", "\\n")
.replaceAll("\r", "\\r")
.replaceAll("\t", "\\t")
}
/**
* Gets `str` with the last `i` characters moved to the front.
*
* We use this to adjust the pump string to match with the beginning of
* a RegExpTerm, so it doesn't start in the middle of a constant.
*/
bindingset[str, i]
private string rotate(string str, int i) {
result = str.suffix(str.length() - i) + str.prefix(str.length() - i)
}

View File

@@ -391,7 +391,7 @@ predicate isPumpable(State pivot, State succ, string pump) {
*/
predicate polynimalReDoS(RegExpTerm t, string pump, string prefixMsg, RegExpTerm prev) {
exists(State s, State pivot |
hasReDoSResult(t, pump, s, prefixMsg) and
ReDoSPruning::hasReDoSResult(t, pump, s, prefixMsg) and
isPumpable(pivot, s, _) and
prev = pivot.getRepr()
)

View File

@@ -19,7 +19,7 @@ import semmle.javascript.security.performance.ReDoSUtil
import semmle.javascript.security.performance.ExponentialBackTracking
from RegExpTerm t, string pump, State s, string prefixMsg
where hasReDoSResult(t, pump, s, prefixMsg)
where ReDoSPruning::hasReDoSResult(t, pump, s, prefixMsg)
select t,
"This part of the regular expression may cause exponential backtracking on strings " + prefixMsg +
"containing many repetitions of '" + pump + "'."

View File

@@ -14,45 +14,6 @@
import ReDoSUtilSpecific
/**
* A configuration for which parts of a regular expression should be considered relevant for
* the different predicates in `ReDoS.qll`.
* Used to adjust the computations for either superlinear or exponential backtracking.
*/
abstract class ReDoSConfiguration extends string {
bindingset[this]
ReDoSConfiguration() { any() }
/**
* Holds if `state` with the pump string `pump` is a candidate for a
* ReDoS vulnerable state.
* This is used to determine which states are considered for the prefix/suffix construction.
*/
abstract predicate isReDoSCandidate(State state, string pump);
}
/**
* Holds if repeating `pump` starting at `state` is a candidate for causing backtracking.
* No check whether a rejected suffix exists has been made.
*/
private predicate isReDoSCandidate(State state, string pump) {
any(ReDoSConfiguration conf).isReDoSCandidate(state, pump) and
(
not any(ReDoSConfiguration conf).isReDoSCandidate(epsilonSucc+(state), _)
or
epsilonSucc+(state) = state and
state =
max(State s, Location l |
s = epsilonSucc+(state) and
l = s.getRepr().getLocation() and
any(ReDoSConfiguration conf).isReDoSCandidate(s, _) and
s.getRepr() instanceof InfiniteRepetitionQuantifier
|
s order by l.getStartLine(), l.getStartColumn(), l.getEndColumn(), l.getEndLine()
)
)
}
/**
* Gets the char after `c` (from a simplified ASCII table).
*/
@@ -876,6 +837,46 @@ predicate isStartState(State state) {
exists(RegExpCaret car | state = after(car))
}
/**
* A configuration for which parts of a regular expression should be considered relevant for
* the different predicates in `ReDoS.qll`.
* Used to adjust the computations for either superlinear or exponential backtracking.
*/
abstract class ReDoSConfiguration extends string {
bindingset[this]
ReDoSConfiguration() { any() }
/**
* Holds if `state` with the pump string `pump` is a candidate for a
* ReDoS vulnerable state.
* This is used to determine which states are considered for the prefix/suffix construction.
*/
abstract predicate isReDoSCandidate(State state, string pump);
}
module ReDoSPruning {
/**
* Holds if repeating `pump` starting at `state` is a candidate for causing backtracking.
* No check whether a rejected suffix exists has been made.
*/
private predicate isReDoSCandidate(State state, string pump) {
any(ReDoSConfiguration conf).isReDoSCandidate(state, pump) and
(
not any(ReDoSConfiguration conf).isReDoSCandidate(epsilonSucc+(state), _)
or
epsilonSucc+(state) = state and
state =
max(State s, Location l |
s = epsilonSucc+(state) and
l = s.getRepr().getLocation() and
any(ReDoSConfiguration conf).isReDoSCandidate(s, _) and
s.getRepr() instanceof InfiniteRepetitionQuantifier
|
s order by l.getStartLine(), l.getStartColumn(), l.getEndColumn(), l.getEndLine()
)
)
}
/**
* Predicates for constructing a prefix string that leads to a given state.
*/
@@ -1139,30 +1140,6 @@ private module SuffixConstruction {
private string getAProcessChar() { result = any(string s | isReDoSCandidate(_, s)).charAt(_) }
}
/**
* Gets the result of backslash-escaping newlines, carriage-returns and
* backslashes in `s`.
*/
bindingset[s]
private string escape(string s) {
result =
s.replaceAll("\\", "\\\\")
.replaceAll("\n", "\\n")
.replaceAll("\r", "\\r")
.replaceAll("\t", "\\t")
}
/**
* Gets `str` with the last `i` characters moved to the front.
*
* We use this to adjust the pump string to match with the beginning of
* a RegExpTerm, so it doesn't start in the middle of a constant.
*/
bindingset[str, i]
private string rotate(string str, int i) {
result = str.suffix(str.length() - i) + str.prefix(str.length() - i)
}
/**
* Holds if `term` may cause superlinear backtracking on strings containing many repetitions of `pump`.
* Gets the shortest string that causes superlinear backtracking.
@@ -1196,3 +1173,28 @@ predicate hasReDoSResult(RegExpTerm t, string pump, State s, string prefixMsg) {
not exists(PrefixConstruction::prefix(s)) and prefixMsg = ""
)
}
}
/**
* Gets the result of backslash-escaping newlines, carriage-returns and
* backslashes in `s`.
*/
bindingset[s]
private string escape(string s) {
result =
s.replaceAll("\\", "\\\\")
.replaceAll("\n", "\\n")
.replaceAll("\r", "\\r")
.replaceAll("\t", "\\t")
}
/**
* Gets `str` with the last `i` characters moved to the front.
*
* We use this to adjust the pump string to match with the beginning of
* a RegExpTerm, so it doesn't start in the middle of a constant.
*/
bindingset[str, i]
private string rotate(string str, int i) {
result = str.suffix(str.length() - i) + str.prefix(str.length() - i)
}

View File

@@ -391,7 +391,7 @@ predicate isPumpable(State pivot, State succ, string pump) {
*/
predicate polynimalReDoS(RegExpTerm t, string pump, string prefixMsg, RegExpTerm prev) {
exists(State s, State pivot |
hasReDoSResult(t, pump, s, prefixMsg) and
ReDoSPruning::hasReDoSResult(t, pump, s, prefixMsg) and
isPumpable(pivot, s, _) and
prev = pivot.getRepr()
)

View File

@@ -18,7 +18,7 @@ import semmle.python.security.performance.ExponentialBackTracking
from RegExpTerm t, string pump, State s, string prefixMsg
where
hasReDoSResult(t, pump, s, prefixMsg) and
ReDoSPruning::hasReDoSResult(t, pump, s, prefixMsg) and
// exclude verbose mode regexes for now
not t.getRegex().getAMode() = "VERBOSE"
select t,

View File

@@ -14,45 +14,6 @@
import ReDoSUtilSpecific
/**
* A configuration for which parts of a regular expression should be considered relevant for
* the different predicates in `ReDoS.qll`.
* Used to adjust the computations for either superlinear or exponential backtracking.
*/
abstract class ReDoSConfiguration extends string {
bindingset[this]
ReDoSConfiguration() { any() }
/**
* Holds if `state` with the pump string `pump` is a candidate for a
* ReDoS vulnerable state.
* This is used to determine which states are considered for the prefix/suffix construction.
*/
abstract predicate isReDoSCandidate(State state, string pump);
}
/**
* Holds if repeating `pump` starting at `state` is a candidate for causing backtracking.
* No check whether a rejected suffix exists has been made.
*/
private predicate isReDoSCandidate(State state, string pump) {
any(ReDoSConfiguration conf).isReDoSCandidate(state, pump) and
(
not any(ReDoSConfiguration conf).isReDoSCandidate(epsilonSucc+(state), _)
or
epsilonSucc+(state) = state and
state =
max(State s, Location l |
s = epsilonSucc+(state) and
l = s.getRepr().getLocation() and
any(ReDoSConfiguration conf).isReDoSCandidate(s, _) and
s.getRepr() instanceof InfiniteRepetitionQuantifier
|
s order by l.getStartLine(), l.getStartColumn(), l.getEndColumn(), l.getEndLine()
)
)
}
/**
* Gets the char after `c` (from a simplified ASCII table).
*/
@@ -876,6 +837,46 @@ predicate isStartState(State state) {
exists(RegExpCaret car | state = after(car))
}
/**
* A configuration for which parts of a regular expression should be considered relevant for
* the different predicates in `ReDoS.qll`.
* Used to adjust the computations for either superlinear or exponential backtracking.
*/
abstract class ReDoSConfiguration extends string {
bindingset[this]
ReDoSConfiguration() { any() }
/**
* Holds if `state` with the pump string `pump` is a candidate for a
* ReDoS vulnerable state.
* This is used to determine which states are considered for the prefix/suffix construction.
*/
abstract predicate isReDoSCandidate(State state, string pump);
}
module ReDoSPruning {
/**
* Holds if repeating `pump` starting at `state` is a candidate for causing backtracking.
* No check whether a rejected suffix exists has been made.
*/
private predicate isReDoSCandidate(State state, string pump) {
any(ReDoSConfiguration conf).isReDoSCandidate(state, pump) and
(
not any(ReDoSConfiguration conf).isReDoSCandidate(epsilonSucc+(state), _)
or
epsilonSucc+(state) = state and
state =
max(State s, Location l |
s = epsilonSucc+(state) and
l = s.getRepr().getLocation() and
any(ReDoSConfiguration conf).isReDoSCandidate(s, _) and
s.getRepr() instanceof InfiniteRepetitionQuantifier
|
s order by l.getStartLine(), l.getStartColumn(), l.getEndColumn(), l.getEndLine()
)
)
}
/**
* Predicates for constructing a prefix string that leads to a given state.
*/
@@ -1139,30 +1140,6 @@ private module SuffixConstruction {
private string getAProcessChar() { result = any(string s | isReDoSCandidate(_, s)).charAt(_) }
}
/**
* Gets the result of backslash-escaping newlines, carriage-returns and
* backslashes in `s`.
*/
bindingset[s]
private string escape(string s) {
result =
s.replaceAll("\\", "\\\\")
.replaceAll("\n", "\\n")
.replaceAll("\r", "\\r")
.replaceAll("\t", "\\t")
}
/**
* Gets `str` with the last `i` characters moved to the front.
*
* We use this to adjust the pump string to match with the beginning of
* a RegExpTerm, so it doesn't start in the middle of a constant.
*/
bindingset[str, i]
private string rotate(string str, int i) {
result = str.suffix(str.length() - i) + str.prefix(str.length() - i)
}
/**
* Holds if `term` may cause superlinear backtracking on strings containing many repetitions of `pump`.
* Gets the shortest string that causes superlinear backtracking.
@@ -1196,3 +1173,28 @@ predicate hasReDoSResult(RegExpTerm t, string pump, State s, string prefixMsg) {
not exists(PrefixConstruction::prefix(s)) and prefixMsg = ""
)
}
}
/**
* Gets the result of backslash-escaping newlines, carriage-returns and
* backslashes in `s`.
*/
bindingset[s]
private string escape(string s) {
result =
s.replaceAll("\\", "\\\\")
.replaceAll("\n", "\\n")
.replaceAll("\r", "\\r")
.replaceAll("\t", "\\t")
}
/**
* Gets `str` with the last `i` characters moved to the front.
*
* We use this to adjust the pump string to match with the beginning of
* a RegExpTerm, so it doesn't start in the middle of a constant.
*/
bindingset[str, i]
private string rotate(string str, int i) {
result = str.suffix(str.length() - i) + str.prefix(str.length() - i)
}

View File

@@ -391,7 +391,7 @@ predicate isPumpable(State pivot, State succ, string pump) {
*/
predicate polynimalReDoS(RegExpTerm t, string pump, string prefixMsg, RegExpTerm prev) {
exists(State s, State pivot |
hasReDoSResult(t, pump, s, prefixMsg) and
ReDoSPruning::hasReDoSResult(t, pump, s, prefixMsg) and
isPumpable(pivot, s, _) and
prev = pivot.getRepr()
)

View File

@@ -19,7 +19,7 @@ import codeql.ruby.security.performance.ReDoSUtil
import codeql.ruby.Regexp
from RegExpTerm t, string pump, State s, string prefixMsg
where hasReDoSResult(t, pump, s, prefixMsg)
where ReDoSPruning::hasReDoSResult(t, pump, s, prefixMsg)
select t,
"This part of the regular expression may cause exponential backtracking on strings " + prefixMsg +
"containing many repetitions of '" + pump + "'."