Chrome · Cookies
CVE-2026-113053
UAF in Cookies
Overview
Low
Severity
—
CVSS
No
Exploited ITW
Fixed
Fix Status
Changed Functions
| Function | Change | Notes |
|---|---|---|
ifchrome/browser/ui/web_applications/web_app_launch_process.cc |
modified |
Files Changed
chrome/browser/ui/web_applications/web_share_target_browsertest.ccchrome/android/junit/src/org/chromium/chrome/browser/customtabs/content/CustomTabActivityUrlLoadingTest.javachrome/android/java/src/org/chromium/chrome/browser/customtabs/content/CustomTabActivityNavigationController.javachrome/browser/ui/web_applications/web_app_launch_process.cc
Patch
From d2bc6331aa0de58aa458bf92c9b13f81255b43b3 Mon Sep 17 00:00:00 2001
From: Glenn Hartmann <hartmanng@chromium.org>
Date: Wed, 23 Sep 2026 07:39:34 -0700
Subject: [PATCH] Preserve the Web Share Target initiator origin through launch
crrev.com/c/8415706 attributed Web Share Target navigations to the
sharing app, so that an in-scope action that redirects cross-site no
longer carries the target site's SameSite=Strict/Lax cookies. Two of
the three initiator origins it set are overwritten further down the
stack, so the fix only took effect on the WebAPK/TWA POST path.
WebAppLaunchProcess::Run() assigns params_->initiator_origin over
whatever NavigateParamsForShareTarget() computed. Share launches carry
no initiator, so the app attribution is reset to std::nullopt and the
navigation is again treated as browser-initiated, leaving the original
SameSite/CSRF bypass in place on desktop and ChromeOS. Only overwrite
the initiator origin when the launch actually has one. Every other
launch builds its NavigateParams fresh with std::nullopt, so this is a
no-op for them.
CustomTabActivityNavigationController::navigate() replaces the
initiator with an opaque origin because the sender of an intent can't
be trusted. That is the right default, but it also discards the
verified origin TwaSharingController sets for GET share targets, which
drops Strict cookies on the initial navigation and breaks signed-in
share targets -- the behavior change the earlier CL set out to avoid.
This direction fails closed, so it is not a cookie leak. Only fall back
to an opaque origin when the caller has not already attributed the
navigation; TwaSharingController is the only caller that sets one.
Each new test was confirmed to fail with its fix reverted.
TAG=agy
CONV=7915d2d9-4027-4cad-827e-e9232a49551b
Bug: 40061291
Fixed: 562714408
Change-Id: I302ab55125dbe47bf6bc4728d22a3e3d6a6a6964
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/8429148
Commit-Queue: Glenn Hartmann <hartmanng@chromium.org>
Reviewed-by: Yaron Friedman <yfriedman@chromium.org>
Cr-Commit-Position: refs/heads/main@{#1703409}
---
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/customtabs/content/CustomTabActivityNavigationController.java b/chrome/android/java/src/org/chromium/chrome/browser/customtabs/content/CustomTabActivityNavigationController.java
index ce4570cd..0102b2fb 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/customtabs/content/CustomTabActivityNavigationController.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/customtabs/content/CustomTabActivityNavigationController.java
@@ -252,8 +252,13 @@
assertNonNull(mIntentDataProvider.getIntent()), transition));
// The sender of an intent can't be trusted, so we navigate from an opaque Origin to
- // avoid sending same-site cookies.
- params.setInitiatorOrigin(Origin.createOpaqueOrigin());
+ // avoid sending same-site cookies. Callers that have verified who really initiated the
+ // navigation (e.g. TWA share targets, whose action URL is verified to be within the
+ // app's scope) set the initiator themselves; don't clobber that. See
+ // crbug.com/40061291.
+ if (params.getInitiatorOrigin() == null) {
+ params.setInitiatorOrigin(Origin.createOpaqueOrigin());
+ }
// Notifies PreloadingImpl that a navigation to CCT is happening. This is used to calculate
// the recall of CCT prefetch's attempt. Please see
diff --git a/chrome/android/junit/src/org/chromium/chrome/browser/customtabs/content/CustomTabActivityUrlLoadingTest.java b/chrome/android/junit/src/org/chromium/chrome/browser/customtabs/content/CustomTabActivityUrlLoadingTest.java
index 1fbada7..7ee2d02 100644
--- a/chrome/android/junit/src/org/chromium/chrome/browser/customtabs/content/CustomTabActivityUrlLoadingTest.java
+++ b/chrome/android/junit/src/org/chromium/chrome/browser/customtabs/content/CustomTabActivityUrlLoadingTest.java
@@ -4,6 +4,7 @@
package org.chromium.chrome.browser.customtabs.content;
+import static org.junit.Assert.assertEquals;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.argThat;
import static org.mockito.Mockito.clearInvocations;
@@ -181,6 +182,42 @@
verify(env.tabFromFactory).loadUrl(argThat(params -> OTHER_URL.equals(params.getUrl())));
}
+ /**
+ * The sender of an intent can't be trusted, so an unattributed navigation must be made from an
+ * opaque origin rather than inheriting same-site cookies.
+ */
+ @Test
+ public void navigatesFromOpaqueOrigin_IfNoInitiatorOriginSpecified() {
+ Origin opaqueOrigin = mock(Origin.class);
+ Origin.setOpaqueOriginFactoryForTesting(() -> opaqueOrigin);
+ mTabController.setUpInitialTab(null);
+ mTabController.finishNativeInitialization();
+
+ LoadUrlParams params = new LoadUrlParams(OTHER_URL);
+ mNavigationController.navigate(params, new Intent());
+
+ assertEquals(opaqueOrigin, params.getInitiatorOrigin());
+ }
+
+ /**
+ * Regression test for crbug.com/40061291: callers that have verified the real initiator (e.g.
+ * TWA share targets, whose action is verified to be in the app's scope) set it themselves, and
+ * it must survive so that SameSite cookies are evaluated against it.
+ */
+ @Test
+ public void keepsInitiatorOrigin_IfSpecifiedByCaller() {
+ Origin.setOpaqueOriginFactoryForTesting(() -> mock(Origin.class));
+ Origin appOrigin = mock(Origin.class);
+ mTabController.setUpInitialTab(null);
+ mTabController.finishNativeInitialization();
+
+ LoadUrlParams params = new LoadUrlParams(OTHER_URL);
+ params.setInitiatorOrigin(appOrigin);
+ mNavigationController.navigate(params, new Intent());
+
+ assertEquals(appOrigin, params.getInitiatorOrigin());
+ }
+
private CustomTabIntentDataProvider createDataProviderForNewIntent() {
CustomTabIntentDataProvider dataProvider = mock(CustomTabIntentDataProvider.class);
when(dataProvider.getUrlToLoad()).thenReturn(OTHER_URL);
diff --git a/chrome/browser/ui/web_applications/web_app_launch_process.cc b/chrome/browser/ui/web_applications/web_app_launch_process.cc
index e6f2ac1..71834f8 100644
--- a/chrome/browser/ui/web_applications/web_app_launch_process.cc
+++ b/chrome/browser/ui/web_applications/web_app_launch_process.cc
@@ -272,8 +272,8 @@
// In the case of prevent-close, we do not navigate but instead focus the
// existing window.
bool app_wants_focus_existing_without_navigation =
- GetLaunchHandler().NeverNavigateExistingClients() ||
- registrar_->IsPreventCloseEnabled(params_->app_id);
+ GetLaunchHandler().NeverNavigateExistingClients() ||
+ registrar_->IsPreventCloseEnabled(params_->app_id);
if (!open_in_new_window && app_wants_focus_existing_without_navigation) {
auto* tab_helper = WebAppTabHelper::FromWebContents(existing_tab);
if (tab_helper->pending_launch_app_id() == params_->app_id) {
@@ -313,7 +313,15 @@
: NavigateParams(browser, url_to_navigate,
ui::PAGE_TRANSITION_AUTO_BOOKMARK);
nav_params.disposition = navigation_disposition;
- nav_params.initiator_origin = params_->initiator_origin;
+ // Only overwrite the initiator origin when the launch actually carries one.
+ // `NavigateParamsForShareTarget()` attributes share target navigations to the
+ // app itself, and share launches have no `initiator_origin`; assigning it
+ // unconditionally would reset that back to std::nullopt, which makes the
+ // navigation look like a user-typed URL to the network stack. See
+ // crbug.com/40061291.
+ if (params_->initiator_origin.has_value()) {
+ nav_params.initiator_origin = params_->initiator_origin;
+ }
if (params_->initiator_origin.has_value() ||
!params_->referrer_url.is_empty()) {
nav_params.referrer = content::Referrer::SanitizeForRequest(
diff --git a/chrome/browser/ui/web_applications/web_share_target_browsertest.cc b/chrome/browser/ui/web_applications/web_share_target_browsertest.cc
index 4309787..24ea08c 100644
--- a/chrome/browser/ui/web_applications/web_share_target_browsertest.cc
+++ b/chrome/browser/ui/web_applications/web_share_target_browsertest.cc
@@ -42,12 +42,14 @@
#include "content/public/browser/web_contents.h"
#include "content/public/test/browser_test.h"
#include "content/public/test/browser_test_utils.h"
+#include "content/public/test/test_navigation_observer.h"
#include "net/base/filename_util.h"
#include "net/test/embedded_test_server/embedded_test_server.h"
#include "services/network/public/cpp/resource_request_body.h"
#include "storage/browser/file_system/file_system_context.h"
#include "ui/display/types/display_constants.h"
#include "url/gurl.h"
+#include "url/origin.h"
namespace {
@@ -356,4 +358,32 @@
EXPECT_EQ(shared_link, ReadTextContent(web_contents, "link"));
}
+// Regression test for crbug.com/40061291: the navigation to a share target has
+// to be attributed to the app being shared to. The action is verified to be in
+// the app's scope but may redirect anywhere, so an unattributed navigation --
+// which the network stack treats like a user-typed URL -- would let the app
+// make the browser send SameSite=Strict/Lax cookies to an arbitrary cross-site
+// URL.
+IN_PROC_BROWSER_TEST_F(WebShareTargetBrowserTest,
+ ShareTargetNavigationIsAttributedToApp) {
+ ASSERT_TRUE(embedded_test_server()->Start());
+ const GURL app_url =
+ embedded_test_server()->GetURL("/web_share_target/poster.html");
+ const webapps::AppId app_id =
+ web_app::InstallWebAppFromManifest(browser(), app_url);
+
+ content::TestNavigationObserver navigation_observer(share_target_url());
+ navigation_observer.StartWatchingNewWebContents();
+
+ apps::IntentPtr intent = apps_util::MakeShareIntent(
+ /*text=*/"https://example.org/a",
+ /*title=*/"Hyperlink");
+
+ LaunchAppWithIntent(app_id, std::move(intent), share_target_url());
+ navigation_observer.Wait();
+
+ EXPECT_EQ(url::Origin::Create(share_target_url()),
+ navigation_observer.last_initiator_origin());
+}
+
} // namespace web_app
Loading diff…
Regression Test / PoC
shipped with the fix
diff --git a/chrome/browser/ui/web_applications/web_share_target_browsertest.cc b/chrome/browser/ui/web_applications/web_share_target_browsertest.cc
index 4309787..24ea08c 100644
--- a/chrome/browser/ui/web_applications/web_share_target_browsertest.cc
+++ b/chrome/browser/ui/web_applications/web_share_target_browsertest.cc
@@ -42,12 +42,14 @@
#include "content/public/browser/web_contents.h"
#include "content/public/test/browser_test.h"
#include "content/public/test/browser_test_utils.h"
+#include "content/public/test/test_navigation_observer.h"
#include "net/base/filename_util.h"
#include "net/test/embedded_test_server/embedded_test_server.h"
#include "services/network/public/cpp/resource_request_body.h"
#include "storage/browser/file_system/file_system_context.h"
#include "ui/display/types/display_constants.h"
#include "url/gurl.h"
+#include "url/origin.h"
namespace {
@@ -356,4 +358,32 @@
EXPECT_EQ(shared_link, ReadTextContent(web_contents, "link"));
}
+// Regression test for crbug.com/40061291: the navigation to a share target has
+// to be attributed to the app being shared to. The action is verified to be in
+// the app's scope but may redirect anywhere, so an unattributed navigation --
+// which the network stack treats like a user-typed URL -- would let the app
+// make the browser send SameSite=Strict/Lax cookies to an arbitrary cross-site
+// URL.
+IN_PROC_BROWSER_TEST_F(WebShareTargetBrowserTest,
+ ShareTargetNavigationIsAttributedToApp) {
+ ASSERT_TRUE(embedded_test_server()->Start());
+ const GURL app_url =
+ embedded_test_server()->GetURL("/web_share_target/poster.html");
+ const webapps::AppId app_id =
+ web_app::InstallWebAppFromManifest(browser(), app_url);
+
+ content::TestNavigationObserver navigation_observer(share_target_url());
+ navigation_observer.StartWatchingNewWebContents();
+
+ apps::IntentPtr intent = apps_util::MakeShareIntent(
+ /*text=*/"https://example.org/a",
+ /*title=*/"Hyperlink");
+
+ LaunchAppWithIntent(app_id, std::move(intent), share_target_url());
+ navigation_observer.Wait();
+
+ EXPECT_EQ(url::Origin::Create(share_target_url()),
+ navigation_observer.last_initiator_origin());
+}
+
} // namespace web_app
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