Chrome · ContentCapture › ContentCapture
CVE-2026-113180
Memory Corruption in ContentCapture (ContentCapture)
Overview
Low
Severity
—
CVSS
No
Exploited ITW
Fixed
Fix Status
Changed Functions
| Function | Change | Notes |
|---|---|---|
forcomponents/content_capture/android/java/src/org/chromium/components/content_capture/PlatformContentCaptureController.java |
modified |
Files Changed
components/content_capture/android/junit/src/org/chromium/components/content_capture/PlatformContentCaptureControllerTest.javacomponents/content_capture/android/java/src/org/chromium/components/content_capture/PlatformContentCaptureController.java
Patch
From c88830605dc40c8509f5664250862efe1b2761d2 Mon Sep 17 00:00:00 2001 From: Peilin Wang <[email protected]> Date: Wed, 16 Sep 2026 07:51:37 -0700 Subject: [PATCH] [ContentCapture] Don't send deletion urls to unauthorized services We should only be sending deletion urls if we used content capture. If we didn't use content capture, the deletion urls should stay within chrome. This CL adds a check to pass the deletion urls only if we used content capture. Bug: 535725111 Change-Id: I844dd527fe52f56fe9917f2f7d11e4a5fbd43834 Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/8415045 Commit-Queue: Peilin Wang <[email protected]> Reviewed-by: Sayed Elabady <[email protected]> Cr-Commit-Position: refs/heads/main@{#1698932} --- diff --git a/components/content_capture/android/java/src/org/chromium/components/content_capture/PlatformContentCaptureController.java b/components/content_capture/android/java/src/org/chromium/components/content_capture/PlatformContentCaptureController.java index a21fe1f..1413009 100644 --- a/components/content_capture/android/java/src/org/chromium/components/content_capture/PlatformContentCaptureController.java +++ b/components/content_capture/android/java/src/org/chromium/components/content_capture/PlatformContentCaptureController.java @@ -140,14 +140,34 @@ Log.i(TAG, msg); } + /** + * Whether data removal requests may be sent to the current ContentCaptureService. + * + * <p>A removal request names the data to delete, and {@link #clearContentCaptureDataForURLs} + * puts the full spec of every deleted URL into it. It must therefore only be sent to a service + * that could have received captured content in the first place; sending it to any other service + * would disclose the user's browsing history to a service that never had it, with no data to + * delete in return. + * + * <p>This is deliberately weaker than {@link #shouldStartCapture()}. AiAi may still hold + * content captured while content capture was enabled, so it has to keep receiving deletions + * after capture is turned off, otherwise deleting history would silently leave the captured + * copy behind. {@code mShouldStartCapture} additionally covers debug and dump-for-testing + * builds, where a non-AiAi service is intentionally allowed to capture. + */ + private boolean canRemoveData() { + if (mContentCaptureManager == null) return false; + return mIsAiai || mShouldStartCapture; + } + public void clearAllContentCaptureData() { - if (mContentCaptureManager == null) return; + if (!canRemoveData()) return; mContentCaptureManager.removeData(new DataRemovalRequest.Builder().forEverything().build()); } public void clearContentCaptureDataForURLs(String[] urlsToDelete) { - if (mContentCaptureManager == null) return; + if (!canRemoveData()) return; DataRemovalRequest.Builder builder = new DataRemovalRequest.Builder(); for (String url : urlsToDelete) { diff --git a/components/content_capture/android/junit/src/org/chromium/components/content_capture/PlatformContentCaptureControllerTest.java b/components/content_capture/android/junit/src/org/chromium/components/content_capture/PlatformContentCaptureControllerTest.java index 92298a8..5fecc63 100644 --- a/components/content_capture/android/junit/src/org/chromium/components/content_capture/PlatformContentCaptureControllerTest.java +++ b/components/content_capture/android/junit/src/org/chromium/components/content_capture/PlatformContentCaptureControllerTest.java @@ -4,9 +4,13 @@ package org.chromium.components.content_capture; +import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; +import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.verify; import android.content.ComponentName; import android.content.Context; @@ -14,17 +18,21 @@ import android.os.Build; import android.view.contentcapture.ContentCaptureCondition; import android.view.contentcapture.ContentCaptureManager; +import android.view.contentcapture.DataRemovalRequest; import androidx.annotation.RequiresApi; +import org.junit.After; import org.junit.Before; import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; +import org.mockito.ArgumentCaptor; import org.mockito.Mockito; import org.mockito.junit.MockitoJUnit; import org.mockito.junit.MockitoRule; import org.robolectric.annotation.Config; +import org.robolectric.util.ReflectionHelpers; import org.chromium.base.test.BaseRobolectricTestRunner; @@ -35,13 +43,22 @@ @Config(sdk = BaseRobolectricTestRunner.MIN_SDK) @RequiresApi(Build.VERSION_CODES.Q) public class PlatformContentCaptureControllerTest { + private static final String SENSITIVE_URL = "https://example.com/account?session_token=secret"; + @Rule public MockitoRule mMockitoRule = MockitoJUnit.rule(); private ContentCaptureManager mContentCaptureManager; private Context mContext; private ComponentName mComponentName; + private String mOriginalBuildType; @Before public void setUp() { + // verifyService() lets a non-AiAi service through on debuggable builds; pin the build type + // so that the tests below exercise the production behavior regardless of the build type + // baked into the Robolectric android-all jar. + mOriginalBuildType = Build.TYPE; + ReflectionHelpers.setStaticField(Build.class, "TYPE", "user"); + mContext = Mockito.mock(Context.class); mContentCaptureManager = Mockito.mock(ContentCaptureManager.class); mComponentName = Mockito.mock(ComponentName.class); @@ -54,6 +71,11 @@ doReturn(null).when(mContentCaptureManager).getContentCaptureConditions(); } + @After + public void tearDown() { + ReflectionHelpers.setStaticField(Build.class, "TYPE", mOriginalBuildType); + } + @Test public void testEverythingAllowed() throws Throwable { PlatformContentCaptureController controller = @@ -105,4 +127,52 @@ new PlatformContentCaptureController(mContext); assertFalse(controller.shouldStartCapture()); } + + @Test + public void testNoDataRemovalRequestSentToNonAiaiService() throws Throwable { + doReturn("org.abc").when(mComponentName).getPackageName(); + PlatformContentCaptureController controller = + new PlatformContentCaptureController(mContext); + assertFalse(controller.isAiai()); + assertFalse(controller.shouldStartCapture()); + + controller.clearContentCaptureDataForURLs(new String[] {SENSITIVE_URL}); + controller.clearAllContentCaptureData(); + + // The service never received any content, so there is nothing for it to delete; sending it + // the deleted URLs would only disclose the user's browsing history to it. + verify(mContentCaptureManager, never()).removeData(any(DataRemovalRequest.class)); + } + + @Test + public void testDataRemovalRequestSentToAiaiService() throws Throwable { + PlatformContentCaptureController controller = + new PlatformContentCaptureController(mContext); + assertTrue(controller.isAiai()); + + controller.clearContentCaptureDataForURLs(new String[] {SENSITIVE_URL}); + + ArgumentCaptor<DataRemovalRequest> captor = + ArgumentCaptor.forClass(DataRemovalRequest.class); + verify(mContentCaptureManager).removeData(captor.capture()); + assertEquals(1, captor.getValue().getLocusIdRequests().size()); + assertEquals( + SENSITIVE_URL, captor.getValue().getLocusIdRequests().get(0).getLocusId().getId()); + } + + @Test + public void testDataRemovalRequestSentToAiaiServiceWhenCaptureDisabled() throws Throwable { + // AiAi may still hold content captured before content capture was turned off, so deletions + // must keep flowing even though no new content is being captured. + doReturn(false).when(mContentCaptureManager).isContentCaptureEnabled(); + PlatformContentCaptureController controller = + new PlatformContentCaptureController(mContext); + assertTrue(controller.isAiai()); + assertFalse(controller.shouldStartCapture()); + + controller.clearContentCaptureDataForURLs(new String[] {SENSITIVE_URL}); + controller.clearAllContentCaptureData(); + + verify(mContentCaptureManager, Mockito.times(2)).removeData(any(DataRemovalRequest.class)); + } }
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