blob: 5388f845af5988be4227a3029a239230a8a57851 [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
Sebastien Marchandf1349f52019-01-25 03:16:415#include "base/bind.h"
[email protected]5cd56342013-04-03 19:50:476#include "base/command_line.h"
avie4d7b6f2015-12-26 00:59:187#include "base/macros.h"
Gabriel Charette1b7d93032020-03-05 20:09:138#include "base/memory/ptr_util.h"
Gabriel Charetteb164afef2017-11-21 20:59:319#include "base/run_loop.h"
[email protected]135cb802013-06-09 16:44:2010#include "base/strings/utf_string_conversions.h"
Devlin Cronin626d80c2018-06-01 01:08:3611#include "base/test/metrics/histogram_tester.h"
Ari Chivukulaa29eb3a2021-07-21 02:57:4812#include "base/unguessable_token.h"
Trent Aptedc9983212021-06-29 02:47:5813#include "build/build_config.h"
calamityae7fed42017-06-22 04:58:2214#include "chrome/browser/extensions/extension_browsertest.h"
[email protected]5cd56342013-04-03 19:50:4715#include "chrome/browser/ui/browser.h"
16#include "chrome/browser/ui/browser_commands.h"
17#include "chrome/browser/ui/singleton_tabs.h"
18#include "chrome/browser/ui/tabs/tab_strip_model.h"
[email protected]5cd56342013-04-03 19:50:4719#include "chrome/test/base/ui_test_utils.h"
Marijn Kruisselbrink604fd7e72017-10-26 16:31:0520#include "content/public/browser/blob_handle.h"
[email protected]5cd56342013-04-03 19:50:4721#include "content/public/browser/notification_observer.h"
22#include "content/public/browser/notification_service.h"
23#include "content/public/browser/notification_types.h"
nick2a8ba8c2016-10-03 18:51:3924#include "content/public/browser/render_frame_host.h"
25#include "content/public/browser/render_process_host.h"
Nasko Oskovd515cab2018-05-09 15:34:2026#include "content/public/browser/site_isolation_policy.h"
[email protected]5cd56342013-04-03 19:50:4727#include "content/public/browser/web_contents_observer.h"
28#include "content/public/common/content_switches.h"
Nasko Oskovd83b5712018-05-04 04:50:5729#include "content/public/common/url_constants.h"
Peter Kasting919ce652020-05-07 10:22:3630#include "content/public/test/browser_test.h"
[email protected]5cd56342013-04-03 19:50:4731#include "content/public/test/browser_test_utils.h"
Charlie Reise2c2c492018-06-15 21:34:0432#include "extensions/common/extension_urls.h"
Julie Jeongeun Kim061709e82019-09-04 02:33:4333#include "mojo/public/cpp/bindings/pending_remote.h"
Julie Jeongeun Kimb8c0b1c2019-10-30 01:13:0634#include "mojo/public/cpp/bindings/self_owned_associated_receiver.h"
nick2a8ba8c2016-10-03 18:51:3935#include "net/dns/mock_host_resolver.h"
svaldeza01f7d92015-11-18 17:47:5636#include "net/test/embedded_test_server/embedded_test_server.h"
Marijn Kruisselbrink8f1b1a72018-01-26 18:09:5637#include "storage/browser/blob/blob_registry_impl.h"
Kinuko Yasuda04a82ab2019-07-26 06:13:3938#include "third_party/blink/public/common/blob/blob_utils.h"
Marijn Kruisselbrinkcde64632018-06-22 22:45:1639#include "third_party/blink/public/common/features.h"
Takuto Ikuta8cfb4892019-01-24 01:04:0540#include "third_party/blink/public/mojom/blob/blob_url_store.mojom-test-utils.h"
Blink Reformata30d4232018-04-07 15:31:0641#include "third_party/blink/public/mojom/blob/blob_url_store.mojom.h"
[email protected]5cd56342013-04-03 19:50:4742
43// The goal of these tests is to "simulate" exploited renderer processes, which
44// can send arbitrary IPC messages and confuse browser process internal state,
45// leading to security bugs. We are trying to verify that the browser doesn't
46// perform any dangerous operations in such cases.
47// This is similar to the security_exploit_browsertest.cc tests, but also
48// includes chrome/ layer concepts such as extensions.
Devlin Cronin836f545d2018-05-09 00:25:0549class ChromeSecurityExploitBrowserTest
50 : public extensions::ExtensionBrowserTest {
[email protected]5cd56342013-04-03 19:50:4751 public:
52 ChromeSecurityExploitBrowserTest() {}
Peter Boström53c6c5952021-09-17 09:41:2653
54 ChromeSecurityExploitBrowserTest(const ChromeSecurityExploitBrowserTest&) =
55 delete;
56 ChromeSecurityExploitBrowserTest& operator=(
57 const ChromeSecurityExploitBrowserTest&) = delete;
58
dchenge1bc7982014-10-30 00:32:4059 ~ChromeSecurityExploitBrowserTest() override {}
[email protected]5cd56342013-04-03 19:50:4760
nick2a8ba8c2016-10-03 18:51:3961 void SetUpOnMainThread() override {
Devlin Cronin836f545d2018-05-09 00:25:0562 extensions::ExtensionBrowserTest::SetUpOnMainThread();
calamityae7fed42017-06-22 04:58:2263
tsergeantbd3b7a4c2016-09-30 00:42:1964 ASSERT_TRUE(embedded_test_server()->Start());
nick2a8ba8c2016-10-03 18:51:3965 host_resolver()->AddRule("*", "127.0.0.1");
calamityae7fed42017-06-22 04:58:2266
67 extension_ = LoadExtension(test_data_dir_.AppendASCII("simple_with_icon"));
nick2a8ba8c2016-10-03 18:51:3968 }
tsergeantbd3b7a4c2016-09-30 00:42:1969
calamityae7fed42017-06-22 04:58:2270 const extensions::Extension* extension() { return extension_; }
71
Marijn Kruisselbrink604fd7e72017-10-26 16:31:0572 std::unique_ptr<content::BlobHandle> CreateMemoryBackedBlob(
73 const std::string& contents,
74 const std::string& content_type) {
75 std::unique_ptr<content::BlobHandle> result;
76 base::RunLoop loop;
Lukasz Anforowicz7ef1cfd2021-05-04 02:18:3777 profile()->CreateMemoryBackedBlob(
78 base::as_bytes(base::make_span(contents)), content_type,
Marijn Kruisselbrink604fd7e72017-10-26 16:31:0579 base::BindOnce(
80 [](std::unique_ptr<content::BlobHandle>* out_blob,
81 base::OnceClosure done,
82 std::unique_ptr<content::BlobHandle> blob) {
83 *out_blob = std::move(blob);
84 std::move(done).Run();
85 },
86 &result, loop.QuitClosure()));
87 loop.Run();
88 EXPECT_TRUE(result);
89 return result;
90 }
91
[email protected]5cd56342013-04-03 19:50:4792 private:
calamityae7fed42017-06-22 04:58:2293 const extensions::Extension* extension_;
[email protected]5cd56342013-04-03 19:50:4794};
95
Charlie Reise2c2c492018-06-15 21:34:0496// Subclass of ChromeSecurityExploitBrowserTest that uses --disable-web-security
97// to simulate an exploited renderer. Note that this also disables some browser
98// process checks, so it's not ideal for all exploit tests.
99class ChromeWebSecurityDisabledBrowserTest
100 : public ChromeSecurityExploitBrowserTest {
101 public:
102 ChromeWebSecurityDisabledBrowserTest() {}
Peter Boström53c6c5952021-09-17 09:41:26103
104 ChromeWebSecurityDisabledBrowserTest(
105 const ChromeWebSecurityDisabledBrowserTest&) = delete;
106 ChromeWebSecurityDisabledBrowserTest& operator=(
107 const ChromeWebSecurityDisabledBrowserTest&) = delete;
108
Charlie Reise2c2c492018-06-15 21:34:04109 ~ChromeWebSecurityDisabledBrowserTest() override {}
110
111 void SetUpCommandLine(base::CommandLine* command_line) override {
112 ChromeSecurityExploitBrowserTest::SetUpCommandLine(command_line);
113 command_line->AppendSwitch(switches::kDisableWebSecurity);
114 }
Charlie Reise2c2c492018-06-15 21:34:04115};
116
Nasko Oskovfd52b6f2019-02-06 19:21:15117// TODO(nasko): This test as written is incompatible with Site Isolation
118// restrictions, which disallow the cross-origin pushState call.
119// Find a different way to implement issuing the illegal request or just
120// delete the test if we have coverage elsewhere. See https://crbug.com/929161.
Charlie Reise2c2c492018-06-15 21:34:04121IN_PROC_BROWSER_TEST_F(ChromeWebSecurityDisabledBrowserTest,
Nasko Oskovfd52b6f2019-02-06 19:21:15122 DISABLED_ChromeExtensionResources) {
[email protected]5cd56342013-04-03 19:50:47123 // Load a page that requests a chrome-extension:// image through XHR. We
124 // expect this load to fail, as it is an illegal request.
nick2a8ba8c2016-10-03 18:51:39125 GURL foo = embedded_test_server()->GetURL("foo.com",
126 "/chrome_extension_resource.html");
[email protected]5cd56342013-04-03 19:50:47127
128 content::DOMMessageQueue msg_queue;
129
Lukasz Anforowiczb78290c2021-09-08 04:31:38130 ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), foo));
[email protected]5cd56342013-04-03 19:50:47131
132 std::string status;
133 std::string expected_status("0");
134 EXPECT_TRUE(msg_queue.WaitForMessage(&status));
135 EXPECT_STREQ(status.c_str(), expected_status.c_str());
136}
nick2a8ba8c2016-10-03 18:51:39137
Charlie Reise2c2c492018-06-15 21:34:04138// Tests that a normal web process cannot send a commit for a Chrome Web Store
139// URL. See https://crbug.com/172119.
140IN_PROC_BROWSER_TEST_F(ChromeSecurityExploitBrowserTest,
141 CommitWebStoreURLInWebProcess) {
142 GURL foo = embedded_test_server()->GetURL("foo.com", "/title1.html");
143
144 content::WebContents* web_contents =
145 browser()->tab_strip_model()->GetActiveWebContents();
146 content::RenderFrameHost* rfh = web_contents->GetMainFrame();
147
148 // This IPC should result in a kill because the Chrome Web Store is not
149 // allowed to commit in |rfh->GetProcess()|.
150 base::HistogramTester histograms;
151 content::RenderProcessHostWatcher crash_observer(
152 rfh->GetProcess(),
153 content::RenderProcessHostWatcher::WATCH_FOR_PROCESS_EXIT);
154
155 // Modify an IPC for a commit of a blank URL, which would otherwise be allowed
156 // to commit in any process.
157 GURL blank_url = GURL(url::kAboutBlankURL);
158 GURL webstore_url = extension_urls::GetWebstoreLaunchURL();
159 content::PwnCommitIPC(web_contents, blank_url, webstore_url,
160 url::Origin::Create(GURL(webstore_url)));
161 web_contents->GetController().LoadURL(
162 blank_url, content::Referrer(), ui::PAGE_TRANSITION_LINK, std::string());
163
164 // If the process is killed in CanCommitURL, this test passes.
165 crash_observer.Wait();
166 histograms.ExpectUniqueSample("Stability.BadMessageTerminated.Content", 1, 1);
167}
168
169// Tests that a non-extension process cannot send a commit of a blank URL with
170// an extension origin.
171IN_PROC_BROWSER_TEST_F(ChromeSecurityExploitBrowserTest,
172 CommitExtensionOriginInWebProcess) {
173 GURL foo = embedded_test_server()->GetURL("foo.com", "/title1.html");
174
175 content::WebContents* web_contents =
176 browser()->tab_strip_model()->GetActiveWebContents();
177 content::RenderFrameHost* rfh = web_contents->GetMainFrame();
178
179 // This IPC should result in a kill because |ext_origin| is not allowed to
180 // commit in |rfh->GetProcess()|.
181 base::HistogramTester histograms;
182 content::RenderProcessHostWatcher crash_observer(
183 rfh->GetProcess(),
184 content::RenderProcessHostWatcher::WATCH_FOR_PROCESS_EXIT);
185
186 // Modify an IPC for a commit of a blank URL, which would otherwise be allowed
187 // to commit in any process.
188 GURL blank_url = GURL(url::kAboutBlankURL);
189 std::string ext_origin = "chrome-extension://" + extension()->id();
190 content::PwnCommitIPC(web_contents, blank_url, blank_url,
191 url::Origin::Create(GURL(ext_origin)));
192 web_contents->GetController().LoadURL(
193 blank_url, content::Referrer(), ui::PAGE_TRANSITION_LINK, std::string());
194
195 // If the process is killed in CanCommitOrigin, this test passes.
196 crash_observer.Wait();
197 histograms.ExpectUniqueSample("Stability.BadMessageTerminated.Content", 114,
198 1);
199}
200
201// Tests that a non-extension process cannot send a commit of an extension URL.
202IN_PROC_BROWSER_TEST_F(ChromeSecurityExploitBrowserTest,
203 CommitExtensionURLInWebProcess) {
204 GURL foo = embedded_test_server()->GetURL("foo.com", "/title1.html");
205
206 content::WebContents* web_contents =
207 browser()->tab_strip_model()->GetActiveWebContents();
208 content::RenderFrameHost* rfh = web_contents->GetMainFrame();
209
210 // This IPC should result in a kill because extension URLs are not allowed to
211 // commit in |rfh->GetProcess()|.
212 base::HistogramTester histograms;
213 content::RenderProcessHostWatcher crash_observer(
214 rfh->GetProcess(),
215 content::RenderProcessHostWatcher::WATCH_FOR_PROCESS_EXIT);
216
217 // Modify an IPC for a commit of a blank URL, which would otherwise be allowed
218 // to commit in any process.
219 GURL blank_url = GURL(url::kAboutBlankURL);
220 std::string ext_origin = "chrome-extension://" + extension()->id();
221 content::PwnCommitIPC(web_contents, blank_url, GURL(ext_origin),
222 url::Origin::Create(GURL(ext_origin)));
223 web_contents->GetController().LoadURL(
224 blank_url, content::Referrer(), ui::PAGE_TRANSITION_LINK, std::string());
225
226 // If the process is killed in CanCommitURL, this test passes.
227 crash_observer.Wait();
228 histograms.ExpectUniqueSample("Stability.BadMessageTerminated.Content", 1, 1);
229}
230
231// Tests that a non-extension process cannot send a commit of an extension
232// filesystem URL.
233IN_PROC_BROWSER_TEST_F(ChromeSecurityExploitBrowserTest,
234 CommitExtensionFilesystemURLInWebProcess) {
235 GURL foo = embedded_test_server()->GetURL("foo.com", "/title1.html");
236
237 content::WebContents* web_contents =
238 browser()->tab_strip_model()->GetActiveWebContents();
239 content::RenderFrameHost* rfh = web_contents->GetMainFrame();
240
241 // This IPC should result in a kill because extension filesystem URLs are not
242 // allowed to commit in |rfh->GetProcess()|.
243 base::HistogramTester histograms;
244 content::RenderProcessHostWatcher crash_observer(
245 rfh->GetProcess(),
246 content::RenderProcessHostWatcher::WATCH_FOR_PROCESS_EXIT);
247
248 // Modify an IPC for a commit of a blank URL, which would otherwise be allowed
249 // to commit in any process.
250 GURL blank_url = GURL(url::kAboutBlankURL);
251 std::string ext_origin = "chrome-extension://" + extension()->id();
252 content::PwnCommitIPC(web_contents, blank_url,
253 GURL("filesystem:" + ext_origin + "/foo"),
254 url::Origin::Create(GURL(ext_origin)));
255 web_contents->GetController().LoadURL(
256 blank_url, content::Referrer(), ui::PAGE_TRANSITION_LINK, std::string());
257
258 // If the process is killed in CanCommitURL, this test passes.
259 crash_observer.Wait();
260 histograms.ExpectUniqueSample("Stability.BadMessageTerminated.Content", 1, 1);
261}
262
Daniel Cheng4ebba552018-07-06 21:43:16263// chrome://xyz should not be able to create a "filesystem:chrome://abc"
264// resource.
265IN_PROC_BROWSER_TEST_F(ChromeSecurityExploitBrowserTest,
266 CreateFilesystemURLInOtherChromeUIOrigin) {
Lukasz Anforowiczb78290c2021-09-08 04:31:38267 ASSERT_TRUE(
268 ui_test_utils::NavigateToURL(browser(), GURL("chrome://version")));
Daniel Cheng4ebba552018-07-06 21:43:16269
270 content::RenderFrameHost* rfh =
271 browser()->tab_strip_model()->GetActiveWebContents()->GetMainFrame();
272
273 // Block the renderer on operation that never completes, to shield it from
274 // receiving unexpected browser->renderer IPCs that might CHECK.
275 rfh->ExecuteJavaScriptWithUserGestureForTests(
Peter Kastingaae6db932021-05-04 12:02:11276 u"var r = new XMLHttpRequest();"
277 u"r.open('GET', '/slow?99999', false);"
278 u"r.send(null);"
279 u"while (1);");
Daniel Cheng4ebba552018-07-06 21:43:16280
281 std::string payload = "<p>Hello world!</p>";
282 std::string payload_type = "text/html";
283
284 // Target an extension.
285 std::string target_origin = "chrome://downloads";
286
287 // Set up a blob ID and populate it with the attacker-controlled payload. This
288 // is just using the blob APIs directly since creating arbitrary blobs is not
289 // what is prohibited; this data is not in any origin.
290 std::unique_ptr<content::BlobHandle> blob =
291 CreateMemoryBackedBlob(payload, payload_type);
292 std::string blob_id = blob->GetUUID();
293
294 // Note: a well-behaved renderer would always send the following message here,
295 // but it's actually not necessary for the original attack to succeed, so we
296 // omit it. As a result there are some log warnings from the quota observer.
297 //
298 // IPC::IpcSecurityTestUtil::PwnMessageReceived(
299 // rfh->GetProcess()->GetChannel(),
300 // FileSystemHostMsg_OpenFileSystem(22, GURL(target_origin),
301 // storage::kFileSystemTypeTemporary));
302
303 GURL target_url =
304 GURL("filesystem:" + target_origin + "/temporary/exploit.html");
305
306 content::PwnMessageHelper::FileSystemCreate(rfh->GetProcess(), 23, target_url,
307 false, false, false);
308
309 // Write the blob into the file. If successful, this places an
310 // attacker-controlled value in a resource on the extension origin.
311 content::PwnMessageHelper::FileSystemWrite(rfh->GetProcess(), 24, target_url,
312 blob_id, 0);
313
314 // Now navigate to |target_url| in a new tab. It should not contain |payload|.
315 AddTabAtIndex(0, target_url, ui::PAGE_TRANSITION_TYPED);
Synthia Islam2761aac2020-02-25 18:48:32316 EXPECT_FALSE(content::WaitForLoadStop(
317 browser()->tab_strip_model()->GetWebContentsAt(0)));
Daniel Cheng4ebba552018-07-06 21:43:16318 rfh = browser()->tab_strip_model()->GetActiveWebContents()->GetMainFrame();
319
320 // If the attack is unsuccessful, the navigation ends up in an error
321 // page.
322 if (content::SiteIsolationPolicy::IsErrorPageIsolationEnabled(
323 !rfh->GetParent())) {
324 EXPECT_EQ(GURL(content::kUnreachableWebDataURL),
325 rfh->GetSiteInstance()->GetSiteURL());
326 } else {
327 EXPECT_EQ(GURL(target_origin), rfh->GetSiteInstance()->GetSiteURL());
328 }
329 std::string body;
330 std::string script = R"(
331 var textContent = document.body.innerText.replace(/\n+/g, '\n');
332 window.domAutomationController.send(textContent);
333 )";
334
335 EXPECT_TRUE(content::ExecuteScriptAndExtractString(rfh, script, &body));
336 EXPECT_EQ(
Lily Chen09b5ce42020-07-16 06:28:41337 "Your file couldn’t be accessed\n"
338 "It may have been moved, edited, or deleted.\n"
Yoshifumi Inoue72d438e2018-08-24 08:17:29339 "ERR_FILE_NOT_FOUND",
Daniel Cheng4ebba552018-07-06 21:43:16340 body);
341}
342
nickbfaea4ee2016-12-02 20:59:31343// Extension isolation prevents a normal renderer process from being able to
nickbfaea4ee2016-12-02 20:59:31344// create a "filesystem:chrome-extension://sdgkjaghsdg/temporary/" resource.
345IN_PROC_BROWSER_TEST_F(ChromeSecurityExploitBrowserTest,
346 CreateFilesystemURLInExtensionOrigin) {
347 GURL page_url =
348 embedded_test_server()->GetURL("a.root-servers.net", "/title1.html");
Lukasz Anforowiczb78290c2021-09-08 04:31:38349 ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), page_url));
nickbfaea4ee2016-12-02 20:59:31350
351 content::RenderFrameHost* rfh =
352 browser()->tab_strip_model()->GetActiveWebContents()->GetMainFrame();
353
354 // Block the renderer on operation that never completes, to shield it from
355 // receiving unexpected browser->renderer IPCs that might CHECK.
356 rfh->ExecuteJavaScriptWithUserGestureForTests(
Peter Kastingaae6db932021-05-04 12:02:11357 u"var r = new XMLHttpRequest();"
358 u"r.open('GET', '/slow?99999', false);"
359 u"r.send(null);"
360 u"while (1);");
nickbfaea4ee2016-12-02 20:59:31361
362 // JS code that the attacker would like to run in an extension process.
363 std::string payload = "<html><body>pwned.</body></html>";
364 std::string payload_type = "text/html";
365
calamityae7fed42017-06-22 04:58:22366 // Target an extension.
367 std::string target_origin = "chrome-extension://" + extension()->id();
nickbfaea4ee2016-12-02 20:59:31368
Marijn Kruisselbrink604fd7e72017-10-26 16:31:05369 // Set up a blob ID and populate it with the attacker-controlled payload. This
370 // is just using the blob APIs directly since creating arbitrary blobs is not
371 // what is prohibited; this data is not in any origin.
372 std::unique_ptr<content::BlobHandle> blob =
373 CreateMemoryBackedBlob(payload, payload_type);
374 std::string blob_id = blob->GetUUID();
nickbfaea4ee2016-12-02 20:59:31375
Adithya Srinivasan0c72ff02018-08-13 19:47:29376 // Note: a well-behaved renderer would always call Open first before calling
377 // Create and Write, but it's actually not necessary for the original attack
378 // to succeed, so we omit it. As a result there are some log warnings from the
379 // quota observer.
nickbfaea4ee2016-12-02 20:59:31380
381 GURL target_url =
calamityae7fed42017-06-22 04:58:22382 GURL("filesystem:" + target_origin + "/temporary/exploit.html");
nickbfaea4ee2016-12-02 20:59:31383
384 content::PwnMessageHelper::FileSystemCreate(rfh->GetProcess(), 23, target_url,
385 false, false, false);
386
387 // Write the blob into the file. If successful, this places an
388 // attacker-controlled value in a resource on the extension origin.
389 content::PwnMessageHelper::FileSystemWrite(rfh->GetProcess(), 24, target_url,
390 blob_id, 0);
391
392 // Now navigate to |target_url| in a new tab. It should not contain |payload|.
393 AddTabAtIndex(0, target_url, ui::PAGE_TRANSITION_TYPED);
Synthia Islam2761aac2020-02-25 18:48:32394 EXPECT_FALSE(content::WaitForLoadStop(
395 browser()->tab_strip_model()->GetWebContentsAt(0)));
nickbfaea4ee2016-12-02 20:59:31396 rfh = browser()->tab_strip_model()->GetActiveWebContents()->GetMainFrame();
Nasko Oskovd83b5712018-05-04 04:50:57397
398 // If the attack is unsuccessful, the navigation ends up in an error
399 // page.
Nasko Oskovd515cab2018-05-09 15:34:20400 if (content::SiteIsolationPolicy::IsErrorPageIsolationEnabled(
401 !rfh->GetParent())) {
402 EXPECT_EQ(GURL(content::kUnreachableWebDataURL),
403 rfh->GetSiteInstance()->GetSiteURL());
404 } else {
405 EXPECT_EQ(GURL(target_origin), rfh->GetSiteInstance()->GetSiteURL());
406 }
nickbfaea4ee2016-12-02 20:59:31407 std::string body;
ntfschrd9f4dd52017-05-02 16:21:27408 std::string script = R"(
409 var textContent = document.body.innerText.replace(/\n+/g, '\n');
410 window.domAutomationController.send(textContent);
411 )";
412
413 EXPECT_TRUE(content::ExecuteScriptAndExtractString(rfh, script, &body));
naskoabed2a52017-05-03 05:10:17414 EXPECT_EQ(
Lily Chen09b5ce42020-07-16 06:28:41415 "Your file couldn’t be accessed\n"
416 "It may have been moved, edited, or deleted.\n"
Yoshifumi Inoue72d438e2018-08-24 08:17:29417 "ERR_FILE_NOT_FOUND",
naskoabed2a52017-05-03 05:10:17418 body);
nick2a8ba8c2016-10-03 18:51:39419}
Marijn Kruisselbrink8f1b1a72018-01-26 18:09:56420
421namespace {
422
423class BlobURLStoreInterceptor
424 : public blink::mojom::BlobURLStoreInterceptorForTesting {
425 public:
Lucas Furukawa Gadaniebc8f7fc32019-06-18 18:06:48426 static void Intercept(
427 GURL target_url,
Julie Jeongeun Kimb8c0b1c2019-10-30 01:13:06428 mojo::SelfOwnedAssociatedReceiverRef<blink::mojom::BlobURLStore>
429 receiver) {
Lucas Furukawa Gadaniebc8f7fc32019-06-18 18:06:48430 auto interceptor =
431 base::WrapUnique(new BlobURLStoreInterceptor(target_url));
432 auto* raw_interceptor = interceptor.get();
Julie Jeongeun Kimb8c0b1c2019-10-30 01:13:06433 auto impl = receiver->SwapImplForTesting(std::move(interceptor));
Lucas Furukawa Gadaniebc8f7fc32019-06-18 18:06:48434 raw_interceptor->url_store_ = std::move(impl);
Marijn Kruisselbrink8f1b1a72018-01-26 18:09:56435 }
436
437 blink::mojom::BlobURLStore* GetForwardingInterface() override {
Lucas Furukawa Gadaniebc8f7fc32019-06-18 18:06:48438 return url_store_.get();
Marijn Kruisselbrink8f1b1a72018-01-26 18:09:56439 }
440
Ari Chivukulaa29eb3a2021-07-21 02:57:48441 void Register(
442 mojo::PendingRemote<blink::mojom::Blob> blob,
443 const GURL& url,
444 // TODO(https://crbug.com/1224926): Remove this once experiment is over.
445 const base::UnguessableToken& unsafe_agent_cluster_id,
446 RegisterCallback callback) override {
Marijn Kruisselbrink8f1b1a72018-01-26 18:09:56447 GetForwardingInterface()->Register(std::move(blob), target_url_,
Ari Chivukulaa29eb3a2021-07-21 02:57:48448 unsafe_agent_cluster_id,
Marijn Kruisselbrink8f1b1a72018-01-26 18:09:56449 std::move(callback));
450 }
451
452 private:
Lucas Furukawa Gadaniebc8f7fc32019-06-18 18:06:48453 explicit BlobURLStoreInterceptor(GURL target_url) : target_url_(target_url) {}
454
455 std::unique_ptr<blink::mojom::BlobURLStore> url_store_;
Marijn Kruisselbrink8f1b1a72018-01-26 18:09:56456 GURL target_url_;
457};
458
459} // namespace
460
461class ChromeSecurityExploitBrowserTestMojoBlobURLs
462 : public ChromeSecurityExploitBrowserTest {
463 public:
Kinuko Yasuda7d925ea22019-08-01 10:08:48464 ChromeSecurityExploitBrowserTestMojoBlobURLs() = default;
Marijn Kruisselbrink8f1b1a72018-01-26 18:09:56465
466 void TearDown() override {
467 storage::BlobRegistryImpl::SetURLStoreCreationHookForTesting(nullptr);
468 }
469};
470
471// Extension isolation prevents a normal renderer process from being able to
472// create a "blob:chrome-extension://" resource.
473IN_PROC_BROWSER_TEST_F(ChromeSecurityExploitBrowserTestMojoBlobURLs,
474 CreateBlobInExtensionOrigin) {
475 // Target an extension.
476 std::string target_origin = "chrome-extension://" + extension()->id();
477 std::string blob_path = "5881f76e-10d2-410d-8c61-ef210502acfd";
Lucas Furukawa Gadaniebc8f7fc32019-06-18 18:06:48478 auto intercept_hook =
479 base::BindRepeating(&BlobURLStoreInterceptor::Intercept,
480 GURL("blob:" + target_origin + "/" + blob_path));
Marijn Kruisselbrink8f1b1a72018-01-26 18:09:56481 storage::BlobRegistryImpl::SetURLStoreCreationHookForTesting(&intercept_hook);
482
Lukasz Anforowiczb78290c2021-09-08 04:31:38483 ASSERT_TRUE(ui_test_utils::NavigateToURL(
Marijn Kruisselbrink8f1b1a72018-01-26 18:09:56484 browser(),
Lukasz Anforowiczb78290c2021-09-08 04:31:38485 embedded_test_server()->GetURL("a.root-servers.net", "/title1.html")));
Marijn Kruisselbrink8f1b1a72018-01-26 18:09:56486
487 content::RenderFrameHost* rfh =
488 browser()->tab_strip_model()->GetActiveWebContents()->GetMainFrame();
489
Lukasz Anforowicz110cda32020-03-23 22:32:03490 content::RenderProcessHostBadMojoMessageWaiter crash_observer(
491 rfh->GetProcess());
Marijn Kruisselbrink8f1b1a72018-01-26 18:09:56492
493 // The renderer should always get killed, but sometimes ExecuteScript returns
494 // true anyway, so just ignore the result.
495 ignore_result(
496 content::ExecuteScript(rfh, "URL.createObjectURL(new Blob(['foo']))"));
497
498 // If the process is killed, this test passes.
Lukasz Anforowicz110cda32020-03-23 22:32:03499 EXPECT_EQ(
500 "Received bad user message: Non committable URL passed to "
501 "BlobURLStore::Register",
502 crash_observer.Wait());
Marijn Kruisselbrink8f1b1a72018-01-26 18:09:56503}
Kinuko Yasuda04a82ab2019-07-26 06:13:39504
Trent Aptedc9983212021-06-29 02:47:58505// Flaky. See https://crbug.com/1224293.
506#if defined(OS_CHROMEOS)
507#define MAYBE_CreateBlobInOtherChromeUIOrigin \
508 DISABLED_CreateBlobInOtherChromeUIOrigin
509#else
510#define MAYBE_CreateBlobInOtherChromeUIOrigin CreateBlobInOtherChromeUIOrigin
511#endif // OS_CHROMEOS
Kinuko Yasuda04a82ab2019-07-26 06:13:39512// chrome://xyz should not be able to create a "blob:chrome://abc" resource.
513IN_PROC_BROWSER_TEST_F(ChromeSecurityExploitBrowserTestMojoBlobURLs,
Trent Aptedc9983212021-06-29 02:47:58514 MAYBE_CreateBlobInOtherChromeUIOrigin) {
Lukasz Anforowiczb78290c2021-09-08 04:31:38515 ASSERT_TRUE(
516 ui_test_utils::NavigateToURL(browser(), GURL("chrome://version")));
Kinuko Yasuda04a82ab2019-07-26 06:13:39517
518 // All these are attacker controlled values.
519 std::string blob_type = "text/html";
520 std::string blob_contents = "<p>Hello world!</p>";
521 std::string blob_path = "f7dfbeb5-8e41-4c4a-8486-a52fed33c4c0";
522
523 // Target an extension.
524 std::string target_origin = "chrome://downloads";
525
526 auto intercept_hook =
527 base::BindRepeating(&BlobURLStoreInterceptor::Intercept,
528 GURL("blob:" + target_origin + "/" + blob_path));
529 storage::BlobRegistryImpl::SetURLStoreCreationHookForTesting(&intercept_hook);
530
531 content::RenderFrameHost* rfh =
532 browser()->tab_strip_model()->GetActiveWebContents()->GetMainFrame();
533
Lukasz Anforowicz110cda32020-03-23 22:32:03534 content::RenderProcessHostBadMojoMessageWaiter crash_observer(
535 rfh->GetProcess());
Kinuko Yasuda04a82ab2019-07-26 06:13:39536
537 // The renderer should always get killed, but sometimes ExecuteScript returns
538 // true anyway, so just ignore the result.
539 ignore_result(
540 content::ExecuteScript(rfh, "URL.createObjectURL(new Blob(['foo']))"));
541
542 // If the process is killed, this test passes.
Lukasz Anforowicz110cda32020-03-23 22:32:03543 EXPECT_EQ(
544 "Received bad user message: Non committable URL passed to "
545 "BlobURLStore::Register",
546 crash_observer.Wait());
Kinuko Yasuda04a82ab2019-07-26 06:13:39547}