Skip to content

Fix double-free of cached WASM module in fizzy vm_manager - #862

Open
pgarciagon wants to merge 1 commit into
koinos:masterfrom
pgarciagon:fix/vm-manager-module-double-free
Open

Fix double-free of cached WASM module in fizzy vm_manager#862
pgarciagon wants to merge 1 commit into
koinos:masterfrom
pgarciagon:fix/vm-manager-module-double-free

Conversation

@pgarciagon

Copy link
Copy Markdown
Contributor

Problem

fizzy_vm_backend::run() caches a parsed WASM module in a module_guard, whose destructor frees it with fizzy_free_module. The same module pointer is then passed to fizzy_resolve_instantiate, which per fizzy's C API takes ownership of the module and frees it via fizzy_free_instance:

fizzy.h: "The instance takes ownership of the module, i.e. fizzy_free_module() must not be called on the [module]"

So the same FizzyModule is freed twice.

A double free is undefined behavior that surfaces only under a strict allocator. It is caught immediately by AddressSanitizer, and crashes deterministically on the macOS 26 SDK toolchain — EXC_BAD_ACCESS in fizzy::Module::~Module during module_cache teardown, showing up as the controller_tests/read_contract_tests failure and in any full block execution. Older/other allocators (older macOS, Linux) tolerate it silently, which is why it has gone unnoticed.

AddressSanitizer

ERROR: AddressSanitizer: attempting double-free
  #10 fizzy_free_module capi.cpp:478
  #11 koinos::vm_manager::fizzy::module_guard::~module_guard() module_cache.hpp
  ...  module_cache::~module_cache()
freed by thread T0 here:
  #16 fizzy_free_instance capi.cpp:683   (fizzy::Instance dtor frees the module)
  ...  fizzy_vm_backend::run() fizzy_vm_backend.cpp:368
previously allocated by thread T0 here:
  #9 fizzy::parse(...) parser.cpp:547
  ...  parse_bytecode(...) fizzy_vm_backend.cpp:125

Fix

  1. Instantiate a fizzy_clone_module() copy, so the FizzyInstance owns its own module and the cache keeps the original.
  2. Make module_guard non-copyable and non-movable — it is the sole owner of a raw FizzyModule and copying it would reintroduce the double free.

Testing

AddressSanitizer build (Debug, -fsanitize=address): read_contract_tests and the full chain suite pass with zero ASan errors. Release full suite also green.

Note: this is independent of the state-delta replay work (#860 / #861); it is a pre-existing vm_manager bug. The same vendored vm_manager exists in the Teleno monolith and should get the same fix.

fizzy_vm_backend::run() caches a parsed module in a module_guard, whose
destructor frees it with fizzy_free_module. The same module pointer was
then passed to fizzy_resolve_instantiate, which per fizzy's API takes
ownership of the module and frees it via fizzy_free_instance. The same
FizzyModule was therefore freed twice.

A double free is undefined behavior that manifests only under a strict
allocator: it is caught immediately by AddressSanitizer and crashes on the
macOS 26 SDK toolchain (EXC_BAD_ACCESS in fizzy::Module::~Module during
module_cache teardown, surfacing as the read_contract_tests failure and any
full block execution), while older/other allocators tolerate it silently.

Instantiate a fizzy_clone_module() copy so the FizzyInstance owns its own
module and the cache keeps the original. Also make module_guard
non-copyable and non-movable: it is the sole owner of a raw FizzyModule and
copying it would reintroduce the double free.

Verified with an AddressSanitizer build: read_contract_tests and the full
chain suite pass with zero ASan errors.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants