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 <[email protected]> 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 <[email protected]> Reviewed-by: Kent Tamura <[email protected]> Commit-Queue: Rohan Raja <[email protected]> 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