{"record":{"id":"191c89d416ce111d","repo":"RightNow-AI/openfang","slug":"host-call-response-exceeds-memory-bounds","errorCode":null,"errorMessage":"host_call: response exceeds memory bounds","messagePattern":"host_call: response exceeds memory bounds","errorType":"validation","errorClass":"SandboxError","httpStatus":null,"severity":"error","filePath":"crates/openfang-runtime/src/sandbox.rs","lineNumber":347,"sourceCode":"\n                    // Allocate space in guest for response\n                    let alloc_fn = caller\n                        .get_export(\"alloc\")\n                        .and_then(|e| e.into_func())\n                        .ok_or_else(|| format_err!(\"no alloc export\"))?;\n                    let alloc_typed = alloc_fn.typed::<i32, i32>(&caller)?;\n                    let ptr = alloc_typed.call(&mut caller, len)?;\n\n                    // Write response into guest memory\n                    let memory = caller\n                        .get_export(\"memory\")\n                        .and_then(|e| e.into_memory())\n                        .ok_or_else(|| format_err!(\"no memory export\"))?;\n                    let mem_data = memory.data_mut(&mut caller);\n                    let dest_start = ptr as usize;\n                    let dest_end = dest_start + response_bytes.len();\n                    if dest_end > mem_data.len() {\n                        bail!(\"host_call: response exceeds memory bounds\");\n                    }\n                    mem_data[dest_start..dest_end].copy_from_slice(&response_bytes);\n\n                    // Pack (ptr, len) into i64\n                    Ok(((ptr as i64) << 32) | (len as i64))\n                },\n            )\n            .map_err(|e| SandboxError::Compilation(e.to_string()))?;\n\n        // host_log: lightweight logging — no capability check required.\n        linker\n            .func_wrap(\n                \"openfang\",\n                \"host_log\",\n                |mut caller: Caller<'_, GuestState>,\n                 level: i32,\n                 msg_ptr: i32,\n                 msg_len: i32|","sourceCodeStart":329,"sourceCodeEnd":365,"githubUrl":"https://github.com/RightNow-AI/openfang/blob/acf2587e46be174c10200489c9a2d23a39a98aeb/crates/openfang-runtime/src/sandbox.rs#L329-L365","documentation":"When the guest invokes host_call, the host serializes the response, asks the guest's exported alloc() for a destination buffer, then copies the response JSON into guest memory. If the returned pointer plus the response length exceeds the guest linear memory size, the host bails instead of writing out of bounds. This usually means the guest's alloc function returned an invalid pointer or did not reserve enough space.","triggerScenarios":"The guest alloc() returns a pointer near the end of linear memory without growing memory; alloc ignores its size argument; alloc is missing/buggy in a hand-written guest; the response payload is large and the guest memory was not grown; pointer arithmetic in the guest wraps (i32 overflow).","commonSituations":"Custom guest runtimes with minimal allocators that return fixed offsets; responses that grew after an API change (bigger JSON payloads) while the guest heap did not; guests compiled for a different ABI where alloc returns a tagged/handle value instead of a raw pointer; running with a reduced initial memory limit in the sandbox config.","solutions":["Fix the guest alloc export to return a valid, suitably sized region: reserve at least the requested size and grow memory (memory.grow) when the current allocation area cannot fit it","Verify alloc returns a raw byte pointer, not a handle or offset relative to a heap base, matching the openfang guest ABI","If the response is legitimately large, grow the guest's linear memory (compile with a larger --initial-memory or call memory.grow before host_call)","Test the guest with a worst-case-size response from dispatch to catch the failure during development, not production","Add a guest-side post-alloc check that ptr + size <= memory.size()*64KiB and fail fast with a clear trap"],"exampleFix":"// before (guest alloc that never grows memory)\npub extern \"C\" fn alloc(size: i32) -> i32 { HEAP_BASE + offset } // can overflow linear memory\n// after\npub extern \"C\" fn alloc(size: i32) -> i32 {\n    if HEAP_BASE + offset + size > mem_size() { grow_memory(size); }\n    HEAP_BASE + offset\n}","handlingStrategy":"validation","validationCode":"// guest-side, after alloc and before host_call\nlet mem_size = memory.size() * 65536;\nassert!(ptr >= 0 && (ptr as u64) + response_bytes.len() as u64 <= mem_size as u64, \"alloc returned invalid region\");","typeGuard":"fn alloc_result_ok(ptr: i32, need: usize, mem_len: usize) -> bool {\n    ptr >= 0 && (ptr as u64).checked_add(need as u64).map_or(false, |end| end <= mem_len as u64)\n}","tryCatchPattern":"match result {\n    Err(e) if e.to_string().contains(\"response exceeds memory bounds\") => {\n        eprintln!(\"guest alloc returned too-small/invalid region: {e}\");\n        // grow guest memory or fix alloc(); retry once after fix\n    }\n    other => other?,\n}","preventionTips":["Implement alloc to honor its size parameter and memory.grow when the heap is exhausted","Test guests with maximum-size responses, not just toy payloads","Return raw byte pointers from alloc per the openfang ABI, not handles/offsets","Keep a headroom test: allocate a response near the memory limit in CI","Log alloc(ptr, size) results in debug builds to catch bogus pointers early"],"tags":["wasm","wasmtime","memory-bounds","alloc"],"backgroundTag":"wasm-guest-memory-out-of-bounds","analyzedSha":"acf2587e46be174c10200489c9a2d23a39a98aeb","analyzedAt":"2026-09-02T22:42:28.464Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-10T02:17:09.455Z"}