CVE-2026-20691
Overview
Impact:
Description: A maliciously crafted webpage may be able to fingerprint the user
Researchers: Gongyu Ma (@Mezone0)
Key insight. A security helper that returned void swallowed its own failure: when a required sandbox extension could not be created the load proceeded anyway, and the resulting path-dependent behavioral difference was an observable side channel. Propagating the failure as a bool and hard-failing the load with blockedError removes the differential.
Patch Details
diff --git a/Source/WebKit/NetworkProcess/NetworkResourceLoadParameters.cpp b/Source/WebKit/NetworkProcess/NetworkResourceLoadParameters.cpp
index 3bc5c1e40bed..2206f06418ad 100644
--- a/Source/WebKit/NetworkProcess/NetworkResourceLoadParameters.cpp
+++ b/Source/WebKit/NetworkProcess/NetworkResourceLoadParameters.cpp
@@ -33,7 +33,7 @@
namespace WebKit {
using namespace WebCore;
-void NetworkResourceLoadParameters::createSandboxExtensionHandlesIfNecessary()
+bool NetworkResourceLoadParameters::createSandboxExtensionHandlesIfNecessary()
{
if (RefPtr httpBody = request.httpBody()) {
for (const FormDataElement& element : httpBody->elements()) {
@@ -57,7 +57,9 @@ void NetworkResourceLoadParameters::createSandboxExtensionHandlesIfNecessary()
if (auto handle = SandboxExtension::createHandle(request.url().fileSystemPath(), SandboxExtension::Type::ReadOnly))
resourceSandboxExtension = WTF::move(*handle);
}
+ return resourceSandboxExtension.has_value();
}
+ return true;
}
RefPtr<SecurityOrigin> NetworkResourceLoadParameters::parentOrigin() const
diff --git a/Source/WebKit/NetworkProcess/NetworkResourceLoadParameters.h b/Source/WebKit/NetworkProcess/NetworkResourceLoadParameters.h
index b30d39f610db..89339c55f05d 100644
--- a/Source/WebKit/NetworkProcess/NetworkResourceLoadParameters.h
+++ b/Source/WebKit/NetworkProcess/NetworkResourceLoadParameters.h
@@ -50,7 +50,7 @@ class Encoder;
namespace WebKit {
struct NetworkResourceLoadParameters {
- void createSandboxExtensionHandlesIfNecessary();
+ bool createSandboxExtensionHandlesIfNecessary();
RefPtr<WebCore::SecurityOrigin> NODELETE parentOrigin() const;
NetworkLoadParameters networkLoadParameters() const;
diff --git a/Source/WebKit/WebProcess/Network/WebLoaderStrategy.cpp b/Source/WebKit/WebProcess/Network/WebLoaderStrategy.cpp
index 0818b6397532..ef050f1bf94d 100644
--- a/Source/WebKit/WebProcess/Network/WebLoaderStrategy.cpp
+++ b/Source/WebKit/WebProcess/Network/WebLoaderStrategy.cpp
@@ -462,7 +462,12 @@ void WebLoaderStrategy::scheduleLoadFromNetworkProcess(ResourceLoader& resourceL
trackingParameters.frameID,
request
};
- loadParameters.createSandboxExtensionHandlesIfNecessary();
+ if (!loadParameters.createSandboxExtensionHandlesIfNecessary()) {
+ RunLoop::mainSingleton().dispatch([resourceLoader = Ref { resourceLoader }, error = blockedError(request)] {
+ resourceLoader->didFail(error);
+ });
+ return;
+ }
loadParameters.identifier = identifier;
loadParameters.parentPID = legacyPresentingApplicationPID();
Background
Advisory: Apple Security Advisory
| Attribute | Value |
|---|---|
| CVE | CVE-2026-20691 |
| Bugzilla | 306827 |
| Component | WebKit |
| Bug Class | LogicError |
| Severity | medium |
| Commit | dfe2e0efc8182466… |
| Advisory | Apple Advisory |
Terminology
- Sandbox extension — A capability token the WebProcess mints and passes to the NetworkProcess to temporarily grant that sandboxed process read (or write) access to a specific local file path.
- NetworkResourceLoadParameters — The bundle of parameters (request, identifiers, sandbox-extension handles, etc.) sent from the WebProcess to the NetworkProcess to describe a resource load.
- blockedError — A ResourceError indicating the load was blocked by policy; used here to fail the load cleanly and uniformly when a required sandbox extension cannot be granted.
- Fingerprinting side channel — Deriving stable distinguishing information about a user’s environment (here, local filesystem state) from observable differences in how requests behave rather than from any explicit data return.
Root Cause Analysis
When the WebProcess schedules a resource load on the NetworkProcess, WebLoaderStrategy::scheduleLoadFromNetworkProcess() builds a NetworkResourceLoadParameters and calls createSandboxExtensionHandlesIfNecessary() to mint sandbox-extension handles that grant the NetworkProcess read access to any local file the load requires (files referenced in an httpBody upload, and, per the shown hunk, the file-system path of the request URL via SandboxExtension::createHandle(request.url().fileSystemPath(), ReadOnly)). Before the patch this function returned void: it attempted to create the handles and then the caller proceeded with the load unconditionally, whether or not handle creation actually succeeded. Sandbox-extension creation for a path can succeed or fail depending on whether that path exists and is accessible, so a load that proceeded without a needed extension would behave observably differently (e.g. proceed to a file read that succeeds or fails, or produce different errors/timing) than one where the extension was granted. That observable difference is a side channel: it lets attacker-controlled web content distinguish states of the local filesystem, which the CVE describes as a fingerprinting capability. The fix changes createSandboxExtensionHandlesIfNecessary() to return bool — it returns resourceSandboxExtension.has_value() after the httpBody/file-path branch and true when there is no body — so the caller can tell whether a required extension was successfully created. WebLoaderStrategy now checks that return value and, if it is false, dispatches resourceLoader->didFail(blockedError(request)) on the main run loop and returns early, aborting the load uniformly instead of proceeding without the extension. This restores the invariant that a load requiring a local-file sandbox extension either runs with that extension or fails as blocked — never runs in a degraded, path-existence-dependent way — so the success/failure of the load no longer leaks whether the underlying file path was accessible. Note: the middle of the function (the httpBody element loop, diff lines 34-56) is not shown, so the exact set of paths for which handles are created is partly inferred from the visible request.url().fileSystemPath() branch and the function’s name.
Attack Path
1. Craft a load referencing a local path
From attacker-controlled web content, initiate a resource load whose parameters cause the NetworkProcess to need a sandbox extension for a local file — e.g. a request whose URL resolves to a file-system path, or a form/httpBody element referencing a file — so createSandboxExtensionHandlesIfNecessary() runs against that path.
2. Vary the target path
Point the load at different candidate local paths whose existence/accessibility the attacker wants to probe.
3. Observe pre-patch differential
Before the fix, if the extension could not be created the load still proceeded, producing a different observable outcome (success vs a different failure/error/timing) than when the extension was granted; the page reads this back through load success, error type, or timing.
4. Infer filesystem state
Correlate the observable outcomes across probes to distinguish which paths exist/are accessible, yielding a fingerprint of the local environment (installed apps, user paths, configuration).
Impact Assessment
This is an information-disclosure / fingerprinting bug, not a memory-corruption primitive: the leaked signal is coarse (whether a local file path is accessible), gated by whichever loads can be induced to reference local paths, and yields no read/write of memory. It spans the WebProcess (WebLoaderStrategy, where the load is now aborted) and the NetworkProcess (which holds the parameters and would otherwise act on a degraded load); there is no path to code execution. Realistic impact is enhanced device/user fingerprinting and possible existence-oracle for known file paths, consistent with the medium severity and the CVE’s fingerprinting description.
Audit Directions
Other void sandbox-extension helpers
Grep for createSandboxExtensionHandle / createSandboxExtensionHandlesIfNecessary and similar SandboxExtension::createHandle callers that ignore the returned std::optional<Handle>, and verify each caller aborts or degrades safely rather than proceeding when the handle is absent.
Loads that proceed after a failed capability grant
Audit WebLoaderStrategy and NetworkProcess load-scheduling paths for places where a capability/extension setup step’s failure does not translate into a uniform didFail(blockedError(…)), which would reintroduce a behavioral differential.
Path-existence-dependent behavior as an oracle
Look across WebKit for request handling that branches on request.url().fileSystemPath() existence/accessibility (file:// handling, form uploads, blob/file body elements) and check whether success/failure/timing differences are observable to web content.
Changed Functions
| Function | File | Change | Note |
|---|---|---|---|
NetworkResourceLoadParameters::createSandboxExtensionHandlesIfNecessary |
Source/WebKit/NetworkProcess/NetworkResourceLoadParameters.cpp |
modified | Return type changed from void to bool; now returns resourceSandboxExtension.has_value() for the body/file-path branch and true when there is no httpBody, signaling whether a required sandbox-extension handle was actually created. |
NetworkResourceLoadParameters::createSandboxExtensionHandlesIfNecessary (declaration) |
Source/WebKit/NetworkProcess/NetworkResourceLoadParameters.h |
modified | Declaration updated from void to bool return to match the definition. |
WebLoaderStrategy::scheduleLoadFromNetworkProcess |
Source/WebKit/WebProcess/Network/WebLoaderStrategy.cpp |
modified | Now checks the bool result; if handle creation failed it dispatches resourceLoader->didFail(blockedError(request)) on the main run loop and returns early, aborting the load uniformly instead of proceeding without the extension. |
Files Changed
Source Files
Source/WebKit/NetworkProcess/NetworkResourceLoadParameters.cppSource/WebKit/NetworkProcess/NetworkResourceLoadParameters.hSource/WebKit/WebProcess/Network/WebLoaderStrategy.cpp