CVE-2026-43732
Overview
Impact:
Description: Processing maliciously crafted web content may disclose sensitive user information
Researchers: Nan Wang (@eternalsakura13)
Key insight. File-backed blob path validation allowed any path whose parent was the storage directory, so a web process could read other origins’ IndexedDB blob files; restricting access to explicitly allow-listed, result-derived paths restores the boundary.
Patch Details
diff --git a/LayoutTests/ipc/register-file-backed-blob-path-validation-expected.txt b/LayoutTests/ipc/register-file-backed-blob-path-validation-expected.txt
new file mode 100644
index 000000000000..05d932b06d4f
--- /dev/null
+++ b/LayoutTests/ipc/register-file-backed-blob-path-validation-expected.txt
@@ -0,0 +1,2 @@
+Unauthorized file-backed blob registration rejected: PASS
+
diff --git a/LayoutTests/ipc/register-file-backed-blob-path-validation.html b/LayoutTests/ipc/register-file-backed-blob-path-validation.html
new file mode 100644
index 000000000000..1dd09d93dace
--- /dev/null
+++ b/LayoutTests/ipc/register-file-backed-blob-path-validation.html
@@ -0,0 +1,60 @@
+<!DOCTYPE html><!-- webkit-test-runner [ IPCTestingAPIEnabled=true IgnoreInvalidMessageWhenIPCTestingAPIEnabled=true BlobFileAccessEnforcementEnabled=true allowTestOnlyIPC=true ] -->
+<pre id="out"></pre>
+<script>
+if (window.testRunner) {
+ testRunner.dumpAsText();
+ testRunner.waitUntilDone();
+}
+</script>
+<script type="module">
+const out = document.getElementById('out');
+const log = s => { out.textContent += s + '\n'; };
+
+if (!window.IPC) {
+ log('Unauthorized file-backed blob registration rejected: PASS');
+ testRunner?.notifyDone();
+} else {
+
+const { CoreIPC } = await import("./coreipc.js");
+
+function blobSize(u) {
+ return new Promise(res => {
+ CoreIPC.Networking.NetworkConnectionToWebProcess.BlobSize(0, { url: { string: u } },
+ (parsed) => res(parsed && parsed.resultSize !== undefined ? Number(parsed.resultSize) : -1));
+ });
+}
+
+function generalStoragePath() {
+ return new Promise(res => {
+ CoreIPC.Networking.NetworkConnectionToWebProcess.GeneralStoragePathForTesting(0, {},
+ (parsed) => res(parsed && parsed.path ? parsed.path : ''));
+ });
+}
+
+(async () => {
+
+ localStorage.setItem('seed', '1');
+
+ let storagePath = await generalStoragePath();
+ if (!storagePath) {
+ log('FAIL: could not retrieve general storage path');
+ testRunner?.notifyDone();
+ return;
+ }
+
+ let targetPath = storagePath + '/salt';
+ let blobURL = URL.createObjectURL(new Blob([new Uint8Array([0])]));
+ CoreIPC.Networking.NetworkConnectionToWebProcess.RegisterInternalBlobURLOptionallyFileBacked(0, {
+ url: { string: blobURL },
+ srcURL: { string: `blob:webkit-internal://${crypto.randomUUID()}` },
+ fileBackedPath: targetPath,
+ contentType: 'application/octet-stream',
+ });
+ let size = await blobSize(blobURL);
+ log('Unauthorized file-backed blob registration rejected: ' + (size === 1 ? 'PASS' : 'FAIL (BlobSize=' + size + ')'));
+
+ testRunner?.notifyDone();
+})().catch(e => { log('FAIL: ' + e); testRunner?.notifyDone(); });
+
+}
+</script>
diff --git a/Source/WebKit/NetworkProcess/NetworkConnectionToWebProcess.cpp b/Source/WebKit/NetworkProcess/NetworkConnectionToWebProcess.cpp
index cf4eec65d80b..6c7c047279cc 100644
--- a/Source/WebKit/NetworkProcess/NetworkConnectionToWebProcess.cpp
+++ b/Source/WebKit/NetworkProcess/NetworkConnectionToWebProcess.cpp
@@ -1151,12 +1151,12 @@ void NetworkConnectionToWebProcess::cookieEnabledStateMayHaveChanged()
m_connection->send(Messages::NetworkProcessConnection::UpdateCachedCookiesEnabled(), 0);
}
-bool NetworkConnectionToWebProcess::isFilePathAllowed(NetworkSession& session, String path)
+bool NetworkConnectionToWebProcess::isFilePathAllowed(String path)
{
path = FileSystem::lexicallyNormal(path);
auto parentPath = FileSystem::parentPath(path);
while (parentPath != path) {
- if (m_allowedFilePaths.contains(path) || parentPath == session.storageManager().path() || parentPath == session.storageManager().customIDBStoragePath())
+ if (m_allowedFilePaths.contains(path))
return true;
path = parentPath;
parentPath = FileSystem::parentPath(path);
@@ -1182,7 +1182,7 @@ void NetworkConnectionToWebProcess::registerInternalFileBlobURL(const URL& url,
if (!session)
return;
if (blobFileAccessEnforcementEnabled() && shouldCheckBlobFileAccess())
- MESSAGE_CHECK(isFilePathAllowed(*session, path));
+ MESSAGE_CHECK(isFilePathAllowed(path));
RefPtr sandboxExtension = SandboxExtension::create(WTF::move(extensionHandle));
@@ -1198,7 +1198,7 @@ void NetworkConnectionToWebProcess::registerInternalFileBlobURL(const URL& url,
MESSAGE_CHECK(false);
}
#else
- MESSAGE_CHECK(isFilePathAllowed(*session, replacementPath));
+ MESSAGE_CHECK(isFilePathAllowed(replacementPath));
#endif
}
@@ -1233,7 +1233,7 @@ void NetworkConnectionToWebProcess::registerInternalBlobURLOptionallyFileBacked(
if (!session)
return;
if (blobFileAccessEnforcementEnabled() && shouldCheckBlobFileAccess())
- MESSAGE_CHECK(isFilePathAllowed(*session, fileBackedPath));
+ MESSAGE_CHECK(isFilePathAllowed(fileBackedPath));
m_blobURLs.add({ url, std::nullopt });
session->blobRegistry().registerInternalBlobURLOptionallyFileBacked(url, srcURL, BlobDataFileReferenceWithSandboxExtension::create(fileBackedPath), contentType, { });
@@ -1329,6 +1329,14 @@ void NetworkConnectionToWebProcess::registerBlobPathForTesting(const String& pat
completion();
}
+void NetworkConnectionToWebProcess::generalStoragePathForTesting(CompletionHandler<void(String&&)>&& completion)
+{
+ CheckedPtr session = networkSession();
+ if (!session)
+ return completion({ });
+ completion(String { session->storageManager().path() });
+}
+
void NetworkConnectionToWebProcess::allowAccessToFile(const String& path)
{
m_allowedFilePaths.add(FileSystem::lexicallyNormal(path));
diff --git a/Source/WebKit/NetworkProcess/NetworkConnectionToWebProcess.h b/Source/WebKit/NetworkProcess/NetworkConnectionToWebProcess.h
index 827774201378..a9c081afbf41 100644
--- a/Source/WebKit/NetworkProcess/NetworkConnectionToWebProcess.h
+++ b/Source/WebKit/NetworkProcess/NetworkConnectionToWebProcess.h
@@ -345,7 +345,8 @@ class NetworkConnectionToWebProcess final
void unregisterBlobURL(const URL&, const std::optional<WebCore::SecurityOriginData>& topOrigin);
void writeBlobsToTemporaryFilesForIndexedDB(const Vector<String>& blobURLs, CompletionHandler<void(Vector<String>&&)>&&);
void registerBlobPathForTesting(const String& path, CompletionHandler<void()>&&);
- bool isFilePathAllowed(NetworkSession&, String path);
+ void generalStoragePathForTesting(CompletionHandler<void(String&&)>&&);
+ bool isFilePathAllowed(String path);
void registerBlobURLHandle(const URL&, const std::optional<WebCore::SecurityOriginData>& topOrigin);
void unregisterBlobURLHandle(const URL&, const std::optional<WebCore::SecurityOriginData>& topOrigin);
diff --git a/Source/WebKit/NetworkProcess/NetworkConnectionToWebProcess.messages.in b/Source/WebKit/NetworkProcess/NetworkConnectionToWebProcess.messages.in
index 6310206e0cad..00fe84f0441a 100644
--- a/Source/WebKit/NetworkProcess/NetworkConnectionToWebProcess.messages.in
+++ b/Source/WebKit/NetworkProcess/NetworkConnectionToWebProcess.messages.in
@@ -76,6 +76,7 @@ messages -> NetworkConnectionToWebProcess WantsDispatchMessage {
RegisterBlobURLHandle(URL url, std::optional<WebCore::SecurityOriginData> topOrigin);
UnregisterBlobURLHandle(URL url, std::optional<WebCore::SecurityOriginData> topOrigin);
RegisterBlobPathForTesting(String path) -> ();
+ [EnabledBy=AllowTestOnlyIPC] GeneralStoragePathForTesting() -> (String path);
SetCaptureExtraNetworkLoadMetricsEnabled(bool enabled)
diff --git a/Source/WebKit/NetworkProcess/storage/IDBStorageConnectionToClient.cpp b/Source/WebKit/NetworkProcess/storage/IDBStorageConnectionToClient.cpp
index e62fcd676a49..caf7a835ab79 100644
--- a/Source/WebKit/NetworkProcess/storage/IDBStorageConnectionToClient.cpp
+++ b/Source/WebKit/NetworkProcess/storage/IDBStorageConnectionToClient.cpp
@@ -161,14 +161,49 @@ WebIDBResult IDBStorageConnectionToClient::prepareCursorResult(const WebCore::ID
});
}
+static Vector<String> resultBlobFilePaths(const WebCore::IDBResultData& resultData)
+{
+ Vector<String> paths;
+ switch (resultData.type()) {
+ case WebCore::IDBResultType::GetRecordSuccess:
+ case WebCore::IDBResultType::OpenCursorSuccess:
+ case WebCore::IDBResultType::IterateCursorSuccess:
+ paths.appendVector(resultData.getResult().value().blobFilePaths());
+ for (auto& record : resultData.getResult().prefetchedRecords())
+ paths.appendVector(record.value.blobFilePaths());
+ break;
+ case WebCore::IDBResultType::GetAllRecordsSuccess:
+ for (auto& value : resultData.getAllResult().values())
+ paths.appendVector(value.blobFilePaths());
+ break;
+ default:
+ break;
+ }
+ return paths;
+}
+
+template<typename Message>
+void IDBStorageConnectionToClient::sendResultWithBlobFileAccess(WebIDBResult&& result)
+{
+ auto blobFilePaths = resultBlobFilePaths(result.resultData());
+ RefPtr networkStorageManager = m_networkStorageManager.get();
+ if (!networkStorageManager) {
+ IPC::Connection::send(m_connection, Message(WTF::move(result)), 0);
+ return;
+ }
+ networkStorageManager->allowAccessToBlobFilesForProcess(m_identifier, WTF::move(blobFilePaths), [connection = m_connection, result = WTF::move(result)]() mutable {
Background
Advisory: Apple Security Advisory
| Attribute | Value |
|---|---|
| CVE | CVE-2026-43732 |
| Bugzilla | 313085 |
| Component | WebKit |
| Bug Class | Bypass |
| Severity | medium |
| Commit | 5be1236842b36137… |
| Advisory | Apple Advisory |
Terminology
- File-backed Blob URL — A Blob whose data is a file on disk; the NetworkProcess reads the file when the blob URL is fetched.
- NetworkProcess path allow-list — m_allowedFilePaths: the set of on-disk paths a given WebContent connection is permitted to access via blobs.
- MESSAGE_CHECK — An IPC guard that validates a message argument (here, that a requested file path is allowed) and terminates the connection on failure.
- IndexedDB blob files — IDB stores large values as separate on-disk blob files under the storage directory, per origin.
Root Cause Analysis
File-backed Blob URLs let the WebContent process reference on-disk files (e.g. IndexedDB blob files) that the NetworkProcess reads on its behalf; the NetworkProcess must validate that a path the web process asks to register/read is one it is actually allowed to access. Pre-patch, NetworkConnectionToWebProcess::isFilePathAllowed(session, path) allowed a path not only when it was in the explicit allow-list (m_allowedFilePaths) but ALSO when its parent directory equaled the session’s storageManager().path() or customIDBStoragePath(). That parent-directory rule is over-broad: it authorizes ANY file directly under the storage directories, so a malicious/compromised WebContent could register file-backed blobs for arbitrary files in those directories (other origins’ IndexedDB blob files) and read their contents over IPC – disclosure of sensitive user information across the storage boundary. The fix tightens isFilePathAllowed(path) to require the path be explicitly in m_allowedFilePaths (dropping the parentPath == storage path shortcuts), and adds the machinery to grant exactly the right paths: IDBStorageConnectionToClient gains resultBlobFilePaths(resultData) (collecting blobFilePaths from Get/GetAll/OpenCursor/IterateCursor results and prefetched records) and sendResultWithBlobFileAccess<Message>(), which registers those specific blob file paths as allowed for the connection before sending the IDB result. So the web process is authorized for precisely the blob files contained in its own IDB results, rather than for everything under the storage directory. The restored invariant is that file-backed blob access is allow-listed per specific, result-derived path, not granted wholesale by parent directory. The added IPC test validates register-file-backed-blob path validation.
Attack Path
1. Control a WebContent process
A malicious page (or a compromised WebContent) sends NetworkProcess IPC to register a file-backed blob URL for a chosen path.
2. Abuse the parent-directory rule
Pre-patch, isFilePathAllowed accepts any path whose parent is the storage/customIDB directory, so paths to other origins’ IndexedDB blob files pass the MESSAGE_CHECK.
3. Register and fetch the blob
Register the file-backed blob and read it via the blob URL; the NetworkProcess serves the file contents back.
4. Disclose sensitive data
The web process reads another origin’s stored blob files – sensitive user information disclosed across the storage boundary.
Impact Assessment
A sandbox/IPC path-validation weakness: the NetworkProcess authorized any file directly under the storage directories, letting a malicious WebContent register file-backed blobs for other origins’ IndexedDB blob files and read them – cross-origin/user-data disclosure. It is an information-disclosure bug at the WebContent->NetworkProcess boundary with no direct code-execution; tightening to per-result allow-listed paths removes it. Rated medium.
Audit Directions
Other parent-directory allow rules
grep NetworkProcess for path checks that authorize by parentPath/prefix equality against storage directories rather than an exact allow-list.
Blob file path granting
Audit all IDB/storage result senders to ensure every blob file path returned to a web process is registered as allowed for exactly that connection.
MESSAGE_CHECK path arguments
Review file-path arguments crossing IPC into the NetworkProcess for canonicalization and exact allow-listing (no directory-prefix shortcuts, symlink/.. handling).
Changed Functions
| Function | File | Change | Note |
|---|---|---|---|
NetworkConnectionToWebProcess::isFilePathAllowed |
Source/WebKit/NetworkProcess/NetworkConnectionToWebProcess.cpp |
modified | Now requires the path to be explicitly in m_allowedFilePaths; removes the over-broad parentPath == storageManager().path()/customIDBStoragePath() shortcuts, and callers pass just the path. |
IDBStorageConnectionToClient::sendResultWithBlobFileAccess / resultBlobFilePaths |
Source/WebKit/NetworkProcess/storage/IDBStorageConnectionToClient.cpp |
added | Collects the blob file paths actually present in an IDB result (Get/GetAll/OpenCursor/IterateCursor + prefetched records) and registers exactly those as allowed for the connection before sending the result. |
NetworkConnectionToWebProcess registerInternalFileBlobURL / registerInternalBlobURLOptionallyFileBacked |
Source/WebKit/NetworkProcess/NetworkConnectionToWebProcess.cpp |
modified | MESSAGE_CHECK(isFilePathAllowed(path)) now enforces the tightened allow-list on registration. |
Files Changed
Source Files
Source/WebKit/NetworkProcess/NetworkConnectionToWebProcess.cppSource/WebKit/NetworkProcess/NetworkConnectionToWebProcess.hSource/WebKit/NetworkProcess/NetworkConnectionToWebProcess.messages.inSource/WebKit/NetworkProcess/storage/IDBStorageConnectionToClient.cppSource/WebKit/NetworkProcess/storage/IDBStorageConnectionToClient.hSource/WebKit/NetworkProcess/storage/IDBStorageRegistry.cppSource/WebKit/NetworkProcess/storage/IDBStorageRegistry.hSource/WebKit/NetworkProcess/storage/NetworkStorageManager.cppSource/WebKit/NetworkProcess/storage/NetworkStorageManager.h
Test Files
LayoutTests/ipc/register-file-backed-blob-path-validation-expected.txtLayoutTests/ipc/register-file-backed-blob-path-validation.html
Regression Test
The fix ships this proof-of-concept / regression test:
diff --git a/LayoutTests/ipc/register-file-backed-blob-path-validation-expected.txt b/LayoutTests/ipc/register-file-backed-blob-path-validation-expected.txt
new file mode 100644
index 000000000000..05d932b06d4f
--- /dev/null
+++ b/LayoutTests/ipc/register-file-backed-blob-path-validation-expected.txt
@@ -0,0 +1,2 @@
+Unauthorized file-backed blob registration rejected: PASS
+
diff --git a/LayoutTests/ipc/register-file-backed-blob-path-validation.html b/LayoutTests/ipc/register-file-backed-blob-path-validation.html
new file mode 100644
index 000000000000..1dd09d93dace
--- /dev/null
+++ b/LayoutTests/ipc/register-file-backed-blob-path-validation.html
@@ -0,0 +1,60 @@
+<!DOCTYPE html><!-- webkit-test-runner [ IPCTestingAPIEnabled=true IgnoreInvalidMessageWhenIPCTestingAPIEnabled=true BlobFileAccessEnforcementEnabled=true allowTestOnlyIPC=true ] -->
+<pre id="out"></pre>
+<script>
+if (window.testRunner) {
+ testRunner.dumpAsText();
+ testRunner.waitUntilDone();
+}
+</script>
+<script type="module">
+const out = document.getElementById('out');
+const log = s => { out.textContent += s + '\n'; };
+
+if (!window.IPC) {
+ log('Unauthorized file-backed blob registration rejected: PASS');
+ testRunner?.notifyDone();
+} else {
+
+const { CoreIPC } = await import("./coreipc.js");
+
+function blobSize(u) {
+ return new Promise(res => {
+ CoreIPC.Networking.NetworkConnectionToWebProcess.BlobSize(0, { url: { string: u } },
+ (parsed) => res(parsed && parsed.resultSize !== undefined ? Number(parsed.resultSize) : -1));
+ });
+}
+
+function generalStoragePath() {
+ return new Promise(res => {
+ CoreIPC.Networking.NetworkConnectionToWebProcess.GeneralStoragePathForTesting(0, {},
+ (parsed) => res(parsed && parsed.path ? parsed.path : ''));
+ });
+}
+
+(async () => {
+
+ localStorage.setItem('seed', '1');
+
+ let storagePath = await generalStoragePath();
+ if (!storagePath) {
+ log('FAIL: could not retrieve general storage path');
+ testRunner?.notifyDone();
+ return;
+ }
+
+ let targetPath = storagePath + '/salt';
+ let blobURL = URL.createObjectURL(new Blob([new Uint8Array([0])]));
+ CoreIPC.Networking.NetworkConnectionToWebProcess.RegisterInternalBlobURLOptionallyFileBacked(0, {
+ url: { string: blobURL },
+ srcURL: { string: `blob:webkit-internal://${crypto.randomUUID()}` },
+ fileBackedPath: targetPath,
+ contentType: 'application/octet-stream',
+ });
+ let size = await blobSize(blobURL);
+ log('Unauthorized file-backed blob registration rejected: ' + (size === 1 ? 'PASS' : 'FAIL (BlobSize=' + size + ')'));
+
+ testRunner?.notifyDone();
+})().catch(e => { log('FAIL: ' + e); testRunner?.notifyDone(); });
+
+}
+</script>