Skip to content

Commit 4f7957d

Browse files
committed
🐛 fix WASM memory allocation and add ops buffer overflow check
Size memory dynamically based on clayterm_size() instead of hardcoding 256 pages, which was too small for large terminals. Prevent silent memory corruption with a bounds check in pack() to make sure we do not pack more operations that can fit in the buffer. 1MB should fit thousands of elements.
1 parent 8521cc0 commit 4f7957d

3 files changed

Lines changed: 25 additions & 3 deletions

File tree

ops.ts

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -62,8 +62,14 @@ function packString(view: DataView, bytes: Uint8Array, o: number): number {
6262
return o;
6363
}
6464

65-
export function pack(ops: Op[], mem: ArrayBufferLike, offset: number): number {
65+
export function pack(
66+
ops: Op[],
67+
mem: ArrayBufferLike,
68+
offset: number,
69+
limit?: number,
70+
): number {
6671
let view = new DataView(mem);
72+
let end = limit ?? mem.byteLength;
6773
let o = offset;
6874

6975
for (let op of ops) {
@@ -192,6 +198,13 @@ export function pack(ops: Op[], mem: ArrayBufferLike, offset: number): number {
192198
break;
193199
}
194200
}
201+
if (o > end) {
202+
throw new RangeError(
203+
`ops exceed buffer capacity (${o - offset} bytes packed, ${
204+
end - offset
205+
} available)`,
206+
);
207+
}
195208
}
196209

197210
return (o - offset) / 4;

term-native.ts

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ export interface Native {
1010
import { compiled } from "./wasm.ts";
1111

1212
export async function createTermNative(w: number, h: number): Promise<Native> {
13-
let memory = new WebAssembly.Memory({ initial: 256 });
13+
let memory = new WebAssembly.Memory({ initial: 2 });
1414
let exports: Record<string, CallableFunction> = {};
1515

1616
let instance = await WebAssembly.instantiate(compiled, {
@@ -49,6 +49,15 @@ export async function createTermNative(w: number, h: number): Promise<Native> {
4949

5050
let heap = ct.__heap_base.value as number;
5151
let size = ct.clayterm_size(w, h);
52+
53+
// grow memory to fit heap + state + ops buffer (1MB headroom for ops)
54+
let needed = heap + size + 1024 * 1024;
55+
let pages = Math.ceil(needed / 65536);
56+
let current = memory.buffer.byteLength / 65536;
57+
if (pages > current) {
58+
memory.grow(pages - current);
59+
}
60+
5261
let statePtr = ct.init(heap, w, h);
5362
let opsBuf = (heap + size + 3) & ~3;
5463

term.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ export async function createTerm(options: TermOptions): Promise<Term> {
2020

2121
return {
2222
render(ops: Op[]): Uint8Array {
23-
let len = pack(ops, memory.buffer, opsBuf);
23+
let len = pack(ops, memory.buffer, opsBuf, memory.buffer.byteLength);
2424
reduce(statePtr, opsBuf, len);
2525
return new Uint8Array(
2626
memory.buffer,

0 commit comments

Comments
 (0)