CVE-2025-43531
Overview
Background
- libpas / bmalloc
- WebKit’s high-performance allocator; segregated heaps use size directories and per-size allocator indices.
- Lock-free dependency read
- A read validated by a mutation counter and value-dependency chain so it can proceed without the heap lock, provided all used values are captured within the guarded read.
- Scavenger
- A background thread that decommits/rematerializes expendable allocator memory, concurrently mutating directory structures.
Root Cause Analysis
This fixes a lock-free data race in libpas (bmalloc) that could return a stale/freed medium-directory pointer and drive allocator confusion. pas_segregated_heap_medium_directory_tuple_for_index supports a fast, lock-free lookup guarded by a mutation-count ‘dependency’ scheme: it reads directory metadata without holding the heap lock and validates afterward that the mutation count did not change. The pre-patch impl returned a raw pas_segregated_heap_medium_directory_tuple* (result.tuple) that callers then DEREFERENCED after the lock-free search (reading directory->allocator_index and pas_compact_atomic_segregated_size_directory_ptr_load(&directory->directory)). Because a concurrent mutation (e.g. the scavenger decommitting/rematerializing expendable memory, or directory reallocation) can free or reuse that tuple’s memory between the read and the dereference, callers could dereference stale/freed metadata and obtain a wrong size directory or allocator_index.
The fix introduces pas_segregated_heap_medium_directory_result, which captures the needed values (directory pointer and allocator_index) DURING the guarded read and folds them into the mutation-count dependency chain, returning them BY VALUE; the raw tuple pointer is kept only as tuple_unsafe_without_lock, explicitly documented as valid only while holding the heap lock. Callers (medium_allocator_index_for_index, medium_size_directory_for_index) now use result.allocator_index / result.directory directly instead of dereferencing the tuple, and lock-holding callers use tuple_unsafe_without_lock. Added PAS_TESTING_ASSERTs (size <= allocator->object_size) in pas_local_allocator_try_allocate guard the downstream consequence — using a wrong allocator index to allocate into a size class that is too small.
The restored invariant is that values obtained on the lock-free path are snapshotted within the guarded read rather than fetched by dereferencing a pointer that a concurrent mutation can invalidate.
Attack Path
- Drive concurrent allocation Run JS/web content that churns medium-size allocations while background scavenging runs, so lock-free medium-directory lookups race with mutations.
- Race the tuple lifetime A concurrent scavenger/reallocation frees or reuses a medium-directory tuple between the lock-free read and the caller’s dereference.
- Read stale metadata The caller dereferences the freed tuple and gets a wrong size directory / allocator_index.
- Allocator confusion Allocating with the wrong index places an object into a mismatched size class, corrupting heap metadata in the WebContent process.
Impact Assessment
Changed Functions
| Function | Change | Notes |
|---|---|---|
medium_directory_tuple_for_index_implSource/bmalloc/libpas/src/libpas/pas_segregated_heap.c |
modified | Builds a pas_segregated_heap_medium_directory_result capturing directory and allocator_index during the guarded read and folds them into the dependency chain, instead of returning a raw tuple pointer to dereference later. |
pas_segregated_heap_medium_directory_tuple_for_index / _with_lockSource/bmalloc/libpas/src/libpas/pas_segregated_heap.c |
modified | Return pas_segregated_heap_medium_directory_result by value; empty result on no rare_data / not found. |
pas_segregated_heap_medium_allocator_index_for_index / _medium_size_directory_for_indexSource/bmalloc/libpas/src/libpas/pas_segregated_heap.c |
modified | Use result.allocator_index / result.directory directly rather than dereferencing a possibly-freed tuple pointer. |
pas_segregated_heap_medium_directory_result (struct + create_empty)Source/bmalloc/libpas/src/libpas/pas_segregated_heap.h |
added | New by-value result holding tuple_unsafe_without_lock (valid only under lock), directory, and allocator_index. |
pas_local_allocator_try_allocateSource/bmalloc/libpas/src/libpas/pas_local_allocator_inlines.h |
modified | Adds PAS_TESTING_ASSERT(size <= allocator->object_size) checks to catch allocation into a wrongly-sized class. |
Files Changed
Source/bmalloc/libpas/src/libpas/pas_local_allocator_inlines.hSource/bmalloc/libpas/src/libpas/pas_segregated_heap.cSource/bmalloc/libpas/src/libpas/pas_segregated_heap.hSource/bmalloc/libpas/src/test/ExpendableMemoryTests.cpp
Audit Directions
- Same file: post-read dereferencesIn pas_segregated_heap.c audit remaining .tuple_unsafe_without_lock uses to confirm they only occur under pas_lock_is_held.
- Lock-free result plumbingGrep libpas for lock-free lookups returning pointers into structures the scavenger can decommit/reallocate; values must be snapshotted inside the dependency chain.