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
42 changes: 27 additions & 15 deletions encoding/varint.ts
Original file line number Diff line number Diff line change
Expand Up @@ -221,26 +221,38 @@ export function encodeVarint(
buf: Uint8Array = new Uint8Array(MaxVarintLen64),
offset = 0,
): [Uint8Array_, number] {
num = BigInt(num);
if (num < 0n) {
const input = BigInt(num);
if (input < 0n) {
throw new RangeError(
`Cannot encode the input into varint as it should be non-negative integer: received ${num}`,
`Cannot encode the input into varint as it should be non-negative integer: received ${input}`,
);
}
for (
let i = offset;
i <= Math.min(buf.length, MaxVarintLen64);
i += 1
) {
if (num < MSBN) {
buf[i] = Number(num);
i += 1;
return [buf.slice(offset, i), i];
// #7147: the previous loop used `i <= Math.min(buf.length, MaxVarintLen64)`,
// which would walk one byte past the end of `buf` on the final iteration.
// For values that needed all 10 uint64 varint bytes plus a continuation,
// the OOB write was silently dropped by `Uint8Array`, the function
// returned a too-short slice, and the caller never learned the buffer
// was full. Cap the iteration count at the smaller of `buf.length`
// (relative to `offset`) and `MaxVarintLen64`, then explicitly
// distinguish "buffer too small" from "input overflows uint64".
const cap = Math.min(buf.length - offset, MaxVarintLen64);
let pending = input;
for (let i = 0; i < cap; i += 1) {
if (pending < MSBN) {
buf[offset + i] = Number(pending);
return [buf.slice(offset, offset + i + 1), offset + i + 1];
}
buf[i] = Number((num & 0xFFn) | MSBN);
num >>= SHIFTN;
buf[offset + i] = Number((pending & 0xFFn) | MSBN);
pending >>= SHIFTN;
}
if (buf.length - offset < MaxVarintLen64) {
throw new RangeError(
`Cannot encode the input ${input} into varint: the provided buffer holds at most ${
buf.length - offset
} byte(s) after offset, but the value requires more`,
);
}
throw new RangeError(
`Cannot encode the input ${num} into varint as it overflows uint64`,
`Cannot encode the input ${input} into varint as it overflows uint64`,
);
}
40 changes: 40 additions & 0 deletions encoding/varint_test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,46 @@ Deno.test("encodeVarint() throws on overflow with negative", () => {
"Cannot encode the input into varint as it should be non-negative integer: received -1",
);
});
Deno.test(
"encodeVarint() throws when the default buffer is too small (#7147)",
() => {
// 0x1234567891234567891n is 76 bits → cannot fit in 10-byte uint64
// varint and must throw. Previously the OOB write at the 11th byte was
// silently dropped and the function returned `[Uint8Array(10), 11]`.
assertThrows(
() => encodeVarint(0x1234567891234567891n),
RangeError,
"overflows uint64",
);
},
);

Deno.test(
"encodeVarint() throws when a caller-provided buffer is too small",
() => {
// 200000 needs 3 varint bytes, buffer only has 2 → must throw rather
// than silently truncate to 2 bytes.
assertThrows(
() => encodeVarint(200_000, new Uint8Array(2)),
RangeError,
"buffer holds at most 2 byte(s) after offset",
);
},
);

Deno.test(
"encodeVarint() throws when offset leaves no room in the buffer",
() => {
// 10-byte buf but offset=10 → 0 bytes available; any positive value
// overflows the slice and must throw.
assertThrows(
() => encodeVarint(42n, new Uint8Array(10), 10),
RangeError,
"buffer holds at most 0 byte(s) after offset",
);
},
);

Deno.test("encodeVarint() encodes with offset", () => {
let uint = new Uint8Array(3);
assertEquals(
Expand Down
Loading