Chrome · Canvas › Canvas2d
CVE-2026-110154
UAF in Canvas (Canvas2d)
Overview
High
Severity
—
CVSS
No
Exploited ITW
Fixed
Fix Status
Changed Functions
| Function | Change | Notes |
|---|---|---|
ifthird_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.ccthird_party/blink/renderer/modules/canvas/canvas2d/canvas_2d_recorder_context.hthird_party/blink/web_tests/fast/canvas/canvas-drawImage-orientation-style-recalc-crash.html
Patch
From 6aed6936e3fbd9f24f816ba18aed9936c6aad219 Mon Sep 17 00:00:00 2001 From: Florin Malita <[email protected]> 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 <[email protected]> Commit-Queue: Florin Malita <[email protected]> Reviewed-by: Andres Ricardo Perez Rojas <[email protected]> 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.
References
On This Page