From 7a96d529c3dd31f17c106da0173a15b66a700106 Mon Sep 17 00:00:00 2001 From: Yarchik Date: Wed, 2 Sep 2026 14:43:01 +0100 Subject: [PATCH] fix: reject an array length that exceeds the remaining buffer 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 = ; $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. --- lib/binary_parser.ts | 7 +++++++ test/composite_parser.ts | 13 +++++++++++++ 2 files changed, 20 insertions(+) diff --git a/lib/binary_parser.ts b/lib/binary_parser.ts index cf511f65..72f2055d 100644 --- a/lib/binary_parser.ts +++ b/lib/binary_parser.ts @@ -1299,6 +1299,13 @@ export class Parser { `for (var ${counter} = offset + ${lengthInBytes}; offset < ${counter}; ) {`, ); } else { + // Reject a length field that claims more elements than the buffer can + // hold, so a tiny crafted input cannot force a huge allocation/loop. + ctx.pushCode(`if (${length} > buffer.length - offset) {`); + ctx.generateError( + `"Array length " + (${length}) + " exceeds buffer length"`, + ); + ctx.pushCode(`}`); ctx.pushCode( `for (var ${counter} = ${length}; ${counter} > 0; ${counter}--) {`, ); diff --git a/test/composite_parser.ts b/test/composite_parser.ts index da744cf3..b23a89ff 100644 --- a/test/composite_parser.ts +++ b/test/composite_parser.ts @@ -24,6 +24,19 @@ function compositeParserTests( message: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12], }); }); + it("should reject an array length that exceeds the remaining buffer", () => { + const parser = Parser.start() + .uint32be("count") + .array("items", { + length: "count", + type: new Parser().buffer("payload", { length: 1 }), + }); + + const buffer = factory([0x00, 0x01, 0x86, 0xa0]); + throws(() => { + parser.parse(buffer); + }); + }); it("should parse array of primitive types with lengthInBytes", () => { const parser = Parser.start().uint8("length").array("message", { lengthInBytes: "length",