Low chrome Logic Error 🔧 Commit mapped

Overview

Low
Severity
CVSS
No
Exploited ITW
Fixed
Fix Status
ImpactInformation leak in DataTransfer
DescriptionInformation leak in DataTransfer
ComponentDataTransfer
Bug ClassLogic Error
Tracker525311654
Fix commit29cb4e0d1ae8 (chromium/src) +97/-49
CISA KEVNot listed
CreditedGoogle
Disclosed2026-08-25

Files Changed

  • third_party/blink/renderer/core/clipboard/system_clipboard.cc
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.