Commit 39603d88 authored by Lukasz Anforowicz's avatar Lukasz Anforowicz Committed by Commit Bot

Reject IPC requests for isolated origin, sent by non-isolated renderer.

The CL tweaks StoragePartitonInterceptor (in
isolated_origin_browsertest.cc) so that it can be configured to inject
different origins, depending on needs of individual tests.  This tweak
is supported by changes in base/lazy_instance.h (adding of inequality
operator implemented on top of the already existing equality operator),
and in render_process_host_impl.cc/.h (to support creating a test-only
StoragePartitionService via a base::Callback, rather than via a function
pointer).

Tweaks of StoragePartitonInterceptor allow forking of
IsolatedOriginTest.LocalStorageOriginEnforcement test into 2 separate
tests:
- LocalStorageOriginEnforcement_IsolatedAccessingNonIsolated
- LocalStorageOriginEnforcement_NonIsolatedAccessingIsolated
The second test is introduced by this CL and was failing before this CL.

Tweaks of ChildProcessSecurityPolicyImpl::CanAccessDataForOrigin
disallow requests from non-isolated renderers (in which case
CheckOriginLock will return NO_LOCK) if the request is for
an isolated origin.  This makes the new test pass.

Bug: 509125, 764958
Change-Id: Ibfff2c91cb2ac51966e1d89295f10592a3814c08
Reviewed-on: https://chromium-review.googlesource.com/775060
Commit-Queue: Łukasz Anforowicz <lukasza@chromium.org>
Reviewed-by: default avatarAlex Moshchuk <alexmos@chromium.org>
Cr-Commit-Position: refs/heads/master@{#521838}
parent 92908c8f
...@@ -259,12 +259,6 @@ class ChildProcessSecurityPolicyImpl::SecurityState { ...@@ -259,12 +259,6 @@ class ChildProcessSecurityPolicyImpl::SecurityState {
return false; return false;
} }
bool CanAccessDataForOrigin(const GURL& site_url) {
if (origin_lock_.is_empty())
return true;
return origin_lock_ == site_url;
}
void LockToOrigin(const GURL& gurl) { void LockToOrigin(const GURL& gurl) {
origin_lock_ = gurl; origin_lock_ = gurl;
} }
...@@ -1046,6 +1040,7 @@ bool ChildProcessSecurityPolicyImpl::CanAccessDataForOrigin(int child_id, ...@@ -1046,6 +1040,7 @@ bool ChildProcessSecurityPolicyImpl::CanAccessDataForOrigin(int child_id,
// URLs. Currently, hosted apps cannot set cookies in this mode. See // URLs. Currently, hosted apps cannot set cookies in this mode. See
// http://crbug.com/160576. // http://crbug.com/160576.
GURL site_url = SiteInstanceImpl::GetSiteForURL(nullptr, url); GURL site_url = SiteInstanceImpl::GetSiteForURL(nullptr, url);
bool is_isolated = IsIsolatedOrigin(url::Origin::Create(site_url));
base::AutoLock lock(lock_); base::AutoLock lock(lock_);
SecurityStateMap::iterator state = security_state_.find(child_id); SecurityStateMap::iterator state = security_state_.find(child_id);
...@@ -1054,7 +1049,30 @@ bool ChildProcessSecurityPolicyImpl::CanAccessDataForOrigin(int child_id, ...@@ -1054,7 +1049,30 @@ bool ChildProcessSecurityPolicyImpl::CanAccessDataForOrigin(int child_id,
// workaround for https://crbug.com/600441 // workaround for https://crbug.com/600441
return true; return true;
} }
bool can_access = state->second->CanAccessDataForOrigin(site_url);
// Check access in a way that ensures that:
// - isolated origins are only allowed access to data for the same origin
// ("jail" enforcement)
// - other sites should not be allowed to access data of an isolated origin
// ("citadel" enforcement)
// TODO(lukasza): https://crbug.com/509125, https://crbug.com/764958:
// Prevent other kinds of forbidden access. Some cherry-picked examples
// of forbidden access:
// - open web -> extension
// - open web -> WebUI
bool can_access = false;
switch (state->second->CheckOriginLock(site_url)) {
case CheckOriginLockResult::NO_LOCK:
can_access = !is_isolated;
break;
case CheckOriginLockResult::HAS_EQUAL_LOCK:
can_access = true;
break;
case CheckOriginLockResult::HAS_WRONG_LOCK:
can_access = false;
break;
}
if (!can_access) { if (!can_access) {
// Returning false here will result in a renderer kill. Set some crash // Returning false here will result in a renderer kill. Set some crash
// keys that will help understand the circumstances of that kill. // keys that will help understand the circumstances of that kill.
...@@ -1063,6 +1081,7 @@ bool ChildProcessSecurityPolicyImpl::CanAccessDataForOrigin(int child_id, ...@@ -1063,6 +1081,7 @@ bool ChildProcessSecurityPolicyImpl::CanAccessDataForOrigin(int child_id,
base::debug::SetCrashKeyValue("killed_process_origin_lock", base::debug::SetCrashKeyValue("killed_process_origin_lock",
state->second->origin_lock().spec()); state->second->origin_lock().spec());
} }
return can_access; return can_access;
} }
......
...@@ -2,13 +2,21 @@ ...@@ -2,13 +2,21 @@
// Use of this source code is governed by a BSD-style license that can be // Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file. // found in the LICENSE file.
#include "base/bind.h"
#include "base/bind_helpers.h"
#include "base/callback.h"
#include "base/command_line.h" #include "base/command_line.h"
#include "base/macros.h" #include "base/macros.h"
#include "base/test/scoped_feature_list.h" #include "base/test/scoped_feature_list.h"
#include "content/browser/bad_message.h" #include "content/browser/bad_message.h"
#include "content/browser/child_process_security_policy_impl.h" #include "content/browser/child_process_security_policy_impl.h"
#include "content/browser/frame_host/render_frame_message_filter.h"
#include "content/browser/renderer_host/render_process_host_impl.h"
#include "content/browser/storage_partition_impl.h" #include "content/browser/storage_partition_impl.h"
#include "content/browser/web_contents/web_contents_impl.h" #include "content/browser/web_contents/web_contents_impl.h"
#include "content/common/render_frame_message_filter.mojom.h"
#include "content/public/browser/browser_thread.h"
#include "content/public/browser/render_frame_host.h"
#include "content/public/browser/render_process_host.h" #include "content/public/browser/render_process_host.h"
#include "content/public/common/browser_side_navigation_policy.h" #include "content/public/common/browser_side_navigation_policy.h"
#include "content/public/common/content_features.h" #include "content/public/common/content_features.h"
...@@ -28,6 +36,16 @@ ...@@ -28,6 +36,16 @@
namespace content { namespace content {
namespace {
mojom::RenderFrameMessageFilter* GetFilterForProcess(
RenderProcessHost* process) {
return static_cast<RenderProcessHostImpl*>(process)
->render_frame_message_filter_for_testing();
}
} // namespace
class IsolatedOriginTest : public ContentBrowserTest { class IsolatedOriginTest : public ContentBrowserTest {
public: public:
IsolatedOriginTest() {} IsolatedOriginTest() {}
...@@ -854,7 +872,9 @@ class StoragePartitonInterceptor ...@@ -854,7 +872,9 @@ class StoragePartitonInterceptor
public RenderProcessHostObserver { public RenderProcessHostObserver {
public: public:
StoragePartitonInterceptor(RenderProcessHostImpl* rph, StoragePartitonInterceptor(RenderProcessHostImpl* rph,
mojom::StoragePartitionServiceRequest request) { mojom::StoragePartitionServiceRequest request,
const url::Origin& origin_to_inject)
: origin_to_inject_(origin_to_inject) {
StoragePartitionImpl* storage_partition = StoragePartitionImpl* storage_partition =
static_cast<StoragePartitionImpl*>(rph->GetStoragePartition()); static_cast<StoragePartitionImpl*>(rph->GetStoragePartition());
...@@ -893,9 +913,7 @@ class StoragePartitonInterceptor ...@@ -893,9 +913,7 @@ class StoragePartitonInterceptor
// security checks can be tested. // security checks can be tested.
void OpenLocalStorage(const url::Origin& origin, void OpenLocalStorage(const url::Origin& origin,
mojom::LevelDBWrapperRequest request) override { mojom::LevelDBWrapperRequest request) override {
url::Origin mismatched_origin = GetForwardingInterface()->OpenLocalStorage(origin_to_inject_,
url::Origin::Create(GURL("http://abc.foo.com"));
GetForwardingInterface()->OpenLocalStorage(mismatched_origin,
std::move(request)); std::move(request));
} }
...@@ -904,27 +922,36 @@ class StoragePartitonInterceptor ...@@ -904,27 +922,36 @@ class StoragePartitonInterceptor
// calls can be forwarded to it. // calls can be forwarded to it.
mojom::StoragePartitionService* storage_partition_service_; mojom::StoragePartitionService* storage_partition_service_;
url::Origin origin_to_inject_;
DISALLOW_COPY_AND_ASSIGN(StoragePartitonInterceptor); DISALLOW_COPY_AND_ASSIGN(StoragePartitonInterceptor);
}; };
void CreateTestStoragePartitionService( void CreateTestStoragePartitionService(
const url::Origin& origin_to_inject,
RenderProcessHostImpl* rph, RenderProcessHostImpl* rph,
mojom::StoragePartitionServiceRequest request) { mojom::StoragePartitionServiceRequest request) {
// This object will register as RenderProcessHostObserver, so it will // This object will register as RenderProcessHostObserver, so it will
// clean itself automatically on process exit. // clean itself automatically on process exit.
new StoragePartitonInterceptor(rph, std::move(request)); new StoragePartitonInterceptor(rph, std::move(request), origin_to_inject);
} }
// Verify that an isolated renderer process cannot read localStorage of an // Verify that an isolated renderer process cannot read localStorage of an
// origin outside of its isolated site. // origin outside of its isolated site.
// TODO(nasko): Write a test to verify the opposite - any non-isolated renderer IN_PROC_BROWSER_TEST_F(
// process cannot access data of an isolated site. IsolatedOriginTest,
IN_PROC_BROWSER_TEST_F(IsolatedOriginTest, LocalStorageOriginEnforcement) { LocalStorageOriginEnforcement_IsolatedAccessingNonIsolated) {
RenderProcessHostImpl::SetCreateStoragePartitionServiceFunction( auto* policy = ChildProcessSecurityPolicyImpl::GetInstance();
CreateTestStoragePartitionService);
auto mismatched_origin = url::Origin::Create(GURL("http://abc.foo.com"));
EXPECT_FALSE(policy->IsIsolatedOrigin(mismatched_origin));
RenderProcessHostImpl::SetStoragePartitionServiceFactoryForTesting(
base::BindRepeating(&CreateTestStoragePartitionService,
mismatched_origin));
GURL isolated_url( GURL isolated_url(
embedded_test_server()->GetURL("isolated.foo.com", "/title1.html")); embedded_test_server()->GetURL("isolated.foo.com", "/title1.html"));
EXPECT_TRUE(policy->IsIsolatedOrigin(url::Origin::Create(isolated_url)));
EXPECT_TRUE(NavigateToURL(shell(), isolated_url)); EXPECT_TRUE(NavigateToURL(shell(), isolated_url));
content::RenderProcessHostWatcher crash_observer( content::RenderProcessHostWatcher crash_observer(
...@@ -938,6 +965,120 @@ IN_PROC_BROWSER_TEST_F(IsolatedOriginTest, LocalStorageOriginEnforcement) { ...@@ -938,6 +965,120 @@ IN_PROC_BROWSER_TEST_F(IsolatedOriginTest, LocalStorageOriginEnforcement) {
crash_observer.Wait(); crash_observer.Wait();
} }
// Verify that a non-isolated renderer process cannot read localStorage of an
// isolated origin.
IN_PROC_BROWSER_TEST_F(
IsolatedOriginTest,
LocalStorageOriginEnforcement_NonIsolatedAccessingIsolated) {
auto* policy = ChildProcessSecurityPolicyImpl::GetInstance();
GURL isolated_url(embedded_test_server()->GetURL("isolated.foo.com", "/"));
auto isolated_origin = url::Origin::Create(isolated_url);
EXPECT_TRUE(policy->IsIsolatedOrigin(isolated_origin));
RenderProcessHostImpl::SetStoragePartitionServiceFactoryForTesting(
base::BindRepeating(&CreateTestStoragePartitionService, isolated_origin));
GURL non_isolated_url(
embedded_test_server()->GetURL("blah.bar.com", "/title1.html"));
EXPECT_TRUE(NavigateToURL(shell(), non_isolated_url));
content::RenderProcessHostWatcher crash_observer(
shell()->web_contents()->GetMainFrame()->GetProcess(),
content::RenderProcessHostWatcher::WATCH_FOR_PROCESS_EXIT);
// Use ignore_result here, since on Android the renderer process is
// terminated, but ExecuteScript still returns true. It properly returns
// false on all other platforms.
ignore_result(ExecuteScript(shell()->web_contents()->GetMainFrame(),
"localStorage.length;"));
crash_observer.Wait();
}
// The RenderFrameMessageFilter should kill the renderer when cookies of an
// isolated origin are accessed by another (possibly non-isolated, non-locked)
// origin.
IN_PROC_BROWSER_TEST_F(IsolatedOriginTest,
GetCookieEnforcement_NonIsolatedAccessingIsolated) {
// Gather URLs and Origins used later in the test.
GURL isolated_url(embedded_test_server()->GetURL("isolated.foo.com", "/"));
auto isolated_origin = url::Origin::Create(isolated_url);
auto* policy = ChildProcessSecurityPolicyImpl::GetInstance();
EXPECT_TRUE(policy->IsIsolatedOrigin(isolated_origin));
GURL non_isolated_url(
embedded_test_server()->GetURL("blah.bar.com", "/title2.html"));
auto non_isolated_origin = url::Origin::Create(non_isolated_url);
EXPECT_FALSE(policy->IsIsolatedOrigin(non_isolated_origin));
// Navigate to a non-isolated page and verify that it doesn't get origin lock
// (unless we are running with --site-per-process).
EXPECT_TRUE(NavigateToURL(shell(), non_isolated_url));
RenderFrameHost* main_frame = web_contents()->GetMainFrame();
GURL origin_lock = policy->GetOriginLock(main_frame->GetProcess()->GetID());
if (AreAllSitesIsolatedForTesting())
EXPECT_FALSE(origin_lock.is_empty());
else
EXPECT_TRUE(origin_lock.is_empty());
// Try to get cross-site cookies from the main frame's process and wait for it
// to be killed.
RenderProcessHostWatcher crash_observer(
main_frame->GetProcess(),
RenderProcessHostWatcher::WATCH_FOR_PROCESS_EXIT);
BrowserThread::GetTaskRunnerForThread(BrowserThread::IO)
->PostTask(FROM_HERE,
base::BindOnce(
[](const GURL& cookie_url, RenderFrameHost* frame) {
GetFilterForProcess(frame->GetProcess())
->GetCookies(
frame->GetRoutingID(), cookie_url, cookie_url,
base::BindOnce([](const std::string&) {}));
},
isolated_url, base::Unretained(main_frame)));
crash_observer.Wait();
}
// The RenderFrameMessageFilter should kill the renderer when cookies of an
// isolated origin are set by another (possibly non-isolated, non-locked)
// origin.
IN_PROC_BROWSER_TEST_F(IsolatedOriginTest,
SetCookieEnforcement_NonIsolatedAccessingIsolated) {
// Gather URLs and Origins used later in the test.
GURL isolated_url(
embedded_test_server()->GetURL("isolated.foo.com", "/title1.html"));
auto isolated_origin = url::Origin::Create(isolated_url);
auto* policy = ChildProcessSecurityPolicyImpl::GetInstance();
EXPECT_TRUE(policy->IsIsolatedOrigin(isolated_origin));
GURL non_isolated_url(
embedded_test_server()->GetURL("blah.bar.com", "/title2.html"));
auto non_isolated_origin = url::Origin::Create(non_isolated_url);
EXPECT_FALSE(policy->IsIsolatedOrigin(non_isolated_origin));
// Navigate to a non-isolated page and verify that it doesn't get origin lock.
EXPECT_TRUE(NavigateToURL(shell(), non_isolated_url));
RenderFrameHost* main_frame = web_contents()->GetMainFrame();
GURL origin_lock = policy->GetOriginLock(main_frame->GetProcess()->GetID());
if (AreAllSitesIsolatedForTesting())
EXPECT_FALSE(origin_lock.is_empty());
else
EXPECT_TRUE(origin_lock.is_empty());
// Try to get cross-site cookies from the main frame's process and wait for it
// to be killed.
RenderProcessHostWatcher crash_observer(
main_frame->GetProcess(),
RenderProcessHostWatcher::WATCH_FOR_PROCESS_EXIT);
BrowserThread::GetTaskRunnerForThread(BrowserThread::IO)
->PostTask(FROM_HERE,
base::BindOnce(
[](const GURL& cookie_url, RenderFrameHost* frame) {
GetFilterForProcess(frame->GetProcess())
->SetCookie(frame->GetRoutingID(), cookie_url,
cookie_url, "pwn=ed",
base::BindOnce(&base::DoNothing));
},
isolated_url, main_frame));
crash_observer.Wait();
}
class IsolatedOriginFieldTrialTest : public ContentBrowserTest { class IsolatedOriginFieldTrialTest : public ContentBrowserTest {
public: public:
IsolatedOriginFieldTrialTest() { IsolatedOriginFieldTrialTest() {
......
...@@ -804,7 +804,7 @@ class SiteProcessCountTracker : public base::SupportsUserData::Data, ...@@ -804,7 +804,7 @@ class SiteProcessCountTracker : public base::SupportsUserData::Data,
std::map<ProcessID, Count>& counts_per_process = result->second; std::map<ProcessID, Count>& counts_per_process = result->second;
--counts_per_process[render_process_host_id]; --counts_per_process[render_process_host_id];
DCHECK(counts_per_process[render_process_host_id] >= 0); DCHECK_GE(counts_per_process[render_process_host_id], 0);
if (counts_per_process[render_process_host_id] == 0) if (counts_per_process[render_process_host_id] == 0)
counts_per_process.erase(render_process_host_id); counts_per_process.erase(render_process_host_id);
...@@ -1057,8 +1057,9 @@ void CopyFeatureSwitch(const base::CommandLine& src, ...@@ -1057,8 +1057,9 @@ void CopyFeatureSwitch(const base::CommandLine& src,
} // namespace } // namespace
RendererMainThreadFactoryFunction g_renderer_main_thread_factory = nullptr; RendererMainThreadFactoryFunction g_renderer_main_thread_factory = nullptr;
RenderProcessHostImpl::CreateStoragePartitionServiceFunction
g_create_storage_partition = nullptr; base::LazyInstance<RenderProcessHostImpl::StoragePartitionServiceFactory>::Leaky
g_storage_partition_service_factory = LAZY_INSTANCE_INITIALIZER;
base::MessageLoop* g_in_process_thread; base::MessageLoop* g_in_process_thread;
...@@ -1389,9 +1390,9 @@ void RenderProcessHostImpl::RegisterRendererMainThreadFactory( ...@@ -1389,9 +1390,9 @@ void RenderProcessHostImpl::RegisterRendererMainThreadFactory(
g_renderer_main_thread_factory = create; g_renderer_main_thread_factory = create;
} }
void RenderProcessHostImpl::SetCreateStoragePartitionServiceFunction( void RenderProcessHostImpl::SetStoragePartitionServiceFactoryForTesting(
CreateStoragePartitionServiceFunction function) { StoragePartitionServiceFactory factory) {
g_create_storage_partition = function; g_storage_partition_service_factory.Get() = factory;
} }
RenderProcessHostImpl::~RenderProcessHostImpl() { RenderProcessHostImpl::~RenderProcessHostImpl() {
...@@ -2034,8 +2035,8 @@ void RenderProcessHostImpl::CreateStoragePartitionService( ...@@ -2034,8 +2035,8 @@ void RenderProcessHostImpl::CreateStoragePartitionService(
mojom::StoragePartitionServiceRequest request) { mojom::StoragePartitionServiceRequest request) {
if (!base::CommandLine::ForCurrentProcess()->HasSwitch( if (!base::CommandLine::ForCurrentProcess()->HasSwitch(
switches::kDisableMojoLocalStorage)) { switches::kDisableMojoLocalStorage)) {
if (g_create_storage_partition) { if (!(g_storage_partition_service_factory == nullptr)) {
g_create_storage_partition(this, std::move(request)); g_storage_partition_service_factory.Get().Run(this, std::move(request));
return; return;
} }
......
...@@ -13,7 +13,10 @@ ...@@ -13,7 +13,10 @@
#include <queue> #include <queue>
#include <set> #include <set>
#include <string> #include <string>
#include <utility>
#include <vector>
#include "base/callback_forward.h"
#include "base/macros.h" #include "base/macros.h"
#include "base/memory/ref_counted.h" #include "base/memory/ref_counted.h"
#include "base/observer_list.h" #include "base/observer_list.h"
...@@ -315,11 +318,11 @@ class CONTENT_EXPORT RenderProcessHostImpl ...@@ -315,11 +318,11 @@ class CONTENT_EXPORT RenderProcessHostImpl
// Allows external code to supply a function which creates a // Allows external code to supply a function which creates a
// StoragePartitionService. Used for supplying test versions of the // StoragePartitionService. Used for supplying test versions of the
// service. // service.
using CreateStoragePartitionServiceFunction = using StoragePartitionServiceFactory = base::RepeatingCallback<void(
void (*)(RenderProcessHostImpl* rph, RenderProcessHostImpl* rph,
mojom::StoragePartitionServiceRequest request); mojom::StoragePartitionServiceRequest request)>;
static void SetCreateStoragePartitionServiceFunction( static void SetStoragePartitionServiceFactoryForTesting(
CreateStoragePartitionServiceFunction function); StoragePartitionServiceFactory factory);
RenderFrameMessageFilter* render_frame_message_filter_for_testing() const { RenderFrameMessageFilter* render_frame_message_filter_for_testing() const {
return render_frame_message_filter_.get(); return render_frame_message_filter_.get();
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment