Medium chrome Logic Error 📄 Reporter bug report 🔧 Commit mapped

Overview

Medium
Severity
CVSS
No
Exploited ITW
Fixed
Fix Status
ImpactIncorrect security UI in Contact Picker
DescriptionIncorrect security UI in Contact Picker
ComponentContact Picker
Bug ClassLogic Error
Tracker502328201
Fix commit8c8396a74dbf (chromium/src) +232/-67
CISA KEVNot listed
Creditedmochazril.ti@gmail.com
Disclosed2026-06-02

Files Changed

  • chrome/android/java/src/org/chromium/chrome/browser/contacts_picker/ChromeContactsPickerDelegate.java
  • chrome/android/javatests/src/org/chromium/chrome/browser/contacts_picker/ContactsPickerLauncherTest.java
  • components/browser_ui/contacts_picker/android/java/src/org/chromium/components/browser_ui/contacts_picker/ContactsPickerDialogTest.java
From 8c8396a74dbf2c5ced8acf749a420231ddacecea Mon Sep 17 00:00:00 2001
From: Andy Paicu <andypaicu@chromium.org>
Date: Thu, 30 Apr 2026 10:46:12 -0700
Subject: [PATCH] Reland "[Contacts] Dismiss picker when spawning tab is hidden"

This is a reland of commit 7deafb683b1ac41212904c5e36860a71b340e8fc

The original CL was reverted due to severe flakiness specifically on
android-desktop-16-x64-rel-brya-tests related to the visiblity of the
dialog.

I added a speculative fix to make waiting for visibility more robust and
ran the buildbot android-desktop-16-x64-rel-brya-tests twice and it
seems to fix it.

Original change's description:
> [Contacts] Dismiss picker when spawning tab is hidden
>
> A WebContentsObserver is added to ContactsPicker to automatically cancel
> the dialog if the spawning tab becomes hidden or destroyed.
>
> This prevents spoofing attacks where the native contacts picker dialog
> is shown at the same time as opening a new tab. This can make the
> contact picker show up on the newly opened tab potentially tricking the
> user into thinking the contact sharing request is from the new tab.
>
> Fixed: 502328201
> Change-Id: I8f0281cdaa002327b2263e0bf68d22015515a240
> Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/7795739
> Reviewed-by: Wenyu Fu <wenyufu@chromium.org>
> Reviewed-by: Yaron Friedman <yfriedman@chromium.org>
> Commit-Queue: Andy Paicu <andypaicu@chromium.org>
> Cr-Commit-Position: refs/heads/main@{#1621858}

Change-Id: Id074b5a040d1ffd026119821607705a91aee6785
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/7806525
Reviewed-by: Yaron Friedman <yfriedman@chromium.org>
Reviewed-by: Wenyu Fu <wenyufu@chromium.org>
Commit-Queue: Andy Paicu <andypaicu@chromium.org>
Cr-Commit-Position: refs/heads/main@{#1623317}
---

diff --git a/chrome/android/java/src/org/chromium/chrome/browser/contacts_picker/ChromeContactsPickerDelegate.java b/chrome/android/java/src/org/chromium/chrome/browser/contacts_picker/ChromeContactsPickerDelegate.java
index 9f50dca..b5c85fc 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/contacts_picker/ChromeContactsPickerDelegate.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/contacts_picker/ChromeContactsPickerDelegate.java
@@ -59,4 +59,9 @@
         dialog.show();
         return dialog;
     }
+
+    @Override
+    public void cancelContactsPicker(Object picker) {
+        ((ContactsPickerDialog) picker).cancel();
+    }
 }
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/contacts_picker/ContactsPickerLauncherTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/contacts_picker/ContactsPickerLauncherTest.java
index 112d557..b557922 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/contacts_picker/ContactsPickerLauncherTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/contacts_picker/ContactsPickerLauncherTest.java
@@ -3,6 +3,14 @@
 // found in the LICENSE file.
 package org.chromium.chrome.browser.contacts_picker;
 
+import static androidx.test.espresso.Espresso.onView;
+import static androidx.test.espresso.assertion.ViewAssertions.doesNotExist;
+import static androidx.test.espresso.assertion.ViewAssertions.matches;
+import static androidx.test.espresso.matcher.RootMatchers.isDialog;
+import static androidx.test.espresso.matcher.ViewMatchers.isDisplayed;
+import static androidx.test.espresso.matcher.ViewMatchers.withId;
+
+import androidx.annotation.Nullable;
 import androidx.test.filters.LargeTest;
 
 import org.junit.Assert;
@@ -11,15 +19,25 @@
 import org.junit.runner.RunWith;
 
 import org.chromium.base.ThreadUtils;
+import org.chromium.base.task.AsyncTask;
 import org.chromium.base.test.util.CommandLineFlags;
+import org.chromium.base.test.util.CriteriaHelper;
+import org.chromium.base.test.util.CriteriaNotSatisfiedException;
+import org.chromium.chrome.R;
 import org.chromium.chrome.browser.flags.ChromeSwitches;
 import org.chromium.chrome.test.ChromeJUnit4ClassRunner;
 import org.chromium.chrome.test.transit.ChromeTransitTestRules;
 import org.chromium.chrome.test.transit.FreshCtaTransitTestRule;
 import org.chromium.chrome.test.transit.page.WebPageStation;
+import org.chromium.content_public.browser.ContactsFetcher;
 import org.chromium.content_public.browser.ContactsPicker;
+import org.chromium.content_public.browser.ContactsPickerListener;
+import org.chromium.content_public.browser.Visibility;
 import org.chromium.content_public.browser.WebContents;
 
+import java.util.ArrayList;
+import java.util.List;
+
 /** TestSuite for Chrome's Contacts Picker implementation. */
 @RunWith(ChromeJUnit4ClassRunner.class)
 @CommandLineFlags.Add({ChromeSwitches.DISABLE_FIRST_RUN_EXPERIENCE})
@@ -31,19 +49,53 @@
             ChromeTransitTestRules.freshChromeTabbedActivityRule();
 
     private boolean showContactsPicker(WebContents webContents) {
+        ContactsFetcher fetcher =
+                new ContactsFetcher() {
+                    @Override
+                    public @Nullable AsyncTask fetchContacts(
+                            boolean includeNames,
+                            boolean includeEmails,
+                            boolean includeTel,
+                            boolean includeAddresses,
+                            ContactsRetrievedCallback callback) {
+                        callback.contactsRetrieved(new ArrayList<>());
+                        return null;
+                    }
+
+                    @Override
+                    public @Nullable AsyncTask fetchIcon(
+                            String id, int iconSize, IconRetrievedCallback callback) {
+                        callback.iconRetrieved(null, id);
+                        return null;
+                    }
+                };
+
+        ContactsPickerListener listener =
+                new ContactsPickerListener() {
+                    @Override
+                    public void onContactsPickerUserAction(
+                            int action,
+                            @Nullable List<Contact> contacts,
+                            int percentageShared,
+                            int propertiesSiteRequested,
+                            int propertiesUserRejected) {
+                        // Do nothing
+                    }
+                };
+
         return ThreadUtils.runOnUiThreadBlocking(
                 () -> {
                     return ContactsPicker.showContactsPicker(
                             webContents,
-                            /* listener= */ null,
+                            listener,
                             /* allowMultiple= */ true,
                             /* includeNames= */ true,
                             /* includeEmails= */ true,
                             /* includeTel= */ true,
                             /* includeAddresses= */ true,
                             /* includeIcons= */ true,
-                            webContents.getMainFrame().getLastCommittedOrigin().getScheme(),
-                            /* contactsFetcher= */ null);
+                            "https://example.com",
+                            fetcher);
                 });
     }
 
@@ -60,4 +112,37 @@
         Assert.assertFalse(ContactsPicker.canShowContactsPicker(webContents));
         Assert.assertFalse(showContactsPicker(webContents));
     }
+
+    @Test
+    @LargeTest
+    public void testPickerDismissedOnNavigation() throws Exception {
+        WebPageStation firstPage = mActivityTestRule.startOnBlankPage();
+        WebContents webContents = firstPage.webContentsElement.value();
+
+        // Wait for WebContents to be visible.
+        CriteriaHelper.pollInstrumentationThread(
+                () -> {
+                    return ThreadUtils.runOnUiThreadBlocking(
+                            () -> webContents.getVisibility() == Visibility.VISIBLE);
+                },
+                "WebContents never became visible");
+
+        // Launch the picker.
+        Assert.assertTrue(showContactsPicker(webContents));
+        onView(withId(R.id.selectable_list)).inRoot(isDialog()).check(matches(isDisplayed()));
+
+        // Switch to a new page/tab.
+        firstPage.openFakeLinkToWebPage(mActivityTestRule.getTestServer().getURL(FILE_PATH));
+
+        // Verify the picker is dismissed and the observer is reset to null.
+        CriteriaHelper.pollInstrumentationThread(
+                () -> {
+                    try {
+                        onView(withId(R.id.selectable_list)).check(doesNotExist());
+                        Assert.assertNull(ContactsPicker.getObserverForTesting());
+                    } catch (AssertionError e) {
+                        throw new CriteriaNotSatisfiedException(e);
+                    }
+                });
+    }
 }
diff --git a/components/browser_ui/contacts_picker/android/java/src/org/chromium/components/browser_ui/contacts_picker/ContactsPickerDialogTest.java b/components/browser_ui/contacts_picker/android/java/src/org/chromium/components/browser_ui/contacts_picker/ContactsPickerDialogTest.java
index ecd0712..bce445c 100644
--- a/components/browser_ui/contacts_picker/android/java/src/org/chromium/components/browser_ui/contacts_picker/ContactsPickerDialogTest.java
+++ b/components/browser_ui/contacts_picker/android/java/src/org/chromium/components/browser_ui/contacts_picker/ContactsPickerDialogTest.java
Loading diff…

Original Bug Report

reported by mo...@gmail.com

The contact dialog box may appear on top of the cross-origin page, which could confuse users

Steps to reproduce the problem

  1. Access https://attacked14.github.io/x/contact.html using Chrome on Android.
  2. Click anywhere on the page (ensure you have not previously granted the Contacts permission to the browser).
  3. Observe that a Contact permission dialog appears, but the underlying visible web page is from https://www.google.com (or another cross-origin page opened by the script).
  4. If the user accepts the permission and selects a few contacts from the dialog, the contact details (names, emails, phone numbers) are leaked to the attacker-controlled server.

Problem Description

Description similar issues : https://issues.chromium.org/issues/40057597

When a user clicks on an attacker-controlled page, it is possible to trigger navigator.contacts.select (Contact Picker API) and simultaneously open a cross-origin page in a new browser window.

This causes the Contact permission dialog to appear above the newly opened cross-origin page. This behavior can confuse the user into believing the permission request originates from the visible (spoofed) page rather than the hidden attacker’s origin. Consequently, the user may unintentionally grant access to their contact list, leaking sensitive information to the attacker.

Although the dialog technically displays the origin that requested the Contact Picker, the dialog should ideally be dismissed or blocked when a cross-origin navigation or new window occurs. The current behavior obscures the malicious origin’s relationship with the foreground cross-origin page.

The attack is most effective when:

The user has not previously granted Chrome/Edge the Contacts permission. The attacker uses a domain similar to the targeted origin being spoofed (e.g., a typosquat or lookalike domain).

Summary

The contact dialog box may appear on top of the cross-origin page, which could confuse users

Additional Data

Category: Security
Chrome Channel: Not sure
Regression: N/A \

View on issue tracker