High chrome UAF 🔧 Commit mapped

Overview

High
Severity
—
CVSS
No
Exploited ITW
Fixed
Fix Status
ImpactUse after free in Canvas
DescriptionUse after free in Canvas
ComponentCanvas
Sub-componentCanvas2d
Bug ClassUAF
Tracker570167920
Fix commit6aed6936e3fb (chromium/src) +50/-13
CISA KEVNot listed
CreditedXinyang Ge
Disclosed2026-10-13

Changed Functions

FunctionChangeNotes
if
third_party/blink/renderer/modules/canvas/canvas2d/canvas_2d_recorder_context.cc
modified

Files Changed

  • third_party/blink/renderer/modules/canvas/canvas2d/canvas_2d_recorder_context.cc
  • third_party/blink/renderer/modules/canvas/canvas2d/canvas_2d_recorder_context.h
  • third_party/blink/web_tests/fast/canvas/canvas-drawImage-orientation-style-recalc-crash.html
From 6aed6936e3fbd9f24f816ba18aed9936c6aad219 Mon Sep 17 00:00:00 2001
From: Florin Malita <fmalita@chromium.org>
Date: Wed, 07 Oct 2026 09:23:18 -0700
Subject: [PATCH] [canvas2d] Pass precomputed image orientation into DrawImageInternal

Canvas2DRecorderContext::drawImage() already queries
RespectImageOrientationInternal() before invoking Draw(), but
DrawImageInternal() queries it a second time while holding a raw
cc::PaintCanvas* pointer.

Because RespectImageOrientationInternal() calls EnsureComputedStyle(),
a synchronous style recalculation inside DrawImageInternal() can run
scripts and resize the canvas, invalidating the active PaintCanvas
pointer mid-draw. Pass the already-resolved RespectImageOrientationEnum
down to DrawImageInternal() instead of recomputing it.

Bug: 570167920
Change-Id: I1e3bcc63d969311d394a0ff4e72a0484c182a084
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/8527128
Reviewed-by: Justin Novosad <junov@chromium.org>
Commit-Queue: Florin Malita <fmalita@chromium.org>
Reviewed-by: Andres Ricardo Perez Rojas <andresrperez@chromium.org>
Cr-Commit-Position: refs/heads/main@{#1713524}
---
diff --git a/third_party/blink/renderer/modules/canvas/canvas2d/canvas_2d_recorder_context.cc b/third_party/blink/renderer/modules/canvas/canvas2d/canvas_2d_recorder_context.cc
index 4342ad0c..6296245 100644
--- a/third_party/blink/renderer/modules/canvas/canvas2d/canvas_2d_recorder_context.cc
+++ b/third_party/blink/renderer/modules/canvas/canvas2d/canvas_2d_recorder_context.cc
@@ -2120,7 +2120,8 @@
     const gfx::RectF& src_rect,
     const gfx::RectF& dst_rect,
     const SkSamplingOptions& sampling,
-    const cc::PaintFlags* flags) {
+    const cc::PaintFlags* flags,
+    RespectImageOrientationEnum respect_orientation) {
   cc::RecordPaintCanvas::DisableFlushCheckScope disable_flush_check_scope(
       static_cast<cc::RecordPaintCanvas*>(c));
   int initial_save_count = c->getSaveCount();
@@ -2164,11 +2165,6 @@
   // the video into the canvas. The fast path is not always faster though (e.g.,
   // when scaling), so sometimes the `image` path may still be used by video.
   if (image) {
-    // We always use the image-orientation property on the canvas element
-    // because the alternative would result in complex rules depending on
-    // the source of the image.
-    RespectImageOrientationEnum respect_orientation =
-        RespectImageOrientationInternal(image_source);
     gfx::RectF corrected_src_rect = src_rect;
     if (respect_orientation == kRespectImageOrientation &&
         !image->HasDefaultOrientation()) {
@@ -2196,8 +2192,7 @@
     VideoFrame* frame = static_cast<VideoFrame*>(image_source);
     auto media_frame = frame->frame();
     bool ignore_transformation =
-        RespectImageOrientationInternal(image_source) ==
-        kDoNotRespectImageOrientation;
+        respect_orientation == kDoNotRespectImageOrientation;
     c->save();
     c->clipRect(gfx::RectFToSkRect(dst_rect));
     c->translate(dst_rect.x(), dst_rect.y());
@@ -2307,8 +2302,13 @@
     return;
   }
 
-  gfx::SizeF image_size = image_source->ElementSize(
-      default_object_size, RespectImageOrientationInternal(image_source));
+  // We always use the image-orientation property on the canvas element because
+  // the alternative would result in complex rules depending on the source of
+  // the image.
+  const RespectImageOrientationEnum respect_orientation =
+      RespectImageOrientationInternal(image_source);
+  gfx::SizeF image_size =
+      image_source->ElementSize(default_object_size, respect_orientation);
 
   ClipRectsToImageRect(gfx::RectF(image_size), &src_rect, &dst_rect);
 
@@ -2326,14 +2326,14 @@
 
   Draw<OverdrawOp::kDrawImage>(
       /*draw_func=*/
-      [this, image_source, image, src_rect, dst_rect](
+      [this, image_source, image, src_rect, dst_rect, respect_orientation](
           MemoryManagedPaintCanvas* c, const cc::PaintFlags* flags) {
         SkSamplingOptions sampling =
             cc::PaintFlags::FilterQualityToSkSamplingOptions(
                 flags ? flags->getFilterQuality()
                       : cc::PaintFlags::FilterQuality::kNone);
         DrawImageInternal(c, image_source, image.get(), src_rect, dst_rect,
-                          sampling, flags);
+                          sampling, flags, respect_orientation);
       },
       /*draw_covers_clip_bounds=*/
       [this, dst_rect](const SkIRect& clip_bounds) {
diff --git a/third_party/blink/renderer/modules/canvas/canvas2d/canvas_2d_recorder_context.h b/third_party/blink/renderer/modules/canvas/canvas2d/canvas_2d_recorder_context.h
index 91dad891..739a7a7b 100644
--- a/third_party/blink/renderer/modules/canvas/canvas2d/canvas_2d_recorder_context.h
+++ b/third_party/blink/renderer/modules/canvas/canvas2d/canvas_2d_recorder_context.h
@@ -602,7 +602,8 @@
                          const gfx::RectF& src_rect,
                          const gfx::RectF& dst_rect,
                          const SkSamplingOptions&,
-                         const cc::PaintFlags*);
+                         const cc::PaintFlags*,
+                         RespectImageOrientationEnum);
   void ClipInternal(const Path&,
                     const V8CanvasFillRule& winding_rule,
                     cc::UsePaintCache);
diff --git a/third_party/blink/web_tests/fast/canvas/canvas-drawImage-orientation-style-recalc-crash.html b/third_party/blink/web_tests/fast/canvas/canvas-drawImage-orientation-style-recalc-crash.html
new file mode 100644
index 0000000..e8ba5828
--- /dev/null
+++ b/third_party/blink/web_tests/fast/canvas/canvas-drawImage-orientation-style-recalc-crash.html
@@ -0,0 +1,36 @@
+<!DOCTYPE html>
+<script src="../../resources/testharness.js"></script>
+<script src="../../resources/testharnessreport.js"></script>
+<div id="outer">
+  <object id="obj" type="text/html" data="about:blank">
+    <svg><foreignObject><canvas></canvas></foreignObject></svg>
+  </object>
+</div>
+<script>
+async_test((t) => {
+  const outer = document.getElementById('outer');
+  const obj = document.getElementById('obj');
+  const canvas = outer.querySelector('canvas');
+  const home = canvas.parentNode;
+  const ctx = canvas.getContext('2d');
+  const other = document.implementation.createHTMLDocument();
+
+  EventTarget.prototype.addEventListener.call(obj, 'load', t.step_func_done(() => {
+    const frame = new VideoFrame(canvas, {timestamp: 0});
+    ctx.filter = 'url(#none)';
+    frames[0].onpagehide = () => {
+      other.adoptNode(canvas);
+      canvas.width = 17;
+      home.append(canvas);
+    };
+
+    outer.style.display = 'none';
+    outer.offsetWidth;
+    getComputedStyle(canvas).color;
+    Element.prototype.setAttribute.call(obj, 'type', 'image/png');
+    Element.prototype.setAttribute.call(obj, 'data', 'data:image/png,x');
+    ctx.drawImage(frame, 0, 0);
+    frame.close();
+  }));
+}, 'Canvas2D drawImage does not crash when style recalc during drawing resizes canvas');
+</script>
Loading diff…

Regression Test / PoC

shipped with the fix
diff --git a/third_party/blink/web_tests/fast/canvas/canvas-drawImage-orientation-style-recalc-crash.html b/third_party/blink/web_tests/fast/canvas/canvas-drawImage-orientation-style-recalc-crash.html
new file mode 100644
index 0000000..e8ba5828
--- /dev/null
+++ b/third_party/blink/web_tests/fast/canvas/canvas-drawImage-orientation-style-recalc-crash.html
@@ -0,0 +1,36 @@
+<!DOCTYPE html>
+<script src="../../resources/testharness.js"></script>
+<script src="../../resources/testharnessreport.js"></script>
+<div id="outer">
+  <object id="obj" type="text/html" data="about:blank">
+    <svg><foreignObject><canvas></canvas></foreignObject></svg>
+  </object>
+</div>
+<script>
+async_test((t) => {
+  const outer = document.getElementById('outer');
+  const obj = document.getElementById('obj');
+  const canvas = outer.querySelector('canvas');
+  const home = canvas.parentNode;
+  const ctx = canvas.getContext('2d');
+  const other = document.implementation.createHTMLDocument();
+
+  EventTarget.prototype.addEventListener.call(obj, 'load', t.step_func_done(() => {
+    const frame = new VideoFrame(canvas, {timestamp: 0});
+    ctx.filter = 'url(#none)';
+    frames[0].onpagehide = () => {
+      other.adoptNode(canvas);
+      canvas.width = 17;
+      home.append(canvas);
+    };
+
+    outer.style.display = 'none';
+    outer.offsetWidth;
+    getComputedStyle(canvas).color;
+    Element.prototype.setAttribute.call(obj, 'type', 'image/png');
+    Element.prototype.setAttribute.call(obj, 'data', 'data:image/png,x');
+    ctx.drawImage(frame, 0, 0);
+    frame.close();
+  }));
+}, 'Canvas2D drawImage does not crash when style recalc during drawing resizes canvas');
+</script>
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.