Skip to content

fuzz_regexp: size the capture array for lre_exec() and add a real stack check - #570

Open
JasonGross wants to merge 1 commit into
bellard:masterfrom
JasonGross:fuzz-regexp-capture-and-stack
Open

JasonGross wants to merge 1 commit into
bellard:masterfrom
JasonGross:fuzz-regexp-capture-and-stack

Conversation

@JasonGross

Copy link
Copy Markdown

Two problems in fuzz/fuzz_regexp.c, the libFuzzer target for libregexp:

  1. The capture array is too small. The harness passes uint8_t *capture[255 * 2] to lre_exec().
    Since the change that added lre_get_alloc_count(), lre_exec() also keeps the quantifier registers
    in that array after the capture slots, so it needs up to 2 * 255 + 255 entries. A pattern with 254
    capture groups followed by a counted quantifier makes lre_exec() write past the end of the stack
    array. For example, the input () repeated 254 times, then a{2}, a NUL byte and aa compiles to
    255 captures plus one quantifier register: 511 slots for a 510-entry array. Each further level of
    nested quantifier adds one more slot, up to the parser's limit of 255 levels (765 slots). The fix
    makes the array 255 * 3 entries.

  2. The stack check is a stub. lre_check_stack_overflow() always returns 0, so the recursive parser
    has no depth limit, and a deeply nested pattern overflows the C stack. For example, 100,000 nested
    non-capturing groups ((?: ... a ... )) crash the current harness with SIGSEGV on an 8 MiB stack.
    The fix does the same check as js_check_stack_overflow() in quickjs.c: it records a limit of
    1 MiB below the stack pointer when LLVMFuzzerTestOneInput() starts and compares against it. Deep
    patterns then fail with the parser's normal "stack overflow" error.

Only the fuzz target changes.

🤖 Written with Claude Code (Claude Opus 5.5)

lre_exec() keeps the quantifier registers after the capture slots, so the
array it is given must hold lre_get_alloc_count() entries: up to
2 * 255 captures plus 255 nested quantifier registers. The harness passed
a 255 * 2 array, so 254 capture groups followed by a counted quantifier
wrote past its end. Make it 255 * 3.

lre_check_stack_overflow() was a stub returning 0, so deeply nested
patterns overflowed the C stack. Check the stack pointer against a limit
1 MiB below where LLVMFuzzerTestOneInput() started, as quickjs.c does.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant