CVE-2026-14005
Overview
Changed Functions
| Function | Change | Notes |
|---|---|---|
ifcomponents/omnibox/browser/clipboard_provider.cc |
modified |
Files Changed
components/omnibox/browser/autocomplete_match.hcomponents/omnibox/browser/autocomplete_match_android.cccomponents/omnibox/browser/clipboard_provider.cccomponents/omnibox/browser/clipboard_provider.h
Patch
From 4ef5115409a27a0c3cebefa9a2d1096e36f6d7b9 Mon Sep 17 00:00:00 2001
From: Tomasz Wiszkowski <ender@google.com>
Date: Wed, 20 May 2026 12:00:28 -0700
Subject: [PATCH] Address potential UAF issue in Android Omnibox.
This change ensures the ClipboardProvider on Android does not pass
a raw pointer to an AutocompleteMatch that may be recycled while
Clipboard is processing Image in the background.
Change-Id: I336a5694cb311eed886d8271d573dd811e275762
Fixed: 514740273
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/7859499
Reviewed-by: Sky Malice <skym@chromium.org>
Commit-Queue: Tomasz Wiszkowski <ender@google.com>
Cr-Commit-Position: refs/heads/main@{#1633742}
---
diff --git a/components/omnibox/browser/autocomplete_match.h b/components/omnibox/browser/autocomplete_match.h
index 6a10b1c..9660e35 100644
--- a/components/omnibox/browser/autocomplete_match.h
+++ b/components/omnibox/browser/autocomplete_match.h
@@ -1134,6 +1134,11 @@
const ACMatchClassifications& classifications,
const std::string& provider_name = "");
+ // Acquires weak instance.
+ base::WeakPtr<AutocompleteMatch> GetWeakPtr() {
+ return weak_ptr_factory_.GetWeakPtr();
+ }
+
private:
#if BUILDFLAG(IS_ANDROID)
// Corresponding Java object.
@@ -1147,9 +1152,8 @@
// See AutocompleteControllerAndroid for more details.
mutable std::unique_ptr<base::android::ScopedJavaGlobalRef<jobject>>
java_match_;
-
- base::WeakPtrFactory<AutocompleteMatch> weak_ptr_factory_{this};
#endif
+ base::WeakPtrFactory<AutocompleteMatch> weak_ptr_factory_{this};
};
typedef AutocompleteMatch::ACMatchClassification ACMatchClassification;
diff --git a/components/omnibox/browser/autocomplete_match_android.cc b/components/omnibox/browser/autocomplete_match_android.cc
index 5653f4d2..5c5bd299 100644
--- a/components/omnibox/browser/autocomplete_match_android.cc
+++ b/components/omnibox/browser/autocomplete_match_android.cc
@@ -153,7 +153,7 @@
ClipboardProvider* clipboard_provider =
static_cast<ClipboardProvider*>(provider);
clipboard_provider->UpdateClipboardMatchWithContent(
- this,
+ weak_ptr_factory_.GetWeakPtr(),
base::BindOnce(&AutocompleteMatch::OnClipboardSuggestionContentUpdated,
weak_ptr_factory_.GetWeakPtr(),
base::android::ScopedJavaGlobalRef<jobject>(j_callback)));
diff --git a/components/omnibox/browser/clipboard_provider.cc b/components/omnibox/browser/clipboard_provider.cc
index 3b6c952..8a06393 100644
--- a/components/omnibox/browser/clipboard_provider.cc
+++ b/components/omnibox/browser/clipboard_provider.cc
@@ -399,24 +399,30 @@
}
void ClipboardProvider::UpdateClipboardMatchWithContent(
- AutocompleteMatch* match,
+ base::WeakPtr<AutocompleteMatch> match,
ClipboardMatchCallback callback) {
DCHECK(match);
if (match->type == AutocompleteMatchType::CLIPBOARD_URL) {
- clipboard_content_->GetRecentURLFromClipboard(base::BindOnce(
- &ClipboardProvider::OnReceiveURLForMatchWithContent,
- callback_weak_ptr_factory_.GetWeakPtr(), std::move(callback), match));
+ clipboard_content_->GetRecentURLFromClipboard(
+ base::BindOnce(&ClipboardProvider::OnReceiveURLForMatchWithContent,
+ callback_weak_ptr_factory_.GetWeakPtr(),
+ std::move(callback), std::move(match)));
return;
} else if (match->type == AutocompleteMatchType::CLIPBOARD_TEXT) {
- clipboard_content_->GetRecentTextFromClipboard(base::BindOnce(
- &ClipboardProvider::OnReceiveTextForMatchWithContent,
- callback_weak_ptr_factory_.GetWeakPtr(), std::move(callback), match));
+ clipboard_content_->GetRecentTextFromClipboard(
+ base::BindOnce(&ClipboardProvider::OnReceiveTextForMatchWithContent,
+ callback_weak_ptr_factory_.GetWeakPtr(),
+ std::move(callback), std::move(match)));
return;
} else if (match->type == AutocompleteMatchType::CLIPBOARD_IMAGE) {
- clipboard_content_->GetRecentImageFromClipboard(base::BindOnce(
- &ClipboardProvider::OnReceiveImageForMatchWithContent,
- callback_weak_ptr_factory_.GetWeakPtr(), std::move(callback), match));
+ clipboard_content_->GetRecentImageFromClipboard(
+ base::BindOnce(&ClipboardProvider::OnReceiveImageForMatchWithContent,
+ callback_weak_ptr_factory_.GetWeakPtr(),
+ std::move(callback), std::move(match)));
return;
+ } else {
+ // No updates, but don't keep the caller hanging.
+ std::move(callback).Run();
}
}
@@ -458,57 +464,67 @@
void ClipboardProvider::OnReceiveURLForMatchWithContent(
ClipboardMatchCallback callback,
- AutocompleteMatch* match,
+ base::WeakPtr<AutocompleteMatch> weak_match,
std::optional<GURL> optional_gurl) {
- if (!optional_gurl)
+ if (!optional_gurl || !weak_match) {
+ std::move(callback).Run();
return;
+ }
GURL url = std::move(optional_gurl).value();
- UpdateClipboardURLContent(url, match);
+ UpdateClipboardURLContent(url, weak_match.get());
std::move(callback).Run();
}
void ClipboardProvider::OnReceiveTextForMatchWithContent(
ClipboardMatchCallback callback,
- AutocompleteMatch* match,
+ base::WeakPtr<AutocompleteMatch> weak_match,
std::optional<std::u16string> optional_text) {
- if (!optional_text)
+ if (!optional_text || !weak_match) {
+ std::move(callback).Run();
return;
+ }
std::u16string text = std::move(optional_text).value();
- if (!UpdateClipboardTextContent(text, match))
+ if (!UpdateClipboardTextContent(text, weak_match.get())) {
return;
+ }
std::move(callback).Run();
}
void ClipboardProvider::OnReceiveImageForMatchWithContent(
ClipboardMatchCallback callback,
- AutocompleteMatch* match,
+ base::WeakPtr<AutocompleteMatch> weak_match,
std::optional<gfx::Image> optional_image) {
- if (!optional_image)
+ if (!optional_image || !weak_match) {
+ std::move(callback).Run();
return;
+ }
gfx::Image image = std::move(optional_image).value();
NewClipboardImageMatch(
image,
base::BindOnce(&ClipboardProvider::OnReceiveImageMatchForMatchWithContent,
callback_weak_ptr_factory_.GetWeakPtr(),
- std::move(callback), match));
+ std::move(callback), std::move(weak_match)));
}
void ClipboardProvider::OnReceiveImageMatchForMatchWithContent(
ClipboardMatchCallback callback,
- AutocompleteMatch* match,
+ base::WeakPtr<AutocompleteMatch> weak_match,
std::optional<AutocompleteMatch> optional_match) {
- DCHECK(match);
- if (!optional_match)
+ if (!optional_match || !weak_match) {
+ // Always run to notify the caller of completion even if we have no image to
+ // serve, or the match no longer exists.
+ std::move(callback).Run();
return;
+ }
- match->destination_url = std::move(optional_match->destination_url);
- match->post_content = std::move(optional_match->post_content);
- match->search_terms_args = std::move(optional_match->search_terms_args);
+ weak_match->destination_url = std::move(optional_match->destination_url);
+ weak_match->post_content = std::move(optional_match->post_content);
+ weak_match->search_terms_args = std::move(optional_match->search_terms_args);
std::move(callback).Run();
}
diff --git a/components/omnibox/browser/clipboard_provider.h b/components/omnibox/browser/clipboard_provider.h
index 6f082cd3..f174e87c 100644
--- a/components/omnibox/browser/clipboard_provider.h
+++ b/components/omnibox/browser/clipboard_provider.h
@@ -46,7 +46,7 @@
using ClipboardMatchCallback = base::OnceCallback<void()>;
// Update clipboard match |match| with the current clipboard content.
- void UpdateClipboardMatchWithContent(AutocompleteMatch* match,
+ void UpdateClipboardMatchWithContent(base::WeakPtr<AutocompleteMatch> match,
ClipboardMatchCallback callback);
// AutocompleteProvider implementation.
@@ -113,28 +113,28 @@
// Called when url data is received from clipboard for creating match with
Regression Test / PoC
diff --git a/components/omnibox/browser/clipboard_provider_unittest.cc b/components/omnibox/browser/clipboard_provider_unittest.cc
index 01a21c5..82921d8a 100644
--- a/components/omnibox/browser/clipboard_provider_unittest.cc
+++ b/components/omnibox/browser/clipboard_provider_unittest.cc
@@ -50,8 +50,9 @@
AutocompleteMatch* match)
: received_(false) {
provider->UpdateClipboardMatchWithContent(
- match, base::BindOnce(&CreateMatchWithContentCallbackWaiter::OnComplete,
- weak_ptr_factory_.GetWeakPtr()));
+ match->GetWeakPtr(),
+ base::BindOnce(&CreateMatchWithContentCallbackWaiter::OnComplete,
+ weak_ptr_factory_.GetWeakPtr()));
}
void WaitForMatchUpdated() {
@@ -296,6 +297,18 @@
EXPECT_EQ(u"alert()", match.fill_into_edit);
EXPECT_EQ(AutocompleteMatchType::CLIPBOARD_TEXT, match.type);
}
+ {
+ SCOPED_TRACE("Match destroyed");
+ SetClipboardUrl(GURL(kClipboardURL));
+ EXPECT_CALL(*client_.get(), GetSchemeClassifier())
+ .WillOnce(testing::ReturnRef(classifier_));
+ auto match =
+ std::make_unique<AutocompleteMatch>(provider_->NewBlankURLMatch());
+ CreateMatchWithContentCallbackWaiter waiter(provider_, match.get());
+ match.reset();
+ // Should be fine.
+ waiter.WaitForMatchUpdated();
+ }
}
TEST_F(ClipboardProviderTest, CreateTextMatchWithContent) {
@@ -349,20 +362,46 @@
EXPECT_EQ(u"alert()", match.fill_into_edit);
EXPECT_EQ(AutocompleteMatchType::CLIPBOARD_TEXT, match.type);
}
+ {
+ SCOPED_TRACE("Match destroyed");
+ SetClipboardText(u"text");
+ auto match =
+ std::make_unique<AutocompleteMatch>(provider_->NewBlankTextMatch());
+ CreateMatchWithContentCallbackWaiter waiter(provider_, match.get());
+ match.reset();
+ // Should be fine.
+ waiter.WaitForMatchUpdated();
+ }
}
TEST_F(ClipboardProviderTest, CreateImageMatchWithContent) {
- gfx::Image test_image = gfx::test::CreateImage(/*width=*/10, /*height=*/10);
- SetClipboardImage(test_image);
- client_->set_template_url_service(
- search_engines_test_environment_.template_url_service());
- AutocompleteMatch match = provider_->NewBlankImageMatch();
- CreateMatchWithContentCallbackWaiter waiter(provider_, &match);
- waiter.WaitForMatchUpdated();
+ {
+ SCOPED_TRACE("Copy Image");
+ gfx::Image test_image = gfx::test::CreateImage(/*width=*/10, /*height=*/10);
+ SetClipboardImage(test_image);
+ client_->set_template_url_service(
+ search_engines_test_environment_.template_url_service());
+ AutocompleteMatch match = provider_->NewBlankImageMatch();
+ CreateMatchWithContentCallbackWaiter waiter(provider_, &match);
+ waiter.WaitForMatchUpdated();
- EXPECT_FALSE(match.post_content->first.empty());
- EXPECT_FALSE(match.post_content->second.empty());
- EXPECT_EQ(AutocompleteMatchType::CLIPBOARD_IMAGE, match.type);
+ EXPECT_FALSE(match.post_content->first.empty());
+ EXPECT_FALSE(match.post_content->second.empty());
+ EXPECT_EQ(AutocompleteMatchType::CLIPBOARD_IMAGE, match.type);
+ }
+ {
+ SCOPED_TRACE("Match destroyed");
+ gfx::Image test_image = gfx::test::CreateImage(/*width=*/10, /*height=*/10);
+ SetClipboardImage(test_image);
+ client_->set_template_url_service(
+ search_engines_test_environment_.template_url_service());
+ auto match =
+ std::make_unique<AutocompleteMatch>(provider_->NewBlankImageMatch());
+ CreateMatchWithContentCallbackWaiter waiter(provider_, match.get());
+ match.reset();
+ // Should be fine.
+ waiter.WaitForMatchUpdated();
+ }
}
#if BUILDFLAG(IS_ANDROID)
Original Bug Report
Potential Use-After-Free in Browser process via raw AutocompleteMatch pointer in ClipboardProvider
Project Fortify, an experimental security project, has identified the following potential security issue. If you’re a feature owner CC-ed on this bug, please do your best to review these reports. Please see https://chromium.googlesource.com/chromium/src/+/main/docs/security/ai-generated-security-bugs-faq.md for more information.
Overview: A raw pointer to an AutocompleteMatch is captured in asynchronous callbacks during clipboard image processing on Android. If a result update occurs while the background tasks are running, the underlying match object can be destroyed, leading to a Use-After-Free (UAF) in the browser process.
Affected files:
components/omnibox/browser/clipboard_provider.cccomponents/omnibox/browser/autocomplete_match_android.cccomponents/omnibox/browser/autocomplete_controller.cccomponents/omnibox/browser/autocomplete_result.cc
Estimated timestamp from git blame: Unknown (Google3 checkout)
Summary
A potential Use-After-Free (UAF) vulnerability has been identified in the browser process within the Omnibox’s ClipboardProvider on Android. The issue stems from capturing a raw pointer to an AutocompleteMatch object across multiple asynchronous ThreadPool hops. If the Omnibox result set is updated (e.g., due to user typing or an asynchronous provider finishing) while image encoding is in progress, the original match object is destroyed, causing a UAF when the callback returns to the UI thread.
Potential Vulnerability Details
On Android, when a user interacts with a clipboard image suggestion, the native AutocompleteMatch::UpdateWithClipboardContent is invoked. This method passes a raw this pointer to ClipboardProvider::UpdateClipboardMatchWithContent to initiate image processing.
For image matches (CLIPBOARD_IMAGE), this pointer is bound into a chain of callbacks involving two asynchronous round-trips to the ThreadPool:
- Read Phase:
ClipboardAndroid::ReadPngretrieves the raw image data on a background thread. - Encode Phase:
ClipboardProvider::EncodeClipboardImageresizes and encodes the image for search processing on a background thread.
The encoding phase can take a significant amount of time (200-800ms for high-resolution images), creating a large race window. During this window, any event that triggers a result update in the AutocompleteController (such as a character being typed or a network-based provider completing) will invoke AutocompleteController::NotifyChanged.
Inside NotifyChanged, the internal_result_ vector (containing the match object) is replaced. Crucially, this method uses base::ScopedSafetyChecksExclusion, which disables PartitionAlloc’s scheduler-loop quarantine, allowing the memory occupied by the destroyed AutocompleteMatch elements to be immediately reclaimed by the allocator.
When the processing callback OnReceiveImageMatchForMatchWithContent eventually executes on the UI thread, it dereferences the dangling match pointer. The code performs move-assignments into the object:
match->destination_url = std::move(optional_match->destination_url);
match->post_content = std::move(optional_match->post_content);
match->search_terms_args = std::move(optional_match->search_terms_args);
Since post_content is a std::unique_ptr, this operation triggers a reset() on the memory address. An attacker who has reclaimed the memory can use this to achieve a controlled-free primitive and a heap write within the unsandboxed browser process.
Potential Steps to Reproduce
- A malicious website writes a high-resolution PNG image to the system clipboard using the
navigator.clipboard.write()API. - The user focuses the Chrome Android omnibox, causing the
ClipboardProviderto generate a suggestion for the image. - The user interacts with the suggestion (e.g., tapping the ‘reveal’ or ‘search’ button).
- While the image is being processed asynchronously (the encoding window), the attacker triggers a result update (e.g., by ensuring another asynchronous provider returns data or by the user typing).
AutocompleteControllerdestroys the original match object and reclaims the memory.- The image processing callback executes, dereferencing the dangling pointer and causing memory corruption.
Suggested Fix
Do not use a raw pointer to an AutocompleteMatch across asynchronous boundaries. Instead, use a base::WeakPtr<AutocompleteMatch> (and ensure the AutocompleteMatch remains in a stable memory location) or pass a unique identifier for the match and look it up in the current AutocompleteResult when the callback returns to the UI thread.
Evaluated with Chrome root at commit: 29093e11cf509e3593f6229e4b1b075cca356049
Results so far have been promising, but there can be wrong deductions. Feel free to adjust as follows:
- If you are familiar with the severity guidelines, you may adjust the severity.
- If this is a false positive, and there’s no work to be done, please close as WAI.
- If there is work to do here but not a vulnerability, please change the issue type to Task/Bug/FR.
Data from false positives will be used to improve accuracy over time. And please feel free to reach out to me directly if you have concerns or feedback on the project.