netliomax25-code opened PR #15085 from netliomax25-code:c-api-cpp-self-assignment to bytecodealliance:main:
Several copy assignment operators in the C++ headers release the current value before cloning from
other, so whenotheris*thisthe clone reads the value that was just freed. Self-assignment usually arrives through an alias or a generic algorithm (for examplestd::fillover a range that contains the fill value). For component values the same thing happens whenotheris owned by the destination, which is a natural way to unwrap nested values:Val v = WitOption(Val::string("...")); v = *v.get_option().value(); // clones from the freed option payloadThe affected operators are
VAL_REPR(componentVal,List,Record,Tuple,Variant,WitOption,WitResult,Flag,Flags,Map),WASMTIME_REF_WRAPPER(EqRef,StructRef,ArrayRef), coreVal,HeapType,RefType,StorageType,FieldType,ValType, componentValTypeandComponentItem. I checked each one with a small program that self-assigns a value, linked against a C API built with-Zsanitizer=address. All of them report heap-use-after-free exceptListandRecord, where self-assignment silently leaves an empty value. ForExternRefand a coreValholding an externref, the assignment drops the last strong reference to the root's liveness flag and then clones it back from the raw pointer, so the flag is later freed while the store still holds aWeakto it andStore::gcreads freed memory.Each operator now clones
otherbefore releasing the old value. Most of them copy-construct and then move-assign, since their move assignment already releases the old value.VAL_REPRsnapshots the raw value instead, because constructor overload resolution there has to considerWitOption(std::optional<Val>)whileValis still incomplete.ValTypeclones into a local first, since its move assignment doesn't release the previouswasmtime_ty(that looks like a separate leak, which I left alone here).I added a
component.assign_from_self_or_childtest tocrates/c-api/tests/component/values.cc. Without the fix it fails (the list comes back with size 0, then reading the freed option payload trips an assertion). With the fix the C API ctest suite passes (181 tests, Debug,-DBUILD_SHARED_LIBS=ON -DBUILD_TESTS=ON, macOS aarch64), the gtest binary also passes when built with-fsanitize=address, and clang-format 22 is clean. Since the C++ API is tier 2 and the trigger is in embedder code, I don't think this falls under the security process, so I'm sending it as a regular PR.
netliomax25-code removed the review request for wasmtime-core-reviewers from PR #15085.
netliomax25-code requested wasmtime-core-reviewers for a review on PR #15085.
netliomax25-code requested dicej for a review on PR #15085.
github-actions[bot] added the label wasmtime:c-api on PR #15085.
:thumbs_up: alexcrichton submitted PR review.
alexcrichton commented on PR #15085:
TIL a new feature of C++...
alexcrichton added PR #15085 c-api: fix use-after-free in C++ copy assignment operators to the merge queue.
:check: alexcrichton merged PR #15085.
alexcrichton removed PR #15085 c-api: fix use-after-free in C++ copy assignment operators from the merge queue.
Last updated: Oct 31 2026 at 23:03 UTC