[M120][Tizen][Onscreen] Fix build errors for TV profile
[platform/framework/web/chromium-efl.git] / chrome / browser / chrome_security_exploit_browsertest.cc
1 // Copyright 2013 The Chromium Authors
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4
5 #include <tuple>
6
7 #include "base/command_line.h"
8 #include "base/feature_list.h"
9 #include "base/functional/bind.h"
10 #include "base/functional/callback_helpers.h"
11 #include "base/memory/ptr_util.h"
12 #include "base/memory/raw_ptr.h"
13 #include "base/memory/weak_ptr.h"
14 #include "base/run_loop.h"
15 #include "base/strings/utf_string_conversions.h"
16 #include "base/test/metrics/histogram_tester.h"
17 #include "base/test/scoped_feature_list.h"
18 #include "base/unguessable_token.h"
19 #include "build/build_config.h"
20 #include "chrome/browser/extensions/extension_browsertest.h"
21 #include "chrome/browser/ui/browser.h"
22 #include "chrome/browser/ui/browser_commands.h"
23 #include "chrome/browser/ui/singleton_tabs.h"
24 #include "chrome/browser/ui/tabs/tab_strip_model.h"
25 #include "chrome/test/base/ui_test_utils.h"
26 #include "content/public/browser/blob_handle.h"
27 #include "content/public/browser/render_frame_host.h"
28 #include "content/public/browser/render_process_host.h"
29 #include "content/public/browser/site_isolation_policy.h"
30 #include "content/public/browser/web_contents_observer.h"
31 #include "content/public/common/content_switches.h"
32 #include "content/public/common/url_constants.h"
33 #include "content/public/test/browser_test.h"
34 #include "content/public/test/browser_test_utils.h"
35 #include "extensions/common/extension_urls.h"
36 #include "mojo/public/cpp/bindings/pending_remote.h"
37 #include "net/base/features.h"
38 #include "net/dns/mock_host_resolver.h"
39 #include "net/test/embedded_test_server/embedded_test_server.h"
40 #include "storage/browser/blob/blob_registry_impl.h"
41 #include "third_party/blink/public/common/blob/blob_utils.h"
42 #include "third_party/blink/public/common/features.h"
43 #include "third_party/blink/public/common/storage_key/storage_key.h"
44 #include "third_party/blink/public/mojom/blob/blob_url_store.mojom.h"
45 #include "url/gurl.h"
46 #include "url/origin.h"
47
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.
54 class ChromeSecurityExploitBrowserTest
55     : public extensions::ExtensionBrowserTest {
56  public:
57   ChromeSecurityExploitBrowserTest() {}
58
59   ChromeSecurityExploitBrowserTest(const ChromeSecurityExploitBrowserTest&) =
60       delete;
61   ChromeSecurityExploitBrowserTest& operator=(
62       const ChromeSecurityExploitBrowserTest&) = delete;
63
64   ~ChromeSecurityExploitBrowserTest() override {}
65
66   void SetUpOnMainThread() override {
67     extensions::ExtensionBrowserTest::SetUpOnMainThread();
68
69     ASSERT_TRUE(embedded_test_server()->Start());
70     host_resolver()->AddRule("*", "127.0.0.1");
71
72     extension_ = LoadExtension(test_data_dir_.AppendASCII("simple_with_icon"));
73   }
74
75   const extensions::Extension* extension() { return extension_; }
76
77   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;
82     profile()->CreateMemoryBackedBlob(
83         base::as_bytes(base::make_span(contents)), content_type,
84         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
97  private:
98   raw_ptr<const extensions::Extension, DanglingUntriaged> extension_;
99 };
100
101 // 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.
104 class ChromeWebSecurityDisabledBrowserTest
105     : public ChromeSecurityExploitBrowserTest {
106  public:
107   ChromeWebSecurityDisabledBrowserTest() {}
108
109   ChromeWebSecurityDisabledBrowserTest(
110       const ChromeWebSecurityDisabledBrowserTest&) = delete;
111   ChromeWebSecurityDisabledBrowserTest& operator=(
112       const ChromeWebSecurityDisabledBrowserTest&) = delete;
113
114   ~ChromeWebSecurityDisabledBrowserTest() override {}
115
116   void SetUpCommandLine(base::CommandLine* command_line) override {
117     ChromeSecurityExploitBrowserTest::SetUpCommandLine(command_line);
118     command_line->AppendSwitch(switches::kDisableWebSecurity);
119   }
120 };
121
122 // 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.
126 IN_PROC_BROWSER_TEST_F(ChromeWebSecurityDisabledBrowserTest,
127                        DISABLED_ChromeExtensionResources) {
128   // Load a page that requests a chrome-extension:// image through XHR. We
129   // expect this load to fail, as it is an illegal request.
130   GURL foo = embedded_test_server()->GetURL("foo.com",
131                                             "/chrome_extension_resource.html");
132
133   content::DOMMessageQueue msg_queue(
134       browser()->tab_strip_model()->GetActiveWebContents());
135
136   ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), foo));
137
138   std::string status;
139   std::string expected_status("0");
140   EXPECT_TRUE(msg_queue.WaitForMessage(&status));
141   EXPECT_STREQ(status.c_str(), expected_status.c_str());
142 }
143
144 // Tests that a normal web process cannot send a commit for a Chrome Web Store
145 // URL.  See https://crbug.com/172119.
146 IN_PROC_BROWSER_TEST_F(ChromeSecurityExploitBrowserTest,
147                        CommitWebStoreURLInWebProcess) {
148   GURL foo = embedded_test_server()->GetURL("foo.com", "/title1.html");
149
150   content::WebContents* web_contents =
151       browser()->tab_strip_model()->GetActiveWebContents();
152   content::RenderFrameHost* rfh = web_contents->GetPrimaryMainFrame();
153
154   // This IPC should result in a kill because the Chrome Web Store is not
155   // allowed to commit in |rfh->GetProcess()|.
156   base::HistogramTester histograms;
157   content::RenderProcessHostWatcher crash_observer(
158       rfh->GetProcess(),
159       content::RenderProcessHostWatcher::WATCH_FOR_PROCESS_EXIT);
160
161   // Modify an IPC for a commit of a blank URL, which would otherwise be allowed
162   // to commit in any process.
163   GURL blank_url = GURL(url::kAboutBlankURL);
164   GURL webstore_url = extension_urls::GetWebstoreLaunchURL();
165   content::PwnCommitIPC(web_contents, blank_url, webstore_url,
166                         url::Origin::Create(GURL(webstore_url)));
167   web_contents->GetController().LoadURL(
168       blank_url, content::Referrer(), ui::PAGE_TRANSITION_LINK, std::string());
169
170   // If the process is killed in CanCommitURL, this test passes.
171   crash_observer.Wait();
172   histograms.ExpectUniqueSample("Stability.BadMessageTerminated.Content", 1, 1);
173 }
174
175 // Tests that a non-extension process cannot send a commit of a blank URL with
176 // an extension origin.
177 IN_PROC_BROWSER_TEST_F(ChromeSecurityExploitBrowserTest,
178                        CommitExtensionOriginInWebProcess) {
179   GURL foo = embedded_test_server()->GetURL("foo.com", "/title1.html");
180
181   content::WebContents* web_contents =
182       browser()->tab_strip_model()->GetActiveWebContents();
183   content::RenderFrameHost* rfh = web_contents->GetPrimaryMainFrame();
184
185   // This IPC should result in a kill because |ext_origin| is not allowed to
186   // commit in |rfh->GetProcess()|.
187   base::HistogramTester histograms;
188   content::RenderProcessHostWatcher crash_observer(
189       rfh->GetProcess(),
190       content::RenderProcessHostWatcher::WATCH_FOR_PROCESS_EXIT);
191
192   // Modify an IPC for a commit of a blank URL, which would otherwise be allowed
193   // to commit in any process.
194   GURL blank_url = GURL(url::kAboutBlankURL);
195   std::string ext_origin = "chrome-extension://" + extension()->id();
196   content::PwnCommitIPC(web_contents, blank_url, blank_url,
197                         url::Origin::Create(GURL(ext_origin)));
198   web_contents->GetController().LoadURL(
199       blank_url, content::Referrer(), ui::PAGE_TRANSITION_LINK, std::string());
200
201   // If the process is killed in CanCommitOrigin, this test passes.
202   crash_observer.Wait();
203   histograms.ExpectUniqueSample("Stability.BadMessageTerminated.Content", 114,
204                                 1);
205 }
206
207 // Tests that a non-extension process cannot send a commit of an extension URL.
208 IN_PROC_BROWSER_TEST_F(ChromeSecurityExploitBrowserTest,
209                        CommitExtensionURLInWebProcess) {
210   GURL foo = embedded_test_server()->GetURL("foo.com", "/title1.html");
211
212   content::WebContents* web_contents =
213       browser()->tab_strip_model()->GetActiveWebContents();
214   content::RenderFrameHost* rfh = web_contents->GetPrimaryMainFrame();
215
216   // This IPC should result in a kill because extension URLs are not allowed to
217   // commit in |rfh->GetProcess()|.
218   base::HistogramTester histograms;
219   content::RenderProcessHostWatcher crash_observer(
220       rfh->GetProcess(),
221       content::RenderProcessHostWatcher::WATCH_FOR_PROCESS_EXIT);
222
223   // Modify an IPC for a commit of a blank URL, which would otherwise be allowed
224   // to commit in any process.
225   GURL blank_url = GURL(url::kAboutBlankURL);
226   std::string ext_origin = "chrome-extension://" + extension()->id();
227   content::PwnCommitIPC(web_contents, blank_url, GURL(ext_origin),
228                         url::Origin::Create(GURL(ext_origin)));
229   web_contents->GetController().LoadURL(
230       blank_url, content::Referrer(), ui::PAGE_TRANSITION_LINK, std::string());
231
232   // If the process is killed in CanCommitURL, this test passes.
233   crash_observer.Wait();
234   histograms.ExpectUniqueSample("Stability.BadMessageTerminated.Content", 1, 1);
235 }
236
237 // Tests that a non-extension process cannot send a commit of an extension
238 // filesystem URL.
239 IN_PROC_BROWSER_TEST_F(ChromeSecurityExploitBrowserTest,
240                        CommitExtensionFilesystemURLInWebProcess) {
241   GURL foo = embedded_test_server()->GetURL("foo.com", "/title1.html");
242
243   content::WebContents* web_contents =
244       browser()->tab_strip_model()->GetActiveWebContents();
245   content::RenderFrameHost* rfh = web_contents->GetPrimaryMainFrame();
246
247   // This IPC should result in a kill because extension filesystem URLs are not
248   // allowed to commit in |rfh->GetProcess()|.
249   base::HistogramTester histograms;
250   content::RenderProcessHostWatcher crash_observer(
251       rfh->GetProcess(),
252       content::RenderProcessHostWatcher::WATCH_FOR_PROCESS_EXIT);
253
254   // Modify an IPC for a commit of a blank URL, which would otherwise be allowed
255   // to commit in any process.
256   GURL blank_url = GURL(url::kAboutBlankURL);
257   std::string ext_origin = "chrome-extension://" + extension()->id();
258   content::PwnCommitIPC(web_contents, blank_url,
259                         GURL("filesystem:" + ext_origin + "/foo"),
260                         url::Origin::Create(GURL(ext_origin)));
261   web_contents->GetController().LoadURL(
262       blank_url, content::Referrer(), ui::PAGE_TRANSITION_LINK, std::string());
263
264   // If the process is killed in CanCommitURL, this test passes.
265   crash_observer.Wait();
266   histograms.ExpectUniqueSample("Stability.BadMessageTerminated.Content", 1, 1);
267 }
268
269 // chrome://xyz should not be able to create a "filesystem:chrome://abc"
270 // resource.
271 IN_PROC_BROWSER_TEST_F(ChromeSecurityExploitBrowserTest,
272                        CreateFilesystemURLInOtherChromeUIOrigin) {
273   ASSERT_TRUE(
274       ui_test_utils::NavigateToURL(browser(), GURL("chrome://version")));
275
276   content::RenderFrameHost* rfh = browser()
277                                       ->tab_strip_model()
278                                       ->GetActiveWebContents()
279                                       ->GetPrimaryMainFrame();
280
281   // Block the renderer on operation that never completes, to shield it from
282   // receiving unexpected browser->renderer IPCs that might CHECK.
283   rfh->ExecuteJavaScriptWithUserGestureForTests(
284       u"var r = new XMLHttpRequest();"
285       u"r.open('GET', '/slow?99999', false);"
286       u"r.send(null);"
287       u"while (1);",
288       base::NullCallback());
289
290   std::string payload = "<p>Hello world!</p>";
291   std::string payload_type = "text/html";
292
293   // Target an extension.
294   std::string target_origin = "chrome://downloads";
295
296   // Set up a blob ID and populate it with the attacker-controlled payload. This
297   // is just using the blob APIs directly since creating arbitrary blobs is not
298   // what is prohibited; this data is not in any origin.
299   std::unique_ptr<content::BlobHandle> blob =
300       CreateMemoryBackedBlob(payload, payload_type);
301   std::string blob_id = blob->GetUUID();
302
303   // Note: a well-behaved renderer would always send the following message here,
304   // but it's actually not necessary for the original attack to succeed, so we
305   // omit it. As a result there are some log warnings from the quota observer.
306   //
307   // IPC::IpcSecurityTestUtil::PwnMessageReceived(
308   //     rfh->GetProcess()->GetChannel(),
309   //     FileSystemHostMsg_OpenFileSystem(22, GURL(target_origin),
310   //                                      storage::kFileSystemTypeTemporary));
311
312   GURL target_url =
313       GURL("filesystem:" + target_origin + "/temporary/exploit.html");
314
315   content::PwnMessageHelper::FileSystemCreate(
316       rfh->GetProcess(), 23, target_url, false, false, false,
317       blink::StorageKey::CreateFirstParty(url::Origin::Create(target_url)));
318
319   // Write the blob into the file. If successful, this places an
320   // attacker-controlled value in a resource on the extension origin.
321   content::PwnMessageHelper::FileSystemWrite(
322       rfh->GetProcess(), 24, target_url, blob_id, 0,
323       blink::StorageKey::CreateFirstParty(url::Origin::Create(target_url)));
324
325   // Now navigate to |target_url| in a new tab. It should not contain |payload|.
326   ASSERT_FALSE(AddTabAtIndex(0, target_url, ui::PAGE_TRANSITION_TYPED));
327   EXPECT_FALSE(content::WaitForLoadStop(
328       browser()->tab_strip_model()->GetWebContentsAt(0)));
329   rfh = browser()
330             ->tab_strip_model()
331             ->GetActiveWebContents()
332             ->GetPrimaryMainFrame();
333
334   // If the attack is unsuccessful, the navigation ends up in an error
335   // page.
336   if (content::SiteIsolationPolicy::IsErrorPageIsolationEnabled(
337           !rfh->GetParent())) {
338     EXPECT_EQ(GURL(content::kUnreachableWebDataURL),
339               rfh->GetSiteInstance()->GetSiteURL());
340   } else {
341     EXPECT_EQ(GURL(target_origin), rfh->GetSiteInstance()->GetSiteURL());
342   }
343   std::string script = R"(
344     var textContent = document.body.innerText.replace(/\n+/g, '\n');
345     textContent;
346   )";
347
348   std::string body = content::EvalJs(rfh, script).ExtractString();
349   EXPECT_EQ(
350       "Your file couldn’t be accessed\n"
351       "It may have been moved, edited, or deleted.\n"
352       "ERR_FILE_NOT_FOUND",
353       body);
354 }
355
356 // Extension isolation prevents a normal renderer process from being able to
357 // create a "filesystem:chrome-extension://sdgkjaghsdg/temporary/" resource.
358 IN_PROC_BROWSER_TEST_F(ChromeSecurityExploitBrowserTest,
359                        CreateFilesystemURLInExtensionOrigin) {
360   GURL page_url =
361       embedded_test_server()->GetURL("a.root-servers.net", "/title1.html");
362   ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), page_url));
363
364   content::RenderFrameHost* rfh = browser()
365                                       ->tab_strip_model()
366                                       ->GetActiveWebContents()
367                                       ->GetPrimaryMainFrame();
368
369   // Block the renderer on operation that never completes, to shield it from
370   // receiving unexpected browser->renderer IPCs that might CHECK.
371   rfh->ExecuteJavaScriptWithUserGestureForTests(
372       u"var r = new XMLHttpRequest();"
373       u"r.open('GET', '/slow?99999', false);"
374       u"r.send(null);"
375       u"while (1);",
376       base::NullCallback());
377
378   // JS code that the attacker would like to run in an extension process.
379   std::string payload = "<html><body>pwned.</body></html>";
380   std::string payload_type = "text/html";
381
382   // Target an extension.
383   std::string target_origin = "chrome-extension://" + extension()->id();
384
385   // Set up a blob ID and populate it with the attacker-controlled payload. This
386   // is just using the blob APIs directly since creating arbitrary blobs is not
387   // what is prohibited; this data is not in any origin.
388   std::unique_ptr<content::BlobHandle> blob =
389       CreateMemoryBackedBlob(payload, payload_type);
390   std::string blob_id = blob->GetUUID();
391
392   // Note: a well-behaved renderer would always call Open first before calling
393   // Create and Write, but it's actually not necessary for the original attack
394   // to succeed, so we omit it. As a result there are some log warnings from the
395   // quota observer.
396
397   GURL target_url =
398       GURL("filesystem:" + target_origin + "/temporary/exploit.html");
399
400   content::PwnMessageHelper::FileSystemCreate(
401       rfh->GetProcess(), 23, target_url, false, false, false,
402       blink::StorageKey::CreateFirstParty(url::Origin::Create(target_url)));
403
404   // Write the blob into the file. If successful, this places an
405   // attacker-controlled value in a resource on the extension origin.
406   content::PwnMessageHelper::FileSystemWrite(
407       rfh->GetProcess(), 24, target_url, blob_id, 0,
408       blink::StorageKey::CreateFirstParty(url::Origin::Create(target_url)));
409
410   // Now navigate to |target_url| in a new tab. It should not contain |payload|.
411   ASSERT_FALSE(AddTabAtIndex(0, target_url, ui::PAGE_TRANSITION_TYPED));
412   EXPECT_FALSE(content::WaitForLoadStop(
413       browser()->tab_strip_model()->GetWebContentsAt(0)));
414   rfh = browser()
415             ->tab_strip_model()
416             ->GetActiveWebContents()
417             ->GetPrimaryMainFrame();
418
419   // If the attack is unsuccessful, the navigation ends up in an error
420   // page.
421   if (content::SiteIsolationPolicy::IsErrorPageIsolationEnabled(
422           !rfh->GetParent())) {
423     EXPECT_EQ(GURL(content::kUnreachableWebDataURL),
424               rfh->GetSiteInstance()->GetSiteURL());
425   } else {
426     EXPECT_EQ(GURL(target_origin), rfh->GetSiteInstance()->GetSiteURL());
427   }
428   std::string body;
429   std::string script = R"(
430     var textContent = document.body.innerText.replace(/\n+/g, '\n');
431     textContent;
432   )";
433
434   body = content::EvalJs(rfh, script).ExtractString();
435   EXPECT_EQ(
436       "Your file couldn’t be accessed\n"
437       "It may have been moved, edited, or deleted.\n"
438       "ERR_FILE_NOT_FOUND",
439       body);
440 }
441
442 enum class ChromeSecurityExploitBrowserTestMojoBlobURLsTestCase {
443   kSupportPartitionedBlobUrlDisabled,
444   kSupportPartitionedBlobUrlEnabled,
445 };
446
447 class ChromeSecurityExploitBrowserTestMojoBlobURLsP
448     : public ChromeSecurityExploitBrowserTest,
449       public testing::WithParamInterface<
450           ChromeSecurityExploitBrowserTestMojoBlobURLsTestCase> {
451  public:
452   ChromeSecurityExploitBrowserTestMojoBlobURLsP() = default;
453
454   void SetUp() override {
455     test_case_ = GetParam();
456     scoped_feature_list_.InitWithFeatureState(
457         net::features::kSupportPartitionedBlobUrl,
458         test_case_ == ChromeSecurityExploitBrowserTestMojoBlobURLsTestCase::
459                           kSupportPartitionedBlobUrlEnabled);
460
461     ChromeSecurityExploitBrowserTest::SetUp();
462   }
463
464   void TearDown() override {
465     if (base::FeatureList::IsEnabled(
466             net::features::kSupportPartitionedBlobUrl)) {
467       storage::BlobUrlRegistry::SetURLStoreCreationHookForTesting(nullptr);
468     } else {
469       storage::BlobRegistryImpl::SetURLStoreCreationHookForTesting(nullptr);
470     }
471   }
472
473  private:
474   ChromeSecurityExploitBrowserTestMojoBlobURLsTestCase test_case_;
475   base::test::ScopedFeatureList scoped_feature_list_;
476 };
477
478 // Extension isolation prevents a normal renderer process from being able to
479 // create a "blob:chrome-extension://" resource.
480 IN_PROC_BROWSER_TEST_P(ChromeSecurityExploitBrowserTestMojoBlobURLsP,
481                        CreateBlobInExtensionOrigin) {
482   // Target an extension.
483   std::string target_origin = "chrome-extension://" + extension()->id();
484   std::string blob_path = "5881f76e-10d2-410d-8c61-ef210502acfd";
485
486   base::RepeatingCallback<void(storage::BlobUrlRegistry*, mojo::ReceiverId)>
487       blob_url_registry_intercept_hook;
488   base::RepeatingCallback<void(
489       mojo::SelfOwnedAssociatedReceiverRef<blink::mojom::BlobURLStore>)>
490       blob_registry_impl_intercept_hook;
491
492   if (base::FeatureList::IsEnabled(net::features::kSupportPartitionedBlobUrl)) {
493     blob_url_registry_intercept_hook =
494         base::BindRepeating(&content::BlobURLStoreInterceptor::Intercept,
495                             GURL("blob:" + target_origin + "/" + blob_path));
496     storage::BlobUrlRegistry::SetURLStoreCreationHookForTesting(
497         &blob_url_registry_intercept_hook);
498   } else {
499     blob_registry_impl_intercept_hook = base::BindRepeating(
500         &content::BlobURLStoreInterceptor::InterceptDeprecated,
501         GURL("blob:" + target_origin + "/" + blob_path));
502     storage::BlobRegistryImpl::SetURLStoreCreationHookForTesting(
503         &blob_registry_impl_intercept_hook);
504   }
505
506   ASSERT_TRUE(ui_test_utils::NavigateToURL(
507       browser(),
508       embedded_test_server()->GetURL("a.root-servers.net", "/title1.html")));
509
510   content::RenderFrameHost* rfh = browser()
511                                       ->tab_strip_model()
512                                       ->GetActiveWebContents()
513                                       ->GetPrimaryMainFrame();
514
515   content::RenderProcessHostBadMojoMessageWaiter crash_observer(
516       rfh->GetProcess());
517
518   // The renderer should always get killed, but sometimes ExecJs returns
519   // true anyway, so just ignore the result.
520   std::ignore = content::ExecJs(rfh, "URL.createObjectURL(new Blob(['foo']))");
521
522   // If the process is killed, this test passes.
523   EXPECT_EQ(
524       "Received bad user message: "
525       "URL with invalid origin passed to BlobURLStore::Register",
526       crash_observer.Wait());
527 }
528
529 // chrome://xyz should not be able to create a "blob:chrome://abc" resource.
530 IN_PROC_BROWSER_TEST_P(ChromeSecurityExploitBrowserTestMojoBlobURLsP,
531                        CreateBlobInOtherChromeUIOrigin) {
532   ASSERT_TRUE(
533       ui_test_utils::NavigateToURL(browser(), GURL("chrome://version")));
534
535   // All these are attacker controlled values.
536   std::string blob_type = "text/html";
537   std::string blob_contents = "<p>Hello world!</p>";
538   std::string blob_path = "f7dfbeb5-8e41-4c4a-8486-a52fed33c4c0";
539
540   // Target an extension.
541   std::string target_origin = "chrome://downloads";
542
543   base::RepeatingCallback<void(storage::BlobUrlRegistry*, mojo::ReceiverId)>
544       blob_url_registry_intercept_hook;
545   base::RepeatingCallback<void(
546       mojo::SelfOwnedAssociatedReceiverRef<blink::mojom::BlobURLStore>)>
547       blob_registry_impl_intercept_hook;
548
549   if (base::FeatureList::IsEnabled(net::features::kSupportPartitionedBlobUrl)) {
550     blob_url_registry_intercept_hook =
551         base::BindRepeating(&content::BlobURLStoreInterceptor::Intercept,
552                             GURL("blob:" + target_origin + "/" + blob_path));
553     storage::BlobUrlRegistry::SetURLStoreCreationHookForTesting(
554         &blob_url_registry_intercept_hook);
555   } else {
556     blob_registry_impl_intercept_hook = base::BindRepeating(
557         &content::BlobURLStoreInterceptor::InterceptDeprecated,
558         GURL("blob:" + target_origin + "/" + blob_path));
559     storage::BlobRegistryImpl::SetURLStoreCreationHookForTesting(
560         &blob_registry_impl_intercept_hook);
561   }
562
563   content::RenderFrameHost* rfh = browser()
564                                       ->tab_strip_model()
565                                       ->GetActiveWebContents()
566                                       ->GetPrimaryMainFrame();
567
568   content::RenderProcessHostBadMojoMessageWaiter crash_observer(
569       rfh->GetProcess());
570
571   // The renderer should always get killed, but sometimes ExecJs returns
572   // true anyway, so just ignore the result.
573   std::ignore = content::ExecJs(rfh, "URL.createObjectURL(new Blob(['foo']))");
574
575   // If the process is killed, this test passes.
576   EXPECT_EQ(
577       "Received bad user message: "
578       "URL with invalid origin passed to BlobURLStore::Register",
579       crash_observer.Wait());
580 }
581
582 INSTANTIATE_TEST_SUITE_P(
583     ChromeSecurityExploitBrowserTestMojoBlobURLs,
584     ChromeSecurityExploitBrowserTestMojoBlobURLsP,
585     ::testing::Values(ChromeSecurityExploitBrowserTestMojoBlobURLsTestCase::
586                           kSupportPartitionedBlobUrlDisabled,
587                       ChromeSecurityExploitBrowserTestMojoBlobURLsTestCase::
588                           kSupportPartitionedBlobUrlEnabled));