[19] PlatformScreen data-race / GPUCanvasContextCocoa worker race
Rated Medium because the two related diffs fix concurrent HashMap access on the process-global
ScreenPropertiessingleton; readers on worker threads iteratedscreenDataMap.values()while the main thread rehashed it — surfaced as MTE tag-mismatch crashes on ARM hardware.
Two co-discovered fixes for the same root cause. The first (cf20d12) adds ASSERT(isMainThread()) to the existing screenProperties() accessor and dispatches off-main-thread GPUCanvasContextCocoa reads through callOnMainThread + BinarySemaphore. The second (08911bd) makes the fix structural: replaces the NeverDestroyed<ScreenProperties> accessor with a ThreadSafeRefCounted<PlatformScreen> singleton accessed via Ref<const PlatformScreen>, with copy-on-write semantics under platformScreenLock().
Source/WebCore/platform/PlatformScreen.cpp
Patch Details
Free-standing screenData(), getScreenProperties(), primaryScreenDisplayID(), setScreenProperties() are deleted. PlatformScreen becomes a ThreadSafeRefCounted class; writers swap a fresh instance into the slot when there are concurrent readers; readers snapshot a Ref<const PlatformScreen> and keep it alive via refcount. All call sites (HTMLMediaElement, GPUCanvasContextCocoa, VP9/GStreamer scanners, PlatformScreen{Mac,iOS,GTK,WPE}) migrate to PlatformScreen::singleton()->....
Data race / use-after-free on a singleton HashMap whose readers escape pointers and iterators into the backing storage while another thread mutates it.
PlatformScreen is a per-process cache mirroring per-display state (rectangles, EDR headroom, color space) keyed by PlatformDisplayID. UIProcess pushes updates via WebProcess::setScreenProperties, which previously called the file-scope setter and overwrote the singleton. WTF HashMap is not thread-safe: concurrent insert or operator= can rehash the bucket array (freeing the old one), invalidating any iterator a concurrent reader holds.
The trigger pattern is recurring: a per-process cache originally main-thread-only acquires off-main-thread callers as new subsystems (media pipelines, WebGPU, HDR observers) hang off it; the original ASSERT(isMainThread()) is the only thing tracking the invariant. When that assertion is wrong rather than enforced, the result is a silent data race — surfaced here as MTE tag-check faults on Apple silicon hardware. The ThreadSafeRefCounted + COW pattern is becoming the canonical remediation.
This vulnerability weakens the memory-safety invariant that the per-process display-properties cache is single-threaded. A WebGPU/OffscreenCanvas-driven worker reading screen state racing a screen-properties update could induce a renderer-process memory-corruption primitive.
Audit directions
NeverDestroyed<HashMap>singletons guarded only byASSERT(isMainThread()). GrepSource/WebCore/platform/andpage/forstatic NeverDestroyed<+ non-atomic containers; trace callers for OffscreenCanvas reachability.- Returning raw pointers from
HashMap::find()/begin()/values()across API boundaries. Grepfind\(.*\)\s*;\s*return &.*->valueand.values\(\)iteration patterns in WebGPU / WebAudio / Worklet contexts. - IPC-driven cache updates mutating singletons while off-main subsystems read. Audit
WebProcess::set*handlers reassigningNeverDestroyedsingletons. - Copy-on-write
updateSingletonPropertiespatterns. VerifyhasOneRef()+ in-place mutation is actually safe under the lock — no other thread can have just released its Ref and started a new read between the check and the mutation.