From 7f2ea8fcf41a68add90efab89609218407e1a824 Mon Sep 17 00:00:00 2001
From: Matthew Finkel
Date: Tue, 22 Aug 2023 19:19:19 -0700
Subject: [PATCH] Partition Blob Registry by the top-level main document origin
https://bugs.webkit.org/show_bug.cgi?id=260035 rdar://problem/113705298
Reviewed by Alex Christensen and Sihui Liu.
Public blob URLs are only accessible from same-origin dcuments, but access is
not restricted by the top-level origin. This means that Blob URLs can be used
as a cross-origin tracking mechanism within iframes. In this patch we partition
public blob URLs within the Blob Registry by top-level origin. This
partitioning is controlled by a feature flag that is disabled by default.
I took a few approaches at solving this. The most difficult challenge was
finding a solution that allowed retrieving BlobData using a public blob URL
from WKWebView APIs. In that case, the relevant top document may not be
obvious, or may not exist. As a result, the design of this partitioning is more
like access control rather than adding another key into the hashmap.
Two alternative designs I considered include creating a second hashmap that is
keyed by and we lookup the BlobData in that map if we
have a SecurityOriginData, otherwise we use the unpartitioned map. Or, we
create a new map from URL -> SecurityOriginData where we can lookup the
associated top origin SecurityOriginData if we don't already know it. However,
both of these options are more complex than the chosen implementation, and
neither of them seemed safer.
This change also enforces a noopener policy on new windows when the top origin
of the opener is cross-origin with the blob's security origin. This is a
mitigation that was discussed in the blob URL storage partitioning issue [0]
with cross-engine support, and that seemed reasonable to me.
[0] https://github.com/w3c/FileAPI/issues/153
* LayoutTests/TestExpectations:
* LayoutTests/http/tests/local/blob/download-blob-from-iframe-expected.txt: Added.
* LayoutTests/http/tests/local/blob/download-blob-from-iframe.html: Added.
* LayoutTests/http/tests/local/blob/navigate-blob-expected.txt: Added.
* LayoutTests/http/tests/local/blob/navigate-blob.html: Added.
* LayoutTests/http/tests/local/blob/resources/broadcast-channel-proxy.html: Added.
* LayoutTests/http/tests/local/blob/resources/iframe-creating-or-downloading-blob.html: Added.
* LayoutTests/http/tests/local/blob/resources/iframe-for-creating-and-navigating-to-blob.html: Added.
* LayoutTests/http/tests/local/blob/resources/main-frame-with-iframe-creating-or-navigating-to-blob.html: Added.
* LayoutTests/http/tests/local/blob/resources/main-frame-with-iframe-downloading-blob.html: Added.
* LayoutTests/http/tests/security/blob-null-url-location-origin-expected.txt:
* LayoutTests/http/tests/security/blob-null-url-location-origin.html:
* LayoutTests/http/tests/security/cross-origin-blob-transfer-expected.txt: Added.
* LayoutTests/http/tests/security/cross-origin-blob-transfer.html: Added.
* LayoutTests/http/tests/security/resources/iframe-cross-origin-blob-transfer.html: Added.
* LayoutTests/http/tests/security/top-level-unique-origin2.https.html:
* LayoutTests/platform/gtk-wk2/http/tests/local/blob/download-blob-from-iframe-expected.txt: Added.
* LayoutTests/platform/mac-wk1/TestExpectations:
* Source/WTF/Scripts/Preferences/UnifiedWebPreferences.yaml:
* Source/WebCore/fileapi/BlobURL.cpp:
(WebCore::BlobURL::isInternalURL):
* Source/WebCore/fileapi/BlobURL.h:
* Source/WebCore/fileapi/ThreadableBlobRegistry.cpp:
(WebCore::ThreadableBlobRegistry::registerInternalFileBlobURL):
(WebCore::ThreadableBlobRegistry::registerInternalBlobURL):
(WebCore::ThreadableBlobRegistry::registerInternalBlobURLOptionallyFileBacked):
(WebCore::ThreadableBlobRegistry::registerInternalBlobURLForSlice):
(WebCore::isInternalBlobURL): Deleted.
* Source/WebCore/loader/FrameLoader.cpp:
(WebCore::FrameLoader::loadURL):
(WebCore::FrameLoader::loadPostRequest):
(WebCore::createWindow):
* Source/WebCore/platform/network/BlobRegistryImpl.cpp:
(WebCore::BlobRegistryImpl::registerBlobURLOptionallyFileBacked):
(WebCore::BlobRegistryImpl::unregisterBlobURL):
(WebCore::BlobRegistryImpl::getBlobDataFromURL const):
(WebCore::BlobRegistryImpl::addBlobData):
(WebCore::BlobRegistryImpl::registerBlobURLHandle):
(WebCore::BlobRegistryImpl::unregisterBlobURLHandle):
* Source/WebCore/platform/network/BlobRegistryImpl.h:
Canonical link: https://commits.webkit.org/267172@main
---
LayoutTests/TestExpectations | 1 +
.../download-blob-from-iframe-expected.txt | 60 +++++++++
.../local/blob/download-blob-from-iframe.html | 103 +++++++++++++++
.../local/blob/navigate-blob-expected.txt | 48 +++++++
.../http/tests/local/blob/navigate-blob.html | 117 ++++++++++++++++++
.../resources/broadcast-channel-proxy.html | 12 ++
.../iframe-creating-or-downloading-blob.html | 28 +++++
...e-for-creating-and-navigating-to-blob.html | 66 ++++++++++
...iframe-creating-or-navigating-to-blob.html | 111 +++++++++++++++++
...in-frame-with-iframe-downloading-blob.html | 24 ++++
...blob-null-url-location-origin-expected.txt | 6 +-
.../blob-null-url-location-origin.html | 2 +-
.../cross-origin-blob-transfer-expected.txt | 16 +++
.../security/cross-origin-blob-transfer.html | 29 +++++
.../iframe-cross-origin-blob-transfer.html | 8 ++
.../top-level-unique-origin2.https.html | 2 +-
.../download-blob-from-iframe-expected.txt | 60 +++++++++
LayoutTests/platform/mac-wk1/TestExpectations | 4 +
.../Preferences/UnifiedWebPreferences.yaml | 6 +-
Source/WebCore/fileapi/BlobURL.cpp | 7 ++
Source/WebCore/fileapi/BlobURL.h | 3 +
.../fileapi/ThreadableBlobRegistry.cpp | 16 +--
Source/WebCore/loader/FrameLoader.cpp | 15 +++
.../platform/network/BlobRegistryImpl.cpp | 75 ++++++++---
.../platform/network/BlobRegistryImpl.h | 5 +-
25 files changed, 787 insertions(+), 37 deletions(-)
create mode 100644 LayoutTests/http/tests/local/blob/download-blob-from-iframe-expected.txt
create mode 100644 LayoutTests/http/tests/local/blob/download-blob-from-iframe.html
create mode 100644 LayoutTests/http/tests/local/blob/navigate-blob-expected.txt
create mode 100644 LayoutTests/http/tests/local/blob/navigate-blob.html
create mode 100644 LayoutTests/http/tests/local/blob/resources/broadcast-channel-proxy.html
create mode 100644 LayoutTests/http/tests/local/blob/resources/iframe-creating-or-downloading-blob.html
create mode 100644 LayoutTests/http/tests/local/blob/resources/iframe-for-creating-and-navigating-to-blob.html
create mode 100644 LayoutTests/http/tests/local/blob/resources/main-frame-with-iframe-creating-or-navigating-to-blob.html
create mode 100644 LayoutTests/http/tests/local/blob/resources/main-frame-with-iframe-downloading-blob.html
create mode 100644 LayoutTests/http/tests/security/cross-origin-blob-transfer-expected.txt
create mode 100644 LayoutTests/http/tests/security/cross-origin-blob-transfer.html
create mode 100644 LayoutTests/http/tests/security/resources/iframe-cross-origin-blob-transfer.html
create mode 100644 LayoutTests/platform/gtk/http/tests/local/blob/download-blob-from-iframe-expected.txt
diff --git a/LayoutTests/TestExpectations b/LayoutTests/TestExpectations
index 6214500565f46..1e65322c87950 100644
--- a/LayoutTests/TestExpectations
+++ b/LayoutTests/TestExpectations
@@ -5046,6 +5046,7 @@ webkit.org/b/227086 imported/w3c/web-platform-tests/webxr/xrWebGLLayer_construct
# Extra logging that needs to be silenced:
imported/w3c/web-platform-tests/webxr/webxr_availability.http.sub.html [ DumpJSConsoleLogInStdErr ]
+http/tests/local/blob/navigate-blob.html [ DumpJSConsoleLogInStdErr ]
# "Opacity on an inline element should apply on float child".
webkit.org/b/234690 imported/w3c/web-platform-tests/css/css-color/inline-opacity-float-child.html [ ImageOnlyFailure ]
diff --git a/LayoutTests/http/tests/local/blob/download-blob-from-iframe-expected.txt b/LayoutTests/http/tests/local/blob/download-blob-from-iframe-expected.txt
new file mode 100644
index 0000000000000..4aeccb4f57bb4
--- /dev/null
+++ b/LayoutTests/http/tests/local/blob/download-blob-from-iframe-expected.txt
@@ -0,0 +1,60 @@
+Download started.
+Downloading URL with suggested filename "testBlobFileName.html"
+Download completed.
+Download started.
+Download failed.
+Failed: WebKitBlobResource, code=1, description=The operation couldn’t be completed. (WebKitBlobResource error 1.)
+Download started.
+Download failed.
+Failed: WebKitBlobResource, code=1, description=The operation couldn’t be completed. (WebKitBlobResource error 1.)
+Download started.
+Download failed.
+Failed: WebKitBlobResource, code=1, description=The operation couldn’t be completed. (WebKitBlobResource error 1.)
+Download started.
+Download failed.
+Failed: WebKitBlobResource, code=1, description=The operation couldn’t be completed. (WebKitBlobResource error 1.)
+Download started.
+Download failed.
+Failed: WebKitBlobResource, code=1, description=The operation couldn’t be completed. (WebKitBlobResource error 1.)
+Download started.
+Download failed.
+Failed: WebKitBlobResource, code=1, description=The operation couldn’t be completed. (WebKitBlobResource error 1.)
+Download started.
+Downloading URL with suggested filename "testBlobFileName.html"
+Download completed.
+PASS successfullyParsed is true
+
+TEST COMPLETE
+Opening https://localhost:8443 as main frame with iframe origin https://localhost:8443, creating blob
+PASS Opened window
+PASS iframe: created blob
+Opening https://localhost:8443 as main frame with iframe origin https://localhost:8443, downloading blob
+PASS Opened window
+PASS iframe: downloading blob
+Opening http://localhost:8000 as main frame with iframe origin https://localhost:8443, downloading blob
+PASS Opened window
+PASS iframe: downloading blob
+Opening http://127.0.0.1:8000 as main frame with iframe origin https://localhost:8443, downloading blob
+PASS Opened window
+PASS iframe: downloading blob
+Opening https://127.0.0.1:8443 as main frame with iframe origin https://localhost:8443, downloading blob
+PASS Opened window
+PASS iframe: downloading blob
+Opening https://127.0.0.1:8443 as main frame with iframe origin https://localhost:8443, creating blob
+PASS Opened window
+PASS iframe: created blob
+Opening https://localhost:8443 as main frame with iframe origin https://localhost:8443, downloading blob
+PASS Opened window
+PASS iframe: downloading blob
+Opening http://localhost:8000 as main frame with iframe origin https://localhost:8443, downloading blob
+PASS Opened window
+PASS iframe: downloading blob
+Opening http://127.0.0.1:8000 as main frame with iframe origin https://localhost:8443, downloading blob
+PASS Opened window
+PASS iframe: downloading blob
+Opening https://127.0.0.1:8443 as main frame with iframe origin https://localhost:8443, downloading blob
+PASS Opened window
+PASS iframe: downloading blob
+
+PASS Test for creating blob in iframe and then downloading to it in same-origin iframe and same-origin, same-site, and cross-site main frames.
+
diff --git a/LayoutTests/http/tests/local/blob/download-blob-from-iframe.html b/LayoutTests/http/tests/local/blob/download-blob-from-iframe.html
new file mode 100644
index 0000000000000..2e84858b2447d
--- /dev/null
+++ b/LayoutTests/http/tests/local/blob/download-blob-from-iframe.html
@@ -0,0 +1,103 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/LayoutTests/http/tests/local/blob/navigate-blob-expected.txt b/LayoutTests/http/tests/local/blob/navigate-blob-expected.txt
new file mode 100644
index 0000000000000..1fe4769c058b3
--- /dev/null
+++ b/LayoutTests/http/tests/local/blob/navigate-blob-expected.txt
@@ -0,0 +1,48 @@
+PASS successfullyParsed is true
+
+TEST COMPLETE
+Opening https://localhost:8443 as main frame with iframe origin https://localhost:8443 creating blob
+PASS iframe: created blob
+PASS main frame: Fetched Blob
+PASS main frame: open() succeeded. Received message from blob: successfully navigated, have opener: true
+Opening http://127.0.0.1:8000 as main frame with iframe origin https://localhost:8443
+FAIL iframe: Fetching blob failed: Load failed
+FAIL iframe: WindowProxy handle is null (probably opened blob url with noopener)
+FAIL iframe: Could not open blob url, timed out
+FAIL main frame: Fetching blob failed: Load failed
+FAIL main frame: Could not open blob url, timed out
+Opening http://localhost:8000 as main frame with iframe origin https://localhost:8443
+FAIL iframe: Fetching blob failed: Load failed
+FAIL iframe: WindowProxy handle is null (probably opened blob url with noopener)
+FAIL iframe: Could not open blob url, timed out
+FAIL main frame: Fetching blob failed: Load failed
+FAIL main frame: Could not open blob url, timed out
+Opening https://localhost:8443 as main frame with iframe origin https://localhost:8443
+PASS iframe: Fetched Blob
+PASS iframe: open() succeeded. Received message from blob: successfully navigated, have opener: true
+PASS main frame: Fetched Blob
+PASS main frame: open() succeeded. Received message from blob: successfully navigated, have opener: true
+Opening http://127.0.0.1:8000 as main frame with iframe origin https://localhost:8443 creating blob
+PASS iframe: created blob
+FAIL main frame: Fetching blob failed: Load failed
+FAIL main frame: Could not open blob url, timed out
+Opening https://localhost:8443 as main frame with iframe origin https://localhost:8443
+FAIL iframe: Fetching blob failed: Load failed
+FAIL iframe: Could not open blob url, timed out
+FAIL main frame: Fetching blob failed: Load failed
+FAIL main frame: Could not open blob url, timed out
+Opening http://localhost:8000 as main frame with iframe origin https://localhost:8443
+FAIL iframe: Fetching blob failed: Load failed
+FAIL iframe: WindowProxy handle is null (probably opened blob url with noopener)
+FAIL iframe: Could not open blob url, timed out
+FAIL main frame: Fetching blob failed: Load failed
+FAIL main frame: Could not open blob url, timed out
+Opening http://127.0.0.1:8000 as main frame with iframe origin https://localhost:8443
+PASS iframe: Fetched Blob
+FAIL iframe: WindowProxy handle is null (probably opened blob url with noopener)
+PASS iframe: open() succeeded. Received message from blob: successfully navigated, have opener: false
+FAIL main frame: Fetching blob failed: Load failed
+FAIL main frame: Could not open blob url, timed out
+
+PASS Test for creating blob in iframe and then navigating to it from same-origin iframe and cross-origin main frames.
+
diff --git a/LayoutTests/http/tests/local/blob/navigate-blob.html b/LayoutTests/http/tests/local/blob/navigate-blob.html
new file mode 100644
index 0000000000000..5b218d167beb5
--- /dev/null
+++ b/LayoutTests/http/tests/local/blob/navigate-blob.html
@@ -0,0 +1,117 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/LayoutTests/http/tests/local/blob/resources/broadcast-channel-proxy.html b/LayoutTests/http/tests/local/blob/resources/broadcast-channel-proxy.html
new file mode 100644
index 0000000000000..fc8767ec110e4
--- /dev/null
+++ b/LayoutTests/http/tests/local/blob/resources/broadcast-channel-proxy.html
@@ -0,0 +1,12 @@
+
+
+
+
+
+
+
diff --git a/LayoutTests/http/tests/local/blob/resources/iframe-creating-or-downloading-blob.html b/LayoutTests/http/tests/local/blob/resources/iframe-creating-or-downloading-blob.html
new file mode 100644
index 0000000000000..cdb4e7507bdd1
--- /dev/null
+++ b/LayoutTests/http/tests/local/blob/resources/iframe-creating-or-downloading-blob.html
@@ -0,0 +1,28 @@
+
+
+
+
+
+
diff --git a/LayoutTests/http/tests/local/blob/resources/iframe-for-creating-and-navigating-to-blob.html b/LayoutTests/http/tests/local/blob/resources/iframe-for-creating-and-navigating-to-blob.html
new file mode 100644
index 0000000000000..646d63f43d4a3
--- /dev/null
+++ b/LayoutTests/http/tests/local/blob/resources/iframe-for-creating-and-navigating-to-blob.html
@@ -0,0 +1,66 @@
+
+
+
+
+
+
diff --git a/LayoutTests/http/tests/local/blob/resources/main-frame-with-iframe-creating-or-navigating-to-blob.html b/LayoutTests/http/tests/local/blob/resources/main-frame-with-iframe-creating-or-navigating-to-blob.html
new file mode 100644
index 0000000000000..63f8fba087adc
--- /dev/null
+++ b/LayoutTests/http/tests/local/blob/resources/main-frame-with-iframe-creating-or-navigating-to-blob.html
@@ -0,0 +1,111 @@
+
+
+
+
+
+
diff --git a/LayoutTests/http/tests/local/blob/resources/main-frame-with-iframe-downloading-blob.html b/LayoutTests/http/tests/local/blob/resources/main-frame-with-iframe-downloading-blob.html
new file mode 100644
index 0000000000000..0b1228661616e
--- /dev/null
+++ b/LayoutTests/http/tests/local/blob/resources/main-frame-with-iframe-downloading-blob.html
@@ -0,0 +1,24 @@
+
+
+
+
+
+
diff --git a/LayoutTests/http/tests/security/blob-null-url-location-origin-expected.txt b/LayoutTests/http/tests/security/blob-null-url-location-origin-expected.txt
index 070b1d23ca182..d15eebc12f9f4 100644
--- a/LayoutTests/http/tests/security/blob-null-url-location-origin-expected.txt
+++ b/LayoutTests/http/tests/security/blob-null-url-location-origin-expected.txt
@@ -1,5 +1,5 @@
CONSOLE MESSAGE: data URL frame loaded
-CONSOLE MESSAGE: blob popup opened
-CONSOLE MESSAGE: blob popup loadednull
-CONSOLE MESSAGE: blob popup message posted
+CONSOLE MESSAGE: blob iframe opened
+CONSOLE MESSAGE: blob iframe loadednull
+CONSOLE MESSAGE: blob iframe message posted
PASS
diff --git a/LayoutTests/http/tests/security/blob-null-url-location-origin.html b/LayoutTests/http/tests/security/blob-null-url-location-origin.html
index 23aaa03382ed6..417ad61a4d793 100644
--- a/LayoutTests/http/tests/security/blob-null-url-location-origin.html
+++ b/LayoutTests/http/tests/security/blob-null-url-location-origin.html
@@ -31,5 +31,5 @@
testRunner.notifyDone();
}, 10000);
-
+
+
+
+
diff --git a/LayoutTests/http/tests/security/cross-origin-blob-transfer-expected.txt b/LayoutTests/http/tests/security/cross-origin-blob-transfer-expected.txt
new file mode 100644
index 0000000000000..6e3bdf7787c8e
--- /dev/null
+++ b/LayoutTests/http/tests/security/cross-origin-blob-transfer-expected.txt
@@ -0,0 +1,16 @@
+PASS successfullyParsed is true
+
+TEST COMPLETE
+
+Summary
+
+Harness status: OK
+
+Found 1 tests
+
+1 Pass
+Details
+
+Result Test Name Message
+Pass Test for creating blob in iframe and then transferring it cross-origin.
+Asserts run
diff --git a/LayoutTests/http/tests/security/cross-origin-blob-transfer.html b/LayoutTests/http/tests/security/cross-origin-blob-transfer.html
new file mode 100644
index 0000000000000..bc4f0a177a580
--- /dev/null
+++ b/LayoutTests/http/tests/security/cross-origin-blob-transfer.html
@@ -0,0 +1,29 @@
+
+
+