Low chrome Memory Corruption 🔧 Commit mapped

Overview

Low
Severity
—
CVSS
No
Exploited ITW
Fixed
Fix Status
ImpactMemory corruption in ContextMenu
DescriptionMemory corruption in ContextMenu
ComponentContextMenu
Bug ClassMemory Corruption
Tracker562909956
Fix commit04640c8b4bb0 (chromium/src) +190/-62
CISA KEVNot listed
CreditedGoogle
Disclosed2026-10-20

Changed Functions

FunctionChangeNotes
if
chrome/android/java/src/org/chromium/chrome/browser/contextmenu/ChromeContextMenuPopulator.java
modified
if
chrome/android/java/src/org/chromium/chrome/browser/multiwindow/MultiInstanceOrchestratorImpl.java
modified
if
chrome/android/java/src/org/chromium/chrome/browser/tab/TabContextMenuItemDelegate.java
modified

Files Changed

  • chrome/android/junit/src/org/chromium/chrome/browser/multiwindow/MultiInstanceOrchestratorImplUnitTest.java
  • chrome/android/javatests/src/org/chromium/chrome/browser/contextmenu/ChromeContextMenuPopulatorTest.java
  • chrome/android/java/src/org/chromium/chrome/browser/multiwindow/MultiInstanceManagerApi31.java
  • chrome/android/java/src/org/chromium/chrome/browser/multiwindow/MultiInstanceOrchestratorImpl.java
  • chrome/browser/ui/android/multiwindow/java/src/org/chromium/chrome/browser/multiwindow/TargetSelectorCoordinator.java
  • components/embedder_support/android/junit/src/org/chromium/components/embedder_support/contextmenu/ContextMenuUtilsUnitTest.java
  • chrome/android/java/src/org/chromium/chrome/browser/contextmenu/ChromeContextMenuPopulator.java
  • chrome/android/java/src/org/chromium/chrome/browser/tab/TabContextMenuItemDelegate.java
  • components/embedder_support/android/java/src/org/chromium/components/embedder_support/contextmenu/ContextMenuParams.java
  • content/public/android/java/src/org/chromium/content/browser/AdditionalNavigationParamsImpl.java
From 04640c8b4bb0b6b58f2eb193438a50ceb0f48e88 Mon Sep 17 00:00:00 2001
From: Jenna Himawan <jhimawan@google.com>
Date: Wed, 23 Sep 2026 15:09:10 -0700
Subject: [PATCH] Keep InitiatorNavigationState alive for deferred menu actions

When an Android link context menu closes,
ContextMenuHelper.mOnMenuClosed unconditionally calls
ContextMenuParams.destroy(), which previously freed the native
InitiatorNavigationState in AdditionalNavigationParamsImpl even when
the params had been attached to a deferred navigation (such as 'Open in
new window', 'Open in other window', Data Controls warn dialogs, or
Ephemeral Tab actor confirmation dialogs).

Fix this by:
1. Renaming ContextMenuParams.getAdditionalNavigationParams() to
   takeAdditionalNavigationParams() and nulling out the field so
   ContextMenuParams.destroy() only destroys unconsumed params.
2. Taking AdditionalNavigationParams synchronously in
   ChromeContextMenuPopulator before asynchronous policy verification
   callbacks.
3. Destroying LifetimeAssert in
   AdditionalNavigationParamsImpl.takeNativeState() when ownership of
   the native pointer is transferred, and deferring
   AsyncTabParamsManager registration in MultiInstanceOrchestratorImpl
   until window launch proceeds.

Bug: 562909956
Change-Id: I2745ab149080eef21e515df0e78c94239877e31d
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/8434001
Reviewed-by: Andrew Grieve <agrieve@chromium.org>
Reviewed-by: Sinan Sahin <sinansahin@google.com>
Commit-Queue: Jenna Himawan <jhimawan@google.com>
Reviewed-by: Bo Liu <boliu@chromium.org>
Cr-Commit-Position: refs/heads/main@{#1703823}
---
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/contextmenu/ChromeContextMenuPopulator.java b/chrome/android/java/src/org/chromium/chrome/browser/contextmenu/ChromeContextMenuPopulator.java
index 0dc553e..f45e0f21 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/contextmenu/ChromeContextMenuPopulator.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/contextmenu/ChromeContextMenuPopulator.java
@@ -35,6 +35,7 @@
 import androidx.browser.customtabs.CustomTabsIntent;
 
 import org.chromium.base.ApiCompatibilityUtils;
+import org.chromium.base.Callback;
 import org.chromium.base.ContextUtils;
 import org.chromium.base.DeviceInfo;
 import org.chromium.base.LocaleUtils;
@@ -1368,13 +1369,17 @@
                                     && !mParams.getLinkUrl().isEmpty())
                             ? mParams.getLinkUrl()
                             : mParams.getSrcUrl();
+            var additionalNavigationParams = mParams.takeAdditionalNavigationParams();
             verifyGenericCopyImageActionIsAllowedByPolicy(
                     url.getSpec(),
-                    () ->
+                    (isAllowed) -> {
+                        if (isAllowed) {
                             mItemDelegate.onOpenImageInNewTab(
-                                    url,
-                                    mParams.getReferrer(),
-                                    mParams.getAdditionalNavigationParams()));
+                                    url, mParams.getReferrer(), additionalNavigationParams);
+                        } else if (additionalNavigationParams != null) {
+                            additionalNavigationParams.destroy();
+                        }
+                    });
         } else if (itemId == R.id.contextmenu_open_image_in_ephemeral_tab) {
             recordContextMenuSelection(ContextMenuUma.Action.OPEN_IMAGE_IN_EPHEMERAL_TAB);
             GURL url =
@@ -1384,12 +1389,17 @@
                                     && !mParams.getLinkUrl().isEmpty())
                             ? mParams.getLinkUrl()
                             : mParams.getSrcUrl();
+            var additionalNavigationParams = mParams.takeAdditionalNavigationParams();
             verifyGenericCopyImageActionIsAllowedByPolicy(
                     url.getSpec(),
-                    () -> {
-                        String title = getTitleOrGuessIfNotPresent();
-                        mItemDelegate.onOpenInEphemeralTab(
-                                url, title, mParams.getAdditionalNavigationParams());
+                    (isAllowed) -> {
+                        if (isAllowed) {
+                            String title = getTitleOrGuessIfNotPresent();
+                            mItemDelegate.onOpenInEphemeralTab(
+                                    url, title, additionalNavigationParams);
+                        } else if (additionalNavigationParams != null) {
+                            additionalNavigationParams.destroy();
+                        }
                     });
         } else if (itemId == R.id.contextmenu_open_in_new_tab) {
             recordContextMenuSelection(ContextMenuUma.Action.OPEN_IN_NEW_TAB);
@@ -1398,14 +1408,14 @@
                     mParams.getUrl(),
                     mParams.getReferrer(),
                     /* navigateToTab= */ false,
-                    mParams.getAdditionalNavigationParams());
+                    mParams.takeAdditionalNavigationParams());
         } else if (itemId == R.id.contextmenu_open_in_new_tab_in_group) {
             recordContextMenuSelection(ContextMenuUma.Action.OPEN_IN_NEW_TAB_IN_GROUP);
             RecordUserAction.record("TabContextMenu.OpenInNewTabInGroup");
             mItemDelegate.onOpenInNewTabInGroup(
                     mParams.getUrl(),
                     mParams.getReferrer(),
-                    mParams.getAdditionalNavigationParams());
+                    mParams.takeAdditionalNavigationParams());
         } else if (itemId == R.id.contextmenu_open_in_incognito_tab) {
             recordContextMenuSelection(ContextMenuUma.Action.OPEN_IN_INCOGNITO_TAB);
             mItemDelegate.onOpenInNewIncognitoTab(mParams.getUrl());
@@ -1419,7 +1429,7 @@
                     mParams.getReferrer(),
                     mItemDelegate.isIncognito(),
                     /* preferNew= */ false,
-                    mParams.getAdditionalNavigationParams());
+                    mParams.takeAdditionalNavigationParams());
         } else if (itemId == R.id.contextmenu_open_in_new_window) {
             recordContextMenuSelection(ContextMenuUma.Action.OPEN_IN_NEW_WINDOW);
             mItemDelegate.openInOtherWindow(
@@ -1427,19 +1437,19 @@
                     mParams.getReferrer(),
                     mItemDelegate.isIncognito(),
                     /* preferNew= */ true,
-                    mParams.getAdditionalNavigationParams());
+                    mParams.takeAdditionalNavigationParams());
         } else if (itemId == R.id.contextmenu_open_in_ephemeral_tab) {
             recordContextMenuSelection(ContextMenuUma.Action.OPEN_IN_EPHEMERAL_TAB);
             mItemDelegate.onOpenInEphemeralTab(
                     mParams.getUrl(),
                     mParams.getLinkText(),
-                    mParams.getAdditionalNavigationParams());
+                    mParams.takeAdditionalNavigationParams());
         } else if (itemId == R.id.contextmenu_open_image) {
             recordContextMenuSelection(ContextMenuUma.Action.OPEN_IMAGE);
             mItemDelegate.onOpenImageUrl(
                     mParams.getSrcUrl(),
                     mParams.getReferrer(),
-                    mParams.getAdditionalNavigationParams());
+                    mParams.takeAdditionalNavigationParams());
         } else if (itemId == R.id.contextmenu_read_later) {
             recordContextMenuSelection(ContextMenuUma.Action.READ_LATER);
             String title = mParams.getTitleText();
@@ -1575,7 +1585,10 @@
     /** Copy the video frame, that triggered the current context menu, to system clipboard. */
     private void copyVideoFrameToClipboard() {
         verifyGenericCopyImageActionIsAllowedByPolicy(
-                mParams.getSrcUrl().getSpec(), mNativeDelegate::copyVideoFrame);
+                mParams.getSrcUrl().getSpec(),
+                (isAllowed) -> {
+                    if (isAllowed) mNativeDelegate.copyVideoFrame();
+                });
     }
 
     /** Download the video frame, that triggered the current context menu, to the device. */
@@ -1584,13 +1597,9 @@
     }
 
     private void verifyGenericCopyImageActionIsAllowedByPolicy(
-            String imageUri, Runnable continueIfCopyAllowed) {
+            String imageUri, Callback<Boolean> callback) {
         DataProtectionBridge.verifyGenericCopyImageActionIsAllowedByPolicy(
-                imageUri,
-                mItemDelegate.getWebContents().getMainFrame(),
-                (isAllowed) -> {
-                    if (isAllowed) continueIfCopyAllowed.run();
-                });
+                imageUri, mItemDelegate.getWebContents().getMainFrame(), callback);
     }
 
     /**
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/multiwindow/MultiInstanceManagerApi31.java b/chrome/android/java/src/org/chromium/chrome/browser/multiwindow/MultiInstanceManagerApi31.java
index 568b91ad..8867e46 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/multiwindow/MultiInstanceManagerApi31.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/multiwindow/MultiInstanceManagerApi31.java
@@ -251,7 +251,7 @@
     }
 
     /* package */ void showTargetSelectorDialog(
-            Callback<InstanceInfo> moveCallback,
+            Callback<@Nullable InstanceInfo> moveCallback,
             @PersistedInstanceType int instanceType,
             @StringRes int titleId) {
         TargetSelectorCoordinator.showDialog(
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/multiwindow/MultiInstanceOrchestratorImpl.java b/chrome/android/java/src/org/chromium/chrome/browser/multiwindow/MultiInstanceOrchestratorImpl.java
index fae1ca16..c9bf250 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/multiwindow/MultiInstanceOrchestratorImpl.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/multiwindow/MultiInstanceOrchestratorImpl.java
@@ -49,6 +49,7 @@
 import java.util.List;
 import java.util.Map;
 import java.util.Set;
+import java.util.function.Supplier;
 
 /** Implements {@link MultiInstanceOrchestrator} as a singleton. */
 @NullMarked
@@ -303,6 +304,7 @@
             ((MultiInstanceManagerApi31) multiInstanceManager)
                     .showTargetSelectorDialog(
                             (instanceInfo) -> {
+                                if (instanceInfo == null) return;
                                 moveTabsToWindowByIdChecked(
                                         instanceInfo.instanceId,
                                         tabs,
@@ -396,6 +398,7 @@
         if (multiInstanceManager != null) {
             multiInstanceManager.showTargetSelectorDialog(
                     (instanceInfo) -> {
+                        if (instanceInfo == null) return;
                         moveTabGroupToWindowByIdChecked(
                                 instanceInfo.instanceId,
                                 tabGroupMetadata,
@@ -464,24 +467,38 @@
             boolean openInTabGroup) {
         var targetActivityClass =
                 MultiWindowUtils.getInstance().getOpenInOtherWindowActivity(sourceActivity);
-        if (targetActivityClass == null) return false;
-
-        Intent intent =
-                getBasicUrlLaunchIntent(
-                        sourceActivity,
-                        loadUrlParams,
-                        parentTabId,
-                        isIncognito,
-                        targetActivityClass);
-
-        if (additionalUrls != null && !additionalUrls.isEmpty()) {
-            intent.putExtra(IntentHandler.EXTRA_ADDITIONAL_URLS, new ArrayList<>(additionalUrls));
-            if (openInTabGroup) {
-                intent.putExtra(IntentHandler.EXTRA_OPEN_ADDITIONAL_URLS_IN_TAB_GROUP, true);
-            }
+        if (targetActivityClass == null) {
+            destroyAdditionalNavigationParams(loadUrlParams);
+            return false;
         }
 
+        // Defer calling getBasicUrlLaunchIntent() until the launch is ready to proceed, because
+        // IntentHandler.createAsyncNewTabIntent() registers loadUrlParams (including its native
+        // AdditionalNavigationParams) in AsyncTabParamsManagerSingleton, which would leak if
+        // instance limits are exceeded or the target selector dialog is dismissed.
+        Supplier<Intent> createIntent =
+                () -> {
+                    Intent intent =
+                            getBasicUrlLaunchIntent(
+                                    sourceActivity,
+                                    loadUrlParams,
+                                    parentTabId,
+                                    isIncognito,
+                                    targetActivityClass);
+                    if (additionalUrls != null && !additionalUrls.isEmpty()) {
+                        intent.putExtra(
+                                IntentHandler.EXTRA_ADDITIONAL_URLS,
+                                new ArrayList<>(additionalUrls));
+                        if (openInTabGroup) {
+                            intent.putExtra(
+                                    IntentHandler.EXTRA_OPEN_ADDITIONAL_URLS_IN_TAB_GROUP, true);
+                        }
+                    }
+                    return intent;
+                };
+
         if (!MultiWindowUtils.isMultiInstanceApi31Enabled()) {
+            Intent intent = createIntent.get();
             addOpenUrlInNewWindowIntentExtras(
                     sourceActivity, intent, /* isIncognitoWindow= */ false);
             MultiInstanceManager.onMultiInstanceModeStarted();
@@ -518,11 +535,12 @@
                 if (multiInstanceManager != null) {
                     multiInstanceManager.showInstanceCreationLimitMessage();
                 }
+                destroyAdditionalNavigationParams(loadUrlParams);
                 return false;
             }
             openUrlInOtherWindowApi31(
                     sourceActivity,
-                    intent,
+                    createIntent.get(),
                     /* windowId= */ INVALID_WINDOW_ID,
                     isTargetIncognitoWindow);
             return true;
@@ -540,23 +558,39 @@
             assert lastAccessedWindowId != INVALID_WINDOW_ID
                     : "Last accessed window id for the target instance type should be valid.";
             openUrlInOtherWindowApi31(
-                    sourceActivity, intent, lastAccessedWindowId, isTargetIncognitoWindow);
+                    sourceActivity,
+                    createIntent.get(),
+                    lastAccessedWindowId,
+                    isTargetIncognitoWindow);
             return true;
         }
 
         @StringRes int title = R.string.contextmenu_open_in_other_window;
         multiInstanceManager.showTargetSelectorDialog(
-                (instanceInfo) ->
-                        openUrlInOtherWindowApi31(
-                                sourceActivity,
-                                intent,
-                                instanceInfo.instanceId,
-                                /* isIncognitoWindow= */ false),
+                (instanceInfo) -> {
+                    if (instanceInfo == null) {
+                        destroyAdditionalNavigationParams(loadUrlParams);
+                        return;
+                    }
+                    openUrlInOtherWindowApi31(
+                            sourceActivity,
+                            createIntent.get(),
+                            instanceInfo.instanceId,
+                            /* isIncognitoWindow= */ false);
+                },
                 targetInstanceType,
                 title);
         return true;
     }
 
+    private static void destroyAdditionalNavigationParams(LoadUrlParams loadUrlParams) {
+        var params = loadUrlParams.getAdditionalNavigationParams();
+        if (params != null) {
+            params.destroy();
+            loadUrlParams.setAdditionalNavigationParams(null);
+        }
+    }
+
     private void moveTabsToOtherWindowPreApi31(List<Tab> tabs) {
         if (tabs.isEmpty()) return;
         Activity sourceActivity = TabUtils.getActivity(tabs.get(0));
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/tab/TabContextMenuItemDelegate.java b/chrome/android/java/src/org/chromium/chrome/browser/tab/TabContextMenuItemDelegate.java
index fe4763a3..eb4900be 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/tab/TabContextMenuItemDelegate.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/tab/TabContextMenuItemDelegate.java
@@ -324,6 +324,8 @@
         if (!isIncognito) {
             loadUrlParams.setReferrer(referrer);
             loadUrlParams.setAdditionalNavigationParams(additionalNavigationParams);
+        } else if (additionalNavigationParams != null) {
+            additionalNavigationParams.destroy();
         }
         MultiInstanceOrchestratorFactory.getInstance()
                 .openUrlInOtherWindow(
@@ -481,6 +483,9 @@
             @Nullable AdditionalNavigationParams additionalNavigationParams) {
         EphemeralTabCoordinator ephemeralTabCoordinator = mEphemeralTabCoordinatorSupplier.get();
         if (ephemeralTabCoordinator == null) {
+            if (additionalNavigationParams != null) {
+                additionalNavigationParams.destroy();
+            }
             return;
         }
         var initiatorOrigin = getWebContents().getMainFrame().getLastCommittedOrigin();
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/contextmenu/ChromeContextMenuPopulatorTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/contextmenu/ChromeContextMenuPopulatorTest.java
index 77003ad..efce489 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/contextmenu/ChromeContextMenuPopulatorTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/contextmenu/ChromeContextMenuPopulatorTest.java
@@ -3554,7 +3554,7 @@
                         params.getUrl(),
                         params.getReferrer(),
                         /* navigateToTab= */ false,
-                        params.getAdditionalNavigationParams());
+                        params.takeAdditionalNavigationParams());
     }
 
     @Test
@@ -3567,7 +3567,7 @@
                 .onOpenInNewTabInGroup(
                         params.getUrl(),
                         params.getReferrer(),
-                        params.getAdditionalNavigationParams());
+                        params.takeAdditionalNavigationParams());
     }
 
     @Test
@@ -3601,7 +3601,7 @@
                         params.getReferrer(),
                         /* isIncognito= */ false,
                         /* preferNew= */ false,
-                        params.getAdditionalNavigationParams());
+                        params.takeAdditionalNavigationParams());
     }
 
     @Test
@@ -3617,7 +3617,7 @@
                         params.getReferrer(),
                         /* isIncognito= */ false,
                         /* preferNew= */ true,
-                        params.getAdditionalNavigationParams());
+                        params.takeAdditionalNavigationParams());
     }
 
     @Test
@@ -3630,7 +3630,7 @@
                 .onOpenInEphemeralTab(
                         params.getUrl(),
                         params.getLinkText(),
-                        params.getAdditionalNavigationParams());
+                        params.takeAdditionalNavigationParams());
     }
 
     @Test
@@ -3643,7 +3643,7 @@
                 .onOpenInEphemeralTab(
                         params.getSrcUrl(),
                         params.getTitleText(),
... (file diff truncated)
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.