← All issues

[12] Wasm InstanceAnchor unregistered too late in destructor

Severity: High | Component: JSC Wasm runtime | 76b3468

Rated High because the diff fixes a publish/unpublish ordering inversion: the destructor freed per-instance state before tearing down the thread-safe handle the compiler thread uses to recover the instance, leaving a wide race window during which a compiler-thread profile merge could read freed baselineData slots.

~JSWebAssemblyInstance ran unregisterMirror, clearJSCallICs, and std::destroy_at loops over importFunctionInfos, tables, and baselineDatas BEFORE calling m_anchor->tearDown() — even though the anchor is what the compiler thread uses to find live instances.

Source/JavaScriptCore/wasm/js/JSWebAssemblyInstance.cpp

JSWebAssemblyInstance::~JSWebAssemblyInstance()
{
+ if (m_anchor) {
+ m_anchor->tearDown();
+ m_anchor = nullptr;
+ }
+
m_vm->traps().unregisterMirror(m_stackMirror);
clearJSCallICs(*m_vm);
for (auto& slot : importFunctionInfos())
std::destroy_at(&slot);
for (auto& slot : baselineDatas())
std::destroy_at(&slot);
- if (m_anchor) {
- m_anchor->tearDown();
- m_anchor = nullptr;
- }
}

m_anchor->tearDown() moves to the very first action of the destructor, before any owned state is destroyed.

Publish/unpublish ordering inversion in a destructor: a thread-safe handle to the object is torn down AFTER the object's owned state is destroyed, leaving a race window where another thread can recover and dereference the partially-destroyed object.

JSWebAssemblyInstance owns per-instance Wasm state (import call link info, tables, per-function baseline profile data). Wasm::InstanceAnchor is a thread-safe-refcounted weak handle that lets the concurrent compiler thread find a live instance from a Wasm::Module; it holds the instance pointer under m_lock. Wasm::Module::m_anchors is a ThreadSafeWeakHashSet<InstanceAnchor> the compiler thread walks in createMergedProfile to combine baseline profiles before tier-up.

finishCreation explicitly publishes the instance to m_anchors (commented as "Expose it to the concurrent compiler"). Symmetric unpublishing must be the first destructor step; instead, it was last.

🔒

Explores the publish/unpublish symmetry that was inverted here, and what an attacker would need to win the destructor race from web content.

Subscribe to read more

🔒

Several reusable audit patterns identified for destructor/registry ordering bugs across JSC's concurrent subsystems, with concrete starting points for variant discovery.

Subscribe to read more