CVE-2026-76019
Overview
Changed Functions
| Function | Change | Notes |
|---|---|---|
ifcontent/browser/worker_host/dedicated_worker_host.cc |
modified | |
TEST_Fcontent/browser/worker_host/dedicated_worker_service_impl_unittest.cc |
modified |
Files Changed
content/browser/worker_host/dedicated_worker_host.cccontent/browser/worker_host/dedicated_worker_host.hcontent/browser/worker_host/dedicated_worker_service_impl_unittest.cc
Patch
From a14280eb93e574171c55abe6e7c4470a1b221f02 Mon Sep 17 00:00:00 2001 From: Yoshisto Yanagisawa <[email protected]> Date: Tue, 04 Aug 2026 02:39:05 -0700 Subject: [PATCH] Consolidate and pass file_url_support to WorkerScriptFetcher This CL cleans up and consolidates the file URL support handling for dedicated and shared worker script and subresource loading: 1. Introduce DoesCreatorAllowFileUrlSupport helper in worker_util to centralize file URL support checks across creator contexts (handling command line switches, WebPreferences, and nested worker inheritance). 2. Update WorkerScriptFetcher::CreateAndStart to take file_url_support directly from callers (DedicatedWorkerHost and SharedWorkerServiceImpl), removing redundant recalculations. 3. Add unit tests in WorkerUtilTest and DedicatedWorkerServiceImplTest to verify file URL support behavior. TAG=agy CONV=6a5aeada-8c83-4b37-8615-53f25113df32 Bug: 539032888 Change-Id: I403e96584b67fb6f0a240ac4ef51de7577c4c793 Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/8182027 Reviewed-by: Hiroki Nakagawa <[email protected]> Auto-Submit: Yoshisato Yanagisawa <[email protected]> Commit-Queue: Yoshisato Yanagisawa <[email protected]> Cr-Commit-Position: refs/heads/main@{#1673207} --- diff --git a/content/browser/worker_host/dedicated_worker_host.cc b/content/browser/worker_host/dedicated_worker_host.cc index c4150dd8..8e448c3 100644 --- a/content/browser/worker_host/dedicated_worker_host.cc +++ b/content/browser/worker_host/dedicated_worker_host.cc @@ -10,6 +10,7 @@ #include <utility> #include <variant> +#include "base/command_line.h" #include "base/functional/bind.h" #include "base/functional/callback_helpers.h" #include "base/memory/safety_checks.h" @@ -39,6 +40,7 @@ #include "content/browser/worker_host/dedicated_worker_hosts_for_document.h" #include "content/browser/worker_host/dedicated_worker_service_impl.h" #include "content/browser/worker_host/worker_script_fetcher.h" +#include "content/browser/worker_host/worker_util.h" #include "content/public/browser/browser_context.h" #include "content/public/browser/browser_thread.h" #include "content/public/browser/network_service_util.h" @@ -48,6 +50,7 @@ #include "content/public/browser/site_isolation_policy.h" #include "content/public/common/child_process_id_util.h" #include "content/public/common/content_client.h" +#include "content/public/common/content_switches.h" #include "mojo/public/cpp/bindings/self_owned_receiver.h" #include "mojo/public/cpp/system/message_pipe.h" #include "net/base/isolation_info.h" @@ -64,6 +67,7 @@ #include "third_party/blink/public/common/service_worker/service_worker_scope_match.h" #include "third_party/blink/public/common/storage_key/storage_key.h" #include "third_party/blink/public/common/tokens/tokens.h" +#include "third_party/blink/public/common/web_preferences/web_preferences.h" #include "third_party/blink/public/mojom/back_forward_cache_not_restored_reasons.mojom.h" #include "third_party/blink/public/mojom/devtools/console_message.mojom.h" #include "third_party/blink/public/mojom/loader/fetch_client_settings_object.mojom.h" @@ -360,7 +364,13 @@ // initiator origin to keep consistency with WorkerScriptFetcher, but probably // this should be calculated based on the worker origin as the factories be // used for subresource loading on the worker. - file_url_support_ = creator_origin_.scheme() == url::kFileScheme; + std::optional<blink::web_pref::WebPreferences> web_preferences; + if (creator_render_frame_host) { + web_preferences = creator_render_frame_host->GetOrCreateWebPreferences(); + } + file_url_support_ = DoesCreatorAllowFileUrlSupport( + creator_origin_, web_preferences ? &*web_preferences : nullptr, + creator_worker ? creator_worker->file_url_support() : false); // For blob URL workers, inherit the controller from the worker's parent. // See https://w3c.github.io/ServiceWorker/#control-and-use-worker-client @@ -409,7 +419,7 @@ nearest_ancestor_render_frame_host->GetIsolationInfoForSubresources(), std::move(client_security_state), credentials_mode, std::move(outside_fetch_client_settings_object), - network::mojom::RequestDestination::kWorker, + network::mojom::RequestDestination::kWorker, file_url_support_, storage_partition_impl->GetServiceWorkerContext(), service_worker_handle_.get(), std::move(blob_url_loader_factory), nullptr, storage_partition_impl, partition_domain, diff --git a/content/browser/worker_host/dedicated_worker_host.h b/content/browser/worker_host/dedicated_worker_host.h index 87227f7..47ce6834 100644 --- a/content/browser/worker_host/dedicated_worker_host.h +++ b/content/browser/worker_host/dedicated_worker_host.h @@ -246,6 +246,8 @@ return service_worker_handle_.get(); } + bool file_url_support() const { return file_url_support_; } + #if BUILDFLAG(ENABLE_COMPUTE_PRESSURE) PressureServiceForDedicatedWorker* pressure_service() { return pressure_service_.get(); diff --git a/content/browser/worker_host/dedicated_worker_service_impl_unittest.cc b/content/browser/worker_host/dedicated_worker_service_impl_unittest.cc index 7249859..25f6312 100644 --- a/content/browser/worker_host/dedicated_worker_service_impl_unittest.cc +++ b/content/browser/worker_host/dedicated_worker_service_impl_unittest.cc @@ -7,9 +7,11 @@ #include <memory> #include <utility> +#include "base/command_line.h" #include "base/functional/callback_helpers.h" #include "base/run_loop.h" #include "base/scoped_observation.h" +#include "base/test/scoped_command_line.h" #include "base/test/scoped_feature_list.h" #include "components/services/storage/privileged/cpp/bucket_client_info.h" #include "content/browser/renderer_host/render_frame_host_impl.h" @@ -21,6 +23,7 @@ #include "content/public/browser/back_forward_cache.h" #include "content/public/browser/storage_partition.h" #include "content/public/common/content_features.h" +#include "content/public/common/content_switches.h" #include "content/public/test/navigation_simulator.h" #include "content/public/test/test_browser_context.h" #include "content/public/test/test_utils.h" @@ -36,6 +39,7 @@ #include "third_party/blink/public/common/storage_key/storage_key.h" #include "third_party/blink/public/common/tokens/tokens.h" #include "third_party/blink/public/common/tokens/tokens_mojom_traits.h" +#include "third_party/blink/public/common/web_preferences/web_preferences.h" #include "third_party/blink/public/mojom/loader/fetch_client_settings_object.mojom.h" #include "third_party/blink/public/mojom/service_worker/controller_service_worker.mojom.h" #include "third_party/blink/public/mojom/service_worker/service_worker_container.mojom.h" @@ -52,21 +56,28 @@ public: MockDedicatedWorker(ChildProcessId worker_process_id, GlobalRenderFrameHostId render_frame_host_id, + const url::Origin& origin) + : MockDedicatedWorker(worker_process_id, + render_frame_host_id, + DedicatedWorkerCreator(render_frame_host_id), + origin) {} + + MockDedicatedWorker(ChildProcessId worker_process_id, + GlobalRenderFrameHostId ancestor_render_frame_host_id, + DedicatedWorkerCreator creator, const url::Origin& origin) { // The COEP reporter is replaced by a placeholder connection. Reports are // ignored. + auto* ancestor_rfh = + RenderFrameHostImpl::FromID(ancestor_render_frame_host_id); auto coep_reporter = std::make_unique<CrossOriginEmbedderPolicyReporter>( - RenderFrameHostImpl::FromID(render_frame_host_id) - ->GetStoragePartition() - ->GetWeakPtr(), - GURL(), std::nullopt, std::nullopt, base::UnguessableToken::Create(), + ancestor_rfh->GetStoragePartition()->GetWeakPtr(), GURL(), std::nullopt, + std::nullopt, base::UnguessableToken::Create(), net::NetworkAnonymizationKey()); DedicatedWorkerHostFactoryImpl::Create( - *RenderFrameHostImpl::FromID(render_frame_host_id), - factory_.BindNewPipeAndPassReceiver(), worker_process_id, - /*creator=*/render_frame_host_id, - RenderFrameHostImpl::FromID(render_frame_host_id)->GetWeakDocumentPtr(), + *ancestor_rfh, factory_.BindNewPipeAndPassReceiver(), worker_process_id, + creator, ancestor_rfh->GetWeakDocumentPtr(), blink::StorageKey::CreateFirstParty(origin), net::IsolationInfo::CreateTransient(/*nonce=*/std::nullopt), network::mojom::ClientSecurityState::New(), PolicyContainerPolicies(), @@ -165,6 +176,12 @@ ->GetDedicatedWorkerService(); } + DedicatedWorkerServiceImpl* GetDedicatedWorkerServiceImpl() const { + return static_cast<DedicatedWorkerServiceImpl*>( + browser_context_->GetDefaultStoragePartition() + ->GetDedicatedWorkerService()); + } + private: std::unique_ptr<TestBrowserContext> browser_context_; }; @@ -295,6 +312,118 @@ EXPECT_TRUE(observer.dedicated_worker_infos().empty()); } +TEST_F(DedicatedWorkerServiceImplTest, FileUrlSupportInheritance) { + TestDedicatedWorkerServiceObserver observer; + base::ScopedObservation<DedicatedWorkerService, + DedicatedWorkerService::Observer> + scoped_observation(&observer); + scoped_observation.Observe(GetDedicatedWorkerService()); + + const GURL kFileUrl("file:///path/to/page.html"); + const auto origin = url::Origin::Create(kFileUrl); + std::unique_ptr<TestWebContents> web_contents = CreateWebContents(kFileUrl); + TestRenderFrameHost* rfh = web_contents->GetPrimaryMainFrame(); +
Regression Test / PoC
diff --git a/content/browser/worker_host/dedicated_worker_service_impl_unittest.cc b/content/browser/worker_host/dedicated_worker_service_impl_unittest.cc
index 7249859..25f6312 100644
--- a/content/browser/worker_host/dedicated_worker_service_impl_unittest.cc
+++ b/content/browser/worker_host/dedicated_worker_service_impl_unittest.cc
@@ -7,9 +7,11 @@
#include <memory>
#include <utility>
+#include "base/command_line.h"
#include "base/functional/callback_helpers.h"
#include "base/run_loop.h"
#include "base/scoped_observation.h"
+#include "base/test/scoped_command_line.h"
#include "base/test/scoped_feature_list.h"
#include "components/services/storage/privileged/cpp/bucket_client_info.h"
#include "content/browser/renderer_host/render_frame_host_impl.h"
@@ -21,6 +23,7 @@
#include "content/public/browser/back_forward_cache.h"
#include "content/public/browser/storage_partition.h"
#include "content/public/common/content_features.h"
+#include "content/public/common/content_switches.h"
#include "content/public/test/navigation_simulator.h"
#include "content/public/test/test_browser_context.h"
#include "content/public/test/test_utils.h"
@@ -36,6 +39,7 @@
#include "third_party/blink/public/common/storage_key/storage_key.h"
#include "third_party/blink/public/common/tokens/tokens.h"
#include "third_party/blink/public/common/tokens/tokens_mojom_traits.h"
+#include "third_party/blink/public/common/web_preferences/web_preferences.h"
#include "third_party/blink/public/mojom/loader/fetch_client_settings_object.mojom.h"
#include "third_party/blink/public/mojom/service_worker/controller_service_worker.mojom.h"
#include "third_party/blink/public/mojom/service_worker/service_worker_container.mojom.h"
@@ -52,21 +56,28 @@
public:
MockDedicatedWorker(ChildProcessId worker_process_id,
GlobalRenderFrameHostId render_frame_host_id,
+ const url::Origin& origin)
+ : MockDedicatedWorker(worker_process_id,
+ render_frame_host_id,
+ DedicatedWorkerCreator(render_frame_host_id),
+ origin) {}
+
+ MockDedicatedWorker(ChildProcessId worker_process_id,
+ GlobalRenderFrameHostId ancestor_render_frame_host_id,
+ DedicatedWorkerCreator creator,
const url::Origin& origin) {
// The COEP reporter is replaced by a placeholder connection. Reports are
// ignored.
+ auto* ancestor_rfh =
+ RenderFrameHostImpl::FromID(ancestor_render_frame_host_id);
auto coep_reporter = std::make_unique<CrossOriginEmbedderPolicyReporter>(
- RenderFrameHostImpl::FromID(render_frame_host_id)
- ->GetStoragePartition()
- ->GetWeakPtr(),
- GURL(), std::nullopt, std::nullopt, base::UnguessableToken::Create(),
+ ancestor_rfh->GetStoragePartition()->GetWeakPtr(), GURL(), std::nullopt,
+ std::nullopt, base::UnguessableToken::Create(),
net::NetworkAnonymizationKey());
DedicatedWorkerHostFactoryImpl::Create(
- *RenderFrameHostImpl::FromID(render_frame_host_id),
- factory_.BindNewPipeAndPassReceiver(), worker_process_id,
- /*creator=*/render_frame_host_id,
- RenderFrameHostImpl::FromID(render_frame_host_id)->GetWeakDocumentPtr(),
+ *ancestor_rfh, factory_.BindNewPipeAndPassReceiver(), worker_process_id,
+ creator, ancestor_rfh->GetWeakDocumentPtr(),
blink::StorageKey::CreateFirstParty(origin),
net::IsolationInfo::CreateTransient(/*nonce=*/std::nullopt),
network::mojom::ClientSecurityState::New(), PolicyContainerPolicies(),
@@ -165,6 +176,12 @@
->GetDedicatedWorkerService();
}
+ DedicatedWorkerServiceImpl* GetDedicatedWorkerServiceImpl() const {
+ return static_cast<DedicatedWorkerServiceImpl*>(
+ browser_context_->GetDefaultStoragePartition()
+ ->GetDedicatedWorkerService());
+ }
+
private:
std::unique_ptr<TestBrowserContext> browser_context_;
};
@@ -295,6 +312,118 @@
EXPECT_TRUE(observer.dedicated_worker_infos().empty());
}
+TEST_F(DedicatedWorkerServiceImplTest, FileUrlSupportInheritance) {
+ TestDedicatedWorkerServiceObserver observer;
+ base::ScopedObservation<DedicatedWorkerService,
+ DedicatedWorkerService::Observer>
+ scoped_observation(&observer);
+ scoped_observation.Observe(GetDedicatedWorkerService());
+
+ const GURL kFileUrl("file:///path/to/page.html");
+ const auto origin = url::Origin::Create(kFileUrl);
+ std::unique_ptr<TestWebContents> web_contents = CreateWebContents(kFileUrl);
+ TestRenderFrameHost* rfh = web_contents->GetPrimaryMainFrame();
+
+ // 1. Without switch or web preferences, file_url_support should be false.
+ {
+ auto mock_worker = std::make_unique<MockDedicatedWorker>(
+ rfh->GetProcess()->GetID(), rfh->GetGlobalId(), origin);
+ observer.RunUntilWorkerEvent();
+
+ ASSERT_EQ(observer.dedicated_worker_infos().size(), 1u);
+ blink::DedicatedWorkerToken token =
+ observer.dedicated_worker_infos().begin()->first;
+ DedicatedWorkerHost* host =
+ GetDedicatedWorkerServiceImpl()->GetDedicatedWorkerHostFromToken(token);
+ ASSERT_TRUE(host);
+ EXPECT_FALSE(host->file_url_support());
+
+ mock_worker = nullptr;
+ observer.RunUntilWorkerEvent();
+ EXPECT_TRUE(observer.dedicated_worker_infos().empty());
+ }
+
+ // 2. With WebPreferences::allow_file_access_from_file_urls, file_url_support
+ // should be true.
+ {
+ blink::web_pref::WebPreferences prefs =
+ web_contents->GetOrCreateWebPreferences();
+ prefs.allow_file_access_from_file_urls = true;
+ web_contents->SetWebPreferences(prefs);
+
+ auto mock_worker = std::make_unique<MockDedicatedWorker>(
+ rfh->GetProcess()->GetID(), rfh->GetGlobalId(), origin);
+ observer.RunUntilWorkerEvent();
+
+ ASSERT_EQ(observer.dedicated_worker_infos().size(), 1u);
+ blink::DedicatedWorkerToken token =
+ observer.dedicated_worker_infos().begin()->first;
+ DedicatedWorkerHost* host =
+ GetDedicatedWorkerServiceImpl()->GetDedicatedWorkerHostFromToken(token);
+ ASSERT_TRUE(host);
+ EXPECT_TRUE(host->file_url_support());
+
+ mock_worker = nullptr;
+ observer.RunUntilWorkerEvent();
+ EXPECT_TRUE(observer.dedicated_worker_infos().empty());
+
+ prefs.allow_file_access_from_file_urls = false;
+ web_contents->SetWebPreferences(prefs);
+ }
+
+ // 3. With WebPreferences::allow_universal_access_from_file_urls,
+ // file_url_support should be true.
+ {
+ blink::web_pref::WebPreferences prefs =
+ web_contents->GetOrCreateWebPreferences();
+ prefs.allow_universal_access_from_file_urls = true;
+ web_contents->SetWebPreferences(prefs);
+
+ auto mock_worker = std::make_unique<MockDedicatedWorker>(
+ rfh->GetProcess()->GetID(), rfh->GetGlobalId(), origin);
+ observer.RunUntilWorkerEvent();
+
+ ASSERT_EQ(observer.dedicated_worker_infos().size(), 1u);
+ blink::DedicatedWorkerToken token =
+ observer.dedicated_worker_infos().begin()->first;
+ DedicatedWorkerHost* host =
+ GetDedicatedWorkerServiceImpl()->GetDedicatedWorkerHostFromToken(token);
+ ASSERT_TRUE(host);
+ EXPECT_TRUE(host->file_url_support());
+
+ mock_worker = nullptr;
+ observer.RunUntilWorkerEvent();
+ EXPECT_TRUE(observer.dedicated_worker_infos().empty());
+
+ prefs.allow_universal_access_from_file_urls = false;
+ web_contents->SetWebPreferences(prefs);
+ }
+
+ // 4. With switches::kAllowFileAccessFromFiles, file_url_support should be
+ // true.
+ {
+ base::test::ScopedCommandLine scoped_command_line;
+ scoped_command_line.GetProcessCommandLine()->AppendSwitch(
+ switches::kAllowFileAccessFromFiles);
+
+ auto mock_worker = std::make_unique<MockDedicatedWorker>(
+ rfh->GetProcess()->GetID(), rfh->GetGlobalId(), origin);
+ observer.RunUntilWorkerEvent();
+
+ ASSERT_EQ(observer.dedicated_worker_infos().size(), 1u);
+ blink::DedicatedWorkerToken token =
+ observer.dedicated_worker_infos().begin()->first;
+ DedicatedWorkerHost* host =
+ GetDedicatedWorkerServiceImpl()->GetDedicatedWorkerHostFromToken(token);
+ ASSERT_TRUE(host);
+ EXPECT_TRUE(host->file_url_support());
+
+ mock_worker = nullptr;
+ observer.RunUntilWorkerEvent();
+ EXPECT_TRUE(observer.dedicated_worker_infos().empty());
+ }
+}
+
class DedicatedWorkerHostNavigationTest
: public DedicatedWorkerServiceImplTest {
public:
diff --git a/content/browser/worker_host/worker_util_unittest.cc b/content/browser/worker_host/worker_util_unittest.cc
new file mode 100644
index 0000000..e1de76cb
--- /dev/null
+++ b/content/browser/worker_host/worker_util_unittest.cc
@@ -0,0 +1,68 @@
+// Copyright 2026 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "content/browser/worker_host/worker_util.h"
+
+#include "base/command_line.h"
+#include "base/test/scoped_command_line.h"
+#include "content/public/common/content_switches.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "third_party/blink/public/common/web_preferences/web_preferences.h"
+#include "url/gurl.h"
+#include "url/origin.h"
+
+namespace content {
+
+TEST(WorkerUtilTest, DoesCreatorAllowFileUrlSupport) {
+ const url::Origin kHttpOrigin =
+ url::Origin::Create(GURL("http://example.com"));
+ const url::Origin kFileOrigin =
+ url::Origin::Create(GURL("file:///path/to/page.html"));
+
+ blink::web_pref::WebPreferences default_prefs;
+ blink::web_pref::WebPreferences allow_file_prefs;
+ allow_file_prefs.allow_file_access_from_file_urls = true;
+ blink::web_pref::WebPreferences allow_universal_prefs;
+ allow_universal_prefs.allow_universal_access_from_file_urls = true;
+
+ // 1. Non-file origin should never allow file URL support.
+ EXPECT_FALSE(DoesCreatorAllowFileUrlSupport(kHttpOrigin, &default_prefs));
+ EXPECT_FALSE(DoesCreatorAllowFileUrlSupport(kHttpOrigin, &allow_file_prefs));
+ EXPECT_FALSE(DoesCreatorAllowFileUrlSupport(
+ kHttpOrigin, /*web_preferences=*/nullptr,
+ /*creator_worker_has_file_url_support=*/true));
+
+ // 2. File origin without flags/preferences or parent support should be false.
+ EXPECT_FALSE(DoesCreatorAllowFileUrlSupport(kFileOrigin, &default_prefs));
+ EXPECT_FALSE(DoesCreatorAllowFileUrlSupport(
+ kFileOrigin, /*web_preferences=*/nullptr,
+ /*creator_worker_has_file_url_support=*/false));
+
+ // 3. File origin with WebPreferences::allow_file_access_from_file_urls.
+ EXPECT_TRUE(DoesCreatorAllowFileUrlSupport(kFileOrigin, &allow_file_prefs));
+
+ // 4. File origin with WebPreferences::allow_universal_access_from_file_urls.
+ EXPECT_TRUE(
+ DoesCreatorAllowFileUrlSupport(kFileOrigin, &allow_universal_prefs));
+
+ // 5. File origin inheriting from creator worker with file URL support.
+ EXPECT_TRUE(DoesCreatorAllowFileUrlSupport(
+ kFileOrigin, /*web_preferences=*/nullptr,
+ /*creator_worker_has_file_url_support=*/true));
+
+ // 6. File origin with switches::kAllowFileAccessFromFiles.
+ {
+ base::test::ScopedCommandLine scoped_command_line;
+ scoped_command_line.GetProcessCommandLine()->AppendSwitch(
+ switches::kAllowFileAccessFromFiles);
+ EXPECT_TRUE(DoesCreatorAllowFileUrlSupport(kFileOrigin, &default_prefs));
+ EXPECT_TRUE(DoesCreatorAllowFileUrlSupport(
+ kFileOrigin, /*web_preferences=*/nullptr,
+ /*creator_worker_has_file_url_support=*/false));
+ // Even with the switch, non-file origins should still be rejected.
+ EXPECT_FALSE(DoesCreatorAllowFileUrlSupport(kHttpOrigin, &default_prefs));
+ }
+}
+
+} // namespace content
diff --git a/content/test/BUILD.gn b/content/test/BUILD.gn
index 7455e883..b79615d 100644
--- a/content/test/BUILD.gn
+++ b/content/test/BUILD.gn
@@ -2998,6 +2998,7 @@
"../browser/worker_host/shared_worker_service_impl_unittest.cc",
"../browser/worker_host/worker_script_fetcher_unittest.cc",
"../browser/worker_host/worker_script_loader_factory_unittest.cc",
+ "../browser/worker_host/worker_util_unittest.cc",
"../browser/xr/metrics/session_tracker_unittest.cc",
"../child/blink_platform_impl_unittest.cc",
"../child/child_performance_coordinator_unittest.cc",
Original Bug Report
WorkerScriptFetcher allows compromised file renderer to bypass Chrome local-file policy
Steps to reproduce the problem
Tested on macOS 26.5.2 with official Google Chrome Stable 150.0.7871.187 from Google’s Stable DMG. The individually attached files are
self-contained and do not require a Chromium checkout, build output, archive extraction, or pre-existing web server. receiver.js is a readable JavaScript bundle containing the exact generated Mojo dependency closure.
-
Download all files then run :
chmod +x poc.sh && ./poc.sh -
The launcher starts its own loopback web server. It creates a disposable positive profile with HTML already configured to open automatically. This represents the supported prior “Always open files of this type” choice (or
AutoOpenFileTypespolicy); the attacker does not set it during the episode. -
The default target is
~/.bash_history, then~/.zsh_history, then/etc/hosts. A different readable file can be selected with./poc.sh --target /absolute/path/to/your/desired/file -
A normal HTTP page with no user activation starts one attachment download. Chrome saves
downloaded-page.htmland opens that exact file automatically in a new Site-Isolated renderer. The destination page explains that the download is only the bootstrap, then displays the unrelated target file’s cleartext contents. -
Run the positive and fresh-profile differential automatically with
./poc.sh --both --headless --target /absolute/path/to/your/desired/file
Note 1: An exact non-default installation can be selected with --chrome /path/to/Google\ Chrome.
Note 2: this PoC uses MojoJS to simulate native compromise of the auto-opened file: renderer. The bug does not itself provide renderer code execution.
Problem Description
Chromium’s default allow_file_access_from_file_urls == false policy prevents a file: document from reading local URLs. Blink records this restriction in the document’s SecurityOrigin. Due to this behavior, normal fetch(target) fails in this following way: new Worker(target) throws SecurityError, and Chromium’s macOS sandbox profile denies the destination renderer direct access to the same path.
A committed file document nevertheless receives renderer-facing browser interfaces, including a generic FileURLLoaderFactory and a document-scoped DedicatedWorkerHostFactory interfaces. A compromised renderer can supply an attacker-chosen file: worker-script URL through the production worker interface.
The browser authenticates the requesting renderer but in the process it loses Blink’s local-access restriction; WorkerScriptFetcher sees only that the creator scheme is file and installs a generic browser-side FileURLLoaderFactory. That factory treats the file:// initiator and target as the same tuple origin, converts the URL to a native path, and applies ChromeNetworkDelegate::IsAccessAllowed(). On macOS (possibly on Linux, haven;t checked) that desktop policy accepts every path, leaving only the browser process’s OS access.
Chrome therefore it opens the target with browser privileges and returns a kBasic response body data pipe. DedicatedWorkerHost::DidStartScriptLoad() forwards it to the renderer-controlled client, which drains the bytes before Blink parses or
executes them as a worker script. The renderer sandbox and Blink’s normal denial are bypassed because the privileged read and body delivery already occurred in the browser process. MojoJS exposes production IPC; Site Isolation remains enabled, thus this assumes a separate compromise of the auto-opened file renderer rather than migration of the HTTP renderer compromise.
The effect is disclosing the content of attacker-known file paths readable by Chrome’s OS account only, without path enumeration, file write nor code execution. Prior HTML auto-open state is a requirement for the no-interaction web entry and is not present in a fresh profile; auto-open itself is not alleged to be the vulnerability.
Additional Comments
Save all attached files in the same directory. receiver.js bundles the exact generated Mojo dependency closure. Attached is a video of this bug for further reference.
Summary
WorkerScriptFetcher allows compromised file renderer to bypass Chrome local-file policy
Custom Questions
Type of crash:
Non applicable
Crash state:
Non applicable
Reporter credit:
Anonymous
Additional Data
Category: Security
Chrome Channel: Stable
Regression: N/A \