blob: 6a2e6a0a60c280b1c9eadebeb29f4c5e791c7226 [file] [log] [blame]
Avi Drissman4e1b7bc32022-09-15 14:03:501// Copyright 2013 The Chromium Authors
[email protected]81374f22013-02-07 02:03:452// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
avib7348942015-12-25 20:57:105#include <stdint.h>
6
Arthur Sonzognic686e8f2024-01-11 08:36:377#include <optional>
Avi Drissman5d5d48d62022-01-07 20:23:588#include <tuple>
9
[email protected]81374f22013-02-07 02:03:4510#include "base/command_line.h"
Jun Cai53c54b222018-08-16 00:14:0411#include "base/feature_list.h"
Alex Ilincf96655e12019-11-21 16:13:5212#include "base/files/file_util.h"
Avi Drissmanadac21992023-01-11 23:46:3913#include "base/functional/bind.h"
14#include "base/functional/callback_helpers.h"
Gabriel Charette9f60dd12020-03-06 20:48:0415#include "base/memory/ptr_util.h"
Keishi Hattori0e45c022021-11-27 09:25:5216#include "base/memory/raw_ptr.h"
Andrew Williams39151a72022-10-18 23:12:4117#include "base/memory/weak_ptr.h"
Lei Zhangf40ac9a612025-06-02 22:35:1018#include "base/strings/string_util.h"
Lei Zhange02299a2021-04-26 23:12:2419#include "base/strings/stringprintf.h"
nick4c8dfd42014-11-14 04:11:4920#include "base/strings/utf_string_conversions.h"
Liam Bradyce3383b2023-08-16 18:37:2121#include "base/synchronization/waitable_event.h"
Dominic Farolinoa185eb72022-04-15 02:34:2922#include "base/test/bind.h"
Nasko Oskov1b7c5622024-04-16 19:47:2523#include "base/test/gtest_util.h"
Alex Moshchuk20780962018-09-27 23:17:0424#include "base/test/scoped_feature_list.h"
Ari Chivukulaa29eb3a2021-07-21 02:57:4825#include "base/unguessable_token.h"
avib7348942015-12-25 20:57:1026#include "build/build_config.h"
Liam Brady56f7d252023-02-28 17:40:1827#include "content/browser/attribution_reporting/attribution_manager.h"
anantaa5721fb2016-08-10 19:12:2628#include "content/browser/bad_message.h"
Lukasz Anforowicz9666a162019-10-12 00:54:2029#include "content/browser/child_process_security_policy_impl.h"
[email protected]04cbd3d2013-12-04 04:58:2030#include "content/browser/dom_storage/dom_storage_context_wrapper.h"
31#include "content/browser/dom_storage/session_storage_namespace_impl.h"
Dominic Farolinoa185eb72022-04-15 02:34:2932#include "content/browser/fenced_frame/fenced_frame.h"
Liam Brady56f7d252023-02-28 17:40:1833#include "content/browser/private_aggregation/private_aggregation_manager.h"
danakj10f32372020-09-15 22:25:1634#include "content/browser/renderer_host/navigator.h"
35#include "content/browser/renderer_host/render_frame_host_impl.h"
36#include "content/browser/renderer_host/render_frame_proxy_host.h"
Yutaka Hirano12c224a12017-12-19 06:12:3537#include "content/browser/renderer_host/render_process_host_impl.h"
[email protected]04cbd3d2013-12-04 04:58:2038#include "content/browser/renderer_host/render_view_host_factory.h"
[email protected]81374f22013-02-07 02:03:4539#include "content/browser/renderer_host/render_view_host_impl.h"
Avi Drissmanbd3e986442020-05-20 21:09:2040#include "content/browser/web_contents/file_chooser_impl.h"
[email protected]81374f22013-02-07 02:03:4541#include "content/browser/web_contents/web_contents_impl.h"
Nasko Oskovfa20eae02023-05-05 22:41:5642#include "content/common/features.h"
csharrison95f01e922017-04-24 18:52:3543#include "content/common/frame.mojom.h"
Hans Wennborg78b52182021-06-15 13:42:1544#include "content/common/frame_messages.mojom.h"
rockot5c478a72016-09-28 23:14:1845#include "content/common/render_message_filter.mojom.h"
Alex Moshchuk20780962018-09-27 23:17:0446#include "content/public/browser/blob_handle.h"
[email protected]04cbd3d2013-12-04 04:58:2047#include "content/public/browser/browser_context.h"
Eric Seckler8652dcd52018-09-20 10:42:2848#include "content/public/browser/browser_task_traits.h"
Gabriel Charette790754c2018-03-16 21:32:5949#include "content/public/browser/browser_thread.h"
creis3710b2382015-08-18 00:12:1550#include "content/public/browser/content_browser_client.h"
Kent Tamura4a3482692019-03-14 01:44:2751#include "content/public/browser/file_select_listener.h"
Hiroki Nakagawa080f86c2020-05-13 10:56:4252#include "content/public/browser/navigation_handle.h"
anantaa5721fb2016-08-10 19:12:2653#include "content/public/browser/resource_context.h"
[email protected]04cbd3d2013-12-04 04:58:2054#include "content/public/browser/storage_partition.h"
Nasko Oskov67730062020-05-08 16:49:2355#include "content/public/common/bindings_policy.h"
[email protected]81374f22013-02-07 02:03:4556#include "content/public/common/content_switches.h"
Avi Drissman73cb9852024-08-26 18:10:5857#include "content/public/common/isolated_world_ids.h"
Lukasz Anforowicz9666a162019-10-12 00:54:2058#include "content/public/common/url_constants.h"
Sreeja Kamishettyce8d5942020-08-19 11:25:5159#include "content/public/test/back_forward_cache_util.h"
Peter Kasting919ce652020-05-07 10:22:3660#include "content/public/test/browser_test.h"
[email protected]04cbd3d2013-12-04 04:58:2061#include "content/public/test/browser_test_utils.h"
[email protected]6e9def12014-03-27 20:23:2862#include "content/public/test/content_browser_test.h"
63#include "content/public/test/content_browser_test_utils.h"
Liviu Tintaeb63e1452021-10-28 18:47:0264#include "content/public/test/fenced_frame_test_util.h"
arthursonzogni85bc1162019-10-15 14:44:1365#include "content/public/test/navigation_handle_observer.h"
Liam Bradyb0f1f0e2022-08-19 21:42:1166#include "content/public/test/test_frame_navigation_observer.h"
Charles Reisc0507202017-09-21 00:40:0267#include "content/public/test/test_navigation_observer.h"
David Van Cleveca4ef9062020-04-27 23:46:5368#include "content/public/test/test_renderer_host.h"
[email protected]81374f22013-02-07 02:03:4569#include "content/public/test/test_utils.h"
[email protected]de7d61ff2013-08-20 11:30:4170#include "content/shell/browser/shell.h"
nasko402cc292016-03-11 09:16:1171#include "content/test/content_browser_test_utils_internal.h"
Arthur Hemery2a0a28be2019-03-06 17:51:3672#include "content/test/did_commit_navigation_interceptor.h"
Lukasz Anforowicz3cfc1efd2019-02-22 02:29:2973#include "content/test/frame_host_interceptor.h"
creis3710b2382015-08-18 00:12:1574#include "content/test/test_content_browser_client.h"
Tom Sepezcc69c442025-07-17 02:00:3075#include "ipc/constants.mojom.h"
Kent Tamura8c9e0562018-11-06 07:02:1976#include "mojo/core/embedder/embedder.h"
Julie Jeongeun Kimed2e5ba72019-09-12 10:14:1777#include "mojo/public/cpp/bindings/pending_associated_remote.h"
Julie Jeongeun Kime003de52019-10-29 05:14:2778#include "mojo/public/cpp/bindings/pending_receiver.h"
Miyoung Shinda5da322019-09-02 10:07:3779#include "mojo/public/cpp/bindings/pending_remote.h"
Henrique Ferreiro3bb206b2019-09-18 12:48:2380#include "mojo/public/cpp/bindings/remote.h"
Lukasz Anforowicz288027b2019-01-17 01:51:2781#include "mojo/public/cpp/test_support/test_utils.h"
Andrew Williams39151a72022-10-18 23:12:4182#include "net/base/features.h"
Lukasz Anforowicz9666a162019-10-12 00:54:2083#include "net/base/filename_util.h"
Alex Moshchuk375f4de82025-05-13 17:30:4884#include "net/base/net_errors.h"
Shivani Sharma11c23f552019-06-11 18:18:3985#include "net/base/network_isolation_key.h"
nick4c8dfd42014-11-14 04:11:4986#include "net/dns/mock_host_resolver.h"
Chris Fredrickson9ffdf5b2024-07-09 20:05:0987#include "net/storage_access_api/status.h"
Hiroshige Hayashizaki8ee3f7e62019-09-05 07:17:4788#include "net/test/embedded_test_server/controllable_http_response.h"
nick4c8dfd42014-11-14 04:11:4989#include "net/test/embedded_test_server/embedded_test_server.h"
Hiroshige Hayashizaki8ee3f7e62019-09-05 07:17:4790#include "net/test/embedded_test_server/http_request.h"
Ramin Halavati81fdc8fb2017-07-09 17:15:4291#include "net/traffic_annotation/network_traffic_annotation_test_helper.h"
John Abd-El-Malek91243b32018-01-19 06:08:3392#include "services/network/public/cpp/network_switches.h"
John Abd-El-Malek1df61792018-01-12 20:40:4593#include "services/network/public/cpp/resource_request.h"
Nan Lind91c8152021-10-21 16:22:3794#include "services/network/public/mojom/fetch_api.mojom.h"
David Van Cleveca4ef9062020-04-27 23:46:5395#include "services/network/public/mojom/trust_tokens.mojom.h"
Ken Rockot54311e62018-02-10 19:01:5296#include "services/network/public/mojom/url_loader.mojom.h"
John Abd-El-Malekd0cd7c5c2018-01-30 07:20:0297#include "services/network/test/test_url_loader_client.h"
Alex Moshchuk20780962018-09-27 23:17:0498#include "storage/browser/blob/blob_registry_impl.h"
David Sandersb355ac42025-07-25 23:32:3999#include "storage/browser/blob/blob_url_registry.h"
Lukasz Anforowicz288027b2019-01-17 01:51:27100#include "testing/gmock/include/gmock/gmock.h"
101#include "testing/gtest/include/gtest/gtest.h"
Kinuko Yasuda04a82ab12019-07-26 06:13:39102#include "third_party/blink/public/common/blob/blob_utils.h"
Dominic Farolinoe724b5332022-04-26 05:00:06103#include "third_party/blink/public/common/fenced_frame/fenced_frame_utils.h"
Liam Bradyb0f1f0e2022-08-19 21:42:11104#include "third_party/blink/public/common/frame/fenced_frame_sandbox_flags.h"
Yeunjoo Choi3df791a2021-02-17 07:07:25105#include "third_party/blink/public/common/navigation/navigation_policy.h"
Andrew Williams39151a72022-10-18 23:12:41106#include "third_party/blink/public/mojom/blob/blob_url_store.mojom.h"
Henrique Ferreiro3bb206b2019-09-18 12:48:23107#include "third_party/blink/public/mojom/choosers/file_chooser.mojom.h"
Nan Line376738a2022-03-25 22:05:41108#include "third_party/blink/public/mojom/fenced_frame/fenced_frame.mojom.h"
Mario Sanchez Pradae54aa9e2020-05-20 15:08:31109#include "third_party/blink/public/mojom/frame/frame.mojom-test-utils.h"
Gyuyoung Kim16a12f52020-12-19 04:24:26110#include "third_party/blink/public/mojom/frame/frame.mojom.h"
Takuto Ikuta247f72f2024-02-21 04:46:06111#include "third_party/blink/public/mojom/frame/remote_frame.mojom-test-utils.h"
Henrique Ferreiro621b3ad2021-02-15 12:51:41112#include "third_party/blink/public/mojom/loader/mixed_content.mojom.h"
nick4c8dfd42014-11-14 04:11:49113
David Van Cleveca4ef9062020-04-27 23:46:53114using ::testing::HasSubstr;
115using ::testing::Optional;
[email protected]81374f22013-02-07 02:03:45116
117namespace content {
118
[email protected]04cbd3d2013-12-04 04:58:20119namespace {
120
121// This is a helper function for the tests which attempt to create a
122// duplicate RenderViewHost or RenderWidgetHost. It tries to create two objects
123// with the same process and routing ids, which causes a collision.
124// It creates a couple of windows in process 1, which causes a few routing ids
125// to be allocated. Then a cross-process navigation is initiated, which causes a
126// new process 2 to be created and have a pending RenderViewHost for it. The
127// routing id of the RenderViewHost which is target for a duplicate is set
Alex Moshchuk27caae82017-09-11 23:11:18128// into |target_routing_id| and the pending RenderFrameHost which is used for
[email protected]04cbd3d2013-12-04 04:58:20129// the attempt is the return value.
Alex Moshchuk27caae82017-09-11 23:11:18130RenderFrameHostImpl* PrepareToDuplicateHosts(Shell* shell,
Aaron Colwelle953e562019-07-24 16:47:36131 net::EmbeddedTestServer* server,
Alex Moshchuk27caae82017-09-11 23:11:18132 int* target_routing_id) {
nick4c8dfd42014-11-14 04:11:49133 GURL foo("http://foo.com/simple_page.html");
[email protected]04cbd3d2013-12-04 04:58:20134
Aaron Colwell5fb878042020-12-17 19:48:44135 if (IsIsolatedOriginRequiredToGuaranteeDedicatedProcess()) {
Aaron Colwelle953e562019-07-24 16:47:36136 // Isolate "bar.com" so we are guaranteed to get a different process
137 // for navigations to this origin.
138 IsolateOriginsForTesting(server, shell->web_contents(), {"bar.com"});
139 }
140
[email protected]04cbd3d2013-12-04 04:58:20141 // Start off with initial navigation, so we get the first process allocated.
Alex Moshchuk4174c192019-08-20 16:58:09142 EXPECT_TRUE(NavigateToURL(shell, foo));
Jan Wilken Dörrie8aeb5742021-03-23 19:27:02143 EXPECT_EQ(u"OK", shell->web_contents()->GetTitle());
[email protected]04cbd3d2013-12-04 04:58:20144
145 // Open another window, so we generate some more routing ids.
146 ShellAddedObserver shell2_observer;
Avi Drissmanc91bd8e2021-04-19 23:58:44147 EXPECT_TRUE(ExecJs(shell, "window.open(document.URL + '#2');"));
[email protected]04cbd3d2013-12-04 04:58:20148 Shell* shell2 = shell2_observer.GetShell();
149
150 // The new window must be in the same process, but have a new routing id.
Emily Andrewsd15fd762024-12-10 20:41:54151 EXPECT_EQ(shell->web_contents()
152 ->GetPrimaryMainFrame()
153 ->GetProcess()
154 ->GetDeprecatedID(),
155 shell2->web_contents()
156 ->GetPrimaryMainFrame()
157 ->GetProcess()
158 ->GetDeprecatedID());
Lucas Furukawa Gadanie7649422021-02-03 03:04:32159 *target_routing_id = shell2->web_contents()
Dave Tapuska327c06c92022-06-13 20:31:51160 ->GetPrimaryMainFrame()
Lucas Furukawa Gadanie7649422021-02-03 03:04:32161 ->GetRenderViewHost()
162 ->GetRoutingID();
163 EXPECT_NE(*target_routing_id, shell->web_contents()
Dave Tapuska327c06c92022-06-13 20:31:51164 ->GetPrimaryMainFrame()
Lucas Furukawa Gadanie7649422021-02-03 03:04:32165 ->GetRenderViewHost()
166 ->GetRoutingID());
[email protected]04cbd3d2013-12-04 04:58:20167
168 // Now, simulate a link click coming from the renderer.
Aaron Colwelle953e562019-07-24 16:47:36169 GURL extension_url("http://bar.com/simple_page.html");
[email protected]04cbd3d2013-12-04 04:58:20170 WebContentsImpl* wc = static_cast<WebContentsImpl*>(shell->web_contents());
Jiacheng Guo4bdd0be2024-06-11 23:35:21171 TestNavigationManager navigation_manager(wc, extension_url);
Carlos Caballero15caeeb2021-10-27 09:57:55172 wc->GetPrimaryFrameTree().root()->navigator().RequestOpenURL(
173 wc->GetPrimaryFrameTree().root()->current_frame_host(), extension_url,
Antonio Sartori9a82f6f32020-12-14 09:22:45174 nullptr /* initiator_frame_token */,
175 ChildProcessHost::kInvalidUniqueID /* initiator_process_id */,
Arthur Sonzognic686e8f2024-01-11 08:36:37176 url::Origin::Create(foo), /* initiator_base_url= */ std::nullopt, nullptr,
177 std::string(), Referrer(), WindowOpenDisposition::CURRENT_TAB,
Garrett Tanzer6c807e32022-04-14 17:33:58178 false /* should_replace_current_entry */, true /* user_gesture */,
Gyuyoung Kim16a12f52020-12-19 04:24:26179 blink::mojom::TriggeringEventInfo::kFromTrustedEvent, std::string(),
Kevin McNee6455638a2024-06-27 22:05:03180 nullptr /* blob_url_loader_factory */, std::nullopt /* impression */,
181 false /* has_rel_opener */);
Jiacheng Guo4bdd0be2024-06-11 23:35:21182 navigation_manager.WaitForSpeculativeRenderFrameHostCreation();
[email protected]04cbd3d2013-12-04 04:58:20183
184 // Since the navigation above requires a cross-process swap, there will be a
carloskc49005eb2015-06-16 11:25:07185 // speculative/pending RenderFrameHost. Ensure it exists and is in a different
186 // process than the initial page.
Sharon Yangf60bc942022-12-01 02:14:07187 RenderFrameHostImpl* next_rfh = wc->GetPrimaryFrameTree()
188 .root()
189 ->render_manager()
190 ->speculative_frame_host();
[email protected]04cbd3d2013-12-04 04:58:20191
carloskc49005eb2015-06-16 11:25:07192 EXPECT_TRUE(next_rfh);
Emily Andrewsd15fd762024-12-10 20:41:54193 EXPECT_NE(shell->web_contents()
194 ->GetPrimaryMainFrame()
195 ->GetProcess()
196 ->GetDeprecatedID(),
197 next_rfh->GetProcess()->GetDeprecatedID());
carloskc49005eb2015-06-16 11:25:07198
Alex Moshchuk27caae82017-09-11 23:11:18199 return next_rfh;
[email protected]04cbd3d2013-12-04 04:58:20200}
201
Yeunjoo Choi9232ea32021-03-08 16:25:17202blink::mojom::OpenURLParamsPtr CreateOpenURLParams(const GURL& url) {
203 auto params = blink::mojom::OpenURLParams::New();
Gyuyoung Kim0028790a2020-06-26 00:09:00204 params->url = url;
205 params->disposition = WindowOpenDisposition::CURRENT_TAB;
206 params->should_replace_current_entry = false;
207 params->user_gesture = true;
208 return params;
209}
210
Alex Moshchuk20780962018-09-27 23:17:04211std::unique_ptr<content::BlobHandle> CreateMemoryBackedBlob(
212 BrowserContext* browser_context,
213 const std::string& contents,
214 const std::string& content_type) {
215 std::unique_ptr<content::BlobHandle> result;
216 base::RunLoop loop;
Lukasz Anforowicz7ef1cfd2021-05-04 02:18:37217 browser_context->CreateMemoryBackedBlob(
Peter Kasting5f6928c2024-11-29 21:25:11218 base::as_byte_span(contents), content_type,
Alex Moshchuk20780962018-09-27 23:17:04219 base::BindOnce(
220 [](std::unique_ptr<content::BlobHandle>* out_blob,
221 base::OnceClosure done,
222 std::unique_ptr<content::BlobHandle> blob) {
223 *out_blob = std::move(blob);
224 std::move(done).Run();
225 },
226 &result, loop.QuitClosure()));
227 loop.Run();
228 EXPECT_TRUE(result);
229 return result;
230}
231
Kent Tamura4a3482692019-03-14 01:44:27232// Constructs a WebContentsDelegate that mocks a file dialog.
233// Unlike content::FileChooserDelegate, this class doesn't make a response in
234// RunFileChooser(), and a user needs to call Choose().
235class DelayedFileChooserDelegate : public WebContentsDelegate {
236 public:
237 void Choose(const base::FilePath& file) {
238 auto file_info = blink::mojom::FileChooserFileInfo::NewNativeFile(
Joel Hockey0174ba72024-11-12 00:23:47239 blink::mojom::NativeFileInfo::New(file, std::u16string(),
240 std::vector<std::u16string>()));
Kent Tamura4a3482692019-03-14 01:44:27241 std::vector<blink::mojom::FileChooserFileInfoPtr> files;
242 files.push_back(std::move(file_info));
243 listener_->FileSelected(std::move(files), base::FilePath(),
244 blink::mojom::FileChooserParams::Mode::kOpen);
245 listener_.reset();
246 }
247
248 // WebContentsDelegate overrides
249 void RunFileChooser(RenderFrameHost* render_frame_host,
Kent Tamura3abb32d2020-07-02 00:23:01250 scoped_refptr<FileSelectListener> listener,
Kent Tamura4a3482692019-03-14 01:44:27251 const blink::mojom::FileChooserParams& params) override {
252 listener_ = std::move(listener);
253 }
254
255 void EnumerateDirectory(WebContents* web_contents,
Kent Tamura3abb32d2020-07-02 00:23:01256 scoped_refptr<FileSelectListener> listener,
Kent Tamura4a3482692019-03-14 01:44:27257 const base::FilePath& directory_path) override {
258 listener->FileSelectionCanceled();
259 }
260
261 private:
Kent Tamura3abb32d2020-07-02 00:23:01262 scoped_refptr<FileSelectListener> listener_;
Kent Tamura4a3482692019-03-14 01:44:27263};
264
265void FileChooserCallback(base::RunLoop* run_loop,
266 blink::mojom::FileChooserResultPtr result) {
267 run_loop->Quit();
268}
269
[email protected]04cbd3d2013-12-04 04:58:20270} // namespace
271
[email protected]81374f22013-02-07 02:03:45272// The goal of these tests will be to "simulate" exploited renderer processes,
273// which can send arbitrary IPC messages and confuse browser process internal
274// state, leading to security bugs. We are trying to verify that the browser
275// doesn't perform any dangerous operations in such cases.
276class SecurityExploitBrowserTest : public ContentBrowserTest {
277 public:
278 SecurityExploitBrowserTest() {}
nick4c8dfd42014-11-14 04:11:49279
avi83883c82014-12-23 00:08:49280 void SetUpCommandLine(base::CommandLine* command_line) override {
gab0fa95f0d2017-01-14 01:38:06281 // EmbeddedTestServer::InitializeAndListen() initializes its |base_url_|
282 // which is required below. This cannot invoke Start() however as that kicks
283 // off the "EmbeddedTestServer IO Thread" which then races with
284 // initialization in ContentBrowserTest::SetUp(), http://crbug.com/674545.
285 ASSERT_TRUE(embedded_test_server()->InitializeAndListen());
[email protected]81374f22013-02-07 02:03:45286
287 // Add a host resolver rule to map all outgoing requests to the test server.
288 // This allows us to use "real" hostnames in URLs, which we can use to
289 // create arbitrary SiteInstances.
290 command_line->AppendSwitchASCII(
John Abd-El-Malek91243b32018-01-19 06:08:33291 network::switches::kHostResolverRules,
nick4c8dfd42014-11-14 04:11:49292 "MAP * " +
293 net::HostPortPair::FromURL(embedded_test_server()->base_url())
294 .ToString() +
[email protected]81374f22013-02-07 02:03:45295 ",EXCLUDE localhost");
296 }
wfh815c4872015-02-25 21:01:31297
gzobqq1af4fad2016-01-30 13:07:06298 void SetUpOnMainThread() override {
gab0fa95f0d2017-01-14 01:38:06299 // Complete the manual Start() after ContentBrowserTest's own
300 // initialization, ref. comment on InitializeAndListen() above.
301 embedded_test_server()->StartAcceptingConnections();
gzobqq1af4fad2016-01-30 13:07:06302 }
303
wfh815c4872015-02-25 21:01:31304 protected:
naskoada75b22016-06-11 16:09:46305 // Tests that a given file path sent in a FrameHostMsg_RunFileChooser will
wfh815c4872015-02-25 21:01:31306 // cause renderer to be killed.
307 void TestFileChooserWithPath(const base::FilePath& path);
Lukasz Anforowicz56211d452019-02-05 18:05:51308
309 void IsolateOrigin(const std::string& hostname) {
Lukasz Anforowicz3cfc1efd2019-02-22 02:29:29310 IsolateOriginsForTesting(embedded_test_server(), shell()->web_contents(),
311 {hostname});
Lukasz Anforowicz56211d452019-02-05 18:05:51312 }
[email protected]81374f22013-02-07 02:03:45313};
314
wfh815c4872015-02-25 21:01:31315void SecurityExploitBrowserTest::TestFileChooserWithPath(
316 const base::FilePath& path) {
317 GURL foo("http://foo.com/simple_page.html");
Alex Moshchuk4174c192019-08-20 16:58:09318 EXPECT_TRUE(NavigateToURL(shell(), foo));
Jan Wilken Dörrie8aeb5742021-03-23 19:27:02319 EXPECT_EQ(u"OK", shell()->web_contents()->GetTitle());
wfh815c4872015-02-25 21:01:31320
naskoada75b22016-06-11 16:09:46321 RenderFrameHost* compromised_renderer =
Dave Tapuska327c06c92022-06-13 20:31:51322 shell()->web_contents()->GetPrimaryMainFrame();
Kent Tamura8c9e0562018-11-06 07:02:19323 blink::mojom::FileChooserParamsPtr params =
324 blink::mojom::FileChooserParams::New();
325 params->default_file_name = path;
wfh815c4872015-02-25 21:01:31326
Lukasz Anforowicz288027b2019-01-17 01:51:27327 mojo::test::BadMessageObserver bad_message_observer;
Avi Drissmanbd3e986442020-05-20 21:09:20328 mojo::Remote<blink::mojom::FileChooser> chooser =
329 FileChooserImpl::CreateBoundForTesting(
330 static_cast<RenderFrameHostImpl*>(compromised_renderer));
331 chooser->OpenFileChooser(
Kent Tamura8c9e0562018-11-06 07:02:19332 std::move(params), blink::mojom::FileChooser::OpenFileChooserCallback());
Avi Drissmanbd3e986442020-05-20 21:09:20333 chooser.FlushForTesting();
Lukasz Anforowicz288027b2019-01-17 01:51:27334 EXPECT_THAT(bad_message_observer.WaitForBadMessage(),
335 ::testing::StartsWith("FileChooser: The default file name"));
wfh815c4872015-02-25 21:01:31336}
337
[email protected]81374f22013-02-07 02:03:45338// Ensure that we kill the renderer process if we try to give it WebUI
339// properties and it doesn't have enabled WebUI bindings.
jaekyun37e572a32014-12-04 23:33:35340IN_PROC_BROWSER_TEST_F(SecurityExploitBrowserTest, SetWebUIProperty) {
nick4c8dfd42014-11-14 04:11:49341 GURL foo("http://foo.com/simple_page.html");
[email protected]81374f22013-02-07 02:03:45342
Alex Moshchuk4174c192019-08-20 16:58:09343 EXPECT_TRUE(NavigateToURL(shell(), foo));
Jan Wilken Dörrie8aeb5742021-03-23 19:27:02344 EXPECT_EQ(u"OK", shell()->web_contents()->GetTitle());
Avi Drissman78865bbb2024-08-22 20:57:19345 EXPECT_TRUE(shell()
346 ->web_contents()
347 ->GetPrimaryMainFrame()
348 ->GetEnabledBindings()
349 .empty());
[email protected]81374f22013-02-07 02:03:45350
Nasko Oskovdce1a622019-11-06 23:58:33351 RenderFrameHost* compromised_renderer =
Dave Tapuska327c06c92022-06-13 20:31:51352 shell()->web_contents()->GetPrimaryMainFrame();
Lukasz Anforowicz1034c0882020-03-21 01:24:53353 RenderProcessHostBadIpcMessageWaiter kill_waiter(
354 compromised_renderer->GetProcess());
Lukasz Anforowicz6f746282018-01-04 23:24:51355 compromised_renderer->SetWebUIProperty("toolkit", "views");
356 EXPECT_EQ(bad_message::RVH_WEB_UI_BINDINGS_MISMATCH, kill_waiter.Wait());
[email protected]81374f22013-02-07 02:03:45357}
358
[email protected]04cbd3d2013-12-04 04:58:20359// This is a test for crbug.com/312016 attempting to create duplicate
360// RenderViewHosts. SetupForDuplicateHosts sets up this test case and leaves
361// it in a state with pending RenderViewHost. Before the commit of the new
362// pending RenderViewHost, this test case creates a new window through the new
363// process.
364IN_PROC_BROWSER_TEST_F(SecurityExploitBrowserTest,
jam5f61aa02017-03-21 20:30:39365 AttemptDuplicateRenderViewHost) {
Tom Sepezcc69c442025-07-17 02:00:30366 int32_t duplicate_routing_id = IPC::mojom::kRoutingIdNone;
Aaron Colwelle953e562019-07-24 16:47:36367 RenderFrameHostImpl* pending_rfh = PrepareToDuplicateHosts(
368 shell(), embedded_test_server(), &duplicate_routing_id);
Tom Sepezcc69c442025-07-17 02:00:30369 EXPECT_NE(IPC::mojom::kRoutingIdNone, duplicate_routing_id);
[email protected]04cbd3d2013-12-04 04:58:20370
nickb3740422017-04-24 22:27:45371 mojom::CreateNewWindowParamsPtr params = mojom::CreateNewWindowParams::New();
372 params->target_url = GURL("about:blank");
Alex Moshchuk27caae82017-09-11 23:11:18373 pending_rfh->CreateNewWindow(
Daniel Cheng8bb30c62017-10-23 20:40:47374 std::move(params), base::BindOnce([](mojom::CreateNewWindowStatus,
375 mojom::CreateNewWindowReplyPtr) {}));
[email protected]04cbd3d2013-12-04 04:58:20376 // If the above operation doesn't cause a crash, the test has succeeded!
[email protected]81374f22013-02-07 02:03:45377}
[email protected]04cbd3d2013-12-04 04:58:20378
wfh815c4872015-02-25 21:01:31379// This is a test for crbug.com/444198. It tries to send a
naskoada75b22016-06-11 16:09:46380// FrameHostMsg_RunFileChooser containing an invalid path. The browser should
wfh815c4872015-02-25 21:01:31381// correctly terminate the renderer in these cases.
382IN_PROC_BROWSER_TEST_F(SecurityExploitBrowserTest, AttemptRunFileChoosers) {
383 TestFileChooserWithPath(base::FilePath(FILE_PATH_LITERAL("../../*.txt")));
384 TestFileChooserWithPath(base::FilePath(FILE_PATH_LITERAL("/etc/*.conf")));
Xiaohan Wang1ecfd002022-01-19 22:33:10385#if BUILDFLAG(IS_WIN)
wfh815c4872015-02-25 21:01:31386 TestFileChooserWithPath(
387 base::FilePath(FILE_PATH_LITERAL("\\\\evilserver\\evilshare\\*.txt")));
388 TestFileChooserWithPath(base::FilePath(FILE_PATH_LITERAL("c:\\*.txt")));
389 TestFileChooserWithPath(base::FilePath(FILE_PATH_LITERAL("..\\..\\*.txt")));
390#endif
391}
392
Kent Tamura4a3482692019-03-14 01:44:27393// A test for crbug.com/941008.
394// Calling OpenFileChooser() and EnumerateChosenDirectory() for a single
395// FileChooser instance had a problem.
396IN_PROC_BROWSER_TEST_F(SecurityExploitBrowserTest, UnexpectedMethodsSequence) {
Alex Moshchuk4174c192019-08-20 16:58:09397 EXPECT_TRUE(NavigateToURL(shell(), GURL("http://foo.com/simple_page.html")));
Kent Tamura4a3482692019-03-14 01:44:27398 RenderFrameHost* compromised_renderer =
Dave Tapuska327c06c92022-06-13 20:31:51399 shell()->web_contents()->GetPrimaryMainFrame();
Kent Tamura4a3482692019-03-14 01:44:27400 auto delegate = std::make_unique<DelayedFileChooserDelegate>();
401 shell()->web_contents()->SetDelegate(delegate.get());
402
Henrique Ferreiro3bb206b2019-09-18 12:48:23403 mojo::Remote<blink::mojom::FileChooser> chooser =
Avi Drissmanbd3e986442020-05-20 21:09:20404 FileChooserImpl::CreateBoundForTesting(
405 static_cast<RenderFrameHostImpl*>(compromised_renderer));
Kent Tamura4a3482692019-03-14 01:44:27406 base::RunLoop run_loop1;
407 base::RunLoop run_loop2;
408 chooser->OpenFileChooser(blink::mojom::FileChooserParams::New(),
409 base::BindOnce(FileChooserCallback, &run_loop2));
410 // The following EnumerateChosenDirectory() runs the specified callback
411 // immediately regardless of the content of the first argument FilePath.
412 chooser->EnumerateChosenDirectory(
413 base::FilePath(FILE_PATH_LITERAL(":*?\"<>|")),
414 base::BindOnce(FileChooserCallback, &run_loop1));
415 run_loop1.Run();
416
417 delegate->Choose(base::FilePath(FILE_PATH_LITERAL("foo.txt")));
418 run_loop2.Run();
419
420 // The test passes if it doesn't crash.
421}
422
Hiroshige Hayashizaki8ee3f7e62019-09-05 07:17:47423class CorsExploitBrowserTest : public ContentBrowserTest {
424 public:
Yutaka Hirano235e8922020-03-17 23:44:32425 CorsExploitBrowserTest() = default;
Hiroshige Hayashizaki8ee3f7e62019-09-05 07:17:47426
Peter Boström9b036532021-10-28 23:37:28427 CorsExploitBrowserTest(const CorsExploitBrowserTest&) = delete;
428 CorsExploitBrowserTest& operator=(const CorsExploitBrowserTest&) = delete;
429
Hiroshige Hayashizaki8ee3f7e62019-09-05 07:17:47430 void SetUpOnMainThread() override {
431 host_resolver()->AddRule("*", "127.0.0.1");
432 SetupCrossSiteRedirector(embedded_test_server());
433 }
Hiroshige Hayashizaki8ee3f7e62019-09-05 07:17:47434};
435
nasko402cc292016-03-11 09:16:11436// Test that receiving a commit with incorrect origin properly terminates the
437// renderer process.
438IN_PROC_BROWSER_TEST_F(SecurityExploitBrowserTest, MismatchedOriginOnCommit) {
439 GURL start_url(embedded_test_server()->GetURL("/title1.html"));
440 EXPECT_TRUE(NavigateToURL(shell(), start_url));
441
442 FrameTreeNode* root = static_cast<WebContentsImpl*>(shell()->web_contents())
Carlos Caballero15caeeb2021-10-27 09:57:55443 ->GetPrimaryFrameTree()
444 .root();
nasko402cc292016-03-11 09:16:11445
Charlie Reise2c2c492018-06-15 21:34:04446 // Navigate to a new URL, with an interceptor that replaces the origin with
447 // one that does not match params.url.
448 GURL url(embedded_test_server()->GetURL("/title2.html"));
449 PwnCommitIPC(shell()->web_contents(), url, url,
450 url::Origin::Create(GURL("http://bar.com/")));
nasko402cc292016-03-11 09:16:11451
452 // Use LoadURL, as the test shouldn't wait for navigation commit.
453 NavigationController& controller = shell()->web_contents()->GetController();
454 controller.LoadURL(url, Referrer(), ui::PAGE_TRANSITION_LINK, std::string());
455 EXPECT_NE(nullptr, controller.GetPendingEntry());
456 EXPECT_EQ(url, controller.GetPendingEntry()->GetURL());
457
Lukasz Anforowicz1034c0882020-03-21 01:24:53458 RenderProcessHostBadIpcMessageWaiter kill_waiter(
Lukasz Anforowicz6f746282018-01-04 23:24:51459 root->current_frame_host()->GetProcess());
nasko402cc292016-03-11 09:16:11460
nasko402cc292016-03-11 09:16:11461 // When the IPC message is received and validation fails, the process is
462 // terminated. However, the notification for that should be processed in a
463 // separate task of the message loop, so ensure that the process is still
464 // considered alive.
Lukasz Anforowicz5510ed652018-06-06 16:16:19465 EXPECT_TRUE(
466 root->current_frame_host()->GetProcess()->IsInitializedAndNotDead());
nasko402cc292016-03-11 09:16:11467
Lukasz Anforowicz6f746282018-01-04 23:24:51468 EXPECT_EQ(bad_message::RFH_INVALID_ORIGIN_ON_COMMIT, kill_waiter.Wait());
nasko402cc292016-03-11 09:16:11469}
470
Rakina Zata Amnid09b6112021-06-05 06:20:14471// Test that receiving a document.open() URL update with incorrect origin
472// properly terminates the renderer process.
473IN_PROC_BROWSER_TEST_F(SecurityExploitBrowserTest,
474 MismatchedOriginOnDocumentOpenURLUpdate) {
475 GURL start_url(embedded_test_server()->GetURL("/title1.html"));
476 EXPECT_TRUE(NavigateToURL(shell(), start_url));
477
478 RenderFrameHostImpl* rfh = static_cast<RenderFrameHostImpl*>(
Dave Tapuska327c06c92022-06-13 20:31:51479 shell()->web_contents()->GetPrimaryMainFrame());
Rakina Zata Amnid09b6112021-06-05 06:20:14480
481 // Simulate a document.open() URL update with incorrect origin.
482 RenderProcessHostBadIpcMessageWaiter kill_waiter(rfh->GetProcess());
483 static_cast<mojom::FrameHost*>(rfh)->DidOpenDocumentInputStream(
484 embedded_test_server()->GetURL("evil.com", "/title1.html"));
485
486 // Ensure that the renderer process gets killed.
487 EXPECT_EQ(AreAllSitesIsolatedForTesting()
488 ? bad_message::RFH_CAN_COMMIT_URL_BLOCKED
489 : bad_message::RFH_INVALID_ORIGIN_ON_COMMIT,
490 kill_waiter.Wait());
491}
492
Charlie Reis95fbca442021-05-21 21:38:24493// Test that same-document navigations cannot go cross-origin (even within the
494// same site).
495IN_PROC_BROWSER_TEST_F(SecurityExploitBrowserTest,
496 CrossOriginSameDocumentCommit) {
497 GURL start_url(embedded_test_server()->GetURL("foo.com", "/title1.html"));
498 EXPECT_TRUE(NavigateToURL(shell(), start_url));
499
500 // Do a same-document navigation to a cross-origin URL/Origin (which match
501 // each other, unlike the MismatchedOriginOnCommit), using an interceptor that
502 // replaces the origin and URL. This intentionally uses a cross-origin but
503 // same-site destination, to avoid failing Site Isolation checks.
504 GURL dest_url(embedded_test_server()->GetURL("bar.foo.com", "/title2.html"));
505 PwnCommitIPC(shell()->web_contents(), start_url, dest_url,
506 url::Origin::Create(dest_url));
507 RenderProcessHostBadIpcMessageWaiter kill_waiter(
Dave Tapuska327c06c92022-06-13 20:31:51508 shell()->web_contents()->GetPrimaryMainFrame()->GetProcess());
Charlie Reis95fbca442021-05-21 21:38:24509 // ExecJs will sometimes finish before the renderer gets killed, so we must
510 // ignore the result.
Dave Tapuska327c06c92022-06-13 20:31:51511 std::ignore = ExecJs(shell()->web_contents()->GetPrimaryMainFrame(),
Avi Drissman5d5d48d62022-01-07 20:23:58512 "history.pushState({}, '', location.href);");
Charlie Reis95fbca442021-05-21 21:38:24513 EXPECT_EQ(bad_message::RFH_INVALID_ORIGIN_ON_COMMIT, kill_waiter.Wait());
514}
515
516// Test that same-document navigations cannot go cross-origin from about:blank
517// (even within the same site). Uses a subframe to inherit an existing origin.
518IN_PROC_BROWSER_TEST_F(SecurityExploitBrowserTest,
519 CrossOriginSameDocumentCommitFromAboutBlank) {
520 GURL start_url(embedded_test_server()->GetURL("foo.com", "/title1.html"));
521 EXPECT_TRUE(NavigateToURL(shell(), start_url));
522
523 // Create an about:blank iframe that inherits the origin.
524 RenderFrameHost* subframe =
525 CreateSubframe(static_cast<WebContentsImpl*>(shell()->web_contents()),
526 "child1", GURL(), false /* wait_for_navigation */);
527 EXPECT_EQ(url::Origin::Create(start_url), subframe->GetLastCommittedOrigin());
528
529 // Do a same-document navigation to another about:blank URL, but using a
530 // different origin. This intentionally uses a cross-origin but same-site
531 // origin to avoid triggering Site Isolation checks.
532 GURL blank_url("about:blank#foo");
533 GURL fake_url(embedded_test_server()->GetURL("bar.foo.com", "/"));
534 PwnCommitIPC(shell()->web_contents(), blank_url, blank_url,
535 url::Origin::Create(fake_url));
536 RenderProcessHostBadIpcMessageWaiter kill_waiter(subframe->GetProcess());
537 // ExecJs will sometimes finish before the renderer gets killed, so we must
538 // ignore the result.
Avi Drissman5d5d48d62022-01-07 20:23:58539 std::ignore = ExecJs(subframe, "location.hash='foo';");
Charlie Reis95fbca442021-05-21 21:38:24540 EXPECT_EQ(bad_message::RFH_INVALID_ORIGIN_ON_COMMIT, kill_waiter.Wait());
541}
542
543// Test that same-document navigations cannot go cross-origin (even within the
544// same site), in the case that allow_universal_access_from_file_urls is enabled
545// but the last committed origin is not a file URL. See also
546// RenderFrameHostManagerTest.EnsureUniversalAccessFromFileSchemeSucceeds for
547// the intended case that file URLs are allowed to go cross-origin.
548IN_PROC_BROWSER_TEST_F(SecurityExploitBrowserTest,
549 CrossOriginSameDocumentCommitUniversalAccessNonFile) {
550 auto prefs = shell()->web_contents()->GetOrCreateWebPreferences();
551 prefs.allow_universal_access_from_file_urls = true;
552 shell()->web_contents()->SetWebPreferences(prefs);
553
554 GURL start_url(embedded_test_server()->GetURL("foo.com", "/title1.html"));
555 EXPECT_TRUE(NavigateToURL(shell(), start_url));
556
557 // Do a same-document navigation to a cross-origin URL, using an interceptor
558 // that replaces the URL but not the origin (to simulate the universal access
559 // case, but for a non-file committed origin). This intentionally uses a
560 // cross-origin but same-site destination, to avoid failing Site Isolation
561 // checks.
562 GURL dest_url(embedded_test_server()->GetURL("bar.foo.com", "/title2.html"));
563 PwnCommitIPC(shell()->web_contents(), start_url, dest_url,
564 url::Origin::Create(start_url));
565 RenderProcessHostBadIpcMessageWaiter kill_waiter(
Dave Tapuska327c06c92022-06-13 20:31:51566 shell()->web_contents()->GetPrimaryMainFrame()->GetProcess());
Charlie Reis95fbca442021-05-21 21:38:24567 // ExecJs will sometimes finish before the renderer gets killed, so we must
568 // ignore the result.
Dave Tapuska327c06c92022-06-13 20:31:51569 std::ignore = ExecJs(shell()->web_contents()->GetPrimaryMainFrame(),
Avi Drissman5d5d48d62022-01-07 20:23:58570 "history.pushState({}, '', location.href);");
Charlie Reis95fbca442021-05-21 21:38:24571 EXPECT_EQ(bad_message::RFH_INVALID_ORIGIN_ON_COMMIT, kill_waiter.Wait());
572}
573
Charlie Reisc8e511e2024-02-14 17:23:06574// Test that receiving a commit with a URL with an invalid scheme properly
575// terminates the renderer process. See https://crbug.com/324934416.
Alison Gale770f3fc2024-04-27 00:39:58576// TODO(crbug.com/40092527): This test can be removed once the browser
Charlie Reisc8e511e2024-02-14 17:23:06577// stops using cross-document URLs computed by the renderer process.
578IN_PROC_BROWSER_TEST_F(SecurityExploitBrowserTest, BadUrlSchemeOnCommit) {
579 GURL start_url(embedded_test_server()->GetURL("foo.com", "/title1.html"));
580 EXPECT_TRUE(NavigateToURL(shell(), start_url));
581
582 FrameTreeNode* root = static_cast<WebContentsImpl*>(shell()->web_contents())
583 ->GetPrimaryFrameTree()
584 .root();
585
586 // Navigate to a new URL, with an interceptor that replaces the URL with one
587 // that has an illegal scheme. Note that most cross-document navigations where
588 // the renderer's commit URL disagrees with the browser's expectation will
589 // currently be caught by a DCHECK in debug builds, but this case still works
590 // in release builds until the browser process becomes the authority for
591 // cross-document URLs in https://crbug.com/888079. For now, we can test this
592 // case and avoid the DCHECK by claiming to commit about:blank#blocked, which
593 // is given an exception in RenderFrameHostImpl's CalculateLoadingURL.
594 GURL url("about:blank#blocked");
595 GURL bad_scheme_url("bar:com");
596 PwnCommitIPC(shell()->web_contents(), url, bad_scheme_url,
597 url::Origin::Create(url));
598
599 RenderProcessHost* process = root->current_frame_host()->GetProcess();
600 RenderProcessHostBadIpcMessageWaiter kill_waiter(process);
601
602 // ExecJs will sometimes finish before the renderer gets killed, so we must
603 // ignore the result.
604 std::ignore = ExecJs(shell()->web_contents()->GetPrimaryMainFrame(),
605 "location.href = 'about:blank#blocked';");
606
607 // When the IPC message is received and validation fails, the process is
608 // terminated. However, the notification for that should be processed in a
609 // separate task of the message loop, so ensure that the process is still
610 // considered alive.
611 EXPECT_TRUE(process->IsInitializedAndNotDead());
612
613 EXPECT_EQ(bad_message::RFH_CAN_COMMIT_URL_BLOCKED, kill_waiter.Wait());
614}
615
616// Test that receiving a same-document commit with a URL with an invalid scheme
617// properly terminates the renderer process. See https://crbug.com/324934416.
618IN_PROC_BROWSER_TEST_F(SecurityExploitBrowserTest,
619 BadUrlSchemeOnSameDocumentCommit) {
620 GURL start_url(embedded_test_server()->GetURL("foo.com", "/title1.html"));
621 EXPECT_TRUE(NavigateToURL(shell(), start_url));
622
623 // Do a same-document navigation to a URL with an incorrect scheme, but with
624 // the expected origin, using an interceptor that replaces the URL.
625 GURL dest_url("bar:com");
626 PwnCommitIPC(shell()->web_contents(), start_url, dest_url,
627 url::Origin::Create(start_url));
628 RenderProcessHostBadIpcMessageWaiter kill_waiter(
629 shell()->web_contents()->GetPrimaryMainFrame()->GetProcess());
630 // ExecJs will sometimes finish before the renderer gets killed, so we must
631 // ignore the result.
632 std::ignore = ExecJs(shell()->web_contents()->GetPrimaryMainFrame(),
633 "history.pushState({}, '', location.href);");
634 EXPECT_EQ(bad_message::RFH_CAN_COMMIT_URL_BLOCKED, kill_waiter.Wait());
635}
636
Balazs Engedy0c8d550b2017-12-06 21:30:23637namespace {
638
Oksana Zhuravlova8b88e572019-01-07 21:54:00639// Interceptor that replaces |interface_params| with the specified
Balazs Engedy0c8d550b2017-12-06 21:30:23640// value for the first DidCommitProvisionalLoad message it observes in the given
641// |web_contents| while in scope.
Arthur Hemery2a0a28be2019-03-06 17:51:36642class ScopedInterfaceParamsReplacer : public DidCommitNavigationInterceptor {
Balazs Engedy0c8d550b2017-12-06 21:30:23643 public:
Oksana Zhuravlova8b88e572019-01-07 21:54:00644 ScopedInterfaceParamsReplacer(
Balazs Engedy0c8d550b2017-12-06 21:30:23645 WebContents* web_contents,
Oksana Zhuravlova8b88e572019-01-07 21:54:00646 mojom::DidCommitProvisionalLoadInterfaceParamsPtr params_override)
Arthur Hemery2a0a28be2019-03-06 17:51:36647 : DidCommitNavigationInterceptor(web_contents),
Oksana Zhuravlova8b88e572019-01-07 21:54:00648 params_override_(std::move(params_override)) {}
Peter Boström828b9022021-09-21 02:28:43649
650 ScopedInterfaceParamsReplacer(const ScopedInterfaceParamsReplacer&) = delete;
651 ScopedInterfaceParamsReplacer& operator=(
652 const ScopedInterfaceParamsReplacer&) = delete;
653
Oksana Zhuravlova8b88e572019-01-07 21:54:00654 ~ScopedInterfaceParamsReplacer() override = default;
Balazs Engedy0c8d550b2017-12-06 21:30:23655
656 protected:
Arthur Hemery2a0a28be2019-03-06 17:51:36657 bool WillProcessDidCommitNavigation(
Balazs Engedy0c8d550b2017-12-06 21:30:23658 RenderFrameHost* render_frame_host,
Arthur Hemery2a0a28be2019-03-06 17:51:36659 NavigationRequest* navigation_request,
arthursonzogni73fe3212020-11-17 13:24:07660 mojom::DidCommitProvisionalLoadParamsPtr*,
Lukasz Anforowicz3cfc1efd2019-02-22 02:29:29661 mojom::DidCommitProvisionalLoadInterfaceParamsPtr* interface_params)
Oksana Zhuravlova8b88e572019-01-07 21:54:00662 override {
Lukasz Anforowicz3cfc1efd2019-02-22 02:29:29663 interface_params->Swap(&params_override_);
Oksana Zhuravlova8b88e572019-01-07 21:54:00664
clamyd3bfdb02018-07-12 13:52:18665 return true;
Balazs Engedy0c8d550b2017-12-06 21:30:23666 }
667
668 private:
Oksana Zhuravlova8b88e572019-01-07 21:54:00669 mojom::DidCommitProvisionalLoadInterfaceParamsPtr params_override_;
Balazs Engedy0c8d550b2017-12-06 21:30:23670};
671
672} // namespace
673
Oksana Zhuravlova8b88e572019-01-07 21:54:00674// Test that, as a general rule, not receiving new
675// DidCommitProvisionalLoadInterfaceParamsPtr for a cross-document navigation
676// properly terminates the renderer process. There is one exception to this
677// rule, see: RenderFrameHostImplBrowserTest.
Balazs Engedy0c8d550b2017-12-06 21:30:23678// InterfaceProviderRequestIsOptionalForFirstCommit.
Alison Gale770f3fc2024-04-27 00:39:58679// TODO(crbug.com/40519010): when all clients are converted to use
Julie Jeongeun Kim641f4752019-12-04 01:53:22680// BrowserInterfaceBroker, PendingReceiver<InterfaceProvider>-related code will
681// be removed.
Balazs Engedy0c8d550b2017-12-06 21:30:23682IN_PROC_BROWSER_TEST_F(SecurityExploitBrowserTest,
683 MissingInterfaceProviderOnNonSameDocumentCommit) {
684 const GURL start_url(embedded_test_server()->GetURL("/title1.html"));
685 const GURL non_same_document_url(
686 embedded_test_server()->GetURL("/title2.html"));
687
688 EXPECT_TRUE(NavigateToURL(shell(), start_url));
689
Lukasz Anforowicz9904bfa32018-07-31 22:19:55690 RenderFrameHostImpl* frame = static_cast<RenderFrameHostImpl*>(
Dave Tapuska327c06c92022-06-13 20:31:51691 shell()->web_contents()->GetPrimaryMainFrame());
Lukasz Anforowicz1034c0882020-03-21 01:24:53692 RenderProcessHostBadIpcMessageWaiter kill_waiter(frame->GetProcess());
Balazs Engedy0c8d550b2017-12-06 21:30:23693
arthursonzogni85bc1162019-10-15 14:44:13694 NavigationHandleObserver navigation_observer(shell()->web_contents(),
695 non_same_document_url);
Oksana Zhuravlova8b88e572019-01-07 21:54:00696 ScopedInterfaceParamsReplacer replacer(shell()->web_contents(), nullptr);
Alex Moshchuk8969de22019-09-28 01:12:26697 EXPECT_TRUE(NavigateToURLAndExpectNoCommit(shell(), non_same_document_url));
Lukasz Anforowicz6f746282018-01-04 23:24:51698 EXPECT_EQ(bad_message::RFH_INTERFACE_PROVIDER_MISSING, kill_waiter.Wait());
Lukasz Anforowicz9904bfa32018-07-31 22:19:55699
arthursonzogni85bc1162019-10-15 14:44:13700 // Verify that the death of the renderer process doesn't leave behind and
701 // leak NavigationRequests - see https://crbug.com/869193.
Alexander Timin26864e12019-10-18 02:00:06702 EXPECT_FALSE(frame->HasPendingCommitNavigation());
arthursonzogni85bc1162019-10-15 14:44:13703 EXPECT_FALSE(navigation_observer.has_committed());
704 EXPECT_TRUE(navigation_observer.is_error());
705 EXPECT_TRUE(navigation_observer.last_committed_url().is_empty());
706 EXPECT_EQ(net::OK, navigation_observer.net_error_code());
Balazs Engedy0c8d550b2017-12-06 21:30:23707}
708
lukasza4ec2e7572017-05-26 23:18:10709// Test that a compromised renderer cannot ask to upload an arbitrary file in
710// OpenURL. This is a regression test for https://crbug.com/726067.
711IN_PROC_BROWSER_TEST_F(SecurityExploitBrowserTest,
712 OpenUrl_ResourceRequestBody) {
713 GURL start_url(embedded_test_server()->GetURL("/title1.html"));
714 GURL target_url(embedded_test_server()->GetURL("/echoall"));
715 EXPECT_TRUE(NavigateToURL(shell(), start_url));
716
717 FrameTreeNode* root = static_cast<WebContentsImpl*>(shell()->web_contents())
Carlos Caballero15caeeb2021-10-27 09:57:55718 ->GetPrimaryFrameTree()
719 .root();
lukasza4ec2e7572017-05-26 23:18:10720
Lukasz Anforowicz1034c0882020-03-21 01:24:53721 RenderProcessHostBadIpcMessageWaiter kill_waiter(
Lukasz Anforowicz6f746282018-01-04 23:24:51722 root->current_frame_host()->GetProcess());
lukasza4ec2e7572017-05-26 23:18:10723
724 // Prepare a file to upload.
Francois Doray21cd53192018-08-21 13:32:35725 base::ScopedAllowBlockingForTesting allow_blocking;
lukasza4ec2e7572017-05-26 23:18:10726 base::ScopedTempDir temp_dir;
727 base::FilePath file_path;
728 std::string file_content("test-file-content");
729 ASSERT_TRUE(temp_dir.CreateUniqueTempDir());
730 ASSERT_TRUE(base::CreateTemporaryFileInDir(temp_dir.GetPath(), &file_path));
Lei Zhang9989f272020-05-11 19:21:21731 ASSERT_TRUE(base::WriteFile(file_path, file_content));
lukasza4ec2e7572017-05-26 23:18:10732
Gyuyoung Kim0028790a2020-06-26 00:09:00733 // Simulate an OpenURL Mojo method asking to POST a file that the renderer
734 // shouldn't have access to.
735 auto params = CreateOpenURLParams(target_url);
736 params->post_body = new network::ResourceRequestBody;
737 params->post_body->AppendFileRange(file_path, 0, file_content.size(),
738 base::Time());
739 params->should_replace_current_entry = true;
lukasza4ec2e7572017-05-26 23:18:10740
Mario Sanchez Pradacb83d6c82020-09-11 13:11:51741 static_cast<mojom::FrameHost*>(root->current_frame_host())
742 ->OpenURL(std::move(params));
lukasza4ec2e7572017-05-26 23:18:10743
744 // Verify that the malicious navigation did not commit the navigation to
745 // |target_url|.
lukasza4ec2e7572017-05-26 23:18:10746 EXPECT_EQ(start_url, root->current_frame_host()->GetLastCommittedURL());
747
748 // Verify that the malicious renderer got killed.
Lukasz Anforowicz445abd42019-01-25 21:27:27749 EXPECT_EQ(bad_message::ILLEGAL_UPLOAD_PARAMS, kill_waiter.Wait());
lukasza4ec2e7572017-05-26 23:18:10750}
751
Rakina Zata Amnia8866d22020-11-19 10:05:10752// Forging a navigation commit after the initial empty document will result in a
753// renderer kill, even if the URL used is about:blank.
754// See https://crbug.com/766262 for an example advanced case that involves
755// forging a frame's unique name.
756IN_PROC_BROWSER_TEST_F(SecurityExploitBrowserTest,
757 NonInitialAboutBlankRendererKill) {
758 // Navigate normally.
759 GURL url(embedded_test_server()->GetURL("/title1.html"));
760 EXPECT_TRUE(NavigateToURL(shell(), url));
761 RenderFrameHostImpl* rfh = static_cast<RenderFrameHostImpl*>(
Dave Tapuska327c06c92022-06-13 20:31:51762 shell()->web_contents()->GetPrimaryMainFrame());
Charles Reisc0507202017-09-21 00:40:02763
Rakina Zata Amnia8866d22020-11-19 10:05:10764 // Simulate an about:blank commit without a NavigationRequest. It will fail
765 // because only initial commits are allowed to do this.
arthursonzogni73fe3212020-11-17 13:24:07766 auto params = mojom::DidCommitProvisionalLoadParams::New();
Charles Reisc0507202017-09-21 00:40:02767 params->did_create_new_entry = false;
768 params->url = GURL("about:blank");
arthursonzogni73fe3212020-11-17 13:24:07769 params->referrer = blink::mojom::Referrer::New();
Rakina Zata Amnia8866d22020-11-19 10:05:10770 params->transition = ui::PAGE_TRANSITION_LINK;
Charles Reisc0507202017-09-21 00:40:02771 params->should_update_history = false;
Charles Reisc0507202017-09-21 00:40:02772 params->method = "GET";
Rakina Zata Amnia8866d22020-11-19 10:05:10773 params->page_state = blink::PageState::CreateFromURL(GURL("about:blank"));
Daniel Cheng88186bd52017-10-20 08:14:46774 params->origin = url::Origin::Create(GURL("about:blank"));
ckitagawa223004a2020-06-23 16:10:17775 params->embedding_token = base::UnguessableToken::Create();
Rakina Zata Amnia8866d22020-11-19 10:05:10776 RenderProcessHostBadIpcMessageWaiter kill_waiter(rfh->GetProcess());
Rakina Zata Amnia8866d22020-11-19 10:05:10777 static_cast<mojom::FrameHost*>(rfh)->DidCommitProvisionalLoad(
778 std::move(params),
779 mojom::DidCommitProvisionalLoadInterfaceParams::New(
Rakina Zata Amnia8866d22020-11-19 10:05:10780 mojo::PendingRemote<blink::mojom::BrowserInterfaceBroker>()
781 .InitWithNewPipeAndPassReceiver()));
Charles Reisc0507202017-09-21 00:40:02782
Rakina Zata Amnia8866d22020-11-19 10:05:10783 // Verify that the malicious renderer got killed.
784 EXPECT_EQ(bad_message::RFH_NO_MATCHING_NAVIGATION_REQUEST_ON_COMMIT,
785 kill_waiter.Wait());
Charles Reisc0507202017-09-21 00:40:02786}
787
Alex Moshchuk375f4de82025-05-13 17:30:48788// Make sure that a renderer is terminated if it sends an invalid net error code
789// in a DidFailLoadWithError() IPC. See https://crbug.com/407069514.
790IN_PROC_BROWSER_TEST_F(SecurityExploitBrowserTest,
791 DidFailLoadWithInvalidErrorCode) {
792 GURL url(embedded_test_server()->GetURL("/title1.html"));
793 EXPECT_TRUE(NavigateToURL(shell(), url));
794
795 // Navigate normally and wait for commit (but not full load).
796 TestFrameNavigationObserver commit_observer(shell());
797 GURL failed_url(embedded_test_server()->GetURL("/title2.html"));
798 ASSERT_TRUE(ExecJs(shell(), JsReplace("location.href = $1;", failed_url)));
799 commit_observer.WaitForCommit();
800
801 RenderFrameHostImpl* rfh = static_cast<RenderFrameHostImpl*>(
802 shell()->web_contents()->GetPrimaryMainFrame());
803
804 // Simulate a DidFailLoadWithError message with an invalid error code.
805 RenderProcessHostBadIpcMessageWaiter kill_waiter(rfh->GetProcess());
806 int32_t invalid_error_code = INT_MAX;
807 ASSERT_FALSE(net::IsOkOrDefinedError(invalid_error_code));
808 static_cast<blink::mojom::LocalFrameHost*>(rfh)->DidFailLoadWithError(
809 failed_url, invalid_error_code);
810
811 // Verify that the malicious renderer got killed.
812 EXPECT_EQ(bad_message::RFHI_INVALID_NET_ERROR_CODE, kill_waiter.Wait());
813}
814
Andrew Williams2bb0d8e2024-08-07 21:37:36815class SecurityExploitBrowserTestMojoBlobURLs
816 : public SecurityExploitBrowserTest {
817 public:
818 SecurityExploitBrowserTestMojoBlobURLs() = default;
819
820 void TearDown() override {
821 storage::BlobUrlRegistry::SetURLStoreCreationHookForTesting(nullptr);
822 }
823};
824
Alex Moshchuk20780962018-09-27 23:17:04825// Check that when site isolation is enabled, an origin can't create a blob URL
826// for a different origin. Similar to the test above, but checks the
827// mojo-based Blob URL implementation. See https://crbug.com/886976.
Andrew Williams2bb0d8e2024-08-07 21:37:36828IN_PROC_BROWSER_TEST_F(SecurityExploitBrowserTestMojoBlobURLs,
Alex Moshchuk20780962018-09-27 23:17:04829 CreateMojoBlobURLInDifferentOrigin) {
830 IsolateAllSitesForTesting(base::CommandLine::ForCurrentProcess());
831
832 GURL main_url(embedded_test_server()->GetURL("a.com", "/title1.html"));
833 EXPECT_TRUE(NavigateToURL(shell(), main_url));
Dave Tapuska327c06c92022-06-13 20:31:51834 RenderFrameHost* rfh = shell()->web_contents()->GetPrimaryMainFrame();
Alex Moshchuk20780962018-09-27 23:17:04835
836 // Intercept future blob URL registrations and overwrite the blob URL origin
837 // with b.com.
838 std::string target_origin = "http://b.com";
839 std::string blob_path = "5881f76e-10d2-410d-8c61-ef210502acfd";
Andrew Williams39151a72022-10-18 23:12:41840
841 base::RepeatingCallback<void(storage::BlobUrlRegistry*, mojo::ReceiverId)>
842 blob_url_registry_intercept_hook;
Andrew Williams39151a72022-10-18 23:12:41843
janiceliu2457d49d2024-07-09 18:15:57844 blob_url_registry_intercept_hook =
845 base::BindRepeating(&BlobURLStoreInterceptor::Intercept,
846 GURL("blob:" + target_origin + "/" + blob_path));
847 storage::BlobUrlRegistry::SetURLStoreCreationHookForTesting(
848 &blob_url_registry_intercept_hook);
Alex Moshchuk20780962018-09-27 23:17:04849
850 // Register a blob URL from the a.com main frame, which will go through the
851 // interceptor above and be rewritten to register the blob URL with the b.com
852 // origin. This should result in a kill because a.com should not be allowed
853 // to create blob URLs outside of its own origin.
Lukasz Anforowicz110cda32020-03-23 22:32:03854 content::RenderProcessHostBadMojoMessageWaiter crash_observer(
855 rfh->GetProcess());
Alex Moshchuk20780962018-09-27 23:17:04856
Chris Fredricksond3bb2682023-05-10 03:32:26857 // The renderer should always get killed, but sometimes ExecJs returns
Alex Moshchuk20780962018-09-27 23:17:04858 // true anyway, so just ignore the result.
Avi Drissman5d5d48d62022-01-07 20:23:58859 std::ignore = ExecJs(rfh, "URL.createObjectURL(new Blob(['foo']))");
Alex Moshchuk20780962018-09-27 23:17:04860
861 // If the process is killed, this test passes.
Lukasz Anforowicz110cda32020-03-23 22:32:03862 EXPECT_EQ(
863 "Received bad user message: "
Marijn Kruisselbrink15cfa2b2021-11-09 21:39:27864 "URL with invalid origin passed to BlobURLStore::Register",
Lukasz Anforowicz110cda32020-03-23 22:32:03865 crash_observer.Wait());
Alex Moshchuk20780962018-09-27 23:17:04866}
867
868// Check that with site isolation enabled, an origin can't create a filesystem
869// URL for a different origin. See https://crbug.com/888001.
870IN_PROC_BROWSER_TEST_F(SecurityExploitBrowserTest,
871 CreateFilesystemURLInDifferentOrigin) {
872 IsolateAllSitesForTesting(base::CommandLine::ForCurrentProcess());
873
874 GURL main_url(embedded_test_server()->GetURL(
875 "a.com", "/cross_site_iframe_factory.html?a(b)"));
876 EXPECT_TRUE(NavigateToURL(shell(), main_url));
kyraseevers235fd482021-10-25 17:33:26877 RenderFrameHostImpl* rfh = static_cast<RenderFrameHostImpl*>(
Dave Tapuska327c06c92022-06-13 20:31:51878 shell()->web_contents()->GetPrimaryMainFrame());
Alex Moshchuk20780962018-09-27 23:17:04879
880 // Block the renderer on operation that never completes, to shield it from
881 // receiving unexpected browser->renderer IPCs that might CHECK.
882 rfh->ExecuteJavaScriptWithUserGestureForTests(
Peter Kasting65491ff2021-04-29 16:40:31883 u"var r = new XMLHttpRequest();"
884 u"r.open('GET', '/slow?99999', false);"
885 u"r.send(null);"
Stephen McGruera37eac12022-04-11 16:17:17886 u"while (1);",
Avi Drissman73cb9852024-08-26 18:10:58887 base::NullCallback(), ISOLATED_WORLD_ID_GLOBAL);
Alex Moshchuk20780962018-09-27 23:17:04888
889 // Set up a blob ID and populate it with attacker-controlled value. This
890 // is just using the blob APIs directly since creating arbitrary blobs is not
891 // what is prohibited; this data is not in any origin.
892 std::string payload = "<html><body>pwned.</body></html>";
893 std::string payload_type = "text/html";
894 std::unique_ptr<content::BlobHandle> blob = CreateMemoryBackedBlob(
895 rfh->GetSiteInstance()->GetBrowserContext(), payload, payload_type);
896 std::string blob_id = blob->GetUUID();
897
898 // Target a different origin.
899 std::string target_origin = "http://b.com";
900 GURL target_url =
901 GURL("filesystem:" + target_origin + "/temporary/exploit.html");
902
903 // Note: a well-behaved renderer would always call Open first before calling
904 // Create and Write, but it's actually not necessary for the original attack
905 // to succeed, so we omit it. As a result there are some log warnings from the
906 // quota observer.
907
908 PwnMessageHelper::FileSystemCreate(rfh->GetProcess(), 23, target_url, false,
Mariam Ali8338d9fa2023-07-24 16:57:31909 false, false, rfh->GetStorageKey());
Alex Moshchuk20780962018-09-27 23:17:04910
911 // Write the blob into the file. If successful, this places an
912 // attacker-controlled value in a resource on the target origin.
913 PwnMessageHelper::FileSystemWrite(rfh->GetProcess(), 24, target_url, blob_id,
Mariam Ali8338d9fa2023-07-24 16:57:31914 0, rfh->GetStorageKey());
Alex Moshchuk20780962018-09-27 23:17:04915
kyraseevers2c703e52021-11-12 17:06:23916 // Now navigate to `target_url` in a subframe. It should not succeed, and the
917 // subframe should not contain `payload`.
Alex Moshchuk20780962018-09-27 23:17:04918 TestNavigationObserver observer(shell()->web_contents());
919 FrameTreeNode* root = static_cast<WebContentsImpl*>(shell()->web_contents())
Carlos Caballero15caeeb2021-10-27 09:57:55920 ->GetPrimaryFrameTree()
921 .root();
Alex Moshchuk20780962018-09-27 23:17:04922 NavigateFrameToURL(root->child_at(0), target_url);
923 EXPECT_FALSE(observer.last_navigation_succeeded());
924 EXPECT_EQ(net::ERR_FILE_NOT_FOUND, observer.last_net_error_code());
925
926 RenderFrameHost* attacked_rfh = root->child_at(0)->current_frame_host();
927 std::string body =
928 EvalJs(attacked_rfh, "document.body.innerText").ExtractString();
929 EXPECT_TRUE(base::StartsWith(body, "Could not load the requested resource",
930 base::CompareCase::INSENSITIVE_ASCII))
931 << " body=" << body;
932}
933
Alex Moshchuk5f8671e2018-10-19 02:10:11934// Verify that when a compromised renderer tries to navigate a remote frame to
935// a disallowed URL (e.g., file URL), that navigation is blocked.
936IN_PROC_BROWSER_TEST_F(SecurityExploitBrowserTest,
937 BlockIllegalOpenURLFromRemoteFrame) {
Lukasz Anforowicz56211d452019-02-05 18:05:51938 // Explicitly isolating a.com helps ensure that this test is applicable on
939 // platforms without site-per-process.
940 IsolateOrigin("a.com");
Alex Moshchuk5f8671e2018-10-19 02:10:11941
942 GURL main_url(embedded_test_server()->GetURL(
943 "a.com", "/cross_site_iframe_factory.html?a(b)"));
944 EXPECT_TRUE(NavigateToURL(shell(), main_url));
945 FrameTreeNode* root = static_cast<WebContentsImpl*>(shell()->web_contents())
Carlos Caballero15caeeb2021-10-27 09:57:55946 ->GetPrimaryFrameTree()
947 .root();
Alex Moshchuk5f8671e2018-10-19 02:10:11948 FrameTreeNode* child = root->child_at(0);
949
950 // Simulate an IPC message where the top frame asks the remote subframe to
951 // navigate to a file: URL.
Sharon Yang7424bda2021-11-04 20:27:43952 SiteInstanceImpl* a_com_instance =
953 root->current_frame_host()->GetSiteInstance();
Alex Moshchuk5f8671e2018-10-19 02:10:11954 RenderFrameProxyHost* proxy =
Harkiran Bolariad22a1dca2022-02-22 17:01:12955 child->current_frame_host()
956 ->browsing_context_state()
957 ->GetRenderFrameProxyHost(a_com_instance->group());
Alex Moshchuk5f8671e2018-10-19 02:10:11958 EXPECT_TRUE(proxy);
959
Gyuyoung Kim0028790a2020-06-26 00:09:00960 TestNavigationObserver observer(shell()->web_contents());
Mario Sanchez Pradacb83d6c82020-09-11 13:11:51961 static_cast<mojom::FrameHost*>(proxy->frame_tree_node()->current_frame_host())
962 ->OpenURL(CreateOpenURLParams(GURL("file:///")));
Gyuyoung Kim0028790a2020-06-26 00:09:00963 observer.Wait();
Alex Moshchuk5f8671e2018-10-19 02:10:11964
965 // Verify that the malicious navigation was blocked. Currently, this happens
Nasko Oskov413468f82018-10-29 23:22:01966 // by rewriting the target URL to about:blank#blocked.
Alex Moshchuk5f8671e2018-10-19 02:10:11967 //
968 // TODO(alexmos): Consider killing the renderer process in this case, since
969 // this security check is already enforced in the renderer process.
Nasko Oskov413468f82018-10-29 23:22:01970 EXPECT_EQ(GURL(kBlockedURL),
Alex Moshchuk5f8671e2018-10-19 02:10:11971 child->current_frame_host()->GetLastCommittedURL());
972
973 // Navigate to the starting page again to recreate the proxy, then try the
974 // same malicious navigation with a chrome:// URL.
975 EXPECT_TRUE(NavigateToURL(shell(), main_url));
976 child = root->child_at(0);
Harkiran Bolariad22a1dca2022-02-22 17:01:12977 proxy = child->current_frame_host()
978 ->browsing_context_state()
979 ->GetRenderFrameProxyHost(a_com_instance->group());
Alex Moshchuk5f8671e2018-10-19 02:10:11980 EXPECT_TRUE(proxy);
981
Gyuyoung Kim0028790a2020-06-26 00:09:00982 TestNavigationObserver observer_2(shell()->web_contents());
Alex Moshchuk5f8671e2018-10-19 02:10:11983 GURL chrome_url(std::string(kChromeUIScheme) + "://" +
984 std::string(kChromeUIGpuHost));
Mario Sanchez Pradacb83d6c82020-09-11 13:11:51985 static_cast<mojom::FrameHost*>(proxy->frame_tree_node()->current_frame_host())
986 ->OpenURL(CreateOpenURLParams(chrome_url));
Gyuyoung Kim0028790a2020-06-26 00:09:00987 observer_2.Wait();
Nasko Oskov413468f82018-10-29 23:22:01988 EXPECT_EQ(GURL(kBlockedURL),
Alex Moshchuk5f8671e2018-10-19 02:10:11989 child->current_frame_host()->GetLastCommittedURL());
990}
991
Gyuyoung Kimeff73ad52021-03-25 22:59:01992class RemoteFrameHostInterceptor
Mario Sanchez Pradae54aa9e2020-05-20 15:08:31993 : public blink::mojom::RemoteFrameHostInterceptorForTesting {
Lukasz Anforowicz3ae9799d2019-01-11 01:01:32994 public:
Gyuyoung Kimeff73ad52021-03-25 22:59:01995 explicit RemoteFrameHostInterceptor(
Mario Sanchez Pradae54aa9e2020-05-20 15:08:31996 RenderFrameProxyHost* render_frame_proxy_host,
Alex Moshchuk1005cd92024-06-05 21:00:40997 const url::Origin& evil_origin)
Daniel Chengf693d882024-05-07 16:48:37998 : evil_origin_(evil_origin),
Will Harriseb6cca012022-02-11 01:23:17999 swapped_impl_(
Daniel Chengf693d882024-05-07 16:48:371000 render_frame_proxy_host->frame_host_receiver_for_testing(),
Will Harriseb6cca012022-02-11 01:23:171001 this) {}
1002
1003 ~RemoteFrameHostInterceptor() override = default;
Lukasz Anforowicz3ae9799d2019-01-11 01:01:321004
Mario Sanchez Pradae54aa9e2020-05-20 15:08:311005 RemoteFrameHost* GetForwardingInterface() override {
Daniel Chengf693d882024-05-07 16:48:371006 return swapped_impl_.old_impl();
Mario Sanchez Pradae54aa9e2020-05-20 15:08:311007 }
1008
1009 void RouteMessageEvent(
Arthur Sonzognic686e8f2024-01-11 08:36:371010 const std::optional<blink::LocalFrameToken>& source_frame_token,
Alex Moshchuk1005cd92024-06-05 21:00:401011 const url::Origin& source_origin,
Jan Wilken Dörrieaace0cfef2021-03-11 22:01:581012 const std::u16string& target_origin,
Mario Sanchez Pradae54aa9e2020-05-20 15:08:311013 blink::TransferableMessage message) override {
1014 // Forward the message to the actual RFPH replacing |source_origin| with the
Alex Moshchuk1005cd92024-06-05 21:00:401015 // "evil origin".
Mario Sanchez Pradae54aa9e2020-05-20 15:08:311016 GetForwardingInterface()->RouteMessageEvent(
Alex Moshchuk1005cd92024-06-05 21:00:401017 std::move(source_frame_token), evil_origin_, std::move(target_origin),
1018 std::move(message));
Lukasz Anforowicz3ae9799d2019-01-11 01:01:321019 }
1020
Gyuyoung Kimeff73ad52021-03-25 22:59:011021 void OpenURL(blink::mojom::OpenURLParamsPtr params) override {
1022 intercepted_params_ = std::move(params);
1023 }
1024
1025 blink::mojom::OpenURLParamsPtr GetInterceptedParams() {
1026 return std::move(intercepted_params_);
1027 }
1028
Lukasz Anforowicz3ae9799d2019-01-11 01:01:321029 private:
Alex Moshchuk1005cd92024-06-05 21:00:401030 url::Origin evil_origin_;
Gyuyoung Kimeff73ad52021-03-25 22:59:011031 blink::mojom::OpenURLParamsPtr intercepted_params_;
Daniel Cheng304b8f52024-05-07 16:48:111032 mojo::test::ScopedSwapImplForTesting<blink::mojom::RemoteFrameHost>
Will Harriseb6cca012022-02-11 01:23:171033 swapped_impl_;
Lukasz Anforowicz3ae9799d2019-01-11 01:01:321034};
1035
Mario Sanchez Pradae54aa9e2020-05-20 15:08:311036// Test verifying that a compromised renderer can't lie about the source_origin
1037// passed along with the RouteMessageEvent() mojo message. See also
Lukasz Anforowicz3ae9799d2019-01-11 01:01:321038// https://crbug.com/915721.
1039IN_PROC_BROWSER_TEST_F(SecurityExploitBrowserTest, PostMessageSourceOrigin) {
Lukasz Anforowicz56211d452019-02-05 18:05:511040 // Explicitly isolating a.com helps ensure that this test is applicable on
1041 // platforms without site-per-process.
1042 IsolateOrigin("b.com");
Lukasz Anforowicz3ae9799d2019-01-11 01:01:321043
1044 // Navigate to a page with an OOPIF.
1045 GURL main_url(embedded_test_server()->GetURL(
1046 "a.com", "/cross_site_iframe_factory.html?a(b)"));
1047 EXPECT_TRUE(NavigateToURL(shell(), main_url));
1048
1049 // Sanity check of test setup: main frame and subframe should be isolated.
1050 WebContents* web_contents = shell()->web_contents();
Dave Tapuska327c06c92022-06-13 20:31:511051 RenderFrameHost* main_frame = web_contents->GetPrimaryMainFrame();
Dave Tapuska217f04d2021-09-22 13:35:411052 RenderFrameHost* subframe = ChildFrameAt(main_frame, 0);
Lukasz Anforowicz3ae9799d2019-01-11 01:01:321053 EXPECT_NE(main_frame->GetProcess(), subframe->GetProcess());
1054
Mario Sanchez Pradae54aa9e2020-05-20 15:08:311055 // We need to get ahold of the RenderFrameProxyHost representing the main
1056 // frame for the subframe's process, to install the mojo interceptor.
1057 FrameTreeNode* main_frame_node =
1058 static_cast<WebContentsImpl*>(shell()->web_contents())
Carlos Caballero15caeeb2021-10-27 09:57:551059 ->GetPrimaryFrameTree()
1060 .root();
Mario Sanchez Pradae54aa9e2020-05-20 15:08:311061 FrameTreeNode* subframe_node = main_frame_node->child_at(0);
Sharon Yang7424bda2021-11-04 20:27:431062 SiteInstanceImpl* b_com_instance =
Mario Sanchez Pradae54aa9e2020-05-20 15:08:311063 subframe_node->current_frame_host()->GetSiteInstance();
1064 RenderFrameProxyHost* main_frame_proxy_host =
Harkiran Bolariad22a1dca2022-02-22 17:01:121065 main_frame_node->current_frame_host()
1066 ->browsing_context_state()
1067 ->GetRenderFrameProxyHost(b_com_instance->group());
Lukasz Anforowicz3ae9799d2019-01-11 01:01:321068
Mario Sanchez Pradae54aa9e2020-05-20 15:08:311069 // Prepare to intercept the RouteMessageEvent IPC message that will come
1070 // from the subframe process.
Alex Moshchuk1005cd92024-06-05 21:00:401071 url::Origin evil_source_origin =
Dave Tapuska327c06c92022-06-13 20:31:511072 web_contents->GetPrimaryMainFrame()->GetLastCommittedOrigin();
Gyuyoung Kimeff73ad52021-03-25 22:59:011073 RemoteFrameHostInterceptor mojo_interceptor(main_frame_proxy_host,
1074 evil_source_origin);
Lukasz Anforowicz3ae9799d2019-01-11 01:01:321075
Mario Sanchez Pradae54aa9e2020-05-20 15:08:311076 // Post a message from the subframe to the cross-site parent and intercept the
1077 // associated IPC message, changing it to simulate a compromised subframe
1078 // renderer lying that the |source_origin| of the postMessage is the origin of
1079 // the parent (not of the subframe).
1080 RenderProcessHostBadIpcMessageWaiter kill_waiter(subframe->GetProcess());
1081 EXPECT_TRUE(ExecJs(subframe, "parent.postMessage('blah', '*')"));
Lukasz Anforowicz3ae9799d2019-01-11 01:01:321082 EXPECT_EQ(bad_message::RFPH_POST_MESSAGE_INVALID_SOURCE_ORIGIN,
1083 kill_waiter.Wait());
1084}
1085
Alex Moshchuk1005cd92024-06-05 21:00:401086// Test verifying that a compromised renderer can't lie about the source_origin
1087// passed along with the RouteMessageEvent() mojo message. Similar to the test
1088// above, but exercises a scenario where the source origin is opaque and the
1089// precursor needs to be validated. This provides coverage for messages sent
1090// from sandboxed frames; see https://crbug.com/40606810 and
1091// https://crbug.com/325410297.
1092IN_PROC_BROWSER_TEST_F(SecurityExploitBrowserTest,
1093 PostMessageOpaqueSourceOrigin) {
1094 // This test requires opaque origin enforcements to be turned on; otherwise,
1095 // there would be no renderer kill to check for.
1096 if (!base::FeatureList::IsEnabled(
1097 features::kAdditionalOpaqueOriginEnforcements)) {
1098 GTEST_SKIP();
1099 }
1100
1101 // Explicitly isolating b.com helps ensure that this test is applicable on
1102 // platforms without site-per-process.
1103 IsolateOrigin("b.com");
1104
1105 GURL main_url(embedded_test_server()->GetURL("a.com", "/title1.html"));
1106 EXPECT_TRUE(NavigateToURL(shell(), main_url));
1107 WebContentsImpl* web_contents =
1108 static_cast<WebContentsImpl*>(shell()->web_contents());
1109 FrameTreeNode* root = web_contents->GetPrimaryFrameTree().root();
1110 RenderFrameHostImpl* main_frame = root->current_frame_host();
1111
1112 // Create cross-site sandboxed child frame.
1113 GURL child_url(embedded_test_server()->GetURL("b.com", "/title1.html"));
1114 {
1115 std::string js_str = base::StringPrintf(
1116 "var frame = document.createElement('iframe'); "
1117 "frame.sandbox = 'allow-scripts'; "
1118 "frame.src = '%s'; "
1119 "document.body.appendChild(frame);",
1120 child_url.spec().c_str());
1121 EXPECT_TRUE(ExecJs(main_frame, js_str));
1122 ASSERT_TRUE(WaitForLoadStop(web_contents));
1123 }
1124
1125 // Sanity check of test setup: main frame and subframe should be in separate
1126 // processes, and subframe should be sandboxed.
1127 FrameTreeNode* subframe_node = root->child_at(0);
1128 RenderFrameHostImpl* subframe = subframe_node->current_frame_host();
1129 EXPECT_NE(main_frame->GetProcess(), subframe->GetProcess());
1130 EXPECT_TRUE(subframe->GetSiteInstance()->GetSiteInfo().is_sandboxed());
1131
1132 // Retrieve the RenderFrameProxyHost representing the main frame for the
1133 // subframe's process.
1134 RenderFrameProxyHost* main_frame_proxy_host =
1135 main_frame->browsing_context_state()->GetRenderFrameProxyHost(
1136 subframe->GetSiteInstance()->group());
1137
1138 // Prepare to intercept the RouteMessageEvent IPC message that will come from
1139 // the subframe process. Set the fake source origin to an opaque origin with
1140 // a.com as the precursor.
1141 url::Origin precursor_origin = main_frame->GetLastCommittedOrigin();
1142 url::Origin evil_source_origin = precursor_origin.DeriveNewOpaqueOrigin();
1143 EXPECT_TRUE(evil_source_origin.opaque());
1144 EXPECT_EQ("a.com",
1145 evil_source_origin.GetTupleOrPrecursorTupleIfOpaque().host());
1146
1147 RemoteFrameHostInterceptor mojo_interceptor(main_frame_proxy_host,
1148 evil_source_origin);
1149
1150 // Post a message from the subframe to the cross-site parent and intercept the
1151 // associated IPC message, changing it to simulate a compromised subframe
1152 // renderer lying that the |source_origin| of the postMessage has an incorrect
1153 // precursor of a.com, rather than b.com. This should result in a renderer
1154 // kill.
1155 RenderProcessHostBadIpcMessageWaiter kill_waiter(subframe->GetProcess());
1156 EXPECT_TRUE(ExecJs(subframe, "parent.postMessage('blah', '*')"));
1157 EXPECT_EQ(bad_message::RFPH_POST_MESSAGE_INVALID_SOURCE_ORIGIN,
1158 kill_waiter.Wait());
1159}
1160
Lukasz Anforowicz56211d452019-02-05 18:05:511161IN_PROC_BROWSER_TEST_F(SecurityExploitBrowserTest,
1162 InvalidRemoteNavigationInitiator) {
1163 // Explicitly isolating a.com helps ensure that this test is applicable on
1164 // platforms without site-per-process.
1165 IsolateOrigin("a.com");
1166
1167 // Navigate to a test page where the subframe is cross-site (and because of
1168 // IsolateOrigin call above in a separate process) from the main frame.
1169 GURL main_url(embedded_test_server()->GetURL(
1170 "a.com", "/cross_site_iframe_factory.html?a(b)"));
1171 EXPECT_TRUE(NavigateToURL(shell(), main_url));
Mario Sanchez Pradacb83d6c82020-09-11 13:11:511172 RenderFrameHostImpl* main_frame = static_cast<RenderFrameHostImpl*>(
Dave Tapuska327c06c92022-06-13 20:31:511173 shell()->web_contents()->GetPrimaryMainFrame());
Lukasz Anforowicz56211d452019-02-05 18:05:511174 RenderProcessHost* main_process = main_frame->GetProcess();
Dave Tapuska217f04d2021-09-22 13:35:411175 RenderFrameHost* subframe = ChildFrameAt(main_frame, 0);
1176 ASSERT_TRUE(subframe);
Lukasz Anforowicz56211d452019-02-05 18:05:511177 RenderProcessHost* subframe_process = subframe->GetProcess();
Emily Andrewsd15fd762024-12-10 20:41:541178 EXPECT_NE(main_process->GetDeprecatedID(),
1179 subframe_process->GetDeprecatedID());
Lukasz Anforowicz56211d452019-02-05 18:05:511180
Gyuyoung Kim0028790a2020-06-26 00:09:001181 // Prepare to intercept OpenURL Mojo message that will come from
1182 // the main frame.
Gyuyoung Kimbfcb9fc2020-09-02 14:13:531183 FrameTreeNode* main_frame_node =
1184 static_cast<WebContentsImpl*>(shell()->web_contents())
Carlos Caballero15caeeb2021-10-27 09:57:551185 ->GetPrimaryFrameTree()
1186 .root();
Gyuyoung Kimbfcb9fc2020-09-02 14:13:531187 FrameTreeNode* child_node = main_frame_node->child_at(0);
Sharon Yang7424bda2021-11-04 20:27:431188 SiteInstanceImpl* a_com_instance =
Gyuyoung Kimbfcb9fc2020-09-02 14:13:531189 main_frame_node->current_frame_host()->GetSiteInstance();
1190 RenderFrameProxyHost* proxy =
Harkiran Bolariad22a1dca2022-02-22 17:01:121191 child_node->current_frame_host()
1192 ->browsing_context_state()
1193 ->GetRenderFrameProxyHost(a_com_instance->group());
Lukasz Anforowicz1034c0882020-03-21 01:24:531194 RenderProcessHostBadIpcMessageWaiter kill_waiter(main_process);
Will Harriseb6cca012022-02-11 01:23:171195 {
Alex Moshchuk1005cd92024-06-05 21:00:401196 RemoteFrameHostInterceptor interceptor(proxy, url::Origin());
Will Harriseb6cca012022-02-11 01:23:171197
1198 // Have the main frame request navigation in the "remote" subframe. This
1199 // will result in OpenURL Mojo message being sent to the
1200 // RenderFrameProxyHost.
Dave Tapuska327c06c92022-06-13 20:31:511201 EXPECT_TRUE(ExecJs(shell()->web_contents()->GetPrimaryMainFrame(),
Will Harriseb6cca012022-02-11 01:23:171202 "window.frames[0].location = '/title1.html';"));
1203
1204 // Change the intercepted message to simulate a compromised subframe
1205 // renderer lying that the |initiator_origin| is the origin of the
1206 // |subframe|.
1207 auto evil_params = interceptor.GetInterceptedParams();
1208 evil_params->initiator_origin = subframe->GetLastCommittedOrigin();
1209
1210 // Inject the invalid IPC and verify that the renderer gets terminated.
1211 static_cast<mojom::FrameHost*>(main_frame)->OpenURL(std::move(evil_params));
1212 }
1213
Lukasz Anforowicz56211d452019-02-05 18:05:511214 EXPECT_EQ(bad_message::INVALID_INITIATOR_ORIGIN, kill_waiter.Wait());
1215}
1216
Lukasz Anforowicz3cfc1efd2019-02-22 02:29:291217class BeginNavigationInitiatorReplacer : public FrameHostInterceptor {
1218 public:
Lukasz Anforowicz2294ca82019-02-25 18:26:051219 BeginNavigationInitiatorReplacer(
1220 WebContents* web_contents,
Arthur Sonzognic686e8f2024-01-11 08:36:371221 std::optional<url::Origin> initiator_to_inject)
Lukasz Anforowicz3cfc1efd2019-02-22 02:29:291222 : FrameHostInterceptor(web_contents),
1223 initiator_to_inject_(initiator_to_inject) {}
1224
Peter Boström9b036532021-10-28 23:37:281225 BeginNavigationInitiatorReplacer(const BeginNavigationInitiatorReplacer&) =
1226 delete;
1227 BeginNavigationInitiatorReplacer& operator=(
1228 const BeginNavigationInitiatorReplacer&) = delete;
1229
Lukasz Anforowicz3cfc1efd2019-02-22 02:29:291230 bool WillDispatchBeginNavigation(
1231 RenderFrameHost* render_frame_host,
Minggang Wangb9f3fa92021-07-01 15:30:311232 blink::mojom::CommonNavigationParamsPtr* common_params,
1233 blink::mojom::BeginNavigationParamsPtr* begin_params,
Julie Jeongeun Kim249cfbb2019-08-30 06:44:051234 mojo::PendingRemote<blink::mojom::BlobURLToken>* blob_url_token,
Antonio Sartori636adba2021-03-09 12:15:271235 mojo::PendingAssociatedRemote<mojom::NavigationClient>* navigation_client)
1236 override {
Lukasz Anforowicz3cfc1efd2019-02-22 02:29:291237 if (is_activated_) {
Lucas Furukawa Gadanief8290a2019-07-29 20:27:511238 (*common_params)->initiator_origin = initiator_to_inject_;
Lukasz Anforowicz3cfc1efd2019-02-22 02:29:291239 is_activated_ = false;
1240 }
1241
1242 return true;
1243 }
1244
1245 void Activate() { is_activated_ = true; }
1246
1247 private:
Arthur Sonzognic686e8f2024-01-11 08:36:371248 std::optional<url::Origin> initiator_to_inject_;
Lukasz Anforowicz3cfc1efd2019-02-22 02:29:291249 bool is_activated_ = false;
Lukasz Anforowicz3cfc1efd2019-02-22 02:29:291250};
1251
1252IN_PROC_BROWSER_TEST_F(SecurityExploitBrowserTest,
1253 InvalidBeginNavigationInitiator) {
arthursonzogni0a1649742019-10-11 08:35:201254 WebContentsImpl* web_contents =
1255 static_cast<WebContentsImpl*>(shell()->web_contents());
arthursonzogni0a1649742019-10-11 08:35:201256
arthursonzognib64b5842019-10-25 09:09:511257 // Prepare to intercept BeginNavigation mojo IPC. This has to be done before
Lukasz Anforowicz3cfc1efd2019-02-22 02:29:291258 // the test creates the RenderFrameHostImpl that is the target of the IPC.
Lukasz Anforowicz3cfc1efd2019-02-22 02:29:291259 BeginNavigationInitiatorReplacer injector(
1260 web_contents, url::Origin::Create(GURL("http://b.com")));
1261
arthursonzognib64b5842019-10-25 09:09:511262 // Explicitly isolating a.com helps ensure that this test is applicable on
1263 // platforms without site-per-process.
1264 IsolateOrigin("a.com");
1265
Lukasz Anforowicz3cfc1efd2019-02-22 02:29:291266 // Navigate to a test page that will be locked to a.com.
1267 GURL main_url(embedded_test_server()->GetURL("a.com", "/title1.html"));
1268 EXPECT_TRUE(NavigateToURL(web_contents, main_url));
1269
1270 // Start monitoring for renderer kills.
Dave Tapuska327c06c92022-06-13 20:31:511271 RenderProcessHost* main_process =
1272 web_contents->GetPrimaryMainFrame()->GetProcess();
Lukasz Anforowicz1034c0882020-03-21 01:24:531273 RenderProcessHostBadIpcMessageWaiter kill_waiter(main_process);
Lukasz Anforowicz3cfc1efd2019-02-22 02:29:291274
1275 // Have the main frame navigate and lie that the initiator origin is b.com.
1276 injector.Activate();
Nate Chapin3501550b2019-04-23 20:58:471277 // Don't expect a response for the script, as the process may be killed
1278 // before the script sends its completion message.
1279 ExecuteScriptAsync(web_contents, "window.location = '/title2.html';");
Lukasz Anforowicz3cfc1efd2019-02-22 02:29:291280
1281 // Verify that the renderer was terminated.
1282 EXPECT_EQ(bad_message::INVALID_INITIATOR_ORIGIN, kill_waiter.Wait());
1283}
1284
Alex Moshchuk4043b732024-06-14 06:02:391285// Similar to the test above, but ensure that initiator origins are validated
1286// even for opaque origins.
1287IN_PROC_BROWSER_TEST_F(SecurityExploitBrowserTest,
1288 InvalidBeginNavigationOpaqueInitiator) {
1289 // This test requires opaque origin enforcements to be turned on; otherwise,
1290 // there would be no renderer kill to check for.
1291 if (!base::FeatureList::IsEnabled(
1292 features::kAdditionalOpaqueOriginEnforcements)) {
1293 GTEST_SKIP();
1294 }
1295
1296 WebContentsImpl* web_contents =
1297 static_cast<WebContentsImpl*>(shell()->web_contents());
1298
1299 // Prepare to intercept BeginNavigation mojo IPC. This has to be done before
1300 // the test creates the RenderFrameHostImpl that is the target of the IPC.
1301 url::Origin injected_origin(url::Origin::Create(GURL("http://evil.com")));
1302 injected_origin = injected_origin.DeriveNewOpaqueOrigin();
1303 BeginNavigationInitiatorReplacer injector(web_contents, injected_origin);
1304
1305 // Explicitly isolating b.com helps ensure that this test is applicable on
1306 // platforms without site-per-process.
1307 IsolateOrigin("b.com");
1308
1309 // Navigate to a test page at a.com.
1310 GURL main_url(embedded_test_server()->GetURL("a.com", "/title1.html"));
1311 EXPECT_TRUE(NavigateToURL(web_contents, main_url));
1312
1313 // Add a cross-site sandboxed child frame at b.com.
1314 FrameTreeNode* root = web_contents->GetPrimaryFrameTree().root();
1315 RenderFrameHostImpl* main_frame = root->current_frame_host();
1316 GURL child_url(embedded_test_server()->GetURL("b.com", "/title1.html"));
1317 {
1318 std::string js_str = base::StringPrintf(
1319 "var frame = document.createElement('iframe'); "
1320 "frame.sandbox = 'allow-scripts'; "
1321 "frame.src = '%s'; "
1322 "document.body.appendChild(frame);",
1323 child_url.spec().c_str());
1324 EXPECT_TRUE(ExecJs(main_frame, js_str));
1325 ASSERT_TRUE(WaitForLoadStop(web_contents));
1326 }
1327
1328 // Sanity check of test setup: main frame and subframe should be in separate
1329 // processes, and subframe should be sandboxed.
1330 FrameTreeNode* subframe_node = root->child_at(0);
1331 RenderFrameHostImpl* subframe = subframe_node->current_frame_host();
1332 EXPECT_NE(main_frame->GetProcess(), subframe->GetProcess());
1333 EXPECT_TRUE(subframe->GetSiteInstance()->GetSiteInfo().is_sandboxed());
1334
1335 // Start monitoring for renderer kills.
1336 RenderProcessHostBadIpcMessageWaiter kill_waiter(subframe->GetProcess());
1337
1338 // Have the sandboxed subframe navigate and lie that the initiator origin is
1339 // an opaque origin with the precursor of evil.com instead of b.com.
1340 injector.Activate();
1341 // Don't expect a response for the script, as the process may be killed
1342 // before the script sends its completion message.
1343 ExecuteScriptAsync(subframe, "window.location = '/title2.html';");
1344
1345 // Verify that the renderer was terminated.
1346 EXPECT_EQ(bad_message::INVALID_INITIATOR_ORIGIN, kill_waiter.Wait());
1347}
1348
Lukasz Anforowicz2294ca82019-02-25 18:26:051349IN_PROC_BROWSER_TEST_F(SecurityExploitBrowserTest,
1350 MissingBeginNavigationInitiator) {
1351 // Prepare to intercept BeginNavigation mojo IPC. This has to be done before
1352 // the test creates the RenderFrameHostImpl that is the target of the IPC.
1353 WebContents* web_contents = shell()->web_contents();
Arthur Sonzognic686e8f2024-01-11 08:36:371354 BeginNavigationInitiatorReplacer injector(web_contents, std::nullopt);
Lukasz Anforowicz2294ca82019-02-25 18:26:051355
1356 // Navigate to a test page.
1357 GURL main_url(embedded_test_server()->GetURL("a.com", "/title1.html"));
1358 EXPECT_TRUE(NavigateToURL(web_contents, main_url));
1359
1360 // Start monitoring for renderer kills.
Dave Tapuska327c06c92022-06-13 20:31:511361 RenderProcessHost* main_process =
1362 web_contents->GetPrimaryMainFrame()->GetProcess();
Lukasz Anforowicz1034c0882020-03-21 01:24:531363 RenderProcessHostBadIpcMessageWaiter kill_waiter(main_process);
Lukasz Anforowicz2294ca82019-02-25 18:26:051364
1365 // Have the main frame submit a BeginNavigation IPC with a missing initiator.
1366 injector.Activate();
Nate Chapin3501550b2019-04-23 20:58:471367 // Don't expect a response for the script, as the process may be killed
1368 // before the script sends its completion message.
1369 ExecuteScriptAsync(web_contents, "window.location = '/title2.html';");
Lukasz Anforowicz2294ca82019-02-25 18:26:051370
1371 // Verify that the renderer was terminated.
1372 EXPECT_EQ(bad_message::RFHI_BEGIN_NAVIGATION_MISSING_INITIATOR_ORIGIN,
1373 kill_waiter.Wait());
1374}
1375
Nasko Oskov7d945392019-04-20 01:59:301376namespace {
1377
1378// An interceptor class that allows replacing the URL of the commit IPC from
1379// the renderer process to the browser process.
1380class DidCommitUrlReplacer : public DidCommitNavigationInterceptor {
1381 public:
1382 DidCommitUrlReplacer(WebContents* web_contents, const GURL& replacement_url)
1383 : DidCommitNavigationInterceptor(web_contents),
1384 replacement_url_(replacement_url) {}
Peter Boström828b9022021-09-21 02:28:431385
1386 DidCommitUrlReplacer(const DidCommitUrlReplacer&) = delete;
1387 DidCommitUrlReplacer& operator=(const DidCommitUrlReplacer&) = delete;
1388
Nasko Oskov7d945392019-04-20 01:59:301389 ~DidCommitUrlReplacer() override = default;
1390
1391 protected:
1392 bool WillProcessDidCommitNavigation(
1393 RenderFrameHost* render_frame_host,
1394 NavigationRequest* navigation_request,
arthursonzogni73fe3212020-11-17 13:24:071395 mojom::DidCommitProvisionalLoadParamsPtr* params,
Nasko Oskov7d945392019-04-20 01:59:301396 mojom::DidCommitProvisionalLoadInterfaceParamsPtr* interface_params)
1397 override {
arthursonzogni73fe3212020-11-17 13:24:071398 (**params).url = replacement_url_;
Nasko Oskov7d945392019-04-20 01:59:301399 return true;
1400 }
1401
1402 private:
1403 GURL replacement_url_;
Nasko Oskov7d945392019-04-20 01:59:301404};
1405
1406} // namespace
1407
1408// Test which verifies that when an exploited renderer process sends a commit
1409// message with URL that the process is not allowed to commit.
Nasko Oskov8e0e1ee2020-07-20 23:50:011410IN_PROC_BROWSER_TEST_F(SecurityExploitBrowserTest, DidCommitInvalidURL) {
Nasko Oskov7d945392019-04-20 01:59:301411 // Explicitly isolating foo.com helps ensure that this test is applicable on
1412 // platforms without site-per-process.
1413 IsolateOrigin("foo.com");
1414
Nasko Oskov8e0e1ee2020-07-20 23:50:011415 RenderFrameDeletedObserver initial_frame_deleted_observer(
Dave Tapuska327c06c92022-06-13 20:31:511416 shell()->web_contents()->GetPrimaryMainFrame());
Nasko Oskov8e0e1ee2020-07-20 23:50:011417
Sreeja Kamishettyce8d5942020-08-19 11:25:511418 // Test assumes the initial RenderFrameHost to be deleted. Disable
1419 // back-forward cache to ensure that it doesn't get preserved in the cache.
1420 DisableBackForwardCacheForTesting(shell()->web_contents(),
Rakina Zata Amni30af7062022-01-19 23:46:361421 BackForwardCache::TEST_REQUIRES_NO_CACHING);
Sreeja Kamishettyce8d5942020-08-19 11:25:511422
Nasko Oskov7d945392019-04-20 01:59:301423 // Navigate to foo.com initially.
1424 GURL foo_url(embedded_test_server()->GetURL("foo.com", "/title1.html"));
1425 EXPECT_TRUE(NavigateToURL(shell(), foo_url));
1426
Nasko Oskov8e0e1ee2020-07-20 23:50:011427 // Wait for the RenderFrameHost which was current before the navigation to
1428 // foo.com to be deleted. This is necessary, since on a slow system the
1429 // UnloadACK event can arrive after the DidCommitUrlReplacer instance below
1430 // is created. The replacer code has checks to ensure that all frames being
1431 // deleted it has seen being created, which with delayed UnloadACK is
1432 // violated.
1433 initial_frame_deleted_observer.WaitUntilDeleted();
1434
Nasko Oskov7d945392019-04-20 01:59:301435 // Create the interceptor object which will replace the URL of the subsequent
1436 // navigation with bar.com based URL.
1437 GURL bar_url(embedded_test_server()->GetURL("bar.com", "/title3.html"));
1438 DidCommitUrlReplacer url_replacer(shell()->web_contents(), bar_url);
1439
1440 // Navigate to another URL within foo.com, which would usually be committed
Alex Moshchuk4174c192019-08-20 16:58:091441 // successfully, but when the URL is modified it should result in the
Nasko Oskov7d945392019-04-20 01:59:301442 // termination of the renderer process.
Lukasz Anforowicz1034c0882020-03-21 01:24:531443 RenderProcessHostBadIpcMessageWaiter kill_waiter(
Dave Tapuska327c06c92022-06-13 20:31:511444 shell()->web_contents()->GetPrimaryMainFrame()->GetProcess());
Alex Moshchuk4174c192019-08-20 16:58:091445 EXPECT_FALSE(NavigateToURL(
1446 shell(), embedded_test_server()->GetURL("foo.com", "/title2.html")));
Nasko Oskov7d945392019-04-20 01:59:301447 EXPECT_EQ(bad_message::RFH_CAN_COMMIT_URL_BLOCKED, kill_waiter.Wait());
1448}
1449
Lukasz Anforowicze2077a9a2020-08-21 23:45:201450// Test which verifies that when an exploited renderer process sends a commit
1451// message with URL that the process is not allowed to commit.
1452IN_PROC_BROWSER_TEST_F(SecurityExploitBrowserTest,
Ritika Guptaaf552142024-11-18 23:20:471453 DISABLED_DidCommitInvalidURLWithOpaqueOrigin) {
Lukasz Anforowicze2077a9a2020-08-21 23:45:201454 // Explicitly isolating foo.com helps ensure that this test is applicable on
1455 // platforms without site-per-process.
1456 IsolateOrigin("foo.com");
1457
1458 RenderFrameDeletedObserver initial_frame_deleted_observer(
Dave Tapuska327c06c92022-06-13 20:31:511459 shell()->web_contents()->GetPrimaryMainFrame());
Lukasz Anforowicze2077a9a2020-08-21 23:45:201460
1461 // Test assumes the initial RenderFrameHost to be deleted. Disable
1462 // back-forward cache to ensure that it doesn't get preserved in the cache.
1463 DisableBackForwardCacheForTesting(shell()->web_contents(),
Rakina Zata Amni30af7062022-01-19 23:46:361464 BackForwardCache::TEST_REQUIRES_NO_CACHING);
Lukasz Anforowicze2077a9a2020-08-21 23:45:201465
1466 // Navigate to foo.com initially.
1467 GURL foo_url(embedded_test_server()->GetURL("foo.com",
1468 "/page_with_blank_iframe.html"));
1469 EXPECT_TRUE(NavigateToURL(shell(), foo_url));
1470
1471 // Wait for the RenderFrameHost which was current before the navigation to
1472 // foo.com to be deleted. This is necessary, since on a slow system the
1473 // UnloadACK event can arrive after the DidCommitUrlReplacer instance below
1474 // is created. The replacer code has checks to ensure that all frames being
1475 // deleted it has seen being created, which with delayed UnloadACK is
1476 // violated.
1477 initial_frame_deleted_observer.WaitUntilDeleted();
1478
1479 // Create the interceptor object which will replace the URL of the subsequent
1480 // navigation with bar.com based URL.
1481 GURL bar_url(embedded_test_server()->GetURL("bar.com", "/title3.html"));
1482 DidCommitUrlReplacer url_replacer(shell()->web_contents(), bar_url);
1483
1484 // Navigate the subframe to a data URL, which would usually be committed
1485 // successfully in the same process as foo.com, but when the URL is modified
1486 // it should result in the termination of the renderer process.
1487 RenderProcessHostBadIpcMessageWaiter kill_waiter(
Dave Tapuska327c06c92022-06-13 20:31:511488 shell()->web_contents()->GetPrimaryMainFrame()->GetProcess());
Nasko Oskov4ef7cb12022-01-14 06:13:471489
1490 // Using BeginNavigateIframeToURL is necessary here, since the process
1491 // termination will result in DidFinishNavigation notification with the
1492 // navigation not in "committed" state. NavigateIframeToURL waits for the
1493 // navigation to complete and ignores non-committed navigations, therefore
1494 // it will wait indefinitely.
Lukasz Anforowicze2077a9a2020-08-21 23:45:201495 GURL data_url(R"(data:text/html,%3Ch1%3EHello%2C%20World!%3C%2Fh1%3E)");
Nasko Oskov4ef7cb12022-01-14 06:13:471496 EXPECT_TRUE(BeginNavigateIframeToURL(shell()->web_contents(), "test_iframe",
1497 data_url));
Lukasz Anforowicze2077a9a2020-08-21 23:45:201498 EXPECT_EQ(bad_message::RFH_CAN_COMMIT_URL_BLOCKED, kill_waiter.Wait());
1499}
1500
Nasko Oskova4acefa2020-02-12 23:30:321501// Test which verifies that a WebUI process cannot send a commit message with
1502// URL for a web document.
1503IN_PROC_BROWSER_TEST_F(SecurityExploitBrowserTest,
1504 WebUIProcessDidCommitWebURL) {
1505 // Navigate to a WebUI document.
1506 GURL webui_url(GetWebUIURL(kChromeUIGpuHost));
1507 EXPECT_TRUE(NavigateToURL(shell(), webui_url));
1508
1509 // Create the interceptor object which will replace the URL of the subsequent
1510 // navigation with |web_url|.
1511 GURL web_url(embedded_test_server()->GetURL("foo.com", "/title3.html"));
1512 DidCommitUrlReplacer url_replacer(shell()->web_contents(), web_url);
1513
1514 // Navigate to another URL within the WebUI, which would usually be committed
1515 // successfully, but when the URL is modified it should result in the
1516 // termination of the renderer process.
Lukasz Anforowicz1034c0882020-03-21 01:24:531517 RenderProcessHostBadIpcMessageWaiter kill_waiter(
Dave Tapuska327c06c92022-06-13 20:31:511518 shell()->web_contents()->GetPrimaryMainFrame()->GetProcess());
Nasko Oskova4acefa2020-02-12 23:30:321519 GURL second_webui_url(webui_url.Resolve("/foo"));
1520 EXPECT_FALSE(NavigateToURL(shell(), second_webui_url));
1521 EXPECT_EQ(bad_message::RFH_CAN_COMMIT_URL_BLOCKED, kill_waiter.Wait());
1522}
1523
Nasko Oskov67730062020-05-08 16:49:231524// Test that verifies that if a RenderFrameHost is incorrectly given WebUI
Nasko Oskov1b7c5622024-04-16 19:47:251525// bindings the browser process crashes due to CHECK enforcements.
Nasko Oskov67730062020-05-08 16:49:231526IN_PROC_BROWSER_TEST_F(SecurityExploitBrowserTest,
Nasko Oskov1b7c5622024-04-16 19:47:251527 AllowBindingsForNonWebUIProcess) {
Nasko Oskov67730062020-05-08 16:49:231528 // Navigate to a web URL.
1529 GURL initial_url(embedded_test_server()->GetURL("foo.com", "/title1.html"));
1530 EXPECT_TRUE(NavigateToURL(shell(), initial_url));
1531
Nasko Oskov1b7c5622024-04-16 19:47:251532 // Grant WebUI bindings to the frame to simulate a bug in the code that
1533 // incorrectly does it and verify the browser process crashes.
Peter Boströmb41d6ee2024-11-20 15:49:391534 EXPECT_NOTREACHED_DEATH(
Nasko Oskov1b7c5622024-04-16 19:47:251535 shell()->web_contents()->GetPrimaryMainFrame()->AllowBindings(
Avi Drissman78865bbb2024-08-22 20:57:191536 BindingsPolicySet({BindingsPolicyValue::kWebUi})));
Nasko Oskov67730062020-05-08 16:49:231537}
1538
Charlie Reisf4b6b3ec2021-07-09 07:31:381539// Tests that a web page cannot bind to a WebUI interface if a WebUI page is the
1540// currently committed RenderFrameHost in the tab (https://crbug.com/1225929).
1541IN_PROC_BROWSER_TEST_F(SecurityExploitBrowserTest, BindToWebUIFromWebViaMojo) {
1542 // Navigate to a non-privileged web page, and simulate a renderer compromise
1543 // by granting MojoJS.
1544 GURL web_url(embedded_test_server()->GetURL("a.com", "/title1.html"));
1545 TestNavigationManager navigation(shell()->web_contents(), web_url);
1546 shell()->LoadURL(web_url);
1547 EXPECT_TRUE(navigation.WaitForResponse());
1548 RenderFrameHostImpl* main_frame = static_cast<RenderFrameHostImpl*>(
Dave Tapuska327c06c92022-06-13 20:31:511549 shell()->web_contents()->GetPrimaryMainFrame());
Jiewei Qiancc639a662022-04-01 02:43:341550 main_frame->GetFrameBindingsControl()->EnableMojoJsBindings(nullptr);
Fergal Daly83bc3cd2023-01-18 00:22:541551 ASSERT_TRUE(navigation.WaitForNavigationFinished());
Charlie Reisf4b6b3ec2021-07-09 07:31:381552
1553 // Open a popup so that the process won't exit on its own when leaving.
1554 OpenBlankWindow(static_cast<WebContentsImpl*>(shell()->web_contents()));
1555
1556 // When the page unloads (after the cross-process navigation to an actual
1557 // WebUI page below), try to bind to a WebUI interface from the web
1558 // RenderFrameHost. Ensure the unload timer and bfcache are disabled so that
1559 // the handler has a chance to run.
Fergal Dalyda98f8e12023-11-22 04:54:261560 // This test uses `pagehide` rather than `unload` since they occur at the
1561 // same timing but `unload` is being deprecated.
Charlie Reisf4b6b3ec2021-07-09 07:31:381562 main_frame->DisableUnloadTimerForTesting();
1563 DisableBackForwardCacheForTesting(shell()->web_contents(),
Fergal Dalyda98f8e12023-11-22 04:54:261564 BackForwardCache::TEST_REQUIRES_NO_CACHING);
Charlie Reisf4b6b3ec2021-07-09 07:31:381565 ASSERT_TRUE(ExecJs(main_frame, R"(
1566 // Intentionally leak pipe as a global so it doesn't get GCed.
1567 newMessagePipe = Mojo.createMessagePipe();
Fergal Dalyda98f8e12023-11-22 04:54:261568 onpagehide = function () {
Charlie Reisf4b6b3ec2021-07-09 07:31:381569 Mojo.bindInterface('mojom.ProcessInternalsHandler',
1570 newMessagePipe.handle0);
1571 };
1572 )"));
1573
1574 // Now navigate to a WebUI page and expect the previous renderer process to be
1575 // killed when asking to bind to the WebUI interface.
1576 GURL webui_url(
1577 GetWebUIURL(kChromeUIProcessInternalsHost).Resolve("#general"));
1578 RenderProcessHostBadIpcMessageWaiter kill_waiter(main_frame->GetProcess());
1579 EXPECT_TRUE(NavigateToURL(shell(), webui_url));
1580
1581 // Verify that the previous renderer was terminated.
1582 EXPECT_EQ(bad_message::RFH_INVALID_WEB_UI_CONTROLLER, kill_waiter.Wait());
1583}
1584
Mike West9286cf12019-04-01 13:44:581585class BeginNavigationTransitionReplacer : public FrameHostInterceptor {
1586 public:
1587 BeginNavigationTransitionReplacer(WebContents* web_contents,
1588 ui::PageTransition transition_to_inject)
1589 : FrameHostInterceptor(web_contents),
1590 transition_to_inject_(transition_to_inject) {}
1591
Peter Boström9b036532021-10-28 23:37:281592 BeginNavigationTransitionReplacer(const BeginNavigationTransitionReplacer&) =
1593 delete;
1594 BeginNavigationTransitionReplacer& operator=(
1595 const BeginNavigationTransitionReplacer&) = delete;
1596
Mike West9286cf12019-04-01 13:44:581597 bool WillDispatchBeginNavigation(
1598 RenderFrameHost* render_frame_host,
Minggang Wangb9f3fa92021-07-01 15:30:311599 blink::mojom::CommonNavigationParamsPtr* common_params,
1600 blink::mojom::BeginNavigationParamsPtr* begin_params,
Julie Jeongeun Kim249cfbb2019-08-30 06:44:051601 mojo::PendingRemote<blink::mojom::BlobURLToken>* blob_url_token,
Antonio Sartori636adba2021-03-09 12:15:271602 mojo::PendingAssociatedRemote<mojom::NavigationClient>* navigation_client)
1603 override {
Mike West9286cf12019-04-01 13:44:581604 if (is_activated_) {
Lucas Furukawa Gadanief8290a2019-07-29 20:27:511605 (*common_params)->transition = transition_to_inject_;
Mike West9286cf12019-04-01 13:44:581606 is_activated_ = false;
1607 }
1608
1609 return true;
1610 }
1611
1612 void Activate() { is_activated_ = true; }
1613
1614 private:
1615 ui::PageTransition transition_to_inject_;
1616 bool is_activated_ = false;
Mike West9286cf12019-04-01 13:44:581617};
1618
1619IN_PROC_BROWSER_TEST_F(SecurityExploitBrowserTest, NonWebbyTransition) {
1620 const ui::PageTransition test_cases[] = {
1621 ui::PAGE_TRANSITION_TYPED,
1622 ui::PAGE_TRANSITION_AUTO_BOOKMARK,
1623 ui::PAGE_TRANSITION_GENERATED,
1624 ui::PAGE_TRANSITION_AUTO_TOPLEVEL,
1625 ui::PAGE_TRANSITION_RELOAD,
1626 ui::PAGE_TRANSITION_KEYWORD,
1627 ui::PAGE_TRANSITION_KEYWORD_GENERATED};
1628
1629 for (ui::PageTransition transition : test_cases) {
1630 // Prepare to intercept BeginNavigation mojo IPC. This has to be done
1631 // before the test creates the RenderFrameHostImpl that is the target of the
1632 // IPC.
1633 WebContents* web_contents = shell()->web_contents();
1634 BeginNavigationTransitionReplacer injector(web_contents, transition);
1635
1636 // Navigate to a test page.
1637 GURL main_url(embedded_test_server()->GetURL("a.com", "/title1.html"));
1638 EXPECT_TRUE(NavigateToURL(web_contents, main_url));
1639
1640 // Start monitoring for renderer kills.
1641 RenderProcessHost* main_process =
Dave Tapuska327c06c92022-06-13 20:31:511642 web_contents->GetPrimaryMainFrame()->GetProcess();
Lukasz Anforowicz1034c0882020-03-21 01:24:531643 RenderProcessHostBadIpcMessageWaiter kill_waiter(main_process);
Mike West9286cf12019-04-01 13:44:581644
1645 // Have the main frame submit a BeginNavigation IPC with a missing
1646 // initiator.
1647 injector.Activate();
Nate Chapine6fd9fb2019-04-22 18:06:021648 // Don't expect a response for the script, as the process may be killed
1649 // before the script sends its completion message.
1650 ExecuteScriptAsync(web_contents, "window.location = '/title2.html';");
Mike West9286cf12019-04-01 13:44:581651
1652 // Verify that the renderer was terminated.
1653 EXPECT_EQ(bad_message::RFHI_BEGIN_NAVIGATION_NON_WEBBY_TRANSITION,
1654 kill_waiter.Wait());
1655 }
1656}
1657
Lukasz Anforowicz9666a162019-10-12 00:54:201658class SecurityExploitViaDisabledWebSecurityTest
1659 : public SecurityExploitBrowserTest {
1660 public:
1661 SecurityExploitViaDisabledWebSecurityTest() {
1662 // To get around BlockedSchemeNavigationThrottle. Other attempts at getting
1663 // around it don't work, i.e.:
1664 // -if the request is made in a child frame then the frame is torn down
1665 // immediately on process killing so the navigation doesn't complete
1666 // -if it's classified as same document, then a DCHECK in
1667 // NavigationRequest::CreateRendererInitiated fires
1668 feature_list_.InitAndEnableFeature(
1669 features::kAllowContentInitiatedDataUrlNavigations);
1670 }
1671
1672 protected:
1673 void SetUpCommandLine(base::CommandLine* command_line) override {
1674 // Simulate a compromised renderer, otherwise the cross-origin request to
1675 // file: is blocked.
1676 command_line->AppendSwitch(switches::kDisableWebSecurity);
1677 SecurityExploitBrowserTest::SetUpCommandLine(command_line);
1678 }
1679
1680 private:
1681 base::test::ScopedFeatureList feature_list_;
1682};
1683
1684// Test to verify that an exploited renderer process trying to specify a
1685// non-empty URL for base_url_for_data_url on navigation is correctly
1686// terminated.
1687IN_PROC_BROWSER_TEST_F(SecurityExploitViaDisabledWebSecurityTest,
1688 ValidateBaseUrlForDataUrl) {
1689 GURL start_url(embedded_test_server()->GetURL("/title1.html"));
1690 EXPECT_TRUE(NavigateToURL(shell(), start_url));
1691
1692 RenderFrameHostImpl* rfh = static_cast<RenderFrameHostImpl*>(
Dave Tapuska327c06c92022-06-13 20:31:511693 shell()->web_contents()->GetPrimaryMainFrame());
Lukasz Anforowicz9666a162019-10-12 00:54:201694
1695 GURL data_url("data:text/html,foo");
1696 base::FilePath file_path = GetTestFilePath("", "simple_page.html");
1697 GURL file_url = net::FilePathToFileURL(file_path);
1698
1699 // Setup a BeginNavigate IPC with non-empty base_url_for_data_url.
Minggang Wangb9f3fa92021-07-01 15:30:311700 blink::mojom::CommonNavigationParamsPtr common_params =
1701 blink::mojom::CommonNavigationParams::New(
Lukasz Anforowicz9666a162019-10-12 00:54:201702 data_url, url::Origin::Create(data_url),
Arthur Sonzognic686e8f2024-01-11 08:36:371703 /* initiator_base_url= */ std::nullopt, blink::mojom::Referrer::New(),
1704 ui::PAGE_TRANSITION_LINK,
Minggang Wangb9f3fa92021-07-01 15:30:311705 blink::mojom::NavigationType::DIFFERENT_DOCUMENT,
Yeunjoo Choi3df791a2021-02-17 07:07:251706 blink::NavigationDownloadPolicy(),
Lukasz Anforowicz9666a162019-10-12 00:54:201707 false /* should_replace_current_entry */,
Charlie Reise1d9b8182025-04-02 04:32:121708 file_url /* base_url_for_data_url */,
1709 base::TimeTicks::Now() /* actual_navigation_start */,
Lukasz Anforowicz9666a162019-10-12 00:54:201710 base::TimeTicks::Now() /* navigation_start */, "GET",
arthursonzogniaf7c62c52020-02-12 10:49:411711 nullptr /* post_data */, network::mojom::SourceLocation::New(),
Lukasz Anforowicz9666a162019-10-12 00:54:201712 false /* started_from_context_menu */, false /* has_user_gesture */,
Antonio Sartori636adba2021-03-09 12:15:271713 false /* text_fragment_token */,
1714 network::mojom::CSPDisposition::CHECK,
Lukasz Anforowicz9666a162019-10-12 00:54:201715 std::vector<int>() /* initiator_origin_trial_features */,
1716 std::string() /* href_translate */,
1717 false /* is_history_navigation_in_new_child_frame */,
Nan Lind91c8152021-10-21 16:22:371718 base::TimeTicks() /* input_start */,
Chris Fredricksonb52bcd0a2023-03-28 14:48:051719 network::mojom::RequestDestination::kDocument);
Minggang Wangb9f3fa92021-07-01 15:30:311720 blink::mojom::BeginNavigationParamsPtr begin_params =
1721 blink::mojom::BeginNavigationParams::New(
Arthur Sonzognic686e8f2024-01-11 08:36:371722 std::nullopt /* initiator_frame_token */, std::string() /* headers */,
1723 net::LOAD_NORMAL, false /* skip_service_worker */,
Lukasz Anforowicz9666a162019-10-12 00:54:201724 blink::mojom::RequestContextType::LOCATION,
Henrique Ferreiro621b3ad2021-02-15 12:51:411725 blink::mojom::MixedContentContextType::kBlockable,
Lukasz Anforowicz9666a162019-10-12 00:54:201726 false /* is_form_submission */,
1727 false /* was_initiated_by_link_click */,
Nate Chapindf6356e2023-02-01 01:11:421728 blink::mojom::ForceHistoryPush::kNo, GURL() /* searchable_form_url */,
Lukasz Anforowicz9666a162019-10-12 00:54:201729 std::string() /* searchable_form_encoding */,
1730 GURL() /* client_side_redirect_url */,
Arthur Sonzognic686e8f2024-01-11 08:36:371731 std::nullopt /* devtools_initiator_info */,
1732 nullptr /* trust_token_params */, std::nullopt /* impression */,
Katie Dilloneb6c69812020-07-10 23:29:201733 base::TimeTicks() /* renderer_before_unload_start */,
Kunihiko Sakamoto7d9429d72021-02-01 07:07:411734 base::TimeTicks() /* renderer_before_unload_end */,
Yao Xiao720ef9d62022-12-09 05:18:291735 blink::mojom::NavigationInitiatorActivationAndAdStatus::
Sergey Poromovdd557c12023-03-01 11:28:451736 kDidNotStartWithTransientActivation,
Chris Fredrickson9ffdf5b2024-07-09 20:05:091737 false /* is_container_initiated */,
1738 net::StorageAccessApiStatus::kNone, false /* has_rel_opener */);
Lukasz Anforowicz9666a162019-10-12 00:54:201739
1740 // Receiving the invalid IPC message should lead to renderer process
1741 // termination.
Lukasz Anforowicz1034c0882020-03-21 01:24:531742 RenderProcessHostBadIpcMessageWaiter process_kill_waiter(rfh->GetProcess());
Lukasz Anforowicz9666a162019-10-12 00:54:201743
1744 mojo::PendingAssociatedRemote<mojom::NavigationClient> navigation_client;
Arthur Hemeryd6297f22019-10-22 14:34:411745 auto navigation_client_receiver =
1746 navigation_client.InitWithNewEndpointAndPassReceiver();
1747 rfh->frame_host_receiver_for_testing().impl()->BeginNavigation(
1748 std::move(common_params), std::move(begin_params), mojo::NullRemote(),
Rakina Zata Amniaf55b5b62022-07-19 23:11:031749 std::move(navigation_client), mojo::NullRemote(), mojo::NullReceiver());
Lukasz Anforowicz9666a162019-10-12 00:54:201750 EXPECT_EQ(bad_message::RFH_BASE_URL_FOR_DATA_URL_SPECIFIED,
1751 process_kill_waiter.Wait());
1752
1753 EXPECT_FALSE(ChildProcessSecurityPolicyImpl::GetInstance()->CanReadFile(
Emily Andrewsd15fd762024-12-10 20:41:541754 rfh->GetProcess()->GetDeprecatedID(), file_path));
Lukasz Anforowicz9666a162019-10-12 00:54:201755
1756 // Reload the page to create another renderer process.
1757 TestNavigationObserver tab_observer(shell()->web_contents(), 1);
1758 shell()->web_contents()->GetController().Reload(ReloadType::NORMAL, false);
1759 tab_observer.Wait();
1760
1761 // Make an XHR request to check if the page has access.
1762 std::string script = base::StringPrintf(
1763 "var xhr = new XMLHttpRequest()\n"
1764 "xhr.open('GET', '%s', false);\n"
1765 "try { xhr.send(); } catch (e) {}\n"
Avi Drissmanc91bd8e2021-04-19 23:58:441766 "xhr.responseText;",
Lukasz Anforowicz9666a162019-10-12 00:54:201767 file_url.spec().c_str());
Avi Drissmanc91bd8e2021-04-19 23:58:441768 std::string result = EvalJs(shell()->web_contents(), script).ExtractString();
Lukasz Anforowicz9666a162019-10-12 00:54:201769 EXPECT_TRUE(result.empty());
1770}
1771
W. James MacLeanead730a92024-06-21 18:34:201772// Test to verify that an exploited renderer process trying to specify a
1773// empty URL for initiator_base_url on navigation is correctly terminated.
1774IN_PROC_BROWSER_TEST_F(SecurityExploitViaDisabledWebSecurityTest,
1775 ValidateInitiatorBaseUrlNotEmpty) {
1776 GURL start_url(embedded_test_server()->GetURL("/title1.html"));
1777 EXPECT_TRUE(NavigateToURL(shell(), start_url));
1778
1779 RenderFrameHostImpl* rfh = static_cast<RenderFrameHostImpl*>(
1780 shell()->web_contents()->GetPrimaryMainFrame());
1781
1782 GURL url("about:blank");
1783
1784 // Setup a BeginNavigate IPC with empty, but not nullopt, initiator_base_url.
1785 blink::mojom::CommonNavigationParamsPtr common_params =
1786 blink::mojom::CommonNavigationParams::New(
1787 url, url::Origin::Create(start_url),
1788 /* initiator_base_url= */ GURL(), blink::mojom::Referrer::New(),
1789 ui::PAGE_TRANSITION_LINK,
1790 blink::mojom::NavigationType::DIFFERENT_DOCUMENT,
1791 blink::NavigationDownloadPolicy(),
1792 false /* should_replace_current_entry */,
Charlie Reise1d9b8182025-04-02 04:32:121793 GURL() /* base_url_for_data_url */,
1794 base::TimeTicks::Now() /* actual_navigation_start */,
W. James MacLeanead730a92024-06-21 18:34:201795 base::TimeTicks::Now() /* navigation_start */, "GET",
1796 nullptr /* post_data */, network::mojom::SourceLocation::New(),
1797 false /* started_from_context_menu */, false /* has_user_gesture */,
1798 false /* text_fragment_token */,
1799 network::mojom::CSPDisposition::CHECK,
1800 std::vector<int>() /* initiator_origin_trial_features */,
1801 std::string() /* href_translate */,
1802 false /* is_history_navigation_in_new_child_frame */,
1803 base::TimeTicks() /* input_start */,
1804 network::mojom::RequestDestination::kDocument);
1805 blink::mojom::BeginNavigationParamsPtr begin_params =
1806 blink::mojom::BeginNavigationParams::New(
1807 std::nullopt /* initiator_frame_token */, std::string() /* headers */,
1808 net::LOAD_NORMAL, false /* skip_service_worker */,
1809 blink::mojom::RequestContextType::LOCATION,
1810 blink::mojom::MixedContentContextType::kBlockable,
1811 false /* is_form_submission */,
1812 false /* was_initiated_by_link_click */,
1813 blink::mojom::ForceHistoryPush::kNo, GURL() /* searchable_form_url */,
1814 std::string() /* searchable_form_encoding */,
1815 GURL() /* client_side_redirect_url */,
1816 std::nullopt /* devtools_initiator_info */,
1817 nullptr /* trust_token_params */, std::nullopt /* impression */,
1818 base::TimeTicks() /* renderer_before_unload_start */,
1819 base::TimeTicks() /* renderer_before_unload_end */,
1820 blink::mojom::NavigationInitiatorActivationAndAdStatus::
1821 kDidNotStartWithTransientActivation,
Chris Fredrickson9ffdf5b2024-07-09 20:05:091822 false /* is_container_initiated */,
1823 net::StorageAccessApiStatus::kNone, false /* has_rel_opener */);
W. James MacLeanead730a92024-06-21 18:34:201824
1825 // Receiving the invalid IPC message should lead to renderer process
1826 // termination.
1827 RenderProcessHostBadIpcMessageWaiter process_kill_waiter(rfh->GetProcess());
1828
1829 mojo::PendingAssociatedRemote<mojom::NavigationClient> navigation_client;
1830 auto navigation_client_receiver =
1831 navigation_client.InitWithNewEndpointAndPassReceiver();
1832 rfh->frame_host_receiver_for_testing().impl()->BeginNavigation(
1833 std::move(common_params), std::move(begin_params), mojo::NullRemote(),
1834 std::move(navigation_client), mojo::NullRemote(), mojo::NullReceiver());
1835 EXPECT_EQ(bad_message::RFH_INITIATOR_BASE_URL_IS_EMPTY,
1836 process_kill_waiter.Wait());
1837}
1838
Lukasz Anforowicz9666a162019-10-12 00:54:201839// Tests what happens when a web renderer asks to begin navigating to a file
1840// url.
1841IN_PROC_BROWSER_TEST_F(SecurityExploitViaDisabledWebSecurityTest,
1842 WebToFileNavigation) {
1843 // Navigate to a web page.
1844 GURL start_url(embedded_test_server()->GetURL("/title1.html"));
1845 EXPECT_TRUE(NavigateToURL(shell(), start_url));
1846
1847 // Have the webpage attempt to open a window with a file URL.
1848 //
1849 // Note that such attempt would normally be blocked in the renderer ("Not
1850 // allowed to load local resource: file:///..."), but the test here simulates
1851 // a compromised renderer by using --disable-web-security cmdline flag.
1852 GURL file_url = GetTestUrl("", "simple_page.html");
1853 WebContentsAddedObserver new_window_observer;
1854 TestNavigationObserver nav_observer(nullptr);
1855 nav_observer.StartWatchingNewWebContents();
1856 ASSERT_TRUE(ExecJs(shell()->web_contents(),
1857 JsReplace("window.open($1, '_blank')", file_url)));
1858 WebContents* new_window = new_window_observer.GetWebContents();
1859 nav_observer.WaitForNavigationFinished();
1860
1861 // Verify that the navigation got blocked.
1862 EXPECT_TRUE(nav_observer.last_navigation_succeeded());
1863 EXPECT_EQ(GURL(kBlockedURL), nav_observer.last_navigation_url());
1864 EXPECT_EQ(GURL(kBlockedURL),
Dave Tapuska327c06c92022-06-13 20:31:511865 new_window->GetPrimaryMainFrame()->GetLastCommittedURL());
1866 EXPECT_EQ(
1867 shell()->web_contents()->GetPrimaryMainFrame()->GetLastCommittedOrigin(),
1868 new_window->GetPrimaryMainFrame()->GetLastCommittedOrigin());
1869 EXPECT_EQ(shell()->web_contents()->GetPrimaryMainFrame()->GetProcess(),
1870 new_window->GetPrimaryMainFrame()->GetProcess());
Lukasz Anforowicz9666a162019-10-12 00:54:201871
1872 // Even though the navigation is blocked, we expect the opener relationship to
1873 // be established between the 2 windows.
1874 EXPECT_EQ(true, ExecJs(new_window, "!!window.opener"));
1875}
1876
1877// Tests what happens when a web renderer asks to begin navigating to a
1878// view-source url.
1879IN_PROC_BROWSER_TEST_F(SecurityExploitViaDisabledWebSecurityTest,
1880 WebToViewSourceNavigation) {
1881 // Navigate to a web page.
1882 GURL start_url(embedded_test_server()->GetURL("/title1.html"));
1883 EXPECT_TRUE(NavigateToURL(shell(), start_url));
1884
1885 // Have the webpage attempt to open a window with a view-source URL.
1886 //
1887 // Note that such attempt would normally be blocked in the renderer ("Not
1888 // allowed to load local resource: view-source:///..."), but the test here
1889 // simulates a compromised renderer by using --disable-web-security flag.
1890 base::FilePath file_path = GetTestFilePath("", "simple_page.html");
1891 GURL view_source_url =
1892 GURL(std::string(kViewSourceScheme) + ":" + start_url.spec());
1893 WebContentsAddedObserver new_window_observer;
1894 TestNavigationObserver nav_observer(nullptr);
1895 nav_observer.StartWatchingNewWebContents();
1896 ASSERT_TRUE(ExecJs(shell()->web_contents(),
1897 JsReplace("window.open($1, '_blank')", view_source_url)));
1898 WebContents* new_window = new_window_observer.GetWebContents();
1899 nav_observer.WaitForNavigationFinished();
1900
1901 // Verify that the navigation got blocked.
1902 EXPECT_TRUE(nav_observer.last_navigation_succeeded());
1903 EXPECT_EQ(GURL(kBlockedURL), nav_observer.last_navigation_url());
1904 EXPECT_EQ(GURL(kBlockedURL),
Dave Tapuska327c06c92022-06-13 20:31:511905 new_window->GetPrimaryMainFrame()->GetLastCommittedURL());
1906 EXPECT_EQ(
1907 shell()->web_contents()->GetPrimaryMainFrame()->GetLastCommittedOrigin(),
1908 new_window->GetPrimaryMainFrame()->GetLastCommittedOrigin());
1909 EXPECT_EQ(shell()->web_contents()->GetPrimaryMainFrame()->GetProcess(),
1910 new_window->GetPrimaryMainFrame()->GetProcess());
Lukasz Anforowicz9666a162019-10-12 00:54:201911
1912 // Even though the navigation is blocked, we expect the opener relationship to
1913 // be established between the 2 windows.
1914 EXPECT_EQ(true, ExecJs(new_window, "!!window.opener"));
1915}
1916
David Van Cleveca4ef9062020-04-27 23:46:531917class BeginNavigationTrustTokenParamsReplacer : public FrameHostInterceptor {
1918 public:
1919 BeginNavigationTrustTokenParamsReplacer(
1920 WebContents* web_contents,
1921 network::mojom::TrustTokenParamsPtr params_to_inject)
1922 : FrameHostInterceptor(web_contents),
1923 params_to_inject_(std::move(params_to_inject)) {}
1924
1925 BeginNavigationTrustTokenParamsReplacer(
1926 const BeginNavigationTrustTokenParamsReplacer&) = delete;
1927 BeginNavigationTrustTokenParamsReplacer& operator=(
1928 const BeginNavigationTrustTokenParamsReplacer&) = delete;
1929
1930 bool WillDispatchBeginNavigation(
1931 RenderFrameHost* render_frame_host,
Minggang Wangb9f3fa92021-07-01 15:30:311932 blink::mojom::CommonNavigationParamsPtr* common_params,
1933 blink::mojom::BeginNavigationParamsPtr* begin_params,
David Van Cleveca4ef9062020-04-27 23:46:531934 mojo::PendingRemote<blink::mojom::BlobURLToken>* blob_url_token,
Antonio Sartori636adba2021-03-09 12:15:271935 mojo::PendingAssociatedRemote<mojom::NavigationClient>* navigation_client)
1936 override {
David Van Cleveca4ef9062020-04-27 23:46:531937 if (is_activated_) {
1938 (*begin_params)->trust_token_params = params_to_inject_.Clone();
1939 is_activated_ = false;
1940 }
1941
1942 return true;
1943 }
1944
1945 void Activate() { is_activated_ = true; }
1946
1947 private:
1948 network::mojom::TrustTokenParamsPtr params_to_inject_;
1949 bool is_activated_ = false;
1950};
1951
1952class SecurityExploitBrowserTestWithTrustTokensEnabled
1953 : public SecurityExploitBrowserTest {
1954 public:
Muyao Xu943e6a62024-12-17 02:51:321955 SecurityExploitBrowserTestWithTrustTokensEnabled() = default;
David Van Cleveca4ef9062020-04-27 23:46:531956};
1957
1958// Test that the browser correctly reports a bad message when a child frame
Sergey Kataev88cea7bb2023-01-06 01:52:531959// attempts to navigate with a Private State Tokens redemption operation
1960// associated with the navigation, but its parent lacks the
Aykut Bulut1af9bfa12023-11-10 21:40:301961// private-state-token-redemption Permissions Policy feature.
Charlie Hubb5943d2021-03-09 19:46:121962IN_PROC_BROWSER_TEST_F(
1963 SecurityExploitBrowserTestWithTrustTokensEnabled,
1964 BrowserForbidsTrustTokenRedemptionWithoutPermissionsPolicy) {
David Van Cleveca4ef9062020-04-27 23:46:531965 WebContents* web_contents = shell()->web_contents();
1966
1967 // Prepare to intercept BeginNavigation mojo IPC. This has to be done before
1968 // the test creates the RenderFrameHostImpl that is the target of the IPC.
1969 auto params = network::mojom::TrustTokenParams::New();
Aykut Bulut832c80f2023-01-19 18:37:351970 params->operation = network::mojom::TrustTokenOperationType::kRedemption;
David Van Cleveca4ef9062020-04-27 23:46:531971 BeginNavigationTrustTokenParamsReplacer replacer(web_contents,
1972 std::move(params));
1973
1974 GURL start_url(embedded_test_server()->GetURL(
Charlie Hu563114f2021-03-11 18:56:361975 "/page-with-trust-token-permissions-policy-disabled.html"));
David Van Cleveca4ef9062020-04-27 23:46:531976 EXPECT_TRUE(NavigateToURL(shell(), start_url));
1977
Dave Tapuska327c06c92022-06-13 20:31:511978 RenderFrameHost* parent = web_contents->GetPrimaryMainFrame();
David Van Cleveca4ef9062020-04-27 23:46:531979 ASSERT_FALSE(parent->IsFeatureEnabled(
Sandor «Alex» Majore9545a72025-01-31 20:40:461980 network::mojom::PermissionsPolicyFeature::kTrustTokenRedemption));
David Van Cleveca4ef9062020-04-27 23:46:531981
Daniel Cheng6a539352023-05-09 03:25:201982 replacer.Activate();
1983
David Van Cleveca4ef9062020-04-27 23:46:531984 RenderFrameHost* child = static_cast<WebContentsImpl*>(web_contents)
Carlos Caballero15caeeb2021-10-27 09:57:551985 ->GetPrimaryFrameTree()
1986 .root()
David Van Cleveca4ef9062020-04-27 23:46:531987 ->child_at(0)
1988 ->current_frame_host();
Daniel Cheng6a539352023-05-09 03:25:201989 ExecuteScriptAsync(child, JsReplace("location = $1", "/title2.html"));
1990
David Van Cleveca4ef9062020-04-27 23:46:531991 RenderProcessHostBadMojoMessageWaiter kill_waiter(child->GetProcess());
David Van Cleveca4ef9062020-04-27 23:46:531992 EXPECT_THAT(kill_waiter.Wait(),
Charlie Hu563114f2021-03-11 18:56:361993 Optional(HasSubstr("Permissions Policy feature is absent")));
David Van Cleveca4ef9062020-04-27 23:46:531994}
1995
1996// Test that the browser correctly reports a bad message when a child frame
Sergey Kataev88cea7bb2023-01-06 01:52:531997// attempts to navigate with a Private State Tokens signing operation associated
Aykut Bulut1af9bfa12023-11-10 21:40:301998// with the navigation, but its parent lacks the private-state-token-redemption
1999// (sic) Permissions Policy feature.
Charlie Hubb5943d2021-03-09 19:46:122000IN_PROC_BROWSER_TEST_F(
2001 SecurityExploitBrowserTestWithTrustTokensEnabled,
2002 BrowserForbidsTrustTokenSigningWithoutPermissionsPolicy) {
David Van Cleveca4ef9062020-04-27 23:46:532003 WebContents* web_contents = shell()->web_contents();
2004
2005 // Prepare to intercept BeginNavigation mojo IPC. This has to be done before
2006 // the test creates the RenderFrameHostImpl that is the target of the IPC.
2007 auto params = network::mojom::TrustTokenParams::New();
Aykut Bulut832c80f2023-01-19 18:37:352008 params->operation = network::mojom::TrustTokenOperationType::kSigning;
David Van Cleveca4ef9062020-04-27 23:46:532009 BeginNavigationTrustTokenParamsReplacer replacer(web_contents,
2010 std::move(params));
2011
2012 GURL start_url(embedded_test_server()->GetURL(
Charlie Hu563114f2021-03-11 18:56:362013 "/page-with-trust-token-permissions-policy-disabled.html"));
David Van Cleveca4ef9062020-04-27 23:46:532014 EXPECT_TRUE(NavigateToURL(shell(), start_url));
2015
Dave Tapuska327c06c92022-06-13 20:31:512016 RenderFrameHost* parent = web_contents->GetPrimaryMainFrame();
David Van Cleveca4ef9062020-04-27 23:46:532017 ASSERT_FALSE(parent->IsFeatureEnabled(
Sandor «Alex» Majore9545a72025-01-31 20:40:462018 network::mojom::PermissionsPolicyFeature::kTrustTokenRedemption));
David Van Cleveca4ef9062020-04-27 23:46:532019
Daniel Cheng6a539352023-05-09 03:25:202020 replacer.Activate();
2021
David Van Cleveca4ef9062020-04-27 23:46:532022 RenderFrameHost* child = static_cast<WebContentsImpl*>(web_contents)
Carlos Caballero15caeeb2021-10-27 09:57:552023 ->GetPrimaryFrameTree()
2024 .root()
David Van Cleveca4ef9062020-04-27 23:46:532025 ->child_at(0)
2026 ->current_frame_host();
Daniel Cheng6a539352023-05-09 03:25:202027 ExecuteScriptAsync(child, JsReplace("location = $1", "/title2.html"));
2028
David Van Cleveca4ef9062020-04-27 23:46:532029 RenderProcessHostBadMojoMessageWaiter kill_waiter(child->GetProcess());
Aykut Bulut871d3d22023-04-25 21:33:122030 EXPECT_THAT(kill_waiter.Wait(),
2031 Optional(HasSubstr("Permissions Policy feature is absent")));
2032}
2033
2034// Test that the browser correctly reports a bad message when a child frame
2035// attempts to navigate with a Private State Tokens issue operation
2036// associated with the navigation, but its parent lacks the
2037// private-state-token-issuance Permissions Policy feature.
2038IN_PROC_BROWSER_TEST_F(
2039 SecurityExploitBrowserTestWithTrustTokensEnabled,
2040 BrowserForbidsTrustTokenIssuanceWithoutPermissionsPolicy) {
2041 WebContents* web_contents = shell()->web_contents();
2042
2043 // Prepare to intercept BeginNavigation mojo IPC. This has to be done before
2044 // the test creates the RenderFrameHostImpl that is the target of the IPC.
2045 auto params = network::mojom::TrustTokenParams::New();
Aykut Bulut871d3d22023-04-25 21:33:122046 params->operation = network::mojom::TrustTokenOperationType::kIssuance;
2047 BeginNavigationTrustTokenParamsReplacer replacer(web_contents,
2048 std::move(params));
2049
2050 GURL start_url(embedded_test_server()->GetURL(
2051 "/page-with-trust-token-permissions-policy-disabled.html"));
2052 EXPECT_TRUE(NavigateToURL(shell(), start_url));
2053
2054 RenderFrameHost* parent = web_contents->GetPrimaryMainFrame();
2055 ASSERT_FALSE(parent->IsFeatureEnabled(
Sandor «Alex» Majore9545a72025-01-31 20:40:462056 network::mojom::PermissionsPolicyFeature::kPrivateStateTokenIssuance));
Aykut Bulut871d3d22023-04-25 21:33:122057
Daniel Cheng6a539352023-05-09 03:25:202058 replacer.Activate();
2059
Aykut Bulut871d3d22023-04-25 21:33:122060 RenderFrameHost* child = static_cast<WebContentsImpl*>(web_contents)
2061 ->GetPrimaryFrameTree()
2062 .root()
2063 ->child_at(0)
2064 ->current_frame_host();
Daniel Cheng6a539352023-05-09 03:25:202065 ExecuteScriptAsync(child, JsReplace("location = $1", "/title2.html"));
2066
Aykut Bulut871d3d22023-04-25 21:33:122067 RenderProcessHostBadMojoMessageWaiter kill_waiter(child->GetProcess());
David Van Cleveca4ef9062020-04-27 23:46:532068 EXPECT_THAT(kill_waiter.Wait(),
Charlie Hu563114f2021-03-11 18:56:362069 Optional(HasSubstr("Permissions Policy feature is absent")));
David Van Cleveca4ef9062020-04-27 23:46:532070}
2071
2072IN_PROC_BROWSER_TEST_F(SecurityExploitBrowserTestWithTrustTokensEnabled,
2073 BrowserForbidsTrustTokenParamsOnMainFrameNav) {
2074 WebContents* web_contents = shell()->web_contents();
2075
2076 // Prepare to intercept BeginNavigation mojo IPC. This has to be done before
2077 // the test creates the RenderFrameHostImpl that is the target of the IPC.
2078 BeginNavigationTrustTokenParamsReplacer replacer(
2079 web_contents, network::mojom::TrustTokenParams::New());
2080
2081 GURL start_url(embedded_test_server()->GetURL("/title1.html"));
2082 EXPECT_TRUE(NavigateToURL(shell(), start_url));
2083
David Van Cleveca4ef9062020-04-27 23:46:532084 replacer.Activate();
2085
Daniel Cheng6a539352023-05-09 03:25:202086 RenderFrameHost* compromised_renderer = web_contents->GetPrimaryMainFrame();
2087 ExecuteScriptAsync(compromised_renderer,
2088 JsReplace("location = $1", "/title2.html"));
David Van Cleveca4ef9062020-04-27 23:46:532089
Daniel Cheng6a539352023-05-09 03:25:202090 RenderProcessHostBadMojoMessageWaiter kill_waiter(
2091 compromised_renderer->GetProcess());
Sergey Kataev88cea7bb2023-01-06 01:52:532092 EXPECT_THAT(
2093 kill_waiter.Wait(),
2094 Optional(HasSubstr("Private State Token params in main frame nav")));
David Van Cleveca4ef9062020-04-27 23:46:532095}
2096
Liviu Tintaeb63e1452021-10-28 18:47:022097class FencedFrameSecurityExploitBrowserTestWithTrustTokensEnabled
2098 : public SecurityExploitBrowserTestWithTrustTokensEnabled {
2099 protected:
2100 FencedFrameSecurityExploitBrowserTestWithTrustTokensEnabled() = default;
2101
2102 WebContentsImpl* web_contents() {
2103 return static_cast<WebContentsImpl*>(shell()->web_contents());
2104 }
2105
2106 RenderFrameHostImpl* primary_main_frame_host() {
Dave Tapuska327c06c92022-06-13 20:31:512107 return web_contents()->GetPrimaryMainFrame();
Liviu Tintaeb63e1452021-10-28 18:47:022108 }
2109
2110 test::FencedFrameTestHelper& fenced_frame_test_helper() {
2111 return fenced_frame_test_helper_;
2112 }
2113
2114 private:
2115 test::FencedFrameTestHelper fenced_frame_test_helper_;
2116};
2117
2118class FencedFrameBeginNavigationTrustTokenParamsReplacer
2119 : public BeginNavigationTrustTokenParamsReplacer {
2120 public:
2121 FencedFrameBeginNavigationTrustTokenParamsReplacer(
2122 WebContents* web_contents,
2123 network::mojom::TrustTokenParamsPtr params_to_inject)
2124 : BeginNavigationTrustTokenParamsReplacer(web_contents,
2125 std::move(params_to_inject)) {}
2126
2127 FencedFrameBeginNavigationTrustTokenParamsReplacer(
2128 const FencedFrameBeginNavigationTrustTokenParamsReplacer&) = delete;
2129 FencedFrameBeginNavigationTrustTokenParamsReplacer& operator=(
2130 const FencedFrameBeginNavigationTrustTokenParamsReplacer&) = delete;
2131
2132 bool WillDispatchBeginNavigation(
2133 RenderFrameHost* render_frame_host,
2134 blink::mojom::CommonNavigationParamsPtr* common_params,
2135 blink::mojom::BeginNavigationParamsPtr* begin_params,
2136 mojo::PendingRemote<blink::mojom::BlobURLToken>* blob_url_token,
2137 mojo::PendingAssociatedRemote<mojom::NavigationClient>* navigation_client)
2138 override {
2139 if (render_frame_host->IsFencedFrameRoot()) {
2140 BeginNavigationTrustTokenParamsReplacer::WillDispatchBeginNavigation(
2141 render_frame_host, common_params, begin_params, blob_url_token,
2142 navigation_client);
2143 }
2144 return true;
2145 }
2146};
2147
2148IN_PROC_BROWSER_TEST_F(
2149 FencedFrameSecurityExploitBrowserTestWithTrustTokensEnabled,
2150 BrowserForbidsTrustTokenParamsOnFencedFrameNav) {
2151 WebContents* web_contents = shell()->web_contents();
2152
2153 // Prepare to intercept BeginNavigation mojo IPC. This has to be done before
2154 // the test creates the RenderFrameHostImpl that is the target of the IPC.
2155 FencedFrameBeginNavigationTrustTokenParamsReplacer replacer(
2156 web_contents, network::mojom::TrustTokenParams::New());
2157 GURL start_url(embedded_test_server()->GetURL("/empty.html"));
2158 EXPECT_TRUE(NavigateToURL(shell(), start_url));
2159
2160 RenderFrameHostImplWrapper primary_rfh(primary_main_frame_host());
2161 RenderFrameHostImplWrapper inner_fenced_frame_rfh(
2162 fenced_frame_test_helper().CreateFencedFrame(
Dominic Farolino667161a92021-11-20 19:29:022163 primary_rfh.get(),
2164 embedded_test_server()->GetURL("/fenced_frames/empty.html")));
Liviu Tintaeb63e1452021-10-28 18:47:022165
2166 RenderFrameHost* compromised_renderer = inner_fenced_frame_rfh.get();
2167 RenderProcessHostBadMojoMessageWaiter kill_waiter(
2168 compromised_renderer->GetProcess());
2169 replacer.Activate();
2170
Avi Drissman5d5d48d62022-01-07 20:23:582171 std::ignore = ExecJs(
Dominic Farolino667161a92021-11-20 19:29:022172 compromised_renderer,
2173 JsReplace("location.href=$1",
Avi Drissman5d5d48d62022-01-07 20:23:582174 embedded_test_server()->GetURL("/fenced_frames/title1.html")));
Liviu Tintaeb63e1452021-10-28 18:47:022175
Arthur Sonzognic686e8f2024-01-11 08:36:372176 std::optional<std::string> result = kill_waiter.Wait();
Liviu Tintaeb63e1452021-10-28 18:47:022177 EXPECT_THAT(result,
Sergey Kataev88cea7bb2023-01-06 01:52:532178 Optional(HasSubstr("Private State Token params in fenced frame "
2179 "nav")));
Liviu Tintaeb63e1452021-10-28 18:47:022180}
2181
John Delaney943f31b2022-05-16 18:35:402182class SecurityExploitTestFencedFramesDisabled
2183 : public SecurityExploitBrowserTest {
2184 public:
2185 SecurityExploitTestFencedFramesDisabled() {
2186 feature_list_.InitAndDisableFeature(blink::features::kFencedFrames);
2187 }
2188
2189 private:
2190 base::test::ScopedFeatureList feature_list_;
2191};
2192
Brendon Tiszka2b3304f2021-12-10 19:51:492193// Ensure that we kill the renderer process if we try to create a
Dominic Farolino0b067632022-11-11 02:57:492194// fenced-frame when the blink::features::kFencedFrames feature is not enabled.
John Delaney943f31b2022-05-16 18:35:402195IN_PROC_BROWSER_TEST_F(SecurityExploitTestFencedFramesDisabled,
Brendon Tiszka2b3304f2021-12-10 19:51:492196 CreateFencedFrameWhenFeatureDisabled) {
2197 GURL foo("http://foo.com/simple_page.html");
2198 EXPECT_TRUE(NavigateToURL(shell(), foo));
2199 EXPECT_EQ(u"OK", shell()->web_contents()->GetTitle());
Brendon Tiszka9b740962021-12-20 08:50:182200 EXPECT_FALSE(blink::features::IsFencedFramesEnabled());
Brendon Tiszka2b3304f2021-12-10 19:51:492201
2202 RenderFrameHostImpl* compromised_rfh = static_cast<RenderFrameHostImpl*>(
Dave Tapuska327c06c92022-06-13 20:31:512203 shell()->web_contents()->GetPrimaryMainFrame());
Brendon Tiszka2b3304f2021-12-10 19:51:492204
2205 mojo::PendingAssociatedRemote<blink::mojom::FencedFrameOwnerHost> remote;
2206 mojo::PendingAssociatedReceiver<blink::mojom::FencedFrameOwnerHost> receiver;
2207 receiver = remote.InitWithNewEndpointAndPassReceiver();
2208
Dave Tapuska82b54012022-07-15 23:26:102209 auto remote_frame_interfaces =
Dave Tapuska68024c12022-07-26 16:09:012210 blink::mojom::RemoteFrameInterfacesFromRenderer::New();
Dave Tapuska82b54012022-07-15 23:26:102211 remote_frame_interfaces->frame_host_receiver =
2212 mojo::AssociatedRemote<blink::mojom::RemoteFrameHost>()
2213 .BindNewEndpointAndPassDedicatedReceiver();
2214 mojo::AssociatedRemote<blink::mojom::RemoteFrame> frame;
2215 std::ignore = frame.BindNewEndpointAndPassDedicatedReceiver();
2216 remote_frame_interfaces->frame = frame.Unbind();
2217
Brendon Tiszka2b3304f2021-12-10 19:51:492218 RenderProcessHostBadIpcMessageWaiter kill_waiter(
2219 compromised_rfh->GetProcess());
Dave Tapuska68024c12022-07-26 16:09:012220 static_cast<blink::mojom::LocalFrameHost*>(compromised_rfh)
Tsuyoshi Horoc733208b2022-02-18 17:32:482221 ->CreateFencedFrame(
Garrett Tanzer291a2d52023-03-20 22:41:572222 std::move(receiver), std::move(remote_frame_interfaces),
2223 blink::RemoteFrameToken(), base::UnguessableToken::Create());
Brendon Tiszka2b3304f2021-12-10 19:51:492224 EXPECT_EQ(bad_message::RFH_FENCED_FRAME_MOJO_WHEN_DISABLED,
2225 kill_waiter.Wait());
2226}
2227
Garrett Tanzer6c807e32022-04-14 17:33:582228// Ensure that we kill the renderer process if we try to do a top-level
2229// navigation using the special _unfencedTop IPC path when we are not inside
2230// a fenced frame. (Test from an iframe instead.)
2231IN_PROC_BROWSER_TEST_F(SecurityExploitBrowserTest,
2232 UnfencedTopFromOutsideFencedFrame) {
2233 GURL main_url(embedded_test_server()->GetURL(
2234 "a.com", "/cross_site_iframe_factory.html?a(b)"));
2235 EXPECT_TRUE(NavigateToURL(shell(), main_url));
2236 FrameTreeNode* root = static_cast<WebContentsImpl*>(shell()->web_contents())
2237 ->GetPrimaryFrameTree()
2238 .root();
Garrett Tanzera42fdef2022-06-13 16:09:142239 RenderFrameHostImpl* compromised_rfh =
2240 root->child_at(0)->current_frame_host();
Garrett Tanzer6c807e32022-04-14 17:33:582241
2242 RenderProcessHostBadIpcMessageWaiter kill_waiter(
Garrett Tanzera42fdef2022-06-13 16:09:142243 compromised_rfh->GetProcess());
Garrett Tanzer6c807e32022-04-14 17:33:582244
2245 GURL url("http://foo.com/simple_page.html");
Garrett Tanzera42fdef2022-06-13 16:09:142246 auto params = CreateOpenURLParams(url);
2247 params->is_unfenced_top_navigation = true;
2248 static_cast<mojom::FrameHost*>(compromised_rfh)->OpenURL(std::move(params));
Garrett Tanzer6c807e32022-04-14 17:33:582249
2250 EXPECT_EQ(bad_message::RFHI_UNFENCED_TOP_IPC_OUTSIDE_FENCED_FRAME,
2251 kill_waiter.Wait());
2252}
2253
Dominic Farolino0b067632022-11-11 02:57:492254class SecurityExploitBrowserTestFencedFrames
2255 : public SecurityExploitBrowserTest {
Dominic Farolinoa185eb72022-04-15 02:34:292256 public:
Liam Bradyb0f1f0e2022-08-19 21:42:112257 void SetUpOnMainThread() override {
2258 host_resolver()->AddRule("*", "127.0.0.1");
2259 https_server()->StartAcceptingConnections();
2260 }
2261
2262 void SetUpCommandLine(base::CommandLine* command_line) override {
2263 https_server()->AddDefaultHandlers(GetTestDataFilePath());
2264 https_server()->ServeFilesFromSourceDirectory(GetTestDataFilePath());
2265 https_server()->SetSSLConfig(net::EmbeddedTestServer::CERT_TEST_NAMES);
2266 SetupCrossSiteRedirector(https_server());
2267
2268 // EmbeddedTestServer::InitializeAndListen() initializes its |base_url_|
2269 // which is required below. This cannot invoke Start() however as that kicks
2270 // off the "EmbeddedTestServer IO Thread" which then races with
2271 // initialization in ContentBrowserTest::SetUp(), http://crbug.com/674545.
2272 ASSERT_TRUE(https_server()->InitializeAndListen());
2273 }
2274
2275 test::FencedFrameTestHelper& fenced_frame_test_helper() {
2276 return fenced_frame_test_helper_;
2277 }
2278
2279 net::EmbeddedTestServer* https_server() { return &https_server_; }
2280
Dominic Farolinoa185eb72022-04-15 02:34:292281 private:
Dominic Farolino0b067632022-11-11 02:57:492282 test::FencedFrameTestHelper fenced_frame_test_helper_{};
Liam Bradyb0f1f0e2022-08-19 21:42:112283 net::EmbeddedTestServer https_server_{net::EmbeddedTestServer::TYPE_HTTPS};
Dominic Farolinoa185eb72022-04-15 02:34:292284};
2285
Dominic Farolino0b067632022-11-11 02:57:492286IN_PROC_BROWSER_TEST_F(SecurityExploitBrowserTestFencedFrames,
Dominic Farolinoe724b5332022-04-26 05:00:062287 NavigateFencedFrameToInvalidURL) {
Liam Bradyb0f1f0e2022-08-19 21:42:112288 GURL main_frame_url(https_server()->GetURL("a.test", "/simple_page.html"));
Liam Brady9108c28d2022-05-06 21:14:322289 std::vector<GURL> invalid_urls = {
2290 GURL("http://example.com"),
Dominic Farolinofd8569852022-08-24 22:26:252291 GURL("http://example.com?<\n=block"),
Liam Brady9108c28d2022-05-06 21:14:322292 GURL("about:srcdoc"),
2293 GURL("data:text/html,<p>foo"),
2294 GURL("blob:https://example.com/a9400bf5-aaa8-4166-86e4-492c50f4ca2b"),
2295 GURL("file://folder"),
2296 GURL("javascript:console.log('foo');"),
2297 GetWebUIURL(kChromeUIHistogramHost),
2298 GURL(blink::kChromeUIHangURL)};
Dominic Farolinoa185eb72022-04-15 02:34:292299
Dominic Farolinoe724b5332022-04-26 05:00:062300 for (const GURL& invalid_url : invalid_urls) {
2301 EXPECT_FALSE(blink::IsValidFencedFrameURL(invalid_url));
2302 EXPECT_TRUE(blink::features::IsFencedFramesEnabled());
2303 EXPECT_TRUE(NavigateToURL(shell(), main_frame_url));
2304 EXPECT_EQ(u"OK", shell()->web_contents()->GetTitle());
Dominic Farolinoa185eb72022-04-15 02:34:292305
Dominic Farolinoe724b5332022-04-26 05:00:062306 RenderFrameHostImpl* compromised_rfh = static_cast<RenderFrameHostImpl*>(
Dave Tapuska327c06c92022-06-13 20:31:512307 shell()->web_contents()->GetPrimaryMainFrame());
Dominic Farolinoa185eb72022-04-15 02:34:292308
Dominic Farolinoe724b5332022-04-26 05:00:062309 mojo::AssociatedRemote<blink::mojom::FencedFrameOwnerHost> remote;
2310 mojo::PendingAssociatedReceiver<blink::mojom::FencedFrameOwnerHost>
2311 pending_receiver = remote.BindNewEndpointAndPassReceiver();
Dominic Farolinoa185eb72022-04-15 02:34:292312
Dave Tapuska82b54012022-07-15 23:26:102313 auto remote_frame_interfaces =
Dave Tapuska68024c12022-07-26 16:09:012314 blink::mojom::RemoteFrameInterfacesFromRenderer::New();
Dave Tapuska82b54012022-07-15 23:26:102315 remote_frame_interfaces->frame_host_receiver =
2316 mojo::AssociatedRemote<blink::mojom::RemoteFrameHost>()
2317 .BindNewEndpointAndPassDedicatedReceiver();
2318 mojo::AssociatedRemote<blink::mojom::RemoteFrame> frame;
2319 std::ignore = frame.BindNewEndpointAndPassDedicatedReceiver();
2320 remote_frame_interfaces->frame = frame.Unbind();
2321
Dominic Farolinoe724b5332022-04-26 05:00:062322 RenderProcessHostBadIpcMessageWaiter kill_waiter(
2323 compromised_rfh->GetProcess());
Dave Tapuska68024c12022-07-26 16:09:012324 static_cast<blink::mojom::LocalFrameHost*>(compromised_rfh)
Garrett Tanzer291a2d52023-03-20 22:41:572325 ->CreateFencedFrame(
2326 std::move(pending_receiver), std::move(remote_frame_interfaces),
2327 blink::RemoteFrameToken(), base::UnguessableToken::Create());
Dominic Farolinoe724b5332022-04-26 05:00:062328 EXPECT_EQ(compromised_rfh->GetFencedFrames().size(), 1u);
2329
2330 FencedFrame* fenced_frame = compromised_rfh->GetFencedFrames()[0];
2331 static_cast<blink::mojom::FencedFrameOwnerHost*>(fenced_frame)
Camillia Smith Barnes6a643962023-03-03 00:28:582332 ->Navigate(invalid_url, base::TimeTicks(),
Arthur Sonzognic686e8f2024-01-11 08:36:372333 /*embedder_shared_storage_context=*/std::nullopt);
Dominic Farolinoe724b5332022-04-26 05:00:062334 EXPECT_EQ(bad_message::FF_NAVIGATION_INVALID_URL, kill_waiter.Wait());
2335 }
Dominic Farolinoa185eb72022-04-15 02:34:292336}
2337
Dominic Farolino0b067632022-11-11 02:57:492338IN_PROC_BROWSER_TEST_F(SecurityExploitBrowserTestFencedFrames,
Liam Bradyb0f1f0e2022-08-19 21:42:112339 ChangeFencedFrameSandboxFlags) {
2340 GURL main_frame_url(https_server()->GetURL("a.test", "/simple_page.html"));
2341
2342 EXPECT_TRUE(NavigateToURL(shell(), main_frame_url));
2343 RenderFrameHostImpl* root_rfh = static_cast<RenderFrameHostImpl*>(
2344 shell()->web_contents()->GetPrimaryMainFrame());
2345
2346 const GURL fenced_frame_url =
2347 https_server()->GetURL("a.test", "/fenced_frames/sandbox_flags.html");
2348 constexpr char kAddFencedFrameScript[] = R"({
2349 const fenced_frame = document.createElement('fencedframe');
Liam Brady4a33cee2023-04-03 21:15:242350 fenced_frame.config = new FencedFrameConfig($1);
Liam Bradyb0f1f0e2022-08-19 21:42:112351 document.body.appendChild(fenced_frame);
2352 })";
2353 EXPECT_TRUE(
2354 ExecJs(root_rfh, JsReplace(kAddFencedFrameScript, fenced_frame_url)));
2355
2356 RenderFrameHostImpl* fenced_rfh = nullptr;
2357 RenderFrameHostImpl* parent_rfh = nullptr;
2358
Dominic Farolino0b067632022-11-11 02:57:492359 std::vector<FencedFrame*> fenced_frames = root_rfh->GetFencedFrames();
2360 EXPECT_EQ(fenced_frames.size(), 1u);
2361 FencedFrame* new_fenced_frame = fenced_frames.back();
2362 fenced_rfh = new_fenced_frame->GetInnerRoot();
2363 parent_rfh = fenced_rfh;
Liam Bradyb0f1f0e2022-08-19 21:42:112364
2365 RenderProcessHostBadIpcMessageWaiter kill_waiter(fenced_rfh->GetProcess());
2366
2367 blink::FramePolicy first_policy =
2368 fenced_rfh->frame_tree_node()->pending_frame_policy();
2369
2370 first_policy.sandbox_flags = blink::kFencedFrameMandatoryUnsandboxedFlags;
2371 static_cast<blink::mojom::LocalFrameHost*>(parent_rfh)
2372 ->DidChangeFramePolicy(std::move(fenced_rfh->GetFrameToken()),
2373 std::move(first_policy));
2374
Dominic Farolino0b067632022-11-11 02:57:492375 EXPECT_EQ(bad_message::RFH_SANDBOX_FLAGS, kill_waiter.Wait());
Liam Bradyb0f1f0e2022-08-19 21:42:112376}
2377
Liam Bradyce3383b2023-08-16 18:37:212378IN_PROC_BROWSER_TEST_F(SecurityExploitBrowserTestFencedFrames,
2379 PullFocusAcrossFencedBoundary) {
2380 base::HistogramTester histogram_tester;
2381 GURL main_frame_url(https_server()->GetURL("a.test", "/simple_page.html"));
2382
2383 EXPECT_TRUE(NavigateToURL(shell(), main_frame_url));
2384 RenderFrameHostImpl* root_rfh = static_cast<RenderFrameHostImpl*>(
2385 shell()->web_contents()->GetPrimaryMainFrame());
2386
2387 const GURL fenced_frame_url =
2388 https_server()->GetURL("a.test", "/fenced_frames/button.html");
2389 constexpr char kAddFencedFrameScript[] = R"({
2390 const fenced_frame = document.createElement('fencedframe');
2391 fenced_frame.config = new FencedFrameConfig($1);
2392 document.body.appendChild(fenced_frame);
2393 })";
2394 EXPECT_TRUE(
2395 ExecJs(root_rfh, JsReplace(kAddFencedFrameScript, fenced_frame_url)));
2396
2397 RenderFrameHostImpl* fenced_rfh = nullptr;
2398
2399 std::vector<FencedFrame*> fenced_frames = root_rfh->GetFencedFrames();
2400 EXPECT_EQ(fenced_frames.size(), 1u);
2401 FencedFrame* new_fenced_frame = fenced_frames.back();
2402 fenced_rfh = new_fenced_frame->GetInnerRoot();
2403
2404 root_rfh->DidFocusFrame();
2405 root_rfh->GetRenderWidgetHost()->ResetLostFocus();
2406
2407 // The fenced frame should not be allowed to focus because it won't have
2408 // user activation, and the RenderWidgetHost won't have recently lost focus.
2409 RenderProcessHostBadIpcMessageWaiter kill_waiter(fenced_rfh->GetProcess());
2410 fenced_rfh->DidFocusFrame();
2411 EXPECT_EQ(bad_message::RFH_FOCUS_ACROSS_FENCED_BOUNDARY, kill_waiter.Wait());
2412}
Rakina Zata Amnie3bcee242025-07-16 23:30:432413
2414namespace {
2415
2416// Interceptor that replaces the origin in the DidCommitProvisionalLoadParams
2417// with the specified value for the first DidCommitProvisionalLoad message it
2418// observes in the given |web_contents| while in scope.
2419class DidCommitParamsOriginReplacer : public DidCommitNavigationInterceptor {
2420 public:
2421 DidCommitParamsOriginReplacer(WebContents* web_contents,
2422 url::Origin origin_override)
2423 : DidCommitNavigationInterceptor(web_contents),
2424 origin_override_(std::move(origin_override)) {}
2425
2426 DidCommitParamsOriginReplacer(const DidCommitParamsOriginReplacer&) = delete;
2427 DidCommitParamsOriginReplacer& operator=(
2428 const DidCommitParamsOriginReplacer&) = delete;
2429
2430 ~DidCommitParamsOriginReplacer() override = default;
2431
2432 protected:
2433 bool WillProcessDidCommitNavigation(
2434 RenderFrameHost* render_frame_host,
2435 NavigationRequest* navigation_request,
2436 mojom::DidCommitProvisionalLoadParamsPtr* params,
2437 mojom::DidCommitProvisionalLoadInterfaceParamsPtr*) override {
2438 (*params)->origin = origin_override_;
2439
2440 return true;
2441 }
2442
2443 private:
2444 url::Origin origin_override_;
2445};
2446
2447} // namespace
2448
2449IN_PROC_BROWSER_TEST_F(SecurityExploitBrowserTest, MismatchedOriginToCommit) {
2450 // Navigate normally.
2451 GURL url(embedded_test_server()->GetURL("a.test", "/title1.html"));
2452 EXPECT_TRUE(NavigateToURL(shell(), url));
2453
2454 // Navigate again, but make it so that the DidCommitParams origin will
2455 // mismatch, causing a renderer kill.
2456 GURL url_2(embedded_test_server()->GetURL("a.test", "/title2.html"));
2457 NavigationHandleObserver navigation_observer(shell()->web_contents(), url_2);
2458 DidCommitParamsOriginReplacer replacer(shell()->web_contents(),
2459 url::Origin::Create(GURL("b.test")));
2460 RenderProcessHostBadIpcMessageWaiter kill_waiter(
2461 shell()->web_contents()->GetPrimaryMainFrame()->GetProcess());
2462 EXPECT_TRUE(NavigateToURLAndExpectNoCommit(shell(), url_2));
2463 EXPECT_EQ(bad_message::RFH_ORIGIN_TO_COMMIT_MISMATCH, kill_waiter.Wait());
2464}
2465
[email protected]04cbd3d2013-12-04 04:58:202466} // namespace content