From f1b1fd30bbb15c667877726744ca0a78e0be2d81 Mon Sep 17 00:00:00 2001 From: Larry Ewing Date: Thu, 30 Oct 2025 17:48:55 -0500 Subject: [PATCH 1/7] Make sure we treat tread the ptr as unsigned --- src/mono/browser/runtime/http.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/mono/browser/runtime/http.ts b/src/mono/browser/runtime/http.ts index 149c3c05a473f6..a764a76b88ff67 100644 --- a/src/mono/browser/runtime/http.ts +++ b/src/mono/browser/runtime/http.ts @@ -95,7 +95,7 @@ export function http_wasm_transform_stream_write (controller: HttpController, bu if (BuildConfiguration === "Debug") commonAsserts(controller); mono_assert(bufferLength > 0, "expected bufferLength > 0"); // the bufferPtr is pinned by the caller - const view = new Span(bufferPtr, bufferLength, MemoryViewType.Byte); + const view = new Span(((bufferPtr as any) >>> 0), bufferLength, MemoryViewType.Byte); const copy = view.slice() as Uint8Array; return wrap_as_cancelable_promise(async () => { mono_assert(controller.streamWriter, "expected streamWriter"); @@ -136,7 +136,7 @@ export function http_wasm_fetch_stream (controller: HttpController, url: string, export function http_wasm_fetch_bytes (controller: HttpController, url: string, header_names: string[], header_values: string[], option_names: string[], option_values: any[], bodyPtr: VoidPtr, bodyLength: number): ControllablePromise { if (BuildConfiguration === "Debug") commonAsserts(controller); // the bodyPtr is pinned by the caller - const view = new Span(bodyPtr, bodyLength, MemoryViewType.Byte); + const view = new Span(((bodyPtr as any) >>> 0), bodyLength, MemoryViewType.Byte); const copy = view.slice() as Uint8Array; return http_wasm_fetch(controller, url, header_names, header_values, option_names, option_values, copy); } From 3ae2b939b0b47445d87e19dabbaf7ba203816d33 Mon Sep 17 00:00:00 2001 From: Larry Ewing Date: Thu, 30 Oct 2025 18:54:14 -0500 Subject: [PATCH 2/7] fix streamed_response_bytes too --- src/mono/browser/runtime/http.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/mono/browser/runtime/http.ts b/src/mono/browser/runtime/http.ts index a764a76b88ff67..9e8c755893aea6 100644 --- a/src/mono/browser/runtime/http.ts +++ b/src/mono/browser/runtime/http.ts @@ -95,7 +95,7 @@ export function http_wasm_transform_stream_write (controller: HttpController, bu if (BuildConfiguration === "Debug") commonAsserts(controller); mono_assert(bufferLength > 0, "expected bufferLength > 0"); // the bufferPtr is pinned by the caller - const view = new Span(((bufferPtr as any) >>> 0), bufferLength, MemoryViewType.Byte); + const view = new Span(((bufferPtr as any) >>> 0), bufferLength, MemoryViewType.Byte); const copy = view.slice() as Uint8Array; return wrap_as_cancelable_promise(async () => { mono_assert(controller.streamWriter, "expected streamWriter"); @@ -136,7 +136,7 @@ export function http_wasm_fetch_stream (controller: HttpController, url: string, export function http_wasm_fetch_bytes (controller: HttpController, url: string, header_names: string[], header_values: string[], option_names: string[], option_values: any[], bodyPtr: VoidPtr, bodyLength: number): ControllablePromise { if (BuildConfiguration === "Debug") commonAsserts(controller); // the bodyPtr is pinned by the caller - const view = new Span(((bodyPtr as any) >>> 0), bodyLength, MemoryViewType.Byte); + const view = new Span(((bodyPtr as any) >>> 0), bodyLength, MemoryViewType.Byte); const copy = view.slice() as Uint8Array; return http_wasm_fetch(controller, url, header_names, header_values, option_names, option_values, copy); } @@ -239,7 +239,7 @@ export function http_wasm_get_response_bytes (controller: HttpController, view: export function http_wasm_get_streamed_response_bytes (controller: HttpController, bufferPtr: VoidPtr, bufferLength: number): ControllablePromise { if (BuildConfiguration === "Debug") commonAsserts(controller); // the bufferPtr is pinned by the caller - const view = new Span(bufferPtr, bufferLength, MemoryViewType.Byte); + const view = new Span(((bufferPtr as any) >>> 0), bufferLength, MemoryViewType.Byte); return wrap_as_cancelable_promise(async () => { await controller.responsePromise; mono_assert(controller.response, "expected response"); From cd942747bfe28afcb1030ef1fa30d9c809a2c91b Mon Sep 17 00:00:00 2001 From: Larry Ewing Date: Fri, 7 Nov 2025 08:40:27 -0600 Subject: [PATCH 3/7] Add a few more defensive shifts --- src/mono/browser/runtime/marshal.ts | 1 + src/mono/browser/runtime/web-socket.ts | 8 ++++---- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/src/mono/browser/runtime/marshal.ts b/src/mono/browser/runtime/marshal.ts index e2028be1a4f2fd..5b500999c2285e 100644 --- a/src/mono/browser/runtime/marshal.ts +++ b/src/mono/browser/runtime/marshal.ts @@ -468,6 +468,7 @@ export const enum MemoryViewType { abstract class MemoryView implements IMemoryView { protected constructor (public _pointer: VoidPtr, public _length: number, public _viewType: MemoryViewType) { + this._pointer = ((this._pointer as any) >>> 0); } abstract dispose(): void; diff --git a/src/mono/browser/runtime/web-socket.ts b/src/mono/browser/runtime/web-socket.ts index fd18955f68dc01..14a93af9fb1fb2 100644 --- a/src/mono/browser/runtime/web-socket.ts +++ b/src/mono/browser/runtime/web-socket.ts @@ -72,7 +72,7 @@ export function ws_wasm_create (uri: string, sub_protocols: string[] | null, rec ws[wasm_ws_pending_open_promise] = open_promise_control; ws[wasm_ws_pending_send_promises] = []; ws[wasm_ws_pending_close_promises] = []; - ws[wasm_ws_receive_status_ptr] = receive_status_ptr; + ws[wasm_ws_receive_status_ptr] = ((receive_status_ptr as any) >>> 0); ws.binaryType = "arraybuffer"; const local_on_open = () => { try { @@ -185,7 +185,7 @@ export function ws_wasm_send (ws: WebSocketExtension, buffer_ptr: VoidPtr, buffe return resolvedPromise(); } - const buffer_view = new Uint8Array(localHeapViewU8().buffer, buffer_ptr, buffer_length); + const buffer_view = new Uint8Array(localHeapViewU8().buffer, ((buffer_ptr as any) >>> 0), buffer_length); const whole_buffer = web_socket_send_buffering(ws, buffer_view, message_type, end_of_message); if (!end_of_message || !whole_buffer) { @@ -232,7 +232,7 @@ export function ws_wasm_receive (ws: WebSocketExtension, buffer_ptr: VoidPtr, bu const { promise, promise_control } = createPromiseController(); const receive_promise_control = promise_control as ReceivePromiseControl; - receive_promise_control.buffer_ptr = buffer_ptr; + receive_promise_control.buffer_ptr = ((buffer_ptr as any) >>> 0); receive_promise_control.buffer_length = buffer_length; receive_promise_queue.enqueue(receive_promise_control); @@ -402,7 +402,7 @@ function web_socket_receive_buffering (ws: WebSocketExtension, event_queue: Queu const count = Math.min(buffer_length, event.data.length - event.offset); if (count > 0) { const sourceView = event.data.subarray(event.offset, event.offset + count); - const bufferView = new Uint8Array(localHeapViewU8().buffer, buffer_ptr, buffer_length); + const bufferView = new Uint8Array(localHeapViewU8().buffer, ((buffer_ptr as any) >>> 0), buffer_length); bufferView.set(sourceView, 0); event.offset += count; } From 5071584793a684a7dd88a8f52c443240657d890c Mon Sep 17 00:00:00 2001 From: Larry Ewing Date: Fri, 7 Nov 2025 09:20:22 -0600 Subject: [PATCH 4/7] Use fixupPointer --- src/mono/browser/runtime/marshal.ts | 1 - src/mono/browser/runtime/web-socket.ts | 10 +++++----- 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/src/mono/browser/runtime/marshal.ts b/src/mono/browser/runtime/marshal.ts index 5b500999c2285e..e2028be1a4f2fd 100644 --- a/src/mono/browser/runtime/marshal.ts +++ b/src/mono/browser/runtime/marshal.ts @@ -468,7 +468,6 @@ export const enum MemoryViewType { abstract class MemoryView implements IMemoryView { protected constructor (public _pointer: VoidPtr, public _length: number, public _viewType: MemoryViewType) { - this._pointer = ((this._pointer as any) >>> 0); } abstract dispose(): void; diff --git a/src/mono/browser/runtime/web-socket.ts b/src/mono/browser/runtime/web-socket.ts index 14a93af9fb1fb2..1f29488d36a8f1 100644 --- a/src/mono/browser/runtime/web-socket.ts +++ b/src/mono/browser/runtime/web-socket.ts @@ -6,7 +6,7 @@ import WasmEnableThreads from "consts:wasmEnableThreads"; import { prevent_timer_throttling } from "./scheduling"; import { Queue } from "./queue"; import { ENVIRONMENT_IS_NODE, ENVIRONMENT_IS_SHELL, createPromiseController, loaderHelpers, mono_assert, Module } from "./globals"; -import { setI32, localHeapViewU8, forceThreadMemoryViewRefresh } from "./memory"; +import { setI32, localHeapViewU8, forceThreadMemoryViewRefresh, fixupPointer } from "./memory"; import { VoidPtr } from "./types/emscripten"; import { PromiseController } from "./types/internal"; import { mono_log_warn } from "./logging"; @@ -72,7 +72,7 @@ export function ws_wasm_create (uri: string, sub_protocols: string[] | null, rec ws[wasm_ws_pending_open_promise] = open_promise_control; ws[wasm_ws_pending_send_promises] = []; ws[wasm_ws_pending_close_promises] = []; - ws[wasm_ws_receive_status_ptr] = ((receive_status_ptr as any) >>> 0); + ws[wasm_ws_receive_status_ptr] = fixupPointer(receive_status_ptr, 0); ws.binaryType = "arraybuffer"; const local_on_open = () => { try { @@ -185,7 +185,7 @@ export function ws_wasm_send (ws: WebSocketExtension, buffer_ptr: VoidPtr, buffe return resolvedPromise(); } - const buffer_view = new Uint8Array(localHeapViewU8().buffer, ((buffer_ptr as any) >>> 0), buffer_length); + const buffer_view = new Uint8Array(localHeapViewU8().buffer, fixupPointer(buffer_ptr, 0), buffer_length); const whole_buffer = web_socket_send_buffering(ws, buffer_view, message_type, end_of_message); if (!end_of_message || !whole_buffer) { @@ -232,7 +232,7 @@ export function ws_wasm_receive (ws: WebSocketExtension, buffer_ptr: VoidPtr, bu const { promise, promise_control } = createPromiseController(); const receive_promise_control = promise_control as ReceivePromiseControl; - receive_promise_control.buffer_ptr = ((buffer_ptr as any) >>> 0); + receive_promise_control.buffer_ptr = fixupPointer(buffer_ptr, 0); receive_promise_control.buffer_length = buffer_length; receive_promise_queue.enqueue(receive_promise_control); @@ -402,7 +402,7 @@ function web_socket_receive_buffering (ws: WebSocketExtension, event_queue: Queu const count = Math.min(buffer_length, event.data.length - event.offset); if (count > 0) { const sourceView = event.data.subarray(event.offset, event.offset + count); - const bufferView = new Uint8Array(localHeapViewU8().buffer, ((buffer_ptr as any) >>> 0), buffer_length); + const bufferView = new Uint8Array(localHeapViewU8().buffer, fixupPointer(buffer_ptr, 0), buffer_length); bufferView.set(sourceView, 0); event.offset += count; } From 38a0c4c0d731143b5c14c670d1f4d5fab48e3aa7 Mon Sep 17 00:00:00 2001 From: Larry Ewing Date: Fri, 7 Nov 2025 10:00:16 -0600 Subject: [PATCH 5/7] More defensive shifts --- src/mono/browser/runtime/marshal.ts | 3 ++- src/mono/browser/runtime/memory.ts | 12 ++++++------ 2 files changed, 8 insertions(+), 7 deletions(-) diff --git a/src/mono/browser/runtime/marshal.ts b/src/mono/browser/runtime/marshal.ts index e2028be1a4f2fd..9431c3df1e405b 100644 --- a/src/mono/browser/runtime/marshal.ts +++ b/src/mono/browser/runtime/marshal.ts @@ -5,7 +5,7 @@ import WasmEnableThreads from "consts:wasmEnableThreads"; import { js_owned_gc_handle_symbol, teardown_managed_proxy } from "./gc-handles"; import { Module, loaderHelpers, mono_assert, runtimeHelpers } from "./globals"; -import { getF32, getF64, getI16, getI32, getI64Big, getU16, getU32, getU8, setF32, setF64, setI16, setI32, setI64Big, setU16, setU32, setU8, localHeapViewF64, localHeapViewI32, localHeapViewU8, _zero_region, forceThreadMemoryViewRefresh, setB8, getB8 } from "./memory"; +import { getF32, getF64, getI16, getI32, getI64Big, getU16, getU32, getU8, setF32, setF64, setI16, setI32, setI64Big, setU16, setU32, setU8, localHeapViewF64, localHeapViewI32, localHeapViewU8, _zero_region, forceThreadMemoryViewRefresh, fixupPointer, setB8, getB8 } from "./memory"; import { mono_wasm_new_external_root } from "./roots"; import { GCHandle, JSHandle, MonoObject, MonoString, GCHandleNull, JSMarshalerArguments, JSFunctionSignature, JSMarshalerType, JSMarshalerArgument, MarshalerToJs, MarshalerToCs, WasmRoot, MarshalerType, PThreadPtr, PThreadPtrNull, VoidPtrNull } from "./types/internal"; import { TypedArray, VoidPtr } from "./types/emscripten"; @@ -468,6 +468,7 @@ export const enum MemoryViewType { abstract class MemoryView implements IMemoryView { protected constructor (public _pointer: VoidPtr, public _length: number, public _viewType: MemoryViewType) { + this._pointer = fixupPointer(_pointer, 0); } abstract dispose(): void; diff --git a/src/mono/browser/runtime/memory.ts b/src/mono/browser/runtime/memory.ts index 3497f05b8b99bc..311151bb368663 100644 --- a/src/mono/browser/runtime/memory.ts +++ b/src/mono/browser/runtime/memory.ts @@ -82,13 +82,13 @@ export function setB8 (offset: MemOffset, value: number | boolean): void { if (typeof (value) === "number") assert_int_in_range(value, 0, 1); receiveWorkerHeapViews(); - Module.HEAPU8[offset] = boolValue ? 1 : 0; + Module.HEAPU8[offset >>> 0] = boolValue ? 1 : 0; } export function setU8 (offset: MemOffset, value: number): void { assert_int_in_range(value, 0, 0xFF); receiveWorkerHeapViews(); - Module.HEAPU8[offset] = value; + Module.HEAPU8[offset >>> 0] = value; } export function setU16 (offset: MemOffset, value: number): void { @@ -122,7 +122,7 @@ export function setU32 (offset: MemOffset, value: NumberOrPointer): void { export function setI8 (offset: MemOffset, value: number): void { assert_int_in_range(value, -0x80, 0x7F); receiveWorkerHeapViews(); - Module.HEAP8[offset] = value; + Module.HEAP8[offset >>> 0] = value; } export function setI16 (offset: MemOffset, value: number): void { @@ -210,12 +210,12 @@ export function getB32 (offset: MemOffset): boolean { export function getB8 (offset: MemOffset): boolean { receiveWorkerHeapViews(); - return !!(Module.HEAPU8[offset]); + return !!(Module.HEAPU8[offset] >>> 0); } export function getU8 (offset: MemOffset): number { receiveWorkerHeapViews(); - return Module.HEAPU8[offset]; + return Module.HEAPU8[offset >>> 0]; } export function getU16 (offset: MemOffset): number { @@ -256,7 +256,7 @@ export function getF64_unaligned (offset: MemOffset): number { export function getI8 (offset: MemOffset): number { receiveWorkerHeapViews(); - return Module.HEAP8[offset]; + return Module.HEAP8[offset >>> 0]; } export function getI16 (offset: MemOffset): number { From ba7a1c0c57c7d01d09f6a28656fd4b0fac2a8dc8 Mon Sep 17 00:00:00 2001 From: Larry Ewing Date: Fri, 7 Nov 2025 10:02:18 -0600 Subject: [PATCH 6/7] remove the now redundant cast --- src/mono/browser/runtime/http.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/mono/browser/runtime/http.ts b/src/mono/browser/runtime/http.ts index 9e8c755893aea6..149c3c05a473f6 100644 --- a/src/mono/browser/runtime/http.ts +++ b/src/mono/browser/runtime/http.ts @@ -95,7 +95,7 @@ export function http_wasm_transform_stream_write (controller: HttpController, bu if (BuildConfiguration === "Debug") commonAsserts(controller); mono_assert(bufferLength > 0, "expected bufferLength > 0"); // the bufferPtr is pinned by the caller - const view = new Span(((bufferPtr as any) >>> 0), bufferLength, MemoryViewType.Byte); + const view = new Span(bufferPtr, bufferLength, MemoryViewType.Byte); const copy = view.slice() as Uint8Array; return wrap_as_cancelable_promise(async () => { mono_assert(controller.streamWriter, "expected streamWriter"); @@ -136,7 +136,7 @@ export function http_wasm_fetch_stream (controller: HttpController, url: string, export function http_wasm_fetch_bytes (controller: HttpController, url: string, header_names: string[], header_values: string[], option_names: string[], option_values: any[], bodyPtr: VoidPtr, bodyLength: number): ControllablePromise { if (BuildConfiguration === "Debug") commonAsserts(controller); // the bodyPtr is pinned by the caller - const view = new Span(((bodyPtr as any) >>> 0), bodyLength, MemoryViewType.Byte); + const view = new Span(bodyPtr, bodyLength, MemoryViewType.Byte); const copy = view.slice() as Uint8Array; return http_wasm_fetch(controller, url, header_names, header_values, option_names, option_values, copy); } @@ -239,7 +239,7 @@ export function http_wasm_get_response_bytes (controller: HttpController, view: export function http_wasm_get_streamed_response_bytes (controller: HttpController, bufferPtr: VoidPtr, bufferLength: number): ControllablePromise { if (BuildConfiguration === "Debug") commonAsserts(controller); // the bufferPtr is pinned by the caller - const view = new Span(((bufferPtr as any) >>> 0), bufferLength, MemoryViewType.Byte); + const view = new Span(bufferPtr, bufferLength, MemoryViewType.Byte); return wrap_as_cancelable_promise(async () => { await controller.responsePromise; mono_assert(controller.response, "expected response"); From 886a06857614aed84e49fc3efbf35019a5d5d737 Mon Sep 17 00:00:00 2001 From: Larry Ewing Date: Fri, 7 Nov 2025 11:30:35 -0600 Subject: [PATCH 7/7] More fixups --- src/mono/browser/runtime/startup.ts | 6 +++--- src/mono/browser/runtime/strings.ts | 5 ++++- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/src/mono/browser/runtime/startup.ts b/src/mono/browser/runtime/startup.ts index 7fc2cefef1ca7d..3eaae69ec3fe74 100644 --- a/src/mono/browser/runtime/startup.ts +++ b/src/mono/browser/runtime/startup.ts @@ -27,7 +27,7 @@ import { populateEmscriptenPool, mono_wasm_init_threads } from "./pthreads"; import { currentWorkerThreadEvents, dotnetPthreadCreated, initWorkerThreadEvents, monoThreadInfo } from "./pthreads"; import { mono_wasm_pthread_ptr, update_thread_info } from "./pthreads"; import { jiterpreter_allocate_tables } from "./jiterpreter-support"; -import { localHeapViewU8, malloc, setU32 } from "./memory"; +import { localHeapViewU8, malloc, setU32, fixupPointer } from "./memory"; import { assertNoProxies } from "./gc-handles"; import { runtimeList } from "./exports"; import { nativeAbort, nativeExit } from "./run"; @@ -594,12 +594,12 @@ export function mono_wasm_asm_loaded (assembly_name: CharPtr, assembly_ptr: numb return; const heapU8 = localHeapViewU8(); const assembly_name_str = assembly_name !== CharPtrNull ? utf8ToString(assembly_name).concat(".dll") : ""; - const assembly_data = new Uint8Array(heapU8.buffer, assembly_ptr, assembly_len); + const assembly_data = new Uint8Array(heapU8.buffer, fixupPointer(assembly_ptr, 0), assembly_len); const assembly_b64 = toBase64StringImpl(assembly_data); let pdb_b64; if (pdb_ptr) { - const pdb_data = new Uint8Array(heapU8.buffer, pdb_ptr, pdb_len); + const pdb_data = new Uint8Array(heapU8.buffer, fixupPointer(pdb_ptr, 0), pdb_len); pdb_b64 = toBase64StringImpl(pdb_data); } diff --git a/src/mono/browser/runtime/strings.ts b/src/mono/browser/runtime/strings.ts index 2b52f82b0320b8..67244a40637d9b 100644 --- a/src/mono/browser/runtime/strings.ts +++ b/src/mono/browser/runtime/strings.ts @@ -7,7 +7,7 @@ import { mono_wasm_new_root, mono_wasm_new_root_buffer } from "./roots"; import { MonoString, MonoStringNull, WasmRoot, WasmRootBuffer } from "./types/internal"; import { Module } from "./globals"; import cwraps from "./cwraps"; -import { isSharedArrayBuffer, localHeapViewU8, getU32_local, setU16_local, localHeapViewU32, getU16_local, localHeapViewU16, _zero_region, malloc, free } from "./memory"; +import { isSharedArrayBuffer, localHeapViewU8, getU32_local, setU16_local, localHeapViewU32, getU16_local, localHeapViewU16, _zero_region, malloc, free, fixupPointer } from "./memory"; import { NativePointer, CharPtr, VoidPtr } from "./types/emscripten"; export const interned_js_string_table = new Map(); @@ -69,6 +69,7 @@ export function utf8ToString (ptr: CharPtr): string { } export function utf8BufferToString (heapOrArray: Uint8Array, idx: number, maxBytesToRead: number): string { + idx = fixupPointer(idx, 0); const endIdx = idx + maxBytesToRead; let endPtr = idx; while (heapOrArray[endPtr] && !(endPtr >= endIdx)) ++endPtr; @@ -83,6 +84,8 @@ export function utf8BufferToString (heapOrArray: Uint8Array, idx: number, maxByt } export function utf16ToString (startPtr: number, endPtr: number): string { + startPtr = fixupPointer(startPtr, 0); + endPtr = fixupPointer(endPtr, 0); if (_text_decoder_utf16) { const subArray = viewOrCopy(localHeapViewU8(), startPtr as any, endPtr as any); return _text_decoder_utf16.decode(subArray);