Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 22 additions & 0 deletions src/common/utils/leb128.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -242,6 +242,28 @@ describe('leb128', () => {
'LEB128 sequence exceeds maximum length for uint32',
);
});

test('throws for a 5-byte encoding whose final byte overflows uint32', () => {
// 5 bytes, but the final byte carries data bits above bit 31
// (0x10 -> 2^32). Without a range check the 32-bit shift silently drops
// the overflow and returns 0 instead of rejecting the value.
const data = new Uint8Array([0x80, 0x80, 0x80, 0x80, 0x10]);
expect(() => decodeUInt32(data)).toThrow(
'LEB128 sequence exceeds uint32 range',
);
});

test('throws when the final byte uses all 7 data bits', () => {
const data = new Uint8Array([0xff, 0xff, 0xff, 0xff, 0x7f]);
expect(() => decodeUInt32(data)).toThrow(
'LEB128 sequence exceeds uint32 range',
);
});

test('accepts MAX_UINT32 whose final byte is exactly 0x0f', () => {
const data = new Uint8Array([0xff, 0xff, 0xff, 0xff, 0x0f]);
expect(decodeUInt32(data).value).toBe(4294967295);
});
});
});

Expand Down
15 changes: 15 additions & 0 deletions src/common/utils/leb128.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,11 @@ const CONTINUATION_BIT = 0x80;
const DATA_BITS_MASK = 0x7f;
const DATA_BITS_PER_BYTE = 7;
const MAX_BYTES_FOR_UINT32 = 5;
// The 5th byte is read at this shift; a uint32 only has 32 - 28 = 4 data bits
// left for it, so its 7-bit payload must not exceed 0x0F. A larger payload
// encodes a value above MAX_UINT32.
const FINAL_BYTE_SHIFT = (MAX_BYTES_FOR_UINT32 - 1) * DATA_BITS_PER_BYTE;
const FINAL_BYTE_MAX_DATA = 0x0f;

/**
* Encodes an unsigned 32-bit integer into LEB128 format.
Expand Down Expand Up @@ -65,6 +70,16 @@ export function decodeUInt32(
throw new Error('LEB128 sequence exceeds maximum length for uint32');
}

// On the final (5th) byte only 4 of its 7 data bits fit in a uint32. Reject a
// larger payload instead of letting the 32-bit `<<` below silently drop the
// overflowing bits and return a wrong value for an out-of-range encoding.
if (
shift === FINAL_BYTE_SHIFT &&
(byte & DATA_BITS_MASK) > FINAL_BYTE_MAX_DATA
) {
throw new Error('LEB128 sequence exceeds uint32 range');
}

result |= (byte & DATA_BITS_MASK) << shift;

if (!hasContinuationBit(byte)) {
Expand Down
Loading