Skip to content

POSIX ERE with two bounded wildcard intervals exhausts memory during pattern compilation (5.0.0–7.8.2) #549

Description

@wanderlust-n

Summary

A pattern of the form .{0,N}literal.{0,M} with two sufficiently large
bounded wildcard intervals causes pathological memory growth during regex
compilation, continuing until an external memory limit or host exhaustion
terminates the process. A 1-byte input file is enough to trigger it. On an
uncapped host this exhausted all RAM and swap (we observed a single ugrep
process reach ~11 GiB anonymous memory in a real incident that took a
self-hosted server running live services offline for ~30 minutes before
the kernel OOM killer intervened); inside a 512 MiB cgroup it reaches the
cap and is OOM-killed within seconds.

Reproduction (memory-caged — recommended)

printf 'x' > f.txt

sudo systemd-run --wait --pipe \
  --unit=ugrep-oom-repro \
  --uid="$(id -u)" \
  -p MemoryMax=512M \
  -p MemorySwapMax=0 \
  -p RuntimeMaxSec=30 \
  -p OOMPolicy=kill \
  -- ugrep -E '.{0,70}dream.{0,90}' f.txt

The core command, shown bare only for illustration — running it uncaged
will consume all available memory:

ugrep -E '.{0,70}dream.{0,90}' f.txt

Results on all builds we tested (Ubuntu 24.04, x86_64):

Build Source Result Runtime to 512 MiB cap
ugrep 5.0.0 Ubuntu 24.04 package 5.0.0+dfsg-1 cgroup OOM-kill 3.9 s
ugrep 7.5.0 built from tag v7.5.0 (eada75555a2b), ALL TESTS PASSED cgroup OOM-kill 3.9 s
ugrep 7.8.2 built from tag v7.8.2, ALL TESTS PASSED cgroup OOM-kill 5.7 s

All three used the same printf 'x' fixture and the same
-E '.{0,70}dream.{0,90}' pattern.

Narrowing (each verified in the same 512 MiB cage)

  • Within this tested pattern family, both intervals are required to
    trigger the failure
    : .{0,70}dream and dream.{0,90} each complete
    instantly (~8 MiB peak). The paired form explodes.
  • Interval size matters: paired .{0,8}dream.{0,8} completes
    instantly; paired {0,70}/{0,90} exhausts memory.
  • The failure is independent of input size and content in all tested
    cases
    : a 1-byte file, 4 MB of y\n lines (zero matches), a single
    800 KB line with zero matches, and a 60 MB text fixture behave
    identically. -o, -i, and file-vs-stdin delivery make no difference.
  • Attribution to compilation: the 1-byte, zero-match reproducer and
    the input ablations show that the growth is attributable to pattern
    compilation rather than input scanning.
  • -P (PCRE2) is unaffected where available: the identical pattern
    with -P completes instantly (~7-8 MiB) on the Ubuntu 5.0.0 package and
    on a PCRE2-enabled 7.5.0 build, for both the 1-byte file and the 60 MB
    fixture. (Our from-tag builds above were compiled without PCRE2 —
    option -P is not available in this build configuration — so -P was
    not testable on those two binaries; this is a build-configuration fact,
    not a -P failure.)
  • GNU grep (grep -E, 3.11) handles the same pattern and inputs in
    MiB-scale memory.

Expected

Bounded memory during pattern compilation, or a graceful diagnostic when
the compiled DFA would exceed a safe complexity or memory limit.

Context

This was discovered through ugrep 7.5.0 embedded in Anthropic's Claude
Code CLI, which transparently shadows grep. Multiple downstream
incidents are documented in anthropics/claude-code#67021. A later
downstream report also reproduces the corresponding escaped BRE form
through -G; the upstream stock-build matrix reported here specifically
verifies the POSIX ERE -E form.

Related reports

Environment

Ubuntu 24.04, x86_64; tag builds via ./build.sh (gcc); distribution
package from the Ubuntu archive.

Happy to test patches or provide more measurements.

AI-assisted investigation; all measurements were operator-verified.

Metadata

Metadata

Assignees

No one assigned

    Labels

    patchA patch to fix an issue

    Type

    No type

    Fields

    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions