Released March 24, 2026

CVE-2026-20691

CVSS 4.3 severity medium CWE-497 vector CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:U/C:L/I:N/A:N

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.cpp
  • Source/WebKit/NetworkProcess/NetworkResourceLoadParameters.h
  • Source/WebKit/WebProcess/Network/WebLoaderStrategy.cpp