ReviewOS

zig-utils/zig-regex

ReDoS guard refuses valid ECMAScript patterns on engines that cannot backtrack

#31
Closed glennmichael123 opened this 9 days ago ·
9 days ago

Regex.compileWithFlags ran pattern_analyzer.analyzeAndValidate(..., .high) on every compile, before an engine was chosen, and returned error.PatternTooComplex for anything the analyzer called critical. That rejected patterns JavaScript accepts:

/(a*)*/.exec("b")    // node: ["", undefined]
/(a+)+/.exec("aaa")  // node: ["aaa", "aaa"]
/(?:a*)+b/.exec("aab")

All three failed to compile at all, so an embedder implementing ECMAScript could not even construct the RegExp.

The guard is aimed at exponential backtracking, which is a property of the backtracking engine. The Thompson engines this library routes most patterns to (the Pike VM, the lazy DFA, one-pass) are linear in the input whatever the pattern shape, so nested quantifiers cost them nothing. What they can blow up on is size, not time: counted repeats expand into copies of their operand, so (a{1000}){1000} would want about 10^6 states.

Fixed in 30c1fa9: run the analyzer only when the pattern is routed to the backtracker, or when the dialect is not ECMAScript, and cap an ECMAScript NFA at compiler.max_nfa_states (200,000) so a pattern that would expand past it still fails with PatternTooComplex.

Evidence, on the 20,775-case node v24.4.1 differential corpus:

refused at compilecases runmismatches
bc141701,66019,915308
30c1fa980020,775308

860 patterns that never ran now do, and every one agrees with node: the mismatch set is identical case for case (0 fixed, 0 regressed) and is entirely the backtracker backlog in #30. zig build test-core passes.

9 days ago

Fixed by 30c1fa9; JS-level behaviour is being pinned through zig-js next.

Sign in to comment on this issue.