Skip to content

Fix stack-use-after-return with unterminated heredocs#4155

Open
matz wants to merge 1 commit into
ruby:mainfrom
matz:fix-heredoc-stack-use-after-return
Open

Fix stack-use-after-return with unterminated heredocs#4155
matz wants to merge 1 commit into
ruby:mainfrom
matz:fix-heredoc-stack-use-after-return

Conversation

@matz

@matz matz commented Jul 3, 2026

Copy link
Copy Markdown
Collaborator

This fixes a stack-use-after-return found while fuzzing mruby, which vendors Prism as its compiler.

Reproduction (24 bytes, minimized from a clusterfuzz testcase):

`#{r{<<~0
#{{i}w<<0
0
}

Parsing this source under valgrind, or ASan with detect_stack_use_after_return=1, reports reads of a dead stack frame in parser_lex:

READ of size 8 at ... thread T0
    #0 parser_lex src/prism.c (heredoc common_whitespace check)
    #1 parse_string_part
    #2 parse_expression_prefix
    ...
Address ... is located in stack of thread T0 in frame parse_expression_prefix
    'common_whitespace' <== Memory access is inside this variable

Cause: the PM_TOKEN_HEREDOC_START handler in parse_expression_prefix stores the address of its stack-local common_whitespace into the heredoc lex mode so that the lexer can report the common leading whitespace back to the parser. On well-formed input the lex mode is popped when PM_TOKEN_HEREDOC_END is lexed, before the handler returns. When the terminator is missing, however, expect1_heredoc_term only records an error and does not pop the lex mode, so the stored pointer outlives the handler's stack frame. Subsequent lexing, still in heredoc mode, then dereferences the dangling pointer at the common_whitespace checks in parser_lex.

Fix: before leaving the handler, walk the lex mode stack and clear any common_whitespace pointer that still points at the handler's local. The lexer already guards the pointer against NULL, so this degrades gracefully; the dedent width is lost, but the source is a syntax error at that point anyway.

I verified the fix with valgrind and ASan on the v1.9.0 sources vendored in mruby, and the same code is present on main.

The heredoc handler in parse_expression_prefix stores the address of
its stack-local common_whitespace into the heredoc lex mode. When the
heredoc terminator is missing, expect1_heredoc_term reports an error
without popping the lex mode, so the stored pointer outlives the stack
frame and subsequent lexing reads dead stack memory.

Clear the pointer before leaving the handler; the lexer already guards
against NULL.

Co-authored-by: Claude <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