Low chrome Logic Error 🔧 Commit mapped

Overview

Low
Severity
CVSS
No
Exploited ITW
Fixed
Fix Status
ImpactInappropriate implementation in Editing
DescriptionInappropriate implementation in Editing
ComponentEditing
Bug ClassLogic Error
Tracker497251066
Fix commita4fd0862a98d (chromium/src) +103/-2
CISA KEVNot listed
CreditedGoogle
Disclosed2026-07-29

Changed Functions

FunctionChangeNotes
TEST
third_party/blink/renderer/core/editing/commands/clipboard_commands_test.cc
modified

Files Changed

  • third_party/blink/renderer/core/clipboard/system_clipboard.cc
  • third_party/blink/renderer/core/clipboard/system_clipboard.h
  • third_party/blink/renderer/core/editing/build.gni
  • third_party/blink/renderer/core/editing/commands/clipboard_commands.cc
  • third_party/blink/renderer/core/editing/commands/clipboard_commands_test.cc
  • third_party/blink/renderer/platform/runtime_enabled_features.json5
From a4fd0862a98d8d65db60e00dc2bc8d43ec60e019 Mon Sep 17 00:00:00 2001
From: Rohan Raja <roraja@microsoft.com>
Date: Wed, 17 Jun 2026 07:42:37 -0700
Subject: [PATCH] Fix middle-click paste leaking kStandard image cross-buffer

ClipboardCommands::GetFragmentFromClipboard's image fallback called
SystemClipboard::ReadImageAsImageMarkup() with a hardcoded
mojom::blink::ClipboardBuffer::kStandard, ignoring the active
SystemClipboard::buffer_. On Linux/Ozone, ExecutePasteGlobalSelection
flips that buffer to kSelection via SetSelectionMode(true) for
middle-click paste; the image fallback then read the standard CLIPBOARD
buffer instead of PRIMARY and emitted any image sitting there as a <img
src="data:image/png;base64,..."> in the contenteditable DOM, leaking it
to any page-controlled MutationObserver or input listener.

Route the editor-paste image fallback through a new no-arg
SystemClipboard::ReadImageAsImageMarkup() overload that follows buffer_,
mirroring ReadHTML()/ReadPlainText()/ReadRtf(). The explicit-buffer
overload is retained (documented) for Async Clipboard API callers that
legitimately address a specific OS buffer.

Bug: 497251066
Change-Id: I2dc43d170340b81210458579f0b6bd5434e2a4e6
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/7868735
Reviewed-by: Shweta Bindal <shwetabindal@microsoft.com>
Reviewed-by: Kent Tamura <tkent@chromium.org>
Commit-Queue: Rohan Raja <roraja@microsoft.com>
Cr-Commit-Position: refs/heads/main@{#1648306}
---

diff --git a/third_party/blink/renderer/core/clipboard/system_clipboard.cc b/third_party/blink/renderer/core/clipboard/system_clipboard.cc
index f09f176..a945987e 100644
--- a/third_party/blink/renderer/core/clipboard/system_clipboard.cc
+++ b/third_party/blink/renderer/core/clipboard/system_clipboard.cc
@@ -281,6 +281,10 @@
   clipboard_->ReadPng(buffer, std::move(callback));
 }
 
+String SystemClipboard::ReadImageAsImageMarkup() {
+  return ReadImageAsImageMarkup(buffer_);
+}
+
 String SystemClipboard::ReadImageAsImageMarkup(
     mojom::blink::ClipboardBuffer buffer) {
   mojo_base::BigBuffer png_data = ReadPng(buffer);
diff --git a/third_party/blink/renderer/core/clipboard/system_clipboard.h b/third_party/blink/renderer/core/clipboard/system_clipboard.h
index b78add4a..fe49f82 100644
--- a/third_party/blink/renderer/core/clipboard/system_clipboard.h
+++ b/third_party/blink/renderer/core/clipboard/system_clipboard.h
@@ -98,6 +98,17 @@
   // with an empty BigBuffer. Tracks crbug.com/474131935.
   void ReadPng(mojom::blink::ClipboardBuffer buffer,
                mojom::blink::ClipboardHost::ReadPngCallback callback);
+
+  // Reads the PNG on the currently-active buffer (`buffer_`) and wraps it as
+  // an <img src="data:image/png;base64,..."> markup string. Mirrors
+  // ReadHTML()/ReadPlainText()/ReadRtf() in honouring SetSelectionMode().
+  // Editor-paste call sites must use this overload.
+  String ReadImageAsImageMarkup();
+
+  // Explicit-buffer overload. Reserved for callers that legitimately address
+  // a specific OS buffer (e.g. the Async Clipboard API, which carries the
+  // buffer on its Web Platform contract). Editor paste must NOT use this
+  // overload; it bypasses the SetSelectionMode() invariant.
   String ReadImageAsImageMarkup(mojom::blink::ClipboardBuffer);
 
   // Write the image and its associated tag (bookmark/HTML types).
diff --git a/third_party/blink/renderer/core/editing/build.gni b/third_party/blink/renderer/core/editing/build.gni
index e4386ae..9d790dfd 100644
--- a/third_party/blink/renderer/core/editing/build.gni
+++ b/third_party/blink/renderer/core/editing/build.gni
@@ -391,6 +391,7 @@
   "character_range_mapper_test.cc",
   "commands/apply_block_element_command_test.cc",
   "commands/apply_style_command_test.cc",
+  "commands/clipboard_commands_test.cc",
   "commands/composite_edit_command_test.cc",
   "commands/delete_selection_command_test.cc",
   "commands/editing_command_test.cc",
diff --git a/third_party/blink/renderer/core/editing/commands/clipboard_commands.cc b/third_party/blink/renderer/core/editing/commands/clipboard_commands.cc
index b2c57ff..9d46409 100644
--- a/third_party/blink/renderer/core/editing/commands/clipboard_commands.cc
+++ b/third_party/blink/renderer/core/editing/commands/clipboard_commands.cc
@@ -526,8 +526,14 @@
   if (fragment)
     return std::make_pair(fragment, false);
 
-  if (const String markup = frame.GetSystemClipboard()->ReadImageAsImageMarkup(
-          mojom::blink::ClipboardBuffer::kStandard)) {
+  String markup;
+  if (RuntimeEnabledFeatures::ClipboardPasteImageRespectBufferEnabled()) {
+    markup = frame.GetSystemClipboard()->ReadImageAsImageMarkup();
+  } else {
+    markup = frame.GetSystemClipboard()->ReadImageAsImageMarkup(
+        mojom::blink::ClipboardBuffer::kStandard);
+  }
+  if (!markup.empty()) {
     fragment = CreateFragmentFromMarkup(*frame.GetDocument(), markup,
                                         /* base_url */ "",
                                         kDisallowScriptingAndPluginContent);
diff --git a/third_party/blink/renderer/core/editing/commands/clipboard_commands_test.cc b/third_party/blink/renderer/core/editing/commands/clipboard_commands_test.cc
new file mode 100644
index 0000000..6f26ee0
--- /dev/null
+++ b/third_party/blink/renderer/core/editing/commands/clipboard_commands_test.cc
@@ -0,0 +1,72 @@
+// Copyright 2026 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include <memory>
+
+#include "testing/gtest/include/gtest/gtest.h"
+#include "third_party/blink/public/platform/browser_interface_broker_proxy.h"
+#include "third_party/blink/renderer/core/clipboard/system_clipboard.h"
+#include "third_party/blink/renderer/core/editing/editor.h"
+#include "third_party/blink/renderer/core/editing/frame_selection.h"
+#include "third_party/blink/renderer/core/editing/selection_template.h"
+#include "third_party/blink/renderer/core/editing/set_selection_options.h"
+#include "third_party/blink/renderer/core/editing/visible_selection.h"
+#include "third_party/blink/renderer/core/frame/local_frame.h"
+#include "third_party/blink/renderer/core/html/html_element.h"
+#include "third_party/blink/renderer/core/html_names.h"
+#include "third_party/blink/renderer/core/keywords.h"
+#include "third_party/blink/renderer/core/testing/dummy_page_holder.h"
+#include "third_party/blink/renderer/core/testing/mock_clipboard_host.h"
+#include "third_party/blink/renderer/core/testing/page_test_base.h"
+#include "third_party/blink/renderer/platform/testing/runtime_enabled_features_test_helpers.h"
+#include "third_party/blink/renderer/platform/testing/task_environment.h"
+#include "third_party/blink/renderer/platform/testing/unit_test_helpers.h"
+#include "third_party/skia/include/core/SkBitmap.h"
+#include "third_party/skia/include/core/SkImageInfo.h"
+#include "ui/gfx/geometry/size.h"
+
+namespace blink {
+
+// Paste in kSelection mode (the state ExecutePasteGlobalSelection sets on
+// middle-click) must not leak an image planted on the kStandard buffer
+// through GetFragmentFromClipboard's image fallback.
+TEST(ClipboardCommandsPasteTest, PasteInSelectionModeDoesNotLeakStandardImage) {
+  ScopedClipboardPasteImageRespectBufferForTest scoped_feature(true);
+  test::TaskEnvironment task_environment;
+  auto page_holder = std::make_unique<DummyPageHolder>(gfx::Size(1, 1));
+  LocalFrame& frame = page_holder->GetFrame();
+
+  PageTestBase::MockClipboardHostProvider mock_clipboard_host_provider(
+      frame.GetBrowserInterfaceBroker());
+
+  HTMLElement* body = page_holder->GetDocument().body();
+  body->setAttribute(html_names::kContenteditableAttr, keywords::kTrue);
+  body->Focus();
+  frame.GetDocument()->UpdateStyleAndLayout(DocumentUpdateReason::kTest);
+  frame.Selection().SetSelection(
+      SelectionInDomTree::Builder().SelectAllChildren(*body).Build(),
+      SetSelectionOptions());
+  ASSERT_TRUE(
+      frame.Selection().ComputeVisibleSelectionInDomTree().IsContentEditable());
+
+  SkBitmap bitmap;
+  ASSERT_TRUE(bitmap.tryAllocPixelsFlags(
+      SkImageInfo::Make(4, 3, kN32_SkColorType, kOpaque_SkAlphaType), 0));
+  mojom::blink::ClipboardHost* clipboard_host =
+      mock_clipboard_host_provider.clipboard_host();
+  clipboard_host->WriteImage(bitmap);
+  clipboard_host->CommitWrite();
+  test::RunPendingTasks();
+
+  frame.GetSystemClipboard()->SetSelectionMode(true);
+  frame.GetEditor().ExecuteCommand("Paste");
+  frame.GetSystemClipboard()->SetSelectionMode(false);
+
+  const String html = body->GetInnerHTMLString();
+  EXPECT_FALSE(html.contains("data:image/png"))
+      << "Image fallback leaked kStandard PNG while buffer_ was kSelection: "
+      << html.Utf8();
+}
+
+}  // namespace blink
diff --git a/third_party/blink/renderer/platform/runtime_enabled_features.json5 b/third_party/blink/renderer/platform/runtime_enabled_features.json5
index 7b590e7b..d35ccb8c 100644
--- a/third_party/blink/renderer/platform/runtime_enabled_features.json5
+++ b/third_party/blink/renderer/platform/runtime_enabled_features.json5
@@ -1188,6 +1188,13 @@
       status: "stable",
     },
     {
+      // Fix middle-click paste (kSelection buffer) from leaking images on
+      // the kStandard buffer via the image fallback in GetFragmentFromClipboard.
+      // crbug.com/497251066
+      name: "ClipboardPasteImageRespectBuffer",
+      status: "stable",
+    },
+    {
       // TODO(crbug.com/440374239): Disabled due to https://crbug.com/440374239.
       name: "ClipElementVisibleBoundsInLocalRoot",
     },
Loading diff…

Regression Test / PoC

shipped with the fix
diff --git a/third_party/blink/renderer/core/editing/commands/clipboard_commands_test.cc b/third_party/blink/renderer/core/editing/commands/clipboard_commands_test.cc
new file mode 100644
index 0000000..6f26ee0
--- /dev/null
+++ b/third_party/blink/renderer/core/editing/commands/clipboard_commands_test.cc
@@ -0,0 +1,72 @@
+// Copyright 2026 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include <memory>
+
+#include "testing/gtest/include/gtest/gtest.h"
+#include "third_party/blink/public/platform/browser_interface_broker_proxy.h"
+#include "third_party/blink/renderer/core/clipboard/system_clipboard.h"
+#include "third_party/blink/renderer/core/editing/editor.h"
+#include "third_party/blink/renderer/core/editing/frame_selection.h"
+#include "third_party/blink/renderer/core/editing/selection_template.h"
+#include "third_party/blink/renderer/core/editing/set_selection_options.h"
+#include "third_party/blink/renderer/core/editing/visible_selection.h"
+#include "third_party/blink/renderer/core/frame/local_frame.h"
+#include "third_party/blink/renderer/core/html/html_element.h"
+#include "third_party/blink/renderer/core/html_names.h"
+#include "third_party/blink/renderer/core/keywords.h"
+#include "third_party/blink/renderer/core/testing/dummy_page_holder.h"
+#include "third_party/blink/renderer/core/testing/mock_clipboard_host.h"
+#include "third_party/blink/renderer/core/testing/page_test_base.h"
+#include "third_party/blink/renderer/platform/testing/runtime_enabled_features_test_helpers.h"
+#include "third_party/blink/renderer/platform/testing/task_environment.h"
+#include "third_party/blink/renderer/platform/testing/unit_test_helpers.h"
+#include "third_party/skia/include/core/SkBitmap.h"
+#include "third_party/skia/include/core/SkImageInfo.h"
+#include "ui/gfx/geometry/size.h"
+
+namespace blink {
+
+// Paste in kSelection mode (the state ExecutePasteGlobalSelection sets on
+// middle-click) must not leak an image planted on the kStandard buffer
+// through GetFragmentFromClipboard's image fallback.
+TEST(ClipboardCommandsPasteTest, PasteInSelectionModeDoesNotLeakStandardImage) {
+  ScopedClipboardPasteImageRespectBufferForTest scoped_feature(true);
+  test::TaskEnvironment task_environment;
+  auto page_holder = std::make_unique<DummyPageHolder>(gfx::Size(1, 1));
+  LocalFrame& frame = page_holder->GetFrame();
+
+  PageTestBase::MockClipboardHostProvider mock_clipboard_host_provider(
+      frame.GetBrowserInterfaceBroker());
+
+  HTMLElement* body = page_holder->GetDocument().body();
+  body->setAttribute(html_names::kContenteditableAttr, keywords::kTrue);
+  body->Focus();
+  frame.GetDocument()->UpdateStyleAndLayout(DocumentUpdateReason::kTest);
+  frame.Selection().SetSelection(
+      SelectionInDomTree::Builder().SelectAllChildren(*body).Build(),
+      SetSelectionOptions());
+  ASSERT_TRUE(
+      frame.Selection().ComputeVisibleSelectionInDomTree().IsContentEditable());
+
+  SkBitmap bitmap;
+  ASSERT_TRUE(bitmap.tryAllocPixelsFlags(
+      SkImageInfo::Make(4, 3, kN32_SkColorType, kOpaque_SkAlphaType), 0));
+  mojom::blink::ClipboardHost* clipboard_host =
+      mock_clipboard_host_provider.clipboard_host();
+  clipboard_host->WriteImage(bitmap);
+  clipboard_host->CommitWrite();
+  test::RunPendingTasks();
+
+  frame.GetSystemClipboard()->SetSelectionMode(true);
+  frame.GetEditor().ExecuteCommand("Paste");
+  frame.GetSystemClipboard()->SetSelectionMode(false);
+
+  const String html = body->GetInnerHTMLString();
+  EXPECT_FALSE(html.contains("data:image/png"))
+      << "Image fallback leaked kStandard PNG while buffer_ was kSelection: "
+      << html.Utf8();
+}
+
+}  // namespace blink
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.