Chrome · DataTransfer
CVE-2026-79246
Logic Error in DataTransfer
Overview
Low
Severity
—
CVSS
No
Exploited ITW
Fixed
Fix Status
Files Changed
third_party/blink/renderer/core/clipboard/system_clipboard.cc
Patch
From 29cb4e0d1ae84a8e76ad6d5699281bcdeaa2c3ab Mon Sep 17 00:00:00 2001 From: Tom Anderson <[email protected]> Date: Thu, 16 Jul 2026 09:57:14 -0700 Subject: [PATCH] Partition SystemClipboard::Snapshot cache per-buffer. Currently, SystemClipboard::Snapshot has buffer-blind HasPng, HasPlainText, etc. accessors. On Linux, standard and selection buffers can be queried within a single snapshot scope, leading to cross-buffer cache poisoning (where selection buffer reads incorrectly return cached standard clipboard data). This CL partitions the snapshot cache into a per-buffer data structure, ensuring that kStandard and kSelection lookups are kept strictly isolated. Bug: 525311654 Change-Id: Ie50e0b81597e9c68f91c9bafdbc046e32c2aacc1 Fixed: 525311654 Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/8033382 Reviewed-by: Kentaro Hara <[email protected]> Commit-Queue: Kentaro Hara <[email protected]> Auto-Submit: Thomas Anderson <[email protected]> Cr-Commit-Position: refs/heads/main@{#1663224} --- diff --git a/third_party/blink/renderer/core/clipboard/system_clipboard.cc b/third_party/blink/renderer/core/clipboard/system_clipboard.cc index a945987e..6872571 100644 --- a/third_party/blink/renderer/core/clipboard/system_clipboard.cc +++ b/third_party/blink/renderer/core/clipboard/system_clipboard.cc @@ -521,49 +521,48 @@ bool SystemClipboard::Snapshot::HasPlainText( mojom::blink::ClipboardBuffer buffer) const { - return buffer_.has_value() && plain_text_.has_value(); + return GetBufferData(buffer)->plain_text_.has_value(); } const String& SystemClipboard::Snapshot::PlainText( mojom::blink::ClipboardBuffer buffer) const { DCHECK(HasPlainText(buffer)); - return plain_text_.value(); + return GetBufferData(buffer)->plain_text_.value(); } void SystemClipboard::Snapshot::SetPlainText( mojom::blink::ClipboardBuffer buffer, const String& text) { - BindToBuffer(buffer); - plain_text_ = text; + GetOrCreateBufferData(buffer)->plain_text_ = text; } bool SystemClipboard::Snapshot::HasHtml( mojom::blink::ClipboardBuffer buffer) const { - return buffer_.has_value() && html_.has_value(); + return GetBufferData(buffer)->html_.has_value(); } const KURL& SystemClipboard::Snapshot::Url( mojom::blink::ClipboardBuffer buffer) const { DCHECK(HasHtml(buffer)); - return url_; + return GetBufferData(buffer)->url_; } unsigned SystemClipboard::Snapshot::FragmentStart( mojom::blink::ClipboardBuffer buffer) const { DCHECK(HasHtml(buffer)); - return fragment_start_; + return GetBufferData(buffer)->fragment_start_; } unsigned SystemClipboard::Snapshot::FragmentEnd( mojom::blink::ClipboardBuffer buffer) const { DCHECK(HasHtml(buffer)); - return fragment_end_; + return GetBufferData(buffer)->fragment_end_; } const String& SystemClipboard::Snapshot::Html( mojom::blink::ClipboardBuffer buffer) const { DCHECK(HasHtml(buffer)); - return html_.value(); + return GetBufferData(buffer)->html_.value(); } void SystemClipboard::Snapshot::SetHtml(mojom::blink::ClipboardBuffer buffer, @@ -571,89 +570,87 @@ const KURL& url, unsigned fragment_start, unsigned fragment_end) { - BindToBuffer(buffer); - html_ = html; - url_ = url; - fragment_start_ = fragment_start; - fragment_end_ = fragment_end; + BufferData* data = GetOrCreateBufferData(buffer); + data->html_ = html; + data->url_ = url; + data->fragment_start_ = fragment_start; + data->fragment_end_ = fragment_end; } bool SystemClipboard::Snapshot::HasRtf( mojom::blink::ClipboardBuffer buffer) const { - return buffer_.has_value() && rtf_.has_value(); + return GetBufferData(buffer)->rtf_.has_value(); } const String& SystemClipboard::Snapshot::Rtf( mojom::blink::ClipboardBuffer buffer) const { DCHECK(HasRtf(buffer)); - return rtf_.value(); + return GetBufferData(buffer)->rtf_.value(); } void SystemClipboard::Snapshot::SetRtf(mojom::blink::ClipboardBuffer buffer, const String& rtf) { - BindToBuffer(buffer); - rtf_ = rtf; + GetOrCreateBufferData(buffer)->rtf_ = rtf; } bool SystemClipboard::Snapshot::HasPng( mojom::blink::ClipboardBuffer buffer) const { - return buffer_.has_value() && png_.has_value(); + return GetBufferData(buffer)->png_.has_value(); } mojo_base::BigBuffer SystemClipboard::Snapshot::Png( mojom::blink::ClipboardBuffer buffer) const { DCHECK(HasPng(buffer)); // Make an owning copy of the png to return to user. - base::span<const uint8_t> span = base::span(png_.value()); + base::span<const uint8_t> span = + base::span(GetBufferData(buffer)->png_.value()); return mojo_base::BigBuffer(span); } // TODO(https://crbug.com/1412180): Reduce data copies. void SystemClipboard::Snapshot::SetPng(mojom::blink::ClipboardBuffer buffer, const mojo_base::BigBuffer& png) { - BindToBuffer(buffer); + BufferData* data = GetOrCreateBufferData(buffer); // Make an owning copy of the png to save locally. base::span<const uint8_t> span = base::span(png); - png_ = mojo_base::BigBuffer(span); + data->png_ = mojo_base::BigBuffer(span); } bool SystemClipboard::Snapshot::HasFiles( mojom::blink::ClipboardBuffer buffer) const { - return buffer_.has_value() && files_.has_value(); + return GetBufferData(buffer)->files_.has_value(); } mojom::blink::ClipboardFilesPtr SystemClipboard::Snapshot::Files( mojom::blink::ClipboardBuffer buffer) const { DCHECK(HasFiles(buffer)); - return CloneFiles(files_.value()); + return CloneFiles(GetBufferData(buffer)->files_.value()); } void SystemClipboard::Snapshot::SetFiles( mojom::blink::ClipboardBuffer buffer, mojom::blink::ClipboardFilesPtr& files) { - BindToBuffer(buffer); - files_ = CloneFiles(files); + GetOrCreateBufferData(buffer)->files_ = CloneFiles(files); } bool SystemClipboard::Snapshot::HasCustomData( mojom::blink::ClipboardBuffer buffer, const String& type) const { - return buffer_.has_value() && custom_data_.Contains(type); + return GetBufferData(buffer)->custom_data_.Contains(type); } String SystemClipboard::Snapshot::CustomData( mojom::blink::ClipboardBuffer buffer, const String& type) const { DCHECK(HasCustomData(buffer, type)); - return custom_data_.at(type); + return GetBufferData(buffer)->custom_data_.at(type); } void SystemClipboard::Snapshot::SetCustomData( mojom::blink::ClipboardBuffer buffer, const String& type, const String& data) { - BindToBuffer(buffer); - custom_data_.Set(type, data); + GetOrCreateBufferData(buffer)->custom_data_.Set(type, data); } void SystemClipboard::OnClipboardDataChanged(const Vector<String>& types, @@ -703,13 +700,22 @@ files->file_system_id); }
Loading diff…
Regression Test / PoC
shipped with the fix
diff --git a/third_party/blink/renderer/core/clipboard/system_clipboard_test.cc b/third_party/blink/renderer/core/clipboard/system_clipboard_test.cc
index 927d8b7..717ab835 100644
--- a/third_party/blink/renderer/core/clipboard/system_clipboard_test.cc
+++ b/third_party/blink/renderer/core/clipboard/system_clipboard_test.cc
@@ -95,6 +95,9 @@
EXPECT_TRUE(
system_clipboard().IsValidBufferType(system_clipboard().buffer_));
}
+ void SetSelectionBufferAvailable(bool available) {
+ system_clipboard().is_selection_buffer_available_ = available;
+ }
void RunUntilIdle() { test::RunPendingTasks(); }
@@ -733,6 +736,43 @@
EXPECT_TRUE(data.empty());
}
+TEST_F(SystemClipboardTest, CrossBufferSnapshotIsolation) {
+ SetSelectionBufferAvailable(true);
+
+ // Populate standard clipboard text.
+ clipboard_host()->WriteText("standard_text");
+ clipboard_host()->CommitWrite();
+
+ // Enter snapshot scope.
+ ScopedSystemClipboardSnapshot snapshot(system_clipboard());
+
+ // 1. Read plain text from kStandard.
+ // This should call MockClipboardHost::ReadText.
+ int initial_calls = mock_clipboard_host()->ReadTextCallCountForTesting();
+ EXPECT_EQ(system_clipboard().ReadPlainText(
+ mojom::blink::ClipboardBuffer::kStandard),
+ "standard_text");
+ EXPECT_EQ(mock_clipboard_host()->ReadTextCallCountForTesting(),
+ initial_calls + 1);
+
+ // 2. Read plain text from kStandard again.
+ // This must hit the cache and not call MockClipboardHost::ReadText.
+ EXPECT_EQ(system_clipboard().ReadPlainText(
+ mojom::blink::ClipboardBuffer::kStandard),
+ "standard_text");
+ EXPECT_EQ(mock_clipboard_host()->ReadTextCallCountForTesting(),
+ initial_calls + 1);
+
+ // 3. Read plain text from kSelection.
+ // Because they are in different buffers, the cache must not hit across
+ // buffers. This should call MockClipboardHost::ReadText.
+ EXPECT_EQ(system_clipboard().ReadPlainText(
+ mojom::blink::ClipboardBuffer::kSelection),
+ "standard_text");
+ EXPECT_EQ(mock_clipboard_host()->ReadTextCallCountForTesting(),
+ initial_calls + 2);
+}
+
#if BUILDFLAG(IS_OZONE)
TEST_F(SystemClipboardTest, DataObjectItemGetAsFileRespectsSelectionMode) {
ScopedClipboardPasteImageRespectBufferForTest scoped_feature(true);
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