| // Copyright 2013 The Chromium Authors |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include <tuple> |
| |
| #include "base/command_line.h" |
| #include "base/feature_list.h" |
| #include "base/functional/bind.h" |
| #include "base/functional/callback_helpers.h" |
| #include "base/memory/ptr_util.h" |
| #include "base/memory/raw_ptr.h" |
| #include "base/memory/weak_ptr.h" |
| #include "base/run_loop.h" |
| #include "base/strings/utf_string_conversions.h" |
| #include "base/test/metrics/histogram_tester.h" |
| #include "base/test/scoped_feature_list.h" |
| #include "base/unguessable_token.h" |
| #include "build/build_config.h" |
| #include "chrome/browser/extensions/extension_browsertest.h" |
| #include "chrome/browser/profiles/profile.h" |
| #include "chrome/browser/ui/browser.h" |
| #include "chrome/browser/ui/browser_commands.h" |
| #include "chrome/browser/ui/singleton_tabs.h" |
| #include "chrome/browser/ui/tabs/tab_strip_model.h" |
| #include "chrome/test/base/ui_test_utils.h" |
| #include "content/public/browser/blob_handle.h" |
| #include "content/public/browser/render_frame_host.h" |
| #include "content/public/browser/render_process_host.h" |
| #include "content/public/browser/site_isolation_policy.h" |
| #include "content/public/browser/web_contents_observer.h" |
| #include "content/public/common/content_switches.h" |
| #include "content/public/common/url_constants.h" |
| #include "content/public/test/browser_test.h" |
| #include "content/public/test/browser_test_utils.h" |
| #include "extensions/common/extension_urls.h" |
| #include "mojo/public/cpp/bindings/pending_remote.h" |
| #include "net/base/features.h" |
| #include "net/dns/mock_host_resolver.h" |
| #include "net/test/embedded_test_server/embedded_test_server.h" |
| #include "storage/browser/blob/blob_registry_impl.h" |
| #include "third_party/blink/public/common/blob/blob_utils.h" |
| #include "third_party/blink/public/common/features.h" |
| #include "third_party/blink/public/common/storage_key/storage_key.h" |
| #include "third_party/blink/public/mojom/blob/blob_url_store.mojom.h" |
| #include "url/gurl.h" |
| #include "url/origin.h" |
| |
| // The goal of these tests is to "simulate" exploited renderer processes, which |
| // can send arbitrary IPC messages and confuse browser process internal state, |
| // leading to security bugs. We are trying to verify that the browser doesn't |
| // perform any dangerous operations in such cases. |
| // This is similar to the security_exploit_browsertest.cc tests, but also |
| // includes chrome/ layer concepts such as extensions. |
| class ChromeSecurityExploitBrowserTest |
| : public extensions::ExtensionBrowserTest { |
| public: |
| ChromeSecurityExploitBrowserTest() {} |
| |
| ChromeSecurityExploitBrowserTest(const ChromeSecurityExploitBrowserTest&) = |
| delete; |
| ChromeSecurityExploitBrowserTest& operator=( |
| const ChromeSecurityExploitBrowserTest&) = delete; |
| |
| ~ChromeSecurityExploitBrowserTest() override {} |
| |
| void SetUpOnMainThread() override { |
| extensions::ExtensionBrowserTest::SetUpOnMainThread(); |
| |
| ASSERT_TRUE(embedded_test_server()->Start()); |
| host_resolver()->AddRule("*", "127.0.0.1"); |
| |
| extension_ = LoadExtension(test_data_dir_.AppendASCII("simple_with_icon")); |
| } |
| |
| void TearDownOnMainThread() override { |
| extension_ = nullptr; |
| extensions::ExtensionBrowserTest::TearDownOnMainThread(); |
| } |
| |
| const extensions::Extension* extension() { return extension_; } |
| |
| std::unique_ptr<content::BlobHandle> CreateMemoryBackedBlob( |
| const std::string& contents, |
| const std::string& content_type) { |
| std::unique_ptr<content::BlobHandle> result; |
| base::RunLoop loop; |
| profile()->CreateMemoryBackedBlob( |
| base::as_bytes(base::make_span(contents)), content_type, |
| base::BindOnce( |
| [](std::unique_ptr<content::BlobHandle>* out_blob, |
| base::OnceClosure done, |
| std::unique_ptr<content::BlobHandle> blob) { |
| *out_blob = std::move(blob); |
| std::move(done).Run(); |
| }, |
| &result, loop.QuitClosure())); |
| loop.Run(); |
| EXPECT_TRUE(result); |
| return result; |
| } |
| |
| private: |
| raw_ptr<const extensions::Extension> extension_ = nullptr; |
| }; |
| |
| // Subclass of ChromeSecurityExploitBrowserTest that uses --disable-web-security |
| // to simulate an exploited renderer. Note that this also disables some browser |
| // process checks, so it's not ideal for all exploit tests. |
| class ChromeWebSecurityDisabledBrowserTest |
| : public ChromeSecurityExploitBrowserTest { |
| public: |
| ChromeWebSecurityDisabledBrowserTest() {} |
| |
| ChromeWebSecurityDisabledBrowserTest( |
| const ChromeWebSecurityDisabledBrowserTest&) = delete; |
| ChromeWebSecurityDisabledBrowserTest& operator=( |
| const ChromeWebSecurityDisabledBrowserTest&) = delete; |
| |
| ~ChromeWebSecurityDisabledBrowserTest() override {} |
| |
| void SetUpCommandLine(base::CommandLine* command_line) override { |
| ChromeSecurityExploitBrowserTest::SetUpCommandLine(command_line); |
| command_line->AppendSwitch(switches::kDisableWebSecurity); |
| } |
| }; |
| |
| // TODO(nasko): This test as written is incompatible with Site Isolation |
| // restrictions, which disallow the cross-origin pushState call. |
| // Find a different way to implement issuing the illegal request or just |
| // delete the test if we have coverage elsewhere. See https://crbug.com/929161. |
| IN_PROC_BROWSER_TEST_F(ChromeWebSecurityDisabledBrowserTest, |
| DISABLED_ChromeExtensionResources) { |
| // Load a page that requests a chrome-extension:// image through XHR. We |
| // expect this load to fail, as it is an illegal request. |
| GURL foo = embedded_test_server()->GetURL("foo.com", |
| "/chrome_extension_resource.html"); |
| |
| content::DOMMessageQueue msg_queue( |
| browser()->tab_strip_model()->GetActiveWebContents()); |
| |
| ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), foo)); |
| |
| std::string status; |
| std::string expected_status("0"); |
| EXPECT_TRUE(msg_queue.WaitForMessage(&status)); |
| EXPECT_STREQ(status.c_str(), expected_status.c_str()); |
| } |
| |
| // Tests that a normal web process cannot send a commit for a Chrome Web Store |
| // URL. See https://crbug.com/172119. |
| IN_PROC_BROWSER_TEST_F(ChromeSecurityExploitBrowserTest, |
| CommitWebStoreURLInWebProcess) { |
| GURL foo = embedded_test_server()->GetURL("foo.com", "/title1.html"); |
| |
| content::WebContents* web_contents = |
| browser()->tab_strip_model()->GetActiveWebContents(); |
| content::RenderFrameHost* rfh = web_contents->GetPrimaryMainFrame(); |
| |
| // This IPC should result in a kill because the Chrome Web Store is not |
| // allowed to commit in |rfh->GetProcess()|. |
| base::HistogramTester histograms; |
| content::RenderProcessHostWatcher crash_observer( |
| rfh->GetProcess(), |
| content::RenderProcessHostWatcher::WATCH_FOR_PROCESS_EXIT); |
| |
| // Modify an IPC for a commit of a blank URL, which would otherwise be allowed |
| // to commit in any process. |
| GURL blank_url = GURL(url::kAboutBlankURL); |
| GURL webstore_url = extension_urls::GetWebstoreLaunchURL(); |
| content::PwnCommitIPC(web_contents, blank_url, webstore_url, |
| url::Origin::Create(GURL(webstore_url))); |
| web_contents->GetController().LoadURL( |
| blank_url, content::Referrer(), ui::PAGE_TRANSITION_LINK, std::string()); |
| |
| // If the process is killed in CanCommitURL, this test passes. |
| crash_observer.Wait(); |
| histograms.ExpectUniqueSample("Stability.BadMessageTerminated.Content", 1, 1); |
| } |
| |
| // Tests that a non-extension process cannot send a commit of a blank URL with |
| // an extension origin. |
| IN_PROC_BROWSER_TEST_F(ChromeSecurityExploitBrowserTest, |
| CommitExtensionOriginInWebProcess) { |
| GURL foo = embedded_test_server()->GetURL("foo.com", "/title1.html"); |
| |
| content::WebContents* web_contents = |
| browser()->tab_strip_model()->GetActiveWebContents(); |
| content::RenderFrameHost* rfh = web_contents->GetPrimaryMainFrame(); |
| |
| // This IPC should result in a kill because |ext_origin| is not allowed to |
| // commit in |rfh->GetProcess()|. |
| base::HistogramTester histograms; |
| content::RenderProcessHostWatcher crash_observer( |
| rfh->GetProcess(), |
| content::RenderProcessHostWatcher::WATCH_FOR_PROCESS_EXIT); |
| |
| // Modify an IPC for a commit of a blank URL, which would otherwise be allowed |
| // to commit in any process. |
| GURL blank_url = GURL(url::kAboutBlankURL); |
| std::string ext_origin = "chrome-extension://" + extension()->id(); |
| content::PwnCommitIPC(web_contents, blank_url, blank_url, |
| url::Origin::Create(GURL(ext_origin))); |
| web_contents->GetController().LoadURL( |
| blank_url, content::Referrer(), ui::PAGE_TRANSITION_LINK, std::string()); |
| |
| // If the process is killed in CanCommitOrigin, this test passes. |
| crash_observer.Wait(); |
| histograms.ExpectUniqueSample("Stability.BadMessageTerminated.Content", 114, |
| 1); |
| } |
| |
| // Tests that a non-extension process cannot send a commit of an extension URL. |
| IN_PROC_BROWSER_TEST_F(ChromeSecurityExploitBrowserTest, |
| CommitExtensionURLInWebProcess) { |
| GURL foo = embedded_test_server()->GetURL("foo.com", "/title1.html"); |
| |
| content::WebContents* web_contents = |
| browser()->tab_strip_model()->GetActiveWebContents(); |
| content::RenderFrameHost* rfh = web_contents->GetPrimaryMainFrame(); |
| |
| // This IPC should result in a kill because extension URLs are not allowed to |
| // commit in |rfh->GetProcess()|. |
| base::HistogramTester histograms; |
| content::RenderProcessHostWatcher crash_observer( |
| rfh->GetProcess(), |
| content::RenderProcessHostWatcher::WATCH_FOR_PROCESS_EXIT); |
| |
| // Modify an IPC for a commit of a blank URL, which would otherwise be allowed |
| // to commit in any process. |
| GURL blank_url = GURL(url::kAboutBlankURL); |
| std::string ext_origin = "chrome-extension://" + extension()->id(); |
| content::PwnCommitIPC(web_contents, blank_url, GURL(ext_origin), |
| url::Origin::Create(GURL(ext_origin))); |
| web_contents->GetController().LoadURL( |
| blank_url, content::Referrer(), ui::PAGE_TRANSITION_LINK, std::string()); |
| |
| // If the process is killed in CanCommitURL, this test passes. |
| crash_observer.Wait(); |
| histograms.ExpectUniqueSample("Stability.BadMessageTerminated.Content", 1, 1); |
| } |
| |
| // Tests that a non-extension process cannot send a commit of an extension |
| // filesystem URL. |
| IN_PROC_BROWSER_TEST_F(ChromeSecurityExploitBrowserTest, |
| CommitExtensionFilesystemURLInWebProcess) { |
| GURL foo = embedded_test_server()->GetURL("foo.com", "/title1.html"); |
| |
| content::WebContents* web_contents = |
| browser()->tab_strip_model()->GetActiveWebContents(); |
| content::RenderFrameHost* rfh = web_contents->GetPrimaryMainFrame(); |
| |
| // This IPC should result in a kill because extension filesystem URLs are not |
| // allowed to commit in |rfh->GetProcess()|. |
| base::HistogramTester histograms; |
| content::RenderProcessHostWatcher crash_observer( |
| rfh->GetProcess(), |
| content::RenderProcessHostWatcher::WATCH_FOR_PROCESS_EXIT); |
| |
| // Modify an IPC for a commit of a blank URL, which would otherwise be allowed |
| // to commit in any process. |
| GURL blank_url = GURL(url::kAboutBlankURL); |
| std::string ext_origin = "chrome-extension://" + extension()->id(); |
| content::PwnCommitIPC(web_contents, blank_url, |
| GURL("filesystem:" + ext_origin + "/foo"), |
| url::Origin::Create(GURL(ext_origin))); |
| web_contents->GetController().LoadURL( |
| blank_url, content::Referrer(), ui::PAGE_TRANSITION_LINK, std::string()); |
| |
| // If the process is killed in CanCommitURL, this test passes. |
| crash_observer.Wait(); |
| histograms.ExpectUniqueSample("Stability.BadMessageTerminated.Content", 1, 1); |
| } |
| |
| // chrome://xyz should not be able to create a "filesystem:chrome://abc" |
| // resource. |
| IN_PROC_BROWSER_TEST_F(ChromeSecurityExploitBrowserTest, |
| CreateFilesystemURLInOtherChromeUIOrigin) { |
| ASSERT_TRUE( |
| ui_test_utils::NavigateToURL(browser(), GURL("chrome://version"))); |
| |
| content::RenderFrameHost* rfh = browser() |
| ->tab_strip_model() |
| ->GetActiveWebContents() |
| ->GetPrimaryMainFrame(); |
| |
| // Block the renderer on operation that never completes, to shield it from |
| // receiving unexpected browser->renderer IPCs that might CHECK. |
| rfh->ExecuteJavaScriptWithUserGestureForTests( |
| u"var r = new XMLHttpRequest();" |
| u"r.open('GET', '/slow?99999', false);" |
| u"r.send(null);" |
| u"while (1);", |
| base::NullCallback()); |
| |
| std::string payload = "<p>Hello world!</p>"; |
| std::string payload_type = "text/html"; |
| |
| // Target an extension. |
| std::string target_origin = "chrome://downloads"; |
| |
| // Set up a blob ID and populate it with the attacker-controlled payload. This |
| // is just using the blob APIs directly since creating arbitrary blobs is not |
| // what is prohibited; this data is not in any origin. |
| std::unique_ptr<content::BlobHandle> blob = |
| CreateMemoryBackedBlob(payload, payload_type); |
| std::string blob_id = blob->GetUUID(); |
| |
| // Note: a well-behaved renderer would always send the following message here, |
| // but it's actually not necessary for the original attack to succeed, so we |
| // omit it. As a result there are some log warnings from the quota observer. |
| // |
| // IPC::IpcSecurityTestUtil::PwnMessageReceived( |
| // rfh->GetProcess()->GetChannel(), |
| // FileSystemHostMsg_OpenFileSystem(22, GURL(target_origin), |
| // storage::kFileSystemTypeTemporary)); |
| |
| GURL target_url = |
| GURL("filesystem:" + target_origin + "/temporary/exploit.html"); |
| |
| content::PwnMessageHelper::FileSystemCreate( |
| rfh->GetProcess(), 23, target_url, false, false, false, |
| blink::StorageKey::CreateFirstParty(url::Origin::Create(target_url))); |
| |
| // Write the blob into the file. If successful, this places an |
| // attacker-controlled value in a resource on the extension origin. |
| content::PwnMessageHelper::FileSystemWrite( |
| rfh->GetProcess(), 24, target_url, blob_id, 0, |
| blink::StorageKey::CreateFirstParty(url::Origin::Create(target_url))); |
| |
| // Now navigate to |target_url| in a new tab. It should not contain |payload|. |
| ASSERT_FALSE(AddTabAtIndex(0, target_url, ui::PAGE_TRANSITION_TYPED)); |
| EXPECT_FALSE(content::WaitForLoadStop( |
| browser()->tab_strip_model()->GetWebContentsAt(0))); |
| rfh = browser() |
| ->tab_strip_model() |
| ->GetActiveWebContents() |
| ->GetPrimaryMainFrame(); |
| |
| // If the attack is unsuccessful, the navigation ends up in an error |
| // page. |
| if (content::SiteIsolationPolicy::IsErrorPageIsolationEnabled( |
| !rfh->GetParent())) { |
| EXPECT_EQ(GURL(content::kUnreachableWebDataURL), |
| rfh->GetSiteInstance()->GetSiteURL()); |
| } else { |
| EXPECT_EQ(GURL(target_origin), rfh->GetSiteInstance()->GetSiteURL()); |
| } |
| std::string script = R"( |
| var textContent = document.body.innerText.replace(/\n+/g, '\n'); |
| textContent; |
| )"; |
| |
| std::string body = content::EvalJs(rfh, script).ExtractString(); |
| EXPECT_EQ( |
| "Your file couldn’t be accessed\n" |
| "It may have been moved, edited, or deleted.\n" |
| "ERR_FILE_NOT_FOUND", |
| body); |
| } |
| |
| // Extension isolation prevents a normal renderer process from being able to |
| // create a "filesystem:chrome-extension://sdgkjaghsdg/temporary/" resource. |
| IN_PROC_BROWSER_TEST_F(ChromeSecurityExploitBrowserTest, |
| CreateFilesystemURLInExtensionOrigin) { |
| GURL page_url = |
| embedded_test_server()->GetURL("a.root-servers.net", "/title1.html"); |
| ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), page_url)); |
| |
| content::RenderFrameHost* rfh = browser() |
| ->tab_strip_model() |
| ->GetActiveWebContents() |
| ->GetPrimaryMainFrame(); |
| |
| // Block the renderer on operation that never completes, to shield it from |
| // receiving unexpected browser->renderer IPCs that might CHECK. |
| rfh->ExecuteJavaScriptWithUserGestureForTests( |
| u"var r = new XMLHttpRequest();" |
| u"r.open('GET', '/slow?99999', false);" |
| u"r.send(null);" |
| u"while (1);", |
| base::NullCallback()); |
| |
| // JS code that the attacker would like to run in an extension process. |
| std::string payload = "<html><body>pwned.</body></html>"; |
| std::string payload_type = "text/html"; |
| |
| // Target an extension. |
| std::string target_origin = "chrome-extension://" + extension()->id(); |
| |
| // Set up a blob ID and populate it with the attacker-controlled payload. This |
| // is just using the blob APIs directly since creating arbitrary blobs is not |
| // what is prohibited; this data is not in any origin. |
| std::unique_ptr<content::BlobHandle> blob = |
| CreateMemoryBackedBlob(payload, payload_type); |
| std::string blob_id = blob->GetUUID(); |
| |
| // Note: a well-behaved renderer would always call Open first before calling |
| // Create and Write, but it's actually not necessary for the original attack |
| // to succeed, so we omit it. As a result there are some log warnings from the |
| // quota observer. |
| |
| GURL target_url = |
| GURL("filesystem:" + target_origin + "/temporary/exploit.html"); |
| |
| content::PwnMessageHelper::FileSystemCreate( |
| rfh->GetProcess(), 23, target_url, false, false, false, |
| blink::StorageKey::CreateFirstParty(url::Origin::Create(target_url))); |
| |
| // Write the blob into the file. If successful, this places an |
| // attacker-controlled value in a resource on the extension origin. |
| content::PwnMessageHelper::FileSystemWrite( |
| rfh->GetProcess(), 24, target_url, blob_id, 0, |
| blink::StorageKey::CreateFirstParty(url::Origin::Create(target_url))); |
| |
| // Now navigate to |target_url| in a new tab. It should not contain |payload|. |
| ASSERT_FALSE(AddTabAtIndex(0, target_url, ui::PAGE_TRANSITION_TYPED)); |
| EXPECT_FALSE(content::WaitForLoadStop( |
| browser()->tab_strip_model()->GetWebContentsAt(0))); |
| rfh = browser() |
| ->tab_strip_model() |
| ->GetActiveWebContents() |
| ->GetPrimaryMainFrame(); |
| |
| // If the attack is unsuccessful, the navigation ends up in an error |
| // page. |
| if (content::SiteIsolationPolicy::IsErrorPageIsolationEnabled( |
| !rfh->GetParent())) { |
| EXPECT_EQ(GURL(content::kUnreachableWebDataURL), |
| rfh->GetSiteInstance()->GetSiteURL()); |
| } else { |
| EXPECT_EQ(GURL(target_origin), rfh->GetSiteInstance()->GetSiteURL()); |
| } |
| std::string body; |
| std::string script = R"( |
| var textContent = document.body.innerText.replace(/\n+/g, '\n'); |
| textContent; |
| )"; |
| |
| body = content::EvalJs(rfh, script).ExtractString(); |
| EXPECT_EQ( |
| "Your file couldn’t be accessed\n" |
| "It may have been moved, edited, or deleted.\n" |
| "ERR_FILE_NOT_FOUND", |
| body); |
| } |
| |
| enum class ChromeSecurityExploitBrowserTestMojoBlobURLsTestCase { |
| kSupportPartitionedBlobUrlDisabled, |
| kSupportPartitionedBlobUrlEnabled, |
| }; |
| |
| class ChromeSecurityExploitBrowserTestMojoBlobURLsP |
| : public ChromeSecurityExploitBrowserTest, |
| public testing::WithParamInterface< |
| ChromeSecurityExploitBrowserTestMojoBlobURLsTestCase> { |
| public: |
| ChromeSecurityExploitBrowserTestMojoBlobURLsP() = default; |
| |
| void SetUp() override { |
| test_case_ = GetParam(); |
| scoped_feature_list_.InitWithFeatureState( |
| net::features::kSupportPartitionedBlobUrl, |
| test_case_ == ChromeSecurityExploitBrowserTestMojoBlobURLsTestCase:: |
| kSupportPartitionedBlobUrlEnabled); |
| |
| ChromeSecurityExploitBrowserTest::SetUp(); |
| } |
| |
| void TearDown() override { |
| if (base::FeatureList::IsEnabled( |
| net::features::kSupportPartitionedBlobUrl)) { |
| storage::BlobUrlRegistry::SetURLStoreCreationHookForTesting(nullptr); |
| } else { |
| storage::BlobRegistryImpl::SetURLStoreCreationHookForTesting(nullptr); |
| } |
| } |
| |
| private: |
| ChromeSecurityExploitBrowserTestMojoBlobURLsTestCase test_case_; |
| base::test::ScopedFeatureList scoped_feature_list_; |
| }; |
| |
| // Extension isolation prevents a normal renderer process from being able to |
| // create a "blob:chrome-extension://" resource. |
| IN_PROC_BROWSER_TEST_P(ChromeSecurityExploitBrowserTestMojoBlobURLsP, |
| CreateBlobInExtensionOrigin) { |
| // Target an extension. |
| std::string target_origin = "chrome-extension://" + extension()->id(); |
| std::string blob_path = "5881f76e-10d2-410d-8c61-ef210502acfd"; |
| |
| base::RepeatingCallback<void(storage::BlobUrlRegistry*, mojo::ReceiverId)> |
| blob_url_registry_intercept_hook; |
| base::RepeatingCallback<void( |
| mojo::SelfOwnedAssociatedReceiverRef<blink::mojom::BlobURLStore>)> |
| blob_registry_impl_intercept_hook; |
| |
| if (base::FeatureList::IsEnabled(net::features::kSupportPartitionedBlobUrl)) { |
| blob_url_registry_intercept_hook = |
| base::BindRepeating(&content::BlobURLStoreInterceptor::Intercept, |
| GURL("blob:" + target_origin + "/" + blob_path)); |
| storage::BlobUrlRegistry::SetURLStoreCreationHookForTesting( |
| &blob_url_registry_intercept_hook); |
| } else { |
| blob_registry_impl_intercept_hook = base::BindRepeating( |
| &content::BlobURLStoreInterceptor::InterceptDeprecated, |
| GURL("blob:" + target_origin + "/" + blob_path)); |
| storage::BlobRegistryImpl::SetURLStoreCreationHookForTesting( |
| &blob_registry_impl_intercept_hook); |
| } |
| |
| ASSERT_TRUE(ui_test_utils::NavigateToURL( |
| browser(), |
| embedded_test_server()->GetURL("a.root-servers.net", "/title1.html"))); |
| |
| content::RenderFrameHost* rfh = browser() |
| ->tab_strip_model() |
| ->GetActiveWebContents() |
| ->GetPrimaryMainFrame(); |
| |
| content::RenderProcessHostBadMojoMessageWaiter crash_observer( |
| rfh->GetProcess()); |
| |
| // The renderer should always get killed, but sometimes ExecJs returns |
| // true anyway, so just ignore the result. |
| std::ignore = content::ExecJs(rfh, "URL.createObjectURL(new Blob(['foo']))"); |
| |
| // If the process is killed, this test passes. |
| EXPECT_EQ( |
| "Received bad user message: " |
| "URL with invalid origin passed to BlobURLStore::Register", |
| crash_observer.Wait()); |
| } |
| |
| // chrome://xyz should not be able to create a "blob:chrome://abc" resource. |
| IN_PROC_BROWSER_TEST_P(ChromeSecurityExploitBrowserTestMojoBlobURLsP, |
| CreateBlobInOtherChromeUIOrigin) { |
| ASSERT_TRUE( |
| ui_test_utils::NavigateToURL(browser(), GURL("chrome://version"))); |
| |
| // All these are attacker controlled values. |
| std::string blob_type = "text/html"; |
| std::string blob_contents = "<p>Hello world!</p>"; |
| std::string blob_path = "f7dfbeb5-8e41-4c4a-8486-a52fed33c4c0"; |
| |
| // Target an extension. |
| std::string target_origin = "chrome://downloads"; |
| |
| base::RepeatingCallback<void(storage::BlobUrlRegistry*, mojo::ReceiverId)> |
| blob_url_registry_intercept_hook; |
| base::RepeatingCallback<void( |
| mojo::SelfOwnedAssociatedReceiverRef<blink::mojom::BlobURLStore>)> |
| blob_registry_impl_intercept_hook; |
| |
| if (base::FeatureList::IsEnabled(net::features::kSupportPartitionedBlobUrl)) { |
| blob_url_registry_intercept_hook = |
| base::BindRepeating(&content::BlobURLStoreInterceptor::Intercept, |
| GURL("blob:" + target_origin + "/" + blob_path)); |
| storage::BlobUrlRegistry::SetURLStoreCreationHookForTesting( |
| &blob_url_registry_intercept_hook); |
| } else { |
| blob_registry_impl_intercept_hook = base::BindRepeating( |
| &content::BlobURLStoreInterceptor::InterceptDeprecated, |
| GURL("blob:" + target_origin + "/" + blob_path)); |
| storage::BlobRegistryImpl::SetURLStoreCreationHookForTesting( |
| &blob_registry_impl_intercept_hook); |
| } |
| |
| content::RenderFrameHost* rfh = browser() |
| ->tab_strip_model() |
| ->GetActiveWebContents() |
| ->GetPrimaryMainFrame(); |
| |
| content::RenderProcessHostBadMojoMessageWaiter crash_observer( |
| rfh->GetProcess()); |
| |
| // The renderer should always get killed, but sometimes ExecJs returns |
| // true anyway, so just ignore the result. |
| std::ignore = content::ExecJs(rfh, "URL.createObjectURL(new Blob(['foo']))"); |
| |
| // If the process is killed, this test passes. |
| EXPECT_EQ( |
| "Received bad user message: " |
| "URL with invalid origin passed to BlobURLStore::Register", |
| crash_observer.Wait()); |
| } |
| |
| INSTANTIATE_TEST_SUITE_P( |
| ChromeSecurityExploitBrowserTestMojoBlobURLs, |
| ChromeSecurityExploitBrowserTestMojoBlobURLsP, |
| ::testing::Values(ChromeSecurityExploitBrowserTestMojoBlobURLsTestCase:: |
| kSupportPartitionedBlobUrlDisabled, |
| ChromeSecurityExploitBrowserTestMojoBlobURLsTestCase:: |
| kSupportPartitionedBlobUrlEnabled)); |