Skip to content

Commit 645f2fd

Browse files
authored
Move StdBasicRegex to shared StdRegex module; return ExploitableStringLiteral to flow layer
1 parent 4fab077 commit 645f2fd

3 files changed

Lines changed: 60 additions & 42 deletions

File tree

cpp/ql/lib/semmle/code/cpp/regex/RegexFlowConfigs.qll

Lines changed: 18 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -30,18 +30,13 @@ import cpp
3030
private import semmle.code.cpp.dataflow.new.DataFlow
3131
private import semmle.code.cpp.dataflow.new.TaintTracking
3232
private import semmle.code.cpp.regex.internal.RegexGrammar as RG
33+
import semmle.code.cpp.regex.internal.StdRegex
3334

3435
// ---------------------------------------------------------------------------
3536
// Re-exports from the flow-free grammar/flag module.
3637
// These are kept in scope for backward compatibility so that consumers can
3738
// continue to import them from `RegexFlowConfigs`.
3839
// ---------------------------------------------------------------------------
39-
/**
40-
* A `std::basic_regex` class type (or instantiation thereof, e.g. `std::regex`,
41-
* `std::wregex`). Defined in `RegexGrammar`; re-exported here.
42-
*/
43-
class StdBasicRegex = RG::StdBasicRegex;
44-
4540
/** See `RegexGrammar::TRegexGrammar`. */
4641
class TRegexGrammar = RG::TRegexGrammar;
4742

@@ -202,10 +197,24 @@ class RegexPatternSink extends DataFlow::Node {
202197

203198
// ---------------------------------------------------------------------------
204199
// Fast-path: only track literals that look regex-y.
205-
// `ExploitableStringLiteral` is defined in the flow-free `RegexGrammar`
206-
// module; re-exported here for backward compatibility.
207200
// ---------------------------------------------------------------------------
208-
class ExploitableStringLiteral = RG::ExploitableStringLiteral;
201+
/**
202+
* A string literal that is a plausible ReDoS candidate: it contains at least
203+
* one unbounded-repetition quantifier (`+`, `*`, or `{n,}`).
204+
*
205+
* This is used as the source set of the taint-tracking configuration below:
206+
* regexes without such a quantifier are not interesting for the
207+
* polynomial-ReDoS analysis, so filtering them out here is a significant
208+
* optimisation.
209+
*/
210+
class ExploitableStringLiteral extends StringLiteral {
211+
ExploitableStringLiteral() {
212+
exists(string s | s = this.getValue() |
213+
s.regexpMatch(".*[+*].*") or
214+
s.regexpMatch(".*\\{[0-9]+,[0-9]*\\}.*")
215+
)
216+
}
217+
}
209218

210219
/**
211220
* A dataflow configuration tracking string literals that reach a regex

cpp/ql/lib/semmle/code/cpp/regex/internal/RegexGrammar.qll

Lines changed: 19 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -21,42 +21,27 @@
2121
*/
2222

2323
import cpp
24+
private import semmle.code.cpp.regex.internal.StdRegex
2425

2526
// ---------------------------------------------------------------------------
2627
// Fast-path filter: only consider literals that *look* regex-y
2728
// ---------------------------------------------------------------------------
2829
/**
29-
* A string literal that is a plausible ReDoS candidate: it contains at least
30-
* one unbounded-repetition quantifier (`+`, `*`, or `{n,}`).
30+
* Holds if `s` is a plausible ReDoS-candidate string literal value: it
31+
* contains at least one unbounded-repetition quantifier (`+`, `*`, or
32+
* `{n,}`).
3133
*
32-
* This filter is applied by the flag readers below as an optimisation:
33-
* regexes without such a quantifier are not interesting for the
34-
* polynomial-ReDoS analysis, and skipping them here mirrors the source
35-
* restriction of the dataflow configuration in `RegexFlowConfigs`,
36-
* preserving analysis results exactly.
37-
*/
38-
class ExploitableStringLiteral extends StringLiteral {
39-
ExploitableStringLiteral() {
40-
exists(string s | s = this.getValue() |
41-
s.regexpMatch(".*[+*].*") or
42-
s.regexpMatch(".*\\{[0-9]+,[0-9]*\\}.*")
43-
)
44-
}
45-
}
46-
47-
// ---------------------------------------------------------------------------
48-
// std::basic_regex identification
49-
// ---------------------------------------------------------------------------
50-
/**
51-
* A `std::basic_regex` class type (or instantiation thereof, e.g. `std::regex`,
52-
* `std::wregex`).
53-
*/
54-
class StdBasicRegex extends Class {
55-
StdBasicRegex() {
56-
this.hasQualifiedName("std", "basic_regex")
57-
or
58-
this.(ClassTemplateInstantiation).getTemplate().hasQualifiedName("std", "basic_regex")
59-
}
34+
* This mirrors the source restriction of the dataflow configuration in
35+
* `RegexFlowConfigs` (whose sources are the `ExploitableStringLiteral`s),
36+
* so that the flag readers below consider exactly the same set of regexes
37+
* as the taint-tracking configuration. Inlined here as a syntactic check
38+
* to keep this module flow-free.
39+
*/
40+
private predicate isExploitableStringLiteralValue(StringLiteral s) {
41+
exists(string v | v = s.getValue() |
42+
v.regexpMatch(".*[+*].*") or
43+
v.regexpMatch(".*\\{[0-9]+,[0-9]*\\}.*")
44+
)
6045
}
6146

6247
// ---------------------------------------------------------------------------
@@ -124,13 +109,14 @@ private predicate isPatternLiteralOf(StringLiteral regex, Expr patternArg) {
124109
* This association is purely syntactic (no dataflow): the pattern literal
125110
* must appear inside the constructor/assign call's first argument.
126111
*
127-
* Restricted to `ExploitableStringLiteral` to preserve the exact set of
112+
* Restricted to string literals that look like plausible ReDoS candidates
113+
* (see `isExploitableStringLiteralValue`) to preserve the exact set of
128114
* regexes considered by the flag readers when they were routed through
129-
* the taint-tracking configuration (whose sources were the same
115+
* the taint-tracking configuration (whose sources are the same
130116
* `ExploitableStringLiteral` set).
131117
*/
132118
private Expr getConstructionFlagArg(StringLiteral regex) {
133-
regex instanceof ExploitableStringLiteral and
119+
isExploitableStringLiteralValue(regex) and
134120
(
135121
// basic_regex(pattern, flags) constructor - both named-variable and
136122
// temporary constructions are covered because the search for `regex`
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
/**
2+
* Provides identification of the `std::basic_regex` standard-library API type.
3+
*
4+
* This is shared infrastructure used by both the flow-free grammar/flag
5+
* module (`RegexGrammar`) and the dataflow layer (`RegexFlowConfigs`). It
6+
* lives in its own module so that importing it does not pull in the
7+
* dataflow libraries: both `RegexGrammar.qll` and `RegexFlowConfigs.qll`
8+
* can import it, keeping the parser/tree-view layer flow-free.
9+
*/
10+
11+
import cpp
12+
13+
/**
14+
* A `std::basic_regex` class type (or instantiation thereof, e.g. `std::regex`,
15+
* `std::wregex`).
16+
*/
17+
class StdBasicRegex extends Class {
18+
StdBasicRegex() {
19+
this.hasQualifiedName("std", "basic_regex")
20+
or
21+
this.(ClassTemplateInstantiation).getTemplate().hasQualifiedName("std", "basic_regex")
22+
}
23+
}

0 commit comments

Comments
 (0)