{"record":{"id":"f6315b798dcbc4a1","repo":"wasmerio/wasmer","slug":"failed-to-free-closure-index-e","errorCode":null,"errorMessage":"Failed to free closure index: {e}","messagePattern":"Failed to free closure index: (.+?)","errorType":"panic","errorClass":null,"httpStatus":null,"severity":"error","filePath":"lib/wasix/src/syscalls/wasix/closure_free.rs","lineNumber":23,"sourceCode":"/// After calling this it is undefined behavior to call the function at the given index.\n#[instrument(level = \"trace\", fields(%closure), ret)]\npub fn closure_free(\n    mut ctx: FunctionEnvMut<'_, WasiEnv>,\n    closure: u32,\n) -> Result<Errno, WasiError> {\n    WasiEnv::do_pending_operations(&mut ctx)?;\n\n    let (env, mut store) = ctx.data_and_store_mut();\n\n    let Some(linker) = env.inner().linker().cloned() else {\n        error!(\"Closures only work for dynamic modules.\");\n        return Ok(Errno::Notsup);\n    };\n\n    let free_result = linker.free_closure_index(&mut ctx, closure);\n    if let Err(e) = free_result {\n        // Should never happen\n        panic!(\"Failed to free closure index: {e}\");\n    }\n\n    return Ok(Errno::Success);\n}\n","sourceCodeStart":5,"sourceCodeEnd":28,"githubUrl":"https://github.com/wasmerio/wasmer/blob/8c4b9ee9d33fb2068863fbb3d328683e7e6ff7f5/lib/wasix/src/syscalls/wasix/closure_free.rs#L5-L28","documentation":"The `closure_free` syscall asks the linker to release a previously allocated closure slot in the `__indirect_function_table`. The library considers any failure from `free_closure_index` an impossible state (\"Should never happen\"), so instead of returning an errno it panics with the underlying error embedded in the message.","triggerScenarios":"Calling the `closure_free` syscall with a closure index that is invalid, already freed, or out of range; freeing a closure in an environment whose linker state is inconsistent (e.g. after a failed dynamic-link operation); double-free of the same closure index from two threads.","commonSituations":"Guest code bugs that free a closure twice or free a never-prepared index; race conditions where two WASIX threads free closures concurrently; modules compiled against a different closure ABI than the runtime supports.","solutions":["Fix the guest module to free each closure index exactly once and only after `closure_prepare` succeeded","Verify the closure index passed to the syscall is within the allocated range before freeing","Serialize closure allocation/free across threads or use the runtime's locking helpers to prevent double-free races","Rebuild the guest module against the current wasix closure ABI if versions mismatch"],"exampleFix":null,"handlingStrategy":"validation","validationCode":"// Guest/host side: only free indices that were successfully prepared and not yet freed\nif !allocated_closures.contains(&closure) {\n    return; // skip free instead of calling closure_free with a bogus index\n}","typeGuard":null,"tryCatchPattern":"let result = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| closure_free(ctx, closure)));\nif result.is_err() { /* log double-free/invalid index; recover by rebuilding the instance */ }","preventionTips":["Track prepared closure indices and free each exactly once","Serialize closure alloc/free across WASIX threads","Validate indices are in range before the syscall","Match the guest's closure ABI version to the runtime's"],"tags":["wasix","syscalls","closures","double-free","panic"],"backgroundTag":"closure-free-failed","analyzedSha":"8c4b9ee9d33fb2068863fbb3d328683e7e6ff7f5","analyzedAt":"2026-09-01T23:06:31.009Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-09T06:17:21.866Z"}