Chrome · Editing
CVE-2026-17902
Logic Error in Editing
Overview
Low
Severity
—
CVSS
No
Exploited ITW
Fixed
Fix Status
Changed Functions
| Function | Change | Notes |
|---|---|---|
TESTthird_party/blink/renderer/core/editing/commands/clipboard_commands_test.cc |
modified |
Files Changed
third_party/blink/renderer/core/clipboard/system_clipboard.ccthird_party/blink/renderer/core/clipboard/system_clipboard.hthird_party/blink/renderer/core/editing/build.gnithird_party/blink/renderer/core/editing/commands/clipboard_commands.ccthird_party/blink/renderer/core/editing/commands/clipboard_commands_test.ccthird_party/blink/renderer/platform/runtime_enabled_features.json5
Patch
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.
References
On This Page