CVE-2025-43216
Overview
Background
- HRTF panner
- Web Audio’s head-related transfer function spatializer; it loads a per-sample-rate HRTF database asynchronously on a loader thread.
- Raw vs weak pointer
- A raw cached pointer does not observe destruction; ThreadSafeWeakPtr becomes null when the referent is freed, safely across threads.
- adoptRef / ref-counting
- HRTFDatabaseLoader is reference counted; a cache holding a non-owning raw pointer can outlive the object.
Root Cause Analysis
This fixes a use-after-free (and a related teardown race) in the Web Audio HRTF panner’s per-sample-rate loader cache. loaderMap() cached each HRTFDatabaseLoader keyed by sample rate as a RAW pointer (HashMap<double, HRTFDatabaseLoader*>). HRTFDatabaseLoader is ref-counted (created via adoptRef), so the map held a non-owning raw pointer to an object whose lifetime is controlled elsewhere. createAndLoadAsynchronouslyIfNecessary looked the loader up with loaderMap().get(sampleRate) and adopted it into a RefPtr; if the cached loader had already reached refcount zero and begun destruction, that raw pointer was dangling and reviving it was a use-after-free. Symmetrically, ~HRTFDatabaseLoader unconditionally did loaderMap().remove(m_databaseSampleRate), which — if a NEW loader for the same sample rate had already replaced the entry — removed the wrong (live) entry.
The fix stores ThreadSafeWeakPtr<HRTFDatabaseLoader> in the map: the lookup upgrades with .get() and safely returns null when the object is gone (forcing creation of a fresh loader), add() becomes set(), and the destructor now finds the entry, upgrades it, and only removes it when the cached loader is actually ’this’ (guarding against clobbering a replacement).
The restored invariant is that the cache never hands out or removes a loader whose lifetime has ended; the weak pointer makes destruction observable across threads.
Attack Path
- Create an HRTF panner Use Web Audio (PannerNode with HRTF panningModel) at a chosen sample rate to trigger HRTFDatabaseLoader::createAndLoadAsynchronouslyIfNecessary.
- Drop the loader Release references so the loader’s refcount hits zero and it begins destruction while its raw pointer is still cached in loaderMap().
- Re-request the same sample rate Ask for a panner at the same sample rate so the stale raw pointer is fetched from the map and revived via adoptRef — a use-after-free.
- Groom and exploit Reclaim the freed HRTFDatabaseLoader allocation with controlled data to turn the UAF into a stronger primitive in the WebContent process.
Impact Assessment
Changed Functions
| Function | Change | Notes |
|---|---|---|
loaderMapSource/WebCore/platform/audio/HRTFDatabaseLoader.cpp |
modified | Changes the cache value type from HRTFDatabaseLoader* to ThreadSafeWeakPtr<HRTFDatabaseLoader> so entries do not dangle. |
HRTFDatabaseLoader::createAndLoadAsynchronouslyIfNecessarySource/WebCore/platform/audio/HRTFDatabaseLoader.cpp |
modified | Upgrades the weak entry with .get() (null when destroyed) and uses set() instead of add() when inserting the new loader. |
HRTFDatabaseLoader::~HRTFDatabaseLoaderSource/WebCore/platform/audio/HRTFDatabaseLoader.cpp |
modified | Only removes the map entry when the cached loader is actually this, preventing removal of a replacement loader for the same sample rate. |
Files Changed
Source/WebCore/platform/audio/HRTFDatabaseLoader.cpp
Audit Directions
- Same pattern: raw pointers in static cachesGrep for HashMap<…, T*> NeverDestroyed caches of ref-counted T; each is a candidate for the same dangling-entry UAF and should hold a Weak/ThreadSafeWeakPtr.
- Unconditional self-removal in destructorsLook for destructors that remove(key) from a shared map without confirming the mapped entry is ’this’ — they can evict a replacement.
- Audio loader lifetimeReview other platform/audio singletons (reverb/convolution kernels) for the same per-sample-rate cache lifetime assumptions.