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
5 changes: 5 additions & 0 deletions .changeset/tidy-reads-bound.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@fleet-sdk/serializer": patch
---

fix(serializer): throw on out-of-bounds reads in SigmaByteReader instead of returning undefined or truncated data
39 changes: 39 additions & 0 deletions packages/serializer/src/coders/sigmaByteReader.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,45 @@ describe("Sigma byte reader", () => {
expect(reader.match(u8a([0x04, 0x05, 0x06, 0x07]), 3)).toBe(false);
});

it("should throw when reading a byte past the end of the input", () => {
const reader = new SigmaByteReader(u8a([0x01]));

expect(reader.readByte()).toBe(0x01);
expect(() => reader.readByte()).toThrow(RangeError);
expect(() => new SigmaByteReader(u8a([])).readByte()).toThrow(RangeError);
});

it("should throw when reading more bytes than remain", () => {
const reader = new SigmaByteReader(u8a([0x01, 0x02, 0x03]));

expect(reader.readBytes(2)).toEqual(u8a([0x01, 0x02]));
expect(() => reader.readBytes(2)).toThrow(RangeError);
expect(reader.readBytes(1)).toEqual(u8a([0x03])); // exact remainder still works
expect(() => reader.readBytes(1)).toThrow(RangeError);
expect(() => reader.readBytes(-1)).toThrow(RangeError);
});

it("should throw when reading bits past the end of the input", () => {
const reader = new SigmaByteReader(u8a([0xff]));

expect(reader.readBits(8)).toEqual([true, true, true, true, true, true, true, true]);
expect(() => reader.readBits(1)).toThrow(RangeError);
expect(() => new SigmaByteReader(u8a([0xff])).readBits(9)).toThrow(RangeError);
});

it("should throw on a VLQ truncated with the continuation bit set", () => {
expect(() => new SigmaByteReader(u8a([0x80])).readUInt()).toThrow(RangeError);
expect(() => new SigmaByteReader(u8a([0x80])).readBigUInt()).toThrow(RangeError);
expect(() => new SigmaByteReader(u8a([0xff, 0xff])).readUInt()).toThrow(RangeError);
});

it("should throw when a length-prefixed field is truncated", () => {
// SLong header is not involved here: readI256 reads a VLQ length (5) and then
// expects 5 bytes, but only 2 remain.
const reader = new SigmaByteReader(u8a([0x05, 0x01, 0x02]));
expect(() => reader.readI256()).toThrow(RangeError);
});

it("should read remaining bytes", () => {
const bytes = u8a([0x01, 0x02, 0x03, 0x04]);
const reader = new SigmaByteReader(bytes);
Expand Down
18 changes: 18 additions & 0 deletions packages/serializer/src/coders/sigmaByteReader.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,12 @@ export class SigmaByteReader {
}

readBits(length: number): ArrayLike<boolean> {
if (length < 0 || this.#cursor + Math.ceil(length / 8) > this.#bytes.length) {
throw new RangeError(
`Reading ${length} bits at position ${this.#cursor} exceeds the input length of ${this.#bytes.length} bytes`
);
}

const bits = new Array<boolean>(length);
let bitOffset = 0;

Expand All @@ -61,10 +67,22 @@ export class SigmaByteReader {
}

readByte(): number {
if (this.#cursor >= this.#bytes.length) {
throw new RangeError(
`Reading a byte at position ${this.#cursor} exceeds the input length of ${this.#bytes.length} bytes`
);
}

return this.#bytes[this.#cursor++];
}

readBytes(length: number): Uint8Array {
if (length < 0 || this.#cursor + length > this.#bytes.length) {
throw new RangeError(
`Reading ${length} bytes at position ${this.#cursor} exceeds the input length of ${this.#bytes.length} bytes`
);
}

return this.#bytes.subarray(this.#cursor, (this.#cursor += length));
}

Expand Down