// 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/preloading/scoped_prewarm_feature_list.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/security_principal.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/isolated_world_ids.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_features.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 "storage/browser/blob/blob_url_registry.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() = default;

  ChromeSecurityExploitBrowserTest(const ChromeSecurityExploitBrowserTest&) =
      delete;
  ChromeSecurityExploitBrowserTest& operator=(
      const ChromeSecurityExploitBrowserTest&) = delete;

  ~ChromeSecurityExploitBrowserTest() override = default;

  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_byte_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:
  // TODO(https://crbug.com/423465927): Explore a better approach to make the
  // existing tests run with the prewarm feature enabled.
  test::ScopedPrewarmFeatureList scoped_prewarm_feature_list_{
      test::ScopedPrewarmFeatureList::PrewarmState::kDisabled};
  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() = default;

  ChromeWebSecurityDisabledBrowserTest(
      const ChromeWebSecurityDisabledBrowserTest&) = delete;
  ChromeWebSecurityDisabledBrowserTest& operator=(
      const ChromeWebSecurityDisabledBrowserTest&) = delete;

  ~ChromeWebSecurityDisabledBrowserTest() override = default;

  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/41439612.
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 an about:blank process cannot send a commit for the URL associated
// with the old Chrome Web Store hosted app. This is never allowed, but is
// blocked for different reasons when it corresponds to the webstore hosted
// app's page. See https://crbug.com/40076869.
// TODO(https://crbug.com/328494022): This test is moot without the webstore
// hosted app. Remove it once the app is no more.
IN_PROC_BROWSER_TEST_F(ChromeSecurityExploitBrowserTest,
                       CommitWebStoreHostedAppInAboutBlankProcess) {
  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.
  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();

  // No matter what, frames aren't allowed to commit to unexpected origins.
  // However, the exact reason for the bad message is different based on the
  // presence of the hosted app: if the hosted app is loaded, we expect
  // the reason to be `1`, which is `RFH_CAN_COMMIT_URL_BLOCKED`; if the hosted
  // app *isn't* loaded, it's `329`, which is `RFH_ORIGIN_TO_COMMIT_MISMATCH`.
  int expected_reason =
      base::FeatureList::IsEnabled(extensions_features::kWebstoreHostedApp)
          ? 1
          : 329;

  histograms.ExpectUniqueSample("Stability.BadMessageTerminated.Content",
                                expected_reason, 1);
}

class WebstoreSecurityExploitBrowserTest
    : public ChromeSecurityExploitBrowserTest,
      public testing::WithParamInterface<GURL> {};

// Tests that a normal web process cannot send a commit for a Chrome
// Web Store URL. See https://crbug.com/40076869.
IN_PROC_BROWSER_TEST_P(WebstoreSecurityExploitBrowserTest,
                       CommitWebStoreURLInWebProcess) {
  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 URL to a normal web page. This should fail due to
  // normal site isolation.
  GURL foo = embedded_test_server()->GetURL("foo.com", "/title1.html");
  GURL webstore_url = GetParam();
  content::PwnCommitIPC(web_contents, foo, webstore_url,
                        url::Origin::Create(GURL(webstore_url)));
  web_contents->GetController().LoadURL(
      foo, 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 cross-origin same-site process cannot send a commit for a Chrome
// Web Store URL.
IN_PROC_BROWSER_TEST_P(WebstoreSecurityExploitBrowserTest,
                       CommitWebStoreURLInSameSiteProcess) {
  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 URL which is cross-origin, but same-site as the
  // Web Store. This should fail as the Web Store origin is specifically
  // isolated from the rest of the site.
  GURL fake = embedded_test_server()->GetURL("fakechromewebstore.google.com",
                                             "/title1.html");
  GURL webstore_url = GetParam();
  content::PwnCommitIPC(web_contents, fake, webstore_url,
                        url::Origin::Create(GURL(webstore_url)));
  web_contents->GetController().LoadURL(
      fake, 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);
}

INSTANTIATE_TEST_SUITE_P(
    WebstoreNewURL,
    WebstoreSecurityExploitBrowserTest,
    testing::Values(GURL("https://chromewebstore.google.com/")));
INSTANTIATE_TEST_SUITE_P(
    WebstoreHostedAppURL,
    WebstoreSecurityExploitBrowserTest,
    testing::Values(GURL("https://chrome.google.com/webstore")));

// 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(), content::ISOLATED_WORLD_ID_GLOBAL);

  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();

  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()->GetSecurityPrincipal().GetDeprecatedSiteURL());
  } else {
    EXPECT_EQ(
        GURL(target_origin),
        rfh->GetSiteInstance()->GetSecurityPrincipal().GetDeprecatedSiteURL());
  }
  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(), content::ISOLATED_WORLD_ID_GLOBAL);

  // 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()->GetSecurityPrincipal().GetDeprecatedSiteURL());
  } else {
    EXPECT_EQ(
        GURL(target_origin),
        rfh->GetSiteInstance()->GetSecurityPrincipal().GetDeprecatedSiteURL());
  }
  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);
}

class ChromeSecurityExploitBrowserTestMojoBlobURLs
    : public ChromeSecurityExploitBrowserTest {
 public:
  ChromeSecurityExploitBrowserTestMojoBlobURLs() = default;

  void TearDown() override {
    storage::BlobUrlRegistry::SetURLStoreCreationHookForTesting(nullptr);
  }
};

// Extension isolation prevents a normal renderer process from being able to
// create a "blob:chrome-extension://" resource.
IN_PROC_BROWSER_TEST_F(ChromeSecurityExploitBrowserTestMojoBlobURLs,
                       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;

  blob_url_registry_intercept_hook =
      base::BindRepeating(&content::BlobURLStoreInterceptor::Intercept,
                          GURL("blob:" + target_origin + "/" + blob_path));
  storage::BlobUrlRegistry::SetURLStoreCreationHookForTesting(
      &blob_url_registry_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_F(ChromeSecurityExploitBrowserTestMojoBlobURLs,
                       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;

  blob_url_registry_intercept_hook =
      base::BindRepeating(&content::BlobURLStoreInterceptor::Intercept,
                          GURL("blob:" + target_origin + "/" + blob_path));
  storage::BlobUrlRegistry::SetURLStoreCreationHookForTesting(
      &blob_url_registry_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());
}
