← All issues

Sampled mprotect write-guard for DOMWrapperWorld::m_wrappers corruption

d2af128beb0b21

DOMWrapperWorld::m_wrappers is the central hash map in the JS/DOM binding layer mapping DOM objects to their live Weak<JSObject> wrapper handles. Every time JS touches a DOM node, cacheWrapper inserts or updates an entry; the GC sweeps stale entries by calling JSC::WeakImpl::clear() during rehash, which dereferences a garbage address if the table is corrupt. This commit adds sampled instrumentation to catch the stray write.

Source/WebCore/bindings/js/DOMWrapperWorld.cpp

+void* WrapperMapTableMalloc::allocate(size_t size)
+{
+ size_t pageSize = WTF::pageSize();
+ size_t rounded = (requested + pageSize - 1) & ~(pageSize - 1);
+ void* base = mmap(nullptr, rounded, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANON, -1, 0);
+ RELEASE_ASSERT(base != MAP_FAILED);
+ if (auto* world = WrapperMutationScope::currentlyMutatedWorld())
+ world->noteTableBacking(base, rounded);
+ return base;
+}
+
+void DOMWrapperWorld::setWrappersTableWritable(bool writable)
+{
+ if (writable) {
+ if (m_wrappersTableWritableDepth++)
+ return;
+ } else {
+ ASSERT(m_wrappersTableWritableDepth);
+ if (--m_wrappersTableWritableDepth)
+ return;
+ }
+ if (m_wrappersTableBase)
+ RELEASE_ASSERT(!mprotect(m_wrappersTableBase, m_wrappersTableSize,
+ PROT_READ | (writable ? PROT_WRITE : 0)));
+}

A new WrapperMapTableMalloc allocator places the hash table's backing on page-aligned mmap memory kept PROT_READ at rest; a WrapperMutationScope RAII guard briefly unlocks it to PROT_READ|WRITE during cacheWrapper/uncacheWrapper/clearWrappers. Enabled in roughly 1/64 processes at startup via weakRandomNumber, so a stray write outside a mutation scope faults immediately at the write site rather than corrupting memory that crashes much later.

This reveals an active, unresolved production heap corruption in WebKit's JS/DOM binding layer — the same garbage-pointer fingerprint appears in topologically unrelated hash tables (m_wrappers, CodeBlockSet), consistent with a freed object reused as a hash-table backing and then written by a dangling pointer.

🔒

Cross-process shared memory with three concurrent writers, a removed extrapolation bound, and new floor/ceiling invariants — audit directions included.

Subscribe to read more