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 <gambard@chromium.org>
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 <michaeldo@chromium.org>
Commit-Queue: Gauthier Ambard <gambard@chromium.org>
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