Skip to content

fix: heap out-of-bounds read in parse_number_expression (#446) - #449

Merged
marcobambini merged 1 commit into
masterfrom
fix/parser-number-oob-read
Aug 5, 2026
Merged

fix: heap out-of-bounds read in parse_number_expression (#446)#449
marcobambini merged 1 commit into
masterfrom
fix/parser-number-oob-read

Conversation

@marcobambini

Copy link
Copy Markdown
Owner

Fixes #446.

What changed

parse_number_expression decided whether a number literal used a 0b/0o/0x prefix by testing value[0] == '0' and then unconditionally reading value[1], with no check that the token is at least 2 bytes long. This adds a token.bytes > 1 guard.

Why

token.value points directly into the caller's source buffer (lexer->buffer + lexer->offset in gravity_lexer.c) and is not separately zero terminated — the lexer treats the buffer as exactly len bytes and bounds-checks every peek. So when the final token of the source is a lone 0, value[1] reads one byte past the end of the buffer.

Reproduced under ASan by passing an exact-size malloc(3) buffer containing x=0 to gravity_compiler_run:

ERROR: AddressSanitizer: heap-buffer-overflow on address 0x6020000000d3
READ of size 1 at 0x6020000000d3 thread T0
    #0 parse_number_expression gravity_parser.c:684
    #1 parse_literal_expression gravity_parser.c:904
    #2 parse_precedence         gravity_parser.c:1074
    ...
0x6020000000d3 is located 0 bytes after 3-byte region [0x6020000000d0,0x6020000000d3)

Notes for the reviewer

Scope is narrower than the issue title suggests. The CLI is not affected: file_read allocates fsize + 1 and sets buffer[fsize] = 0, so value[1] lands on the in-bounds terminator. I confirmed this with an ASan-built gravity on a file containing exactly var x=0 with no trailing newline — clean. gravity -i is fine for the same reason (argv is terminated). The exposure is to embedders calling gravity_compiler_run / gravity_vm_loadbuffer with a non-terminated exact-length buffer, which the explicit size_t len parameter permits — that includes the fuzz harness that found this.

Impact is capped at the single byte; there is no escalation path. If the out-of-bounds byte happens to be b/o/x, the decoders get called with token.bytes == 1 and token.bytes - 2 underflows to 0xFFFFFFFF on uint32_t. All three bail on the length before touching memory (number_from_bin returns 0 for len > 64, number_from_oct / number_from_hex for len > 24), so nothing is dereferenced and the literal still decodes to the correct 0. I checked all four trailing-byte variants under ASan. The reporter's "Medium" rating is fair, arguably generous.

gravity_parser.c:378 (&token.value[2] in decode_number_binary) is the only other indexed token.value read in the tree — technically UB pointer arithmetic on a 1-byte token, but never dereferenced thanks to that same guard.

Testing

  • ASan clean on x=0 and on a bare 0
  • 0xFF, 0b101, 0o17 still decode correctly
  • Full unit suite: 350/350 passing

🤖 Generated with Claude Code

parse_number_expression checked for a 0b/0o/0x prefix by reading value[1]
whenever value[0] was '0', without first confirming the token is at least
2 bytes long. token.value points directly into the caller's source buffer
(lexer->buffer + lexer->offset) and is not separately zero terminated, so
a source whose last byte is a lone '0' caused a 1-byte read past the end
of the buffer.

The CLI is unaffected because file_read over-allocates by one byte and
zero terminates, but gravity_compiler_run accepts an explicit length and
embedders may legitimately pass an exact-size, non terminated buffer.
Confirmed with ASan on a 3-byte malloc holding "x=0":

  ERROR: AddressSanitizer: heap-buffer-overflow
  READ of size 1 at 0x6020000000d3 thread T0
      #0 parse_number_expression gravity_parser.c:684

Guard the prefix check with token.bytes > 1.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@vercel

vercel Bot commented Aug 5, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
gravity Ready Ready Preview Aug 5, 2026 7:22am

Request Review

@marcobambini
marcobambini merged commit e1f38c2 into master Aug 5, 2026
4 checks passed
@marcobambini
marcobambini deleted the fix/parser-number-oob-read branch August 5, 2026 07:36
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.

[Bug]heap out-of-bounds read in parse_number_expression on a trailing single '0' (CWE-125)

1 participant