← WebKit — 2026-W38
7f5ab158824beeb9a5da5dd8b80086b23bbe5186 [JSC] Redesign WeakBlock
severity medium
class UAF
confidence 0.35
signal score 11.36
Source/JavaScriptCore/runtime
Primitive: WeakBlock surviving across MarkedBlock cell-size/owner change reading stale mark bits
Triage note: Rewrites JSC WeakBlock lifecycle into a detached/pooled model (addDetachedWeakBlock, returnWeakBlockToPool, destroyAllPooledWeakBlocks, didBecomeEmpty) and changes LocalAllocator to RELEASE_ASSERT a repurposed block owns no WeakBlock; the comments flag a stale-mark-bit read, but this reads as a pooling refactor that maintains the invariant rather than fixing an identified pre-existing exploitable bug.
Triage note: Rewrites JSC WeakBlock lifecycle into a detached/pooled model (addDetachedWeakBlock, returnWeakBlockToPool, destroyAllPooledWeakBlocks, didBecomeEmpty) and changes LocalAllocator to RELEASE_ASSERT a repurposed block owns no WeakBlock; the comments flag a stale-mark-bit read, but this reads as a pooling refactor that maintains the invariant rather than fixing an identified pre-existing exploitable bug.
Structural signals: security_assert (+6.47), null_guard (+2.59), clamp_saturate (+1.8), area:Source/JavaScriptCore/runtime (+2.5), large_churn (-2.0)
Commit message
[JSC] Redesign WeakBlock
https://bugs.webkit.org/show_bug.cgi?id=324140
rdar://187358319
Reviewed by Keith Miller.
This patch redesigns WeakBlock implementation. Previously deallocation
of Weak<> just marking WeakImpl as Deallocated. This is not desiable
since we end up scanning WeakBlock repeatedly to detect it becomes empty.
WeakBlock with non-dead WeakImpls with all dead JSValues are called
logically-empty WeakBlocks. Heap is managing them, and each WeakBlock
allocation is having a piggy-backed job which scans logically-empty
WeakBlocks to detect whether it is empty now. But this touches
completely unrelated memory and super costly. And this is the reason why
we excluded MarkedBlock with WeakBlock from the block-stealing target.
But the mechanism should be dramatically simpler since WeakImpl
deallocation is synchronous and not GC-managed thing. Thus we can take
similar design to StrongBlock. WeakBlock should track # of Live, # of
Dead, # of allocated WeakImpls, and WeakImpl deallocation updates these
numbers. And when we reach to 0 allocation state, WeakBlock is empty. So
no longer need to have random logically-empty WeakBlock scanning.
Also, WeakSet should not pool empty WeakBlocks and instead Heap should pool
them. It does not make sense to have a pool of empty WeakBlocks for each
MarkedBlock. While partially released WeakBlock should be tied to this
MarkedBlock since all of WeakImpl's stored cells need to be tied to this
MarkedBlock, once it becomes completely empty, it is freely usable from
any MarkedBlocks.
One of the most carefully handled place is `WeakSet::sweep` function.
The reason is that this `sweep`'s finalize call can allocate and
deallocate WeakImpl, thus and now empty WeakBlock is synchronously
detected and destroyed while iterating WeakBlocks. We are marking the
currently iterated WeakBlock and defer empty / logically-empty chaining
of this WeakBlock. And after all sweep, we iterate again to detect the
newly empty / logically-empty blocks and chain them again.
These improvements allow us to always steal MarkedBlocks since now empty
MarkedBlocks never have WeakBlocks.
Test: JSTests/stress/weak-block-recycling.js
* JSTests/stress/weak-block-recycling.js: Added.
(churnOpaqueRoots):
(churnRegExps):
(churnArrayBuffers):
(churnSizeClasses):
* JSTests/stress/weakblock-trigger-gc.js:
(vm.gc.let.start.vm.totalGCTime): Deleted.
* Source/JavaScriptCore/heap/BlockDirectory.cpp:
(JSC::BlockDirectory::findEmptyBlockToSteal):
* Source/JavaScriptCore/heap/Heap.cpp:
(JSC::Heap::~Heap):
(JSC::Heap::lastChanceToFinalize):
(JSC::Heap::clearConcurrentRetainedDataIfPossible):
* Source/JavaScriptCore/heap/Heap.h:
(JSC::Heap::weakBlockCount const):
* Source/JavaScriptCore/heap/IncrementalSweeper.cpp:
(JSC::IncrementalSweeper::sweepNextBlock):
* Source/JavaScriptCore/heap/LocalAllocator.cpp:
(JSC::LocalAllocator::tryAllocateWithoutCollecting):
* Source/JavaScriptCore/heap/MarkedBlock.h:
* Source/JavaScriptCore/heap/MarkedSpaceInlines.h:
(JSC::MarkedSpace::forEachWeakInParallel):
* Source/JavaScriptCore/heap/WeakBlock.cpp:
(JSC::WeakBlock::create):
(JSC::WeakBlock::destroy):
(JSC::WeakBlock::WeakBlock):
(JSC::WeakBlock::didBecomeEmpty):
(JSC::WeakBlock::lastChanceToFinalize):
(JSC::WeakBlock::sweep):
(JSC::WeakBlock::specializedVisit):
(JSC::WeakBlock::visitImpl):
(JSC::WeakBlock::reap):
* Source/JavaScriptCore/heap/WeakBlock.h:
(JSC::WeakBlock::IterationScope::IterationScope):
(JSC::WeakBlock::IterationScope::~IterationScope):
(JSC::WeakBlock::heap const):
(JSC::WeakBlock::isEmpty const):
(JSC::WeakBlock::hasLiveHandles const):
(JSC::WeakBlock::hasOnlyFinalizedHandles const):
(JSC::WeakBlock::hasFreeCell const):
(JSC::WeakBlock::asFreeCell):
(JSC::WeakBlock::blockContaining):
(JSC::WeakBlock::weakImplCount):
(JSC::WeakBlock::blockFor):
(JSC::WeakBlock::takeFreeCell):
(JSC::WeakBlock::pushFreeCell):
(JSC::WeakBlock::deallocate):
(JSC::WeakBlock::reattach):
(JSC::WeakBlock::setDetached):
(JSC::WeakBlock::setPooled):
(JSC::WeakBlock::assertFreeListIsConsistent):
(JSC::WeakImpl::clear):
(JSC::WeakBlock::disconnectContainer): Deleted.
(JSC::WeakBlock::SweepResult::isNull const): Deleted.
(JSC::WeakBlock::takeSweepResult): Deleted.
(JSC::WeakBlock::addToFreeList): Deleted.
(JSC::WeakBlock::isEmpty): Deleted.
(JSC::WeakBlock::isLogicallyEmptyButNotFree const): Deleted.
* Source/JavaScriptCore/heap/WeakImpl.h:
(JSC::WeakImpl::clear): Deleted.
* Source/JavaScriptCore/heap/WeakInlinesLight.h:
* Source/JavaScriptCore/heap/WeakSet.cpp:
(JSC::WeakSet::~WeakSet):
(JSC::WeakSet::lastChanceToFinalize):
(JSC::WeakSet::didBecomeEmpty):
(JSC::WeakSet::tryReleaseBlock):
(JSC::WeakSet::sweep):
(JSC::WeakSet::shrink):
(JSC::WeakSet::findAllocator):
(JSC::WeakSet::tryFindAllocator):
(JSC::WeakSet::addAllocator):
(JSC::WeakSet::removeAllocator): Deleted.
* Source/JavaScriptCore/heap/WeakSet.h:
(JSC::WeakSet::detachAllocator):
(JSC::WeakSet::resetAllocator):
* Source/JavaScriptCore/heap/WeakSetInlines.h:
(JSC::WeakSet::allocate):
(JSC::WeakBlock::finalize):
* Source/JavaScriptCore/runtime/OptionsList.h:
* Source/JavaScriptCore/tools/Integrity.cpp:
(JSC::Integrity::Analyzer::analyzeCell):
* Source/JavaScriptCore/tools/JSDollarVM.cpp:
(JSC::JSC_DEFINE_HOST_FUNCTION):
(JSC::JSDollarVM::finishCreation):
* Source/WTF/wtf/DebugHeap.h:
* Source/WTF/wtf/FastMalloc.h:
(WTF::FastMalloc::alignedMalloc):
(WTF::FastMalloc::tryAlignedMalloc):
(WTF::FastCompactMalloc::alignedMalloc):
(WTF::FastCompactMalloc::tryAlignedMalloc):
Canonical link: https://commits.webkit.org/321344@main
Before / after
Loading diff…
On This Page