Medium CVSS 6.1 webkit Cross Origin 🔧 Commit mapped

Overview

Medium
Severity
6.1
CVSS
No
Exploited ITW
Fixed
Fix Status
DescriptionProcessing maliciously crafted web content may lead to universal cross site scripting
ComponentWebKit UIProcess
Bug ClassCross Origin
Tracker268724
Fix commita350c1e9191a (WebKit/WebKit)
CWECWE-79 (Cross-site scripting)
CVSS vectorCVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:C/C:L/I:L/A:N
CISA KEVNot listed
CreditedRon Masas
Disclosed2024-09-16

Background

Web Archive (.webarchive)
A single-file snapshot of a page and its subresources whose main resource can specify an arbitrary origin/URL and can contain executable script, which is why loading one is security-sensitive.
UXSS (universal cross-site scripting)
A class of bug where attacker script escapes the same-origin policy and can run against or read state of arbitrary origins, bypassing per-site isolation.
WebsiteDataStore
The container for a browsing profile’s persistent or non-persistent state (cookies, storage, caches); createNonPersistent() yields an ephemeral, isolated store.
First-party for cookies
The top-level origin the network process uses to scope cookie access for a load; AddAllowedFirstPartyForCookies registers which domains a process may treat as first-party.
Process swap on navigation
WebKit’s mechanism to move a navigation into a fresh WebContent process to enforce isolation boundaries, forced here so the archive runs against the ephemeral store.

Root Cause Analysis

This is a universal cross-site-scripting (UXSS) / cross-origin-isolation logic bug in WebKit’s Web Archive loading path. A .webarchive (application/x-webarchive, application/x-mimearchive, multipart/related) is a self-contained snapshot whose main resource can declare an arbitrary origin/URL and can contain script; the security invariant is that loading such an archive must not let its script run against, or read the persistent state (cookies, storage, credentials, permissions) of, real origins in the user’s normal (persistent) data store. The large diff is spread across many files, but the security-relevant core is isolable: it moves web-archive loads onto an isolated, non-persistent (ephemeral) WebsiteDataStore and hardens the gate that decides when an archive may load at all. In WebPageProxy::receivedNavigationActionPolicyDecision(), when the new loadWebArchiveWithEphemeralStorageEnabled preference is on and the navigation is a Use decision for a substitute web archive or a file:// URL ending in .webarchive, the code now swaps in a freshly created non-persistent data store (WebsiteDataStore::createNonPersistent()), records the replaced persistent store, forces a process swap, and marks the load as LoadedWebArchive::Yes; navigating away restores the original store. That LoadedWebArchive flag is threaded through backForwardAddItemShared(), ProvisionalPageProxy, WebBackForwardListItem (which now carries a dataStoreForWebArchive), and NetworkProcess::AddAllowedFirstPartyForCookies so cookie/first-party bookkeeping matches the ephemeral store rather than the user’s real store. Additionally, once a web archive has been loaded, WebPageProxy::queryPermission() now returns PermissionState::Denied outright, preventing archive content from gaining camera/mic/geolocation etc. DocumentLoader::disallowWebArchive() is refactored to use the new shared MIMETypeRegistry::isWebArchiveMIMEType() helper (whose isValidValue guard also avoids feeding an over-long/invalid string to the hash set) and to key the local-webarchive allowance on the AlwaysAllowLocalWebarchive setting rather than a per-loader flag. Separately, isRequestFromClientOrUserInput is now propagated through LoadParameters and only set on the WebProcess FrameLoadRequest when the UI process says the load actually originated from client/user input, so a web-content-initiated navigation cannot masquerade as user-initiated. The pre-patch invariant violation: archive content loaded into (or sharing) the persistent data store and inheriting normal permission/first-party-cookie treatment, allowing it to act with the authority of real origins — i.e. UXSS. Note: because the enumerated source files (LoadParameters.serialization.in, UnifiedWebPreferences.yaml, the API tests) are omitted, the exact new-preference default and IPC serialization are inferred from the code that references them, not shown by the diff.
Key insight
The root cause is that web-archive content — which can spoof any origin and carry script — was loaded against the user’s persistent data store with normal permission and first-party-cookie treatment; the fix isolates every archive load into a throwaway non-persistent WebsiteDataStore (threaded through provisional pages, back/forward items, and cookie bookkeeping) and hard-denies permission queries once an archive is loaded.

Attack Path

  1. Deliver a malicious web archive The attacker gets a crafted .webarchive onto the victim’s machine or induces a load of one (download, email attachment, or a page that triggers a substitute-data web-archive load), with a main resource that claims a sensitive origin and embeds attacker script.
  2. Archive loads under the persistent store (pre-patch) Before the fix, the archive is committed into a WebContent process using the user’s normal persistent WebsiteDataStore, so its scripted content shares cookies/storage/credentials and first-party-for-cookies treatment with real origins.
  3. Script runs with borrowed authority Because the archive’s main resource can spoof any origin, its script executes as that origin and can read cookies, localStorage, and issue authenticated requests — universal cross-site scripting against arbitrary sites.
  4. Escalate via permissions Pre-patch, archive content could also request powerful permissions (camera, microphone, geolocation) as the spoofed origin, since queryPermission did not special-case archive loads.
  5. Fix cuts off the primitive Post-patch, the archive is confined to a throwaway non-persistent data store isolated from real cookies/storage, all permission queries return Denied, and cookie/first-party bookkeeping tracks the ephemeral store, so archive script can no longer read or act with real-origin state.

Impact Assessment

This is a same-origin-policy / cross-origin isolation failure (UXSS), not a memory-corruption primitive: exploited pre-patch, malicious web-archive script runs with the authority of spoofed real origins, letting it read cookies/storage and make authenticated cross-site requests, and (before the queryPermission fix) request powerful device permissions as those origins. It is a confidentiality/integrity break confined to the WebContent process and the browsing profile rather than a path to native code execution, so no heap-grooming or ACE chain is implied by the diff. Because a web archive is a delivered file rather than a drive-by web page, exploitation typically requires getting the victim to open a crafted archive, which is why the severity is rated medium.

Changed Functions

FunctionChangeNotes
DocumentLoader::disallowWebArchive
Source/WebCore/loader/DocumentLoader.cpp
modified Replaces the inline MIME hash set with MIMETypeRegistry::isWebArchiveMIMEType() and gates the local-archive allowance on the AlwaysAllowLocalWebarchive setting via a new alwaysAllowLocalWebArchive path guarded by ENABLE(WEB_ARCHIVE).
MIMETypeRegistry::isWebArchiveMIMEType
Source/WebCore/platform/MIMETypeRegistry.cpp
added New shared helper returning whether a MIME type is a web-archive type, with an isValidValue() guard so an invalid/over-long string is rejected before contains().
WebPageProxy::receivedNavigationActionPolicyDecision
Source/WebKit/UIProcess/WebPageProxy.cpp
modified Core fix: for a substitute or file:// .webarchive Use decision it swaps in a non-persistent WebsiteDataStore (or reuses the back/forward item's stored one), records the replaced store, forces a process swap, and threads LoadedWebArchive/replacedDataStore into the continuation.
WebPageProxy::continueNavigationInNewProcess
Source/WebKit/UIProcess/WebPageProxy.cpp
modified Gains LoadedWebArchive and replacedDataStoreForWebArchiveLoad parameters, passes the archive flag to AddAllowedFirstPartyForCookies and the replaced store to the new ProvisionalPageProxy.
WebPageProxy::backForwardAddItem / backForwardAddItemShared
Source/WebKit/UIProcess/WebPageProxy.cpp
modified Takes a LoadedWebArchive argument and, when Yes, stores the process's (ephemeral) data store on the WebBackForwardListItem so back/forward reloads reuse the isolated store.
WebPageProxy::queryPermission
Source/WebKit/UIProcess/WebPageProxy.cpp
modified Returns PermissionState::Denied immediately when didLoadWebArchive() is true, blocking powerful-feature grants to archive content.
WebPageProxy::swapToProvisionalPage
Source/WebKit/UIProcess/WebPageProxy.cpp
modified Carries the provisional page's replacedDataStoreForWebArchiveLoad onto the committed page so the persistent store can be restored later.
WebPageProxy::loadRequestWithNavigationShared / loadFile
Source/WebKit/UIProcess/WebPageProxy.cpp
modified Set loadParameters.isRequestFromClientOrUserInput from navigation/app-initiated state so only genuinely client/user-initiated loads carry that flag.
ProvisionalPageProxy::ProvisionalPageProxy / backForwardAddItem
Source/WebKit/UIProcess/ProvisionalPageProxy.cpp
modified New replacedDataStoreForWebArchiveLoad member; forwards LoadedWebArchive::Yes/No to backForwardAddItemShared based on it.
FrameLoader::loadWithNavigationAction / loadDifferentDocumentItem
Source/WebCore/loader/FrameLoader.cpp
modified Propagates isRequestFromClientOrUserInput onto the DocumentLoader and preserves the app-initiated bit when rebuilding the navigation action for original-URL loads.
WebPage::loadRequest
Source/WebKit/WebProcess/WebPage/WebPage.cpp
modified Only calls frameLoadRequest.setIsRequestFromClientOrUserInput() when loadParameters.isRequestFromClientOrUserInput is true, instead of unconditionally, so web-initiated loads are not mislabeled as user input.
WebBackForwardListItem (dataStoreForWebArchive)
Source/WebKit/Shared/WebBackForwardListItem.h
modified Adds m_dataStoreForWebArchive with getter/setter so the ephemeral store associated with an archive load survives on the history item.

Audit Directions

  • Completeness of the archive-isolation gate
    In receivedNavigationActionPolicyDecision audit every branch that decides isSubstituteWebArchive / file .webarchive: check reloads, back/forward (dataStoreForWebArchive reuse), and the didLoadWebArchive() restore path for a way to load archive content while m_replacedDataStoreForWebArchiveLoad stays null (grep LoadedWebArchive, replacedDataStoreForWebArchiveLoad, didLoadWebArchive).
  • MIME/type-sniffing bypasses of disallowWebArchive
    Check whether an archive can reach a load without matching isWebArchiveMIMEType (e.g. null/spoofed MIME, multipart/related sub-types, GTK message/rfc822, or QUICK_LOOK) so it is treated as a normal document; grep isWebArchiveMIMEType, disallowWebArchive, allowsWebArchiveForMainFrame, alwaysAllowLocalWebarchive.
  • isRequestFromClientOrUserInput spoofing
    Trace every setter/consumer of isRequestFromClientOrUserInput across LoadParameters/FrameLoadRequest/NavigationAction (grep isRequestFromClientOrUserInput, setIsRequestFromClientOrUserInput) for other loads that still set it unconditionally, letting web-initiated navigations pose as user-initiated and bypass local-file/archive restrictions.
  • Permission and state leaks under archive loads
    Beyond queryPermission, look for other UIProcess features (autofill, credential/storage access, media capture, downloads) that grant capability without checking didLoadWebArchive(); grep for PermissionState, websiteDataStore() uses that assume the persistent store during an archive load.

Original Bug Report

The reporter's bug is still restricted on the tracker.