diff --git a/components/omnibox/browser/clipboard_provider_unittest.cc b/components/omnibox/browser/clipboard_provider_unittest.cc index 01a21c5fb58a3..1d46a0b0db5e8 100644 --- a/components/omnibox/browser/clipboard_provider_unittest.cc +++ b/components/omnibox/browser/clipboard_provider_unittest.cc @@ -8,6 +8,7 @@ #include #include +#include "base/functional/callback_helpers.h" #include "base/memory/ref_counted.h" #include "base/run_loop.h" #include "base/strings/utf_string_conversions.h" @@ -365,6 +366,56 @@ TEST_F(ClipboardProviderTest, CreateImageMatchWithContent) { EXPECT_EQ(AutocompleteMatchType::CLIPBOARD_IMAGE, match.type); } +// Regression test: UpdateClipboardMatchWithContent binds a raw +// AutocompleteMatch* across a ThreadPool round-trip (EncodeClipboardImage). +// On Android, the match lives by-value inside +// AutocompleteController::published_result_.matches_; if NotifyChanged() +// reassigns that vector while the encode is in flight, the bound pointer +// dangles and OnReceiveImageMatchForMatchWithContent writes through freed +// memory. The provider's WeakPtr only guards ClipboardProvider lifetime, not +// the match's, and ClipboardProvider::Start() does not invalidate it. +TEST_F(ClipboardProviderTest, ImageMatchFreedDuringThreadPoolEncode) { + client_->set_template_url_service( + search_engines_test_environment_.template_url_service()); + + gfx::Image test_image = gfx::test::CreateImage(/*width=*/10, /*height=*/10); + SetClipboardImage(test_image); + + // Mirror the production storage: matches live by-value in a heap-backed + // std::vector (AutocompleteResult::matches_). + std::vector published_matches; + published_matches.emplace_back(provider_.get(), + /*relevance=*/1000, + /*deletable=*/false, + AutocompleteMatchType::CLIPBOARD_IMAGE); + AutocompleteMatch* raw_match = &published_matches[0]; + + // Mirror AutocompleteMatch::UpdateWithClipboardContent (Android JNI entry + // point at autocomplete_match_android.cc): the raw element address is bound + // into the async callback chain. NewClipboardImageMatch posts + // EncodeClipboardImage to ThreadPool before this call returns. + provider_->UpdateClipboardMatchWithContent(raw_match, base::DoNothing()); + + // While the encode task is in flight, the user types a character. + // AutocompleteController::Start() does NOT call Stop() on providers; it calls + // ClipboardProvider::Start() directly, which currently does not invalidate + // callback_weak_ptr_factory_ ... + provider_->Start( + CreateAutocompleteInput(metrics::OmniboxFocusType::INTERACTION_DEFAULT), + false); + + // ... and then AutocompleteController::NotifyChanged() copy-assigns + // published_result_.matches_, destroying the old elements and freeing the + // backing buffer. + std::vector().swap(published_matches); + + // ThreadPool reply posts back to the main thread: + // ConstructImageMatchCallback -> OnReceiveImageMatchForMatchWithContent + // writes match->destination_url / post_content / search_terms_args through + // the freed pointer. Under ASAN this is a heap-use-after-free. + task_environment_.RunUntilIdle(); +} + #if BUILDFLAG(IS_ANDROID) TEST_F(ClipboardProviderTest, Android_MergedWithPZPSGroupOnNTP) { SetClipboardText(kClipboardText);