[Blob URL] Extend cross-partition blob URL fetching bypass to dedicated workers
If a document context requests storage access, has it granted, and then creates a dedicated worker, that dedicated worker should be considered to have storage access as well. This means that if the third-party context creates a blob URL using the StorageAccessHandle and sends it to the dedicated worker, the dedicated worker should be able to fetch it. Bug: 403297818 Change-Id: Ifebc1f5ec78bd0e8e79e48ed21f6c98b90def715 Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/6409631 Reviewed-by: Bo Liu <boliu@chromium.org> Reviewed-by: Fergal Daly <fergal@chromium.org> Reviewed-by: Andrew Williams <awillia@chromium.org> Reviewed-by: Robert Flack <flackr@chromium.org> Commit-Queue: Janice Liu <janiceliu@chromium.org> Cr-Commit-Position: refs/heads/main@{#1443531}
This commit is contained in:

committed by
Chromium LUCI CQ

parent
882174e6f9
commit
557c11c341
content/browser
compute_pressure
service_worker
worker_host
storage/browser/blob
third_party/blink/web_tests
@ -146,7 +146,8 @@ class PressureServiceForDedicatedWorkerTest
|
||||
rfh->GetGlobalId(), rfh->GetGlobalId(), rfh->GetStorageKey(),
|
||||
rfh->GetStorageKey().origin(), rfh->GetIsolationInfoForSubresources(),
|
||||
rfh->BuildClientSecurityState(), nullptr, nullptr,
|
||||
mojo::PendingReceiver<blink::mojom::DedicatedWorkerHost>());
|
||||
mojo::PendingReceiver<blink::mojom::DedicatedWorkerHost>(),
|
||||
net::StorageAccessApiStatus::kNone);
|
||||
mojo::Receiver<blink::mojom::BrowserInterfaceBroker>& bib =
|
||||
worker_host_->browser_interface_broker_receiver_for_testing();
|
||||
blink::mojom::BrowserInterfaceBroker* broker = bib.internal_state()->impl();
|
||||
|
@ -230,6 +230,8 @@ void ServiceWorkerHost::CreateBlobUrlStoreProvider(
|
||||
storage_partition_impl->GetBlobUrlRegistry()->AddReceiver(
|
||||
version()->key(), version()->key().origin(),
|
||||
GetProcessHost()->GetDeprecatedID(), std::move(receiver),
|
||||
// Storage access can only be granted to dedicated workers.
|
||||
base::BindRepeating([]() -> bool { return false; }),
|
||||
!(GetContentClient()->browser()->IsBlobUrlPartitioningEnabled(
|
||||
GetProcessHost()->GetBrowserContext())));
|
||||
}
|
||||
|
@ -81,7 +81,8 @@ DedicatedWorkerHost::DedicatedWorkerHost(
|
||||
network::mojom::ClientSecurityStatePtr creator_client_security_state,
|
||||
base::WeakPtr<CrossOriginEmbedderPolicyReporter> creator_coep_reporter,
|
||||
base::WeakPtr<CrossOriginEmbedderPolicyReporter> ancestor_coep_reporter,
|
||||
mojo::PendingReceiver<blink::mojom::DedicatedWorkerHost> host)
|
||||
mojo::PendingReceiver<blink::mojom::DedicatedWorkerHost> host,
|
||||
net::StorageAccessApiStatus storage_access_api_status)
|
||||
: service_(service),
|
||||
token_(token),
|
||||
worker_process_host_(worker_process_host),
|
||||
@ -101,7 +102,8 @@ DedicatedWorkerHost::DedicatedWorkerHost(
|
||||
ancestor_coep_reporter_(std::move(ancestor_coep_reporter)),
|
||||
code_cache_host_receivers_(GetProcessHost()
|
||||
->GetStoragePartition()
|
||||
->GetGeneratedCodeCacheContext()) {
|
||||
->GetGeneratedCodeCacheContext()),
|
||||
storage_access_api_status_(storage_access_api_status) {
|
||||
DCHECK_CURRENTLY_ON(BrowserThread::UI);
|
||||
DCHECK(worker_process_host_);
|
||||
DCHECK(worker_process_host_->IsInitializedAndNotDead());
|
||||
@ -791,6 +793,15 @@ void DedicatedWorkerHost::CreateBroadcastChannelProvider(
|
||||
std::move(receiver));
|
||||
}
|
||||
|
||||
bool DedicatedWorkerHost::WasStorageAccessGranted() {
|
||||
switch (storage_access_api_status_) {
|
||||
case net::StorageAccessApiStatus::kAccessViaAPI:
|
||||
return true;
|
||||
case net::StorageAccessApiStatus::kNone:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
void DedicatedWorkerHost::CreateBlobUrlStoreProvider(
|
||||
mojo::PendingReceiver<blink::mojom::BlobURLStore> receiver) {
|
||||
DCHECK_CURRENTLY_ON(BrowserThread::UI);
|
||||
@ -800,6 +811,14 @@ void DedicatedWorkerHost::CreateBlobUrlStoreProvider(
|
||||
storage_partition_impl->GetBlobUrlRegistry()->AddReceiver(
|
||||
GetStorageKey(), renderer_origin_, GetProcessHost()->GetDeprecatedID(),
|
||||
std::move(receiver),
|
||||
base::BindRepeating(
|
||||
[](base::WeakPtr<DedicatedWorkerHost> host) -> bool {
|
||||
if (!host) {
|
||||
return false;
|
||||
}
|
||||
return host->WasStorageAccessGranted();
|
||||
},
|
||||
weak_factory_.GetWeakPtr()),
|
||||
!(GetContentClient()->browser()->IsBlobUrlPartitioningEnabled(
|
||||
GetProcessHost()->GetBrowserContext())),
|
||||
storage::BlobURLValidityCheckBehavior::
|
||||
|
@ -112,7 +112,8 @@ class CONTENT_EXPORT DedicatedWorkerHost final
|
||||
network::mojom::ClientSecurityStatePtr creator_client_security_state,
|
||||
base::WeakPtr<CrossOriginEmbedderPolicyReporter> creator_coep_reporter,
|
||||
base::WeakPtr<CrossOriginEmbedderPolicyReporter> ancestor_coep_reporter,
|
||||
mojo::PendingReceiver<blink::mojom::DedicatedWorkerHost> host);
|
||||
mojo::PendingReceiver<blink::mojom::DedicatedWorkerHost> host,
|
||||
net::StorageAccessApiStatus storage_access_api_status);
|
||||
|
||||
DedicatedWorkerHost(const DedicatedWorkerHost&) = delete;
|
||||
DedicatedWorkerHost& operator=(const DedicatedWorkerHost&) = delete;
|
||||
@ -160,6 +161,7 @@ class CONTENT_EXPORT DedicatedWorkerHost final
|
||||
mojo::PendingReceiver<blink::mojom::CodeCacheHost> receiver);
|
||||
void CreateBroadcastChannelProvider(
|
||||
mojo::PendingReceiver<blink::mojom::BroadcastChannelProvider> receiver);
|
||||
bool WasStorageAccessGranted();
|
||||
void CreateBlobUrlStoreProvider(
|
||||
mojo::PendingReceiver<blink::mojom::BlobURLStore> receiver);
|
||||
void CreateBucketManagerHost(
|
||||
@ -430,6 +432,11 @@ class CONTENT_EXPORT DedicatedWorkerHost final
|
||||
|
||||
BackForwardCacheBlockingDetails bfcache_blocking_details_;
|
||||
|
||||
// This tracks whether the document that created this dedicated worker had
|
||||
// been granted storage access when the dedicated worker was created, which
|
||||
// also grants storage access to the dedicated worker.
|
||||
net::StorageAccessApiStatus storage_access_api_status_;
|
||||
|
||||
base::WeakPtrFactory<DedicatedWorkerHost> weak_factory_{this};
|
||||
};
|
||||
|
||||
|
@ -125,7 +125,8 @@ void DedicatedWorkerHostFactoryImpl::CreateWorkerHostAndStartScriptLoad(
|
||||
ancestor_render_frame_host_id_, creator_storage_key_, renderer_origin,
|
||||
isolation_info_, std::move(creator_client_security_state_),
|
||||
std::move(creator_coep_reporter_), std::move(ancestor_coep_reporter_),
|
||||
pending_remote_host.InitWithNewPipeAndPassReceiver());
|
||||
pending_remote_host.InitWithNewPipeAndPassReceiver(),
|
||||
storage_access_api_status);
|
||||
mojo::PendingRemote<blink::mojom::BrowserInterfaceBroker> broker;
|
||||
host->BindBrowserInterfaceBrokerReceiver(
|
||||
broker.InitWithNewPipeAndPassReceiver());
|
||||
|
@ -604,6 +604,8 @@ void SharedWorkerHost::CreateBlobUrlStoreProvider(
|
||||
storage_partition_impl->GetBlobUrlRegistry()->AddReceiver(
|
||||
GetStorageKey(), instance().renderer_origin(),
|
||||
GetProcessHost()->GetDeprecatedID(), std::move(receiver),
|
||||
// Storage access can only be granted to dedicated workers.
|
||||
base::BindRepeating([]() -> bool { return false; }),
|
||||
!(GetContentClient()->browser()->IsBlobUrlPartitioningEnabled(
|
||||
GetProcessHost()->GetBrowserContext())),
|
||||
storage::BlobURLValidityCheckBehavior::
|
||||
|
@ -58,13 +58,14 @@ void BlobUrlRegistry::AddReceiver(
|
||||
const url::Origin& renderer_origin,
|
||||
int render_process_host_id,
|
||||
mojo::PendingReceiver<blink::mojom::BlobURLStore> receiver,
|
||||
base::RepeatingCallback<bool()> storage_access_check_callback,
|
||||
bool partitioning_disabled_by_policy,
|
||||
BlobURLValidityCheckBehavior validity_check_behavior) {
|
||||
worker_receivers_.Add(
|
||||
std::make_unique<storage::BlobURLStoreImpl>(
|
||||
storage_key, renderer_origin, render_process_host_id, AsWeakPtr(),
|
||||
validity_check_behavior, base::DoNothing(),
|
||||
base::BindRepeating([]() -> bool { return false; }),
|
||||
std::move(storage_access_check_callback),
|
||||
partitioning_disabled_by_policy),
|
||||
std::move(receiver));
|
||||
}
|
||||
|
@ -69,13 +69,16 @@ class COMPONENT_EXPORT(STORAGE_BROWSER) BlobUrlRegistry {
|
||||
|
||||
// Binds receivers corresponding to connections from renderer worker
|
||||
// contexts and stores them in `worker_receivers_`.
|
||||
void AddReceiver(const blink::StorageKey& storage_key,
|
||||
const url::Origin& renderer_origin,
|
||||
int render_process_host_id,
|
||||
mojo::PendingReceiver<blink::mojom::BlobURLStore> receiver,
|
||||
bool partitioning_disabled_by_policy = false,
|
||||
BlobURLValidityCheckBehavior validity_check_behavior =
|
||||
BlobURLValidityCheckBehavior::DEFAULT);
|
||||
void AddReceiver(
|
||||
const blink::StorageKey& storage_key,
|
||||
const url::Origin& renderer_origin,
|
||||
int render_process_host_id,
|
||||
mojo::PendingReceiver<blink::mojom::BlobURLStore> receiver,
|
||||
base::RepeatingCallback<bool()> storage_access_check_callback =
|
||||
base::BindRepeating([]() -> bool { return false; }),
|
||||
bool partitioning_disabled_by_policy = false,
|
||||
BlobURLValidityCheckBehavior validity_check_behavior =
|
||||
BlobURLValidityCheckBehavior::DEFAULT);
|
||||
|
||||
// Returns the receivers corresponding to renderer frame contexts for use in
|
||||
// tests.
|
||||
|
@ -1066,7 +1066,8 @@
|
||||
"external/wpt/FileAPI/BlobURL/cross-partition.https.html",
|
||||
"external/wpt/FileAPI/BlobURL/cross-partition-worker-creation.https.html",
|
||||
"external/wpt/FileAPI/BlobURL/cross-partition-navigation.https.html",
|
||||
"external/wpt/storage-access-api/storage-access-beyond-cookies.blobStorage.sub.https.window.html"
|
||||
"external/wpt/storage-access-api/storage-access-beyond-cookies.blobStorage.sub.https.window.html",
|
||||
"external/wpt/storage-access-api/storage-access-beyond-cookies.BlobURLDedicatedWorker.sub.https.tentative.window.html"
|
||||
],
|
||||
"args": ["--enable-features=BlockCrossPartitionBlobUrlFetching,EnforceNoopenerOnBlobURLNavigation"],
|
||||
"expires": "Sep 1, 2025",
|
||||
|
@ -310,6 +310,68 @@
|
||||
handle_shared_worker.port.postMessage("Same-origin handle access");
|
||||
break;
|
||||
}
|
||||
case "BlobURLDedicatedWorker": {
|
||||
const fetch_unsuccessful_response = "fetch_unsuccessful";
|
||||
const fetch_successful_response = "fetch_successful";
|
||||
|
||||
const can_blob_url_be_fetched_js = `
|
||||
onmessage = async (e) => {
|
||||
const blob_url = e.data;
|
||||
try {
|
||||
const blob = await fetch(blob_url).then(response => response.blob());
|
||||
await blob.text();
|
||||
postMessage("${fetch_successful_response}");
|
||||
} catch(e) {
|
||||
postMessage("${fetch_unsuccessful_response}");
|
||||
}
|
||||
};
|
||||
`;
|
||||
|
||||
// case 1: create dedicated worker w/o granting storage access
|
||||
const worker_blob_url = new Blob([can_blob_url_be_fetched_js], { type: 'text/javascript' });
|
||||
const third_party_blob_url = URL.createObjectURL(worker_blob_url);
|
||||
|
||||
const worker_1 = new Worker(third_party_blob_url);
|
||||
|
||||
await MaybeSetStorageAccess("*", "*", "allowed");
|
||||
const handle = await test_driver.bless("fake user interaction", () => document.requestStorageAccess({all: true}));
|
||||
|
||||
const worker_blob = new Blob(["potato"]);
|
||||
const first_party_blob_url = handle.createObjectURL(worker_blob);
|
||||
|
||||
const worker_response_promise = new Promise((resolve) => {
|
||||
worker_1.onmessage = (e) => { resolve(e.data) };
|
||||
worker_1.postMessage(first_party_blob_url);
|
||||
});
|
||||
|
||||
const worker_response = await worker_response_promise;
|
||||
if (worker_response === fetch_unsuccessful_response) {
|
||||
message = "Dedicated worker expectedly failed fetching first-party blob URL from a third-party context without granting storage access.";
|
||||
} else if (worker_response === fetch_successful_response) {
|
||||
message = "Dedicated worker unexpectedly fetched first-party blob URL from a third-party context without granting storage access.";
|
||||
break;
|
||||
}
|
||||
|
||||
// case 2: create dedicated worker after storage access is granted
|
||||
const worker_2 = new Worker(third_party_blob_url);
|
||||
const worker_response_promise2 = new Promise((resolve) => {
|
||||
worker_2.onmessage = (e) => { resolve(e.data) };
|
||||
worker_2.postMessage(first_party_blob_url);
|
||||
});
|
||||
const worker_response2 = await worker_response_promise2;
|
||||
|
||||
URL.revokeObjectURL(third_party_blob_url);
|
||||
handle.revokeObjectURL(first_party_blob_url);
|
||||
worker_2.terminate();
|
||||
|
||||
if (worker_response2 === fetch_unsuccessful_response) {
|
||||
message = "Dedicated worker unexpectedly failed fetching first-party blob URL from a third-party context with granting storage access.";
|
||||
break;
|
||||
} else if (worker_response2 === fetch_successful_response) {
|
||||
message = "Blob URL DedicatedWorker tests completed successfully.";
|
||||
}
|
||||
break;
|
||||
}
|
||||
case "unpartitioned": {
|
||||
await MaybeSetStorageAccess("*", "*", "allowed");
|
||||
await test_driver.set_permission({ name: 'storage-access' }, 'denied');
|
||||
|
@ -133,6 +133,9 @@ window.addEventListener("message", async (e) => {
|
||||
shared_worker.port.postMessage("Cross-origin handle access");
|
||||
break;
|
||||
}
|
||||
case "BlobURLDedicatedWorker": {
|
||||
break;
|
||||
}
|
||||
case "unpartitioned": {
|
||||
const channel = handle.BroadcastChannel(id);
|
||||
channel.postMessage("Cross-origin handle access");
|
||||
|
5
third_party/blink/web_tests/external/wpt/storage-access-api/storage-access-beyond-cookies.BlobURLDedicatedWorker.sub.https.tentative.window-expected.txt
vendored
Normal file
5
third_party/blink/web_tests/external/wpt/storage-access-api/storage-access-beyond-cookies.BlobURLDedicatedWorker.sub.https.tentative.window-expected.txt
vendored
Normal file
@ -0,0 +1,5 @@
|
||||
This is a testharness.js-based test.
|
||||
[FAIL] Verify that if the third-party context creates a blob URL using the StorageAccessHandle and sends it to the dedicated worker, the dedicated worker fetch succeeds.
|
||||
assert_equals: expected "Blob URL DedicatedWorker tests completed successfully." but got "Dedicated worker unexpectedly fetched first-party blob URL from a third-party context without granting storage access."
|
||||
Harness: the test ran to completion.
|
||||
|
22
third_party/blink/web_tests/external/wpt/storage-access-api/storage-access-beyond-cookies.BlobURLDedicatedWorker.sub.https.tentative.window.js
vendored
Normal file
22
third_party/blink/web_tests/external/wpt/storage-access-api/storage-access-beyond-cookies.BlobURLDedicatedWorker.sub.https.tentative.window.js
vendored
Normal file
@ -0,0 +1,22 @@
|
||||
// META: script=/resources/testdriver.js
|
||||
// META: script=/resources/testdriver-vendor.js
|
||||
|
||||
'use strict';
|
||||
|
||||
async_test(t => {
|
||||
// Set up a message listener that simply calls t.done() when a message is received.
|
||||
window.addEventListener("message", t.step_func(e => {
|
||||
if (e.data.type != "result") {
|
||||
return;
|
||||
}
|
||||
assert_equals(e.data.message, "Blob URL DedicatedWorker tests completed successfully.");
|
||||
t.done();
|
||||
}));
|
||||
|
||||
// Create an iframe that's cross-site with top-frame.
|
||||
const id = Date.now();
|
||||
let iframe = document.createElement("iframe");
|
||||
iframe.src = "https://{{hosts[alt][]}}:{{ports[https][0]}}/storage-access-api/resources/storage-access-beyond-cookies-iframe.sub.html?type=BlobURLDedicatedWorker&id=" + id;
|
||||
document.body.appendChild(iframe);
|
||||
|
||||
}, "Verify that if the third-party context creates a blob URL using the StorageAccessHandle and sends it to the dedicated worker, the dedicated worker fetch succeeds.");
|
3
third_party/blink/web_tests/virtual/cross-partition-blob-url/external/wpt/storage-access-api/storage-access-beyond-cookies.BlobURLDedicatedWorker.sub.https.tentative.window-expected.txt
vendored
Normal file
3
third_party/blink/web_tests/virtual/cross-partition-blob-url/external/wpt/storage-access-api/storage-access-beyond-cookies.BlobURLDedicatedWorker.sub.https.tentative.window-expected.txt
vendored
Normal file
@ -0,0 +1,3 @@
|
||||
This is a testharness.js-based test.
|
||||
Harness: the test ran to completion.
|
||||
|
Reference in New Issue
Block a user