fix(wasi-shim): re-read memory.buffer after await in all host functions

WebAssembly memory can grow (and return a new ArrayBuffer) during an
async host function call. Reading memory.buffer before await and using
it after the await causes host functions (kv_get / crypto_decrypt /
crypto_sign_rs256 / http_request) to write into a detached buffer,
so the WASM side reads zero bytes → empty string → JSON parse failure.

Fix: read inputs before await using the current buffer snapshot,
then call memory.buffer again after the await to write the result.
For crypto_sign_rs256 and http_request, input arrays are copied
before await so the snapshot can be released.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
2026-04-24 17:13:03 +08:00
parent 83a01fe028
commit e2221161a8
+17 -17
View File
@@ -221,15 +221,17 @@ export function createWasiShim(stdinData: string, hostFunctions?: WasiHostFuncti
headersPtr: number, headersLen: number, bodyPtr: number, bodyLen: number, headersPtr: number, headersLen: number, bodyPtr: number, bodyLen: number,
outPtr: number, outLenPtr: number): Promise<number> => { outPtr: number, outLenPtr: number): Promise<number> => {
if (!memory) return 1; if (!memory) return 1;
const buf = memory.buffer; // 在 await 前讀完所有輸入(memory.buffer 在 await 後可能因 grow 而失效)
const snapBuf = memory.buffer;
const dec = new TextDecoder(); const dec = new TextDecoder();
const url = dec.decode(new Uint8Array(buf, urlPtr, urlLen)); const url = dec.decode(new Uint8Array(snapBuf, urlPtr, urlLen));
const method = dec.decode(new Uint8Array(buf, methodPtr, methodLen)); const method = dec.decode(new Uint8Array(snapBuf, methodPtr, methodLen));
const headers = dec.decode(new Uint8Array(buf, headersPtr, headersLen)); const headers = dec.decode(new Uint8Array(snapBuf, headersPtr, headersLen));
const body = dec.decode(new Uint8Array(buf, bodyPtr, bodyLen)); const body = dec.decode(new Uint8Array(snapBuf, bodyPtr, bodyLen));
try { try {
const result = await hostFunctions!.http_request!(url, method, headers, body); const result = await hostFunctions!.http_request!(url, method, headers, body);
return writeOut(buf, outPtr, outLenPtr, new TextEncoder().encode(result)); // await 後重新拿 memory.buffergrow 會產生新的 ArrayBuffer
return writeOut(memory.buffer, outPtr, outLenPtr, new TextEncoder().encode(result));
} catch { } catch {
return 1; return 1;
} }
@@ -240,12 +242,11 @@ export function createWasiShim(stdinData: string, hostFunctions?: WasiHostFuncti
kv_get: hostFunctions?.kv_get kv_get: hostFunctions?.kv_get
? async (keyPtr: number, keyLen: number, outPtr: number, outLenPtr: number): Promise<number> => { ? async (keyPtr: number, keyLen: number, outPtr: number, outLenPtr: number): Promise<number> => {
if (!memory) return 1; if (!memory) return 1;
const buf = memory.buffer; const key = new TextDecoder().decode(new Uint8Array(memory.buffer, keyPtr, keyLen));
const key = new TextDecoder().decode(new Uint8Array(buf, keyPtr, keyLen));
try { try {
const result = await hostFunctions!.kv_get!(key); const result = await hostFunctions!.kv_get!(key);
if (result === null) return 2; if (result === null) return 2;
return writeOut(buf, outPtr, outLenPtr, new TextEncoder().encode(result)); return writeOut(memory.buffer, outPtr, outLenPtr, new TextEncoder().encode(result));
} catch { } catch {
return 1; return 1;
} }
@@ -258,13 +259,12 @@ export function createWasiShim(stdinData: string, hostFunctions?: WasiHostFuncti
? async (encPtr: number, encLen: number, ivPtr: number, ivLen: number, ? async (encPtr: number, encLen: number, ivPtr: number, ivLen: number,
outPtr: number, outLenPtr: number): Promise<number> => { outPtr: number, outLenPtr: number): Promise<number> => {
if (!memory) return 1; if (!memory) return 1;
const buf = memory.buffer;
const dec = new TextDecoder(); const dec = new TextDecoder();
const encB64 = dec.decode(new Uint8Array(buf, encPtr, encLen)); const encB64 = dec.decode(new Uint8Array(memory.buffer, encPtr, encLen));
const ivB64 = dec.decode(new Uint8Array(buf, ivPtr, ivLen)); const ivB64 = dec.decode(new Uint8Array(memory.buffer, ivPtr, ivLen));
try { try {
const plaintext = await hostFunctions!.crypto_decrypt!(encB64, ivB64); const plaintext = await hostFunctions!.crypto_decrypt!(encB64, ivB64);
return writeOut(buf, outPtr, outLenPtr, new TextEncoder().encode(plaintext)); return writeOut(memory.buffer, outPtr, outLenPtr, new TextEncoder().encode(plaintext));
} catch { } catch {
return 1; return 1;
} }
@@ -276,12 +276,12 @@ export function createWasiShim(stdinData: string, hostFunctions?: WasiHostFuncti
? async (dataPtr: number, dataLen: number, pkcs8Ptr: number, pkcs8Len: number, ? async (dataPtr: number, dataLen: number, pkcs8Ptr: number, pkcs8Len: number,
outPtr: number, outLenPtr: number): Promise<number> => { outPtr: number, outLenPtr: number): Promise<number> => {
if (!memory) return 1; if (!memory) return 1;
const buf = memory.buffer; // await 前複製 typed array(避免 memory grow 後 buffer 失效)
const data = new Uint8Array(new Uint8Array(buf, dataPtr, dataLen)); const data = new Uint8Array(new Uint8Array(memory.buffer, dataPtr, dataLen));
const pkcs8 = new Uint8Array(new Uint8Array(buf, pkcs8Ptr, pkcs8Len)); const pkcs8 = new Uint8Array(new Uint8Array(memory.buffer, pkcs8Ptr, pkcs8Len));
try { try {
const sig = await hostFunctions!.crypto_sign_rs256!(data, pkcs8); const sig = await hostFunctions!.crypto_sign_rs256!(data, pkcs8);
return writeOut(buf, outPtr, outLenPtr, sig); return writeOut(memory.buffer, outPtr, outLenPtr, sig);
} catch { } catch {
return 1; return 1;
} }