f4a3c606be8278f75d69df55b2a25bdc521c8361 Fix null deref in ApplyIterationAccumulation
Triage note: Adds !cached_value_ guard; fixes three ClusterFuzz null-deref reports reachable from web content via the Web Animations API. Null-deref crash (DoS), not exploitable corruption.
The bug at a glance
This is a genuine but low-severity security bug: a null-pointer dereference in the Web Animations interpolation path, reachable from ordinary script, that crashes the renderer. It was found by ClusterFuzz (three crash IDs cited) and fixed with a one-line null guard plus a WPT crashtest. Null derefs at a fixed, non-attacker-influenced address are a reliable denial-of-service (renderer tab crash) but are not memory corruption: there is no OOB write, no controlled pointer, no path to code execution shown or implied by the diff. Chromium consistently rates such renderer-crash null derefs as low; the upstream record agrees (severity low, CVSS 3.7). The intake band=high/score 8.3 is a scoring artifact and overstates it — the diff shows a straightforward defensive null check, not a corruption primitive. Low is the honest rating.
Web Animations lets script build a KeyframeEffect for any property name, including ones the animation engine does not actually support as an interpolable/animatable target (here the CSS ‘content’ property). Combined with iterationComposite:‘accumulate’ and iterations>1, the accumulation code path runs. It had already null-checked one cached pointer (cached_end_value_) but not the sibling cached_value_. For an unsupported property the interpolation never produces a cached_value_, so it stays null and the accumulation logic dereferences it, crashing the renderer. The angle is purely reachability of an under-guarded code path with a legitimate but ‘unsupported’ property.
Root cause
In ApplyIterationAccumulation (invalidatable_interpolation.cc) an early-return guard bails out unless the effect is mid-iteration, using kIterationCompositeAccumulate, and has a valid cached_end_value_. The guard omitted cached_value_. When an animation targets a property that the interpolation stack cannot resolve to a typed interpolation value, cached_value_ is never populated and remains null while cached_end_value_ may still be set, so the guard passes and downstream code dereferences the null cached_value_. The fix adds ‘|| !cached_value_’ to the guard so the function returns early whenever either cached pointer is absent, making accumulation a no-op for unsupported properties instead of crashing. The accompanying crashtest constructs exactly this configuration: a KeyframeEffect on the ‘content’ property with iterations:10 and iterationComposite:‘accumulate’, assigned to a running animation, which pre-patch triggers the null deref during a sample.
Key code
Guard extended to also require cached_value_ before accumulation runs
if (current_iteration_ <= 0 ||
current_iteration_composite_ !=
EffectModel::kIterationCompositeAccumulate ||
- !cached_end_value_) {
+ !cached_end_value_ || !cached_value_) {
return;
}
Patch walkthrough
third_party/blink/renderer/core/animation/invalidatable_interpolation.cc— The one-line functional fix. Inside ApplyIterationAccumulation, the guarding if-condition that early-returns for cases where accumulation should not run gains the clause ‘|| !cached_value_’. Previously only cached_end_value_ was checked; the sibling cached_value_ pointer could still be null (as happens for unsupported/non-interpolable properties) and be dereferenced later in the function. With the added check the function returns cleanly, so accumulation is skipped and no crash occurs. Behavior for supported properties is unchanged because for them cached_value_ is populated and the extra clause is false.third_party/blink/web_tests/external/wpt/web-animations/crashtests/iteration-composite-unsupported-propery.html— New WPT crashtest (filename typo ‘propery’ is upstream’s). It creates a KeyframeEffect targeting the CSS ‘content’ property with duration 1000, iterations 10, iterationComposite:‘accumulate’, assigns it to target.animate([]) via anim.effect = effect on window load, and asserts the page does not crash. This is the regression guard; a crashtest passes iff the renderer stays alive through sampling.
Background
iterationComposite: accumulate — A Web Animations mode where the effect of successive iterations accumulates rather than replacing the prior iteration’s value. It engages the ApplyIterationAccumulation path that combines the current interpolation value with an end value across iterations.
cached_value_ vs cached_end_value_ — InvalidatableInterpolation caches the resolved interpolation value for the current position (cached_value_) and the end-of-iteration value (cached_end_value_). Both are lazily populated; for a property the stack cannot interpolate, cached_value_ can remain null while other state exists.
Unsupported property (e.g. content) — Script may name any property in keyframes. Some, like ‘content’, are not animatable/interpolable, so the interpolation resolves to nothing and the cached typed value is never produced — the precondition that exposes the missing guard.
ClusterFuzz — Chromium’s continuous fuzzing infrastructure. It filed the three crash reports (467450081, 467312393) that motivated this fix, indicating the bug was found by automated fuzzing rather than in-the-wild abuse.
Vulnerability window
- Introduction — The accumulation guard was written checking only cached_end_value_, implicitly assuming cached_value_ would be present whenever accumulation ran — an assumption that breaks for unsupported properties.
- Discovery — Found by ClusterFuzz as three duplicate/related null-deref crash reports (bugs 467450081, 467312393), targeting the iteration-composite accumulate path with a non-interpolable property.
- Fix — Landed 2026-01-09 as main@{#1567027} by Kevin Ellis, adding the null check and a WPT crashtest, reviewed by David Awogbemila.
- Impact window — Any build before the fix could be crashed on demand by a page using this animation configuration; impact is a renderer/tab crash (DoS), not corruption.
Proof of concept
A crashtest ships with the commit, so the trigger is fully known and reconstructible; it demonstrates a crash (DoS), not an exploit. The reproduction: build a KeyframeEffect on an unsupported property (‘content’), set iterations>1 and iterationComposite:‘accumulate’, attach it to a running animation, and let the animation sample. Pre-patch this dereferences the null cached_value_ in ApplyIterationAccumulation and crashes the renderer; post-patch the guard returns early and nothing happens. The shipped test HTML below is the actual repro from the diff — it is a crashtest (proves liveness), and no memory-corruption exploit is demonstrated or possible from this null deref as shown.
<!DOCTYPE html>
<html>
<style>
#target {
background-color: hotpink;
width: 100px;
height: 100px;
margin: 50px;
}
</style>
<script>
window.onload = function(){
const effect = new KeyframeEffect(
target,
[ { "content": "normal" } ],
{
duration: 1000,
iterations: 10,
iterationComposite:"accumulate"
});
const anim = target.animate([]);
anim.effect = effect;
};
</script>
<body>
<div id="target"></div>
</body>
</html>
Exploitation
- Construct effect — Script creates a KeyframeEffect whose keyframes target a non-interpolable property (content), so no typed interpolation value is cached.
- Enable accumulation — Set timing with iterations>1 and iterationComposite:‘accumulate’ to route sampling through ApplyIterationAccumulation.
- Attach and run — Assign the effect to a live Animation (anim.effect = effect) so the effect is sampled while current_iteration_ > 0 and cached_end_value_ is set but cached_value_ is null.
- Outcome — Pre-patch: null dereference crashes the renderer process (tab loss / DoS). No control over the faulting address is shown, so this does not extend to memory corruption or code execution.
Detection & hunting
For defenders and detection engineers:
- Renderer crash signature — Look for null-deref crashes with ApplyIterationAccumulation / InvalidatableInterpolation on the stack, correlated with Web Animations sampling. The three ClusterFuzz IDs are the canonical fingerprints.
- Content pattern — Telemetry or fuzz corpus hits combining iterationComposite:‘accumulate’ with keyframes on non-animatable properties are the reproduction shape and worth flagging.
- Crashtest regression — Ensure iteration-composite-unsupported-propery.html stays in the WPT crashtest suite; a future refactor removing the guard would re-break it.
Audit directions
- Sibling cached-pointer guards — Audit other early-return guards in invalidatable_interpolation.cc and related interpolation code that check one cached pointer but dereference another; the same asymmetric-guard pattern may exist for cached_value_/cached_end_value_ in composite or blending paths.
- Unsupported-property handling — Review all Web Animations code paths that assume a cached typed interpolation value exists, and confirm they tolerate non-interpolable properties (content, and other non-animatable CSS properties) without dereferencing null.
- Composite modes — Extend the crashtest matrix to other iterationComposite/composite combinations with unsupported properties to catch analogous null-state assumptions.