// Copyright (c) 2013 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include <stdint.h>

#include "base/bind.h"
#include "base/command_line.h"
#include "base/feature_list.h"
#include "base/macros.h"
#include "base/optional.h"
#include "base/strings/utf_string_conversions.h"
#include "base/task/post_task.h"
#include "base/test/scoped_feature_list.h"
#include "build/build_config.h"
#include "content/browser/bad_message.h"
#include "content/browser/child_process_security_policy_impl.h"
#include "content/browser/dom_storage/dom_storage_context_wrapper.h"
#include "content/browser/dom_storage/session_storage_namespace_impl.h"
#include "content/browser/frame_host/navigator.h"
#include "content/browser/frame_host/render_frame_host_impl.h"
#include "content/browser/frame_host/render_frame_proxy_host.h"
#include "content/browser/renderer_host/render_process_host_impl.h"
#include "content/browser/renderer_host/render_view_host_factory.h"
#include "content/browser/renderer_host/render_view_host_impl.h"
#include "content/browser/web_contents/web_contents_impl.h"
#include "content/common/frame.mojom.h"
#include "content/common/frame_messages.h"
#include "content/common/render_message_filter.mojom.h"
#include "content/common/view_messages.h"
#include "content/public/browser/blob_handle.h"
#include "content/public/browser/browser_context.h"
#include "content/public/browser/browser_task_traits.h"
#include "content/public/browser/browser_thread.h"
#include "content/public/browser/content_browser_client.h"
#include "content/public/browser/file_select_listener.h"
#include "content/public/browser/interstitial_page.h"
#include "content/public/browser/interstitial_page_delegate.h"
#include "content/public/browser/resource_context.h"
#include "content/public/browser/storage_partition.h"
#include "content/public/common/content_features.h"
#include "content/public/common/content_switches.h"
#include "content/public/common/navigation_policy.h"
#include "content/public/common/url_constants.h"
#include "content/public/test/browser_test_utils.h"
#include "content/public/test/content_browser_test.h"
#include "content/public/test/content_browser_test_utils.h"
#include "content/public/test/navigation_handle_observer.h"
#include "content/public/test/test_navigation_observer.h"
#include "content/public/test/test_utils.h"
#include "content/shell/browser/shell.h"
#include "content/test/content_browser_test_utils_internal.h"
#include "content/test/did_commit_navigation_interceptor.h"
#include "content/test/frame_host_interceptor.h"
#include "content/test/mock_widget_impl.h"
#include "content/test/test_content_browser_client.h"
#include "ipc/ipc_security_test_util.h"
#include "mojo/core/embedder/embedder.h"
#include "mojo/public/cpp/bindings/pending_associated_remote.h"
#include "mojo/public/cpp/bindings/pending_remote.h"
#include "mojo/public/cpp/bindings/remote.h"
#include "mojo/public/cpp/bindings/strong_associated_binding.h"
#include "mojo/public/cpp/test_support/test_utils.h"
#include "net/base/filename_util.h"
#include "net/base/network_isolation_key.h"
#include "net/dns/mock_host_resolver.h"
#include "net/test/embedded_test_server/controllable_http_response.h"
#include "net/test/embedded_test_server/embedded_test_server.h"
#include "net/test/embedded_test_server/http_request.h"
#include "net/test/url_request/url_request_slow_download_job.h"
#include "net/traffic_annotation/network_traffic_annotation_test_helper.h"
#include "services/network/public/cpp/features.h"
#include "services/network/public/cpp/network_switches.h"
#include "services/network/public/cpp/resource_request.h"
#include "services/network/public/mojom/url_loader.mojom.h"
#include "services/network/test/test_url_loader_client.h"
#include "storage/browser/blob/blob_registry_impl.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.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/navigation/triggering_event_info.h"
#include "third_party/blink/public/mojom/appcache/appcache.mojom.h"
#include "third_party/blink/public/mojom/blob/blob_url_store.mojom-test-utils.h"
#include "third_party/blink/public/mojom/choosers/file_chooser.mojom.h"

using IPC::IpcSecurityTestUtil;

namespace content {

namespace {

// This request id is used by tests that call CreateLoaderAndStart. The id is
// sufficiently large that it doesn't collide with ids used by previous
// navigation requests.
const int kRequestIdNotPreviouslyUsed = 10000;

// This is a helper function for the tests which attempt to create a
// duplicate RenderViewHost or RenderWidgetHost. It tries to create two objects
// with the same process and routing ids, which causes a collision.
// It creates a couple of windows in process 1, which causes a few routing ids
// to be allocated. Then a cross-process navigation is initiated, which causes a
// new process 2 to be created and have a pending RenderViewHost for it. The
// routing id of the RenderViewHost which is target for a duplicate is set
// into |target_routing_id| and the pending RenderFrameHost which is used for
// the attempt is the return value.
RenderFrameHostImpl* PrepareToDuplicateHosts(Shell* shell,
                                             net::EmbeddedTestServer* server,
                                             int* target_routing_id) {
  GURL foo("http://foo.com/simple_page.html");

  if (AreDefaultSiteInstancesEnabled()) {
    // Isolate "bar.com" so we are guaranteed to get a different process
    // for navigations to this origin.
    IsolateOriginsForTesting(server, shell->web_contents(), {"bar.com"});
  }

  // Start off with initial navigation, so we get the first process allocated.
  EXPECT_TRUE(NavigateToURL(shell, foo));
  EXPECT_EQ(base::ASCIIToUTF16("OK"), shell->web_contents()->GetTitle());

  // Open another window, so we generate some more routing ids.
  ShellAddedObserver shell2_observer;
  EXPECT_TRUE(ExecuteScript(shell, "window.open(document.URL + '#2');"));
  Shell* shell2 = shell2_observer.GetShell();

  // The new window must be in the same process, but have a new routing id.
  EXPECT_EQ(shell->web_contents()->GetMainFrame()->GetProcess()->GetID(),
            shell2->web_contents()->GetMainFrame()->GetProcess()->GetID());
  *target_routing_id =
      shell2->web_contents()->GetRenderViewHost()->GetRoutingID();
  EXPECT_NE(*target_routing_id,
            shell->web_contents()->GetRenderViewHost()->GetRoutingID());

  // Now, simulate a link click coming from the renderer.
  GURL extension_url("http://bar.com/simple_page.html");
  WebContentsImpl* wc = static_cast<WebContentsImpl*>(shell->web_contents());
  wc->GetFrameTree()->root()->navigator()->RequestOpenURL(
      wc->GetFrameTree()->root()->current_frame_host(), extension_url,
      url::Origin::Create(foo), nullptr, std::string(), Referrer(),
      WindowOpenDisposition::CURRENT_TAB, false, true,
      blink::TriggeringEventInfo::kFromTrustedEvent, std::string(),
      nullptr /* blob_url_loader_factory */);

  // Since the navigation above requires a cross-process swap, there will be a
  // speculative/pending RenderFrameHost. Ensure it exists and is in a different
  // process than the initial page.
  RenderFrameHostImpl* next_rfh =
      wc->GetRenderManagerForTesting()->speculative_frame_host();

  EXPECT_TRUE(next_rfh);
  EXPECT_NE(shell->web_contents()->GetMainFrame()->GetProcess()->GetID(),
            next_rfh->GetProcess()->GetID());

  return next_rfh;
}

network::ResourceRequest CreateXHRRequest(const char* url) {
  network::ResourceRequest request;
  request.method = "GET";
  request.url = GURL(url);
  request.referrer_policy = Referrer::GetDefaultReferrerPolicy();
  request.request_initiator = url::Origin();
  request.load_flags = 0;
  request.resource_type = static_cast<int>(ResourceType::kXhr);
  request.should_reset_appcache = false;
  request.is_main_frame = true;
  request.transition_type = ui::PAGE_TRANSITION_LINK;
  return request;
}

std::unique_ptr<content::BlobHandle> CreateMemoryBackedBlob(
    BrowserContext* browser_context,
    const std::string& contents,
    const std::string& content_type) {
  std::unique_ptr<content::BlobHandle> result;
  base::RunLoop loop;
  BrowserContext::CreateMemoryBackedBlob(
      browser_context, contents.c_str(), contents.length(), 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;
}

// Helper class to interpose on Blob URL registrations, replacing the URL
// contained in incoming registration requests with the specified URL.
class BlobURLStoreInterceptor
    : public blink::mojom::BlobURLStoreInterceptorForTesting {
 public:
  static void Intercept(
      GURL target_url,
      mojo::StrongAssociatedBindingPtr<blink::mojom::BlobURLStore> binding) {
    auto interceptor =
        base::WrapUnique(new BlobURLStoreInterceptor(target_url));
    auto* raw_interceptor = interceptor.get();
    auto impl = binding->SwapImplForTesting(std::move(interceptor));
    raw_interceptor->url_store_ = std::move(impl);
  }

  blink::mojom::BlobURLStore* GetForwardingInterface() override {
    return url_store_.get();
  }

  void Register(mojo::PendingRemote<blink::mojom::Blob> blob,
                const GURL& url,
                RegisterCallback callback) override {
    GetForwardingInterface()->Register(std::move(blob), target_url_,
                                       std::move(callback));
  }

 private:
  explicit BlobURLStoreInterceptor(GURL target_url) : target_url_(target_url) {}

  std::unique_ptr<blink::mojom::BlobURLStore> url_store_;
  GURL target_url_;
};

// Constructs a WebContentsDelegate that mocks a file dialog.
// Unlike content::FileChooserDelegate, this class doesn't make a response in
// RunFileChooser(), and a user needs to call Choose().
class DelayedFileChooserDelegate : public WebContentsDelegate {
 public:
  void Choose(const base::FilePath& file) {
    auto file_info = blink::mojom::FileChooserFileInfo::NewNativeFile(
        blink::mojom::NativeFileInfo::New(file, base::string16()));
    std::vector<blink::mojom::FileChooserFileInfoPtr> files;
    files.push_back(std::move(file_info));
    listener_->FileSelected(std::move(files), base::FilePath(),
                            blink::mojom::FileChooserParams::Mode::kOpen);
    listener_.reset();
  }

  // WebContentsDelegate overrides
  void RunFileChooser(RenderFrameHost* render_frame_host,
                      std::unique_ptr<FileSelectListener> listener,
                      const blink::mojom::FileChooserParams& params) override {
    listener_ = std::move(listener);
  }

  void EnumerateDirectory(WebContents* web_contents,
                          std::unique_ptr<FileSelectListener> listener,
                          const base::FilePath& directory_path) override {
    listener->FileSelectionCanceled();
  }

 private:
  std::unique_ptr<FileSelectListener> listener_;
};

void FileChooserCallback(base::RunLoop* run_loop,
                         blink::mojom::FileChooserResultPtr result) {
  run_loop->Quit();
}

}  // namespace

// The goal of these tests will be 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.
class SecurityExploitBrowserTest : public ContentBrowserTest {
 public:
  SecurityExploitBrowserTest() {}

  void SetUpCommandLine(base::CommandLine* command_line) override {
    // EmbeddedTestServer::InitializeAndListen() initializes its |base_url_|
    // which is required below. This cannot invoke Start() however as that kicks
    // off the "EmbeddedTestServer IO Thread" which then races with
    // initialization in ContentBrowserTest::SetUp(), http://crbug.com/674545.
    ASSERT_TRUE(embedded_test_server()->InitializeAndListen());

    // Add a host resolver rule to map all outgoing requests to the test server.
    // This allows us to use "real" hostnames in URLs, which we can use to
    // create arbitrary SiteInstances.
    command_line->AppendSwitchASCII(
        network::switches::kHostResolverRules,
        "MAP * " +
            net::HostPortPair::FromURL(embedded_test_server()->base_url())
                .ToString() +
            ",EXCLUDE localhost");
  }

  void SetUpOnMainThread() override {
    // Complete the manual Start() after ContentBrowserTest's own
    // initialization, ref. comment on InitializeAndListen() above.
    embedded_test_server()->StartAcceptingConnections();

    base::PostTask(
        FROM_HERE, {BrowserThread::IO},
        base::BindOnce(&net::URLRequestSlowDownloadJob::AddUrlHandler));
  }

  static void CreateLoaderAndStart(
      RenderFrameHost* frame,
      int route_id,
      int request_id,
      const network::ResourceRequest& resource_request) {
    network::mojom::URLLoaderPtr loader;
    network::TestURLLoaderClient client;
    CreateLoaderAndStart(frame, mojo::MakeRequest(&loader), route_id,
                         request_id, resource_request,
                         client.CreateInterfacePtr().PassInterface());
  }

  static void CreateLoaderAndStart(
      RenderFrameHost* frame,
      network::mojom::URLLoaderRequest request,
      int route_id,
      int request_id,
      const network::ResourceRequest& resource_request,
      network::mojom::URLLoaderClientPtrInfo client) {
    mojo::Remote<network::mojom::URLLoaderFactory> factory;
    frame->GetProcess()->CreateURLLoaderFactory(
        frame->GetLastCommittedOrigin(),
        network::mojom::CrossOriginEmbedderPolicy::kNone,
        nullptr /* preferences */, net::NetworkIsolationKey(),
        mojo::NullRemote() /* header_client */,
        factory.BindNewPipeAndPassReceiver());
    factory->CreateLoaderAndStart(
        std::move(request), route_id, request_id,
        network::mojom::kURLLoadOptionNone, resource_request,
        network::mojom::URLLoaderClientPtr(std::move(client)),
        net::MutableNetworkTrafficAnnotationTag(TRAFFIC_ANNOTATION_FOR_TESTS));
  }

  void TryCreateDuplicateRequestIds(Shell* shell, bool block_loaders) {
    EXPECT_TRUE(NavigateToURL(shell, GURL("http://foo.com/simple_page.html")));
    RenderFrameHostImpl* rfh = static_cast<RenderFrameHostImpl*>(
        shell->web_contents()->GetMainFrame());

    if (block_loaders) {
      // Test the case where loaders are placed into blocked_loaders_map_.
      rfh->BlockRequestsForFrame();
    }

    // URLRequestSlowDownloadJob waits for another request to kFinishDownloadUrl
    // to finish all pending requests. It is never sent, so the following URL
    // blocks indefinitely, which is good because the request stays alive and
    // the test can try to reuse the request id without a race.
    const char* blocking_url = net::URLRequestSlowDownloadJob::kUnknownSizeUrl;
    network::ResourceRequest request(CreateXHRRequest(blocking_url));

    // Use the same request id twice.
    RenderProcessHostKillWaiter kill_waiter(rfh->GetProcess());
    // We need to keep loader and client to keep the requests alive.
    network::mojom::URLLoaderPtr loader1, loader2;
    network::TestURLLoaderClient client1, client2;

    CreateLoaderAndStart(rfh, mojo::MakeRequest(&loader1), rfh->GetRoutingID(),
                         kRequestIdNotPreviouslyUsed, request,
                         client1.CreateInterfacePtr().PassInterface());
    CreateLoaderAndStart(rfh, mojo::MakeRequest(&loader2), rfh->GetRoutingID(),
                         kRequestIdNotPreviouslyUsed, request,
                         client2.CreateInterfacePtr().PassInterface());
    EXPECT_EQ(bad_message::RDH_INVALID_REQUEST_ID, kill_waiter.Wait());
  }

 protected:
  // Tests that a given file path sent in a FrameHostMsg_RunFileChooser will
  // cause renderer to be killed.
  void TestFileChooserWithPath(const base::FilePath& path);

  void IsolateOrigin(const std::string& hostname) {
    IsolateOriginsForTesting(embedded_test_server(), shell()->web_contents(),
                             {hostname});
  }
};

void SecurityExploitBrowserTest::TestFileChooserWithPath(
    const base::FilePath& path) {
  GURL foo("http://foo.com/simple_page.html");
  EXPECT_TRUE(NavigateToURL(shell(), foo));
  EXPECT_EQ(base::ASCIIToUTF16("OK"), shell()->web_contents()->GetTitle());

  RenderFrameHost* compromised_renderer =
      shell()->web_contents()->GetMainFrame();
  blink::mojom::FileChooserParamsPtr params =
      blink::mojom::FileChooserParams::New();
  params->default_file_name = path;

  mojo::test::BadMessageObserver bad_message_observer;
  mojo::Remote<blink::mojom::FileChooser> factory =
      static_cast<RenderFrameHostImpl*>(compromised_renderer)
          ->BindFileChooserForTesting();
  factory->OpenFileChooser(
      std::move(params), blink::mojom::FileChooser::OpenFileChooserCallback());
  factory.FlushForTesting();
  EXPECT_THAT(bad_message_observer.WaitForBadMessage(),
              ::testing::StartsWith("FileChooser: The default file name"));
}

// Ensure that we kill the renderer process if we try to give it WebUI
// properties and it doesn't have enabled WebUI bindings.
IN_PROC_BROWSER_TEST_F(SecurityExploitBrowserTest, SetWebUIProperty) {
  GURL foo("http://foo.com/simple_page.html");

  EXPECT_TRUE(NavigateToURL(shell(), foo));
  EXPECT_EQ(base::ASCIIToUTF16("OK"), shell()->web_contents()->GetTitle());
  EXPECT_EQ(0, shell()->web_contents()->GetMainFrame()->GetEnabledBindings());

  RenderViewHost* compromised_renderer =
      shell()->web_contents()->GetRenderViewHost();
  RenderProcessHostKillWaiter kill_waiter(compromised_renderer->GetProcess());
  compromised_renderer->SetWebUIProperty("toolkit", "views");
  EXPECT_EQ(bad_message::RVH_WEB_UI_BINDINGS_MISMATCH, kill_waiter.Wait());
}

// This is a test for crbug.com/312016 attempting to create duplicate
// RenderViewHosts. SetupForDuplicateHosts sets up this test case and leaves
// it in a state with pending RenderViewHost. Before the commit of the new
// pending RenderViewHost, this test case creates a new window through the new
// process.
IN_PROC_BROWSER_TEST_F(SecurityExploitBrowserTest,
                       AttemptDuplicateRenderViewHost) {
  int32_t duplicate_routing_id = MSG_ROUTING_NONE;
  RenderFrameHostImpl* pending_rfh = PrepareToDuplicateHosts(
      shell(), embedded_test_server(), &duplicate_routing_id);
  EXPECT_NE(MSG_ROUTING_NONE, duplicate_routing_id);

  mojom::CreateNewWindowParamsPtr params = mojom::CreateNewWindowParams::New();
  params->target_url = GURL("about:blank");
  pending_rfh->CreateNewWindow(
      std::move(params), base::BindOnce([](mojom::CreateNewWindowStatus,
                                           mojom::CreateNewWindowReplyPtr) {}));
  // If the above operation doesn't cause a crash, the test has succeeded!
}

// This is a test for crbug.com/312016. It tries to create two RenderWidgetHosts
// with the same process and routing ids, which causes a collision. It is almost
// identical to the AttemptDuplicateRenderViewHost test case.
// Crashes on all platforms.  http://crbug.com/939338
IN_PROC_BROWSER_TEST_F(SecurityExploitBrowserTest,
                       DISABLED_AttemptDuplicateRenderWidgetHost) {
  int duplicate_routing_id = MSG_ROUTING_NONE;
  RenderFrameHostImpl* pending_rfh = PrepareToDuplicateHosts(
      shell(), embedded_test_server(), &duplicate_routing_id);
  EXPECT_NE(MSG_ROUTING_NONE, duplicate_routing_id);

  mojo::PendingRemote<mojom::Widget> widget;
  std::unique_ptr<MockWidgetImpl> widget_impl =
      std::make_unique<MockWidgetImpl>(widget.InitWithNewPipeAndPassReceiver());

  // Since this test executes on the UI thread and hopping threads might cause
  // different timing in the test, let's simulate a CreateNewWidget call coming
  // from the IO thread.  Use the existing window routing id to cause a
  // deliberate collision.
  pending_rfh->render_view_host()->CreateNewWidget(duplicate_routing_id,
                                                   std::move(widget));

  // If the above operation doesn't crash, the test has succeeded!
}

// This is a test for crbug.com/444198. It tries to send a
// FrameHostMsg_RunFileChooser containing an invalid path. The browser should
// correctly terminate the renderer in these cases.
IN_PROC_BROWSER_TEST_F(SecurityExploitBrowserTest, AttemptRunFileChoosers) {
  TestFileChooserWithPath(base::FilePath(FILE_PATH_LITERAL("../../*.txt")));
  TestFileChooserWithPath(base::FilePath(FILE_PATH_LITERAL("/etc/*.conf")));
#if defined(OS_WIN)
  TestFileChooserWithPath(
      base::FilePath(FILE_PATH_LITERAL("\\\\evilserver\\evilshare\\*.txt")));
  TestFileChooserWithPath(base::FilePath(FILE_PATH_LITERAL("c:\\*.txt")));
  TestFileChooserWithPath(base::FilePath(FILE_PATH_LITERAL("..\\..\\*.txt")));
#endif
}

// A test for crbug.com/941008.
// Calling OpenFileChooser() and EnumerateChosenDirectory() for a single
// FileChooser instance had a problem.
IN_PROC_BROWSER_TEST_F(SecurityExploitBrowserTest, UnexpectedMethodsSequence) {
  EXPECT_TRUE(NavigateToURL(shell(), GURL("http://foo.com/simple_page.html")));
  RenderFrameHost* compromised_renderer =
      shell()->web_contents()->GetMainFrame();
  auto delegate = std::make_unique<DelayedFileChooserDelegate>();
  shell()->web_contents()->SetDelegate(delegate.get());

  mojo::Remote<blink::mojom::FileChooser> chooser =
      static_cast<RenderFrameHostImpl*>(compromised_renderer)
          ->BindFileChooserForTesting();
  base::RunLoop run_loop1;
  base::RunLoop run_loop2;
  chooser->OpenFileChooser(blink::mojom::FileChooserParams::New(),
                           base::BindOnce(FileChooserCallback, &run_loop2));
  // The following EnumerateChosenDirectory() runs the specified callback
  // immediately regardless of the content of the first argument FilePath.
  chooser->EnumerateChosenDirectory(
      base::FilePath(FILE_PATH_LITERAL(":*?\"<>|")),
      base::BindOnce(FileChooserCallback, &run_loop1));
  run_loop1.Run();

  delegate->Choose(base::FilePath(FILE_PATH_LITERAL("foo.txt")));
  run_loop2.Run();

  // The test passes if it doesn't crash.
}

class SecurityExploitTestInterstitialPage : public InterstitialPageDelegate {
 public:
  explicit SecurityExploitTestInterstitialPage(WebContents* contents) {
    InterstitialPage* interstitial = InterstitialPage::Create(
        contents, true, contents->GetLastCommittedURL(), this);
    interstitial->Show();
  }

  // InterstitialPageDelegate implementation.
  void CommandReceived(const std::string& command) override {
    last_command_ = command;
  }

  std::string GetHTMLContents() override {
    return "<html><head><script>"
           "window.domAutomationController.send(\"okay\");"
           "</script></head>"
           "<body>this page is an interstitial</body></html>";
  }

  std::string last_command() { return last_command_; }

 private:
  std::string last_command_;
  DISALLOW_COPY_AND_ASSIGN(SecurityExploitTestInterstitialPage);
};

// Fails due to InterstitialPage's reliance on PostNonNestableTask
// http://crbug.com/432737
#if defined(OS_ANDROID)
#define MAYBE_InterstitialCommandFromUnderlyingContent \
  DISABLED_InterstitialCommandFromUnderlyingContent
#else
#define MAYBE_InterstitialCommandFromUnderlyingContent \
  InterstitialCommandFromUnderlyingContent
#endif

// The interstitial should not be controllable by the underlying content.
IN_PROC_BROWSER_TEST_F(SecurityExploitBrowserTest,
                       MAYBE_InterstitialCommandFromUnderlyingContent) {
  // Start off with initial navigation, to allocate the process.
  GURL foo("http://foo.com/simple_page.html");
  EXPECT_TRUE(NavigateToURL(shell(), foo));
  EXPECT_EQ(base::ASCIIToUTF16("OK"), shell()->web_contents()->GetTitle());

  DOMMessageQueue message_queue;

  // Install and show an interstitial page.
  SecurityExploitTestInterstitialPage* interstitial =
      new SecurityExploitTestInterstitialPage(shell()->web_contents());

  ASSERT_EQ("", interstitial->last_command());
  WaitForInterstitialAttach(shell()->web_contents());

  InterstitialPage* interstitial_page =
      shell()->web_contents()->GetInterstitialPage();
  ASSERT_TRUE(interstitial_page != nullptr);
  ASSERT_TRUE(shell()->web_contents()->ShowingInterstitialPage());
  ASSERT_TRUE(interstitial_page->GetDelegateForTesting() == interstitial);

  // The interstitial page ought to be able to send a message.
  std::string message;
  ASSERT_TRUE(message_queue.WaitForMessage(&message));
  ASSERT_EQ("\"okay\"", message);
  ASSERT_EQ("\"okay\"", interstitial->last_command());

  // Send an automation message from the underlying content and wait for it to
  // be dispatched on this thread. This message should not be received by the
  // interstitial.
  RenderFrameHost* compromised_renderer =
      shell()->web_contents()->GetMainFrame();
  FrameHostMsg_DomOperationResponse evil(compromised_renderer->GetRoutingID(),
                                         "evil");
  IpcSecurityTestUtil::PwnMessageReceived(
      compromised_renderer->GetProcess()->GetChannel(), evil);

  ASSERT_TRUE(message_queue.WaitForMessage(&message));
  ASSERT_EQ("evil", message)
      << "Automation message should be received by WebContents.";
  ASSERT_EQ("\"okay\"", interstitial->last_command())
      << "Interstitial should not be affected.";

  // Send a second message from the interstitial page, and make sure that the
  // "evil" message doesn't arrive in the intervening period.
  ExecuteScriptAsync(interstitial_page->GetMainFrame(),
                     "window.domAutomationController.send(\"okay2\");");
  ASSERT_TRUE(message_queue.WaitForMessage(&message));
  ASSERT_EQ("\"okay2\"", message);
  ASSERT_EQ("\"okay2\"", interstitial->last_command());
}

class CorsExploitBrowserTest : public ContentBrowserTest {
 public:
  CorsExploitBrowserTest() {
    feature_list_.InitAndEnableFeature(
        blink::features::kHtmlImportsRequestInitiatorLock);
  }

  void SetUpCommandLine(base::CommandLine* command_line) override {
    // TODO(yoichio): This is temporary switch to support chrome internal
    // components migration from the old web APIs.
    // After completion of the migration, we should remove this.
    // See https://crbug.com/911943 for detail.
    command_line->AppendSwitchASCII("enable-blink-features", "HTMLImports");
  }

  void SetUpOnMainThread() override {
    host_resolver()->AddRule("*", "127.0.0.1");
    SetupCrossSiteRedirector(embedded_test_server());
  }

 private:
  base::test::ScopedFeatureList feature_list_;

  DISALLOW_COPY_AND_ASSIGN(CorsExploitBrowserTest);
};

// This is a regression test for https://crbug.com/961614 - it makes sure that
// the trustworthy |request_initiator_site_lock| takes precedent over
// the untrustworthy |request.request_initiator|.
//
// For spoofing a |request.request_initiator| that doesn't match
// |request_initiator_site_lock|, the test relies on a misfeature of HTML
// Imports.  It is unclear how to replicate such spoofing once HTML imports are
// deprecated.
IN_PROC_BROWSER_TEST_F(CorsExploitBrowserTest,
                       OriginHeaderSpoofViaHtmlImports) {
  std::string victim_path = "/victim/secret.json";
  net::test_server::ControllableHttpResponse victim_response(
      embedded_test_server(), victim_path, false);

  ASSERT_TRUE(embedded_test_server()->Start());
  GURL attacker_url(
      embedded_test_server()->GetURL("attacker.com", "/title1.html"));
  GURL module_url(embedded_test_server()->GetURL(
      "module.com", "/cross_site_document_blocking/html_import3.html"));
  GURL victim_url(embedded_test_server()->GetURL("victim.com", victim_path));

  EXPECT_TRUE(NavigateToURL(shell(), attacker_url));

  // From a renderer process locked to attacker.com, load a HTML Import from
  // module.com.  HTML Imports implementation allows attacker.com to issue
  // requests on behalf of the module.com module - here attacker.com initiates a
  // request for a victim.com resource.
  const char kScriptTemplate[] = R"(
      link = document.createElement('link');
      link.rel = 'import';
      link.href = $1;
      link.onload = () => {
          with(link.import.documentElement.appendChild(
              link.import.createElement('script'))) {
            crossOrigin = 'use-credentials';
            src = $2
          }
      };
      document.documentElement.appendChild(link);
  )";
  std::string script = JsReplace(kScriptTemplate, module_url, victim_url);
  ASSERT_TRUE(ExecJs(shell(), script));

  // Verify that attacker.com-controlled request for a victim.com resource uses
  // CORS and has `Origin: http://attacker.com` request header (rather than
  // `Origin: http://module.com`).
  victim_response.WaitForRequest();
  net::test_server::HttpRequest::HeaderMap headers =
      victim_response.http_request()->headers;
  ASSERT_TRUE(base::Contains(headers, "Origin"));
  EXPECT_EQ(url::Origin::Create(attacker_url).Serialize(), headers["Origin"]);
}

// Test that receiving a commit with incorrect origin properly terminates the
// renderer process.
IN_PROC_BROWSER_TEST_F(SecurityExploitBrowserTest, MismatchedOriginOnCommit) {
  GURL start_url(embedded_test_server()->GetURL("/title1.html"));
  EXPECT_TRUE(NavigateToURL(shell(), start_url));

  FrameTreeNode* root = static_cast<WebContentsImpl*>(shell()->web_contents())
                            ->GetFrameTree()
                            ->root();

  // Navigate to a new URL, with an interceptor that replaces the origin with
  // one that does not match params.url.
  GURL url(embedded_test_server()->GetURL("/title2.html"));
  PwnCommitIPC(shell()->web_contents(), url, url,
               url::Origin::Create(GURL("http://bar.com/")));

  // Use LoadURL, as the test shouldn't wait for navigation commit.
  NavigationController& controller = shell()->web_contents()->GetController();
  controller.LoadURL(url, Referrer(), ui::PAGE_TRANSITION_LINK, std::string());
  EXPECT_NE(nullptr, controller.GetPendingEntry());
  EXPECT_EQ(url, controller.GetPendingEntry()->GetURL());

  RenderProcessHostKillWaiter kill_waiter(
      root->current_frame_host()->GetProcess());

  // When the IPC message is received and validation fails, the process is
  // terminated. However, the notification for that should be processed in a
  // separate task of the message loop, so ensure that the process is still
  // considered alive.
  EXPECT_TRUE(
      root->current_frame_host()->GetProcess()->IsInitializedAndNotDead());

  EXPECT_EQ(bad_message::RFH_INVALID_ORIGIN_ON_COMMIT, kill_waiter.Wait());
}

namespace {

// Interceptor that replaces |interface_params| with the specified
// value for the first DidCommitProvisionalLoad message it observes in the given
// |web_contents| while in scope.
class ScopedInterfaceParamsReplacer : public DidCommitNavigationInterceptor {
 public:
  ScopedInterfaceParamsReplacer(
      WebContents* web_contents,
      mojom::DidCommitProvisionalLoadInterfaceParamsPtr params_override)
      : DidCommitNavigationInterceptor(web_contents),
        params_override_(std::move(params_override)) {}
  ~ScopedInterfaceParamsReplacer() override = default;

 protected:
  bool WillProcessDidCommitNavigation(
      RenderFrameHost* render_frame_host,
      NavigationRequest* navigation_request,
      ::FrameHostMsg_DidCommitProvisionalLoad_Params* params,
      mojom::DidCommitProvisionalLoadInterfaceParamsPtr* interface_params)
      override {
    interface_params->Swap(&params_override_);

    return true;
  }

 private:
  mojom::DidCommitProvisionalLoadInterfaceParamsPtr params_override_;

  DISALLOW_COPY_AND_ASSIGN(ScopedInterfaceParamsReplacer);
};

}  // namespace

// Test that, as a general rule, not receiving new
// DidCommitProvisionalLoadInterfaceParamsPtr for a cross-document navigation
// properly terminates the renderer process. There is one exception to this
// rule, see: RenderFrameHostImplBrowserTest.
// InterfaceProviderRequestIsOptionalForFirstCommit.
// TODO(crbug.com/718652): when all clients are converted to use
// DocumentInterfaceBroker, InterfaceProviderRequest-related code will be
// removed.
IN_PROC_BROWSER_TEST_F(SecurityExploitBrowserTest,
                       MissingInterfaceProviderOnNonSameDocumentCommit) {
  const GURL start_url(embedded_test_server()->GetURL("/title1.html"));
  const GURL non_same_document_url(
      embedded_test_server()->GetURL("/title2.html"));

  EXPECT_TRUE(NavigateToURL(shell(), start_url));

  RenderFrameHostImpl* frame = static_cast<RenderFrameHostImpl*>(
      shell()->web_contents()->GetMainFrame());
  RenderProcessHostKillWaiter kill_waiter(frame->GetProcess());

  NavigationHandleObserver navigation_observer(shell()->web_contents(),
                                               non_same_document_url);
  ScopedInterfaceParamsReplacer replacer(shell()->web_contents(), nullptr);
  EXPECT_TRUE(NavigateToURLAndExpectNoCommit(shell(), non_same_document_url));
  EXPECT_EQ(bad_message::RFH_INTERFACE_PROVIDER_MISSING, kill_waiter.Wait());

  // Verify that the death of the renderer process doesn't leave behind and
  // leak NavigationRequests - see https://crbug.com/869193.
  EXPECT_FALSE(frame->HasPendingCommitNavigation());
  EXPECT_FALSE(navigation_observer.has_committed());
  EXPECT_TRUE(navigation_observer.is_error());
  EXPECT_TRUE(navigation_observer.last_committed_url().is_empty());
  EXPECT_EQ(net::OK, navigation_observer.net_error_code());
}

// Test that a compromised renderer cannot ask to upload an arbitrary file in
// OpenURL.  This is a regression test for https://crbug.com/726067.
IN_PROC_BROWSER_TEST_F(SecurityExploitBrowserTest,
                       OpenUrl_ResourceRequestBody) {
  GURL start_url(embedded_test_server()->GetURL("/title1.html"));
  GURL target_url(embedded_test_server()->GetURL("/echoall"));
  EXPECT_TRUE(NavigateToURL(shell(), start_url));

  FrameTreeNode* root = static_cast<WebContentsImpl*>(shell()->web_contents())
                            ->GetFrameTree()
                            ->root();

  RenderProcessHostKillWaiter kill_waiter(
      root->current_frame_host()->GetProcess());

  // Prepare a file to upload.
  base::ScopedAllowBlockingForTesting allow_blocking;
  base::ScopedTempDir temp_dir;
  base::FilePath file_path;
  std::string file_content("test-file-content");
  ASSERT_TRUE(temp_dir.CreateUniqueTempDir());
  ASSERT_TRUE(base::CreateTemporaryFileInDir(temp_dir.GetPath(), &file_path));
  ASSERT_LT(
      0, base::WriteFile(file_path, file_content.data(), file_content.size()));

  // Simulate an IPC message asking to POST a file that the renderer shouldn't
  // have access to.
  FrameHostMsg_OpenURL_Params params;
  params.url = target_url;
  params.post_body = new network::ResourceRequestBody;
  params.post_body->AppendFileRange(file_path, 0, file_content.size(),
                                    base::Time());
  params.disposition = WindowOpenDisposition::CURRENT_TAB;
  params.should_replace_current_entry = true;
  params.user_gesture = true;

  FrameHostMsg_OpenURL msg(root->current_frame_host()->routing_id(), params);
  IPC::IpcSecurityTestUtil::PwnMessageReceived(
      root->current_frame_host()->GetProcess()->GetChannel(), msg);

  // Verify that the malicious navigation did not commit the navigation to
  // |target_url|.
  WaitForLoadStop(shell()->web_contents());
  EXPECT_EQ(start_url, root->current_frame_host()->GetLastCommittedURL());

  // Verify that the malicious renderer got killed.
  EXPECT_EQ(bad_message::ILLEGAL_UPLOAD_PARAMS, kill_waiter.Wait());
}

// Test that forging a frame's unique name and commit won't allow changing the
// PageState of a cross-process FrameNavigationEntry.
// See https://crbug.com/766262.
IN_PROC_BROWSER_TEST_F(SecurityExploitBrowserTest, PageStateToWrongEntry) {
  IsolateAllSitesForTesting(base::CommandLine::ForCurrentProcess());

  // Commit a page with nested iframes and a separate cross-process iframe.
  GURL main_url(embedded_test_server()->GetURL(
      "a.com", "/cross_site_iframe_factory.html?a(a(a),b)"));
  EXPECT_TRUE(NavigateToURL(shell(), main_url));
  NavigationEntryImpl* back_entry = static_cast<NavigationEntryImpl*>(
      shell()->web_contents()->GetController().GetLastCommittedEntry());
  int nav_entry_id = back_entry->GetUniqueID();

  FrameTreeNode* root = static_cast<WebContentsImpl*>(shell()->web_contents())
                            ->GetFrameTree()
                            ->root();
  FrameTreeNode* child0_0 = root->child_at(0)->child_at(0);
  std::string child0_0_unique_name = child0_0->unique_name();
  FrameTreeNode* child1 = root->child_at(1);
  GURL child1_url = child1->current_url();
  int child1_pid = child1->current_frame_host()->GetProcess()->GetID();
  PageState child1_page_state = back_entry->GetFrameEntry(child1)->page_state();

  // Add a history item in the nested frame.  It's important to do it there and
  // not the main frame for the repro to work, since we don't walk the subtree
  // when navigating back/forward between same document items.
  TestNavigationObserver fragment_observer(shell()->web_contents());
  EXPECT_TRUE(ExecuteScript(child0_0, "location.href = '#foo';"));
  fragment_observer.Wait();

  // Simulate a name change IPC from the nested iframe, matching the cross-site
  // iframe's unique name.
  child0_0->SetFrameName("foo", child1->unique_name());

  // Simulate a back navigation from the now renamed nested iframe, which would
  // put a PageState on the cross-site iframe's FrameNavigationEntry.  Forge a
  // data URL within the PageState that differs from child1_url.
  std::unique_ptr<FrameHostMsg_DidCommitProvisionalLoad_Params> params =
      std::make_unique<FrameHostMsg_DidCommitProvisionalLoad_Params>();
  params->nav_entry_id = nav_entry_id;
  params->did_create_new_entry = false;
  params->url = GURL("about:blank");
  params->transition = ui::PAGE_TRANSITION_AUTO_SUBFRAME;
  params->should_update_history = false;
  params->gesture = NavigationGestureAuto;
  params->method = "GET";
  params->page_state = PageState::CreateFromURL(GURL("data:text/html,foo"));
  params->origin = url::Origin::Create(GURL("about:blank"));

  service_manager::mojom::InterfaceProviderPtr isolated_interface_provider;
  static_cast<mojom::FrameHost*>(child0_0->current_frame_host())
      ->DidCommitProvisionalLoad(
          std::move(params),
          mojom::DidCommitProvisionalLoadInterfaceParams::New(
              mojo::MakeRequest(&isolated_interface_provider),
              mojo::PendingRemote<blink::mojom::DocumentInterfaceBroker>()
                  .InitWithNewPipeAndPassReceiver(),
              mojo::PendingRemote<blink::mojom::DocumentInterfaceBroker>()
                  .InitWithNewPipeAndPassReceiver(),
              mojo::PendingRemote<blink::mojom::BrowserInterfaceBroker>()
                  .InitWithNewPipeAndPassReceiver()));

  // Make sure we haven't changed the FrameNavigationEntry.  An attack would
  // modify the PageState but leave the SiteInstance as it was.
  EXPECT_EQ(child1->current_frame_host()->GetSiteInstance(),
            back_entry->GetFrameEntry(child1)->site_instance());
  EXPECT_EQ(child1_page_state, back_entry->GetFrameEntry(child1)->page_state());

  // Put the frame's unique name back.
  child0_0->SetFrameName("bar", child0_0_unique_name);

  // Go forward after the fake back navigation.
  TestNavigationObserver forward_observer(shell()->web_contents());
  shell()->web_contents()->GetController().GoForward();
  forward_observer.Wait();

  // Go back to the possibly corrupted entry and ensure we didn't load the data
  // URL in the previous process.  A test failure here would appear as a failure
  // of the URL check and not the process ID check.
  TestNavigationObserver back_observer(shell()->web_contents());
  shell()->web_contents()->GetController().GoBack();
  back_observer.Wait();
  EXPECT_EQ(child1_pid, child1->current_frame_host()->GetProcess()->GetID());
  ASSERT_EQ(child1_url, child1->current_url());
}

class SecurityExploitBrowserTestMojoBlobURLs
    : public SecurityExploitBrowserTest {
 public:
  SecurityExploitBrowserTestMojoBlobURLs() = default;

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

// Check that when site isolation is enabled, an origin can't create a blob URL
// for a different origin.  Similar to the test above, but checks the
// mojo-based Blob URL implementation.  See https://crbug.com/886976.
IN_PROC_BROWSER_TEST_F(SecurityExploitBrowserTestMojoBlobURLs,
                       CreateMojoBlobURLInDifferentOrigin) {
  IsolateAllSitesForTesting(base::CommandLine::ForCurrentProcess());

  GURL main_url(embedded_test_server()->GetURL("a.com", "/title1.html"));
  EXPECT_TRUE(NavigateToURL(shell(), main_url));
  RenderFrameHost* rfh = shell()->web_contents()->GetMainFrame();

  // Intercept future blob URL registrations and overwrite the blob URL origin
  // with b.com.
  std::string target_origin = "http://b.com";
  std::string blob_path = "5881f76e-10d2-410d-8c61-ef210502acfd";
  auto intercept_hook =
      base::BindRepeating(&BlobURLStoreInterceptor::Intercept,
                          GURL("blob:" + target_origin + "/" + blob_path));
  storage::BlobRegistryImpl::SetURLStoreCreationHookForTesting(&intercept_hook);

  // Register a blob URL from the a.com main frame, which will go through the
  // interceptor above and be rewritten to register the blob URL with the b.com
  // origin. This should result in a kill because a.com should not be allowed
  // to create blob URLs outside of its own origin.
  base::HistogramTester histograms;
  RenderProcessHostWatcher crash_observer(
      rfh->GetProcess(), RenderProcessHostWatcher::WATCH_FOR_PROCESS_EXIT);

  // The renderer should always get killed, but sometimes ExecuteScript returns
  // true anyway, so just ignore the result.
  ignore_result(
      content::ExecuteScript(rfh, "URL.createObjectURL(new Blob(['foo']))"));

  // If the process is killed, this test passes.
  crash_observer.Wait();
  histograms.ExpectUniqueSample("Stability.BadMessageTerminated.Content", 123,
                                1);
}

// Check that with site isolation enabled, an origin can't create a filesystem
// URL for a different origin.  See https://crbug.com/888001.
IN_PROC_BROWSER_TEST_F(SecurityExploitBrowserTest,
                       CreateFilesystemURLInDifferentOrigin) {
  IsolateAllSitesForTesting(base::CommandLine::ForCurrentProcess());

  GURL main_url(embedded_test_server()->GetURL(
      "a.com", "/cross_site_iframe_factory.html?a(b)"));
  EXPECT_TRUE(NavigateToURL(shell(), main_url));
  RenderFrameHost* rfh = shell()->web_contents()->GetMainFrame();

  // Block the renderer on operation that never completes, to shield it from
  // receiving unexpected browser->renderer IPCs that might CHECK.
  rfh->ExecuteJavaScriptWithUserGestureForTests(
      base::ASCIIToUTF16("var r = new XMLHttpRequest();"
                         "r.open('GET', '/slow?99999', false);"
                         "r.send(null);"
                         "while (1);"));

  // Set up a blob ID and populate it with attacker-controlled value. 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::string payload = "<html><body>pwned.</body></html>";
  std::string payload_type = "text/html";
  std::unique_ptr<content::BlobHandle> blob = CreateMemoryBackedBlob(
      rfh->GetSiteInstance()->GetBrowserContext(), payload, payload_type);
  std::string blob_id = blob->GetUUID();

  // Target a different origin.
  std::string target_origin = "http://b.com";
  GURL target_url =
      GURL("filesystem:" + target_origin + "/temporary/exploit.html");

  // 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.

  PwnMessageHelper::FileSystemCreate(rfh->GetProcess(), 23, target_url, false,
                                     false, false);

  // Write the blob into the file. If successful, this places an
  // attacker-controlled value in a resource on the target origin.
  PwnMessageHelper::FileSystemWrite(rfh->GetProcess(), 24, target_url, blob_id,
                                    0);

  // Now navigate to |target_url| in a subframe. It should not succeed, and the
  // subframe should not contain |payload|.
  TestNavigationObserver observer(shell()->web_contents());
  FrameTreeNode* root = static_cast<WebContentsImpl*>(shell()->web_contents())
                            ->GetFrameTree()
                            ->root();
  NavigateFrameToURL(root->child_at(0), target_url);
  EXPECT_FALSE(observer.last_navigation_succeeded());
  EXPECT_EQ(net::ERR_FILE_NOT_FOUND, observer.last_net_error_code());

  RenderFrameHost* attacked_rfh = root->child_at(0)->current_frame_host();
  std::string body =
      EvalJs(attacked_rfh, "document.body.innerText").ExtractString();
  EXPECT_TRUE(base::StartsWith(body, "Could not load the requested resource",
                               base::CompareCase::INSENSITIVE_ASCII))
      << " body=" << body;
}

// Verify that when a compromised renderer tries to navigate a remote frame to
// a disallowed URL (e.g., file URL), that navigation is blocked.
IN_PROC_BROWSER_TEST_F(SecurityExploitBrowserTest,
                       BlockIllegalOpenURLFromRemoteFrame) {
  // Explicitly isolating a.com helps ensure that this test is applicable on
  // platforms without site-per-process.
  IsolateOrigin("a.com");

  GURL main_url(embedded_test_server()->GetURL(
      "a.com", "/cross_site_iframe_factory.html?a(b)"));
  EXPECT_TRUE(NavigateToURL(shell(), main_url));
  FrameTreeNode* root = static_cast<WebContentsImpl*>(shell()->web_contents())
                            ->GetFrameTree()
                            ->root();
  FrameTreeNode* child = root->child_at(0);

  // Simulate an IPC message where the top frame asks the remote subframe to
  // navigate to a file: URL.
  GURL file_url("file:///");
  FrameHostMsg_OpenURL_Params params;
  params.url = file_url;
  params.disposition = WindowOpenDisposition::CURRENT_TAB;
  params.should_replace_current_entry = false;
  params.user_gesture = true;

  SiteInstance* a_com_instance = root->current_frame_host()->GetSiteInstance();
  RenderFrameProxyHost* proxy =
      child->render_manager()->GetRenderFrameProxyHost(a_com_instance);
  EXPECT_TRUE(proxy);

  {
    FrameHostMsg_OpenURL msg(proxy->GetRoutingID(), params);
    IPC::IpcSecurityTestUtil::PwnMessageReceived(
        proxy->GetProcess()->GetChannel(), msg);
  }

  // Verify that the malicious navigation was blocked.  Currently, this happens
  // by rewriting the target URL to about:blank#blocked.
  //
  // TODO(alexmos): Consider killing the renderer process in this case, since
  // this security check is already enforced in the renderer process.
  EXPECT_TRUE(WaitForLoadStop(shell()->web_contents()));
  EXPECT_EQ(GURL(kBlockedURL),
            child->current_frame_host()->GetLastCommittedURL());

  // Navigate to the starting page again to recreate the proxy, then try the
  // same malicious navigation with a chrome:// URL.
  EXPECT_TRUE(NavigateToURL(shell(), main_url));
  child = root->child_at(0);
  proxy = child->render_manager()->GetRenderFrameProxyHost(a_com_instance);
  EXPECT_TRUE(proxy);

  GURL chrome_url(std::string(kChromeUIScheme) + "://" +
                  std::string(kChromeUIGpuHost));
  params.url = chrome_url;
  {
    FrameHostMsg_OpenURL msg(proxy->GetRoutingID(), params);
    IPC::IpcSecurityTestUtil::PwnMessageReceived(
        proxy->GetProcess()->GetChannel(), msg);
  }
  EXPECT_TRUE(WaitForLoadStop(shell()->web_contents()));
  EXPECT_EQ(GURL(kBlockedURL),
            child->current_frame_host()->GetLastCommittedURL());
}

class PostMessageIpcInterceptor : public BrowserMessageFilter {
 public:
  // Starts listening for IPC messages to |process|, intercepting
  // FrameHostMsg_RouteMessageEvent and storing once it comes.
  explicit PostMessageIpcInterceptor(RenderProcessHost* process)
      : BrowserMessageFilter(FrameMsgStart) {
    process->AddFilter(this);
  }

  // Waits for FrameMsg_PostMessage_Params (if it didn't come yet) and returns
  // message payload to the caller.
  void WaitForMessage(int32_t* out_routing_id,
                      FrameMsg_PostMessage_Params* out_params) {
    run_loop_.Run();
    *out_routing_id = intercepted_routing_id_;
    *out_params = intercepted_params_;
  }

 private:
  ~PostMessageIpcInterceptor() override = default;

  void OnRouteMessageEvent(const FrameMsg_PostMessage_Params& params) {
    intercepted_params_ = params;

    // UaF would have happened without the call below - the call ensures that
    // the data is still valid even once the original message is destroyed.
    intercepted_params_.message->data.EnsureDataIsOwned();
  }

  bool OnMessageReceived(const IPC::Message& message) override {
    // Only intercept one message.
    if (already_intercepted_)
      return false;

    // See if we got FrameHostMsg_RouteMessageEvent and if so unpack and store
    // its payload in OnRouteMessageEvent.
    bool handled = true;
    IPC_BEGIN_MESSAGE_MAP(PostMessageIpcInterceptor, message)
      IPC_MESSAGE_HANDLER(FrameHostMsg_RouteMessageEvent, OnRouteMessageEvent)
      IPC_MESSAGE_UNHANDLED(handled = false)
    IPC_END_MESSAGE_MAP()

    // If we got FrameHostMsg_RouteMessageEvent, then also store the routing ID
    // and signal to the main test thread that it can stop waiting.
    if (handled) {
      already_intercepted_ = true;
      intercepted_routing_id_ = message.routing_id();
      run_loop_.Quit();
    }

    return handled;
  }

  bool already_intercepted_ = false;
  int32_t intercepted_routing_id_;
  FrameMsg_PostMessage_Params intercepted_params_;
  base::RunLoop run_loop_;

  DISALLOW_COPY_AND_ASSIGN(PostMessageIpcInterceptor);
};

// Test verifying that a compromised renderer can't lie about
// FrameMsg_PostMessage_Params::source_origin.  See also
// https://crbug.com/915721.
IN_PROC_BROWSER_TEST_F(SecurityExploitBrowserTest, PostMessageSourceOrigin) {
  // Explicitly isolating a.com helps ensure that this test is applicable on
  // platforms without site-per-process.
  IsolateOrigin("b.com");

  // Navigate to a page with an OOPIF.
  GURL main_url(embedded_test_server()->GetURL(
      "a.com", "/cross_site_iframe_factory.html?a(b)"));
  EXPECT_TRUE(NavigateToURL(shell(), main_url));

  // Sanity check of test setup: main frame and subframe should be isolated.
  WebContents* web_contents = shell()->web_contents();
  RenderFrameHost* main_frame = web_contents->GetMainFrame();
  RenderFrameHost* subframe = web_contents->GetAllFrames()[1];
  EXPECT_NE(main_frame->GetProcess(), subframe->GetProcess());

  // Prepare to intercept FrameHostMsg_RouteMessageEvent IPC message that will
  // come from the subframe process.
  RenderProcessHost* subframe_process = subframe->GetProcess();
  auto ipc_interceptor =
      base::MakeRefCounted<PostMessageIpcInterceptor>(subframe_process);

  // Post a message from the subframe to the cross-site parent and intercept the
  // associated IPC message.
  EXPECT_TRUE(ExecJs(subframe, "parent.postMessage('blah', '*')"));
  int intercepted_routing_id;
  FrameMsg_PostMessage_Params intercepted_params;
  ipc_interceptor->WaitForMessage(&intercepted_routing_id, &intercepted_params);

  // Change the intercepted message to simulate a compromised subframe renderer
  // lying that the |source_origin| of the postMessage is the origin of the
  // parent (not of the subframe).
  url::Origin invalid_origin =
      web_contents->GetMainFrame()->GetLastCommittedOrigin();
  FrameMsg_PostMessage_Params evil_params = intercepted_params;
  evil_params.source_origin = base::UTF8ToUTF16(invalid_origin.Serialize());
  FrameHostMsg_RouteMessageEvent evil_msg(intercepted_routing_id, evil_params);

  // Inject the invalid IPC and verify that the renderer gets terminated.
  RenderProcessHostKillWaiter kill_waiter(subframe_process);
  IpcSecurityTestUtil::PwnMessageReceived(subframe_process->GetChannel(),
                                          evil_msg);
  EXPECT_EQ(bad_message::RFPH_POST_MESSAGE_INVALID_SOURCE_ORIGIN,
            kill_waiter.Wait());
}

class OpenUrlIpcInterceptor : public BrowserMessageFilter {
 public:
  // Starts listening for IPC messages to |process|, intercepting
  // FrameHostMsg_OpenURL IPC message and storing once it comes.
  explicit OpenUrlIpcInterceptor(RenderProcessHost* process)
      : BrowserMessageFilter(FrameMsgStart) {
    process->AddFilter(this);
  }

  // Waits for FrameHostMsg_OpenURL (if it didn't come yet) and returns
  // message payload to the caller.
  void WaitForMessage(int32_t* out_routing_id,
                      FrameHostMsg_OpenURL_Params* out_params) {
    run_loop_.Run();
    *out_routing_id = intercepted_routing_id_;
    *out_params = intercepted_params_;
  }

 private:
  ~OpenUrlIpcInterceptor() override = default;

  void OnOpenURL(const FrameHostMsg_OpenURL_Params& params) {
    intercepted_params_ = params;
  }

  bool OnMessageReceived(const IPC::Message& message) override {
    // Only intercept one message.
    if (already_intercepted_)
      return false;

    // See if we got FrameHostMsg_RouteMessageEvent and if so unpack and store
    // its payload in OnRouteMessageEvent.
    bool handled = true;
    IPC_BEGIN_MESSAGE_MAP(OpenUrlIpcInterceptor, message)
      IPC_MESSAGE_HANDLER(FrameHostMsg_OpenURL, OnOpenURL)
      IPC_MESSAGE_UNHANDLED(handled = false)
    IPC_END_MESSAGE_MAP()

    // If we got FrameHostMsg_RouteMessageEvent, then also store the routing ID
    // and signal to the main test thread that it can stop waiting.
    if (handled) {
      already_intercepted_ = true;
      intercepted_routing_id_ = message.routing_id();
      run_loop_.Quit();
    }

    return handled;
  }

  bool already_intercepted_ = false;
  int32_t intercepted_routing_id_;
  FrameHostMsg_OpenURL_Params intercepted_params_;
  base::RunLoop run_loop_;

  DISALLOW_COPY_AND_ASSIGN(OpenUrlIpcInterceptor);
};

IN_PROC_BROWSER_TEST_F(SecurityExploitBrowserTest,
                       InvalidRemoteNavigationInitiator) {
  // Explicitly isolating a.com helps ensure that this test is applicable on
  // platforms without site-per-process.
  IsolateOrigin("a.com");

  // Navigate to a test page where the subframe is cross-site (and because of
  // IsolateOrigin call above in a separate process) from the main frame.
  GURL main_url(embedded_test_server()->GetURL(
      "a.com", "/cross_site_iframe_factory.html?a(b)"));
  EXPECT_TRUE(NavigateToURL(shell(), main_url));
  RenderFrameHost* main_frame = shell()->web_contents()->GetMainFrame();
  RenderProcessHost* main_process = main_frame->GetProcess();
  EXPECT_EQ(2u, shell()->web_contents()->GetAllFrames().size());
  RenderFrameHost* subframe = shell()->web_contents()->GetAllFrames()[1];
  RenderProcessHost* subframe_process = subframe->GetProcess();
  EXPECT_NE(main_process->GetID(), subframe_process->GetID());

  // Prepare to intercept FrameHostMsg_OpenURL IPC message that will come from
  // the main frame process.
  auto ipc_interceptor =
      base::MakeRefCounted<OpenUrlIpcInterceptor>(main_process);

  // Have the main frame request navigation in the "remote" subframe.  This will
  // result in FrameHostMsg_OpenURL IPC being sent to the RenderFrameProxyHost.
  EXPECT_TRUE(ExecJs(shell()->web_contents()->GetMainFrame(),
                     "window.frames[0].location = '/title1.html';"));
  int intercepted_routing_id;
  FrameHostMsg_OpenURL_Params intercepted_params;
  ipc_interceptor->WaitForMessage(&intercepted_routing_id, &intercepted_params);

  // Change the intercepted message to simulate a compromised subframe renderer
  // lying that the |initiator_origin| is the origin of the |subframe|.
  FrameHostMsg_OpenURL_Params evil_params = intercepted_params;
  evil_params.initiator_origin = subframe->GetLastCommittedOrigin();
  FrameHostMsg_OpenURL evil_msg(intercepted_routing_id, evil_params);

  // Inject the invalid IPC and verify that the renderer gets terminated.
  RenderProcessHostKillWaiter kill_waiter(main_process);
  IpcSecurityTestUtil::PwnMessageReceived(main_process->GetChannel(), evil_msg);
  EXPECT_EQ(bad_message::INVALID_INITIATOR_ORIGIN, kill_waiter.Wait());
}

class BeginNavigationInitiatorReplacer : public FrameHostInterceptor {
 public:
  BeginNavigationInitiatorReplacer(
      WebContents* web_contents,
      base::Optional<url::Origin> initiator_to_inject)
      : FrameHostInterceptor(web_contents),
        initiator_to_inject_(initiator_to_inject) {}

  bool WillDispatchBeginNavigation(
      RenderFrameHost* render_frame_host,
      mojom::CommonNavigationParamsPtr* common_params,
      mojom::BeginNavigationParamsPtr* begin_params,
      mojo::PendingRemote<blink::mojom::BlobURLToken>* blob_url_token,
      mojo::PendingAssociatedRemote<mojom::NavigationClient>* navigation_client,
      mojo::PendingRemote<blink::mojom::NavigationInitiator>*
          navigation_initiator) override {
    if (is_activated_) {
      (*common_params)->initiator_origin = initiator_to_inject_;
      is_activated_ = false;
    }

    return true;
  }

  void Activate() { is_activated_ = true; }

 private:
  base::Optional<url::Origin> initiator_to_inject_;
  bool is_activated_ = false;

  DISALLOW_COPY_AND_ASSIGN(BeginNavigationInitiatorReplacer);
};

IN_PROC_BROWSER_TEST_F(SecurityExploitBrowserTest,
                       InvalidBeginNavigationInitiator) {
  WebContentsImpl* web_contents =
      static_cast<WebContentsImpl*>(shell()->web_contents());

  // Prepare to intercept BeginNavigation mojo IPC. This has to be done before
  // the test creates the RenderFrameHostImpl that is the target of the IPC.
  BeginNavigationInitiatorReplacer injector(
      web_contents, url::Origin::Create(GURL("http://b.com")));

  // Explicitly isolating a.com helps ensure that this test is applicable on
  // platforms without site-per-process.
  IsolateOrigin("a.com");

  // Navigate to a test page that will be locked to a.com.
  GURL main_url(embedded_test_server()->GetURL("a.com", "/title1.html"));
  EXPECT_TRUE(NavigateToURL(web_contents, main_url));

  // Start monitoring for renderer kills.
  RenderProcessHost* main_process = web_contents->GetMainFrame()->GetProcess();
  RenderProcessHostKillWaiter kill_waiter(main_process);

  // Have the main frame navigate and lie that the initiator origin is b.com.
  injector.Activate();
  // Don't expect a response for the script, as the process may be killed
  // before the script sends its completion message.
  ExecuteScriptAsync(web_contents, "window.location = '/title2.html';");

  // Verify that the renderer was terminated.
  EXPECT_EQ(bad_message::INVALID_INITIATOR_ORIGIN, kill_waiter.Wait());
}

IN_PROC_BROWSER_TEST_F(SecurityExploitBrowserTest,
                       MissingBeginNavigationInitiator) {
  // Prepare to intercept BeginNavigation mojo IPC.  This has to be done before
  // the test creates the RenderFrameHostImpl that is the target of the IPC.
  WebContents* web_contents = shell()->web_contents();
  BeginNavigationInitiatorReplacer injector(web_contents, base::nullopt);

  // Navigate to a test page.
  GURL main_url(embedded_test_server()->GetURL("a.com", "/title1.html"));
  EXPECT_TRUE(NavigateToURL(web_contents, main_url));

  // Start monitoring for renderer kills.
  RenderProcessHost* main_process = web_contents->GetMainFrame()->GetProcess();
  RenderProcessHostKillWaiter kill_waiter(main_process);

  // Have the main frame submit a BeginNavigation IPC with a missing initiator.
  injector.Activate();
  // Don't expect a response for the script, as the process may be killed
  // before the script sends its completion message.
  ExecuteScriptAsync(web_contents, "window.location = '/title2.html';");

  // Verify that the renderer was terminated.
  EXPECT_EQ(bad_message::RFHI_BEGIN_NAVIGATION_MISSING_INITIATOR_ORIGIN,
            kill_waiter.Wait());
}

namespace {

// An interceptor class that allows replacing the URL of the commit IPC from
// the renderer process to the browser process.
class DidCommitUrlReplacer : public DidCommitNavigationInterceptor {
 public:
  DidCommitUrlReplacer(WebContents* web_contents, const GURL& replacement_url)
      : DidCommitNavigationInterceptor(web_contents),
        replacement_url_(replacement_url) {}
  ~DidCommitUrlReplacer() override = default;

 protected:
  bool WillProcessDidCommitNavigation(
      RenderFrameHost* render_frame_host,
      NavigationRequest* navigation_request,
      ::FrameHostMsg_DidCommitProvisionalLoad_Params* params,
      mojom::DidCommitProvisionalLoadInterfaceParamsPtr* interface_params)
      override {
    params->url = replacement_url_;
    return true;
  }

 private:
  GURL replacement_url_;

  DISALLOW_COPY_AND_ASSIGN(DidCommitUrlReplacer);
};

}  // namespace

// Test which verifies that when an exploited renderer process sends a commit
// message with URL that the process is not allowed to commit.
IN_PROC_BROWSER_TEST_F(SecurityExploitBrowserTest, DidCommitInvalidURL) {
  // Explicitly isolating foo.com helps ensure that this test is applicable on
  // platforms without site-per-process.
  IsolateOrigin("foo.com");

  // Navigate to foo.com initially.
  GURL foo_url(embedded_test_server()->GetURL("foo.com", "/title1.html"));
  EXPECT_TRUE(NavigateToURL(shell(), foo_url));

  // Create the interceptor object which will replace the URL of the subsequent
  // navigation with bar.com based URL.
  GURL bar_url(embedded_test_server()->GetURL("bar.com", "/title3.html"));
  DidCommitUrlReplacer url_replacer(shell()->web_contents(), bar_url);

  // Navigate to another URL within foo.com, which would usually be committed
  // successfully, but when the URL is modified it should result in the
  // termination of the renderer process.
  RenderProcessHostKillWaiter kill_waiter(
      shell()->web_contents()->GetMainFrame()->GetProcess());
  EXPECT_FALSE(NavigateToURL(
      shell(), embedded_test_server()->GetURL("foo.com", "/title2.html")));
  EXPECT_EQ(bad_message::RFH_CAN_COMMIT_URL_BLOCKED, kill_waiter.Wait());
}

class BeginNavigationTransitionReplacer : public FrameHostInterceptor {
 public:
  BeginNavigationTransitionReplacer(WebContents* web_contents,
                                    ui::PageTransition transition_to_inject)
      : FrameHostInterceptor(web_contents),
        transition_to_inject_(transition_to_inject) {}

  bool WillDispatchBeginNavigation(
      RenderFrameHost* render_frame_host,
      mojom::CommonNavigationParamsPtr* common_params,
      mojom::BeginNavigationParamsPtr* begin_params,
      mojo::PendingRemote<blink::mojom::BlobURLToken>* blob_url_token,
      mojo::PendingAssociatedRemote<mojom::NavigationClient>* navigation_client,
      mojo::PendingRemote<blink::mojom::NavigationInitiator>*
          navigation_initiator) override {
    if (is_activated_) {
      (*common_params)->transition = transition_to_inject_;
      is_activated_ = false;
    }

    return true;
  }

  void Activate() { is_activated_ = true; }

 private:
  ui::PageTransition transition_to_inject_;
  bool is_activated_ = false;

  DISALLOW_COPY_AND_ASSIGN(BeginNavigationTransitionReplacer);
};

IN_PROC_BROWSER_TEST_F(SecurityExploitBrowserTest, NonWebbyTransition) {
  const ui::PageTransition test_cases[] = {
      ui::PAGE_TRANSITION_TYPED,
      ui::PAGE_TRANSITION_AUTO_BOOKMARK,
      ui::PAGE_TRANSITION_GENERATED,
      ui::PAGE_TRANSITION_AUTO_TOPLEVEL,
      ui::PAGE_TRANSITION_RELOAD,
      ui::PAGE_TRANSITION_KEYWORD,
      ui::PAGE_TRANSITION_KEYWORD_GENERATED};

  for (ui::PageTransition transition : test_cases) {
    // Prepare to intercept BeginNavigation mojo IPC.  This has to be done
    // before the test creates the RenderFrameHostImpl that is the target of the
    // IPC.
    WebContents* web_contents = shell()->web_contents();
    BeginNavigationTransitionReplacer injector(web_contents, transition);

    // Navigate to a test page.
    GURL main_url(embedded_test_server()->GetURL("a.com", "/title1.html"));
    EXPECT_TRUE(NavigateToURL(web_contents, main_url));

    // Start monitoring for renderer kills.
    RenderProcessHost* main_process =
        web_contents->GetMainFrame()->GetProcess();
    RenderProcessHostKillWaiter kill_waiter(main_process);

    // Have the main frame submit a BeginNavigation IPC with a missing
    // initiator.
    injector.Activate();
    // Don't expect a response for the script, as the process may be killed
    // before the script sends its completion message.
    ExecuteScriptAsync(web_contents, "window.location = '/title2.html';");

    // Verify that the renderer was terminated.
    EXPECT_EQ(bad_message::RFHI_BEGIN_NAVIGATION_NON_WEBBY_TRANSITION,
              kill_waiter.Wait());
  }
}

class SecurityExploitViaDisabledWebSecurityTest
    : public SecurityExploitBrowserTest {
 public:
  SecurityExploitViaDisabledWebSecurityTest() {
    // To get around BlockedSchemeNavigationThrottle. Other attempts at getting
    // around it don't work, i.e.:
    // -if the request is made in a child frame then the frame is torn down
    // immediately on process killing so the navigation doesn't complete
    // -if it's classified as same document, then a DCHECK in
    // NavigationRequest::CreateRendererInitiated fires
    feature_list_.InitAndEnableFeature(
        features::kAllowContentInitiatedDataUrlNavigations);
  }

 protected:
  void SetUpCommandLine(base::CommandLine* command_line) override {
    // Simulate a compromised renderer, otherwise the cross-origin request to
    // file: is blocked.
    command_line->AppendSwitch(switches::kDisableWebSecurity);
    SecurityExploitBrowserTest::SetUpCommandLine(command_line);
  }

 private:
  base::test::ScopedFeatureList feature_list_;
};

// Test to verify that an exploited renderer process trying to specify a
// non-empty URL for base_url_for_data_url on navigation is correctly
// terminated.
IN_PROC_BROWSER_TEST_F(SecurityExploitViaDisabledWebSecurityTest,
                       ValidateBaseUrlForDataUrl) {
  GURL start_url(embedded_test_server()->GetURL("/title1.html"));
  EXPECT_TRUE(NavigateToURL(shell(), start_url));

  RenderFrameHostImpl* rfh = static_cast<RenderFrameHostImpl*>(
      shell()->web_contents()->GetMainFrame());

  GURL data_url("data:text/html,foo");
  base::FilePath file_path = GetTestFilePath("", "simple_page.html");
  GURL file_url = net::FilePathToFileURL(file_path);

  // Setup a BeginNavigate IPC with non-empty base_url_for_data_url.
  mojom::CommonNavigationParamsPtr common_params =
      mojom::CommonNavigationParams::New(
          data_url, url::Origin::Create(data_url),
          blink::mojom::Referrer::New(), ui::PAGE_TRANSITION_LINK,
          mojom::NavigationType::DIFFERENT_DOCUMENT, NavigationDownloadPolicy(),
          false /* should_replace_current_entry */,
          file_url, /* base_url_for_data_url */
          GURL() /* history_url_for_data_url */, PREVIEWS_UNSPECIFIED,
          base::TimeTicks::Now() /* navigation_start */, "GET",
          nullptr /* post_data */, base::Optional<SourceLocation>(),
          false /* started_from_context_menu */, false /* has_user_gesture */,
          InitiatorCSPInfo(),
          std::vector<int>() /* initiator_origin_trial_features */,
          std::string() /* href_translate */,
          false /* is_history_navigation_in_new_child_frame */,
          base::TimeTicks());
  mojom::BeginNavigationParamsPtr begin_params =
      mojom::BeginNavigationParams::New(
          std::string() /* headers */, net::LOAD_NORMAL,
          false /* skip_service_worker */,
          blink::mojom::RequestContextType::LOCATION,
          blink::WebMixedContentContextType::kBlockable,
          false /* is_form_submission */,
          false /* was_initiated_by_link_click */,
          GURL() /* searchable_form_url */,
          std::string() /* searchable_form_encoding */,
          GURL() /* client_side_redirect_url */,
          base::nullopt /* devtools_initiator_info */);

  // Receiving the invalid IPC message should lead to renderer process
  // termination.
  RenderProcessHostKillWaiter process_kill_waiter(rfh->GetProcess());

  mojo::PendingAssociatedRemote<mojom::NavigationClient> navigation_client;
  auto navigation_client_receiver =
      navigation_client.InitWithNewEndpointAndPassReceiver();
  rfh->frame_host_receiver_for_testing().impl()->BeginNavigation(
      std::move(common_params), std::move(begin_params), mojo::NullRemote(),
      std::move(navigation_client), mojo::NullRemote());
  EXPECT_EQ(bad_message::RFH_BASE_URL_FOR_DATA_URL_SPECIFIED,
            process_kill_waiter.Wait());

  EXPECT_FALSE(ChildProcessSecurityPolicyImpl::GetInstance()->CanReadFile(
      rfh->GetProcess()->GetID(), file_path));

  // Reload the page to create another renderer process.
  TestNavigationObserver tab_observer(shell()->web_contents(), 1);
  shell()->web_contents()->GetController().Reload(ReloadType::NORMAL, false);
  tab_observer.Wait();

  // Make an XHR request to check if the page has access.
  std::string script = base::StringPrintf(
      "var xhr = new XMLHttpRequest()\n"
      "xhr.open('GET', '%s', false);\n"
      "try { xhr.send(); } catch (e) {}\n"
      "window.domAutomationController.send(xhr.responseText);",
      file_url.spec().c_str());
  std::string result;
  EXPECT_TRUE(
      ExecuteScriptAndExtractString(shell()->web_contents(), script, &result));
  EXPECT_TRUE(result.empty());
}

// Tests what happens when a web renderer asks to begin navigating to a file
// url.
IN_PROC_BROWSER_TEST_F(SecurityExploitViaDisabledWebSecurityTest,
                       WebToFileNavigation) {
  // Navigate to a web page.
  GURL start_url(embedded_test_server()->GetURL("/title1.html"));
  EXPECT_TRUE(NavigateToURL(shell(), start_url));

  // Have the webpage attempt to open a window with a file URL.
  //
  // Note that such attempt would normally be blocked in the renderer ("Not
  // allowed to load local resource: file:///..."), but the test here simulates
  // a compromised renderer by using --disable-web-security cmdline flag.
  GURL file_url = GetTestUrl("", "simple_page.html");
  WebContentsAddedObserver new_window_observer;
  TestNavigationObserver nav_observer(nullptr);
  nav_observer.StartWatchingNewWebContents();
  ASSERT_TRUE(ExecJs(shell()->web_contents(),
                     JsReplace("window.open($1, '_blank')", file_url)));
  WebContents* new_window = new_window_observer.GetWebContents();
  nav_observer.WaitForNavigationFinished();

  // Verify that the navigation got blocked.
  EXPECT_TRUE(nav_observer.last_navigation_succeeded());
  EXPECT_EQ(GURL(kBlockedURL), nav_observer.last_navigation_url());
  EXPECT_EQ(GURL(kBlockedURL),
            new_window->GetMainFrame()->GetLastCommittedURL());
  EXPECT_EQ(shell()->web_contents()->GetMainFrame()->GetLastCommittedOrigin(),
            new_window->GetMainFrame()->GetLastCommittedOrigin());
  EXPECT_EQ(shell()->web_contents()->GetMainFrame()->GetProcess(),
            new_window->GetMainFrame()->GetProcess());

  // Even though the navigation is blocked, we expect the opener relationship to
  // be established between the 2 windows.
  EXPECT_EQ(true, ExecJs(new_window, "!!window.opener"));
}

// Tests what happens when a web renderer asks to begin navigating to a
// view-source url.
IN_PROC_BROWSER_TEST_F(SecurityExploitViaDisabledWebSecurityTest,
                       WebToViewSourceNavigation) {
  // Navigate to a web page.
  GURL start_url(embedded_test_server()->GetURL("/title1.html"));
  EXPECT_TRUE(NavigateToURL(shell(), start_url));

  // Have the webpage attempt to open a window with a view-source URL.
  //
  // Note that such attempt would normally be blocked in the renderer ("Not
  // allowed to load local resource: view-source:///..."), but the test here
  // simulates a compromised renderer by using --disable-web-security flag.
  base::FilePath file_path = GetTestFilePath("", "simple_page.html");
  GURL view_source_url =
      GURL(std::string(kViewSourceScheme) + ":" + start_url.spec());
  WebContentsAddedObserver new_window_observer;
  TestNavigationObserver nav_observer(nullptr);
  nav_observer.StartWatchingNewWebContents();
  ASSERT_TRUE(ExecJs(shell()->web_contents(),
                     JsReplace("window.open($1, '_blank')", view_source_url)));
  WebContents* new_window = new_window_observer.GetWebContents();
  nav_observer.WaitForNavigationFinished();

  // Verify that the navigation got blocked.
  EXPECT_TRUE(nav_observer.last_navigation_succeeded());
  EXPECT_EQ(GURL(kBlockedURL), nav_observer.last_navigation_url());
  EXPECT_EQ(GURL(kBlockedURL),
            new_window->GetMainFrame()->GetLastCommittedURL());
  EXPECT_EQ(shell()->web_contents()->GetMainFrame()->GetLastCommittedOrigin(),
            new_window->GetMainFrame()->GetLastCommittedOrigin());
  EXPECT_EQ(shell()->web_contents()->GetMainFrame()->GetProcess(),
            new_window->GetMainFrame()->GetProcess());

  // Even though the navigation is blocked, we expect the opener relationship to
  // be established between the 2 windows.
  EXPECT_EQ(true, ExecJs(new_window, "!!window.opener"));
}

}  // namespace content