CVE-2026-43821
Overview
Background
- Sandbox extension
- A token passed from the UI process that grants the Network/WebContent process temporary access to a specific file; the normal way local file loads are authorized.
- Container temp directory
- The app’s private temporary directory; an iOS-family exemption allowed extension-less local loads from here for apps that use fetch/loadHTMLString without passing an extension.
- isLocalFileLoadAllowed
- The Network-process gate deciding whether a WebContent-initiated local file load is permitted.
Root Cause Analysis
This fixes a sandbox file-read exemption that was too broad in the Network process. NetworkResourceLoader::isLocalFileLoadAllowed contains an iOS-family exemption that allows WebContent-initiated local file loads (via the fetch JS API or -[WKWebView loadHTMLString:baseURL:]) from the app’s container temporary directory, because such loads are not accompanied by a sandbox extension from the UI process.
Before the fix this exemption applied to every app, including MobileSafari, so web content running in Safari could use fetch/loadHTMLString to read local files under the container temp directory that lie outside what the web content should be able to access — reading files outside its sandbox.
The fix adds !WTF::IOSApplication::isMobileSafari() to the condition, so the undocumented temp-directory exemption no longer applies to Safari (it remains only for third-party apps that depend on it), and Safari’s local file loads must go through the normal sandbox-extension path.
The restored invariant is that Safari web content cannot load local files by this extension-less exemption. INFERENCE: the concrete file an attacker reads depends on what lands in the container temp directory; the commit establishes that the exemption previously covered Safari and now excludes it.
!WTF::IOSApplication::isMobileSafari() narrows the exemption to the apps that need it and forces Safari back onto the normal sandbox-extension path.Attack Path
- Run web content in Safari The attacker gets script executing in a MobileSafari WebContent process.
- Request a local file in the container temp directory The script uses fetch() (or a loadHTMLString base-URL relative link) to load a file:// URL under the app’s container temporary directory.
- Hit the extension-less exemption Pre-patch isLocalFileLoadAllowed returned true for the temp directory even for Safari, so the Network process loaded the file without a sandbox extension.
- Read files outside the sandbox Web content obtains the contents of local files it should not have access to (now denied for MobileSafari).
Impact Assessment
Changed Functions
| Function | Change | Notes |
|---|---|---|
NetworkResourceLoader::isLocalFileLoadAllowedSource/WebKit/NetworkProcess/NetworkResourceLoader.cpp |
modified | Adds !WTF::IOSApplication::isMobileSafari() to the container-temp-directory local-file-load exemption so it no longer applies to Safari, closing the extension-less local file read for Safari web content. |
Files Changed
Source/WebKit/NetworkProcess/NetworkResourceLoader.cpp
Audit Directions
- App-specific security exemptionsAudit other isXApplication()/quirk-style carve-outs in the Network and WebContent sandbox gates; each is a place where a broad default may unintentionally include the browser itself.
- Extension-less load pathsTrace every local-file load that proceeds without a sandbox extension and confirm the initiating app is one that genuinely requires the exemption.
Patch
diff --git a/Source/WebKit/NetworkProcess/NetworkResourceLoader.cpp b/Source/WebKit/NetworkProcess/NetworkResourceLoader.cpp
index 91809cdd24f4..ccfe30b53983 100644
--- a/Source/WebKit/NetworkProcess/NetworkResourceLoader.cpp
+++ b/Source/WebKit/NetworkProcess/NetworkResourceLoader.cpp
@@ -103,6 +103,7 @@
#if PLATFORM(COCOA)
#include "PathsBlockedForSandboxExtensions.h"
+#include <wtf/cocoa/RuntimeApplicationChecksCocoa.h>
#endif
#define LOADER_RELEASE_LOG_WITH_THIS(thisPtr, fmt, ...) RELEASE_LOG(Network, "%p - [pageProxyID=%" PRIu64 ", webPageID=%" PRIu64 ", frameID=%" PRIu64 ", resourceID=%" PRIu64 ", isMainResource=%d, destination=%u, isSynchronous=%d] NetworkResourceLoader::" fmt, WTF::getPtr(thisPtr), thisPtr->webPageProxyID().toUInt64(), thisPtr->pageID().toUInt64(), thisPtr->frameID().toUInt64(), thisPtr->coreIdentifier().toUInt64(), thisPtr->isMainResource(), static_cast<unsigned>(thisPtr->m_parameters.options.destination), thisPtr->isSynchronous(), ##__VA_ARGS__)
@@ -396,13 +397,18 @@ bool NetworkResourceLoader::shouldSendResourceLoadMessages() const
bool NetworkResourceLoader::isLocalFileLoadAllowed(const URL& url)
{
#if PLATFORM(IOS_FAMILY)
- // Some applications are relying on using the fetch JS API to load local files they have created in their temp directory.
+ // Some 3rd party apps are relying on using the fetch JS API or -[WKWebView loadHTMLString:baseURL:] to load local files in their temp directory.
// In this case, the WebContent process will not provide the Networking process with a sandbox extension to that file, since it does not have access.
// This is because the load is not initiated from the UI process which would provide an extension, but from JS in the WebContent process.
// To continue supporting this undocumented feature, we should allow local file loads from that location.
+ // FIXME: rdar://177160334
+ // The method -[WKWebView loadHTMLString:baseURL:] can be used to load local files by referring to links relative to the base URL in the HTML string.
+ // When the app is using -[WKWebView loadHTMLString:baseURL:] to load files in the temp directory, we should create a sandbox extension for the base URL.
+ // This can be done in WebPageProxy::loadDataWithNavigationShared. However, this is a larger change, so for now we rely on this exemption.
+
String directory = connectionToWebProcess().networkProcess().containerTemporaryDirectory();
- if (!directory.isEmpty() && FileSystem::isAncestor(directory, FileSystem::realPath(url.fileSystemPath()))) {
+ if (!WTF::IOSApplication::isMobileSafari() && !directory.isEmpty() && FileSystem::isAncestor(directory, FileSystem::realPath(url.fileSystemPath()))) {
RELEASE_LOG(Network, "shouldAllowLocalFileLoad: allowing loads from the temp directory");
return true;
}