Medium CVSS 3.1 webkit Race 🔧 Commit mapped

Overview

Medium
Severity
3.1
CVSS
No
Exploited ITW
Fixed
Fix Status
DescriptionProcessing maliciously crafted web content may lead to an unexpected process crash
Componentbmalloc
Bug ClassRace
Tracker301940
Fix commit39a5ac271398 (WebKit/WebKit) +76/-46
CWECWE-362 (Race condition)
CVSS vectorCVSS:3.1/AV:N/AC:H/PR:N/UI:R/S:U/C:N/I:N/A:L
CISA KEVNot listed
CreditedPhil Pizlo of Epic Games
Disclosed2025-12-12

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.

Key insight
The lock-free medium-directory lookup returned a raw tuple pointer that callers dereferenced after the guarded read, so a concurrent scavenger/reallocation could free it; capturing the needed fields by value within the dependency-checked read removes the race.

Attack Path

  1. 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.
  2. 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.
  3. Read stale metadata The caller dereferences the freed tuple and gets a wrong size directory / allocator_index.
  4. Allocator confusion Allocating with the wrong index places an object into a mismatched size class, corrupting heap metadata in the WebContent process.

Impact Assessment

A race in the core allocator confined to the process using it (WebContent), where a lock-free lookup could dereference freed medium-directory metadata and yield a wrong size class / allocator index — a path to heap metadata corruption. Allocator-internal races are difficult to weaponize and timing-dependent; the advisory rates it a crash, but allocator confusion is a serious corruption class if made reliable.

Changed Functions

FunctionChangeNotes
medium_directory_tuple_for_index_impl
Source/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_lock
Source/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_index
Source/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_allocate
Source/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.h
  • Source/bmalloc/libpas/src/libpas/pas_segregated_heap.c
  • Source/bmalloc/libpas/src/libpas/pas_segregated_heap.h
  • Source/bmalloc/libpas/src/test/ExpendableMemoryTests.cpp

Audit Directions

  • Same file: post-read dereferences
    In pas_segregated_heap.c audit remaining .tuple_unsafe_without_lock uses to confirm they only occur under pas_lock_is_held.
  • Lock-free result plumbing
    Grep libpas for lock-free lookups returning pointers into structures the scavenger can decommit/reallocate; values must be snapshotted inside the dependency chain.

Original Bug Report

The reporter's bug is still restricted on the tracker.