Causes of Regular Expression Denial of Service in Python

Regular Expression Denial of Service (ReDoS) in Python's standard re module occurs when an inefficiently constructed regular expression encounters an input string designed to exploit the engine's backtracking algorithm. Because Python's built-in engine relies on a backtracking Non-deterministic Finite Automaton (NFA), ambiguous patterns with nested or overlapping repetitions can trigger catastrophic backtracking. When an input almost matches the pattern but fails at the very end, the engine exhaustively tests an exponential number of possible matching paths, freezing the execution thread, consuming 100% of the assigned CPU core, and effectively causing a denial of service.

The Underlying Engine: Backtracking NFA

Python's re module utilizes a traditional NFA engine. When evaluating a pattern against a string, the engine moves character by character. If it encounters a quantifier (such as *, +, or {n,m}), it makes an optimistic choice to consume as much text as possible (greedy matching) and saves a state marker.

If a subsequent token in the pattern fails to match, the engine steps backward to the last saved marker and attempts an alternate matching path. If the pattern is unambiguous, this process is fast. However, if the pattern contains multiple paths that can match the exact same substring, the engine enters an algorithmic state known as catastrophic backtracking.

Structural Patterns That Trigger ReDoS

ReDoS conditions are driven by patterns where multiple overlapping paths multiply together exponentially. The most common vulnerability triggers include:

1. Nested Quantifiers

When a quantified group is placed inside another quantifier, the number of combinations multiplies exponentially (\(O(2^n)\) or worse).

2. Overlapping Alternations Inside Repetition

When a repeating group contains choices that match identical prefixes, the engine cannot determine which choice is correct without exhaustively testing both.

3. Overlapping Adjacent Tokens

Patterns with back-to-back greedy tokens that match the same character class force the engine into polynomial time complexity (\(O(n^2)\) or \(O(n^3)\)).

Limitations of Python's Standard re Module

Several characteristics specific to Python's standard library amplify ReDoS risks:

Preventing ReDoS in Python

  1. Eliminate Ambiguity: Rewrite patterns to ensure mutually exclusive branches. Instead of (\d+|\w+), use patterns where a character can only match one specific branch.
  2. Use Linear-Time Alternative Engines: Replace re with third-party libraries designed with deterministic finite automata (DFA) that guarantee \(O(n)\) linear-time execution, such as Google's re2 (via the google-re2 Python package).
  3. Use the Third-Party regex Module: The alternative regex package supports atomic grouping (?>...) and possessive quantifiers, allowing developers to manually disable backtracking over critical sections.
  4. Input Validation: Enforce strict length limits on user input before passing data to regular expressions.