Overview

Critical
Severity
CVSS
No
Exploited ITW
Fixed
Fix Status
ImpactUse after free in Canvas
DescriptionUse after free in Canvas
ComponentCanvas
Bug ClassUAF
Tracker494352590
Fix commit4d94eaa23143 (chromium/src) +991/-108
CISA KEVNot listed
Creditedheapracer
Disclosed2026-04-28

Changed Functions

FunctionChangeNotes
if
third_party/blink/renderer/modules/canvas/canvas2d/base_rendering_context_2d.cc
modified
for
third_party/blink/web_tests/wpt_internal/html/canvas/context-lost/2d-invalid-size-context-lost.html
modified

Files Changed

  • third_party/blink/renderer/modules/canvas/canvas2d/base_rendering_context_2d.cc
  • third_party/blink/renderer/modules/canvas/canvas2d/base_rendering_context_2d.h
  • third_party/blink/web_tests/external/wpt/html/canvas/resources/canvas-promise-test.js
  • third_party/blink/web_tests/wpt_internal/html/canvas/context-lost/2d-invalid-size-context-lost.html
From 4d94eaa23143776dd5b66e9a79b50dd5be3c5773 Mon Sep 17 00:00:00 2001
From: Jean-Philippe Gravel <[email protected]>
Date: Wed, 15 Apr 2026 16:10:17 -0700
Subject: [PATCH] Don't honor transient canvas size restoration

When giving an invalid size to a 2D canvas, a contextlost event is
fired. If a valid size is later given, contextrestored will fire.
Things get complicated if the page changes the size again while
the contextlost and contextrestore state transitions happen. The
current implementation was trying to honor all state transitions
in scenarios where, for instance, the canvas is given a valid size
and an invalid size again, before contextlost fires. A unit test
for instance cycled the size as "good => bad => good => bad" in the
same task and expected events contextlost, contextrestored,
contextlost [1].

This approach however doesn't really make sense. We cannot possibly
generalize to honoring any chain of same-task transitions. What if
we did one more "=> good => bad"? Really, doing "bad => good => bad"
in the same task is just the same as "bad => bad". Moreover, if the
canvas is restored while the size is invalid (even temporarily), the
CanvasResourceDispatcher of an OffscreenCanvas with a placeholder
would try to push a new frame, but since the size is invalid, the
context would get lost again, destroying the dispatcher while it's
being used.

This CL simplifies the logic by getting rid of the boolean
dispatch_context_restored_event_timer_ and instead just checking the
actual canvas size when it's time to move forward in the state machine.

This change is validated by adding web tests to validate more state
transition. There are lots of possible transitions to consider, given
that the page can change size before, during and after each events
forcing the canvas to bounce between lost and restored states.

[1] https://crsrc.org/c/third_party/blink/web_tests/wpt_internal/html/canvas/context-lost/2d-invalid-size-context-lost.html;l=146-165;drc=eb6fbdafc5a54fd4fd6f81bdc83e66b50891b682

Fixed: 494352590
Change-Id: Ib5ddab7bc51106ec04c361b3f9f8b63497f0847a
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/7760835
Commit-Queue: Jean-Philippe Gravel <[email protected]>
Reviewed-by: Vasiliy Telezhnikov <[email protected]>
Cr-Commit-Position: refs/heads/main@{#1615474}
---

diff --git a/third_party/blink/renderer/modules/canvas/canvas2d/base_rendering_context_2d.cc b/third_party/blink/renderer/modules/canvas/canvas2d/base_rendering_context_2d.cc
index d1520141..808c108 100644
--- a/third_party/blink/renderer/modules/canvas/canvas2d/base_rendering_context_2d.cc
+++ b/third_party/blink/renderer/modules/canvas/canvas2d/base_rendering_context_2d.cc
@@ -179,15 +179,13 @@
 }
 
 void BaseRenderingContext2D::DispatchContextLostEvent(TimerBase*) {
-  // If `need_dispatch_context_restored_` is `true`, the context has been
-  // restored already (e.g. by fixing a `kInvalidCanvasSize` context loss), but
-  // the oncontextrestored event was postponed until the oncontextlost event was
-  // dispatched first. This is happening now, so irrespective of how this
-  // function returns, `need_dispatch_context_restored_` should be cleared.
-  absl::Cleanup cleanup = [this] { need_dispatch_context_restored_ = false; };
+  CanvasRenderingContextHost* host = GetCanvasRenderingContextHost();
+  if (!host) {
+    return;
+  }
 
   Event* event = Event::CreateCancelable(event_type_names::kContextlost);
-  GetCanvasRenderingContextHost()->HostDispatchEvent(event);
+  host->HostDispatchEvent(event);
 
   UseCounter::Count(GetTopExecutionContext(),
                     WebFeature::kCanvasRenderingContext2DContextLostEvent);
@@ -199,7 +197,8 @@
     return;
   }
 
-  if (need_dispatch_context_restored_) {
+  if (context_lost_mode_ == CanvasRenderingContext::kInvalidCanvasSize &&
+      host->IsValidImageSize()) {
     // The context is already restored (an invalid canvas size was probably
     // fixed). We can send the restored event right away.
     dispatch_context_restored_event_timer_.StartOneShot(base::TimeDelta(),
@@ -298,16 +297,20 @@
   DCHECK(!GetResourceProvider());
 
   if (host->IsValidImageSize()) {
-    if (dispatch_context_lost_event_timer_.IsActive()) {
-      // An oncontextlost event is still pending. We can't send the
-      // oncontextrestored right away because the oncontextlost callback could
-      // choose to prevent restoration. Thus, we need to delay queuing the
-      // restored event to after the lost event completed.
-      need_dispatch_context_restored_ = true;
-    } else {
+    // The size was restored. Fire a contextrestored event, but only if there's
+    // no pending contextlost. contextlost needs to run first and it will take
+    // care of running contextrestored if the size is still valid at that point.
+    if (!dispatch_context_lost_event_timer_.IsActive()) {
       dispatch_context_restored_event_timer_.StartOneShot(base::TimeDelta(),
                                                           FROM_HERE);
     }
+  } else {
+    // The canvas was given another invalid size. Abort any pending
+    // contextrestored event, these would have to wait until the canvas is given
+    // a valid size.
+    if (dispatch_context_restored_event_timer_.IsActive()) {
+      dispatch_context_restored_event_timer_.Stop();
+    }
   }
 }
 
diff --git a/third_party/blink/renderer/modules/canvas/canvas2d/base_rendering_context_2d.h b/third_party/blink/renderer/modules/canvas/canvas2d/base_rendering_context_2d.h
index 910511c4..f6af4bd 100644
--- a/third_party/blink/renderer/modules/canvas/canvas2d/base_rendering_context_2d.h
+++ b/third_party/blink/renderer/modules/canvas/canvas2d/base_rendering_context_2d.h
@@ -365,7 +365,6 @@
   std::unique_ptr<Canvas2DResourceProviderSharedImage>
       resource_provider_from_webgpu_access_;
   Canvas2DColorParams color_params_;
-  bool need_dispatch_context_restored_ = false;
   base::RepeatingClosure on_restore_failed_callback_for_testing_;
 };
 
diff --git a/third_party/blink/web_tests/external/wpt/html/canvas/resources/canvas-promise-test.js b/third_party/blink/web_tests/external/wpt/html/canvas/resources/canvas-promise-test.js
index afcbec9a..63aaa11f 100644
--- a/third_party/blink/web_tests/external/wpt/html/canvas/resources/canvas-promise-test.js
+++ b/third_party/blink/web_tests/external/wpt/html/canvas/resources/canvas-promise-test.js
@@ -38,6 +38,7 @@
 ];
 OFFSCREEN_CANVAS_TEST_TYPES = [
     CanvasTestType.OFFSCREEN,
+    CanvasTestType.PLACEHOLDER,
     CanvasTestType.WORKER,
 ];
 MAIN_THREAD_CANVAS_TEST_TYPES = [
diff --git a/third_party/blink/web_tests/wpt_internal/html/canvas/context-lost/2d-invalid-size-context-lost.html b/third_party/blink/web_tests/wpt_internal/html/canvas/context-lost/2d-invalid-size-context-lost.html
index f299873..293de55 100644
--- a/third_party/blink/web_tests/wpt_internal/html/canvas/context-lost/2d-invalid-size-context-lost.html
+++ b/third_party/blink/web_tests/wpt_internal/html/canvas/context-lost/2d-invalid-size-context-lost.html
@@ -1,4 +1,5 @@
 <!doctype html>
+<meta name="timeout" content="long">
 <script src="/resources/testharness.js"></script>
 <script src="/resources/testharnessreport.js"></script>
 <script src="/html/canvas/resources/canvas-promise-test.js"></script>
@@ -12,6 +13,164 @@
 }
 
 /**
+ * Returns a promise that settles after `frameCount` animation frames.
+ */
+async function waitFrames(frameCount) {
+  for (let i = 0; i < frameCount; ++i) {
+    await new Promise(resolve => requestAnimationFrame(resolve))
+  }
+}
+
+/**
+ * Returns a promise that is rejected after `timeoutMs` milliseconds.
+ * On timeout, the promise rejection will include the `errorMessage` text.
+ */
+function promiseFinishBefore(timeoutMs, errorMessage) {
+  const { promise, reject } = Promise.withResolvers();
+  setTimeout(() => reject(new Error(errorMessage)), timeoutMs);
+  return promise;
+}
+
+/**
+ * Returns a promise that resolves when the contextlost event of the canvas
+ * associated with `ctx` fires.
+ *
+ * This function watches for contextlost events by registering an event
+ * handler. That event handler is unregistered when `signal` aborts. This allows
+ * racing between multiple events using `raceAndAbortOthers`.
+ *
+ * Of course, `promiseContextLost()` must be called before the contextlost event
+ * fires. If the event already fired before calling this function, the promise
+ * will never resolve. `promiseContextLost()` can still be called anytime before
+ * the current task finishes though because events can't be called concurrently
+ * to the current task:
+ *
+ *   somehowLoseTheContext();
+ *   const controller = new AbortController();
+ *   await promiseContextLost(controller.signal)
+ *       .finally(() => controller.abort());
+ *
+ * If dealing with asynchronous logic however, the promise needs to be created
+ * before the task that lost the context ends:
+ *
+ *   somehowLoseTheContext();
+ *   const controller = new AbortController();
+ *   const promise = promiseContextLost(ctx, controller.signal);
+ *   await waitFrames(1);  // The contextlost event could fire here.
+ *   await promise.finally(() => controller.abort());
+ */
+function promiseContextLost(ctx, signal) {
+  const { promise, resolve } = Promise.withResolvers();
+  ctx.canvas.addEventListener('contextlost', resolve, {signal});
+  return promise;
+}
+
+/**
+ * Returns a promise that resolves when the contextrestored event of the canvas
Loading diff…

Regression Test / PoC

shipped with the fix
diff --git a/third_party/blink/web_tests/external/wpt/html/canvas/resources/canvas-promise-test.js b/third_party/blink/web_tests/external/wpt/html/canvas/resources/canvas-promise-test.js
index afcbec9a..63aaa11f 100644
--- a/third_party/blink/web_tests/external/wpt/html/canvas/resources/canvas-promise-test.js
+++ b/third_party/blink/web_tests/external/wpt/html/canvas/resources/canvas-promise-test.js
@@ -38,6 +38,7 @@
 ];
 OFFSCREEN_CANVAS_TEST_TYPES = [
     CanvasTestType.OFFSCREEN,
+    CanvasTestType.PLACEHOLDER,
     CanvasTestType.WORKER,
 ];
 MAIN_THREAD_CANVAS_TEST_TYPES = [
diff --git a/third_party/blink/web_tests/wpt_internal/html/canvas/context-lost/2d-invalid-size-context-lost.html b/third_party/blink/web_tests/wpt_internal/html/canvas/context-lost/2d-invalid-size-context-lost.html
index f299873..293de55 100644
--- a/third_party/blink/web_tests/wpt_internal/html/canvas/context-lost/2d-invalid-size-context-lost.html
+++ b/third_party/blink/web_tests/wpt_internal/html/canvas/context-lost/2d-invalid-size-context-lost.html
@@ -1,4 +1,5 @@
 <!doctype html>
+<meta name="timeout" content="long">
 <script src="/resources/testharness.js"></script>
 <script src="/resources/testharnessreport.js"></script>
 <script src="/html/canvas/resources/canvas-promise-test.js"></script>
@@ -12,6 +13,164 @@
 }
 
 /**
+ * Returns a promise that settles after `frameCount` animation frames.
+ */
+async function waitFrames(frameCount) {
+  for (let i = 0; i < frameCount; ++i) {
+    await new Promise(resolve => requestAnimationFrame(resolve))
+  }
+}
+
+/**
+ * Returns a promise that is rejected after `timeoutMs` milliseconds.
+ * On timeout, the promise rejection will include the `errorMessage` text.
+ */
+function promiseFinishBefore(timeoutMs, errorMessage) {
+  const { promise, reject } = Promise.withResolvers();
+  setTimeout(() => reject(new Error(errorMessage)), timeoutMs);
+  return promise;
+}
+
+/**
+ * Returns a promise that resolves when the contextlost event of the canvas
+ * associated with `ctx` fires.
+ *
+ * This function watches for contextlost events by registering an event
+ * handler. That event handler is unregistered when `signal` aborts. This allows
+ * racing between multiple events using `raceAndAbortOthers`.
+ *
+ * Of course, `promiseContextLost()` must be called before the contextlost event
+ * fires. If the event already fired before calling this function, the promise
+ * will never resolve. `promiseContextLost()` can still be called anytime before
+ * the current task finishes though because events can't be called concurrently
+ * to the current task:
+ *
+ *   somehowLoseTheContext();
+ *   const controller = new AbortController();
+ *   await promiseContextLost(controller.signal)
+ *       .finally(() => controller.abort());
+ *
+ * If dealing with asynchronous logic however, the promise needs to be created
+ * before the task that lost the context ends:
+ *
+ *   somehowLoseTheContext();
+ *   const controller = new AbortController();
+ *   const promise = promiseContextLost(ctx, controller.signal);
+ *   await waitFrames(1);  // The contextlost event could fire here.
+ *   await promise.finally(() => controller.abort());
+ */
+function promiseContextLost(ctx, signal) {
+  const { promise, resolve } = Promise.withResolvers();
+  ctx.canvas.addEventListener('contextlost', resolve, {signal});
+  return promise;
+}
+
+/**
+ * Returns a promise that resolves when the contextrestored event of the canvas
+ * associated with `ctx` fires.
+ *
+ * Behaves similarly to `promiseContextLost`, see that function for more
+ * details.
+ */
+function promiseContextRestored(ctx, signal) {
+  const { promise, resolve } = Promise.withResolvers();
+  ctx.canvas.addEventListener('contextrestored', resolve, {signal});
+  return promise;
+}
+
+/**
+ * Returns a promise that is rejected if the contextlost event of the canvas
+ * associated with `ctx` fires.
+ *
+ * Behaves similarly to `promiseContextLost`, see that function for more
+ * details.
+ */
+function promiseContextNotLost(ctx, signal) {
+  const { promise, reject } = Promise.withResolvers();
+  ctx.canvas.addEventListener(
+      'contextlost',
+      () => reject(new Error('Unexpected contextlost event.')),
+      {signal});
+  return promise;
+}
+
+
+/**
+ * Returns a promise that is rejected if the contextrestored event of the canvas
+ * associated with `ctx` fires.
+ *
+ * Behaves similarly to `promiseContextLost`, see that function for more
+ * details.
+ */
+function promiseContextNotRestored(ctx, signal) {
+  const { promise, reject } = Promise.withResolvers();
+  ctx.canvas.addEventListener(
+      'contextrestored',
+      () => reject(new Error('Unexpected contextrestored event.')),
+      {signal});
+  return promise;
+}
+
+/**
+ * Returns a promise that resolves or rejects when the first promise in
+ * `promises` resolves or rejects. Once settled, this promise aborts
+ * `controller` to clean up all passed `promises`.
+ */
+function raceAndAbortOthers(controller, promises) {
+  return Promise.race(promises)
+      .finally(() => controller.abort());
+}
+
+/**
+ * Returns a promise that resolves when the canvas associated with `ctx` loses
+ * it's context. The promise rejects if the contextrestored fires, or if
+ * contextlost doesn't fire within `deadlineMs` milliseconds.
+ *
+ * Must be called before, or in the same task that loses the context or else the
+ * promise will never resolve.
+ */
+async function waitForContextLost(ctx, deadlineMs=2000) {
+  const controller = new AbortController();
+  await raceAndAbortOthers(controller, [
+    promiseFinishBefore(deadlineMs, 'Event contextlost should have fired.'),
+    promiseContextLost(ctx, controller.signal),
+    promiseContextNotRestored(ctx, controller.signal),
+  ]);
+  assert_true(ctx.isContextLost(), 'GPU context should be lost.');
+}
+
+/**
+ * Returns a promise that resolves when the canvas associated with `ctx` has
+ * it's context restored. The promise rejects if the contextlost fires, or if
+ * contextrestored doesn't fire within `deadlineMs` milliseconds.
+ *
+ * Must be called before the context is restored or else the promise will never
+ * resolve.
+ */
+async function waitForContextRestored(ctx, deadlineMs=2000) {
+  const controller = new AbortController();
+  await raceAndAbortOthers(controller, [
+    promiseFinishBefore(deadlineMs, 'Event contextrestored should have fired.'),
+    promiseContextNotLost(ctx, controller.signal),
+    promiseContextRestored(ctx, controller.signal),
+  ]);
+  assert_false(ctx.isContextLost(), 'GPU context should be restored.');
+}
+
+/**
+ * Returns a promise that resolves after `frameCount` animation frames and
+ * rejects if a contextlost or contextrestored event fires before then.
+ */
+async function waitFramesAndRejectOnContextEvents(ctx, frameCount=5) {
+  const controller = new AbortController();
+  await raceAndAbortOthers(controller, [
+    waitFrames(frameCount),
+    promiseContextNotLost(ctx, controller.signal),
+    promiseContextNotRestored(ctx, controller.signal),
+  ]);
+}
+
+/**
  * Tests that canvas 2d context can be lost by setting an invalid canvas size
  * and can be restored by later setting a valid size.
  */
@@ -84,98 +243,6 @@
    {testTypes: ALL_CANVAS_TEST_TYPES});
 
 /**
- * Validates that after losing the context due to an invalid canvas size, the
- * page can set a valid canvas size before the oncontextlost event is fired.
- */
-canvasPromiseTest(async (canvas) => {
-  const ctx = canvas.getContext('2d', {
-    // Stay on GPU acceleration despite read-backs.
-    willReadFrequently: false,
-  });
-
-  let contextLostEventInvoked = false;
-  let contextRestoredEventInvoked = false;
-  let contextLost = new Promise(resolve => {
-    canvas.oncontextlost = () => {
-      assert_false(contextRestoredEventInvoked,
-                   `oncontextrestore shouldn't be called before oncontextlost`);
-      contextLostEventInvoked = true;
-      resolve();
-    }
-  });
-  let contextRestored = new Promise(resolve => {
-    canvas.oncontextrestored = () => {
-      assert_true(contextLostEventInvoked,
-                  'oncontextlost should be called before oncontextrestored.');
-      contextRestoredEventInvoked = true;
-      resolve();
-    }
-  });
-
-  // Make sure the canvas is fully initialized.
-  drawAndReadBack(ctx);
-
-  // Set invalid size and use the canvas so it tries (and fails) to allocate a
-  // surface, triggering the context lost logic.
-  canvas.width = 100000000;
-  drawAndReadBack(ctx);
-  assert_true(ctx.isContextLost(),
-              'Context lost is detected when the canvas is used.');
-
-  // Restore the canvas size before the oneventloss event fires.
-  canvas.width = 200;
-
-  // The contextlost and contextrestored events should fire in the right order.
-  await contextLost;
-  await contextRestored;
-}, 'Invalid size can be corrected before the contextlost event.',
-   {testTypes: ALL_CANVAS_TEST_TYPES});
-
-/**
- * Validates setting an invalid size right after restoring from an invalid one.
- */
- canvasPromiseTest(async (canvas) => {
-  const ctx = canvas.getContext('2d', {
-    // Stay on GPU acceleration despite read-backs.
-    willReadFrequently: false,
-  });
-
-  // Make sure the canvas is fully initialized.
-  drawAndReadBack(ctx);
-
-  // Set invalid canvas size. Using the canvas attempts a resource provider
-  // creation which fails, setting context_lost_mode_ = kInvalidCanvasSize. An
-  // oncontextlost event is queued.
-  canvas.width = 100000000;
-  drawAndReadBack(ctx);
-
-  // When correcting the canvas size, we find that an oncontextlost
-  // is queued, so we set need_dispatch_context_restored_ = true
-  canvas.width = 200;
-
-  // Set an invalid size, but since context_lost_mode_ ==
-  // kInvalidCanvasSize, the draw is no-op and no provider creation
-  // is attempted.
-  canvas.width = 100000000;
-  drawAndReadBack(ctx);
-
-  // The oncontextlost is fired, which runs oncontextrestored because
-  // need_dispatch_context_restored_ == true
-  await new Promise(resolve => { canvas.oncontextlost = resolve; });
-  await new Promise(resolve => { canvas.oncontextrestored = resolve; });
-
-  // Now that the context is restored, we can draw to the canvas.
-  // Doing so attempts to create a provider, which fails and sets
-  // context_lost_mode_ = kInvalidCanvasSize
-  drawAndReadBack(ctx);
-
-  // This fires another oncontextlost event.
-  await new Promise(resolve => { canvas.oncontextlost = resolve; });
-}, 'Canvas can be given an invalid size right after restoring from a ' +
-   'previous invalid size.',
-   {testTypes: ALL_CANVAS_TEST_TYPES});
-
-/**
  * Validates that after losing the context due to an invalid canvas size,
  * context restoration can be disabled by using preventDefault() in
  * oncontextlost callback.
@@ -267,4 +334,817 @@
    'fixed in oncontextlost task.',
    {testTypes: ALL_CANVAS_TEST_TYPES});
 
+/**
+ * Check that the canvas is unusable while the context is lost.
+ */
+canvasPromiseTest(async (canvas, {canvasType} = {}) => {
+  const ctx = canvas.getContext('2d', {
+    willReadFrequently: false,  // Stay on GPU acceleration despite read-backs.
+  });
+
+  assert_array_equals(drawAndReadBack(ctx), [255, 0, 0, 255],
+      'Canvas should start in an healthy, usable state.');
... (truncated)
Loading diff…

Original Bug Report

reported by [email protected]

Heap UAF in Blink OffscreenCanvas

VULNERABILITY DETAILS

A heap-use-after-free vulnerability exists in Chromium’s Blink engine, specifically within the OffscreenCanvas component (core and modules). The crash triggers at mojo::Handle::is_valid() via blink::CanvasResourceDispatcher::OnBeginFrame.

=================================================================
==2029962==ERROR: AddressSanitizer: heap-use-after-free on address 0x7c80e0d45ef0 at pc 0x7f3104ff0f42 bp 0x7ffec54ac8a0 sp 0x7ffec54ac898
READ of size 8 at 0x7c80e0d45ef0 thread T0 (chrome)
    #0 0x7f3104ff0f41 in std::__Cr::unique_ptr<viz::mojom::blink::CompositorFrameSinkProxy, std::__Cr::default_delete<viz::mojom::blink::CompositorFrameSinkProxy>>::operator bool() const gen/third_party/libc++/src/include/__memory/unique_ptr.h:275:12
    #1 0x7f3104ff0f41 in mojo::internal::InterfacePtrState<viz::mojom::blink::CompositorFrameSink>::ConfigureProxyIfNecessary() mojo/public/cpp/bindings/lib/interface_ptr_state.h:261:9
    #2 0x7f310501bc44 in mojo::internal::InterfacePtrState<viz::mojom::blink::CompositorFrameSink>::instance() mojo/public/cpp/bindings/lib/interface_ptr_state.h:145:5
    #3 0x7f310501bc44 in mojo::Remote<viz::mojom::blink::CompositorFrameSink>::get() const mojo/public/cpp/bindings/remote.h:100:28
    #4 0x7f310501bc44 in mojo::Remote<viz::mojom::blink::CompositorFrameSink>::operator->() const mojo/public/cpp/bindings/remote.h:104:59
    #5 0x7f310501bc44 in blink::CanvasResourceDispatcher::OnBeginFrame(viz::BeginFrameArgs const&, blink::HashMap<unsigned int, viz::FrameTimingDetails, blink::HashTraits<unsigned int>, blink::HashTraits<viz::FrameTimingDetails>, blink::PartitionAllocator> const&, blink::Vector<viz::ReturnedResource, 0u, blink::PartitionAllocator>) third_party/blink/renderer/platform/graphics/canvas_resource_dispatcher.cc:416:5
    #6 0x7f310730b8d1 in viz::mojom::blink::CompositorFrameSinkClientStubDispatch::Accept(viz::mojom::blink::CompositorFrameSinkClient*, mojo::Message*) gen/services/viz/public/mojom/compositing/compositor_frame_sink.mojom-blink.cc:1677:13
    #7 0x7f315fd743e2 in mojo::InterfaceEndpointClient::HandleValidatedMessage(mojo::Message*) mojo/public/cpp/bindings/lib/interface_endpoint_client.cc:1085:54
    #8 0x7f315fd8b78b in mojo::MessageDispatcher::Accept(mojo::Message*) mojo/public/cpp/bindings/lib/message_dispatcher.cc:44:19
    #9 0x7f315fd79c94 in mojo::InterfaceEndpointClient::HandleIncomingMessage(mojo::Message*) mojo/public/cpp/bindings/lib/interface_endpoint_client.cc:747:20
    #10 0x7f315fd9ad6e in mojo::internal::MultiplexRouter::ProcessIncomingMessage(mojo::internal::MultiplexRouter::MessageWrapper*, mojo::internal::MultiplexRouter::ClientCallBehavior, base::SequencedTaskRunner*) mojo/public/cpp/bindings/lib/multiplex_router.cc:1204:42
    #11 0x7f315fd9959d in mojo::internal::MultiplexRouter::Accept(mojo::Message*) mojo/public/cpp/bindings/lib/multiplex_router.cc:790:7
    #12 0x7f315fd8b78b in mojo::MessageDispatcher::Accept(mojo::Message*) mojo/public/cpp/bindings/lib/message_dispatcher.cc:44:19
    #13 0x7f315fd6002f in mojo::Connector::DispatchMessage(mojo::ScopedHandleBase<mojo::MessageHandle>) mojo/public/cpp/bindings/lib/connector.cc:568:49
    #14 0x7f315fd6187e in mojo::Connector::ReadAllAvailableMessages() mojo/public/cpp/bindings/lib/connector.cc:629:14
    #15 0x7f315fd627a4 in void base::internal::DecayedFunctorTraits<void (mojo::Connector::*)(), base::WeakPtr<mojo::Connector>&&>::Invoke<void (mojo::Connector::*)(), base::WeakPtr<mojo::Connector> const&>(void (mojo::Connector::*)(), base::WeakPtr<mojo::Connector> const&) base/functional/bind_internal.h:740:12
    #16 0x7f315fd627a4 in void base::internal::InvokeHelper<true, base::internal::FunctorTraits<void (mojo::Connector::*&&)(), base::WeakPtr<mojo::Connector>&&>, void, 0ul>::MakeItSo<void (mojo::Connector::*)(), std::__Cr::tuple<base::WeakPtr<mojo::Connector>>>(void (mojo::Connector::*&&)(), std::__Cr::tuple<base::WeakPtr<mojo::Connector>>&&) base/functional/bind_internal.h:956:5
    #17 0x7f315fd627a4 in void base::internal::Invoker<base::internal::FunctorTraits<void (mojo::Connector::*&&)(), base::WeakPtr<mojo::Connector>&&>, base::internal::BindState<true, true, false, void (mojo::Connector::*)(), base::WeakPtr<mojo::Connector>>, void ()>::RunImpl<void (mojo::Connector::*)(), std::__Cr::tuple<base::WeakPtr<mojo::Connector>>, 0ul>(void (mojo::Connector::*&&)(), std::__Cr::tuple<base::WeakPtr<mojo::Connector>>&&, std::__Cr::integer_sequence<unsigned long, 0ul>) base/functional/bind_internal.h:1069:14
    #18 0x7f315fd627a4 in base::internal::Invoker<base::internal::FunctorTraits<void (mojo::Connector::*&&)(), base::WeakPtr<mojo::Connector>&&>, base::internal::BindState<true, true, false, void (mojo::Connector::*)(), base::WeakPtr<mojo::Connector>>, void ()>::RunOnce(base::internal::BindStateBase*) base/functional/bind_internal.h:982:12
    #19 0x7f315f5cbed2 in base::OnceCallback<void ()>::Run() && base/functional/callback.h:155:12
    #20 0x7f315f5cbed2 in base::TaskAnnotator::RunTaskImpl(base::PendingTask&) base/task/common/task_annotator.cc:229:34
    #21 0x7f315f64d3ce in void base::TaskAnnotator::RunTask<base::sequence_manager::internal::ThreadControllerWithMessagePumpImpl::DoWorkImpl(base::LazyNow*)::$_3>(perfetto::StaticString, base::PendingTask&, base::sequence_manager::internal::ThreadControllerWithMessagePumpImpl::DoWorkImpl(base::LazyNow*)::$_3&&) base/task/common/task_annotator.h:112:5
    #22 0x7f315f64d3ce in base::sequence_manager::internal::ThreadControllerWithMessagePumpImpl::DoWorkImpl(base::LazyNow*) base/task/sequence_manager/thread_controller_with_message_pump_impl.cc:475:23
    #23 0x7f315f64c3a6 in base::sequence_manager::internal::ThreadControllerWithMessagePumpImpl::DoWork() base/task/sequence_manager/thread_controller_with_message_pump_impl.cc:346:40
    #24 0x7f315f46dea1 in base::MessagePumpDefault::Run(base::MessagePump::Delegate*) base/message_loop/message_pump_default.cc:42:55
    #25 0x7f315f64ea48 in base::sequence_manager::internal::ThreadControllerWithMessagePumpImpl::Run(bool, base::TimeDelta) base/task/sequence_manager/thread_controller_with_message_pump_impl.cc:650:12
    #26 0x7f315f536512 in base::RunLoop::Run(base::Location const&) base/run_loop.cc:135:14
    #27 0x7f3155215bfe in content::RendererMain(content::MainFunctionParams) content/renderer/renderer_main.cc:364:16
    #28 0x7f31556480b7 in content::RunZygote(content::ContentMainDelegate*) content/app/content_main_runner_impl.cc:664:14
    #29 0x7f315564921f in content::RunOtherNamedProcessTypeMain(std::__Cr::basic_string<char, std::__Cr::char_traits<char>, std::__Cr::allocator<char>> const&, content::MainFunctionParams, content::ContentMainDelegate*) content/app/content_main_runner_impl.cc:771:12
    #30 0x7f315564b80a in content::ContentMainRunnerImpl::Run() content/app/content_main_runner_impl.cc:1152:10
    #31 0x7f3155645f53 in content::RunContentProcess(content::ContentMainParams, content::ContentMainRunner*) content/app/content_main.cc:358:36
    #32 0x7f31556462ea in content::ContentMain(content::ContentMainParams) content/app/content_main.cc:371:10
    #33 0x556bdcd8f345 in ChromeMain chrome/app/chrome_main.cc:191:12
    #34 0x7f30ef8e7d8f in __libc_start_call_main csu/../sysdeps/nptl/libc_start_call_main.h:58:16

0x7c80e0d45ef0 is located 112 bytes inside of 472-byte region [0x7c80e0d45e80,0x7c80e0d46058)
freed by thread T0 (chrome) here:
    #0 0x556bdcd8e502 in operator delete(void*, unsigned long) (/mnt/lvm_data/chromium/src/out/asan_nodcheck/chrome+0x686f502) (BuildId: f667536c8ff65a4b)
    #1 0x7f30f6fc3e03 in blink::OffscreenCanvasRenderingContext2D::LoseContext(blink::CanvasRenderingContext::LostContextMode) third_party/blink/renderer/modules/canvas/offscreencanvas2d/offscreen_canvas_rendering_context_2d.cc:445:11
    #2 0x7f30f6fc2e9b in blink::OffscreenCanvasRenderingContext2D::GetOrCreateResourceProvider() third_party/blink/renderer/modules/canvas/offscreencanvas2d/offscreen_canvas_rendering_context_2d.cc:197:5
    #3 0x7f30f6fc2a03 in blink::OffscreenCanvasRenderingContext2D::FinalizeFrame(blink::FlushReason) third_party/blink/renderer/modules/canvas/offscreencanvas2d/offscreen_canvas_rendering_context_2d.cc:136:8
    #4 0x7f30f6fc4d46 in blink::OffscreenCanvasRenderingContext2D::PushFrame() third_party/blink/renderer/modules/canvas/offscreencanvas2d/offscreen_canvas_rendering_context_2d.cc:322:3
    #5 0x7f310501bc36 in blink::CanvasResourceDispatcher::OnBeginFrame(viz::BeginFrameArgs const&, blink::HashMap<unsigned int, viz::FrameTimingDetails, blink::HashTraits<unsigned int>, blink::HashTraits<viz::FrameTimingDetails>, blink::PartitionAllocator> const&, blink::Vector<viz::ReturnedResource, 0u, blink::PartitionAllocator>) third_party/blink/renderer/platform/graphics/canvas_resource_dispatcher.cc:413:54
    #6 0x7f310730b8d1 in viz::mojom::blink::CompositorFrameSinkClientStubDispatch::Accept(viz::mojom::blink::CompositorFrameSinkClient*, mojo::Message*) gen/services/viz/public/mojom/compositing/compositor_frame_sink.mojom-blink.cc:1677:13
    #7 0x7f315fd743e2 in mojo::InterfaceEndpointClient::HandleValidatedMessage(mojo::Message*) mojo/public/cpp/bindings/lib/interface_endpoint_client.cc:1085:54
    #8 0x7f315fd8b78b in mojo::MessageDispatcher::Accept(mojo::Message*) mojo/public/cpp/bindings/lib/message_dispatcher.cc:44:19
    #9 0x7f315fd79c94 in mojo::InterfaceEndpointClient::HandleIncomingMessage(mojo::Message*) mojo/public/cpp/bindings/lib/interface_endpoint_client.cc:747:20
    #10 0x7f315fd9ad6e in mojo::internal::MultiplexRouter::ProcessIncomingMessage(mojo::internal::MultiplexRouter::MessageWrapper*, mojo::internal::MultiplexRouter::ClientCallBehavior, base::SequencedTaskRunner*) mojo/public/cpp/bindings/lib/multiplex_router.cc:1204:42
    #11 0x7f315fd9959d in mojo::internal::MultiplexRouter::Accept(mojo::Message*) mojo/public/cpp/bindings/lib/multiplex_router.cc:790:7
    #12 0x7f315fd8b78b in mojo::MessageDispatcher::Accept(mojo::Message*) mojo/public/cpp/bindings/lib/message_dispatcher.cc:44:19
    #13 0x7f315fd6002f in mojo::Connector::DispatchMessage(mojo::ScopedHandleBase<mojo::MessageHandle>) mojo/public/cpp/bindings/lib/connector.cc:568:49
    #14 0x7f315fd6187e in mojo::Connector::ReadAllAvailableMessages() mojo/public/cpp/bindings/lib/connector.cc:629:14
    #15 0x7f315fd627a4 in void base::internal::DecayedFunctorTraits<void (mojo::Connector::*)(), base::WeakPtr<mojo::Connector>&&>::Invoke<void (mojo::Connector::*)(), base::WeakPtr<mojo::Connector> const&>(void (mojo::Connector::*)(), base::WeakPtr<mojo::Connector> const&) base/functional/bind_internal.h:740:12
    #16 0x7f315fd627a4 in void base::internal::InvokeHelper<true, base::internal::FunctorTraits<void (mojo::Connector::*&&)(), base::WeakPtr<mojo::Connector>&&>, void, 0ul>::MakeItSo<void (mojo::Connector::*)(), std::__Cr::tuple<base::WeakPtr<mojo::Connector>>>(void (mojo::Connector::*&&)(), std::__Cr::tuple<base::WeakPtr<mojo::Connector>>&&) base/functional/bind_internal.h:956:5
    #17 0x7f315fd627a4 in void base::internal::Invoker<base::internal::FunctorTraits<void (mojo::Connector::*&&)(), base::WeakPtr<mojo::Connector>&&>, base::internal::BindState<true, true, false, void (mojo::Connector::*)(), base::WeakPtr<mojo::Connector>>, void ()>::RunImpl<void (mojo::Connector::*)(), std::__Cr::tuple<base::WeakPtr<mojo::Connector>>, 0ul>(void (mojo::Connector::*&&)(), std::__Cr::tuple<base::WeakPtr<mojo::Connector>>&&, std::__Cr::integer_sequence<unsigned long, 0ul>) base/functional/bind_internal.h:1069:14
    #18 0x7f315fd627a4 in base::internal::Invoker<base::internal::FunctorTraits<void (mojo::Connector::*&&)(), base::WeakPtr<mojo::Connector>&&>, base::internal::BindState<true, true, false, void (mojo::Connector::*)(), base::WeakPtr<mojo::Connector>>, void ()>::RunOnce(base::internal::BindStateBase*) base/functional/bind_internal.h:982:12
    #19 0x7f315f5cbed2 in base::OnceCallback<void ()>::Run() && base/functional/callback.h:155:12
    #20 0x7f315f5cbed2 in base::TaskAnnotator::RunTaskImpl(base::PendingTask&) base/task/common/task_annotator.cc:229:34
    #21 0x7f315f64d3ce in void base::TaskAnnotator::RunTask<base::sequence_manager::internal::ThreadControllerWithMessagePumpImpl::DoWorkImpl(base::LazyNow*)::$_3>(perfetto::StaticString, base::PendingTask&, base::sequence_manager::internal::ThreadControllerWithMessagePumpImpl::DoWorkImpl(base::LazyNow*)::$_3&&) base/task/common/task_annotator.h:112:5
    #22 0x7f315f64d3ce in base::sequence_manager::internal::ThreadControllerWithMessagePumpImpl::DoWorkImpl(base::LazyNow*) base/task/sequence_manager/thread_controller_with_message_pump_impl.cc:475:23
    #23 0x7f315f64c3a6 in base::sequence_manager::internal::ThreadControllerWithMessagePumpImpl::DoWork() base/task/sequence_manager/thread_controller_with_message_pump_impl.cc:346:40
    #24 0x7f315f46dea1 in base::MessagePumpDefault::Run(base::MessagePump::Delegate*) base/message_loop/message_pump_default.cc:42:55
    #25 0x7f315f64ea48 in base::sequence_manager::internal::ThreadControllerWithMessagePumpImpl::Run(bool, base::TimeDelta) base/task/sequence_manager/thread_controller_with_message_pump_impl.cc:650:12
    #26 0x7f315f536512 in base::RunLoop::Run(base::Location const&) base/run_loop.cc:135:14
    #27 0x7f3155215bfe in content::RendererMain(content::MainFunctionParams) content/renderer/renderer_main.cc:364:16
    #28 0x7f31556480b7 in content::RunZygote(content::ContentMainDelegate*) content/app/content_main_runner_impl.cc:664:14
    #29 0x7f315564921f in content::RunOtherNamedProcessTypeMain(std::__Cr::basic_string<char, std::__Cr::char_traits<char>, std::__Cr::allocator<char>> const&, content::MainFunctionParams, content::ContentMainDelegate*) content/app/content_main_runner_impl.cc:771:12
    #30 0x7f315564b80a in content::ContentMainRunnerImpl::Run() content/app/content_main_runner_impl.cc:1152:10
    #31 0x7f3155645f53 in content::RunContentProcess(content::ContentMainParams, content::ContentMainRunner*) content/app/content_main.cc:358:36
    #32 0x7f31556462ea in content::ContentMain(content::ContentMainParams) content/app/content_main.cc:371:10
    #33 0x556bdcd8f345 in ChromeMain chrome/app/chrome_main.cc:191:12
    #34 0x7f30ef8e7d8f in __libc_start_call_main csu/../sysdeps/nptl/libc_start_call_main.h:58:16

previously allocated by thread T0 (chrome) here:
    #0 0x556bdcd8d8fd in operator new(unsigned long) (/mnt/lvm_data/chromium/src/out/asan_nodcheck/chrome+0x686e8fd) (BuildId: f667536c8ff65a4b)
    #1 0x7f310f6f0ce2 in std::__Cr::unique_ptr<blink::CanvasResourceDispatcher, std::__Cr::default_delete<blink::CanvasResourceDispatcher>> std::__Cr::make_unique<blink::CanvasResourceDispatcher, blink::OffscreenCanvas*, scoped_refptr<base::SingleThreadTaskRunner>, scoped_refptr<base::SingleThreadTaskRunner>, unsigned int&, unsigned int&, int&, gfx::Size, 0>(blink::OffscreenCanvas*&&, scoped_refptr<base::SingleThreadTaskRunner>&&, scoped_refptr<base::SingleThreadTaskRunner>&&, unsigned int&, unsigned int&, int&, gfx::Size&&) gen/third_party/libc++/src/include/__memory/unique_ptr.h:756:26
    #2 0x7f310f6f0ce2 in blink::OffscreenCanvas::GetOrCreateResourceDispatcher() third_party/blink/renderer/core/offscreencanvas/offscreen_canvas.cc:466:25
    #3 0x7f310f6f14a4 in blink::OffscreenCanvas::DidDraw(SkIRect const&) third_party/blink/renderer/core/offscreencanvas/offscreen_canvas.cc:484:7
    #4 0x7f310f6f14a4 in non-virtual thunk to blink::OffscreenCanvas::DidDraw(SkIRect const&) third_party/blink/renderer/core/offscreencanvas/offscreen_canvas.cc
    #5 0x7f310dfb28e3 in blink::CanvasRenderingContext::DidDraw(SkIRect const&, blink::CanvasPerformanceMonitor::DrawType) third_party/blink/renderer/core/html/canvas/canvas_rendering_context.cc:314:9
    #6 0x7f310f6eccf8 in blink::CanvasRenderingContext::DidDraw(blink::CanvasPerformanceMonitor::DrawType) third_party/blink/renderer/core/html/canvas/canvas_rendering_context.h:195:12
    #7 0x7f310f6eccf8 in blink::OffscreenCanvas::SetSize(gfx::Size) third_party/blink/renderer/core/offscreencanvas/offscreen_canvas.cc:177:15
    #8 0x7f30f62aea08 in blink::(anonymous namespace)::v8_offscreen_canvas::WidthAttributeSetCallback(v8::FunctionCallbackInfo<v8::Value> const&) gen/third_party/blink/renderer/bindings/modules/v8/v8_offscreen_canvas.cc:96:17
    #9 0x7b30afdd06a3  (<unknown module>)
    #10 0x7b30afe2b528  (<unknown module>)
    #11 0x7b3090000843  (<unknown module>)
    #12 0x7b30afe1356d  (<unknown module>)
    #13 0x7b30aff01a29  (<unknown module>)
    #14 0x7b30afe01392  (<unknown module>)
    #15 0x7b30afdcb52a  (<unknown module>)
    #16 0x7f30fdf2748d in v8::internal::GeneratedCode<unsigned long, unsigned long, v8::internal::MicrotaskQueue*>::Call(unsigned long, v8::internal::MicrotaskQueue*) v8/src/execution/simulator.h:216:12
    #17 0x7f30fdf2748d in v8::internal::(anonymous namespace)::Invoke(v8::internal::Isolate*, v8::internal::(anonymous namespace)::InvokeParams const&) v8/src/execution/execution.cc:492:41
    #18 0x7f30fdf29689 in v8::internal::(anonymous namespace)::InvokeWithTryCatch(v8::internal::Isolate*, v8::internal::(anonymous namespace)::InvokeParams const&) v8/src/execution/execution.cc:534:18
    #19 0x7f30fdf29a8f in v8::internal::Execution::TryRunMicrotasks(v8::internal::Isolate*, v8::internal::MicrotaskQueue*) v8/src/execution/execution.cc:638:10
    #20 0x7f30fdfd21fc in v8::internal::MicrotaskQueue::RunMicrotasks(v8::internal::Isolate*) v8/src/execution/microtask-queue.cc:185:22
    #21 0x7f30fdfd1c54 in v8::internal::MicrotaskQueue::PerformCheckpointInternal(v8::Isolate*) v8/src/execution/microtask-queue.cc:129:3
    #22 0x7f310c693c83 in blink::V8ScriptRunner::CallFunction(v8::Local<v8::Function>, blink::ExecutionContext*, v8::Local<v8::Value>, int, v8::Local<v8::Value>*, v8::Isolate*) third_party/blink/renderer/bindings/core/v8/v8_script_runner.cc:863:1
    #23 0x7f310c4e1b1b in blink::bindings::CallbackInvokeHelper<blink::CallbackFunctionBase, (blink::bindings::CallbackInvokeHelperMode)0, (blink::bindings::CallbackReturnTypeIsPromise)0>::CallInternal(int, v8::Local<v8::Value>*) third_party/blink/renderer/bindings/core/v8/callback_invoke_helper.cc:126:12
    #24 0x7f310c4e1b1b in blink::bindings::CallbackInvokeHelper<blink::CallbackFunctionBase, (blink::bindings::CallbackInvokeHelperMode)0, (blink::bindings::CallbackReturnTypeIsPromise)0>::Call(int, v8::Local<v8::Value>*) third_party/blink/renderer/bindings/core/v8/callback_invoke_helper.cc:150:10
    #25 0x7f3110c9262b in blink::V8Function::Invoke(blink::bindings::V8ValueOrScriptWrappableAdapter, blink::BasicHeapVector<(blink::internal::HeapCollectionType)1, blink::ScriptValue, 0u> const&) gen/third_party/blink/renderer/bindings/core/v8/v8_function.cc:73:13
    #26 0x7f3110c93a03 in blink::V8Function::InvokeAndReportException(blink::bindings::V8ValueOrScriptWrappableAdapter, blink::BasicHeapVector<(blink::internal::HeapCollectionType)1, blink::ScriptValue, 0u> const&) gen/third_party/blink/renderer/bindings/core/v8/v8_function.cc:133:15
    #27 0x7f310fbc0669 in blink::ScheduledAction::Execute(blink::ExecutionContext*) third_party/blink/renderer/core/scheduler/scheduled_action.cc:145:18
    #28 0x7f310fbb8e57 in blink::DOMTimer::Fired() third_party/blink/renderer/core/scheduler/dom_timer.cc:446:11
    #29 0x7f3105590855 in blink::TimerBase::RunInternal() third_party/blink/renderer/platform/timer.cc:166:3
    #30 0x7f310ead069c in void base::internal::DecayedFunctorTraits<void (blink::TimerBase::*)(), blink::TimerBase*>::Invoke<void (blink::TimerBase::*)(), blink::TimerBase*>(void (blink::TimerBase::*)(), blink::TimerBase*&&) base/functional/bind_internal.h:740:12
    #31 0x7f310ead069c in void base::internal::InvokeHelper<false, base::internal::FunctorTraits<void (blink::TimerBase::*&&)(), blink::TimerBase*>, void, 0ul>::MakeItSo<void (blink::TimerBase::*)(), std::__Cr::tuple<blink::UnretainedWrapper<blink::TimerBase>>>(void (blink::TimerBase::*&&)(), std::__Cr::tuple<blink::UnretainedWrapper<blink::TimerBase>>&&) base/functional/bind_internal.h:932:12
    #32 0x7f310ead069c in void base::internal::Invoker<base::internal::FunctorTraits<void (blink::TimerBase::*&&)(), blink::TimerBase*>, base::internal::BindState<true, true, false, void (blink::TimerBase::*)(), blink::UnretainedWrapper<blink::TimerBase>>, void ()>::RunImpl<void (blink::TimerBase::*)(), std::__Cr::tuple<blink::UnretainedWrapper<blink::TimerBase>>, 0ul>(void (blink::TimerBase::*&&)(), std::__Cr::tuple<blink::UnretainedWrapper<blink::TimerBase>>&&, std::__Cr::integer_sequence<unsigned long, 0ul>) base/functional/bind_internal.h:1069:14
    #33 0x7f310ead069c in base::internal::Invoker<base::internal::FunctorTraits<void (blink::TimerBase::*&&)(), blink::TimerBase*>, base::internal::BindState<true, true, false, void (blink::TimerBase::*)(), blink::UnretainedWrapper<blink::TimerBase>>, void ()>::RunOnce(base::internal::BindStateBase*) base/functional/bind_internal.h:982:12
    #34 0x7f315f5cbed2 in base::OnceCallback<void ()>::Run() && base/functional/callback.h:155:12
    #35 0x7f315f5cbed2 in base::TaskAnnotator::RunTaskImpl(base::PendingTask&) base/task/common/task_annotator.cc:229:34
    #36 0x7f315f64d3ce in void base::TaskAnnotator::RunTask<base::sequence_manager::internal::ThreadControllerWithMessagePumpImpl::DoWorkImpl(base::LazyNow*)::$_3>(perfetto::StaticString, base::PendingTask&, base::sequence_manager::internal::ThreadControllerWithMessagePumpImpl::DoWorkImpl(base::LazyNow*)::$_3&&) base/task/common/task_annotator.h:112:5
    #37 0x7f315f64d3ce in base::sequence_manager::internal::ThreadControllerWithMessagePumpImpl::DoWorkImpl(base::LazyNow*) base/task/sequence_manager/thread_controller_with_message_pump_impl.cc:475:23
    #38 0x7f315f64c3a6 in base::sequence_manager::internal::ThreadControllerWithMessagePumpImpl::DoWork() base/task/sequence_manager/thread_controller_with_message_pump_impl.cc:346:40
    #39 0x7f315f46dea1 in base::MessagePumpDefault::Run(base::MessagePump::Delegate*) base/message_loop/message_pump_default.cc:42:55

SUMMARY: AddressSanitizer: heap-use-after-free gen/third_party/libc++/src/include/__memory/unique_ptr.h:275:12 in std::__Cr::unique_ptr<viz::mojom::blink::CompositorFrameSinkProxy, std::__Cr::default_delete<viz::mojom::blink::CompositorFrameSinkProxy>>::operator bool() const
Shadow bytes around the buggy address:
  0x7c80e0d45c00: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
  0x7c80e0d45c80: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
  0x7c80e0d45d00: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
  0x7c80e0d45d80: 00 00 00 00 00 00 00 00 00 00 00 00 fa fa fa fa
  0x7c80e0d45e00: fa fa fa fa fa fa fa fa fa fa fa fa fa fa f7 fa
=>0x7c80e0d45e80: fd fd fd fd fd fd fd fd fd fd fd fd fd fd[fd]fd
  0x7c80e0d45f00: fd fd fd fd fd fd fd fd fd fd fd fd fd fd fd fd
  0x7c80e0d45f80: fd fd fd fd fd fd fd fd fd fd fd fd fd fd fd fd
  0x7c80e0d46000: fd fd fd fd fd fd fd fd fd fd fd fa fa fa fa fa
  0x7c80e0d46080: fa fa fa fa fa fa fa fa fa fa fa fa fa fa f7 fa
  0x7c80e0d46100: fd fd fd fd fd fd fd fd fd fd fd fd fd fd fd fd
Shadow byte legend (one shadow byte represents 8 application bytes):
  Addressable:           00
  Partially addressable: 01 02 03 04 05 06 07 
  Heap left redzone:       fa
  Freed heap region:       fd
  Stack left redzone:      f1
  Stack mid redzone:       f2
  Stack right redzone:     f3
  Stack after return:      f5
  Stack use after scope:   f8
  Global redzone:          f9
  Global init order:       f6
  Poisoned by user:        f7
  Container overflow:      fc
  Array cookie:            ac
  Intra object redzone:    bb
  ASan internal:           fe
  Left alloca redzone:     ca
  Right alloca redzone:    cb

==2029962==ADDITIONAL INFO

==2029962==Note: Please include this section with the ASan report.
Task trace:
    #0 0x7f315fd62336 in mojo::Connector::PostDispatchNextMessageFromPipe() mojo/public/cpp/bindings/lib/connector.cc:589:7
    #1 0x7f315fd62336 in mojo::Connector::ScheduleDispatchOfPendingMessagesOrWaitForMore(unsigned long) mojo/public/cpp/bindings/lib/connector.cc:612:5
    #2 0x7f315e08b9ea in mojo::SimpleWatcher::Context::Notify(unsigned int, MojoHandleSignalsState, unsigned int) mojo/public/cpp/system/simple_watcher.cc:103:13


Command line: `/proc/self/exe --type=renderer --crashpad-handler-pid=2029916 --enable-crash-reporter=, --noerrdialogs --user-data-dir=/tmp/org.chromium.Chromium.scoped_dir.8w1ili --change-stack-guard-on-fork=enable --no-sandbox --ozone-platform=headless --lang=en-US --num-raster-threads=4 --enable-main-frame-before-activation --renderer-client-id=5 --time-ticks-at-unix-epoch=-1772947841576980 --launch-time-ticks=1025574931179 --shared-files=v8_context_snapshot_data:100 --field-trial-handle=3,i,7742491733228300046,8070823627392218987,262144 --disable-features=PaintHolding --variations-seed-version --pseudonymization-salt-handle=7,i,2791269340090131867,8337486435613622678,4 --trace-process-track-uuid=3190708990997080739`


MiraclePtr Status: NOT PROTECTED
No raw_ptr<T> access to this region was detected prior to this crash.
This crash is still exploitable with MiraclePtr.
Refer to https://chromium.googlesource.com/chromium/src/+/main/base/memory/raw_ptr.md for details.

==2029962==END OF ADDITIONAL INFO

VERSION

Chrome Version: Chromium 147.0.7703.0 and Chromium 148.0.7729.0 dev ASAN builds
Operating System: Linux x86_64 on the two provided repro hosts (Ubuntu 22.04.3 LTS class and Ubuntu 25.04 class)

REPRODUCTION CASE

Primary HTML artifact: poc_standalone.html

Required flags (for PoC, not related to the vulnerability): --no-first-run --no-default-browser-check --disable-background-networking

chrome file:///path/to/poc_standalone.html --no-first-run --no-default-browser-check --disable-background-networking

Note: due to requiring race condition to reach this vulnerability, the PoC is a bit flaky and can take longer time (~1min) to crash on a slower machine. Also, please retry if it doesn’t crash.

FOR CRASHES, PLEASE INCLUDE THE FOLLOWING ADDITIONAL INFORMATION

Type of crash: Renderer process crash

CREDIT INFORMATION

Reporter credit: heapracer

View on issue tracker