Chrome · Mobile
CVE-2026-85044
Logic Error in Mobile
Overview
Medium
Severity
—
CVSS
No
Exploited ITW
Fixed
Fix Status
Changed Functions
| Function | Change | Notes |
|---|---|---|
ifandroid_webview/java/src/org/chromium/android_webview/contextmenu/AwContextMenuCoordinator.java |
modified | |
ifchrome/android/java/src/org/chromium/chrome/browser/contextmenu/ContextMenuHelper.java |
modified |
Files Changed
android_webview/browser/aw_context_menu_helper.ccandroid_webview/java/src/org/chromium/android_webview/contextmenu/AwContextMenuCoordinator.javaandroid_webview/java/src/org/chromium/android_webview/contextmenu/AwContextMenuHelper.javachrome/android/java/src/org/chromium/chrome/browser/contextmenu/ContextMenuHelper.javachrome/android/javatests/src/org/chromium/chrome/browser/tabmodel/ContextMenuLoadUrlParamsTest.javachrome/browser/ui/android/context_menu_helper.cc
Patch
From a9de56747eeb6b737003a1c4d1ef5bbca617ba69 Mon Sep 17 00:00:00 2001
From: Jenna Himawan <jhimawan@google.com>
Date: Mon, 13 Jul 2026 12:45:59 -0700
Subject: [PATCH] Plumb InitiatorNavigationState for Android context menu navigations
Bypass-Check-License: Moving files, not creating new files
Bug: 517482830
Change-Id: I7cfa48e6d171461c5ad49afd3b610ce94d575962
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/8001092
Commit-Queue: Jenna Himawan <jhimawan@google.com>
Reviewed-by: Bo Liu <boliu@chromium.org>
Reviewed-by: Calder Kitagawa <ckitagawa@chromium.org>
Cr-Commit-Position: refs/heads/main@{#1661311}
---
diff --git a/android_webview/browser/aw_context_menu_helper.cc b/android_webview/browser/aw_context_menu_helper.cc
index a8c4c0d..e8cc7148 100644
--- a/android_webview/browser/aw_context_menu_helper.cc
+++ b/android_webview/browser/aw_context_menu_helper.cc
@@ -6,7 +6,7 @@
#include "android_webview/browser_jni_headers/AwContextMenuHelper_jni.h"
#include "components/embedder_support/android/contextmenu/context_menu_builder.h"
-#include "content/public/browser/render_process_host.h"
+#include "content/public/browser/render_frame_host.h"
#include "ui/android/view_android.h"
using base::android::JavaRef;
@@ -33,9 +33,8 @@
gfx::NativeView view = GetWebContents().GetNativeView();
Java_AwContextMenuHelper_showContextMenu(
env, java_obj_,
- context_menu::BuildJavaContextMenuParams(
- params, nullptr, render_frame_host.GetProcess()->GetID(),
- render_frame_host.GetFrameToken().value()),
+ context_menu::BuildJavaContextMenuParams(params, nullptr,
+ render_frame_host),
view->GetContainerView());
}
diff --git a/android_webview/java/src/org/chromium/android_webview/contextmenu/AwContextMenuCoordinator.java b/android_webview/java/src/org/chromium/android_webview/contextmenu/AwContextMenuCoordinator.java
index c3520c0e..adef660 100644
--- a/android_webview/java/src/org/chromium/android_webview/contextmenu/AwContextMenuCoordinator.java
+++ b/android_webview/java/src/org/chromium/android_webview/contextmenu/AwContextMenuCoordinator.java
@@ -68,6 +68,7 @@
private WebContentsObserver mWebContentsObserver;
private final boolean mIsDragDropEnabled;
private final boolean mUsePopupWindow;
+ private boolean mDismissed;
AwContextMenuCoordinator(
WindowAndroid windowAndroid,
@@ -97,6 +98,9 @@
}
public void dismiss() {
+ if (mDismissed) return;
+ mDismissed = true;
+
if (mWebContentsObserver != null) {
mWebContentsObserver.observe(null);
}
@@ -115,6 +119,8 @@
mCurrentPopulator.onMenuClosed();
mCurrentPopulator = null;
}
+
+ mParams.destroy();
}
void displayMenu() {
diff --git a/android_webview/java/src/org/chromium/android_webview/contextmenu/AwContextMenuHelper.java b/android_webview/java/src/org/chromium/android_webview/contextmenu/AwContextMenuHelper.java
index c4c61e2..591f796 100644
--- a/android_webview/java/src/org/chromium/android_webview/contextmenu/AwContextMenuHelper.java
+++ b/android_webview/java/src/org/chromium/android_webview/contextmenu/AwContextMenuHelper.java
@@ -64,6 +64,7 @@
|| windowAndroid.getActivity().get() == null
|| windowAndroid.getContext().get() == null) {
Log.w(TAG, "Could not create context menu");
+ params.destroy();
return false;
}
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/contextmenu/ContextMenuHelper.java b/chrome/android/java/src/org/chromium/chrome/browser/contextmenu/ContextMenuHelper.java
index 0e42eb0..84bb744d 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/contextmenu/ContextMenuHelper.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/contextmenu/ContextMenuHelper.java
@@ -85,6 +85,8 @@
dismissContextMenu();
if (mCurrentNativeDelegate != null) mCurrentNativeDelegate.destroy();
if (mPopulatorFactory != null) mPopulatorFactory.onDestroy();
+ destroyContextMenuParams(mCurrentContextMenuParams);
+ mCurrentContextMenuParams = null;
var removedValue = sContextMenuHelperMap.remove(mNativeContextMenuHelper);
assert removedValue == this;
mNativeContextMenuHelper = 0;
@@ -94,6 +96,8 @@
private void setPopulatorFactory(ContextMenuPopulatorFactory populatorFactory) {
dismissContextMenu();
if (mCurrentNativeDelegate != null) mCurrentNativeDelegate.destroy();
+ destroyContextMenuParams(mCurrentContextMenuParams);
+ mCurrentContextMenuParams = null;
mCurrentPopulator = null;
if (mPopulatorFactory != null) mPopulatorFactory.onDestroy();
mPopulatorFactory = populatorFactory;
@@ -101,6 +105,7 @@
/**
* Starts showing a context menu for {@code view} based on {@code params}.
+ *
* @param params The {@link ContextMenuParams} that indicate what menu items to show.
* @param renderFrameHost {@link RenderFrameHost} to get the encoded images from.
* @param view container view for the menu.
@@ -112,7 +117,10 @@
RenderFrameHost renderFrameHost,
View view,
float topContentOffsetPx) {
- if (params.isFile()) return;
+ if (params.isFile()) {
+ destroyContextMenuParams(params);
+ return;
+ }
final WindowAndroid windowAndroid = mWebContents.getTopLevelNativeWindow();
@@ -124,6 +132,7 @@
|| mPopulatorFactory == null
|| !mPopulatorFactory.isEnabled()
|| mCurrentContextMenu != null) {
+ destroyContextMenuParams(params);
return;
}
@@ -162,6 +171,8 @@
// Has no effect if the classification already succeeded.
mChipDelegate.onMenuClosed();
}
+ destroyContextMenuParams(mCurrentContextMenuParams);
+ mCurrentContextMenuParams = null;
if (mNativeContextMenuHelper == 0) return;
ContextMenuHelperJni.get().onContextMenuClosed(mNativeContextMenuHelper);
};
@@ -263,6 +274,12 @@
return assertNonNull(sContextMenuHelperMap.get(nativeContextMenuHelper));
}
+ private static void destroyContextMenuParams(@Nullable ContextMenuParams params) {
+ if (params != null) {
+ params.destroy();
+ }
+ }
+
@NativeMethods
interface Natives {
void onContextMenuClosed(long nativeContextMenuHelper);
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/tabmodel/ContextMenuLoadUrlParamsTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/tabmodel/ContextMenuLoadUrlParamsTest.java
index 03cb17b..c812319 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/tabmodel/ContextMenuLoadUrlParamsTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/tabmodel/ContextMenuLoadUrlParamsTest.java
@@ -9,7 +9,6 @@
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
-import static org.junit.Assert.assertTrue;
import android.app.Activity;
import android.content.Context;
@@ -195,7 +194,6 @@
AdditionalNavigationParams navigationParams =
sOpenNewTabLoadUrlParams.getAdditionalNavigationParams();
assertNotNull(navigationParams);
- assertTrue(navigationParams.getInitiatorProcessId().isValid());
}
/**
diff --git a/chrome/browser/ui/android/context_menu_helper.cc b/chrome/browser/ui/android/context_menu_helper.cc
index 2d50e5fc..2205a9b 100644
--- a/chrome/browser/ui/android/context_menu_helper.cc
+++ b/chrome/browser/ui/android/context_menu_helper.cc
@@ -16,7 +16,6 @@
#include "components/embedder_support/android/contextmenu/context_menu_builder.h"
#include "content/public/browser/context_menu_params.h"
#include "content/public/browser/render_frame_host.h"
-#include "content/public/browser/render_process_host.h"
#include "extensions/buildflags/buildflags.h"
#include "third_party/blink/public/mojom/context_menu/context_menu.mojom.h"
#include "ui/android/view_android.h"
@@ -72,10 +71,8 @@
Java_ContextMenuHelper_showContextMenu(
env, GetJavaObject(env),
- context_menu::BuildJavaContextMenuParams(
- context_menu_params_, model_ptr,
- render_frame_host.GetProcess()->GetID(),
- render_frame_host.GetFrameToken().value()),
+ context_menu::BuildJavaContextMenuParams(context_menu_params_, model_ptr,
+ render_frame_host),
render_frame_host.GetJavaRenderFrameHost(), view->GetContainerView(),
view->content_offset() * view->GetDipScale());
}
Loading diff…
Regression Test / PoC
shipped with the fix
diff --git a/content/browser/android/additional_navigation_params_browsertest.cc b/content/browser/android/additional_navigation_params_browsertest.cc
new file mode 100644
index 0000000..afad9807
--- /dev/null
+++ b/content/browser/android/additional_navigation_params_browsertest.cc
@@ -0,0 +1,98 @@
+// Copyright 2026 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "content/browser/android/additional_navigation_params.h"
+
+#include "base/android/jni_android.h"
+#include "base/functional/callback_helpers.h"
+#include "base/run_loop.h"
+#include "base/test/run_until.h"
+#include "content/browser/renderer_host/render_frame_host_impl.h"
+#include "content/browser/storage_partition_impl.h"
+#include "content/public/browser/android/additional_navigation_params_android.h"
+#include "content/public/browser/initiator_navigation_state.h"
+#include "content/public/browser/render_process_host.h"
+#include "content/public/common/child_process_id.h"
+#include "content/public/test/browser_test.h"
+#include "content/public/test/browser_test_utils.h"
+#include "content/public/test/content_browser_test.h"
+#include "content/public/test/content_browser_test_utils.h"
+#include "content/public/test/test_utils.h"
+#include "content/shell/browser/shell.h"
+#include "net/dns/mock_host_resolver.h"
+#include "third_party/blink/public/common/tokens/tokens.h"
+#include "url/gurl.h"
+
+namespace content {
+
+class AdditionalNavigationParamsBrowserTest : public ContentBrowserTest {
+ public:
+ void SetUpOnMainThread() override {
+ host_resolver()->AddRule("*", "127.0.0.1");
+ ASSERT_TRUE(embedded_test_server()->Start());
+ ContentBrowserTest::SetUpOnMainThread();
+ }
+
+ WebContents* web_contents() { return shell()->web_contents(); }
+};
+
+// AdditionalNavigationParams carries the initiator frame token across an
+// arbitrary delay before it is consumed by NavigationController::LoadUrl. The
+// initiator frame may be detached during that delay, so creating the params
+// must keep the frame's PolicyContainerHost reachable for the lifetime of the
+// returned keep-alive handle.
+IN_PROC_BROWSER_TEST_F(AdditionalNavigationParamsBrowserTest,
+ InitiatorPolicyContainerHostKeptAliveAfterFrameDetach) {
+ ASSERT_TRUE(NavigateToURL(
+ shell(), embedded_test_server()->GetURL(
+ "a.com", "/cross_site_iframe_factory.html?a.com(b.com)")));
+
+ RenderFrameHostImpl* child_rfh =
+ static_cast<RenderFrameHostImpl*>(ChildFrameAt(web_contents(), 0));
+ ASSERT_TRUE(child_rfh);
+
+ const blink::LocalFrameToken child_token = child_rfh->GetFrameToken();
+ const ChildProcessId child_process_id = child_rfh->GetProcess()->GetID();
+
+ base::android::ScopedJavaLocalRef<jobject> java_params =
+ CreateJavaAdditionalNavigationParams(
+ base::android::AttachCurrentThread(), *child_rfh,
+ /*attribution_src_token=*/std::nullopt);
+ ASSERT_TRUE(java_params);
+
+ // Detach the initiator frame while still holding the params.
+ RenderFrameDeletedObserver deleted_observer(child_rfh);
+ ASSERT_TRUE(
+ ExecJs(web_contents(), "document.querySelector('iframe').remove();"));
+ ASSERT_TRUE(deleted_observer.WaitUntilDeleted());
+ ASSERT_FALSE(
+ RenderFrameHostImpl::FromFrameToken(child_process_id, child_token));
+
+ // Verify round-trip extraction of frame token and process ID even after frame
+ // detachment.
+ std::optional<blink::LocalFrameToken> extracted_frame_token =
+ GetInitiatorFrameTokenFromJavaAdditionalNavigationParams(
+ base::android::AttachCurrentThread(), java_params);
+ EXPECT_TRUE(extracted_frame_token.has_value());
+ EXPECT_EQ(extracted_frame_token.value(), child_token);
+
+ ChildProcessId extracted_process_id =
+ GetInitiatorProcessIdFromJavaAdditionalNavigationParams(
+ base::android::AttachCurrentThread(), java_params);
+ EXPECT_FALSE(extracted_process_id.is_null());
+ EXPECT_EQ(extracted_process_id, child_process_id);
+
+ // We can get the state from the Java object.
+ scoped_refptr<InitiatorNavigationState> taken_state =
+ TakeNativeStateFromJavaAdditionalNavigationParams(
+ base::android::AttachCurrentThread(), java_params);
+ EXPECT_TRUE(taken_state);
+
+ // Clean up the Java object to satisfy LifetimeAssert and release native
+ // state.
+ DestroyJavaAdditionalNavigationParams(base::android::AttachCurrentThread(),
+ java_params);
+}
+
+} // namespace content
diff --git a/content/test/BUILD.gn b/content/test/BUILD.gn
index a2b70da..6dfdf63 100644
--- a/content/test/BUILD.gn
+++ b/content/test/BUILD.gn
@@ -2299,6 +2299,7 @@
sources += [
"../browser/accessibility/android_granularity_movement_browsertest.cc",
"../browser/accessibility/ime_accessibility_browsertest.cc",
+ "../browser/android/additional_navigation_params_browsertest.cc",
"../browser/android/nfc_host_browsertest.cc",
"../browser/android/render_widget_host_connector_browsertest.cc",
"../browser/android/render_widget_host_connector_browsertest.h",
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