fix: heap out-of-bounds read in parse_number_expression (#446) - #449
Merged
Conversation
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>
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
This was referenced Aug 5, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fixes #446.
What changed
parse_number_expressiondecided whether a number literal used a0b/0o/0xprefix by testingvalue[0] == '0'and then unconditionally readingvalue[1], with no check that the token is at least 2 bytes long. This adds atoken.bytes > 1guard.Why
token.valuepoints directly into the caller's source buffer (lexer->buffer + lexer->offsetingravity_lexer.c) and is not separately zero terminated — the lexer treats the buffer as exactlylenbytes and bounds-checks every peek. So when the final token of the source is a lone0,value[1]reads one byte past the end of the buffer.Reproduced under ASan by passing an exact-size
malloc(3)buffer containingx=0togravity_compiler_run:Notes for the reviewer
Scope is narrower than the issue title suggests. The CLI is not affected:
file_readallocatesfsize + 1and setsbuffer[fsize] = 0, sovalue[1]lands on the in-bounds terminator. I confirmed this with an ASan-builtgravityon a file containing exactlyvar x=0with no trailing newline — clean.gravity -iis fine for the same reason (argv is terminated). The exposure is to embedders callinggravity_compiler_run/gravity_vm_loadbufferwith a non-terminated exact-length buffer, which the explicitsize_t lenparameter 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 withtoken.bytes == 1andtoken.bytes - 2underflows to0xFFFFFFFFonuint32_t. All three bail on the length before touching memory (number_from_binreturns 0 forlen > 64,number_from_oct/number_from_hexforlen > 24), so nothing is dereferenced and the literal still decodes to the correct0. 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]indecode_number_binary) is the only other indexedtoken.valueread in the tree — technically UB pointer arithmetic on a 1-byte token, but never dereferenced thanks to that same guard.Testing
x=0and on a bare00xFF,0b101,0o17still decode correctly🤖 Generated with Claude Code