Low watch CVSS 8.8 chrome UAF 🔧 Commit mapped

Overview

Low
Severity
8.8
CVSS
No
Exploited ITW
Fixed
Fix Status
ImpactUse after free in Animation
DescriptionUse after free in Animation
ComponentAnimation
Bug ClassUAF
Action tierwatch — corruption primitive but gated (uncommon) — not default-reachable
Computed by PatchGap's deterministic exploitability ruler from the fixing commit. patch-now = RCE-class & attacker-reachable in a default config, or exploited in the wild; watch = real security signal below that bar; noise = no security relevance.
Tracker523572877
Fix commit827068bf9f5c (chromium/src) +12/-1
CWECWE-416 (Use-after-free)
CVSS vectorCVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:U/C:H/I:H/A:H
CISA KEVNot listed
CreditedGoogle
Reported2026-06-13 (per vendor advisory)
Fix landed2026-07-23 (public git)
Disclosed2026-08-25
Patch gap33 days — fix public in source before the advisory

Background

AnimationWorklet
a Blink module allowing script to register worklet-driven animations; each animation is assigned a unique id via AnimationWorklet::NextWorkletAnimationId().
`WorkletAnimationController::animations_`
a HeapHashMap<int, Member<WorkletAnimationBase>> keyed by the 32-bit signed animation id, tracking live worklet animations.
`WTF::IntHashTraits<int>`
Blink’s hash traits for int keys, which reserve sentinel values (empty 0 and a DeletedValue) that must never be used as real keys.
`CHECK_GT`
a Blink/Chromium runtime assertion that aborts the process if the first argument is not strictly greater than the second.

Root Cause Analysis

AnimationWorklet::NextWorkletAnimationId() produced each new id with ++last_animation_id_ on a 32-bit signed int counter, assuming ids remain positive so they are safe to use as keys with default hash traits. If the counter is driven high enough to overflow, ++last_animation_id_ wraps INT_MAX to the negative range and eventually to 0 or -1 — values WTF::IntHashTraits<int> reserves for the empty and DeletedValue sentinels of HeapHashMap<int, Member<WorkletAnimationBase>> animations_. Using a sentinel value as a real map key corrupts the map’s bookkeeping, so an entry can collide with or be treated as deleted, leaving a Member<WorkletAnimationBase> that is freed while still referenced — the use-after-free.

The fix increments the counter as a separate statement and adds CHECK_GT(last_animation_id_, 0) immediately after, so any wrap to a non-positive value deterministically crashes instead of yielding a poisoned key. This restores the invariant, stated in the retained comment, that ids “start from 1” and are always positive.

Key insight
The single mistake was treating an unbounded post-increment id counter as always positive and thus safe as a hash-map key, ignoring signed overflow into the reserved 0/DeletedValue sentinels; the fix adds CHECK_GT(last_animation_id_, 0) to convert the dangerous overflow into a safe abort.

Attack Path

  1. Drive id allocation Script repeatedly triggers worklet animation creation so AnimationWorklet::NextWorkletAnimationId() is called enough times to push last_animation_id_ toward INT_MAX (large volume required; feasibility is (inferred)).
  2. Overflow the counter ++last_animation_id_ wraps past INT_MAX, and continued allocation reaches 0 or -1.
  3. Collide with hash sentinels The id 0/-1 is used as a key in animations_, colliding with the empty/DeletedValue sentinels of WTF::IntHashTraits<int> and corrupting the map’s entry state.
  4. Trigger use-after-free The sentinel collision causes a WorkletAnimationBase entry to be dropped/freed while still referenced, so a later access dereferences freed memory (inferred) as the exploitable UAF.

Impact Assessment

An attacker with script execution in a page using AnimationWorklet could, by exhausting the animation-id space, corrupt WorkletAnimationController::animations_ and cause a use-after-free of a WorkletAnimationBase object in the Blink renderer process. Exploitation requires driving the counter through 32-bit signed overflow, and any escalation to controlled read/write or code execution beyond the freed-object access is (inferred). The advisory classifies this as a use-after-free in Animation; no exploited-in-the-wild status is stated.

Changed Functions

FunctionChangeNotes
TEST_F
third_party/blink/renderer/modules/animationworklet/animation_worklet_global_scope_test.cc
modified

Files Changed

  • third_party/blink/renderer/modules/animationworklet/animation_worklet_global_scope_test.cc
  • third_party/blink/renderer/modules/animationworklet/animation_worklet.cc

Audit Directions

  • Monotonic id counters as hash keys
    Review any int/int32 counter incremented without bound whose values become HeapHashMap/HashSet keys, since overflow into 0/DeletedValue sentinels corrupts the container.
  • Default hash-traits sentinel assumptions
    Audit code assuming ids “start from 1” or are always positive for WTF::IntHashTraits-keyed maps, and verify overflow, negative, or zero values cannot reach the key.
  • Long-lived allocators without saturation guards
    Look for id/handle generators lacking overflow checks in renderer components reachable from script, where sustained allocation can reach wraparound.
From 827068bf9f5c9e087cecb790491cd657aae2705b Mon Sep 17 00:00:00 2001
From: Etienne Bergeron <etienneb@google.com>
Date: Thu, 23 Jul 2026 08:21:04 -0700
Subject: [PATCH] Fix potential Use-After-Free in WorkletAnimationController via animation ID overflow

In AnimationWorklet::NextWorkletAnimationId(), add a
CHECK_GT(last_animation_id_, 0) guard after incrementing
last_animation_id_. This prevents 32-bit signed integer overflow from
wrapping around to 0 or -1, which collides with the reserved
DeletedValue sentinel in WTF::IntHashTraits<int> used by
HeapHashMap<int, Member<WorkletAnimationBase>> animations_ in
WorkletAnimationController.

Fixed: 523572877
Change-Id: I225c015e2b96994202abb34a3bc7dc547ac30fdc
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/8132941
Reviewed-by: Robert Flack <flackr@chromium.org>
Commit-Queue: Etienne Bergeron <etienneb@chromium.org>
Cr-Commit-Position: refs/heads/main@{#1667116}
---
diff --git a/third_party/blink/renderer/modules/animationworklet/animation_worklet.cc b/third_party/blink/renderer/modules/animationworklet/animation_worklet.cc
index 4182efc..86bc2295 100644
--- a/third_party/blink/renderer/modules/animationworklet/animation_worklet.cc
+++ b/third_party/blink/renderer/modules/animationworklet/animation_worklet.cc
@@ -60,7 +60,9 @@
 WorkletAnimationId AnimationWorklet::NextWorkletAnimationId() {
   // Id starts from 1. This way it safe to use it as key in hashmap with default
   // key traits.
-  return WorkletAnimationId(worklet_id_, ++last_animation_id_);
+  last_animation_id_++;
+  CHECK_GT(last_animation_id_, 0);
+  return WorkletAnimationId(worklet_id_, last_animation_id_);
 }
 
 void AnimationWorklet::Trace(Visitor* visitor) const {
diff --git a/third_party/blink/renderer/modules/animationworklet/animation_worklet_global_scope_test.cc b/third_party/blink/renderer/modules/animationworklet/animation_worklet_global_scope_test.cc
index 8f2b008..f86d758 100644
--- a/third_party/blink/renderer/modules/animationworklet/animation_worklet_global_scope_test.cc
+++ b/third_party/blink/renderer/modules/animationworklet/animation_worklet_global_scope_test.cc
@@ -471,4 +471,13 @@
   worklet->WaitForShutdownForTesting();
 }
 
+TEST_F(AnimationWorkletGlobalScopeTest, NextWorkletAnimationId) {
+  AnimationWorklet* worklet =
+      MakeGarbageCollected<AnimationWorklet>(*GetDocument().domWindow());
+  WorkletAnimationId id1 = worklet->NextWorkletAnimationId();
+  WorkletAnimationId id2 = worklet->NextWorkletAnimationId();
+  EXPECT_GT(id1.animation_id, 0);
+  EXPECT_GT(id2.animation_id, id1.animation_id);
+}
+
 }  // namespace blink
Loading diff…

Regression Test / PoC

shipped with the fix
diff --git a/third_party/blink/renderer/modules/animationworklet/animation_worklet_global_scope_test.cc b/third_party/blink/renderer/modules/animationworklet/animation_worklet_global_scope_test.cc
index 8f2b008..f86d758 100644
--- a/third_party/blink/renderer/modules/animationworklet/animation_worklet_global_scope_test.cc
+++ b/third_party/blink/renderer/modules/animationworklet/animation_worklet_global_scope_test.cc
@@ -471,4 +471,13 @@
   worklet->WaitForShutdownForTesting();
 }
 
+TEST_F(AnimationWorkletGlobalScopeTest, NextWorkletAnimationId) {
+  AnimationWorklet* worklet =
+      MakeGarbageCollected<AnimationWorklet>(*GetDocument().domWindow());
+  WorkletAnimationId id1 = worklet->NextWorkletAnimationId();
+  WorkletAnimationId id2 = worklet->NextWorkletAnimationId();
+  EXPECT_GT(id1.animation_id, 0);
+  EXPECT_GT(id2.animation_id, id1.animation_id);
+}
+
 }  // namespace blink
Loading diff…

Original Bug Report

The reporter's bug is still restricted on the tracker. Chrome de-restricts security bugs ~30–90 days after the fix ships; a later run will backfill it here.