Chrome · Navigation › iOS
CVE-2026-113268
Memory Corruption in Navigation (iOS)
Overview
Low
Severity
—
CVSS
No
Exploited ITW
Fixed
Fix Status
Changed Functions
| Function | Change | Notes |
|---|---|---|
TEST_Pios/web/navigation/navigation_manager_impl_unittest.mm |
modified |
Files Changed
ios/web/navigation/navigation_item_impl.hios/web/navigation/navigation_manager_impl.mmios/web/navigation/navigation_item_impl.mmios/web/navigation/navigation_manager_impl_unittest.mmios/web/navigation/navigation_item_impl_unittest.mm
Patch
From 1fea4f84760555ac0766eb7dbaca357f0c370ae1 Mon Sep 17 00:00:00 2001 From: Gauthier Ambard <[email protected]> Date: Tue, 22 Sep 2026 06:36:42 -0700 Subject: [PATCH] [ios] Prevent Referrer-Policy bypass in synthesized NavigationItem NavigationManagerImpl::WKWebViewCache lazily synthesizes missing NavigationItem entries using the previous item's URL and ReferrerPolicyAlways. When a request is replayed (e.g., via "Request Desktop Site" or web view recreation), the unscrubbed full URL is sent in the Referer header, bypassing the source site's declared Referrer-Policy (e.g., no-referrer, same-origin) as well as HTTPS-to-HTTP downgrade protections. Additionally, NavigationItemImpl::RestoreStateFromItem() previously omitted the persisted referrer_, dropping the saved policy during tab restoration. This change fixes the issue by: 1. Restoring referrer_ in NavigationItemImpl::RestoreStateFromItem() when the URLs match, preserving the original Referrer-Policy persisted in session storage across tab restorations. 2. Changing the synthesized referrer policy in WKWebViewCache::GetNavigationItemImplAtIndex() from ReferrerPolicyAlways to ReferrerPolicyStrictOriginWhenCrossOrigin, aligning with W3C/WebKit defaults and stripping paths and query parameters on cross-origin requests. 3. Adding unit test coverage in NavigationItemTest.RestoreState and NavigationManagerTest to verify referrer restoration, clearing on empty referrers, and strict-origin synthesized policy. Bug: 553661952 Change-Id: I12d8531dae669a8fd7fd7ab591c6716a6d3ef8af Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/8397807 Reviewed-by: Mike Dougherty <[email protected]> Commit-Queue: Gauthier Ambard <[email protected]> Cr-Commit-Position: refs/heads/main@{#1702549} --- diff --git a/ios/web/navigation/navigation_item_impl.h b/ios/web/navigation/navigation_item_impl.h index 9d3d948d..817221c6 100644 --- a/ios/web/navigation/navigation_item_impl.h +++ b/ios/web/navigation/navigation_item_impl.h @@ -133,7 +133,9 @@ void SetUntrusted(); bool IsUntrusted(); - // Restores the state of the `other` navigation item in this item. + // Restores the state (virtual URL, security scoped file resource, and + // referrer) of the `other` navigation item in this item if their URLs match. + // Also restores the user agent type regardless of URL match. void RestoreStateFromItem(NavigationItem* other); #ifndef NDEBUG diff --git a/ios/web/navigation/navigation_item_impl.mm b/ios/web/navigation/navigation_item_impl.mm index 5259de7..172e5a4a 100644 --- a/ios/web/navigation/navigation_item_impl.mm +++ b/ios/web/navigation/navigation_item_impl.mm @@ -382,15 +382,17 @@ void NavigationItemImpl::RestoreStateFromItem(NavigationItem* other) { // Restore the UserAgent type in any case, as if the URLs are different it - // might mean that `this` is a next navigation. The page display state and the - // virtual URL only make sense if it is the same item. The other headers might - // not make sense after creating a new navigation to the page. + // might mean that `this` is a next navigation. The page display state, the + // virtual URL, the security scoped file resource, and the referrer only make + // sense if it is the same item. Other headers might not make sense after + // creating a new navigation to the page. if (other->GetUserAgentType() != UserAgentType::NONE) { SetUserAgentType(other->GetUserAgentType()); } if (url_ == other->GetURL()) { SetVirtualURL(other->GetVirtualURL()); SetSecurityScopedFileResource(other->GetSecurityScopedFileResource()); + SetReferrer(other->GetReferrer()); } } diff --git a/ios/web/navigation/navigation_manager_impl.mm b/ios/web/navigation/navigation_manager_impl.mm index e761446..a942a4c 100644 --- a/ios/web/navigation/navigation_manager_impl.mm +++ b/ios/web/navigation/navigation_manager_impl.mm @@ -1579,9 +1579,10 @@ std::unique_ptr<web::NavigationItemImpl> new_item = navigation_manager_->CreateNavigationItemWithRewriters( net::GURLWithNSURL(wk_item.URL), - (prev_wk_item ? web::Referrer(net::GURLWithNSURL(prev_wk_item.URL), - web::ReferrerPolicyAlways) - : web::Referrer()), + (prev_wk_item + ? web::Referrer(net::GURLWithNSURL(prev_wk_item.URL), + web::ReferrerPolicyStrictOriginWhenCrossOrigin) + : web::Referrer()), ui::PageTransition::PAGE_TRANSITION_LINK, NavigationInitiationType::RENDERER_INITIATED, HttpsUpgradeType::kNone, // Not using GetLastCommittedItem()->GetURL() in case the last diff --git a/ios/web/navigation/navigation_item_impl_unittest.mm b/ios/web/navigation/navigation_item_impl_unittest.mm index f1dce29..bdd7a97 100644 --- a/ios/web/navigation/navigation_item_impl_unittest.mm +++ b/ios/web/navigation/navigation_item_impl_unittest.mm @@ -19,6 +19,7 @@ namespace { const char kItemURLString[] = "http://init.test"; +constexpr std::string_view kReferrerURLString = "http://referrer.url"; static NSString* const kHTTPHeaderKey1 = @"key1"; static NSString* const kHTTPHeaderKey2 = @"key2"; static NSString* const kHTTPHeaderValue1 = @"value1"; @@ -213,6 +214,8 @@ other_item.SetVirtualURL(GURL("www.virtual.com")); NSData* data = [@"data" dataUsingEncoding:NSUTF8StringEncoding]; other_item.SetSecurityScopedFileResource(data); + other_item.SetReferrer( + Referrer(GURL(kReferrerURLString), ReferrerPolicyDefault)); ASSERT_NE(other_item.GetURL(), item_->GetURL()); @@ -222,12 +225,15 @@ EXPECT_NE(other_item.GetVirtualURL(), item_->GetVirtualURL()); EXPECT_NE(other_item.GetSecurityScopedFileResource(), item_->GetSecurityScopedFileResource()); + EXPECT_NE(other_item.GetReferrer(), item_->GetReferrer()); NavigationItemImpl other_item2; other_item2.SetUserAgentType(UserAgentType::DESKTOP); other_item2.SetURL(item_->GetURL()); other_item2.SetVirtualURL(GURL("www.virtual.com")); other_item2.SetSecurityScopedFileResource(data); + other_item2.SetReferrer( + Referrer(GURL(kReferrerURLString), ReferrerPolicyDefault)); // Same URL, everything is restored. item_->RestoreStateFromItem(&other_item2); @@ -235,6 +241,15 @@ EXPECT_EQ(other_item2.GetVirtualURL(), item_->GetVirtualURL()); EXPECT_EQ(other_item2.GetSecurityScopedFileResource(), item_->GetSecurityScopedFileResource()); + EXPECT_EQ(other_item2.GetReferrer(), item_->GetReferrer()); + + // Restoring an item with an empty referrer correctly updates the referrer. + NavigationItemImpl other_item3; + other_item3.SetURL(item_->GetURL()); + ASSERT_TRUE(other_item3.GetReferrer().url.is_empty()); + item_->RestoreStateFromItem(&other_item3); + EXPECT_EQ(other_item3.GetReferrer(), item_->GetReferrer()); + EXPECT_TRUE(item_->GetReferrer().url.is_empty()); } // Tests that NavigationItemImpl round trip correctly when serialized to proto. diff --git a/ios/web/navigation/navigation_manager_impl_unittest.mm b/ios/web/navigation/navigation_manager_impl_unittest.mm index 09a39514..2853f3a 100644 --- a/ios/web/navigation/navigation_manager_impl_unittest.mm +++ b/ios/web/navigation/navigation_manager_impl_unittest.mm @@ -2136,6 +2136,22 @@ EXPECT_EQ(GURL("http://www.url.com/0"), item->GetURL()); } +// Tests that a lazily created NavigationItemImpl uses +// ReferrerPolicyStrictOriginWhenCrossOrigin with the previous entry's URL. +TEST_P(NavigationManagerTest, + GetNavigationItemImplAtIndex_SynthesizedReferrerPolicy) { + [mock_wk_list_ setCurrentURL:@"http://www.url.com/1" + backListURLs:@[ @"http://www.url.com/0" ] + forwardListURLs:nil]; + + NavigationItem* item = navigation_manager()->GetNavigationItemImplAtIndex(1); + ASSERT_TRUE(item); + EXPECT_EQ(GURL("http://www.url.com/1"), item->GetURL()); + EXPECT_EQ(GURL("http://www.url.com/0"), item->GetReferrer().url); + EXPECT_EQ(web::ReferrerPolicyStrictOriginWhenCrossOrigin, + item->GetReferrer().policy); +} + TEST_P(NavigationManagerTest, UpdateCurrentItemForReplaceState) { navigation_manager()->AddPendingItem( GURL("http://www.url.com/0"),
Loading diff…
Regression Test / PoC
shipped with the fix
diff --git a/ios/web/navigation/navigation_item_impl_unittest.mm b/ios/web/navigation/navigation_item_impl_unittest.mm
index f1dce29..bdd7a97 100644
--- a/ios/web/navigation/navigation_item_impl_unittest.mm
+++ b/ios/web/navigation/navigation_item_impl_unittest.mm
@@ -19,6 +19,7 @@
namespace {
const char kItemURLString[] = "http://init.test";
+constexpr std::string_view kReferrerURLString = "http://referrer.url";
static NSString* const kHTTPHeaderKey1 = @"key1";
static NSString* const kHTTPHeaderKey2 = @"key2";
static NSString* const kHTTPHeaderValue1 = @"value1";
@@ -213,6 +214,8 @@
other_item.SetVirtualURL(GURL("www.virtual.com"));
NSData* data = [@"data" dataUsingEncoding:NSUTF8StringEncoding];
other_item.SetSecurityScopedFileResource(data);
+ other_item.SetReferrer(
+ Referrer(GURL(kReferrerURLString), ReferrerPolicyDefault));
ASSERT_NE(other_item.GetURL(), item_->GetURL());
@@ -222,12 +225,15 @@
EXPECT_NE(other_item.GetVirtualURL(), item_->GetVirtualURL());
EXPECT_NE(other_item.GetSecurityScopedFileResource(),
item_->GetSecurityScopedFileResource());
+ EXPECT_NE(other_item.GetReferrer(), item_->GetReferrer());
NavigationItemImpl other_item2;
other_item2.SetUserAgentType(UserAgentType::DESKTOP);
other_item2.SetURL(item_->GetURL());
other_item2.SetVirtualURL(GURL("www.virtual.com"));
other_item2.SetSecurityScopedFileResource(data);
+ other_item2.SetReferrer(
+ Referrer(GURL(kReferrerURLString), ReferrerPolicyDefault));
// Same URL, everything is restored.
item_->RestoreStateFromItem(&other_item2);
@@ -235,6 +241,15 @@
EXPECT_EQ(other_item2.GetVirtualURL(), item_->GetVirtualURL());
EXPECT_EQ(other_item2.GetSecurityScopedFileResource(),
item_->GetSecurityScopedFileResource());
+ EXPECT_EQ(other_item2.GetReferrer(), item_->GetReferrer());
+
+ // Restoring an item with an empty referrer correctly updates the referrer.
+ NavigationItemImpl other_item3;
+ other_item3.SetURL(item_->GetURL());
+ ASSERT_TRUE(other_item3.GetReferrer().url.is_empty());
+ item_->RestoreStateFromItem(&other_item3);
+ EXPECT_EQ(other_item3.GetReferrer(), item_->GetReferrer());
+ EXPECT_TRUE(item_->GetReferrer().url.is_empty());
}
// Tests that NavigationItemImpl round trip correctly when serialized to proto.
diff --git a/ios/web/navigation/navigation_manager_impl_unittest.mm b/ios/web/navigation/navigation_manager_impl_unittest.mm
index 09a39514..2853f3a 100644
--- a/ios/web/navigation/navigation_manager_impl_unittest.mm
+++ b/ios/web/navigation/navigation_manager_impl_unittest.mm
@@ -2136,6 +2136,22 @@
EXPECT_EQ(GURL("http://www.url.com/0"), item->GetURL());
}
+// Tests that a lazily created NavigationItemImpl uses
+// ReferrerPolicyStrictOriginWhenCrossOrigin with the previous entry's URL.
+TEST_P(NavigationManagerTest,
+ GetNavigationItemImplAtIndex_SynthesizedReferrerPolicy) {
+ [mock_wk_list_ setCurrentURL:@"http://www.url.com/1"
+ backListURLs:@[ @"http://www.url.com/0" ]
+ forwardListURLs:nil];
+
+ NavigationItem* item = navigation_manager()->GetNavigationItemImplAtIndex(1);
+ ASSERT_TRUE(item);
+ EXPECT_EQ(GURL("http://www.url.com/1"), item->GetURL());
+ EXPECT_EQ(GURL("http://www.url.com/0"), item->GetReferrer().url);
+ EXPECT_EQ(web::ReferrerPolicyStrictOriginWhenCrossOrigin,
+ item->GetReferrer().policy);
+}
+
TEST_P(NavigationManagerTest, UpdateCurrentItemForReplaceState) {
navigation_manager()->AddPendingItem(
GURL("http://www.url.com/0"),
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