diff --git a/src/Array.js b/src/Array.js index 150c991..939d2e6 100644 --- a/src/Array.js +++ b/src/Array.js @@ -46,6 +46,11 @@ class ArrayT extends Base { } } else { + // A count larger than the remaining bytes cannot be decoded and would allocate unbounded. + if (length > stream.length - stream.pos) { + throw new Error('Array length exceeds stream length'); + } + for (let i = 0, end = length; i < end; i++) { res.push(this.type.decode(stream, ctx)); } diff --git a/test/Array.js b/test/Array.js index bdf5fac..70e649b 100644 --- a/test/Array.js +++ b/test/Array.js @@ -1,5 +1,5 @@ import assert from 'assert'; -import {Array as ArrayT, Pointer, uint8, uint16, DecodeStream, EncodeStream} from 'restructure'; +import {Array as ArrayT, String as StringT, Pointer, uint8, uint16, uint32, DecodeStream, EncodeStream} from 'restructure'; describe('Array', function() { describe('decode', function() { @@ -62,6 +62,13 @@ describe('Array', function() { const array = new ArrayT(uint8); assert.deepEqual(array.fromBuffer(buffer), [1, 2, 3, 4]); }); + + it('should throw when the decoded length exceeds the stream', function() { + // 32-bit count of 100000 elements, but only one byte of payload follows. + const buffer = new Uint8Array([0x00, 0x01, 0x86, 0xa0, 0x00]); + const array = new ArrayT(new StringT(), uint32); + assert.throws(() => array.fromBuffer(buffer), /exceeds stream length/); + }); }); describe('size', function() {