Medium watch CVSS 4.3 chrome Info Disclosure 📄 Reporter bug report 🔧 Commit mapped

Overview

Medium
Severity
4.3
CVSS
No
Exploited ITW
Fixed
Fix Status
ImpactInformation leak in Canvas
DescriptionInformation leak in Canvas
ComponentCanvas
Sub-componentCanvas2D
Bug ClassInfo Disclosure
Action tierwatch — information disclosure — security-relevant, below the RCE-class bar
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.
Tracker517655953
Fix commit919d6580fccb (chromium/src) +63/-2
CWECWE-200 (Information exposure)
CVSS vectorCVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:U/C:L/I:N/A:N
CISA KEVNot listed
CreditedGoogle
Reported2026-05-28 (per vendor advisory)
Fix landed2026-07-17 (public git)
Disclosed2026-08-25
Patch gap39 days — fix public in source before the advisory

Background

EXIF orientation
Metadata embedded in an image (e.g. a JPEG) that instructs a renderer to rotate/flip the decoded pixels; whether it is honored is controlled by a RespectImageOrientationEnum value (kRespectImageOrientation vs. kDoNotRespectImageOrientation).
`WouldTaintOrigin()`
A CanvasImageSource predicate that returns true when drawing the source onto a canvas would taint the canvas because the pixel data is cross-origin and not readable by the page.
`CSSImageValue`
An image sourced from CSS (e.g. a background-image obtained via computedStyleMap()), one of several CanvasImageSource types that can be passed to drawImage.
Tainted canvas
A canvas whose pixel data has been contaminated by cross-origin content; the security model forbids reading it back (getImageData, toDataURL) precisely to prevent cross-origin pixel disclosure.

Root Cause Analysis

Canvas2DRecorderContext::RespectImageOrientationInternal decides whether to force kRespectImageOrientation for a given CanvasImageSource. The pre-patch condition forced orientation only when the source WouldTaintOrigin() and was specifically an IsImageBitmap() or IsImageElement(); for any other cross-origin taint-inducing source — notably a CSSImageValue — the type gate was false, so the page’s own image-orientation: none was honored instead of the image’s real EXIF orientation. Because the same cross-origin image is rendered with its true EXIF orientation in a same-origin/reference path but with orientation suppressed on the CSS-image canvas path, the dimensions/shape of the drawn result differ in a way observable to the page even though direct pixel readback is blocked, leaking cross-origin information (e.g. whether the image is portrait vs. landscape after EXIF rotation).

The fix removes the IsImageBitmap() || IsImageElement() type gate so that any source that WouldTaintOrigin() forces kRespectImageOrientation, closing the discrepancy. The commit message notes the type gates existed only to avoid a SVGImage::CheckLoaded() CHECK failure that a later change (crrev.com/c/5892795) already eliminated, so the gate was now purely harmful.

Key insight
The core mistake was gating the orientation-safety rule on specific image types (IsImageBitmap/IsImageElement) rather than on the security-relevant condition alone; the fix keys the behavior solely on WouldTaintOrigin(), so cross-origin CSS images are handled like every other tainting source.

Attack Path

  1. Load a cross-origin EXIF image via CSS The attacker page sets a background-image to a cross-origin JPEG carrying an EXIF orientation (e.g. orientation 6, a 90° rotation), as in the regression test’s #loader.
  2. Obtain it as a `CSSImageValue` The page reads the computed background-image through computedStyleMap().get('background-image'), yielding a CanvasImageSource of a type not covered by the old gate.
  3. Draw with orientation suppressed Calling drawImage(cssImage, 0, 0) on a canvas with image-orientation: none reaches RespectImageOrientationInternal; because the source is neither an ImageBitmap nor an image element, the pre-patch code returns RespectImageOrientation() and honors none, drawing the image unrotated.
  4. Observe orientation-dependent output By comparing the drawn geometry to the reference (EXIF-respecting) rendering, the page infers cross-origin EXIF/orientation properties that should not be disclosed, despite the canvas being tainted against direct readback. (inferred)

Impact Assessment

An attacker-controlled web page running in the renderer can infer orientation/shape information about a cross-origin image it is not permitted to read, a cross-origin information leak in Canvas. Preconditions are minimal: the ability to load the target cross-origin image via a CSS property and pass the resulting CSSImageValue to drawImage. The advisory classifies this as an information leak in Canvas with no exploited-in-the-wild status stated; the precise quantity of leaked data beyond orientation is not demonstrated by the diff.

Files Changed

  • third_party/blink/web_tests/external/wpt/css/css-images/image-orientation/image-orientation-none-cross-origin-canvas-css-image-value.sub.html
  • third_party/blink/web_tests/external/wpt/css/css-images/image-orientation/reference/image-orientation-none-cross-origin-canvas-css-image-value-ref.sub.html
  • third_party/blink/renderer/modules/canvas/canvas2d/canvas_2d_recorder_context.cc

Audit Directions

  • Type-gated security rules
    Search for security-relevant decisions guarded by IsImageElement()/IsImageBitmap()/other CanvasImageSource type checks combined with WouldTaintOrigin(); the taint condition should generally stand alone.
  • Cross-origin rendering discrepancies
    Look for code paths where a rendering attribute (orientation, color profile, resolution, intrinsic size) is applied differently depending on source type, which can turn a non-readable tainted resource into an observable oracle.
  • Stale guards after refactors
    Audit conditions whose comments reference an old CHECK/crash they were meant to avoid (e.g. SVGImage::CheckLoaded()); when the underlying crash is removed, such guards may silently become security-weakening.
From 919d6580fccb08492b4822b32403bb7e78a77cab Mon Sep 17 00:00:00 2001
From: Kevin Babbitt <kbabbitt@microsoft.com>
Date: Fri, 17 Jul 2026 09:34:50 -0700
Subject: [PATCH] Canvas2D: Respect EXIF orientation for cross-origin CSSImageValue

Enforce kRespectImageOrientation for any source that would taint the
origin, including CSS image values.

The IsImageBitmap() || IsImageElement() checks which previously gated
the WouldTaintOrigin() check had been introduced to fix a CHECK failure
in SVGImage::CheckLoaded(); see https://crrev.com/c/2521049. However, in
https://crrev.com/c/5892795 we removed the call to CheckLoaded() on this
path, so the image type gates are no longer needed.

Fixed: 517655953
Change-Id: I1e1838c3af56264fb49c4086c9cf6ae28fe84d15
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/8102890
Commit-Queue: Kevin Babbitt <kbabbitt@microsoft.com>
Reviewed-by: Colin Blundell <blundell@chromium.org>
Cr-Commit-Position: refs/heads/main@{#1663972}
---
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 ddda6d1e..133ae40 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
@@ -855,8 +855,7 @@
 RespectImageOrientationEnum
 Canvas2DRecorderContext::RespectImageOrientationInternal(
     CanvasImageSource* image_source) {
-  if ((image_source->IsImageBitmap() || image_source->IsImageElement()) &&
-      image_source->WouldTaintOrigin()) {
+  if (image_source->WouldTaintOrigin()) {
     return kRespectImageOrientation;
   }
   return RespectImageOrientation();
diff --git a/third_party/blink/web_tests/external/wpt/css/css-images/image-orientation/image-orientation-none-cross-origin-canvas-css-image-value.sub.html b/third_party/blink/web_tests/external/wpt/css/css-images/image-orientation/image-orientation-none-cross-origin-canvas-css-image-value.sub.html
new file mode 100644
index 0000000..4a42203
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/css/css-images/image-orientation/image-orientation-none-cross-origin-canvas-css-image-value.sub.html
@@ -0,0 +1,36 @@
+<!DOCTYPE html>
+<html class="reftest-wait">
+<head>
+<meta charset="utf-8">
+<title>CSS Images Module Level 3: image-orientation: none for a cross-origin CSSImageValue drawn to canvas</title>
+<link rel="help" href="https://github.com/w3c/csswg-drafts/issues/5165">
+<link rel="match" href="reference/image-orientation-none-cross-origin-canvas-css-image-value-ref.sub.html">
+<style>
+  #target { image-orientation: none; }
+  #loader {
+    width: 0;
+    height: 0;
+    background-image: url("http://{{hosts[][]}}:{{ports[http][1]}}/css/css-images/image-orientation/support/exif-orientation-6-ru.jpg");
+  }
+</style>
+</head>
+<body>
+  <canvas id="target" width="100" height="50"></canvas>
+  <div id="loader"></div>
+  <img id="probe" style="display:none">
+</body>
+<script>
+  const probe = document.getElementById('probe');
+  probe.onload = () => {
+    requestAnimationFrame(() => requestAnimationFrame(() => {
+      const loader = document.getElementById('loader');
+      const cssImage = loader.computedStyleMap().get('background-image');
+      const ctx = document.getElementById('target').getContext('2d');
+      ctx.drawImage(cssImage, 0, 0);
+      document.documentElement.classList.remove('reftest-wait');
+    }));
+  };
+  probe.src = getComputedStyle(document.getElementById('loader'))
+      .backgroundImage.slice(5, -2);
+</script>
+</html>
diff --git a/third_party/blink/web_tests/external/wpt/css/css-images/image-orientation/reference/image-orientation-none-cross-origin-canvas-css-image-value-ref.sub.html b/third_party/blink/web_tests/external/wpt/css/css-images/image-orientation/reference/image-orientation-none-cross-origin-canvas-css-image-value-ref.sub.html
new file mode 100644
index 0000000..b99292f
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/css/css-images/image-orientation/reference/image-orientation-none-cross-origin-canvas-css-image-value-ref.sub.html
@@ -0,0 +1,26 @@
+<!DOCTYPE html>
+<html class="reftest-wait">
+<head>
+<meta charset="utf-8">
+<title>CSS Images Module Level 3: image-orientation: none for a cross-origin CSSImageValue drawn to canvas (reference)</title>
+<style>
+  #target { image-orientation: none; }
+</style>
+</head>
+<body>
+  <canvas id="target" width="100" height="50"></canvas>
+  <div id="loader"></div>
+  <img id="probe" style="display:none">
+</body>
+<script>
+  const probe = document.getElementById('probe');
+  probe.onload = () => {
+    requestAnimationFrame(() => requestAnimationFrame(() => {
+      const ctx = document.getElementById('target').getContext('2d');
+      ctx.drawImage(probe, 0, 0);
+      document.documentElement.classList.remove('reftest-wait');
+    }));
+  };
+  probe.src = "http://{{hosts[][]}}:{{ports[http][1]}}/css/css-images/image-orientation/support/exif-orientation-6-ru.jpg";
+</script>
+</html>
Loading diff…

Regression Test / PoC

shipped with the fix
diff --git a/third_party/blink/web_tests/external/wpt/css/css-images/image-orientation/image-orientation-none-cross-origin-canvas-css-image-value.sub.html b/third_party/blink/web_tests/external/wpt/css/css-images/image-orientation/image-orientation-none-cross-origin-canvas-css-image-value.sub.html
new file mode 100644
index 0000000..4a42203
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/css/css-images/image-orientation/image-orientation-none-cross-origin-canvas-css-image-value.sub.html
@@ -0,0 +1,36 @@
+<!DOCTYPE html>
+<html class="reftest-wait">
+<head>
+<meta charset="utf-8">
+<title>CSS Images Module Level 3: image-orientation: none for a cross-origin CSSImageValue drawn to canvas</title>
+<link rel="help" href="https://github.com/w3c/csswg-drafts/issues/5165">
+<link rel="match" href="reference/image-orientation-none-cross-origin-canvas-css-image-value-ref.sub.html">
+<style>
+  #target { image-orientation: none; }
+  #loader {
+    width: 0;
+    height: 0;
+    background-image: url("http://{{hosts[][]}}:{{ports[http][1]}}/css/css-images/image-orientation/support/exif-orientation-6-ru.jpg");
+  }
+</style>
+</head>
+<body>
+  <canvas id="target" width="100" height="50"></canvas>
+  <div id="loader"></div>
+  <img id="probe" style="display:none">
+</body>
+<script>
+  const probe = document.getElementById('probe');
+  probe.onload = () => {
+    requestAnimationFrame(() => requestAnimationFrame(() => {
+      const loader = document.getElementById('loader');
+      const cssImage = loader.computedStyleMap().get('background-image');
+      const ctx = document.getElementById('target').getContext('2d');
+      ctx.drawImage(cssImage, 0, 0);
+      document.documentElement.classList.remove('reftest-wait');
+    }));
+  };
+  probe.src = getComputedStyle(document.getElementById('loader'))
+      .backgroundImage.slice(5, -2);
+</script>
+</html>
diff --git a/third_party/blink/web_tests/external/wpt/css/css-images/image-orientation/reference/image-orientation-none-cross-origin-canvas-css-image-value-ref.sub.html b/third_party/blink/web_tests/external/wpt/css/css-images/image-orientation/reference/image-orientation-none-cross-origin-canvas-css-image-value-ref.sub.html
new file mode 100644
index 0000000..b99292f
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/css/css-images/image-orientation/reference/image-orientation-none-cross-origin-canvas-css-image-value-ref.sub.html
@@ -0,0 +1,26 @@
+<!DOCTYPE html>
+<html class="reftest-wait">
+<head>
+<meta charset="utf-8">
+<title>CSS Images Module Level 3: image-orientation: none for a cross-origin CSSImageValue drawn to canvas (reference)</title>
+<style>
+  #target { image-orientation: none; }
+</style>
+</head>
+<body>
+  <canvas id="target" width="100" height="50"></canvas>
+  <div id="loader"></div>
+  <img id="probe" style="display:none">
+</body>
+<script>
+  const probe = document.getElementById('probe');
+  probe.onload = () => {
+    requestAnimationFrame(() => requestAnimationFrame(() => {
+      const ctx = document.getElementById('target').getContext('2d');
+      ctx.drawImage(probe, 0, 0);
+      document.documentElement.classList.remove('reftest-wait');
+    }));
+  };
+  probe.src = "http://{{hosts[][]}}:{{ports[http][1]}}/css/css-images/image-orientation/support/exif-orientation-6-ru.jpg";
+</script>
+</html>
Loading diff…

Original Bug Report

reported by vm...@google.com

Canvas2D drawImage(CSSImageValue) bypasses cross-origin EXIF-orientation clamp

Project Fortify, an experimental security project, has identified the following potential security issue. If you’re a feature owner CC-ed on this bug, please do your best to review these reports. Please see https://chromium.googlesource.com/chromium/src/+/main/docs/security/ai-generated-security-bugs-faq.md for more information.

Overview: A potential vulnerability in Canvas2D allows cross-origin EXIF orientation tags to be leaked via a paint-timing side channel. When drawing a cross-origin, no-CORS image using a CSSImageValue source onto a canvas with image-orientation: none, Blink fails to clamp the EXIF orientation. This allows an attacker to potentially distinguish whether an image has been rotated and leak its orientation tag.

Affected files:

  • third_party/blink/renderer/modules/canvas/canvas2d/canvas_2d_recorder_context.cc
  • third_party/blink/renderer/core/css/cssom/css_style_image_value.h

Estimated timestamp from git blame: 2020-11-06

Description

To prevent leaking metadata of cross-origin, opaque (no-CORS) images, the browser must enforce standard image orientation (kRespectImageOrientation) regardless of CSS styling (such as image-orientation: none).

In Canvas2D rendering, Canvas2DRecorderContext::RespectImageOrientationInternal is responsible for overriding the image orientation behavior for cross-origin sources to prevent orientation leaks.

Root Cause

The implementation of RespectImageOrientationInternal uses a hard-coded closed list of source types (IsImageBitmap and IsImageElement) and completely omits CSSStyleImageValue:

// File: third_party/blink/renderer/modules/canvas/canvas2d/canvas_2d_recorder_context.cc
RespectImageOrientationEnum
Canvas2DRecorderContext::RespectImageOrientationInternal(
    CanvasImageSource* image_source) {
  if ((image_source->IsImageBitmap() || image_source->IsImageElement()) &&
      image_source->WouldTaintOrigin()) {
    return kRespectImageOrientation;
  }
  return RespectImageOrientation();
}

CSSStyleImageValue (the underlying C++ implementation for the CSSImageValue IDL interface) implements CanvasImageSource, but inherits the default implementations of IsImageElement() and IsImageBitmap() which return false (defined in third_party/blink/renderer/core/html/canvas/canvas_image_source.h).

Consequently, when drawing a CSSStyleImageValue source, the override condition evaluates to (false || false) && true, which is false. Blink then falls back to RespectImageOrientation(), which respects the canvas element’s author-controlled image-orientation CSS style property. This allows raw, unrotated pixels to be drawn to the canvas when image-orientation: none is set.

While drawing taints the canvas origin and blocks read-back APIs like getImageData, the differential between the unrotated raw image paint and standard orientation paints is observable via timing-based paint side channels.

Potential Attack Vector / Trigger Path

Note: These are potential steps as our tooling does not yet have the ability to run proof-of-concept code.

  1. Load Target Image: An attacker loads a cross-origin, no-CORS image (e.g., via background-image CSS style). Once loaded, the attacker fetches its CSSImageValue representation using the Typed OM computedStyleMap().get('background-image') API.
  2. Set Orientation to None: The attacker sets imageOrientation = 'none' on a canvas element.
  3. Draw CSSImageValue: The attacker draws the CSSImageValue onto the canvas via ctx.drawImage(cssImageValue, 0, 0). Because the source is not recognized as restricted, the clamp is bypassed and the raw unrotated decode is drawn.
  4. Measure Difference: By comparing this canvas to a canvas drawn with default orientation using a paint-timing side channel (e.g., stacking both with mix-blend-mode: difference under an expensive SVG filter and measuring rendering times via requestAnimationFrame), the EXIF orientation tag of the target image is leaked.

Suggested Fix

Update RespectImageOrientationInternal to include CSSStyleImageValue in the check, or more simply, enforce kRespectImageOrientation for any cross-origin resource that can taint the origin and has orientation properties. For example:

RespectImageOrientationEnum
Canvas2DRecorderContext::RespectImageOrientationInternal(
    CanvasImageSource* image_source) {
  if (image_source->WouldTaintOrigin() &&
      !image_source->IsCanvasElement() &&
      !image_source->IsOffscreenCanvas()) {
    return kRespectImageOrientation;
  }
  return RespectImageOrientation();
}

Evaluated with Chrome root at commit: 5133b93d189b383c37805b1cf3a9d2dbfe8d7379


Results so far have been promising, but there can be wrong deductions. Feel free to adjust as follows:

  • If you are familiar with the severity guidelines, you may adjust the severity.
  • If this is a false positive, and there’s no work to be done, please close as WAI.
  • If there is work to do here but not a vulnerability, please change the issue type to Task/Bug/FR.

Data from false positives will be used to improve accuracy over time. And please feel free to reach out to me directly if you have concerns or feedback on the project.

View on issue tracker