mirror of
https://github.com/github/codeql.git
synced 2025-12-24 04:36:35 +01:00
move predicates that depend on isReDoSCandidate into a ReDoSPruning module
This commit is contained in:
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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()
|
||||
)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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()
|
||||
)
|
||||
|
||||
@@ -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 + "'."
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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()
|
||||
)
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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()
|
||||
)
|
||||
|
||||
@@ -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 + "'."
|
||||
|
||||
Reference in New Issue
Block a user