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 <thomasanderson@chromium.org>
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 <haraken@chromium.org>
Commit-Queue: Kentaro Hara <haraken@chromium.org>
Auto-Submit: Thomas Anderson <thomasanderson@chromium.org>
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