blob: a7e85c84e083f978bbc5d8bebaeca538a12fa99d [file] [log] [blame]
[email protected]5cd56342013-04-03 19:50:471// Copyright (c) 2013 The Chromium Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
Avi Drissman8eed2d772022-01-07 21:58:235#include <tuple>
6
Sebastien Marchandf1349f52019-01-25 03:16:417#include "base/bind.h"
[email protected]5cd56342013-04-03 19:50:478#include "base/command_line.h"
Gabriel Charette1b7d93032020-03-05 20:09:139#include "base/memory/ptr_util.h"
Keishi Hattori0e45c022021-11-27 09:25:5210#include "base/memory/raw_ptr.h"
Gabriel Charetteb164afef2017-11-21 20:59:3111#include "base/run_loop.h"
[email protected]135cb802013-06-09 16:44:2012#include "base/strings/utf_string_conversions.h"
Devlin Cronin626d80c2018-06-01 01:08:3613#include "base/test/metrics/histogram_tester.h"
Ari Chivukulaa29eb3a2021-07-21 02:57:4814#include "base/unguessable_token.h"
Trent Aptedc9983212021-06-29 02:47:5815#include "build/build_config.h"
calamityae7fed42017-06-22 04:58:2216#include "chrome/browser/extensions/extension_browsertest.h"
[email protected]5cd56342013-04-03 19:50:4717#include "chrome/browser/ui/browser.h"
18#include "chrome/browser/ui/browser_commands.h"
19#include "chrome/browser/ui/singleton_tabs.h"
20#include "chrome/browser/ui/tabs/tab_strip_model.h"
[email protected]5cd56342013-04-03 19:50:4721#include "chrome/test/base/ui_test_utils.h"
Marijn Kruisselbrink604fd7e72017-10-26 16:31:0522#include "content/public/browser/blob_handle.h"
[email protected]5cd56342013-04-03 19:50:4723#include "content/public/browser/notification_observer.h"
24#include "content/public/browser/notification_service.h"
25#include "content/public/browser/notification_types.h"
nick2a8ba8c2016-10-03 18:51:3926#include "content/public/browser/render_frame_host.h"
27#include "content/public/browser/render_process_host.h"
Nasko Oskovd515cab2018-05-09 15:34:2028#include "content/public/browser/site_isolation_policy.h"
[email protected]5cd56342013-04-03 19:50:4729#include "content/public/browser/web_contents_observer.h"
30#include "content/public/common/content_switches.h"
Nasko Oskovd83b5712018-05-04 04:50:5731#include "content/public/common/url_constants.h"
Peter Kasting919ce652020-05-07 10:22:3632#include "content/public/test/browser_test.h"
[email protected]5cd56342013-04-03 19:50:4733#include "content/public/test/browser_test_utils.h"
Charlie Reise2c2c492018-06-15 21:34:0434#include "extensions/common/extension_urls.h"
Julie Jeongeun Kim061709e82019-09-04 02:33:4335#include "mojo/public/cpp/bindings/pending_remote.h"
Julie Jeongeun Kimb8c0b1c2019-10-30 01:13:0636#include "mojo/public/cpp/bindings/self_owned_associated_receiver.h"
nick2a8ba8c2016-10-03 18:51:3937#include "net/dns/mock_host_resolver.h"
svaldeza01f7d92015-11-18 17:47:5638#include "net/test/embedded_test_server/embedded_test_server.h"
Marijn Kruisselbrink8f1b1a72018-01-26 18:09:5639#include "storage/browser/blob/blob_registry_impl.h"
Kinuko Yasuda04a82ab2019-07-26 06:13:3940#include "third_party/blink/public/common/blob/blob_utils.h"
Marijn Kruisselbrinkcde64632018-06-22 22:45:1641#include "third_party/blink/public/common/features.h"
kyraseevers235fd482021-10-25 17:33:2642#include "third_party/blink/public/common/storage_key/storage_key.h"
Takuto Ikuta8cfb4892019-01-24 01:04:0543#include "third_party/blink/public/mojom/blob/blob_url_store.mojom-test-utils.h"
Blink Reformata30d4232018-04-07 15:31:0644#include "third_party/blink/public/mojom/blob/blob_url_store.mojom.h"
kyraseevers235fd482021-10-25 17:33:2645#include "url/gurl.h"
46#include "url/origin.h"
[email protected]5cd56342013-04-03 19:50:4747
48// The goal of these tests is to "simulate" exploited renderer processes, which
49// can send arbitrary IPC messages and confuse browser process internal state,
50// leading to security bugs. We are trying to verify that the browser doesn't
51// perform any dangerous operations in such cases.
52// This is similar to the security_exploit_browsertest.cc tests, but also
53// includes chrome/ layer concepts such as extensions.
Devlin Cronin836f545d2018-05-09 00:25:0554class ChromeSecurityExploitBrowserTest
55 : public extensions::ExtensionBrowserTest {
[email protected]5cd56342013-04-03 19:50:4756 public:
57 ChromeSecurityExploitBrowserTest() {}
Peter Boström53c6c5952021-09-17 09:41:2658
59 ChromeSecurityExploitBrowserTest(const ChromeSecurityExploitBrowserTest&) =
60 delete;
61 ChromeSecurityExploitBrowserTest& operator=(
62 const ChromeSecurityExploitBrowserTest&) = delete;
63
dchenge1bc7982014-10-30 00:32:4064 ~ChromeSecurityExploitBrowserTest() override {}
[email protected]5cd56342013-04-03 19:50:4765
nick2a8ba8c2016-10-03 18:51:3966 void SetUpOnMainThread() override {
Devlin Cronin836f545d2018-05-09 00:25:0567 extensions::ExtensionBrowserTest::SetUpOnMainThread();
calamityae7fed42017-06-22 04:58:2268
tsergeantbd3b7a4c2016-09-30 00:42:1969 ASSERT_TRUE(embedded_test_server()->Start());
nick2a8ba8c2016-10-03 18:51:3970 host_resolver()->AddRule("*", "127.0.0.1");
calamityae7fed42017-06-22 04:58:2271
72 extension_ = LoadExtension(test_data_dir_.AppendASCII("simple_with_icon"));
nick2a8ba8c2016-10-03 18:51:3973 }
tsergeantbd3b7a4c2016-09-30 00:42:1974
calamityae7fed42017-06-22 04:58:2275 const extensions::Extension* extension() { return extension_; }
76
Marijn Kruisselbrink604fd7e72017-10-26 16:31:0577 std::unique_ptr<content::BlobHandle> CreateMemoryBackedBlob(
78 const std::string& contents,
79 const std::string& content_type) {
80 std::unique_ptr<content::BlobHandle> result;
81 base::RunLoop loop;
Lukasz Anforowicz7ef1cfd2021-05-04 02:18:3782 profile()->CreateMemoryBackedBlob(
83 base::as_bytes(base::make_span(contents)), content_type,
Marijn Kruisselbrink604fd7e72017-10-26 16:31:0584 base::BindOnce(
85 [](std::unique_ptr<content::BlobHandle>* out_blob,
86 base::OnceClosure done,
87 std::unique_ptr<content::BlobHandle> blob) {
88 *out_blob = std::move(blob);
89 std::move(done).Run();
90 },
91 &result, loop.QuitClosure()));
92 loop.Run();
93 EXPECT_TRUE(result);
94 return result;
95 }
96
[email protected]5cd56342013-04-03 19:50:4797 private:
Keishi Hattori0e45c022021-11-27 09:25:5298 raw_ptr<const extensions::Extension> extension_;
[email protected]5cd56342013-04-03 19:50:4799};
100
Charlie Reise2c2c492018-06-15 21:34:04101// Subclass of ChromeSecurityExploitBrowserTest that uses --disable-web-security
102// to simulate an exploited renderer. Note that this also disables some browser
103// process checks, so it's not ideal for all exploit tests.
104class ChromeWebSecurityDisabledBrowserTest
105 : public ChromeSecurityExploitBrowserTest {
106 public:
107 ChromeWebSecurityDisabledBrowserTest() {}
Peter Boström53c6c5952021-09-17 09:41:26108
109 ChromeWebSecurityDisabledBrowserTest(
110 const ChromeWebSecurityDisabledBrowserTest&) = delete;
111 ChromeWebSecurityDisabledBrowserTest& operator=(
112 const ChromeWebSecurityDisabledBrowserTest&) = delete;
113
Charlie Reise2c2c492018-06-15 21:34:04114 ~ChromeWebSecurityDisabledBrowserTest() override {}
115
116 void SetUpCommandLine(base::CommandLine* command_line) override {
117 ChromeSecurityExploitBrowserTest::SetUpCommandLine(command_line);
118 command_line->AppendSwitch(switches::kDisableWebSecurity);
119 }
Charlie Reise2c2c492018-06-15 21:34:04120};
121
Nasko Oskovfd52b6f2019-02-06 19:21:15122// TODO(nasko): This test as written is incompatible with Site Isolation
123// restrictions, which disallow the cross-origin pushState call.
124// Find a different way to implement issuing the illegal request or just
125// delete the test if we have coverage elsewhere. See https://crbug.com/929161.
Charlie Reise2c2c492018-06-15 21:34:04126IN_PROC_BROWSER_TEST_F(ChromeWebSecurityDisabledBrowserTest,
Nasko Oskovfd52b6f2019-02-06 19:21:15127 DISABLED_ChromeExtensionResources) {
[email protected]5cd56342013-04-03 19:50:47128 // Load a page that requests a chrome-extension:// image through XHR. We
129 // expect this load to fail, as it is an illegal request.
nick2a8ba8c2016-10-03 18:51:39130 GURL foo = embedded_test_server()->GetURL("foo.com",
131 "/chrome_extension_resource.html");
[email protected]5cd56342013-04-03 19:50:47132
133 content::DOMMessageQueue msg_queue;
134
Lukasz Anforowiczb78290c2021-09-08 04:31:38135 ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), foo));
[email protected]5cd56342013-04-03 19:50:47136
137 std::string status;
138 std::string expected_status("0");
139 EXPECT_TRUE(msg_queue.WaitForMessage(&status));
140 EXPECT_STREQ(status.c_str(), expected_status.c_str());
141}
nick2a8ba8c2016-10-03 18:51:39142
Charlie Reise2c2c492018-06-15 21:34:04143// Tests that a normal web process cannot send a commit for a Chrome Web Store
144// URL. See https://crbug.com/172119.
145IN_PROC_BROWSER_TEST_F(ChromeSecurityExploitBrowserTest,
146 CommitWebStoreURLInWebProcess) {
147 GURL foo = embedded_test_server()->GetURL("foo.com", "/title1.html");
148
149 content::WebContents* web_contents =
150 browser()->tab_strip_model()->GetActiveWebContents();
151 content::RenderFrameHost* rfh = web_contents->GetMainFrame();
152
153 // This IPC should result in a kill because the Chrome Web Store is not
154 // allowed to commit in |rfh->GetProcess()|.
155 base::HistogramTester histograms;
156 content::RenderProcessHostWatcher crash_observer(
157 rfh->GetProcess(),
158 content::RenderProcessHostWatcher::WATCH_FOR_PROCESS_EXIT);
159
160 // Modify an IPC for a commit of a blank URL, which would otherwise be allowed
161 // to commit in any process.
162 GURL blank_url = GURL(url::kAboutBlankURL);
163 GURL webstore_url = extension_urls::GetWebstoreLaunchURL();
164 content::PwnCommitIPC(web_contents, blank_url, webstore_url,
165 url::Origin::Create(GURL(webstore_url)));
166 web_contents->GetController().LoadURL(
167 blank_url, content::Referrer(), ui::PAGE_TRANSITION_LINK, std::string());
168
169 // If the process is killed in CanCommitURL, this test passes.
170 crash_observer.Wait();
171 histograms.ExpectUniqueSample("Stability.BadMessageTerminated.Content", 1, 1);
172}
173
174// Tests that a non-extension process cannot send a commit of a blank URL with
175// an extension origin.
176IN_PROC_BROWSER_TEST_F(ChromeSecurityExploitBrowserTest,
177 CommitExtensionOriginInWebProcess) {
178 GURL foo = embedded_test_server()->GetURL("foo.com", "/title1.html");
179
180 content::WebContents* web_contents =
181 browser()->tab_strip_model()->GetActiveWebContents();
182 content::RenderFrameHost* rfh = web_contents->GetMainFrame();
183
184 // This IPC should result in a kill because |ext_origin| is not allowed to
185 // commit in |rfh->GetProcess()|.
186 base::HistogramTester histograms;
187 content::RenderProcessHostWatcher crash_observer(
188 rfh->GetProcess(),
189 content::RenderProcessHostWatcher::WATCH_FOR_PROCESS_EXIT);
190
191 // Modify an IPC for a commit of a blank URL, which would otherwise be allowed
192 // to commit in any process.
193 GURL blank_url = GURL(url::kAboutBlankURL);
194 std::string ext_origin = "chrome-extension://" + extension()->id();
195 content::PwnCommitIPC(web_contents, blank_url, blank_url,
196 url::Origin::Create(GURL(ext_origin)));
197 web_contents->GetController().LoadURL(
198 blank_url, content::Referrer(), ui::PAGE_TRANSITION_LINK, std::string());
199
200 // If the process is killed in CanCommitOrigin, this test passes.
201 crash_observer.Wait();
202 histograms.ExpectUniqueSample("Stability.BadMessageTerminated.Content", 114,
203 1);
204}
205
206// Tests that a non-extension process cannot send a commit of an extension URL.
207IN_PROC_BROWSER_TEST_F(ChromeSecurityExploitBrowserTest,
208 CommitExtensionURLInWebProcess) {
209 GURL foo = embedded_test_server()->GetURL("foo.com", "/title1.html");
210
211 content::WebContents* web_contents =
212 browser()->tab_strip_model()->GetActiveWebContents();
213 content::RenderFrameHost* rfh = web_contents->GetMainFrame();
214
215 // This IPC should result in a kill because extension URLs are not allowed to
216 // commit in |rfh->GetProcess()|.
217 base::HistogramTester histograms;
218 content::RenderProcessHostWatcher crash_observer(
219 rfh->GetProcess(),
220 content::RenderProcessHostWatcher::WATCH_FOR_PROCESS_EXIT);
221
222 // Modify an IPC for a commit of a blank URL, which would otherwise be allowed
223 // to commit in any process.
224 GURL blank_url = GURL(url::kAboutBlankURL);
225 std::string ext_origin = "chrome-extension://" + extension()->id();
226 content::PwnCommitIPC(web_contents, blank_url, GURL(ext_origin),
227 url::Origin::Create(GURL(ext_origin)));
228 web_contents->GetController().LoadURL(
229 blank_url, content::Referrer(), ui::PAGE_TRANSITION_LINK, std::string());
230
231 // If the process is killed in CanCommitURL, this test passes.
232 crash_observer.Wait();
233 histograms.ExpectUniqueSample("Stability.BadMessageTerminated.Content", 1, 1);
234}
235
236// Tests that a non-extension process cannot send a commit of an extension
237// filesystem URL.
238IN_PROC_BROWSER_TEST_F(ChromeSecurityExploitBrowserTest,
239 CommitExtensionFilesystemURLInWebProcess) {
240 GURL foo = embedded_test_server()->GetURL("foo.com", "/title1.html");
241
242 content::WebContents* web_contents =
243 browser()->tab_strip_model()->GetActiveWebContents();
244 content::RenderFrameHost* rfh = web_contents->GetMainFrame();
245
246 // This IPC should result in a kill because extension filesystem URLs are not
247 // allowed to commit in |rfh->GetProcess()|.
248 base::HistogramTester histograms;
249 content::RenderProcessHostWatcher crash_observer(
250 rfh->GetProcess(),
251 content::RenderProcessHostWatcher::WATCH_FOR_PROCESS_EXIT);
252
253 // Modify an IPC for a commit of a blank URL, which would otherwise be allowed
254 // to commit in any process.
255 GURL blank_url = GURL(url::kAboutBlankURL);
256 std::string ext_origin = "chrome-extension://" + extension()->id();
257 content::PwnCommitIPC(web_contents, blank_url,
258 GURL("filesystem:" + ext_origin + "/foo"),
259 url::Origin::Create(GURL(ext_origin)));
260 web_contents->GetController().LoadURL(
261 blank_url, content::Referrer(), ui::PAGE_TRANSITION_LINK, std::string());
262
263 // If the process is killed in CanCommitURL, this test passes.
264 crash_observer.Wait();
265 histograms.ExpectUniqueSample("Stability.BadMessageTerminated.Content", 1, 1);
266}
267
Daniel Cheng4ebba552018-07-06 21:43:16268// chrome://xyz should not be able to create a "filesystem:chrome://abc"
269// resource.
270IN_PROC_BROWSER_TEST_F(ChromeSecurityExploitBrowserTest,
271 CreateFilesystemURLInOtherChromeUIOrigin) {
Lukasz Anforowiczb78290c2021-09-08 04:31:38272 ASSERT_TRUE(
273 ui_test_utils::NavigateToURL(browser(), GURL("chrome://version")));
Daniel Cheng4ebba552018-07-06 21:43:16274
275 content::RenderFrameHost* rfh =
276 browser()->tab_strip_model()->GetActiveWebContents()->GetMainFrame();
277
278 // Block the renderer on operation that never completes, to shield it from
279 // receiving unexpected browser->renderer IPCs that might CHECK.
280 rfh->ExecuteJavaScriptWithUserGestureForTests(
Peter Kastingaae6db932021-05-04 12:02:11281 u"var r = new XMLHttpRequest();"
282 u"r.open('GET', '/slow?99999', false);"
283 u"r.send(null);"
284 u"while (1);");
Daniel Cheng4ebba552018-07-06 21:43:16285
286 std::string payload = "<p>Hello world!</p>";
287 std::string payload_type = "text/html";
288
289 // Target an extension.
290 std::string target_origin = "chrome://downloads";
291
292 // Set up a blob ID and populate it with the attacker-controlled payload. This
293 // is just using the blob APIs directly since creating arbitrary blobs is not
294 // what is prohibited; this data is not in any origin.
295 std::unique_ptr<content::BlobHandle> blob =
296 CreateMemoryBackedBlob(payload, payload_type);
297 std::string blob_id = blob->GetUUID();
298
299 // Note: a well-behaved renderer would always send the following message here,
300 // but it's actually not necessary for the original attack to succeed, so we
301 // omit it. As a result there are some log warnings from the quota observer.
302 //
303 // IPC::IpcSecurityTestUtil::PwnMessageReceived(
304 // rfh->GetProcess()->GetChannel(),
305 // FileSystemHostMsg_OpenFileSystem(22, GURL(target_origin),
306 // storage::kFileSystemTypeTemporary));
307
308 GURL target_url =
309 GURL("filesystem:" + target_origin + "/temporary/exploit.html");
310
kyraseevers235fd482021-10-25 17:33:26311 content::PwnMessageHelper::FileSystemCreate(
312 rfh->GetProcess(), 23, target_url, false, false, false,
313 blink::StorageKey(url::Origin::Create(target_url)));
Daniel Cheng4ebba552018-07-06 21:43:16314
315 // Write the blob into the file. If successful, this places an
316 // attacker-controlled value in a resource on the extension origin.
kyraseevers235fd482021-10-25 17:33:26317 content::PwnMessageHelper::FileSystemWrite(
318 rfh->GetProcess(), 24, target_url, blob_id, 0,
319 blink::StorageKey(url::Origin::Create(target_url)));
Daniel Cheng4ebba552018-07-06 21:43:16320
321 // Now navigate to |target_url| in a new tab. It should not contain |payload|.
Fergal Dalye7ac9942022-01-18 23:22:16322 ASSERT_FALSE(AddTabAtIndex(0, target_url, ui::PAGE_TRANSITION_TYPED));
Synthia Islam2761aac2020-02-25 18:48:32323 EXPECT_FALSE(content::WaitForLoadStop(
324 browser()->tab_strip_model()->GetWebContentsAt(0)));
Daniel Cheng4ebba552018-07-06 21:43:16325 rfh = browser()->tab_strip_model()->GetActiveWebContents()->GetMainFrame();
326
327 // If the attack is unsuccessful, the navigation ends up in an error
328 // page.
329 if (content::SiteIsolationPolicy::IsErrorPageIsolationEnabled(
330 !rfh->GetParent())) {
331 EXPECT_EQ(GURL(content::kUnreachableWebDataURL),
332 rfh->GetSiteInstance()->GetSiteURL());
333 } else {
334 EXPECT_EQ(GURL(target_origin), rfh->GetSiteInstance()->GetSiteURL());
335 }
336 std::string body;
337 std::string script = R"(
338 var textContent = document.body.innerText.replace(/\n+/g, '\n');
339 window.domAutomationController.send(textContent);
340 )";
341
342 EXPECT_TRUE(content::ExecuteScriptAndExtractString(rfh, script, &body));
343 EXPECT_EQ(
Lily Chen09b5ce42020-07-16 06:28:41344 "Your file couldn’t be accessed\n"
345 "It may have been moved, edited, or deleted.\n"
Yoshifumi Inoue72d438e2018-08-24 08:17:29346 "ERR_FILE_NOT_FOUND",
Daniel Cheng4ebba552018-07-06 21:43:16347 body);
348}
349
nickbfaea4ee2016-12-02 20:59:31350// Extension isolation prevents a normal renderer process from being able to
nickbfaea4ee2016-12-02 20:59:31351// create a "filesystem:chrome-extension://sdgkjaghsdg/temporary/" resource.
352IN_PROC_BROWSER_TEST_F(ChromeSecurityExploitBrowserTest,
353 CreateFilesystemURLInExtensionOrigin) {
354 GURL page_url =
355 embedded_test_server()->GetURL("a.root-servers.net", "/title1.html");
Lukasz Anforowiczb78290c2021-09-08 04:31:38356 ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), page_url));
nickbfaea4ee2016-12-02 20:59:31357
358 content::RenderFrameHost* rfh =
359 browser()->tab_strip_model()->GetActiveWebContents()->GetMainFrame();
360
361 // Block the renderer on operation that never completes, to shield it from
362 // receiving unexpected browser->renderer IPCs that might CHECK.
363 rfh->ExecuteJavaScriptWithUserGestureForTests(
Peter Kastingaae6db932021-05-04 12:02:11364 u"var r = new XMLHttpRequest();"
365 u"r.open('GET', '/slow?99999', false);"
366 u"r.send(null);"
367 u"while (1);");
nickbfaea4ee2016-12-02 20:59:31368
369 // JS code that the attacker would like to run in an extension process.
370 std::string payload = "<html><body>pwned.</body></html>";
371 std::string payload_type = "text/html";
372
calamityae7fed42017-06-22 04:58:22373 // Target an extension.
374 std::string target_origin = "chrome-extension://" + extension()->id();
nickbfaea4ee2016-12-02 20:59:31375
Marijn Kruisselbrink604fd7e72017-10-26 16:31:05376 // Set up a blob ID and populate it with the attacker-controlled payload. This
377 // is just using the blob APIs directly since creating arbitrary blobs is not
378 // what is prohibited; this data is not in any origin.
379 std::unique_ptr<content::BlobHandle> blob =
380 CreateMemoryBackedBlob(payload, payload_type);
381 std::string blob_id = blob->GetUUID();
nickbfaea4ee2016-12-02 20:59:31382
Adithya Srinivasan0c72ff02018-08-13 19:47:29383 // Note: a well-behaved renderer would always call Open first before calling
384 // Create and Write, but it's actually not necessary for the original attack
385 // to succeed, so we omit it. As a result there are some log warnings from the
386 // quota observer.
nickbfaea4ee2016-12-02 20:59:31387
388 GURL target_url =
calamityae7fed42017-06-22 04:58:22389 GURL("filesystem:" + target_origin + "/temporary/exploit.html");
nickbfaea4ee2016-12-02 20:59:31390
kyraseevers235fd482021-10-25 17:33:26391 content::PwnMessageHelper::FileSystemCreate(
392 rfh->GetProcess(), 23, target_url, false, false, false,
393 blink::StorageKey(url::Origin::Create(target_url)));
nickbfaea4ee2016-12-02 20:59:31394
395 // Write the blob into the file. If successful, this places an
396 // attacker-controlled value in a resource on the extension origin.
kyraseevers235fd482021-10-25 17:33:26397 content::PwnMessageHelper::FileSystemWrite(
398 rfh->GetProcess(), 24, target_url, blob_id, 0,
399 blink::StorageKey(url::Origin::Create(target_url)));
nickbfaea4ee2016-12-02 20:59:31400
401 // Now navigate to |target_url| in a new tab. It should not contain |payload|.
Fergal Dalye7ac9942022-01-18 23:22:16402 ASSERT_FALSE(AddTabAtIndex(0, target_url, ui::PAGE_TRANSITION_TYPED));
Synthia Islam2761aac2020-02-25 18:48:32403 EXPECT_FALSE(content::WaitForLoadStop(
404 browser()->tab_strip_model()->GetWebContentsAt(0)));
nickbfaea4ee2016-12-02 20:59:31405 rfh = browser()->tab_strip_model()->GetActiveWebContents()->GetMainFrame();
Nasko Oskovd83b5712018-05-04 04:50:57406
407 // If the attack is unsuccessful, the navigation ends up in an error
408 // page.
Nasko Oskovd515cab2018-05-09 15:34:20409 if (content::SiteIsolationPolicy::IsErrorPageIsolationEnabled(
410 !rfh->GetParent())) {
411 EXPECT_EQ(GURL(content::kUnreachableWebDataURL),
412 rfh->GetSiteInstance()->GetSiteURL());
413 } else {
414 EXPECT_EQ(GURL(target_origin), rfh->GetSiteInstance()->GetSiteURL());
415 }
nickbfaea4ee2016-12-02 20:59:31416 std::string body;
ntfschrd9f4dd52017-05-02 16:21:27417 std::string script = R"(
418 var textContent = document.body.innerText.replace(/\n+/g, '\n');
419 window.domAutomationController.send(textContent);
420 )";
421
422 EXPECT_TRUE(content::ExecuteScriptAndExtractString(rfh, script, &body));
naskoabed2a52017-05-03 05:10:17423 EXPECT_EQ(
Lily Chen09b5ce42020-07-16 06:28:41424 "Your file couldn’t be accessed\n"
425 "It may have been moved, edited, or deleted.\n"
Yoshifumi Inoue72d438e2018-08-24 08:17:29426 "ERR_FILE_NOT_FOUND",
naskoabed2a52017-05-03 05:10:17427 body);
nick2a8ba8c2016-10-03 18:51:39428}
Marijn Kruisselbrink8f1b1a72018-01-26 18:09:56429
430namespace {
431
432class BlobURLStoreInterceptor
433 : public blink::mojom::BlobURLStoreInterceptorForTesting {
434 public:
Lucas Furukawa Gadaniebc8f7fc32019-06-18 18:06:48435 static void Intercept(
436 GURL target_url,
Julie Jeongeun Kimb8c0b1c2019-10-30 01:13:06437 mojo::SelfOwnedAssociatedReceiverRef<blink::mojom::BlobURLStore>
438 receiver) {
Lucas Furukawa Gadaniebc8f7fc32019-06-18 18:06:48439 auto interceptor =
440 base::WrapUnique(new BlobURLStoreInterceptor(target_url));
441 auto* raw_interceptor = interceptor.get();
Julie Jeongeun Kimb8c0b1c2019-10-30 01:13:06442 auto impl = receiver->SwapImplForTesting(std::move(interceptor));
Lucas Furukawa Gadaniebc8f7fc32019-06-18 18:06:48443 raw_interceptor->url_store_ = std::move(impl);
Marijn Kruisselbrink8f1b1a72018-01-26 18:09:56444 }
445
446 blink::mojom::BlobURLStore* GetForwardingInterface() override {
Lucas Furukawa Gadaniebc8f7fc32019-06-18 18:06:48447 return url_store_.get();
Marijn Kruisselbrink8f1b1a72018-01-26 18:09:56448 }
449
Ari Chivukulaa29eb3a2021-07-21 02:57:48450 void Register(
451 mojo::PendingRemote<blink::mojom::Blob> blob,
452 const GURL& url,
453 // TODO(https://crbug.com/1224926): Remove this once experiment is over.
454 const base::UnguessableToken& unsafe_agent_cluster_id,
455 RegisterCallback callback) override {
Marijn Kruisselbrink8f1b1a72018-01-26 18:09:56456 GetForwardingInterface()->Register(std::move(blob), target_url_,
Ari Chivukulaa29eb3a2021-07-21 02:57:48457 unsafe_agent_cluster_id,
Marijn Kruisselbrink8f1b1a72018-01-26 18:09:56458 std::move(callback));
459 }
460
461 private:
Lucas Furukawa Gadaniebc8f7fc32019-06-18 18:06:48462 explicit BlobURLStoreInterceptor(GURL target_url) : target_url_(target_url) {}
463
464 std::unique_ptr<blink::mojom::BlobURLStore> url_store_;
Marijn Kruisselbrink8f1b1a72018-01-26 18:09:56465 GURL target_url_;
466};
467
468} // namespace
469
470class ChromeSecurityExploitBrowserTestMojoBlobURLs
471 : public ChromeSecurityExploitBrowserTest {
472 public:
Kinuko Yasuda7d925ea22019-08-01 10:08:48473 ChromeSecurityExploitBrowserTestMojoBlobURLs() = default;
Marijn Kruisselbrink8f1b1a72018-01-26 18:09:56474
475 void TearDown() override {
476 storage::BlobRegistryImpl::SetURLStoreCreationHookForTesting(nullptr);
477 }
478};
479
480// Extension isolation prevents a normal renderer process from being able to
481// create a "blob:chrome-extension://" resource.
482IN_PROC_BROWSER_TEST_F(ChromeSecurityExploitBrowserTestMojoBlobURLs,
483 CreateBlobInExtensionOrigin) {
484 // Target an extension.
485 std::string target_origin = "chrome-extension://" + extension()->id();
486 std::string blob_path = "5881f76e-10d2-410d-8c61-ef210502acfd";
Lucas Furukawa Gadaniebc8f7fc32019-06-18 18:06:48487 auto intercept_hook =
488 base::BindRepeating(&BlobURLStoreInterceptor::Intercept,
489 GURL("blob:" + target_origin + "/" + blob_path));
Marijn Kruisselbrink8f1b1a72018-01-26 18:09:56490 storage::BlobRegistryImpl::SetURLStoreCreationHookForTesting(&intercept_hook);
491
Lukasz Anforowiczb78290c2021-09-08 04:31:38492 ASSERT_TRUE(ui_test_utils::NavigateToURL(
Marijn Kruisselbrink8f1b1a72018-01-26 18:09:56493 browser(),
Lukasz Anforowiczb78290c2021-09-08 04:31:38494 embedded_test_server()->GetURL("a.root-servers.net", "/title1.html")));
Marijn Kruisselbrink8f1b1a72018-01-26 18:09:56495
496 content::RenderFrameHost* rfh =
497 browser()->tab_strip_model()->GetActiveWebContents()->GetMainFrame();
498
Lukasz Anforowicz110cda32020-03-23 22:32:03499 content::RenderProcessHostBadMojoMessageWaiter crash_observer(
500 rfh->GetProcess());
Marijn Kruisselbrink8f1b1a72018-01-26 18:09:56501
502 // The renderer should always get killed, but sometimes ExecuteScript returns
503 // true anyway, so just ignore the result.
Avi Drissman8eed2d772022-01-07 21:58:23504 std::ignore =
505 content::ExecuteScript(rfh, "URL.createObjectURL(new Blob(['foo']))");
Marijn Kruisselbrink8f1b1a72018-01-26 18:09:56506
507 // If the process is killed, this test passes.
Lukasz Anforowicz110cda32020-03-23 22:32:03508 EXPECT_EQ(
Marijn Kruisselbrink15cfa2b2021-11-09 21:39:27509 "Received bad user message: "
510 "URL with invalid origin passed to BlobURLStore::Register",
Lukasz Anforowicz110cda32020-03-23 22:32:03511 crash_observer.Wait());
Marijn Kruisselbrink8f1b1a72018-01-26 18:09:56512}
Kinuko Yasuda04a82ab2019-07-26 06:13:39513
Trent Aptedc9983212021-06-29 02:47:58514// Flaky. See https://crbug.com/1224293.
Xiaohan Wang55ae2c012022-01-20 21:49:11515#if BUILDFLAG(IS_CHROMEOS)
Trent Aptedc9983212021-06-29 02:47:58516#define MAYBE_CreateBlobInOtherChromeUIOrigin \
517 DISABLED_CreateBlobInOtherChromeUIOrigin
518#else
519#define MAYBE_CreateBlobInOtherChromeUIOrigin CreateBlobInOtherChromeUIOrigin
Xiaohan Wang55ae2c012022-01-20 21:49:11520#endif // BUILDFLAG(IS_CHROMEOS)
Kinuko Yasuda04a82ab2019-07-26 06:13:39521// chrome://xyz should not be able to create a "blob:chrome://abc" resource.
522IN_PROC_BROWSER_TEST_F(ChromeSecurityExploitBrowserTestMojoBlobURLs,
Trent Aptedc9983212021-06-29 02:47:58523 MAYBE_CreateBlobInOtherChromeUIOrigin) {
Lukasz Anforowiczb78290c2021-09-08 04:31:38524 ASSERT_TRUE(
525 ui_test_utils::NavigateToURL(browser(), GURL("chrome://version")));
Kinuko Yasuda04a82ab2019-07-26 06:13:39526
527 // All these are attacker controlled values.
528 std::string blob_type = "text/html";
529 std::string blob_contents = "<p>Hello world!</p>";
530 std::string blob_path = "f7dfbeb5-8e41-4c4a-8486-a52fed33c4c0";
531
532 // Target an extension.
533 std::string target_origin = "chrome://downloads";
534
535 auto intercept_hook =
536 base::BindRepeating(&BlobURLStoreInterceptor::Intercept,
537 GURL("blob:" + target_origin + "/" + blob_path));
538 storage::BlobRegistryImpl::SetURLStoreCreationHookForTesting(&intercept_hook);
539
540 content::RenderFrameHost* rfh =
541 browser()->tab_strip_model()->GetActiveWebContents()->GetMainFrame();
542
Lukasz Anforowicz110cda32020-03-23 22:32:03543 content::RenderProcessHostBadMojoMessageWaiter crash_observer(
544 rfh->GetProcess());
Kinuko Yasuda04a82ab2019-07-26 06:13:39545
546 // The renderer should always get killed, but sometimes ExecuteScript returns
547 // true anyway, so just ignore the result.
Avi Drissman8eed2d772022-01-07 21:58:23548 std::ignore =
549 content::ExecuteScript(rfh, "URL.createObjectURL(new Blob(['foo']))");
Kinuko Yasuda04a82ab2019-07-26 06:13:39550
551 // If the process is killed, this test passes.
Lukasz Anforowicz110cda32020-03-23 22:32:03552 EXPECT_EQ(
Marijn Kruisselbrink15cfa2b2021-11-09 21:39:27553 "Received bad user message: "
554 "URL with invalid origin passed to BlobURLStore::Register",
Lukasz Anforowicz110cda32020-03-23 22:32:03555 crash_observer.Wait());
Kinuko Yasuda04a82ab2019-07-26 06:13:39556}