Chrome · Glic
CVE-2026-79181
Logic Error in Glic
Overview
Low
Severity
—
CVSS
No
Exploited ITW
Fixed
Fix Status
Changed Functions
| Function | Change | Notes |
|---|---|---|
RegisterFrameCallbackthird_party/blink/renderer/core/dom/frame_request_callback_collection.cc |
modified | |
ifthird_party/blink/renderer/core/dom/frame_request_callback_collection.cc |
modified | |
forthird_party/blink/renderer/core/dom/frame_request_callback_collection.cc |
modified |
Files Changed
third_party/blink/renderer/core/animation_frame/worker_animation_frame_provider.ccthird_party/blink/renderer/core/dom/document.ccthird_party/blink/renderer/core/dom/document.hthird_party/blink/renderer/core/dom/frame_request_callback_collection.cc
Patch
From 6937cb76b634b8f44877a3e9443b30baddf28c5f Mon Sep 17 00:00:00 2001
From: William Liu <liuwilliam@chromium.org>
Date: Mon, 27 Jul 2026 11:02:16 -0700
Subject: [PATCH] [blink] Distinguish between Web and internal requestAnimationFrame
Fixed: b:506539337
Change-Id: I891083a789417a1d9317b95223091d3c65f30bc1
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/8144040
Reviewed-by: Adithya Srinivasan <adithyas@chromium.org>
Commit-Queue: William Liu <liuwilliam@chromium.org>
Reviewed-by: Philip Rogers <pdr@chromium.org>
Cr-Commit-Position: refs/heads/main@{#1668807}
---
diff --git a/third_party/blink/renderer/core/animation_frame/worker_animation_frame_provider.cc b/third_party/blink/renderer/core/animation_frame/worker_animation_frame_provider.cc
index 7a646f03..2a9b35f 100644
--- a/third_party/blink/renderer/core/animation_frame/worker_animation_frame_provider.cc
+++ b/third_party/blink/renderer/core/animation_frame/worker_animation_frame_provider.cc
@@ -32,13 +32,14 @@
}
FrameRequestCallbackCollection::CallbackId id =
- callback_collection_.RegisterFrameCallback(callback);
+ callback_collection_.RegisterFrameCallback(
+ callback, FrameCallbackType::kWebExposed);
begin_frame_provider_->RequestBeginFrame();
return id;
}
void WorkerAnimationFrameProvider::CancelCallback(int id) {
- callback_collection_.CancelFrameCallback(id);
+ callback_collection_.CancelFrameCallback(id, FrameCallbackType::kWebExposed);
}
void WorkerAnimationFrameProvider::BeginFrame(const viz::BeginFrameArgs& args) {
diff --git a/third_party/blink/renderer/core/dom/document.cc b/third_party/blink/renderer/core/dom/document.cc
index 460ac2eb..e3961a3 100644
--- a/third_party/blink/renderer/core/dom/document.cc
+++ b/third_party/blink/renderer/core/dom/document.cc
@@ -8849,12 +8849,13 @@
return *scripted_animation_controller_;
}
-int Document::RequestAnimationFrame(FrameCallback* callback) {
- return scripted_animation_controller_->RegisterFrameCallback(callback);
+int Document::RequestAnimationFrame(FrameCallback* callback,
+ FrameCallbackType type) {
+ return scripted_animation_controller_->RegisterFrameCallback(callback, type);
}
-void Document::CancelAnimationFrame(int id) {
- scripted_animation_controller_->CancelFrameCallback(id);
+void Document::CancelAnimationFrame(int id, FrameCallbackType type) {
+ scripted_animation_controller_->CancelFrameCallback(id, type);
}
DocumentLoader* Document::Loader() const {
diff --git a/third_party/blink/renderer/core/dom/document.h b/third_party/blink/renderer/core/dom/document.h
index 6e735c85..a6c0c2e0 100644
--- a/third_party/blink/renderer/core/dom/document.h
+++ b/third_party/blink/renderer/core/dom/document.h
@@ -75,6 +75,7 @@
#include "third_party/blink/renderer/core/dom/element.h"
#include "third_party/blink/renderer/core/dom/events/event_path.h"
#include "third_party/blink/renderer/core/dom/focus_params.h"
+#include "third_party/blink/renderer/core/dom/frame_request_callback_collection.h"
#include "third_party/blink/renderer/core/dom/live_node_list_registry.h"
#include "third_party/blink/renderer/core/dom/node_list_invalidation_type.h"
#include "third_party/blink/renderer/core/dom/qualified_name.h"
@@ -1660,8 +1661,8 @@
IsInOutermostMainFrame();
}
- int RequestAnimationFrame(FrameCallback*);
- void CancelAnimationFrame(int id);
+ int RequestAnimationFrame(FrameCallback*, FrameCallbackType type);
+ void CancelAnimationFrame(int id, FrameCallbackType type);
ScriptedAnimationController& GetScriptedAnimationController();
diff --git a/third_party/blink/renderer/core/dom/frame_request_callback_collection.cc b/third_party/blink/renderer/core/dom/frame_request_callback_collection.cc
index d20b374..1b6f2302 100644
--- a/third_party/blink/renderer/core/dom/frame_request_callback_collection.cc
+++ b/third_party/blink/renderer/core/dom/frame_request_callback_collection.cc
@@ -16,12 +16,18 @@
: context_(context) {}
FrameRequestCallbackCollection::CallbackId
-FrameRequestCallbackCollection::RegisterFrameCallback(FrameCallback* callback) {
- FrameRequestCallbackCollection::CallbackId id = ++next_callback_id_;
+FrameRequestCallbackCollection::RegisterFrameCallback(FrameCallback* callback,
+ FrameCallbackType type) {
+ FrameRequestCallbackCollection::CallbackId id =
+ (type == FrameCallbackType::kInternal) ? ++next_internal_callback_id_
+ : ++next_callback_id_;
callback->SetIsCancelled(false);
callback->SetId(id);
- frame_callbacks_.push_back(callback);
-
+ if (type == FrameCallbackType::kInternal) {
+ internal_frame_callbacks_.push_back(callback);
+ } else {
+ frame_callbacks_.push_back(callback);
+ }
DEVTOOLS_TIMELINE_TRACE_EVENT_INSTANT("RequestAnimationFrame",
inspector_animation_frame_event::Data,
context_, id);
@@ -30,19 +36,28 @@
return id;
}
-void FrameRequestCallbackCollection::CancelFrameCallback(CallbackId id) {
- for (wtf_size_t i = 0; i < frame_callbacks_.size(); ++i) {
- if (frame_callbacks_[i]->Id() == id) {
- frame_callbacks_[i]->async_task_context()->Cancel();
+void FrameRequestCallbackCollection::CancelFrameCallback(
+ CallbackId id,
+ FrameCallbackType type) {
+ auto& callbacks = (type == FrameCallbackType::kInternal)
+ ? internal_frame_callbacks_
+ : frame_callbacks_;
+ auto& callbacks_to_invoke = (type == FrameCallbackType::kInternal)
+ ? internal_callbacks_to_invoke_
+ : callbacks_to_invoke_;
+
+ for (wtf_size_t i = 0; i < callbacks.size(); ++i) {
+ if (callbacks[i]->Id() == id) {
+ callbacks[i]->async_task_context()->Cancel();
probe::BreakableLocation(context_, "cancelAnimationFrame");
- frame_callbacks_.EraseAt(i);
+ callbacks.EraseAt(i);
DEVTOOLS_TIMELINE_TRACE_EVENT_INSTANT(
"CancelAnimationFrame", inspector_animation_frame_event::Data,
context_.Get(), id);
return;
}
}
- for (const auto& callback : callbacks_to_invoke_) {
+ for (const auto& callback : callbacks_to_invoke) {
if (callback->Id() == id) {
callback->async_task_context()->Cancel();
probe::BreakableLocation(context_, "cancelAnimationFrame");
@@ -64,19 +79,32 @@
ExecutionContext::ScopedRequestAnimationFrameStatus scoped_raf_status(
context_);
- // First, generate a list of callbacks to consider. Callbacks registered from
- // this point on are considered only for the "next" frame, not this one.
+ // First, snapshot both lists. Callbacks registered from this point on are
+ // considered only for the "next" frame, not this one.
DCHECK(callbacks_to_invoke_.empty());
+ DCHECK(internal_callbacks_to_invoke_.empty());
swap(callbacks_to_invoke_, frame_callbacks_);
+ swap(internal_callbacks_to_invoke_, internal_frame_callbacks_);
- for (const auto& callback : callbacks_to_invoke_) {
+ ExecuteFrameCallbacksImpl(callbacks_to_invoke_, high_res_now_ms,
+ high_res_now_ms_legacy);
+ ExecuteFrameCallbacksImpl(internal_callbacks_to_invoke_, high_res_now_ms,
+ high_res_now_ms_legacy);
+}
+
+void FrameRequestCallbackCollection::ExecuteFrameCallbacksImpl(
+ CallbackList& callbacks_to_invoke,
+ double high_res_now_ms,
+ double high_res_now_ms_legacy) {
+ for (const auto& callback : callbacks_to_invoke) {
// When the ExecutionContext is destroyed (e.g. an iframe is detached),
// there is no path to perform wrapper tracing for the callbacks. In such a
// case, the callback functions may already have been collected by V8 GC.
// Since it's possible that a callback function being invoked detaches an
// iframe, we need to check the condition for each callback.
- if (context_->IsContextDestroyed())
+ if (context_->IsContextDestroyed()) {
break;
+ }
if (callback->IsCancelled()) {
// Another requestAnimationFrame callback already cancelled this one
UseCounter::Count(context_,
@@ -89,18 +117,21 @@
probe::AsyncTask async_task(context_, callback->async_task_context());
probe::UserCallback probe(context_, "requestAnimationFrame", AtomicString(),
true);
- if (callback->GetUseLegacyTimeBase())
+ if (callback->GetUseLegacyTimeBase()) {
callback->Invoke(high_res_now_ms_legacy);
- else
+ } else {
callback->Invoke(high_res_now_ms);
+ }
}
- callbacks_to_invoke_.clear();
+ callbacks_to_invoke.clear();
}
void FrameRequestCallbackCollection::Trace(Visitor* visitor) const {
visitor->Trace(frame_callbacks_);
visitor->Trace(callbacks_to_invoke_);
+ visitor->Trace(internal_frame_callbacks_);
Loading diff…
Regression Test / PoC
shipped with the fix
diff --git a/third_party/blink/renderer/core/dom/scripted_animation_controller_test.cc b/third_party/blink/renderer/core/dom/scripted_animation_controller_test.cc
index c4001c15..b5473fe 100644
--- a/third_party/blink/renderer/core/dom/scripted_animation_controller_test.cc
+++ b/third_party/blink/renderer/core/dom/scripted_animation_controller_test.cc
@@ -184,7 +184,8 @@
event->SetTarget(&GetDocument());
Controller().RegisterFrameCallback(
- MakeGarbageCollected<RunTaskCallback>(observer.CreateTask(1)));
+ MakeGarbageCollected<RunTaskCallback>(observer.CreateTask(1)),
+ FrameCallbackType::kWebExposed);
Controller().EnqueueTask(observer.CreateTask(2));
EXPECT_EQ(0u, observer.Order().size());
@@ -199,19 +200,22 @@
TaskOrderObserver observer;
Controller().RegisterFrameCallback(
- MakeGarbageCollected<RunTaskCallback>(observer.CreateTask(1)));
+ MakeGarbageCollected<RunTaskCallback>(observer.CreateTask(1)),
+ FrameCallbackType::kWebExposed);
EXPECT_TRUE(Controller().HasFrameCallback());
- Controller().CancelFrameCallback(1);
+ Controller().CancelFrameCallback(1, FrameCallbackType::kWebExposed);
EXPECT_FALSE(Controller().HasFrameCallback());
Controller().RegisterFrameCallback(
- MakeGarbageCollected<RunTaskCallback>(observer.CreateTask(1)));
+ MakeGarbageCollected<RunTaskCallback>(observer.CreateTask(1)),
+ FrameCallbackType::kWebExposed);
Controller().RegisterFrameCallback(
- MakeGarbageCollected<RunTaskCallback>(observer.CreateTask(2)));
+ MakeGarbageCollected<RunTaskCallback>(observer.CreateTask(2)),
+ FrameCallbackType::kWebExposed);
EXPECT_TRUE(Controller().HasFrameCallback());
- Controller().CancelFrameCallback(1);
+ Controller().CancelFrameCallback(1, FrameCallbackType::kWebExposed);
EXPECT_TRUE(Controller().HasFrameCallback());
// Servicing the scripted animations should call the remaining callback and
@@ -233,7 +237,8 @@
*ran_callback = true;
},
WrapPersistent(&Controller()),
- blink::subtle::UnretainedException(&ran_callback))));
+ blink::subtle::UnretainedException(&ran_callback))),
+ FrameCallbackType::kWebExposed);
PageAnimator::ServiceScriptedAnimations(base::TimeTicks(),
{{Controller(), false}});
@@ -242,4 +247,52 @@
EXPECT_FALSE(Controller().GetExecutionContext()->IsInRequestAnimationFrame());
}
+TEST_F(ScriptedAnimationControllerTest, TestInternalCallbackIsolation) {
+ TaskOrderObserver observer;
+
+ // Web-exposed callback registration gets ID 1 in web pool.
+ int web_id = Controller().RegisterFrameCallback(
+ MakeGarbageCollected<RunTaskCallback>(observer.CreateTask(1)),
+ FrameCallbackType::kWebExposed);
+ EXPECT_EQ(1, web_id);
+
+ // Internal callback registration gets independent ID 1 in internal pool.
+ int internal_id = Controller().RegisterFrameCallback(
+ MakeGarbageCollected<RunTaskCallback>(observer.CreateTask(2)),
+ FrameCallbackType::kInternal);
+ EXPECT_EQ(1, internal_id);
+
+ // Canceling internal callback with FrameCallbackType::kInternal should cancel
+ // internal without affecting web callback.
+ Controller().CancelFrameCallback(internal_id, FrameCallbackType::kInternal);
+
+ // Servicing scripted animations should run only the web callback.
+ PageAnimator::ServiceScriptedAnimations(base::TimeTicks(),
+ {{Controller(), false}});
+ EXPECT_EQ(1u, observer.Order().size());
+ EXPECT_EQ(1, observer.Order()[0]);
+}
+
+TEST_F(ScriptedAnimationControllerTest,
+ TestCancelWebCallbackDoesNotCancelInternalCallback) {
+ TaskOrderObserver observer;
+
+ int web_id = Controller().RegisterFrameCallback(
+ MakeGarbageCollected<RunTaskCallback>(observer.CreateTask(1)),
+ FrameCallbackType::kWebExposed);
+ Controller().RegisterFrameCallback(
+ MakeGarbageCollected<RunTaskCallback>(observer.CreateTask(2)),
+ FrameCallbackType::kInternal);
+
+ // CancelFrameCallback with kWebExposed cancels web callback without
+ // affecting internal callback.
+ Controller().CancelFrameCallback(web_id, FrameCallbackType::kWebExposed);
+
+ // Servicing scripted animations should run only the internal callback.
+ PageAnimator::ServiceScriptedAnimations(base::TimeTicks(),
+ {{Controller(), false}});
+ EXPECT_EQ(1u, observer.Order().size());
+ EXPECT_EQ(2, observer.Order()[0]);
+}
+
} // namespace blink
diff --git a/third_party/blink/renderer/core/frame/document_loading_rendering_test.cc b/third_party/blink/renderer/core/frame/document_loading_rendering_test.cc
index 786c949..223d8dc4 100644
--- a/third_party/blink/renderer/core/frame/document_loading_rendering_test.cc
+++ b/third_party/blink/renderer/core/frame/document_loading_rendering_test.cc
@@ -343,7 +343,8 @@
// Frame while the child frame still has pending sheets.
auto* frame1_callback = MakeGarbageCollected<CheckRafCallback>();
- child_frame->contentDocument()->RequestAnimationFrame(frame1_callback);
+ child_frame->contentDocument()->RequestAnimationFrame(
+ frame1_callback, FrameCallbackType::kWebExposed);
auto frame1 = Compositor().BeginFrame();
EXPECT_FALSE(frame1_callback->WasCalled());
EXPECT_TRUE(frame1.Contains(SimCanvas::kRect, "red"));
@@ -356,7 +357,8 @@
// Frame with all lifecycle updates enabled.
auto* frame2_callback = MakeGarbageCollected<CheckRafCallback>();
- child_frame->contentDocument()->RequestAnimationFrame(frame2_callback);
+ child_frame->contentDocument()->RequestAnimationFrame(
+ frame2_callback, FrameCallbackType::kWebExposed);
auto frame2 = Compositor().BeginFrame();
EXPECT_TRUE(frame1_callback->WasCalled());
EXPECT_TRUE(frame2_callback->WasCalled());
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.
References
On This Page