fix: reject an array length that exceeds the remaining buffer - #300
Open
spokodev wants to merge 1 commit into
Open
fix: reject an array length that exceeds the remaining buffer#300spokodev wants to merge 1 commit into
spokodev wants to merge 1 commit into
Conversation
An array whose `length` is read from a parsed field (e.g. a length-
prefixed list of records) generates a loop bounded only by that field:
for (var $c = <length>; $c > 0; $c--) { ... }
The count is taken from attacker-controlled input, and elements decoded
via `buffer.subarray()` (a nested Parser, a buffer or a string) do not
throw past the end of the input, so a tiny frame declaring a huge count
walks the offset past EOF while allocating one object per declared
element. A uint32 count lets a <=6-byte buffer force a multi-GB
allocation / OOM.
Guard the count against the bytes remaining before looping: every array
element consumes at least one byte, so a count larger than the remaining
buffer is unsatisfiable and can be rejected without affecting valid
input.
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.
An
arraywhoselengthis read from a parsed field — the common length-prefixed shape, e.g. auint32count followed by that many records — generates a loop bounded only by that field:The parser definition is developer-written (trusted), but the buffer passed to
parse()is attacker-controlled. When the element decodes viabuffer.subarray()(a nestedParser, abuffer, or astring), reading past the end of the input does not throw, so a tiny frame that declares a huge count walksoffsetpast EOF while allocating one object per declared element. Auint32count lets a ≤6-byte buffer drive a multi-GB allocation / OOM.Repro
Measured (constant 4-byte input, only the declared count varies): count=10M → ~1.1 s / ~1.8 GB heap; count=40M with
--max-old-space-size=1024→FATAL ERROR: JavaScript heap out of memory. (Primitive-typed arrays are not affected —DataView.getXthrows past EOF — so only thesubarray-based element shapes are exploitable.)Fix
Every array element consumes at least one byte, so a count larger than the number of bytes remaining is unsatisfiable. Guard the count against
buffer.length - offsetbefore the loop; this rejects only physically-impossible counts and never fires on valid data (thereadUntilandlengthInBytespaths are untouched).Test
Adds a case: a
uint32becount of 100000 followed by no element bytes must throw. It fails onmaster(the parser silently produces 100000 elements) and passes with the fix. Full suite: 168 passing (was 166; the new case runs under both theBufferandUint8Arrayparametrizations).