← All issues

[19] PlatformScreen data-race / GPUCanvasContextCocoa worker race

Severity: Medium | Component: WebCore PlatformScreen | 08911bd, cf20d12

Rated Medium because the two related diffs fix concurrent HashMap access on the process-global ScreenProperties singleton; readers on worker threads iterated screenDataMap.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

-static ScreenProperties& NODELETE screenProperties()
-{
- ASSERT(isMainThread());
- static NeverDestroyed<ScreenProperties> screenProperties;
- return screenProperties;
-}
+Ref<const PlatformScreen> PlatformScreen::singleton()
+{
+ Locker locker { platformScreenLock() };
+ return instance().get();
+}
+void PlatformScreen::updateSingletonProperties(ScreenProperties&& properties)
+{
+ Locker locker { platformScreenLock() };
+ Ref<PlatformScreen>& platformScreenRef = PlatformScreen::instance();
+ if (platformScreenRef->hasOneRef())
+ platformScreenRef->m_properties = WTF::move(properties);
+ else
+ platformScreenRef = PlatformScreen::create(WTF::move(properties));
+}

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 threading and ownership model behind this singleton rewrite is dissected, including how the copy-on-write fast path interacts with concurrent readers.

Subscribe to read more

🔒

Four reusable audit patterns covering singleton caches, escaped HashMap pointers, IPC-driven mutation, and copy-on-write fast paths.

Subscribe to read more