39b67e68ebc2aed8f549fdf73622f8f6c4871647 REGRESSION(319079@main): TestWebKitAPI.WKNavigation.PreferredHTTPSPolicyAutomaticHTTPFallbackAfterTerminateProcess (api-test) is a constant fail.
Triage note: handleBackForwardNavigation read only targetItem(), so a reload (which sets reloadItem instead) was treated as Enhanced Security Disabled; fix falls back to reloadItem(), preventing a security-policy downgrade on reload.
Contents
The bug at a glance
This is a security-policy downgrade, not a memory-safety bug: on a reload that traverses the back/forward machinery after a process relaunch, EnhancedSecurityTracking::handleBackForwardNavigation read the prior Enhanced Security state from the wrong history item and defaulted it to Disabled, causing the tracker to go dormant, the HTTPS-fallback state to be discarded, and the connection to be silently upgraded back to https. Impact is a loss of an intended protection state (and thus a defeat of the HTTP-fallback decision) rather than direct code execution, and the trigger requires a fairly specific relaunch-then-reload sequence, which places it in the Medium band. There is no memory corruption, so 5.3 is appropriate.
Enhanced Security tracks per-history-item state so that navigating to an item restores the security posture it was last seen with. handleBackForwardNavigation asked exactly one question — does this navigation have a targetItem()? — and if not, assumed the prior state was Disabled. But a reload doesn’t populate targetItem(); it populates reloadItem(). A regression (319079@main) began routing certain post-relaunch history restores through the back/forward path as reloads, so the code read a null targetItem, concluded Enhanced Security had been off, went dormant, and let the ensuing process swap throw away the HTTP-fallback decision — quietly re-upgrading the load to https. The one-line fix is to also consult reloadItem() before defaulting to Disabled.
Root cause
EnhancedSecurityTracking::handleBackForwardNavigation() derives priorState — the Enhanced Security state the destination history item was last associated with — and uses it to decide whether the tracker stays active or goes dormant. The original code was EnhancedSecurity priorState = navigation.targetItem() ? navigation.targetItem()->enhancedSecurity() : EnhancedSecurity::Disabled;. It only ever looked at targetItem().
For a genuine back/forward navigation, targetItem() names the item being navigated to. For a reload, however, the API::Navigation stores the item in reloadItem(), and targetItem() is null. targetItem() and reloadItem() are mutually exclusive — exactly one is ever set — but both denote ’the item being navigated to’. Reading only targetItem() therefore misclassifies every reload that reaches this function as having no prior item, collapsing priorState to Disabled.
The reason a reload reaches this function at all is regression 319079@main: it normalized an empty document URL to about:blank in NavigationRequester::from (so about:blank inherits its initiator’s policy container). A side effect is that NavigationAction::isEmpty() now returns false for a history restore in a freshly relaunched process, so the triggering action is preserved and the navigation reports NavigationType::BackForward — steering a reload into handleBackForwardNavigation. There, the null targetItem made priorState read as Disabled.
With priorState == Disabled, the tracker takes its ‘go dormant’ branch (when m_activeState != ActivationState::None). The subsequent process swap then discards the HTTP-fallback state that had downgraded the load to http, and the reload is upgraded back to https — the observable failure the bug (and the failing TestWebKitAPI PreferredHTTPSPolicyAutomaticHTTPFallbackAfterTerminateProcess case) captured. The fix computes RefPtr item = navigation.targetItem() ? navigation.targetItem() : navigation.reloadItem(); and reads priorState from that item, defaulting to Disabled only when neither exists. Now the item’s stored Enhanced Security state is honored on reload instead of being silently dropped.
Key code
Fall back to reloadItem() so a reload’s history item is consulted instead of defaulting to Disabled
- EnhancedSecurity priorState = navigation.targetItem() ? navigation.targetItem()->enhancedSecurity() : EnhancedSecurity::Disabled;
+ // targetItem() and reloadItem() are both "the item being navigated to", and only one is ever set.
+ // Reading just targetItem() would treat a reload's missing target as Enhanced Security being off.
+ RefPtr item = navigation.targetItem() ? navigation.targetItem() : navigation.reloadItem();
+ EnhancedSecurity priorState = item ? item->enhancedSecurity() : EnhancedSecurity::Disabled;
if (priorState == EnhancedSecurity::Disabled) {
if (m_activeState != ActivationState::None)
Patch walkthrough
Source/WebKit/UIProcess/EnhancedSecurityTracking.cpp— handleBackForwardNavigation() no longer reads Enhanced Security state from targetItem() alone. It now resolves the destination history item asnavigation.targetItem() ? navigation.targetItem() : navigation.reloadItem()and reads enhancedSecurity() from whichever is set, only falling back to EnhancedSecurity::Disabled when neither exists. A comment documents that targetItem() and reloadItem() both name the item being navigated to and only one is ever populated, so a reload’s null targetItem no longer masquerades as Enhanced Security being off.
Background
Enhanced Security tracking — A UIProcess-side mechanism that records, per navigation/history item, whether an elevated security posture is in effect and restores it across navigations. When it reads the prior state as Disabled it can go dormant and let a process swap reset associated state such as HTTPS-upgrade/fallback decisions.
targetItem() vs reloadItem() — Two accessors on API::Navigation naming the history item being navigated to. A back/forward traversal sets targetItem(); a reload sets reloadItem(). They are mutually exclusive, so code that wants ’the destination item’ must check both.
HTTPS-Only / HTTP fallback — WebKit can attempt https and, on failure, fall back to http, recording that decision so a reload after a process relaunch keeps the working scheme. Losing that state re-upgrades the reload to https, the exact regression the failing PreferredHTTPSPolicyAutomaticHTTPFallbackAfterTerminateProcess API test surfaced.
319079@main normalization — The upstream change that normalized an empty document URL to about:blank in NavigationRequester::from. Its side effect made NavigationAction::isEmpty() false for a post-relaunch history restore, so the navigation reported NavigationType::BackForward and reloads began flowing through handleBackForwardNavigation.
Vulnerability window
- Precondition — A load reaches http via HTTPS-fallback, and Enhanced Security is active with its per-item state recorded on the history item.
- Process relaunch — The web content process terminates and is relaunched; a history restore is initiated to bring back the prior page.
- Misrouting (regression) — Post-319079@main, NavigationAction::isEmpty() is false for the restore, the triggering action is preserved, and the navigation reports NavigationType::BackForward — routing the reload into handleBackForwardNavigation.
- Misclassification — handleBackForwardNavigation reads only targetItem(), which is null for a reload (reloadItem() is set instead), so priorState collapses to EnhancedSecurity::Disabled.
- Downgrade — With priorState Disabled the tracker goes dormant; the ensuing process swap discards the HTTP-fallback state and the reload is silently upgraded back to https, dropping the intended posture.
- Fix — handleBackForwardNavigation falls back to reloadItem() when targetItem() is null, so the reload’s stored Enhanced Security state is honored and the tracker no longer goes dormant erroneously.
Triggering
No web-page PoC is derivable from the patch; the reproducer is the C++ API test TestWebKitAPI.WKNavigation.PreferredHTTPSPolicyAutomaticHTTPFallbackAfterTerminateProcess, which programmatically drives an https attempt with automatic HTTP fallback, terminates the web process, and then reloads/restores — asserting the fallback scheme survives. That harness exercises internal WKNavigation and process-termination APIs not directly reachable from untrusted script, and the bug is a state-tracking downgrade rather than a scriptable memory primitive, so a self-contained web PoC would not faithfully represent it.
Exploitation
- Induce the sequence — An attacker (or a network position that forces an https failure and http fallback) would need the victim’s content process to relaunch and then a history-restore/reload to occur, so the destination item flows through handleBackForwardNavigation with only reloadItem() set.
- Benefit from the downgrade — Once Enhanced Security goes dormant and the fallback state is discarded, the reload’s protection posture is reset. The practical gain is defeating the intended Enhanced Security / fallback decision for that navigation, not memory corruption; leverage depends on what protections that posture gated.
Detection & hunting
For defenders and SOC / detection engineers:
- Scheme flip after relaunch —
- Enhanced Security going dormant on reload —
- API test regression —
Audit directions
- Other targetItem()-only readers —
- State restored across process swaps —
- isEmpty()/about:blank normalization fallout —