Skip to content

Commit bc5cecf

Browse files
authored
Add end-to-end ERE ReDoS query tests
1 parent 9a3613e commit bc5cecf

4 files changed

Lines changed: 157 additions & 43 deletions

File tree

cpp/ql/test/query-tests/Security/CWE/CWE-1333-ReDoS/ReDoS.expected

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -32,5 +32,7 @@
3232
| test.cpp:167:28:167:28 | b | This part of the regular expression may cause exponential backtracking on strings starting with 'W' and containing many repetitions of 'bW'. |
3333
| test.cpp:176:24:176:25 | ab | This part of the regular expression may cause exponential backtracking on strings starting with 'a' and containing many repetitions of 'ab'. |
3434
| test.cpp:229:24:229:29 | [a-z]+ | This part of the regular expression may cause exponential backtracking on strings containing many repetitions of 'a'. |
35-
| test.cpp:250:24:250:35 | [[:alpha:]]+ | This part of the regular expression may cause exponential backtracking on strings containing many repetitions of 'a'. |
36-
| test.cpp:252:24:252:35 | [[:alpha:]]+ | This part of the regular expression may cause exponential backtracking on strings containing many repetitions of '0'. |
35+
| test.cpp:236:24:236:25 | a+ | This part of the regular expression may cause exponential backtracking on strings containing many repetitions of 'a'. |
36+
| test.cpp:257:24:257:35 | [[:alpha:]]+ | This part of the regular expression may cause exponential backtracking on strings containing many repetitions of 'a'. |
37+
| test.cpp:259:24:259:35 | [[:alpha:]]+ | This part of the regular expression may cause exponential backtracking on strings containing many repetitions of '0'. |
38+
| test.cpp:324:24:324:29 | [a-z]+ | This part of the regular expression may cause exponential backtracking on strings containing many repetitions of 'a'. |

cpp/ql/test/query-tests/Security/CWE/CWE-1333-ReDoS/test.cpp

Lines changed: 59 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -227,15 +227,22 @@ int main(int argc, char** argv) {
227227

228228
// BAD: exponential regex with icase — case-insensitivity does not suppress the alert.
229229
{ std::regex re("^([a-z]+)+$", std::regex_constants::icase); run(re, input); }
230-
// GOOD: non-ECMAScript grammar (basic) is excluded by the Phase 1 parser.
230+
// GOOD: BRE grammar (basic) is not yet modelled by the parser and is
231+
// excluded by the `RegExp` characteristic predicate.
231232
{ std::regex re("^(a+)+$", std::regex_constants::basic); run(re, input); }
232-
// GOOD: non-ECMAScript grammar (extended) is excluded.
233+
// BAD: extended selects the ERE grammar (modelled by `EreRegExp` since
234+
// Phase C) and is backtracking-eligible, so the shared engine reports
235+
// the nested-quantifier as exponential — same as the ECMAScript case.
233236
{ std::regex re("^(a+)+$", std::regex_constants::extended); run(re, input); }
234-
// GOOD: non-ECMAScript grammar (awk) is excluded.
237+
// GOOD: awk parses as ERE (same as `extended`) but is excluded from the
238+
// ReDoS queries by `isBacktrackingEngine` (POSIX tool-style engines are
239+
// treated as linear-time), *not* by any parsing/grammar difference.
235240
{ std::regex re("^(a+)+$", std::regex_constants::awk); run(re, input); }
236-
// GOOD: non-ECMAScript grammar (grep) is excluded.
241+
// GOOD: grep selects BRE and, like `basic`, is not yet parsed.
237242
{ std::regex re("^(a+)+$", std::regex_constants::grep); run(re, input); }
238-
// GOOD: non-ECMAScript grammar (egrep) is excluded.
243+
// GOOD: egrep parses as ERE (same as `extended`) but is excluded from
244+
// the ReDoS queries by `isBacktrackingEngine`, *not* by any
245+
// parsing/grammar difference.
239246
{ std::regex re("^(a+)+$", std::regex_constants::egrep); run(re, input); }
240247

241248
// -------------------------------------------------------------------------
@@ -266,15 +273,17 @@ int main(int argc, char** argv) {
266273
// 10. Non-backtracking POSIX tool-style grammars (`awk`, `grep`, `egrep`)
267274
// combined with other flags via bitwise-OR.
268275
//
269-
// Regression guard: these must remain excluded from cpp/redos once a
270-
// future phase relaxes the parser to accept non-ECMAScript grammars, via
271-
// the `isBacktrackingEngine` gate. The equivalent default-grammar
272-
// patterns (section 2 above) fire, showing that the gate — not some
273-
// unrelated exclusion — suppresses these cases.
276+
// Regression guard for the `isBacktrackingEngine` gate: `awk` and
277+
// `egrep` are parsed as ERE (`EreRegExp`, Phase C) — structurally the
278+
// same tree the `extended` positive cases above are analyzed with — so
279+
// suppression here is exclusively due to `isBacktrackingEngine`, not
280+
// any parsing/grammar difference. `grep` still selects BRE and is
281+
// additionally unparsed until BRE support lands, but the query-level
282+
// gate would suppress it regardless.
274283
//
275-
// Today the parser also drops all non-ECMAScript grammar literals, so
276-
// these cases would be suppressed regardless; the query-level gate is a
277-
// regression guard for the parser-relaxation phase.
284+
// The equivalent default-grammar (ECMAScript) patterns in section 2
285+
// above fire, showing that the gate — not some unrelated exclusion —
286+
// suppresses these cases.
278287
// -------------------------------------------------------------------------
279288

280289
// GOOD: awk grammar — non-backtracking.
@@ -290,5 +299,42 @@ int main(int argc, char** argv) {
290299
(std::regex_constants::egrep | std::regex_constants::icase));
291300
run(re, input); }
292301

302+
// -------------------------------------------------------------------------
303+
// 11. End-to-end ERE-grammar coverage for cpp/redos.
304+
//
305+
// These cases exercise the ERE parser (Phase C, `EreRegExp`) end-to-end
306+
// through the shared backtracking engine. All three flags below
307+
// (`extended`, `egrep`, `awk`) select the *same* grammar (ERE) and
308+
// therefore produce structurally-identical parse trees for the same
309+
// pattern string — the only axis they differ on is
310+
// `isBacktrackingEngine`:
311+
//
312+
// * `extended` → ERE + backtracking-eligible → flagged.
313+
// * `egrep` / `awk` → ERE + non-backtracking → NOT flagged.
314+
//
315+
// The pattern `^([a-z]+)+$` is chosen because it (a) uses only ERE-legal
316+
// constructs (no `\d`/`\w`/backrefs/lookaround, which are literals in
317+
// ERE) so the ERE parse tree is genuinely equivalent to the ECMAScript
318+
// one, and (b) is a known-exponential nested-quantifier shape (same as
319+
// the ECMAScript case at line 99 above) so we can be confident the
320+
// shared engine reports it.
321+
// -------------------------------------------------------------------------
322+
323+
// BAD: ERE grammar via `extended` — parsed as ERE and backtracking-eligible.
324+
{ std::regex re("^([a-z]+)+$", std::regex_constants::extended); run(re, input); }
325+
// GOOD: same pattern under `egrep` — parses identically as ERE but
326+
// excluded by `isBacktrackingEngine`.
327+
{ std::regex re("^([a-z]+)+$", std::regex_constants::egrep); run(re, input); }
328+
// GOOD: same pattern under `awk` — parses identically as ERE but
329+
// excluded by `isBacktrackingEngine`.
330+
{ std::regex re("^([a-z]+)+$", std::regex_constants::awk); run(re, input); }
331+
// GOOD: `egrep | icase` bitwise-OR combination — still ERE-parsed and
332+
// still excluded by `isBacktrackingEngine`; case-folding does not
333+
// re-enable the backtracking-engine gate.
334+
{ std::regex re("^([a-z]+)+$",
335+
(std::regex_constants::syntax_option_type)
336+
(std::regex_constants::egrep | std::regex_constants::icase));
337+
run(re, input); }
338+
293339
return 0;
294340
}

cpp/ql/test/query-tests/Security/CWE/CWE-1333/PolynomialReDoS.expected

Lines changed: 24 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -7,12 +7,14 @@ edges
77
| test.cpp:107:27:107:30 | **argv | test.cpp:192:28:192:32 | *input | provenance | TaintFunction |
88
| test.cpp:107:27:107:30 | **argv | test.cpp:279:27:279:31 | *input | provenance | TaintFunction |
99
| test.cpp:107:27:107:30 | **argv | test.cpp:308:28:308:32 | *input | provenance | TaintFunction |
10-
| test.cpp:107:27:107:30 | **argv | test.cpp:342:27:342:31 | *input | provenance | TaintFunction |
11-
| test.cpp:107:27:107:30 | **argv | test.cpp:349:27:349:31 | *input | provenance | TaintFunction |
12-
| test.cpp:107:27:107:30 | **argv | test.cpp:421:26:421:30 | *input | provenance | TaintFunction |
13-
| test.cpp:369:27:369:57 | *call to getenv | test.cpp:369:27:369:57 | *call to getenv | provenance | |
14-
| test.cpp:369:27:369:57 | *call to getenv | test.cpp:372:28:372:28 | *s | provenance | TaintFunction |
15-
| test.cpp:380:20:380:22 | fread output argument | test.cpp:383:28:383:28 | *s | provenance | TaintFunction |
10+
| test.cpp:107:27:107:30 | **argv | test.cpp:323:28:323:32 | *input | provenance | TaintFunction |
11+
| test.cpp:107:27:107:30 | **argv | test.cpp:353:27:353:31 | *input | provenance | TaintFunction |
12+
| test.cpp:107:27:107:30 | **argv | test.cpp:360:27:360:31 | *input | provenance | TaintFunction |
13+
| test.cpp:107:27:107:30 | **argv | test.cpp:432:26:432:30 | *input | provenance | TaintFunction |
14+
| test.cpp:107:27:107:30 | **argv | test.cpp:496:28:496:32 | *input | provenance | TaintFunction |
15+
| test.cpp:380:27:380:57 | *call to getenv | test.cpp:380:27:380:57 | *call to getenv | provenance | |
16+
| test.cpp:380:27:380:57 | *call to getenv | test.cpp:383:28:383:28 | *s | provenance | TaintFunction |
17+
| test.cpp:391:20:391:22 | fread output argument | test.cpp:394:28:394:28 | *s | provenance | TaintFunction |
1618
nodes
1719
| test.cpp:107:27:107:30 | **argv | semmle.label | **argv |
1820
| test.cpp:117:28:117:32 | *input | semmle.label | *input |
@@ -23,14 +25,16 @@ nodes
2325
| test.cpp:192:28:192:32 | *input | semmle.label | *input |
2426
| test.cpp:279:27:279:31 | *input | semmle.label | *input |
2527
| test.cpp:308:28:308:32 | *input | semmle.label | *input |
26-
| test.cpp:342:27:342:31 | *input | semmle.label | *input |
27-
| test.cpp:349:27:349:31 | *input | semmle.label | *input |
28-
| test.cpp:369:27:369:57 | *call to getenv | semmle.label | *call to getenv |
29-
| test.cpp:369:27:369:57 | *call to getenv | semmle.label | *call to getenv |
30-
| test.cpp:372:28:372:28 | *s | semmle.label | *s |
31-
| test.cpp:380:20:380:22 | fread output argument | semmle.label | fread output argument |
28+
| test.cpp:323:28:323:32 | *input | semmle.label | *input |
29+
| test.cpp:353:27:353:31 | *input | semmle.label | *input |
30+
| test.cpp:360:27:360:31 | *input | semmle.label | *input |
31+
| test.cpp:380:27:380:57 | *call to getenv | semmle.label | *call to getenv |
32+
| test.cpp:380:27:380:57 | *call to getenv | semmle.label | *call to getenv |
3233
| test.cpp:383:28:383:28 | *s | semmle.label | *s |
33-
| test.cpp:421:26:421:30 | *input | semmle.label | *input |
34+
| test.cpp:391:20:391:22 | fread output argument | semmle.label | fread output argument |
35+
| test.cpp:394:28:394:28 | *s | semmle.label | *s |
36+
| test.cpp:432:26:432:30 | *input | semmle.label | *input |
37+
| test.cpp:496:28:496:32 | *input | semmle.label | *input |
3438
subpaths
3539
#select
3640
| test.cpp:117:28:117:32 | *input | test.cpp:107:27:107:30 | **argv | test.cpp:117:28:117:32 | *input | This $@ that depends on a $@ may run slow on strings with many repetitions of ' '. | test.cpp:116:29:116:31 | \\s+ | regular expression | test.cpp:107:27:107:30 | **argv | user-provided value |
@@ -42,8 +46,10 @@ subpaths
4246
| test.cpp:192:28:192:32 | *input | test.cpp:107:27:107:30 | **argv | test.cpp:192:28:192:32 | *input | This $@ that depends on a $@ may run slow on strings with many repetitions of ' '. | test.cpp:191:29:191:31 | \\s+ | regular expression | test.cpp:107:27:107:30 | **argv | user-provided value |
4347
| test.cpp:279:27:279:31 | *input | test.cpp:107:27:107:30 | **argv | test.cpp:279:27:279:31 | *input | This $@ that depends on a $@ may run slow on strings with many repetitions of ' '. | test.cpp:278:29:278:31 | \\s+ | regular expression | test.cpp:107:27:107:30 | **argv | user-provided value |
4448
| test.cpp:308:28:308:32 | *input | test.cpp:107:27:107:30 | **argv | test.cpp:308:28:308:32 | *input | This $@ that depends on a $@ may run slow on strings with many repetitions of ' '. | test.cpp:307:29:307:31 | \\s+ | regular expression | test.cpp:107:27:107:30 | **argv | user-provided value |
45-
| test.cpp:342:27:342:31 | *input | test.cpp:107:27:107:30 | **argv | test.cpp:342:27:342:31 | *input | This $@ that depends on a $@ may run slow on strings with many repetitions of '9'. | test.cpp:341:25:341:27 | \\d+ | regular expression | test.cpp:107:27:107:30 | **argv | user-provided value |
46-
| test.cpp:349:27:349:31 | *input | test.cpp:107:27:107:30 | **argv | test.cpp:349:27:349:31 | *input | This $@ that depends on a $@ may run slow on strings with many repetitions of 'a'. | test.cpp:348:26:348:27 | .* | regular expression | test.cpp:107:27:107:30 | **argv | user-provided value |
47-
| test.cpp:372:28:372:28 | *s | test.cpp:369:27:369:57 | *call to getenv | test.cpp:372:28:372:28 | *s | This $@ that depends on a $@ may run slow on strings with many repetitions of ' '. | test.cpp:371:29:371:31 | \\s+ | regular expression | test.cpp:369:27:369:57 | *call to getenv | user-provided value |
48-
| test.cpp:383:28:383:28 | *s | test.cpp:380:20:380:22 | fread output argument | test.cpp:383:28:383:28 | *s | This $@ that depends on a $@ may run slow on strings with many repetitions of ' '. | test.cpp:382:29:382:31 | \\s+ | regular expression | test.cpp:380:20:380:22 | fread output argument | user-provided value |
49-
| test.cpp:421:26:421:30 | *input | test.cpp:107:27:107:30 | **argv | test.cpp:421:26:421:30 | *input | This $@ that depends on a $@ may run slow on strings with many repetitions of 'a'. | test.cpp:420:25:420:26 | a+ | regular expression | test.cpp:107:27:107:30 | **argv | user-provided value |
49+
| test.cpp:323:28:323:32 | *input | test.cpp:107:27:107:30 | **argv | test.cpp:323:28:323:32 | *input | This $@ that depends on a $@ may run slow on strings with many repetitions of ' '. | test.cpp:322:29:322:31 | \\s+ | regular expression | test.cpp:107:27:107:30 | **argv | user-provided value |
50+
| test.cpp:353:27:353:31 | *input | test.cpp:107:27:107:30 | **argv | test.cpp:353:27:353:31 | *input | This $@ that depends on a $@ may run slow on strings with many repetitions of '9'. | test.cpp:352:25:352:27 | \\d+ | regular expression | test.cpp:107:27:107:30 | **argv | user-provided value |
51+
| test.cpp:360:27:360:31 | *input | test.cpp:107:27:107:30 | **argv | test.cpp:360:27:360:31 | *input | This $@ that depends on a $@ may run slow on strings with many repetitions of 'a'. | test.cpp:359:26:359:27 | .* | regular expression | test.cpp:107:27:107:30 | **argv | user-provided value |
52+
| test.cpp:383:28:383:28 | *s | test.cpp:380:27:380:57 | *call to getenv | test.cpp:383:28:383:28 | *s | This $@ that depends on a $@ may run slow on strings with many repetitions of ' '. | test.cpp:382:29:382:31 | \\s+ | regular expression | test.cpp:380:27:380:57 | *call to getenv | user-provided value |
53+
| test.cpp:394:28:394:28 | *s | test.cpp:391:20:391:22 | fread output argument | test.cpp:394:28:394:28 | *s | This $@ that depends on a $@ may run slow on strings with many repetitions of ' '. | test.cpp:393:29:393:31 | \\s+ | regular expression | test.cpp:391:20:391:22 | fread output argument | user-provided value |
54+
| test.cpp:432:26:432:30 | *input | test.cpp:107:27:107:30 | **argv | test.cpp:432:26:432:30 | *input | This $@ that depends on a $@ may run slow on strings with many repetitions of 'a'. | test.cpp:431:25:431:26 | a+ | regular expression | test.cpp:107:27:107:30 | **argv | user-provided value |
55+
| test.cpp:496:28:496:32 | *input | test.cpp:107:27:107:30 | **argv | test.cpp:496:28:496:32 | *input | This $@ that depends on a $@ may run slow on strings with many repetitions of ' '. | test.cpp:495:29:495:31 | \\s+ | regular expression | test.cpp:107:27:107:30 | **argv | user-provided value |

0 commit comments

Comments
 (0)