[2] GradientRendererCG: per-thread sampled gradient cache
Severity: Medium | Component: WebCore CoreGraphics gradient renderer | d6dbb47
Rated Medium because the diff removes unsynchronized concurrent mutation of a static LRU cache holding RetainPtr<CGGradientRef> entries, eliminating a race between eviction-driven release and concurrent reads. The race plausibly yields a UAF or refcount imbalance on a CoreGraphics handle reachable from any web content that drives concurrent gradient sampling, though the diff does not establish a concrete control primitive.
GradientRendererCG::makeGradientBySampling() used a static 8-entry TinyLRUCache shared across all threads. When multiple threads sample gradients concurrently, they evict each other's entries from the small cache, collapsing the hit rate and forcing repeated CGGradientRef rebuilds. The fix wraps the cache in WTF::ThreadSpecific so each thread gets its own 8-entry LRU; the per-thread working set fits cleanly in 8 entries, and the shared mutable structure is gone by construction.
Source/WebCore/platform/graphics/cg/GradientRendererCG.cpp
#include "SampledGradientBuilder.h"
#include <pal/spi/cg/CoreGraphicsSPI.h>
#include <wtf/HashMap.h>
+#include <wtf/ThreadSpecific.h>
#include <wtf/TinyLRUCache.h>
...
GradientRendererCG::Gradient GradientRendererCG::makeGradientBySampling(ColorInterpolationMethod colorInterpolationMethod, const GradientColorStops& stops) const
{
auto colorStops = stops.sorted().stops();
- static NeverDestroyed<TinyLRUCache<WTF::SampledGradientCacheKey, RetainPtr<CGGradientRef>, 8>> cache;
- RetainPtr gradient = cache.get().get({ colorInterpolationMethod, colorStops, m_colorSpace });
+ static NeverDestroyed<ThreadSpecific<TinyLRUCache<WTF::SampledGradientCacheKey, RetainPtr<CGGradientRef>, 8>>> cache;
+ RetainPtr gradient = cache.get()->get({ colorInterpolationMethod, colorStops, m_colorSpace });
return Gradient { WTF::move(gradient) };
}
Patch Details
The change is a single-line restructuring of the static cache declaration plus an accessor update. static NeverDestroyed<TinyLRUCache<...>> becomes static NeverDestroyed<ThreadSpecific<TinyLRUCache<...>>>; cache.get().get(...) becomes cache.get()->get(...) because ThreadSpecific<T>::operator->() returns a T* for the current thread's instance, lazily constructed on first access. <wtf/ThreadSpecific.h> is added. No locking is introduced and no other code path is touched.
Unsynchronized concurrent mutation of a shared static LRU cache holding refcounted handles.
Background
GradientRendererCG builds CGGradientRefs for CSS, SVG, and Canvas gradients. The sampling path is used whenever the interpolation color space is non-sRGB or any color component is none, and is invoked from whichever thread is currently executing graphics work — the main thread, scrolling / display-list threads in the GPU process, or off-main-thread image decoders.
TinyLRUCache<K, V, N> is a fixed-capacity (N=8 here) least-recently-used cache stored in-place. get(key) returns the stored value on a hit; on a miss it calls the policy's createValueForKey to materialize a new value, inserts it, and evicts the least-recently-used entry. Both paths mutate the cache: hits update LRU ordering, misses replace a slot. NeverDestroyed<T> is WebKit's pattern for a function-local static constructed on first use and intentionally never destructed. WTF::ThreadSpecific<T> is WebKit's portable wrapper over pthread / Win32 thread-local storage. RetainPtr<CGGradientRef> calls CGGradientRetain / CGGradientRelease on assignment and destruction; assigning over a RetainPtr releases the prior value, which may be the last reference.
Analysis
The bug is a data race on a shared mutable structure. Before the fix, every thread that called makeGradientBySampling reached the same process-wide TinyLRUCache instance. The commit message frames the symptom as thrashing — cross-thread eviction collapses the hit rate — but the underlying property the fix changes is sharing of a mutable structure across threads without synchronization. The cache exposes no internal locking and the call site holds none.
Aaa Aaaaaaaaaa Aaaaaaaa Aaaaaaa Aaa Aaa Aaaaa Aa Aaa Aaa Aaaaaaaa Aaaaa Aaaaaaa Aaaaaa Aaaaaaa Aaaaaaa Aa a Aaa Aaaaaaa Aaaaaaaaa Aaaaaaa Aa Aaaaaaaaaaaaaaaaaa Aaaaaaaaaaaaaaa Aaa Aaa Aaaa Aaaaaaa Aaaaaaaaaaaaaa Aaaaaaaa Aaa Aa Aaaaaaa Aa Aaaaaa a Aaaaa Aaaaaa a Aa Aaaaaaa Aaa Aa Aaa Aaaaa Aaaaa Aa Aaaaaaaa Aa Aaaaaa a Aaaaa Aaa Aaaa Aaaaaaaaaaaaaaaaaaaaaaaaaa Aaaaaaaaa Aaa a Aaaa Aaaa Aaaaaa a Aa Aaaaa Aaaaaaaaaa Aaa Aaaaaa Aaa Aaa Aaaaaa Aaa Aa Aaaaaa Aaa Aaaaaaaa Aa Aaaaaa a Aaa Aaaaaa a Aaa Aaaaaaaaaaaaaaaa Aaaa Aaa Aaaaa Aaaa Aaa Aa Aaaaaaaa Aaaa a Aaaaaaaaaaaa Aaaaaaaa Aa Aaaaaaaaaaaaaaa Aaaaaaaaaaaaaaaaaaa Aa Aaaaaa a Aaa Aaaaaaaaaa Aaa Aaaaaaaaaa Aa Aaaaaa Aaaaa Aaaaaa Aaa Aaaaaaa Aa Aaaaa Aaaaa Aaa Aaaa Aaaa Aaa Aaaaa Aa Aaaaa Aa Aaaaaaaa Aaa Aaa Aaaaaaa Aaa Aaa Aaaaaa Aa Aaaa Aaaaa Aaaaaaaa Aaaaaaaaa Aaa Aaaaaaaa Aaaaa Aaaaaa Aaa Aaaa Aaaa Aaaaaaaaa Aaaaaa Aaaa Aaaaa
a Aaaaaaaaaa Aaaaaaa Aaaa Aa Aaaaa Aaaaaaaaaa Aaaaaaaa Aaaaaaaa a Aaaaaaaa Aaaaaaaa Aaaaaaaaa Aaa Aaaaaaaaa Aaaa Aaaaaaaa Aaaaaaaaa Aaaaaa Aaaaaaaa Aaaaaaaaaa Aaaaaaaa Aa Aaaaaaaaaaa Aaaaaaaaaaaaaaa Aaaaa Aaaaaa a Aaa Aaaaaaaaa Aaaaaaaa Aaa Aaaa Aaaa Aaaaaaaaa Aaaaaaaa Aaa Aaaaaaaaaa Aaaaaa Aa a Aaaaaaaaaa Aaaa Aa a Aaa Aa Aaaaaaaa Aaaaaaaaa Aa a Aaaaaaaaaaaa Aaaaaa Aaaaaa Aaa Aaaaaaaa Aa Aaa Aaaaaaaa Aaa Aaaa Aaaa Aaa Aaaaaaaaa Aaa Aaa Aaaaa Aaaaaaaa Aaaaaaaa Aaa Aaaaaa a Aaaa Aaaaaaa Aa Aaaaaaaaaaaaa Aaaaaaaa Aaaaaaaaa Aaaaaaaaa Aaa Aa Aaa Aaaaaaaa Aaaaaa Aa Aaaaaaa Aaaaaa Aaaaa a Aa Aaa Aaaaaaaa Aaaaaaaaaa Aaaaaaaaa Aa Aaa Aaaaaaaa Aaaaaaaaaa Aaaa Aaaa Aaaaaa
Aaaa Aaaaaaaaaaaaa Aaaaaaa Aaa Aaaaaaaaaaaaa Aaaaaaaa Aaaaaa Aaa Aaaaaaaa Aaa Aaa Aaaaaaa Aa Aaaaaaaaa Aaa Aaaaaaaaa Aaaa Aaaa Aaaaaaaaaa Aaaaaa Aaaaaa Aaaaaaa Aaaaaa Aaaaa Aaaaaaaa Aaaaaaaaaaaaaaa Aa Aaa Aaaaaaaa Aa a Aaaaaa Aaaaaaa Aaa Aaaaaaaaaa Aaaaaaaa Aa Aaaaa Aaaaaaa Aaa Aaaaaaaaaaa Aaaaa Aaaaaaaaaaaaaaaa Aaaaaa Aaaa Aaaaaaaaaaa a Aaaaa Aaaaa Aaaaaaaa Aaaaaa Aaa Aaaaa Aa Aaa Aaaaaa Aaaa Aaaaaaa Aaaaa Aaaaaaa Aaaaaaaaaaaa Aa Aaaa Aa Aaaa Aaaaaaa Aaaaaaaaaa Aaaaa Aa Aaaaaaaaaaa Aa Aaaa Aaaa Aaaaaaa Aaaaaaaaa Aaaaaaaaaaaaaa Aaaaaaa
🔒What looks like a performance fix in a gradient renderer also tightens a concurrency invariant — the memory-safety implications of that change are analyzed in depth.
Subscribe to read more
Audit directions
a Aaaaaaaaaaaaaaaa Aaaaaaa Aaaaaaaaaaaaaaaaaaaa Aaaaaa Aaaaaaaaa Aaaa Aaaaaaaa Aaaaaaa Aaaaaaa Aaaaaaaaaaaaaaaaaa Aaaa Aaaaaaa Aaa Aaaaaaa Aaaaaaaaaaaaaaaa Aaaaaaaa Aa Aaaaaaaaaaaaaaa Aaaaaaaaaa Aaaaaaaaaa Aa Aaaaaaaaaa Aaaaaa Aaaaaaaaaa Aaaaaa Aaaaaaaaaa Aaaa Aaaaa Aaaaaaa Aaa Aaaaaaaaa Aaaaaaaa Aa Aaaaaaa Aaa Aaa Aaaa Aaaaaa Aaaaaaaaaa Aaaaa Aaaaaaaaa Aaaa Aaaaaaaa Aaaaaa Aaaaaaaaa Aaaaa Aaaa Aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa Aaa Aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
a Aaaaaaaa Aaaaaa Aaaaa Aaaaaa Aaa Aaaaaaaaaaaa a Aaaaaaaaaaaaaa Aaaaaaaaaaaaa Aaaaa Aaaaaaaa Aaaaa Aaa Aaaa Aaaaaaaaaaaa Aaaaa Aaa Aaaaaaaaaaaaaaaa Aaaaaaaaaaaaaaaa Aaa Aaaaaaaaaaa Aaaaaaaaaaaaaaaa Aa Aaaaaaaa Aaaa Aaa Aaaaaaaaaaaaaa a Aaaa Aaaaaaa Aaaaaaaaaaaaaaaa Aaa a Aaaaaaaaaa Aaaaaa Aa Aaa Aaaa Aaaa Aa a Aaa Aaaaaaaaa Aaaa Aaaa Aaa Aaaaa Aaaaaa Aaaaa Aaaaa Aaa Aaaaa Aaaa Aaaaaaaaaaaaaaa Aaaaaaaaaaaaaaa Aaaaaaaaaaaaaa Aaaaaa Aaaaa Aaaaa Aaaaa Aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa Aaa Aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
a Aaaaaaaaaaaa Aaaaaa Aa a Aaaaaaaa Aaa Aaaaaaa Aaaaaaaaaaaaaaaaaa Aaaaaa Aaaaa Aaaa Aa Aaaaaaaaaaaaaaaaaaaaa Aa Aaaaaa Aa Aaa Aaaaaaa Aaaa Aaa Aaaaaaaa a Aaaaaaaaaa Aaaaaa Aaaaaaa Aaaaaaaaaa Aaaaa Aaaaa Aaa Aaaaaaaaa Aa Aaaaa Aaa Aaaaaaaa Aaaaa Aaaaa Aaaa Aaa Aaaaa Aaaa Aaaaaaaa Aaaaaaaaaa Aaa Aaaa Aaa Aa Aaaaaaaaaaaaaaaa Aa Aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa Aaa Aaaaaaa Aaa Aaaaa Aaaaaaaaaaaaaaaa
a Aaaaaaaaaaaaaaaa Aaaaaa Aaa Aaaaaaaaaaaaa Aa Aaaaaaaaaaaaa Aaaaaaaaaaaaaaa Aa Aaa Aaaaaaaa Aaaaaaa Aa Aaaaaaaaa Aaaaa Aaaaaaaa Aaaaaaaaaaaaa Aaaaaa Aaaaaa Aa Aa Aaaaa Aaaaaaaaaa Aaaaaaaaa Aaaa Aaaa Aaaaa Aaa Aaaaaaaaaaaaaaa Aaaaaaaa Aaa Aaaaaa Aaaa Aaaa Aaaa Aa Aaaaaa Aaaaaaaaaaaaaaa Aa Aaaaaaaaaa Aaaaaaa
Aaaaaaaaa Aaaa Aaaaaaaaa a Aaa Aaaaaaa Aa Aaaaaaaa Aaaaaaaaaaaaaaa Aa Aaaaaaaaaaaaaaa Aaa Aaaaa Aaa Aa Aaaaaaaaaaaaaaa Aaaa Aaaaa Aaa Aaaaaaaaaaaaaaaaaaaaaaaaa Aaa Aaaaaaaa Aa Aaaaaaaaaaaaaaaaaaaaaaaaaaaaaa a Aaa Aaaaaaaa Aaaa Aaaaaaaaaaa Aaaa Aaaaaaaa Aaaaaa Aaaa Aaaaaaaa Aaaaaaa Aa Aaa Aaaaa Aaa Aaaa Aaaa Aaaaaaaaa Aaa Aaa Aaaaaaaaaaa Aaa Aaaaaaaa Aaaaaaaaa Aa Aaa Aaaaaa
🔒Multiple reusable audit patterns for shared static caches and refcounted handle lifetimes across graphics subsystems, with concrete starting points for variant discovery.
Subscribe to read more