// 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/command_line.h"
#include "base/containers/hash_tables.h"
#include "base/macros.h"
#include "base/strings/utf_string_conversions.h"
#include "build/build_config.h"
#include "content/browser/bad_message.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/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_messages.h"
#include "content/common/render_message_filter.mojom.h"
#include "content/common/resource_messages.h"
#include "content/common/resource_request.h"
#include "content/common/view_messages.h"
#include "content/public/browser/browser_context.h"
#include "content/public/browser/content_browser_client.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/resource_dispatcher_host.h"
#include "content/public/browser/storage_partition.h"
#include "content/public/common/appcache_info.h"
#include "content/public/common/browser_side_navigation_policy.h"
#include "content/public/common/content_switches.h"
#include "content/public/common/file_chooser_params.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/test_utils.h"
#include "content/shell/browser/shell.h"
#include "content/test/content_browser_test_utils_internal.h"
#include "content/test/test_content_browser_client.h"
#include "ipc/ipc_security_test_util.h"
#include "net/dns/mock_host_resolver.h"
#include "net/test/embedded_test_server/embedded_test_server.h"
#include "net/test/url_request/url_request_slow_download_job.h"

using IPC::IpcSecurityTestUtil;

namespace content {

namespace {

// This request id is used by tests that craft a
// ResourceHostMsg_RequestResource. 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 RenderViewHost which is used for
// the attempt is the return value.
RenderViewHostImpl* PrepareToDuplicateHosts(Shell* shell,
                                            int* target_routing_id) {
  GURL foo("http://foo.com/simple_page.html");

  // Start off with initial navigation, so we get the first process allocated.
  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()->GetRenderViewHost()->GetProcess()->GetID(),
            shell2->web_contents()->GetRenderViewHost()->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("https://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, false,
      nullptr, std::string(), Referrer(), WindowOpenDisposition::CURRENT_TAB,
      false, true);

  // 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;
  if (IsBrowserSideNavigationEnabled())
    next_rfh = wc->GetRenderManagerForTesting()->speculative_frame_host();
  else
    next_rfh = wc->GetRenderManagerForTesting()->pending_frame_host();

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

  return next_rfh->render_view_host();
}

ResourceRequest CreateXHRRequest(const char* url) {
  ResourceRequest request;
  request.method = "GET";
  request.url = GURL(url);
  request.referrer_policy = blink::WebReferrerPolicyDefault;
  request.request_initiator = url::Origin();
  request.load_flags = 0;
  request.origin_pid = 0;
  request.resource_type = RESOURCE_TYPE_XHR;
  request.request_context = 0;
  request.appcache_host_id = kAppCacheNoHostId;
  request.download_to_file = false;
  request.should_reset_appcache = false;
  request.is_main_frame = true;
  request.parent_is_main_frame = false;
  request.parent_render_frame_id = -1;
  request.transition_type = ui::PAGE_TRANSITION_LINK;
  request.allow_download = true;
  return request;
}

ResourceRequest CreateXHRRequestWithOrigin(const char* origin) {
  ResourceRequest request = CreateXHRRequest("http://bar.com/simple_page.html");
  request.first_party_for_cookies = GURL(origin);
  request.headers = base::StringPrintf("Origin: %s\r\n", origin);
  return request;
}

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

  if (block_loaders) {
    // Test the case where loaders are placed into blocked_loaders_map_.
    ResourceDispatcherHost::BlockRequestsForFrameFromUI(rfh);
  }

  // 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;
  ResourceRequest request(CreateXHRRequest(blocking_url));

  // Use the same request id twice.
  RenderProcessHostWatcher process_killed(
      rfh->GetProcess(), RenderProcessHostWatcher::WATCH_FOR_PROCESS_EXIT);
  IPC::IpcSecurityTestUtil::PwnMessageReceived(
      rfh->GetProcess()->GetChannel(),
      ResourceHostMsg_RequestResource(rfh->GetRoutingID(),
                                      kRequestIdNotPreviouslyUsed, request));
  IPC::IpcSecurityTestUtil::PwnMessageReceived(
      rfh->GetProcess()->GetChannel(),
      ResourceHostMsg_RequestResource(rfh->GetRoutingID(),
                                      kRequestIdNotPreviouslyUsed, request));
  process_killed.Wait();
}

}  // 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 {
    ASSERT_TRUE(embedded_test_server()->Start());

    // 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(
        switches::kHostResolverRules,
        "MAP * " +
            net::HostPortPair::FromURL(embedded_test_server()->base_url())
                .ToString() +
            ",EXCLUDE localhost");
  }

  void SetUpOnMainThread() override {
    BrowserThread::PostTask(
        BrowserThread::IO, FROM_HERE,
        base::Bind(&net::URLRequestSlowDownloadJob::AddUrlHandler));
  }

 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 SecurityExploitBrowserTest::TestFileChooserWithPath(
    const base::FilePath& path) {
  GURL foo("http://foo.com/simple_page.html");
  NavigateToURL(shell(), foo);
  EXPECT_EQ(base::ASCIIToUTF16("OK"), shell()->web_contents()->GetTitle());

  RenderFrameHost* compromised_renderer =
      shell()->web_contents()->GetMainFrame();
  RenderProcessHostWatcher terminated(
      shell()->web_contents(),
      RenderProcessHostWatcher::WATCH_FOR_PROCESS_EXIT);

  FileChooserParams params;
  params.default_file_name = path;

  FrameHostMsg_RunFileChooser evil(compromised_renderer->GetRoutingID(),
                                   params);

  IpcSecurityTestUtil::PwnMessageReceived(
      compromised_renderer->GetProcess()->GetChannel(), evil);
  terminated.Wait();
}

// 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");

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

  RenderProcessHostWatcher terminated(
      shell()->web_contents(),
      RenderProcessHostWatcher::WATCH_FOR_PROCESS_EXIT);
  shell()->web_contents()->GetRenderViewHost()->SetWebUIProperty(
      "toolkit", "views");
  terminated.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;
  RenderViewHostImpl* pending_rvh =
      PrepareToDuplicateHosts(shell(), &duplicate_routing_id);
  EXPECT_NE(MSG_ROUTING_NONE, duplicate_routing_id);

  // Since this test executes on the UI thread and hopping threads might cause
  // different timing in the test, let's simulate a CreateNewWindow call coming
  // from the IO thread.
  DOMStorageContextWrapper* dom_storage_context =
      static_cast<DOMStorageContextWrapper*>(
          BrowserContext::GetStoragePartition(
              shell()->web_contents()->GetBrowserContext(),
              pending_rvh->GetSiteInstance())->GetDOMStorageContext());
  scoped_refptr<SessionStorageNamespaceImpl> session_storage(
      new SessionStorageNamespaceImpl(dom_storage_context));
  // Cause a deliberate collision in routing ids.
  int32_t main_frame_routing_id = duplicate_routing_id + 1;
  // TODO(avi): This should be made unique from the view routing ID once
  // RenderViewHostImpl has-a RenderWidgetHostImpl. https://crbug.com/545684
  int32_t main_frame_widget_routing_id = duplicate_routing_id;
  pending_rvh->CreateNewWindow(duplicate_routing_id, main_frame_routing_id,
                               main_frame_widget_routing_id,
                               mojom::CreateNewWindowParams(),
                               session_storage.get());

  // 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.
IN_PROC_BROWSER_TEST_F(SecurityExploitBrowserTest,
                       AttemptDuplicateRenderWidgetHost) {
  int duplicate_routing_id = MSG_ROUTING_NONE;
  RenderViewHostImpl* pending_rvh =
      PrepareToDuplicateHosts(shell(), &duplicate_routing_id);
  EXPECT_NE(MSG_ROUTING_NONE, duplicate_routing_id);

  // 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_rvh->CreateNewWidget(duplicate_routing_id, blink::WebPopupTypePage);

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

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.setAutomationId(1);"
           "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");
  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 != NULL);
  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.
  ASSERT_TRUE(ExecuteScript(interstitial_page->GetMainFrame(),
                            "window.domAutomationController.send(\"okay2\");"));
  ASSERT_TRUE(message_queue.WaitForMessage(&message));
  ASSERT_EQ("\"okay2\"", message);
  ASSERT_EQ("\"okay2\"", interstitial->last_command());
}

// Intercepts the HTTP origin header and on being invoked once it is found
// aborts the requeest.
void OnHttpHeaderReceived(const std::string& header,
                          const std::string& value,
                          int child_process_id,
                          content::ResourceContext* resource_context,
                          OnHeaderProcessedCallback callback) {
  callback.Run(false, content::bad_message::RDH_ILLEGAL_ORIGIN);
}

// Renderer processes should not be able to spoof Origin HTTP headers.
IN_PROC_BROWSER_TEST_F(SecurityExploitBrowserTest, InvalidOriginHeaders) {
  // Create a set of IPC messages with various Origin headers.
  ResourceRequest chrome_origin_msg(
      CreateXHRRequestWithOrigin("chrome://settings"));
  ResourceRequest embedder_isolated_origin_msg(
      CreateXHRRequestWithOrigin("https://isolated.bar.com"));
  ResourceRequest invalid_origin_msg(CreateXHRRequestWithOrigin("invalidurl"));
  ResourceRequest invalid_scheme_origin_msg(
      CreateXHRRequestWithOrigin("fake-scheme://foo"));

  GURL web_url("http://foo.com/simple_page.html");
  NavigateToURL(shell(), web_url);
  RenderFrameHost* web_rfh = shell()->web_contents()->GetMainFrame();

  // Web processes cannot make XHRs with chrome:// Origin headers.
  {
    RenderProcessHostWatcher web_process_killed(
        web_rfh->GetProcess(),
        RenderProcessHostWatcher::WATCH_FOR_PROCESS_EXIT);
    IPC::IpcSecurityTestUtil::PwnMessageReceived(
        web_rfh->GetProcess()->GetChannel(),
        ResourceHostMsg_RequestResource(web_rfh->GetRoutingID(),
                                        kRequestIdNotPreviouslyUsed,
                                        chrome_origin_msg));
    web_process_killed.Wait();
  }

  // Web processes cannot make XHRs with URLs that the content embedder expects
  // to have process isolation.  Ideally this would test chrome-extension://
  // URLs for Chrome Apps, but those can't be tested inside content/ and the
  // ResourceRequest IPC can't be created in a test outside content/.
  NavigateToURL(shell(), web_url);
  {
    content::ResourceDispatcherHost::Get()->RegisterInterceptor(
        "Origin", "", base::Bind(&OnHttpHeaderReceived));

    RenderProcessHostWatcher web_process_killed(
        web_rfh->GetProcess(),
        RenderProcessHostWatcher::WATCH_FOR_PROCESS_EXIT);
    IPC::IpcSecurityTestUtil::PwnMessageReceived(
        web_rfh->GetProcess()->GetChannel(),
        ResourceHostMsg_RequestResource(web_rfh->GetRoutingID(),
                                        kRequestIdNotPreviouslyUsed,
                                        embedder_isolated_origin_msg));
    web_process_killed.Wait();
  }

  // Web processes cannot make XHRs with invalid Origin headers.
  NavigateToURL(shell(), web_url);
  {
    RenderProcessHostWatcher web_process_killed(
        web_rfh->GetProcess(),
        RenderProcessHostWatcher::WATCH_FOR_PROCESS_EXIT);
    IPC::IpcSecurityTestUtil::PwnMessageReceived(
        web_rfh->GetProcess()->GetChannel(),
        ResourceHostMsg_RequestResource(web_rfh->GetRoutingID(),
                                        kRequestIdNotPreviouslyUsed,
                                        invalid_origin_msg));
    web_process_killed.Wait();
  }

  // Web processes cannot make XHRs with invalid scheme Origin headers.
  NavigateToURL(shell(), web_url);
  {
    RenderProcessHostWatcher web_process_killed(
        web_rfh->GetProcess(),
        RenderProcessHostWatcher::WATCH_FOR_PROCESS_EXIT);
    IPC::IpcSecurityTestUtil::PwnMessageReceived(
        web_rfh->GetProcess()->GetChannel(),
        ResourceHostMsg_RequestResource(web_rfh->GetRoutingID(),
                                        kRequestIdNotPreviouslyUsed,
                                        invalid_scheme_origin_msg));
    web_process_killed.Wait();
  }
}

// Renderer process should not be able to create multiple requests with the same
// id.
IN_PROC_BROWSER_TEST_F(SecurityExploitBrowserTest, InvalidRequestId) {
  // Existing loader in pending_loaders_.
  TryCreateDuplicateRequestIds(shell(), false);
  // Existing loader in blocked_loaders_map_.
  TryCreateDuplicateRequestIds(shell(), true);
}

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

  // Setup an URL which will never commit, allowing this test to send its own,
  // malformed, commit message.
  GURL url(embedded_test_server()->GetURL("/title2.html"));
  NavigationStallDelegate stall_delegate(url);
  ResourceDispatcherHost::Get()->SetDelegate(&stall_delegate);

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

  RenderProcessHostWatcher exit_observer(
      root->current_frame_host()->GetProcess(),
      RenderProcessHostWatcher::WATCH_FOR_PROCESS_EXIT);

  // Create commit params with different origins in params.url and
  // params.origin.
  FrameHostMsg_DidCommitProvisionalLoad_Params params;
  params.nav_entry_id = 0;
  params.did_create_new_entry = false;
  params.url = url;
  params.transition = ui::PAGE_TRANSITION_LINK;
  params.should_update_history = false;
  params.gesture = NavigationGestureAuto;
  params.was_within_same_page = false;
  params.method = "GET";
  params.page_state = PageState::CreateFromURL(url);
  params.origin = url::Origin(GURL("http://bar.com/"));

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

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

  exit_observer.Wait();
  EXPECT_FALSE(exit_observer.did_exit_normally());
  ResourceDispatcherHost::Get()->SetDelegate(nullptr);
}

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

  RenderProcessHostWatcher exit_observer(
      root->current_frame_host()->GetProcess(),
      RenderProcessHostWatcher::WATCH_FOR_PROCESS_EXIT);

  // Prepare a file to upload.
  base::ThreadRestrictions::ScopedAllowIO allow_io_for_temp_dir;
  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.uses_post = true;
  params.resource_request_body = new ResourceRequestBodyImpl;
  params.resource_request_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;
  params.is_history_navigation_in_new_child = false;

  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.
  exit_observer.Wait();
  EXPECT_FALSE(exit_observer.did_exit_normally());
}

}  // namespace content
