diff --git a/cpp/ql/lib/semmle/code/cpp/regex/RegexFlowConfigs.qll b/cpp/ql/lib/semmle/code/cpp/regex/RegexFlowConfigs.qll index 63052408ffb..9a3ce4ed5cf 100644 --- a/cpp/ql/lib/semmle/code/cpp/regex/RegexFlowConfigs.qll +++ b/cpp/ql/lib/semmle/code/cpp/regex/RegexFlowConfigs.qll @@ -30,18 +30,13 @@ import cpp private import semmle.code.cpp.dataflow.new.DataFlow private import semmle.code.cpp.dataflow.new.TaintTracking private import semmle.code.cpp.regex.internal.RegexGrammar as RG +import semmle.code.cpp.regex.internal.StdRegex // --------------------------------------------------------------------------- // Re-exports from the flow-free grammar/flag module. // These are kept in scope for backward compatibility so that consumers can // continue to import them from `RegexFlowConfigs`. // --------------------------------------------------------------------------- -/** - * A `std::basic_regex` class type (or instantiation thereof, e.g. `std::regex`, - * `std::wregex`). Defined in `RegexGrammar`; re-exported here. - */ -class StdBasicRegex = RG::StdBasicRegex; - /** See `RegexGrammar::TRegexGrammar`. */ class TRegexGrammar = RG::TRegexGrammar; @@ -202,10 +197,24 @@ class RegexPatternSink extends DataFlow::Node { // --------------------------------------------------------------------------- // Fast-path: only track literals that look regex-y. -// `ExploitableStringLiteral` is defined in the flow-free `RegexGrammar` -// module; re-exported here for backward compatibility. // --------------------------------------------------------------------------- -class ExploitableStringLiteral = RG::ExploitableStringLiteral; +/** + * A string literal that is a plausible ReDoS candidate: it contains at least + * one unbounded-repetition quantifier (`+`, `*`, or `{n,}`). + * + * This is used as the source set of the taint-tracking configuration below: + * regexes without such a quantifier are not interesting for the + * polynomial-ReDoS analysis, so filtering them out here is a significant + * optimisation. + */ +class ExploitableStringLiteral extends StringLiteral { + ExploitableStringLiteral() { + exists(string s | s = this.getValue() | + s.regexpMatch(".*[+*].*") or + s.regexpMatch(".*\\{[0-9]+,[0-9]*\\}.*") + ) + } +} /** * A dataflow configuration tracking string literals that reach a regex diff --git a/cpp/ql/lib/semmle/code/cpp/regex/internal/RegexGrammar.qll b/cpp/ql/lib/semmle/code/cpp/regex/internal/RegexGrammar.qll index 0fa7df51342..60557c7b6f8 100644 --- a/cpp/ql/lib/semmle/code/cpp/regex/internal/RegexGrammar.qll +++ b/cpp/ql/lib/semmle/code/cpp/regex/internal/RegexGrammar.qll @@ -21,42 +21,27 @@ */ import cpp +private import semmle.code.cpp.regex.internal.StdRegex // --------------------------------------------------------------------------- // Fast-path filter: only consider literals that *look* regex-y // --------------------------------------------------------------------------- /** - * A string literal that is a plausible ReDoS candidate: it contains at least - * one unbounded-repetition quantifier (`+`, `*`, or `{n,}`). + * Holds if `s` is a plausible ReDoS-candidate string literal value: it + * contains at least one unbounded-repetition quantifier (`+`, `*`, or + * `{n,}`). * - * This filter is applied by the flag readers below as an optimisation: - * regexes without such a quantifier are not interesting for the - * polynomial-ReDoS analysis, and skipping them here mirrors the source - * restriction of the dataflow configuration in `RegexFlowConfigs`, - * preserving analysis results exactly. + * This mirrors the source restriction of the dataflow configuration in + * `RegexFlowConfigs` (whose sources are the `ExploitableStringLiteral`s), + * so that the flag readers below consider exactly the same set of regexes + * as the taint-tracking configuration. Inlined here as a syntactic check + * to keep this module flow-free. */ -class ExploitableStringLiteral extends StringLiteral { - ExploitableStringLiteral() { - exists(string s | s = this.getValue() | - s.regexpMatch(".*[+*].*") or - s.regexpMatch(".*\\{[0-9]+,[0-9]*\\}.*") - ) - } -} - -// --------------------------------------------------------------------------- -// std::basic_regex identification -// --------------------------------------------------------------------------- -/** - * A `std::basic_regex` class type (or instantiation thereof, e.g. `std::regex`, - * `std::wregex`). - */ -class StdBasicRegex extends Class { - StdBasicRegex() { - this.hasQualifiedName("std", "basic_regex") - or - this.(ClassTemplateInstantiation).getTemplate().hasQualifiedName("std", "basic_regex") - } +private predicate isExploitableStringLiteralValue(StringLiteral s) { + exists(string v | v = s.getValue() | + v.regexpMatch(".*[+*].*") or + v.regexpMatch(".*\\{[0-9]+,[0-9]*\\}.*") + ) } // --------------------------------------------------------------------------- @@ -124,13 +109,14 @@ private predicate isPatternLiteralOf(StringLiteral regex, Expr patternArg) { * This association is purely syntactic (no dataflow): the pattern literal * must appear inside the constructor/assign call's first argument. * - * Restricted to `ExploitableStringLiteral` to preserve the exact set of + * Restricted to string literals that look like plausible ReDoS candidates + * (see `isExploitableStringLiteralValue`) to preserve the exact set of * regexes considered by the flag readers when they were routed through - * the taint-tracking configuration (whose sources were the same + * the taint-tracking configuration (whose sources are the same * `ExploitableStringLiteral` set). */ private Expr getConstructionFlagArg(StringLiteral regex) { - regex instanceof ExploitableStringLiteral and + isExploitableStringLiteralValue(regex) and ( // basic_regex(pattern, flags) constructor - both named-variable and // temporary constructions are covered because the search for `regex` diff --git a/cpp/ql/lib/semmle/code/cpp/regex/internal/StdRegex.qll b/cpp/ql/lib/semmle/code/cpp/regex/internal/StdRegex.qll new file mode 100644 index 00000000000..366c45bd7a9 --- /dev/null +++ b/cpp/ql/lib/semmle/code/cpp/regex/internal/StdRegex.qll @@ -0,0 +1,23 @@ +/** + * Provides identification of the `std::basic_regex` standard-library API type. + * + * This is shared infrastructure used by both the flow-free grammar/flag + * module (`RegexGrammar`) and the dataflow layer (`RegexFlowConfigs`). It + * lives in its own module so that importing it does not pull in the + * dataflow libraries: both `RegexGrammar.qll` and `RegexFlowConfigs.qll` + * can import it, keeping the parser/tree-view layer flow-free. + */ + +import cpp + +/** + * A `std::basic_regex` class type (or instantiation thereof, e.g. `std::regex`, + * `std::wregex`). + */ +class StdBasicRegex extends Class { + StdBasicRegex() { + this.hasQualifiedName("std", "basic_regex") + or + this.(ClassTemplateInstantiation).getTemplate().hasQualifiedName("std", "basic_regex") + } +}