[4] TimingFunction reference-count race across the scrolling thread
Severity: Medium | Component: WebCore platform animation | c541bf0
Rated Medium because the diff converts a refcount that is provably touched from two threads to atomic, closing a data race that can drive the count to zero while a logical reference is live; reaching a usable UAF requires reliably winning the race on a renderer-reachable heap object, and the object's narrow attacker-controlled state keeps the immediate outcome a crash rather than a confirmed corruption primitive.
Because accelerated effects may be accessed from both the main thread and the scrolling thread on macOS, TimingFunction should use ThreadSafeRefCounted like the other ref-counted types used by AcceleratedEffect and AcceleratedEffectValues. The crash surfaced under ASan on an existing threaded-animations layout test; the commit notes the fix was suggested by an LLM during bug analysis and validated by the author.
Source/WebCore/platform/animation/TimingFunction.h
-#include <wtf/RefCounted.h>
+#include <wtf/ThreadSafeRefCounted.h>
...
-class TimingFunction : public RefCounted<TimingFunction> {
+class TimingFunction : public ThreadSafeRefCounted<TimingFunction> {
LayoutTests/webanimations/threaded-animations/timing-function-threading-check.html
+ const easing = "cubic-bezier(0.1, 0.7, 1.0, 0.1)";
+ const animations = [ target.animate(...{ duration, easing }), ... ];
+ await Promise.all(animations.map(animation => animationAcceleration(animation)));
+ // Both paths call AnimationEffectTiming::resolve() which protects the
+ // effect's TimingFunction; this must not trip the RefCounted threading check.
+ for (let i = 0; i < 50; ++i)
+ await UIHelper.remoteAnimationStackForElement(target);
Patch Details
The patch changes the base class of TimingFunction from RefCounted<TimingFunction> to ThreadSafeRefCounted<TimingFunction>, swapping the include accordingly. No logic in transformProgress, clone, or the subclasses (LinearTimingFunction, CubicBezierTimingFunction, StepsTimingFunction, SpringTimingFunction) is altered. The added test creates four accelerated animations with a cubic-bezier easing and repeatedly resolves the animation stack on the main thread while the scrolling thread concurrently applies effects, asserting the RefCounted threading check is not tripped.
Non-atomic reference counting on an object shared across threads, allowing the refcount to be corrupted by a data race.
Background
RefCounted<T> is WTF's single-threaded reference-counted base: ref()/deref() mutate the count non-atomically and, in assertion-enabled builds, carry a thread-ownership check that traps if the count is touched from a thread other than the owner. ThreadSafeRefCounted<T> is the atomic counterpart, using atomic read-modify-write on the count so concurrent ref/deref from multiple threads is safe. Threaded (accelerated) animations on macOS run a copy of the animation effect data on the scrolling thread so scroll-driven and time-driven animations can be resolved without the main thread; AcceleratedEffect/AcceleratedEffectValues hold the effect's TimingFunction. TimingFunction::transformProgress() maps a linear progress value through the easing curve (e.g. cubic-bezier) and is called both from the main-thread keyframe interpolation path and the scrolling-thread scroll-animation path, each taking a transient ref on the shared TimingFunction for the duration of the call.
Analysis
This is a data race on a non-thread-safe reference count, leading to use-after-free / double-free. Before the fix, TimingFunction derived from RefCounted, whose ref()/deref() perform plain non-atomic increments/decrements and embed a thread-ownership assertion. On macOS, accelerated animations make the same TimingFunction instance reachable from two threads simultaneously: the main thread resolves timing via AnimationEffectTiming::resolve() / KeyframeInterpolation while the scrolling thread applies effects via ScrollAnimationSmooth, with both contexts taking a transient RefPtr/protect() ref before calling transformProgress. When both threads take and drop these transient refs concurrently, the non-atomic read-modify-write races.
Aaa Aaaaaaaaaa Aaaaaaaaa Aaaaaaaaaa Aaa Aaaa Aaa Aaaa Aaaaa Aaaaa Aaa Aaaa Aaaaaaaaa Aa Aaa Aaaa Aaaaaaa Aaaaaa Aaa Aaaaaaaaa Aa Aaa Aaaaaa Aaaaaa Aa Aaa Aaaaaaa Aaaaaaaaaaaaa a Aaaa Aaaaaaaaa Aaaa Aaa Aaaaa Aaa Aaaa Aaaaa Aaaaaaa Aaaaaa Aaaaa Aaaaa a Aaaaaaa Aaaaaaaaaa Aaaaaaa Aaa Aaaaaaaaaaaaaaaa Aaaaa Aa Aa Aaaaa Aaaa Aaaaaaaaaaaaaaaaaaaa Aa Aaaaaaa Aaa Aa Aaaaaaa Aa a Aaaaaaaaaaaaaa Aaaaaaaa Aaa Aaaaa Aaaaaaaaaaaa Aaaaaaaaaaaaaaa Aaaaaaaaa Aaaaa Aaaaaaaaa Aaaaaaa Aaaaaaaaa Aaaaaa Aaa Aaa Aaaaaa Aaaaaa a Aaaaa Aa Aaaa Aaa Aaaaaaaaaaaaa Aaaa Aaaaa Aaaaaaaaa
Aaaa Aaaaaaaaaaaaa Aaaaaaa Aaaaaa Aaaaaa Aaaaaa Aaa Aaaaaaaaaa Aaaaaaa Aa Aaaaaaaaa Aaa Aaaaaaaa Aaaaaaaaa Aaaa a Aaaaaaaaaaaaaaaaaa Aaaaaaaa Aa Aaaaaaaa Aa a Aaaaaaaaaa Aaaaaaaaa Aaaaaa Aaaaaa Aaa Aaaa Aa Aaaaaa Aaaaaa Aaaaaaa Aaa Aaaa Aaaaaa Aaa Aaa Aaaaaaaaa Aaaaaa Aaaaa Aaaa Aaa Aaaaaaaa Aaaaaaaaaa Aa Aaa Aaaaaaaaaa Aa Aaaaa Aaaaaaaaaa Aaaaaa Aa Aaa Aaaaaa Aaaaa Aa Aaaaaa Aaaaa Aaaaaaaaaa Aaaaaaa Aa Aa Aaaaaaaa Aaaaaaaa Aaaa Aaa Aaaa Aa Aaa Aaaaa Aaaaaaaaaaa Aaaaaaa Aaaaa Aaaa Aaaaa Aaaaaa a Aaaaaaaaaaaaaa Aa a Aaaa Aaaaaa Aaaaaaaaa Aaaa Aaa Aaaaaaa a a Aaaaaaaa Aaaaa Aaaaaa Aaaaaaaa Aaaaaa Aaaaaaaaaaa
Aaaa Aa a Aaaaaaaaa Aaaaaa Aaaaaaaa a Aaaa Aaaaaaaaaa Aaaaaaaa Aa Aaaaaaaaaaaaaaa Aaaaaaaaaaaaaa Aaaa Aaaaaaa Aa a Aaaaaaaaaaa Aaaaaaaaaaaa Aaaaaaaaa Aaaaaaaaa Aaaaaaaaaaa Aaaaaaa Aaaaaaaa Aaaaaaa Aaa Aa Aaa Aaaaaaaaaaaa Aaa Aaaaaaaaaaaa Aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa Aaaaaaa Aaaa Aaaaaaaaaaaaaaaaaaaaaaa Aaa Aaa Aaaa Aaaaaaaaaa Aaaaaaaaaaaaaaaa Aaa Aaaaaa a Aaa Aaa Aa Aaaaaaaaaaa Aaaaa Aaa Aaaaa Aaaaaaaaa Aaaaaa Aaaaa Aaaaaaaaaaaaa
Aaaaaaaaa Aaa Aaaaaaaaaaaaa Aaaaaaaa Aa Aaaaaaaaaaaaaaaaaaaaaaaaaaa Aaa Aaaaaaaaaaaaaaaaaaaaaaaaaaaa Aaa Aaaaaaaa Aaaaaaaaaaa Aaaaaa Aa Aaa Aaaaaa Aaaaaa Aaaaaaaa Aaa Aaa Aaaaaaaaaaaaaa Aaaaaaaaaaaaaaaa Aaaaaaaaaaaa Aaa Aaaaaaaa Aaaa Aaa Aaaaaa Aaaaaaa Aaa Aaaa Aaaaaaaaaa Aaaaaa Aaaa Aaa Aaaa Aaaaaaa Aaa Aaaaaaaaaaaaaaaaaaa Aaaa Aaaaa Aaa Aaa Aaa Aaa Aaaaaaaa Aaaaaaaaa Aa Aaa Aaaaaa
🔒The cross-thread lifetime and reference-counting implications of this bug are analyzed in depth, including how realistic an escalation beyond the observed crash is.
Subscribe to read more
Audit directions
a Aaaa Aaaaaa Aaaaa Aaaaaa Aaaaaa Aaaaaaa Aaaa Aaaa Aaaaa Aaaaaaaaaaa Aaaa Aaa Aaaaaaaaaaaaaaaaaaaaaaa Aaa Aaaa Aaa Aaaaaaa Aaaaa Aaa Aaaaaaa Aaaaaaaaa Aaaa Aaaaaaaaaaaaaaaaaaa Aaa Aaaaaaaaaaaaaaaaaaaaaaaaa Aaa Aaa Aaaa Aaaaa Aaaaaaaa Aaaa Aaaaa Aaaaaaaaaaaaa Aaaaa Aa Aaaaaaaa Aaa Aaaaaaaaaaaaaaaaaaaa Aaa Aaaaaaaaa Aaaaaaaaaaa Aaa Aa Aaaaaa Aaaaaaaaaaaa Aaa Aaaaaaaaaaaaaaaaa Aaaaaaa Aaaaa Aaaaaa Aa Aaa Aaaaaaaaaaaaaaaaaa Aaaaaaaa
a Aaaaaa Aaaaa Aaaaaaa Aa a Aaaaa Aaaaaaaaaaaa Aaaaaaaaa Aaaaaaa a Aaaaaaaaaaaaa Aaaaaaaa Aaaaaa Aaaaa Aaaaaaa Aaaaaaaa Aaaaa Aaa Aaaaaaa Aa Aaa Aaaaaaaaa Aaaaaa Aaaaa Aaa Aaaaaaaaaaaaaaaaaaaaaaaa Aaaaaaaaaaaaa Aaaaaaaaa Aaaaaaaaaa Aaa Aaaaaaa Aaaa Aa Aaaaaaaaaaaaaaaaaaaaaaa Aaaaa Aaaaaaa Aa Aaaaaaaaaaaaaaaaaaaaaaaaaaaaa Aaaa Aaaaaaa Aaa Aaa Aaaa Aaaaaaa
a Aaaaaaaa Aaaaaaa Aaaaa Aaaaaaaaaaaaaaa Aaaaa Aaaaa Aaaaaa Aaaa Aaa Aaaaaaaa Aaaaaaaa Aaaaaaa Aaaaaaaaaa Aaaaaa Aaaaaaaaaaa Aaaaaa Aaaaaa Aaaaa Aaaaaaaa Aaa Aaaaaaaaa Aaa Aaaaaaaaaaa Aaaa Aaaaaa Aaaaaa Aaaa Aaa Aaaaaaaaaaaaa Aaaaa Aaaaa Aaaaaaaaaa Aa Aaaaaaaaaaaaaaaaaaaaaaaaa Aa Aaaa Aaaaaaa Aaaaaaaaaaaaaaa Aaaaaa Aaaaaa Aaaaaa Aaaaaaaa
🔒Multiple reusable audit patterns identified for finding thread-safety gaps across related WebKit animation subsystems, with concrete starting points.
Subscribe to read more