Skip to content
Merged
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
40 changes: 40 additions & 0 deletions src/decode/decode.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -603,6 +603,46 @@ describe("decode", () => {
);
});

it("throws for out-of-bounds GENERATED_RANGE_SUBRANGE_BINDING variable indices in strict mode", () => {
const encoder = new ItemEncoder();
encoder.addUnsignedVLQs(Tag.GENERATED_RANGE_START, 0, 0).finishItem();
encoder.addUnsignedVLQs(Tag.GENERATED_RANGE_SUBRANGE_BINDING, 1_000_000)
.finishItem();
encoder.addUnsignedVLQs(Tag.GENERATED_RANGE_END, 0).finishItem();
const map = createMap({ ranges: encoder.encode() });

assertThrows(
() => decode(map, { mode: DecodeMode.STRICT }),
Error,
"GENERATED_RANGE_SUBRANGE_BINDING variable index out of bounds",
);
});

it("ignores out-of-bounds GENERATED_RANGE_SUBRANGE_BINDING variable indices in lax mode", () => {
const encoder = new ItemEncoder();
encoder.addUnsignedVLQs(Tag.GENERATED_RANGE_START, 0, 0).finishItem();
encoder.addUnsignedVLQs(Tag.GENERATED_RANGE_SUBRANGE_BINDING, 1_000_000)
.finishItem();
encoder.addUnsignedVLQs(Tag.GENERATED_RANGE_END, 0).finishItem();
const map = createMap({ ranges: encoder.encode() });

const info = decode(map, { mode: DecodeMode.LAX });

assertExists(info.ranges[0]);
assertEquals(info.ranges[0].values, []);
});

it("ignores huge GENERATED_RANGE_SUBRANGE_BINDING variable indices without hanging", () => {
// "H//////B" encodes variable index 2^31 - 1. This used to create a sparse
// `values` array of length 2^31 and iterate every hole.
const map = createMap({ ranges: "EAA,H//////B,FA" });

const info = decode(map, { mode: DecodeMode.LAX });

assertExists(info.ranges[0]);
assertEquals(info.ranges[0].values, []);
});

it("ignores multiple GENERATED_RANGE_SUBRANGE_BINDING items for the same variable in lax mode", () => {
const scopeEncoder = new ItemEncoder();
// Original scope with 1 variable.
Expand Down
11 changes: 11 additions & 0 deletions src/decode/decode.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ import { TokenIterator } from "../vlq.ts";
* - Encountering ORIGINAL_SCOPE_VARIABLES items outside a surrounding scope START/END.
* - Encountering GENERATED_RANGE_BINDINGS items outside a surrounding range START/END.
* - Miss-matches between the number of variables in a scope vs the number of value expressions in the ranges.
* - GENERATED_RANGE_SUBRANGE_BINDING items whose variable index is out of bounds of the range's bindings.
* - Out-of-bound indices into the "names" array.
*/
export const enum DecodeMode {
Expand Down Expand Up @@ -622,6 +623,16 @@ class Decoder {
if (!subRangeBindings) return;

for (const [variableIndex, bindings] of subRangeBindings) {
// Sub-range bindings can only refine an existing binding from the
// GENERATED_RANGE_BINDINGS item. Writing to an arbitrary (attacker
// controlled) index would turn `range.values` into a huge sparse array.
if (variableIndex < 0 || variableIndex >= range.values.length) {
this.#throwInStrictMode(
"GENERATED_RANGE_SUBRANGE_BINDING variable index out of bounds",
);
continue;
}

const value = range.values[variableIndex];
const subRanges: SubRangeBinding[] = [];
range.values[variableIndex] = subRanges;
Expand Down
Loading