blob: 459bfe50cf65d0643d5f0f2b8263ff6e3a210163 [file] [log] [blame]
// Copyright 2015 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 <string>
#include "base/bind.h"
#include "base/bind_helpers.h"
#include "base/command_line.h"
#include "base/files/file_path.h"
#include "base/task/post_task.h"
#include "base/test/metrics/histogram_tester.h"
#include "content/browser/bad_message.h"
#include "content/browser/frame_host/frame_tree.h"
#include "content/browser/frame_host/render_frame_message_filter.h"
#include "content/browser/web_contents/web_contents_impl.h"
#include "content/common/frame_messages.h"
#include "content/common/render_frame_message_filter.mojom.h"
#include "content/public/browser/browser_task_traits.h"
#include "content/public/browser/browser_thread.h"
#include "content/public/browser/render_process_host.h"
#include "content/public/common/content_switches.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 "ipc/ipc_security_test_util.h"
#include "net/dns/mock_host_resolver.h"
#include "net/test/embedded_test_server/embedded_test_server.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "url/gurl.h"
namespace content {
namespace {
std::string GetCookieFromJS(RenderFrameHost* frame) {
std::string cookie;
EXPECT_TRUE(ExecuteScriptAndExtractString(
frame, "window.domAutomationController.send(document.cookie);", &cookie));
return cookie;
}
mojom::RenderFrameMessageFilter* GetFilterForProcess(
RenderProcessHost* process) {
return static_cast<RenderProcessHostImpl*>(process)
->render_frame_message_filter_for_testing();
}
} // namespace
class RenderFrameMessageFilterBrowserTest : public ContentBrowserTest {
protected:
void SetUp() override {
base::CommandLine::ForCurrentProcess()->AppendSwitch(
switches::kEnableExperimentalWebPlatformFeatures);
ContentBrowserTest::SetUp();
}
void SetUpOnMainThread() override {
// Support multiple sites on the test server.
host_resolver()->AddRule("*", "127.0.0.1");
}
};
// Exercises basic cookie operations via javascript, including an http page
// interacting with secure cookies.
IN_PROC_BROWSER_TEST_F(RenderFrameMessageFilterBrowserTest, Cookies) {
SetupCrossSiteRedirector(embedded_test_server());
ASSERT_TRUE(embedded_test_server()->Start());
net::EmbeddedTestServer https_server(net::EmbeddedTestServer::TYPE_HTTPS);
https_server.AddDefaultHandlers(GetTestDataFilePath());
ASSERT_TRUE(https_server.Start());
// The server sends a HttpOnly cookie. The RenderFrameMessageFilter should
// never allow this to be sent to any renderer process.
GURL https_url = https_server.GetURL("/set-cookie?notforjs=1;HttpOnly");
GURL http_url = embedded_test_server()->GetURL("/frame_with_load_event.html");
Shell* shell2 = CreateBrowser();
NavigateToURL(shell(), http_url);
NavigateToURL(shell2, https_url);
WebContentsImpl* web_contents_https =
static_cast<WebContentsImpl*>(shell2->web_contents());
WebContentsImpl* web_contents_http =
static_cast<WebContentsImpl*>(shell()->web_contents());
EXPECT_EQ("http://127.0.0.1/",
web_contents_http->GetSiteInstance()->GetSiteURL().spec());
EXPECT_EQ("https://127.0.0.1/",
web_contents_https->GetSiteInstance()->GetSiteURL().spec());
EXPECT_NE(web_contents_http->GetSiteInstance()->GetProcess(),
web_contents_https->GetSiteInstance()->GetProcess());
EXPECT_EQ("", GetCookieFromJS(web_contents_https->GetMainFrame()));
EXPECT_EQ("", GetCookieFromJS(web_contents_http->GetMainFrame()));
// Non-TLS page writes secure cookie.
EXPECT_TRUE(ExecuteScript(web_contents_http->GetMainFrame(),
"document.cookie = 'A=1; secure;';"));
EXPECT_EQ("", GetCookieFromJS(web_contents_https->GetMainFrame()));
EXPECT_EQ("", GetCookieFromJS(web_contents_http->GetMainFrame()));
// TLS page writes not-secure cookie.
EXPECT_TRUE(ExecuteScript(web_contents_http->GetMainFrame(),
"document.cookie = 'B=2';"));
EXPECT_EQ("B=2", GetCookieFromJS(web_contents_https->GetMainFrame()));
EXPECT_EQ("B=2", GetCookieFromJS(web_contents_http->GetMainFrame()));
// TLS page writes secure cookie.
EXPECT_TRUE(ExecuteScript(web_contents_https->GetMainFrame(),
"document.cookie = 'C=3;secure;';"));
EXPECT_EQ("B=2; C=3",
GetCookieFromJS(web_contents_https->GetMainFrame()));
EXPECT_EQ("B=2", GetCookieFromJS(web_contents_http->GetMainFrame()));
// TLS page writes not-secure cookie.
EXPECT_TRUE(ExecuteScript(web_contents_https->GetMainFrame(),
"document.cookie = 'D=4';"));
EXPECT_EQ("B=2; C=3; D=4",
GetCookieFromJS(web_contents_https->GetMainFrame()));
EXPECT_EQ("B=2; D=4", GetCookieFromJS(web_contents_http->GetMainFrame()));
}
// Ensure "priority" cookie option is settable via document.cookie.
IN_PROC_BROWSER_TEST_F(RenderFrameMessageFilterBrowserTest, CookiePriority) {
ASSERT_TRUE(embedded_test_server()->Start());
struct {
std::string param;
net::CookiePriority priority;
} cases[] = {{"name=value", net::COOKIE_PRIORITY_DEFAULT},
{"name=value;priority=Low", net::COOKIE_PRIORITY_LOW},
{"name=value;priority=Medium", net::COOKIE_PRIORITY_MEDIUM},
{"name=value;priority=High", net::COOKIE_PRIORITY_HIGH}};
for (auto test_case : cases) {
GURL url = embedded_test_server()->GetURL("/set_document_cookie.html?" +
test_case.param);
NavigateToURL(shell(), url);
std::vector<net::CanonicalCookie> cookies =
GetCanonicalCookies(shell()->web_contents()->GetBrowserContext(), url);
EXPECT_EQ(1u, cookies.size());
EXPECT_EQ("name", cookies[0].Name());
EXPECT_EQ("value", cookies[0].Value());
EXPECT_EQ(test_case.priority, cookies[0].Priority());
}
}
// SameSite cookies (that aren't marked as http-only) should be available to
// JavaScript.
IN_PROC_BROWSER_TEST_F(RenderFrameMessageFilterBrowserTest, SameSiteCookies) {
SetupCrossSiteRedirector(embedded_test_server());
ASSERT_TRUE(embedded_test_server()->Start());
// The server sets five cookies on 'a.com' and on 'b.com', then loads a
// page that frames both 'a.com' and 'b.com' under 'a.com'.
std::string cookies_to_set =
"/set-cookie?normal=1"
"&strict=1;SameSite=Strict"
"&lax=1;SameSite=Lax"
"&strict-http=1;SameSite=Strict;httponly"
"&lax-http=1;SameSite=Lax;httponly";
GURL url = embedded_test_server()->GetURL("a.com", cookies_to_set);
NavigateToURL(shell(), url);
url = embedded_test_server()->GetURL("b.com", cookies_to_set);
NavigateToURL(shell(), url);
url = embedded_test_server()->GetURL(
"a.com", "/cross_site_iframe_factory.html?a(a(),b())");
NavigateToURL(shell(), url);
WebContentsImpl* web_contents =
static_cast<WebContentsImpl*>(shell()->web_contents());
RenderFrameHost* main_frame = web_contents->GetMainFrame();
RenderFrameHost* a_iframe =
web_contents->GetFrameTree()->root()->child_at(0)->current_frame_host();
RenderFrameHost* b_iframe =
web_contents->GetFrameTree()->root()->child_at(1)->current_frame_host();
// The top-level frame should get both kinds of same-site cookies.
EXPECT_EQ("normal=1; strict=1; lax=1", GetCookieFromJS(main_frame));
// Same-site cookies will be delievered to the 'a.com' frame, as it is same-
// site with its ancestors.
EXPECT_EQ("normal=1; strict=1; lax=1", GetCookieFromJS(a_iframe));
// Same-site cookies should not be delievered to the 'b.com' frame, as it
// isn't same-site with its ancestors.
EXPECT_EQ("normal=1", GetCookieFromJS(b_iframe));
}
// The RenderFrameMessageFilter will kill processes when they access the cookies
// of sites other than the site the process is dedicated to, under site
// isolation.
IN_PROC_BROWSER_TEST_F(RenderFrameMessageFilterBrowserTest,
CrossSiteCookieSecurityEnforcement) {
// The code under test is only active under site isolation.
if (!AreAllSitesIsolatedForTesting()) {
return;
}
SetupCrossSiteRedirector(embedded_test_server());
ASSERT_TRUE(embedded_test_server()->Start());
NavigateToURL(shell(),
embedded_test_server()->GetURL("/frame_with_load_event.html"));
WebContentsImpl* tab = static_cast<WebContentsImpl*>(shell()->web_contents());
// The iframe on the http page should get its own process.
FrameTreeVisualizer v;
EXPECT_EQ(
" Site A ------------ proxies for B\n"
" +--Site B ------- proxies for A\n"
"Where A = http://127.0.0.1/\n"
" B = http://baz.com/",
v.DepictFrameTree(tab->GetFrameTree()->root()));
RenderFrameHost* main_frame = tab->GetMainFrame();
RenderFrameHost* iframe =
tab->GetFrameTree()->root()->child_at(0)->current_frame_host();
EXPECT_NE(iframe->GetProcess(), main_frame->GetProcess());
// Try to get cross-site cookies from the subframe's process and wait for it
// to be killed.
{
RenderProcessHostKillWaiter iframe_kill_waiter(iframe->GetProcess());
base::CreateSingleThreadTaskRunnerWithTraits({BrowserThread::IO})
->PostTask(FROM_HERE,
base::BindOnce(
[](RenderFrameHost* frame) {
GetFilterForProcess(frame->GetProcess())
->GetCookies(
frame->GetRoutingID(),
GURL("http://127.0.0.1/"),
GURL("http://127.0.0.1/"),
base::BindOnce([](const std::string&) {}));
},
iframe));
EXPECT_EQ(bad_message::RFMF_GET_COOKIES_BAD_ORIGIN,
iframe_kill_waiter.Wait());
}
EXPECT_EQ(
" Site A ------------ proxies for B\n"
" +--Site B ------- proxies for A\n"
"Where A = http://127.0.0.1/\n"
" B = http://baz.com/ (no process)",
v.DepictFrameTree(tab->GetFrameTree()->root()));
// Now set a cross-site cookie from the main frame's process and wait for it
// to be killed.
{
RenderProcessHostKillWaiter main_frame_kill_waiter(
tab->GetMainFrame()->GetProcess());
base::CreateSingleThreadTaskRunnerWithTraits({BrowserThread::IO})
->PostTask(FROM_HERE, base::BindOnce(
[](RenderFrameHost* frame) {
GetFilterForProcess(frame->GetProcess())
->SetCookie(frame->GetRoutingID(),
GURL("https://baz.com/"),
GURL("https://baz.com/"),
"pwn=ed",
base::DoNothing());
},
main_frame));
EXPECT_EQ(bad_message::RFMF_SET_COOKIE_BAD_ORIGIN,
main_frame_kill_waiter.Wait());
}
EXPECT_EQ(
" Site A\n"
"Where A = http://127.0.0.1/ (no process)",
v.DepictFrameTree(tab->GetFrameTree()->root()));
}
// FrameHostMsg_RenderProcessGone is a synthetic message that's really an
// implementation detail of RenderProcessHostImpl's crash recovery. It should be
// ignored if it arrives over the IPC channel.
IN_PROC_BROWSER_TEST_F(RenderFrameMessageFilterBrowserTest, RenderProcessGone) {
GURL web_url("http://foo.com/simple_page.html");
NavigateToURL(shell(), web_url);
RenderFrameHost* web_rfh = shell()->web_contents()->GetMainFrame();
ASSERT_TRUE(web_rfh->IsRenderFrameLive());
RenderProcessHostKillWaiter kill_waiter(web_rfh->GetProcess());
IPC::IpcSecurityTestUtil::PwnMessageReceived(
web_rfh->GetProcess()->GetChannel(),
FrameHostMsg_RenderProcessGone(
web_rfh->GetRoutingID(), base::TERMINATION_STATUS_NORMAL_TERMINATION,
0));
// If the message had gone through, we'd have marked the RFH as dead but
// left the RPH and its connection alive, and the Wait below would hang.
EXPECT_EQ(bad_message::RFMF_RENDERER_FAKED_ITS_OWN_DEATH, kill_waiter.Wait());
ASSERT_FALSE(web_rfh->GetProcess()->IsInitializedAndNotDead());
ASSERT_FALSE(web_rfh->IsRenderFrameLive());
}
class WaitingCookieStore : public net::CookieMonster {
public:
WaitingCookieStore() : CookieMonster(nullptr, nullptr) {}
void GetCookieListWithOptionsAsync(const GURL& url,
const net::CookieOptions& options,
GetCookieListCallback callback) override {
callback_ = std::move(callback);
}
void Finish() { std::move(callback_).Run({}, {}); }
private:
GetCookieListCallback callback_;
};
class CookieStoreContentBrowserClient : public ContentBrowserClient {
public:
~CookieStoreContentBrowserClient() override {
BrowserThread::DeleteSoon(BrowserThread::IO, FROM_HERE,
std::move(cookie_store_));
}
net::CookieStore* OverrideCookieStoreForURL(
const GURL& url,
ResourceContext* context) override {
if (!cookie_store_)
cookie_store_ = std::make_unique<WaitingCookieStore>();
return cookie_store_.get();
}
bool AllowGetCookie(const GURL& url,
const GURL& first_party,
const net::CookieList& cookie_list,
ResourceContext* context,
int render_process_id,
int render_frame_id) override {
num_allow_get_cookie_calls_++;
return false;
}
void FinishGetCookieList() {
base::PostTaskWithTraits(
FROM_HERE, {BrowserThread::IO},
base::BindOnce(&WaitingCookieStore::Finish,
base::Unretained(cookie_store_.get())));
}
int num_allow_get_cookie_calls() const { return num_allow_get_cookie_calls_; }
private:
int num_allow_get_cookie_calls_ = 0;
std::unique_ptr<WaitingCookieStore> cookie_store_;
};
IN_PROC_BROWSER_TEST_F(RenderFrameMessageFilterBrowserTest,
CookieCallbackAfterProfileDestroyed) {
CookieStoreContentBrowserClient browser_client;
content::ContentBrowserClient* old_browser_client =
content::SetBrowserClientForTesting(&browser_client);
ASSERT_TRUE(embedded_test_server()->Start());
NavigateToURL(shell(), embedded_test_server()->GetURL("/title1.html"));
base::RunLoop run_loop;
WebContentsImpl* web_contents =
static_cast<WebContentsImpl*>(shell()->web_contents());
FrameTreeNode* root = web_contents->GetFrameTree()->root();
base::PostTaskWithTraits(
FROM_HERE, {BrowserThread::IO},
base::BindOnce(
[](RenderFrameHost* frame, base::RunLoop* run_loop) {
GetFilterForProcess(frame->GetProcess())
->GetCookies(
frame->GetRoutingID(), GURL("http://127.0.0.1/"),
GURL("http://127.0.0.1/"),
base::BindOnce([](base::RunLoop* run_loop,
const std::string&) { run_loop->Quit(); },
run_loop));
},
root->current_frame_host(), &run_loop));
shell()->Close();
base::RunLoop().RunUntilIdle();
int num_calls = browser_client.num_allow_get_cookie_calls();
browser_client.FinishGetCookieList();
run_loop.Run();
EXPECT_EQ(num_calls, browser_client.num_allow_get_cookie_calls());
content::SetBrowserClientForTesting(old_browser_client);
}
} // namespace content