Medium CVSS 6.5 webkit Bypass 🔧 Commit mapped

Overview

Medium
Severity
6.5
CVSS
No
Exploited ITW
Fixed
Fix Status
DescriptionProcessing maliciously crafted web content may disclose sensitive user information
ComponentWebKit NetworkProcess
Bug ClassBypass
Tracker313085
Fix commit5be1236842b3 (WebKit/WebKit) +109/-6
CWECWE-22
CVSS vectorCVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:U/C:H/I:N/A:N
CISA KEVNot listed
CreditedNan Wang (@eternalsakura13)
Disclosed2026-06-29

Background

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.

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.

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.

Changed Functions

FunctionChangeNotes
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

  • LayoutTests/ipc/register-file-backed-blob-path-validation-expected.txt
  • LayoutTests/ipc/register-file-backed-blob-path-validation.html
  • Source/WebKit/NetworkProcess/NetworkConnectionToWebProcess.cpp
  • Source/WebKit/NetworkProcess/NetworkConnectionToWebProcess.h
  • Source/WebKit/NetworkProcess/NetworkConnectionToWebProcess.messages.in
  • Source/WebKit/NetworkProcess/storage/IDBStorageConnectionToClient.cpp
  • Source/WebKit/NetworkProcess/storage/IDBStorageConnectionToClient.h
  • Source/WebKit/NetworkProcess/storage/IDBStorageRegistry.cpp
  • Source/WebKit/NetworkProcess/storage/IDBStorageRegistry.h
  • Source/WebKit/NetworkProcess/storage/NetworkStorageManager.cpp
  • Source/WebKit/NetworkProcess/storage/NetworkStorageManager.h

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).
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 {
Loading diff…

Original Bug Report

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