
Currently chrome:// URLs are not allowed to embed chrome-untrusted:// URLs because we never grant request access to chrome-untrusted:// URLs. To allow for this, this CL makes a couple of changes (Note that this is only fixes the issue on the browser side. The renderer still has some checks that stop us from loading chrome-untrusted:// iframes.): 1. Make the set of requestable schemes part of content::WebUI. This way clients of WebUI can control which additional schemes they want their WebUI to be able to request. The function to add a scheme is added as a webUI API because it is expected to be used by chrome/ WebUIControllers that use chrome-untrusted://. 2. When creating a WebUI in RenderFrameHostImpl::CreateWebUI(), grant request access to the schemes the WebUI specified. 3. Decouple granting access to chrome:// and file:// from ChildProcessSecurityPolicy::GrantWebUIBindings(). Instead add this to the list of schemes in content::WebUIImpl. Long term, we should be able to remove these schemes from content::WebUIImpl and instead have each client specify the schemes they want access to, similarly to how clients will call EnableMojoJsBindings()[1]. [1] https://cs.chromium.org/chromium/src/content/browser/process_internals/process_internals_ui.cc?l=53&rcl=0ab24b983b19f971f40b2f1e3f5a5b95c7a1ad9a Bug: 1043548 Change-Id: I71d31f86e7c21da253df4f32634d945b45d071cb Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2009589 Commit-Queue: Jessica Huang <jessing@google.com> Reviewed-by: Giovanni Ortuño Urquidi <ortuno@chromium.org> Reviewed-by: Alex Moshchuk <alexmos@chromium.org> Cr-Commit-Position: refs/heads/master@{#737655}
2664 lines
114 KiB
C++
2664 lines
114 KiB
C++
// Copyright (c) 2012 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 <algorithm>
|
|
#include <set>
|
|
#include <string>
|
|
|
|
#include "base/bind_helpers.h"
|
|
#include "base/files/file_path.h"
|
|
#include "base/logging.h"
|
|
#include "base/memory/ref_counted.h"
|
|
#include "base/synchronization/waitable_event.h"
|
|
#include "base/test/bind_test_util.h"
|
|
#include "base/test/mock_log.h"
|
|
#include "build/build_config.h"
|
|
#include "content/browser/child_process_security_policy_impl.h"
|
|
#include "content/browser/isolated_origin_util.h"
|
|
#include "content/browser/site_instance_impl.h"
|
|
#include "content/public/common/bindings_policy.h"
|
|
#include "content/public/common/content_client.h"
|
|
#include "content/public/common/url_constants.h"
|
|
#include "content/public/test/browser_task_environment.h"
|
|
#include "content/public/test/test_browser_context.h"
|
|
#include "content/public/test/test_utils.h"
|
|
#include "content/test/test_content_browser_client.h"
|
|
#include "storage/browser/file_system/file_permission_policy.h"
|
|
#include "storage/browser/file_system/file_system_url.h"
|
|
#include "storage/browser/file_system/isolated_context.h"
|
|
#include "storage/common/file_system/file_system_types.h"
|
|
#include "testing/gmock/include/gmock/gmock.h"
|
|
#include "testing/gtest/include/gtest/gtest.h"
|
|
#include "url/gurl.h"
|
|
#include "url/origin.h"
|
|
|
|
namespace content {
|
|
namespace {
|
|
|
|
using IsolatedOriginSource = ChildProcessSecurityPolicy::IsolatedOriginSource;
|
|
|
|
const int kRendererID = 42;
|
|
|
|
#if defined(FILE_PATH_USES_DRIVE_LETTERS)
|
|
#define TEST_PATH(x) FILE_PATH_LITERAL("c:") FILE_PATH_LITERAL(x)
|
|
#else
|
|
#define TEST_PATH(x) FILE_PATH_LITERAL(x)
|
|
#endif
|
|
|
|
class ChildProcessSecurityPolicyTestBrowserClient
|
|
: public TestContentBrowserClient {
|
|
public:
|
|
ChildProcessSecurityPolicyTestBrowserClient() {}
|
|
|
|
bool IsHandledURL(const GURL& url) override {
|
|
return schemes_.find(url.scheme()) != schemes_.end();
|
|
}
|
|
|
|
void ClearSchemes() {
|
|
schemes_.clear();
|
|
}
|
|
|
|
void AddScheme(const std::string& scheme) {
|
|
schemes_.insert(scheme);
|
|
}
|
|
|
|
private:
|
|
std::set<std::string> schemes_;
|
|
};
|
|
|
|
bool IsCitadelProtectionEnabled() {
|
|
#if !defined(OS_ANDROID)
|
|
// TODO(lukasza): https://crbug.com/566091: Once remote NTP is capable of
|
|
// embedding OOPIFs, start enforcing citadel-style checks on desktop
|
|
// platforms.
|
|
return false;
|
|
#else
|
|
return true;
|
|
#endif
|
|
}
|
|
|
|
void LockProcessIfNeeded(int process_id,
|
|
BrowserContext* browser_context,
|
|
const GURL& url) {
|
|
scoped_refptr<SiteInstanceImpl> site_instance =
|
|
SiteInstanceImpl::CreateForURL(browser_context, url);
|
|
if (site_instance->RequiresDedicatedProcess() &&
|
|
SiteInstanceImpl::ShouldLockToOrigin(site_instance->GetIsolationContext(),
|
|
site_instance->GetSiteURL(),
|
|
site_instance->IsGuest())) {
|
|
ChildProcessSecurityPolicyImpl::GetInstance()->LockToOrigin(
|
|
site_instance->GetIsolationContext(), process_id,
|
|
site_instance->GetSiteURL());
|
|
}
|
|
}
|
|
|
|
} // namespace
|
|
|
|
class ChildProcessSecurityPolicyTest : public testing::Test {
|
|
public:
|
|
ChildProcessSecurityPolicyTest()
|
|
: task_environment_(BrowserTaskEnvironment::REAL_IO_THREAD),
|
|
old_browser_client_(nullptr) {}
|
|
|
|
void SetUp() override {
|
|
old_browser_client_ = SetBrowserClientForTesting(&test_browser_client_);
|
|
|
|
// Claim to always handle chrome:// URLs because the CPSP's notion of
|
|
// allowing WebUI bindings is hard-wired to this particular scheme.
|
|
test_browser_client_.AddScheme(kChromeUIScheme);
|
|
|
|
// Claim to always handle file:// URLs like the browser would.
|
|
// net::URLRequest::IsHandledURL() no longer claims support for default
|
|
// protocols as this is the responsibility of the browser (which is
|
|
// responsible for adding the appropriate ProtocolHandler).
|
|
test_browser_client_.AddScheme(url::kFileScheme);
|
|
}
|
|
|
|
void TearDown() override {
|
|
test_browser_client_.ClearSchemes();
|
|
SetBrowserClientForTesting(old_browser_client_);
|
|
}
|
|
|
|
// Helpers to construct (key, value) entries used to validate the
|
|
// isolated_origins_ map. The key is a site URL, calculated from the
|
|
// provided origin, and the value is a list of IsolatedOriginEntries. These
|
|
// helpers are members of ChildProcessSecurityPolicyTest so they can access
|
|
// the private IsolatedOriginEntry struct.
|
|
using IsolatedOriginEntry =
|
|
ChildProcessSecurityPolicyImpl::IsolatedOriginEntry;
|
|
// Converts |min_browsing_instance_id|, |origin| -> (site_url, {entry})
|
|
// where site_url is created from |origin| and
|
|
// entry contains |origin| and |min_browsing_instance_id|.
|
|
auto GetIsolatedOriginEntry(int min_browsing_instance_id,
|
|
const url::Origin& origin,
|
|
bool isolate_all_subdomains = false) {
|
|
return std::pair<GURL, std::vector<IsolatedOriginEntry>>(
|
|
SiteInstanceImpl::GetSiteForOrigin(origin),
|
|
{IsolatedOriginEntry(
|
|
origin,
|
|
BrowsingInstanceId::FromUnsafeValue(min_browsing_instance_id),
|
|
nullptr, nullptr, isolate_all_subdomains,
|
|
IsolatedOriginSource::TEST)});
|
|
}
|
|
// Converts |origin| -> (site_url, {entry})
|
|
// where site_url is created from |origin| and
|
|
// entry contains |origin| and the latest BrowsingInstance ID.
|
|
auto GetIsolatedOriginEntry(const url::Origin& origin,
|
|
bool isolate_all_subdomains = false) {
|
|
return GetIsolatedOriginEntry(
|
|
SiteInstanceImpl::NextBrowsingInstanceId().GetUnsafeValue(), origin,
|
|
isolate_all_subdomains);
|
|
}
|
|
// Converts |origin1|, |origin2| -> (site_url, {entry1, entry2})
|
|
// where |site_url| is created from |origin1|, but is assumed to be the
|
|
// same for |origin2| (i.e., |origin1| and |origin2| are
|
|
// same-site),
|
|
// entry1 contains |origin1| and the latest BrowsingInstance ID,
|
|
// entry2 contains |origin2| and the latest BrowsingInstance ID.
|
|
auto GetIsolatedOriginEntry(const url::Origin& origin1,
|
|
const url::Origin& origin2,
|
|
bool origin1_isolate_all_subdomains = false,
|
|
bool origin2_isolate_all_subdomains = false) {
|
|
EXPECT_EQ(SiteInstanceImpl::GetSiteForOrigin(origin1),
|
|
SiteInstanceImpl::GetSiteForOrigin(origin2));
|
|
return std::pair<GURL, std::vector<IsolatedOriginEntry>>(
|
|
SiteInstanceImpl::GetSiteForOrigin(origin1),
|
|
{IsolatedOriginEntry(origin1,
|
|
SiteInstanceImpl::NextBrowsingInstanceId(),
|
|
nullptr, nullptr, origin1_isolate_all_subdomains,
|
|
IsolatedOriginSource::TEST),
|
|
IsolatedOriginEntry(origin2,
|
|
SiteInstanceImpl::NextBrowsingInstanceId(),
|
|
nullptr, nullptr, origin2_isolate_all_subdomains,
|
|
IsolatedOriginSource::TEST)});
|
|
}
|
|
|
|
bool IsIsolatedOrigin(BrowserContext* context,
|
|
int browsing_instance_id,
|
|
const url::Origin& origin) {
|
|
ChildProcessSecurityPolicyImpl* p =
|
|
ChildProcessSecurityPolicyImpl::GetInstance();
|
|
return p->IsIsolatedOrigin(
|
|
IsolationContext(
|
|
BrowsingInstanceId::FromUnsafeValue(browsing_instance_id), context),
|
|
origin);
|
|
}
|
|
|
|
// Returns the number of isolated origin entries for a particular origin.
|
|
// There may be more than one such entry if each is associated with a
|
|
// different profile.
|
|
int GetIsolatedOriginEntryCount(const url::Origin& origin) {
|
|
ChildProcessSecurityPolicyImpl* p =
|
|
ChildProcessSecurityPolicyImpl::GetInstance();
|
|
GURL key(SiteInstanceImpl::GetSiteForOrigin(origin));
|
|
base::AutoLock isolated_origins_lock(p->isolated_origins_lock_);
|
|
auto origins_for_key = p->isolated_origins_[key];
|
|
return std::count_if(origins_for_key.begin(), origins_for_key.end(),
|
|
[origin](const IsolatedOriginEntry& entry) {
|
|
return entry.origin() == origin;
|
|
});
|
|
}
|
|
|
|
void CheckGetSiteForURL(BrowserContext* context,
|
|
std::map<GURL, GURL> to_test) {
|
|
for (const auto& entry : to_test) {
|
|
EXPECT_EQ(SiteInstanceImpl::GetSiteForURL(IsolationContext(context),
|
|
entry.first),
|
|
entry.second);
|
|
}
|
|
}
|
|
|
|
protected:
|
|
void RegisterTestScheme(const std::string& scheme) {
|
|
test_browser_client_.AddScheme(scheme);
|
|
}
|
|
|
|
void GrantPermissionsForFile(ChildProcessSecurityPolicyImpl* p,
|
|
int child_id,
|
|
const base::FilePath& file,
|
|
int permissions) {
|
|
p->GrantPermissionsForFile(child_id, file, permissions);
|
|
}
|
|
|
|
void CheckHasNoFileSystemPermission(ChildProcessSecurityPolicyImpl* p,
|
|
const std::string& child_id) {
|
|
EXPECT_FALSE(p->CanReadFileSystem(kRendererID, child_id));
|
|
EXPECT_FALSE(p->CanReadWriteFileSystem(kRendererID, child_id));
|
|
EXPECT_FALSE(p->CanCopyIntoFileSystem(kRendererID, child_id));
|
|
EXPECT_FALSE(p->CanDeleteFromFileSystem(kRendererID, child_id));
|
|
}
|
|
|
|
void CheckHasNoFileSystemFilePermission(ChildProcessSecurityPolicyImpl* p,
|
|
const base::FilePath& file,
|
|
const storage::FileSystemURL& url) {
|
|
EXPECT_FALSE(p->CanReadFile(kRendererID, file));
|
|
EXPECT_FALSE(p->CanCreateReadWriteFile(kRendererID, file));
|
|
EXPECT_FALSE(p->CanReadFileSystemFile(kRendererID, url));
|
|
EXPECT_FALSE(p->CanWriteFileSystemFile(kRendererID, url));
|
|
EXPECT_FALSE(p->CanCreateFileSystemFile(kRendererID, url));
|
|
EXPECT_FALSE(p->CanCreateReadWriteFileSystemFile(kRendererID, url));
|
|
EXPECT_FALSE(p->CanCopyIntoFileSystemFile(kRendererID, url));
|
|
EXPECT_FALSE(p->CanDeleteFileSystemFile(kRendererID, url));
|
|
|
|
auto handle = p->CreateHandle(kRendererID);
|
|
EXPECT_FALSE(handle.CanReadFile(file));
|
|
EXPECT_FALSE(handle.CanReadFileSystemFile(url));
|
|
}
|
|
|
|
BrowserContext* browser_context() { return &browser_context_; }
|
|
|
|
private:
|
|
BrowserTaskEnvironment task_environment_;
|
|
TestBrowserContext browser_context_;
|
|
ChildProcessSecurityPolicyTestBrowserClient test_browser_client_;
|
|
ContentBrowserClient* old_browser_client_;
|
|
};
|
|
|
|
|
|
TEST_F(ChildProcessSecurityPolicyTest, IsWebSafeSchemeTest) {
|
|
ChildProcessSecurityPolicyImpl* p =
|
|
ChildProcessSecurityPolicyImpl::GetInstance();
|
|
|
|
EXPECT_TRUE(p->IsWebSafeScheme(url::kHttpScheme));
|
|
EXPECT_TRUE(p->IsWebSafeScheme(url::kHttpsScheme));
|
|
EXPECT_TRUE(p->IsWebSafeScheme(url::kFtpScheme));
|
|
EXPECT_TRUE(p->IsWebSafeScheme(url::kDataScheme));
|
|
EXPECT_TRUE(p->IsWebSafeScheme("feed"));
|
|
EXPECT_TRUE(p->IsWebSafeScheme(url::kBlobScheme));
|
|
EXPECT_TRUE(p->IsWebSafeScheme(url::kFileSystemScheme));
|
|
|
|
EXPECT_FALSE(p->IsWebSafeScheme("registered-web-safe-scheme"));
|
|
p->RegisterWebSafeScheme("registered-web-safe-scheme");
|
|
EXPECT_TRUE(p->IsWebSafeScheme("registered-web-safe-scheme"));
|
|
|
|
EXPECT_FALSE(p->IsWebSafeScheme(kChromeUIScheme));
|
|
}
|
|
|
|
TEST_F(ChildProcessSecurityPolicyTest, IsPseudoSchemeTest) {
|
|
ChildProcessSecurityPolicyImpl* p =
|
|
ChildProcessSecurityPolicyImpl::GetInstance();
|
|
|
|
EXPECT_TRUE(p->IsPseudoScheme(url::kAboutScheme));
|
|
EXPECT_TRUE(p->IsPseudoScheme(url::kJavaScriptScheme));
|
|
EXPECT_TRUE(p->IsPseudoScheme(kViewSourceScheme));
|
|
EXPECT_TRUE(p->IsPseudoScheme(kGoogleChromeScheme));
|
|
|
|
EXPECT_FALSE(p->IsPseudoScheme("registered-pseudo-scheme"));
|
|
p->RegisterPseudoScheme("registered-pseudo-scheme");
|
|
EXPECT_TRUE(p->IsPseudoScheme("registered-pseudo-scheme"));
|
|
|
|
EXPECT_FALSE(p->IsPseudoScheme(kChromeUIScheme));
|
|
}
|
|
|
|
TEST_F(ChildProcessSecurityPolicyTest, StandardSchemesTest) {
|
|
ChildProcessSecurityPolicyImpl* p =
|
|
ChildProcessSecurityPolicyImpl::GetInstance();
|
|
|
|
p->Add(kRendererID, browser_context());
|
|
|
|
auto handle = p->CreateHandle(kRendererID);
|
|
|
|
// Safe to request, redirect or commit.
|
|
EXPECT_TRUE(p->CanRequestURL(kRendererID, GURL("http://www.google.com/")));
|
|
EXPECT_TRUE(p->CanRequestURL(kRendererID, GURL("https://www.paypal.com/")));
|
|
EXPECT_TRUE(p->CanRequestURL(kRendererID, GURL("ftp://ftp.gnu.org/")));
|
|
EXPECT_TRUE(p->CanRequestURL(kRendererID, GURL("data:text/html,<b>Hi</b>")));
|
|
EXPECT_TRUE(p->CanRequestURL(
|
|
kRendererID, GURL("filesystem:http://localhost/temporary/a.gif")));
|
|
EXPECT_TRUE(p->CanRedirectToURL(GURL("http://www.google.com/")));
|
|
EXPECT_TRUE(p->CanRedirectToURL(GURL("https://www.paypal.com/")));
|
|
EXPECT_TRUE(p->CanRedirectToURL(GURL("ftp://ftp.gnu.org/")));
|
|
EXPECT_TRUE(p->CanRedirectToURL(GURL("data:text/html,<b>Hi</b>")));
|
|
EXPECT_TRUE(
|
|
p->CanRedirectToURL(GURL("filesystem:http://localhost/temporary/a.gif")));
|
|
|
|
const std::vector<std::string> kCommitURLs({
|
|
"http://www.google.com/",
|
|
"https://www.paypal.com/",
|
|
"ftp://ftp.gnu.org/",
|
|
"data:text/html,<b>Hi</b>",
|
|
"filesystem:http://localhost/temporary/a.gif",
|
|
});
|
|
for (const auto url_string : kCommitURLs) {
|
|
const GURL commit_url(url_string);
|
|
if (AreAllSitesIsolatedForTesting() && IsCitadelProtectionEnabled()) {
|
|
// A non-locked process cannot access URL (because with
|
|
// site-per-process all the URLs need to be isolated).
|
|
EXPECT_FALSE(p->CanCommitURL(kRendererID, commit_url)) << commit_url;
|
|
EXPECT_FALSE(handle.CanCommitURL(commit_url)) << commit_url;
|
|
} else {
|
|
EXPECT_TRUE(p->CanCommitURL(kRendererID, commit_url)) << commit_url;
|
|
EXPECT_TRUE(handle.CanCommitURL(commit_url)) << commit_url;
|
|
}
|
|
}
|
|
|
|
// Dangerous to request, commit, or set as origin header.
|
|
EXPECT_FALSE(p->CanRequestURL(kRendererID,
|
|
GURL("file:///etc/passwd")));
|
|
EXPECT_FALSE(p->CanRequestURL(kRendererID, GetWebUIURL("foo/bar")));
|
|
EXPECT_FALSE(p->CanRequestURL(kRendererID,
|
|
GURL("view-source:http://www.google.com/")));
|
|
EXPECT_TRUE(p->CanRedirectToURL(GURL("file:///etc/passwd")));
|
|
EXPECT_TRUE(p->CanRedirectToURL(GetWebUIURL("foo/bar")));
|
|
EXPECT_FALSE(p->CanRedirectToURL(GURL("view-source:http://www.google.com/")));
|
|
EXPECT_FALSE(p->CanRedirectToURL(GURL(kUnreachableWebDataURL)));
|
|
|
|
const std::vector<std::string> kFailedCommitURLs(
|
|
{"file:///etc/passwd", "view-source:http://www.google.com/",
|
|
kUnreachableWebDataURL, GetWebUIURL("foo/bar").spec()});
|
|
for (const auto url_string : kFailedCommitURLs) {
|
|
const GURL commit_url(url_string);
|
|
EXPECT_FALSE(p->CanCommitURL(kRendererID, commit_url)) << commit_url;
|
|
EXPECT_FALSE(handle.CanCommitURL(commit_url)) << commit_url;
|
|
}
|
|
|
|
p->Remove(kRendererID);
|
|
}
|
|
|
|
TEST_F(ChildProcessSecurityPolicyTest, BlobSchemeTest) {
|
|
ChildProcessSecurityPolicyImpl* p =
|
|
ChildProcessSecurityPolicyImpl::GetInstance();
|
|
|
|
GURL localhost_url("http://localhost/");
|
|
p->Add(kRendererID, browser_context());
|
|
LockProcessIfNeeded(kRendererID, browser_context(), localhost_url);
|
|
|
|
EXPECT_TRUE(
|
|
p->CanRequestURL(kRendererID, GURL("blob:http://localhost/some-guid")));
|
|
EXPECT_TRUE(p->CanRequestURL(kRendererID, GURL("blob:null/some-guid")));
|
|
EXPECT_TRUE(
|
|
p->CanRequestURL(kRendererID, GURL("blob:http://localhost/some-guid")));
|
|
EXPECT_TRUE(p->CanRequestURL(kRendererID, GURL("blob:NulL/some-guid")));
|
|
EXPECT_TRUE(
|
|
p->CanRequestURL(kRendererID, GURL("blob:NulL/some-guid#fragment")));
|
|
EXPECT_TRUE(p->CanRequestURL(kRendererID, GURL("blob:NulL/some-guid?query")));
|
|
EXPECT_FALSE(p->CanRequestURL(
|
|
kRendererID, GURL("blob:http://username@localhost/some-guid")));
|
|
EXPECT_FALSE(p->CanRequestURL(
|
|
kRendererID, GURL("blob:http://username @localhost/some-guid")));
|
|
EXPECT_FALSE(p->CanRequestURL(kRendererID, GURL("blob:blob:some-guid")));
|
|
EXPECT_FALSE(p->CanRequestURL(kRendererID, GURL("blob:some-guid")));
|
|
EXPECT_FALSE(p->CanRequestURL(kRendererID,
|
|
GURL("blob:filesystem:http://localhost/path")));
|
|
EXPECT_FALSE(p->CanRequestURL(kRendererID,
|
|
GURL("filesystem:blob:http://localhost/guid")));
|
|
|
|
EXPECT_TRUE(p->CanRedirectToURL(GURL("blob:http://localhost/some-guid")));
|
|
EXPECT_TRUE(p->CanRedirectToURL(GURL("blob:null/some-guid")));
|
|
EXPECT_TRUE(p->CanRedirectToURL(GURL("blob:http://localhost/some-guid")));
|
|
EXPECT_TRUE(p->CanRedirectToURL(GURL("blob:NulL/some-guid")));
|
|
EXPECT_TRUE(p->CanRedirectToURL(GURL("blob:NulL/some-guid#fragment")));
|
|
EXPECT_TRUE(p->CanRedirectToURL(GURL("blob:NulL/some-guid?query")));
|
|
EXPECT_TRUE(
|
|
p->CanRedirectToURL(GURL("blob:http://username@localhost/some-guid")));
|
|
EXPECT_TRUE(p->CanRedirectToURL(
|
|
GURL("blob:http://username @localhost/some-guid")));
|
|
EXPECT_TRUE(p->CanRedirectToURL(GURL("blob:blob:some-guid")));
|
|
EXPECT_TRUE(p->CanRedirectToURL(GURL("blob:some-guid")));
|
|
EXPECT_TRUE(
|
|
p->CanRedirectToURL(GURL("blob:filesystem:http://localhost/path")));
|
|
EXPECT_FALSE(
|
|
p->CanRedirectToURL(GURL("filesystem:blob:http://localhost/guid")));
|
|
|
|
EXPECT_TRUE(
|
|
p->CanCommitURL(kRendererID, GURL("blob:http://localhost/some-guid")));
|
|
EXPECT_TRUE(p->CanCommitURL(kRendererID, GURL("blob:null/some-guid")));
|
|
EXPECT_TRUE(
|
|
p->CanCommitURL(kRendererID, GURL("blob:http://localhost/some-guid")));
|
|
EXPECT_TRUE(p->CanCommitURL(kRendererID, GURL("blob:NulL/some-guid")));
|
|
EXPECT_TRUE(
|
|
p->CanCommitURL(kRendererID, GURL("blob:NulL/some-guid#fragment")));
|
|
EXPECT_FALSE(p->CanCommitURL(
|
|
kRendererID, GURL("blob:http://username@localhost/some-guid")));
|
|
EXPECT_FALSE(p->CanCommitURL(
|
|
kRendererID, GURL("blob:http://username @localhost/some-guid")));
|
|
EXPECT_FALSE(p->CanCommitURL(kRendererID, GURL("blob:blob:some-guid")));
|
|
EXPECT_FALSE(p->CanCommitURL(kRendererID, GURL("blob:some-guid")));
|
|
EXPECT_FALSE(p->CanCommitURL(kRendererID,
|
|
GURL("blob:filesystem:http://localhost/path")));
|
|
EXPECT_FALSE(p->CanCommitURL(kRendererID,
|
|
GURL("filesystem:blob:http://localhost/guid")));
|
|
|
|
p->Remove(kRendererID);
|
|
}
|
|
|
|
TEST_F(ChildProcessSecurityPolicyTest, AboutTest) {
|
|
ChildProcessSecurityPolicyImpl* p =
|
|
ChildProcessSecurityPolicyImpl::GetInstance();
|
|
|
|
p->Add(kRendererID, browser_context());
|
|
|
|
EXPECT_TRUE(p->CanRequestURL(kRendererID, GURL("about:blank")));
|
|
EXPECT_FALSE(p->CanRequestURL(kRendererID, GURL("about:BlAnK")));
|
|
EXPECT_FALSE(p->CanRequestURL(kRendererID, GURL("aBouT:BlAnK")));
|
|
EXPECT_TRUE(p->CanRequestURL(kRendererID, GURL("aBouT:blank")));
|
|
EXPECT_TRUE(p->CanRedirectToURL(GURL("about:blank")));
|
|
EXPECT_FALSE(p->CanRedirectToURL(GURL("about:BlAnK")));
|
|
EXPECT_FALSE(p->CanRedirectToURL(GURL("aBouT:BlAnK")));
|
|
EXPECT_TRUE(p->CanRedirectToURL(GURL("aBouT:blank")));
|
|
EXPECT_TRUE(p->CanCommitURL(kRendererID, GURL("about:blank")));
|
|
EXPECT_FALSE(p->CanCommitURL(kRendererID, GURL("about:BlAnK")));
|
|
EXPECT_FALSE(p->CanCommitURL(kRendererID, GURL("aBouT:BlAnK")));
|
|
EXPECT_TRUE(p->CanCommitURL(kRendererID, GURL("aBouT:blank")));
|
|
|
|
EXPECT_TRUE(p->CanRequestURL(kRendererID, GURL("about:srcdoc")));
|
|
EXPECT_FALSE(p->CanRedirectToURL(GURL("about:srcdoc")));
|
|
EXPECT_TRUE(p->CanCommitURL(kRendererID, GURL("about:srcdoc")));
|
|
EXPECT_FALSE(p->CanRequestURL(kRendererID, GURL("about:SRCDOC")));
|
|
EXPECT_FALSE(p->CanCommitURL(kRendererID, GURL("about:SRCDOC")));
|
|
|
|
EXPECT_FALSE(p->CanRequestURL(kRendererID, GURL("about:crash")));
|
|
EXPECT_FALSE(p->CanRequestURL(kRendererID, GURL("about:cache")));
|
|
EXPECT_FALSE(p->CanRequestURL(kRendererID, GURL("about:hang")));
|
|
EXPECT_FALSE(p->CanRequestURL(kRendererID, GURL("about:version")));
|
|
EXPECT_FALSE(p->CanRedirectToURL(GURL("about:crash")));
|
|
EXPECT_FALSE(p->CanRedirectToURL(GURL("about:cache")));
|
|
EXPECT_FALSE(p->CanRedirectToURL(GURL("about:hang")));
|
|
EXPECT_FALSE(p->CanRedirectToURL(GURL("about:version")));
|
|
EXPECT_FALSE(p->CanCommitURL(kRendererID, GURL("about:crash")));
|
|
EXPECT_FALSE(p->CanCommitURL(kRendererID, GURL("about:cache")));
|
|
EXPECT_FALSE(p->CanCommitURL(kRendererID, GURL("about:hang")));
|
|
EXPECT_FALSE(p->CanCommitURL(kRendererID, GURL("about:version")));
|
|
|
|
EXPECT_FALSE(p->CanRequestURL(kRendererID, GURL("aBoUt:version")));
|
|
EXPECT_FALSE(p->CanRequestURL(kRendererID, GURL("about:CrASh")));
|
|
EXPECT_FALSE(p->CanRequestURL(kRendererID, GURL("abOuT:cAChe")));
|
|
EXPECT_FALSE(p->CanRedirectToURL(GURL("aBoUt:version")));
|
|
EXPECT_FALSE(p->CanRedirectToURL(GURL("about:CrASh")));
|
|
EXPECT_FALSE(p->CanRedirectToURL(GURL("abOuT:cAChe")));
|
|
EXPECT_FALSE(p->CanCommitURL(kRendererID, GURL("aBoUt:version")));
|
|
EXPECT_FALSE(p->CanCommitURL(kRendererID, GURL("about:CrASh")));
|
|
EXPECT_FALSE(p->CanCommitURL(kRendererID, GURL("abOuT:cAChe")));
|
|
EXPECT_FALSE(p->CanCommitURL(kRendererID, GURL("aBoUt:version")));
|
|
|
|
// Requests for about: pages should be denied.
|
|
p->GrantCommitURL(kRendererID, GURL("about:crash"));
|
|
EXPECT_FALSE(p->CanRequestURL(kRendererID, GURL("about:crash")));
|
|
EXPECT_FALSE(p->CanRedirectToURL(GURL("about:crash")));
|
|
EXPECT_FALSE(p->CanCommitURL(kRendererID, GURL("about:crash")));
|
|
|
|
p->Remove(kRendererID);
|
|
}
|
|
|
|
TEST_F(ChildProcessSecurityPolicyTest, JavaScriptTest) {
|
|
ChildProcessSecurityPolicyImpl* p =
|
|
ChildProcessSecurityPolicyImpl::GetInstance();
|
|
|
|
p->Add(kRendererID, browser_context());
|
|
|
|
EXPECT_FALSE(p->CanRequestURL(kRendererID, GURL("javascript:alert('xss')")));
|
|
EXPECT_FALSE(p->CanRedirectToURL(GURL("javascript:alert('xss')")));
|
|
EXPECT_FALSE(p->CanCommitURL(kRendererID, GURL("javascript:alert('xss')")));
|
|
p->GrantCommitURL(kRendererID, GURL("javascript:alert('xss')"));
|
|
EXPECT_FALSE(p->CanRequestURL(kRendererID, GURL("javascript:alert('xss')")));
|
|
EXPECT_FALSE(p->CanRedirectToURL(GURL("javascript:alert('xss')")));
|
|
EXPECT_FALSE(p->CanCommitURL(kRendererID, GURL("javascript:alert('xss')")));
|
|
|
|
p->Remove(kRendererID);
|
|
}
|
|
|
|
TEST_F(ChildProcessSecurityPolicyTest, RegisterWebSafeSchemeTest) {
|
|
ChildProcessSecurityPolicyImpl* p =
|
|
ChildProcessSecurityPolicyImpl::GetInstance();
|
|
|
|
p->Add(kRendererID, browser_context());
|
|
|
|
// Currently, "asdf" is destined for ShellExecute, so it is allowed to be
|
|
// requested but not committed.
|
|
EXPECT_TRUE(p->CanRequestURL(kRendererID, GURL("asdf:rockers")));
|
|
EXPECT_TRUE(p->CanRedirectToURL(GURL("asdf:rockers")));
|
|
EXPECT_FALSE(p->CanCommitURL(kRendererID, GURL("asdf:rockers")));
|
|
|
|
// Once we register "asdf", we default to deny.
|
|
RegisterTestScheme("asdf");
|
|
EXPECT_FALSE(p->CanRequestURL(kRendererID, GURL("asdf:rockers")));
|
|
EXPECT_TRUE(p->CanRedirectToURL(GURL("asdf:rockers")));
|
|
EXPECT_FALSE(p->CanCommitURL(kRendererID, GURL("asdf:rockers")));
|
|
|
|
// We can allow new schemes by adding them to the whitelist.
|
|
p->RegisterWebSafeScheme("asdf");
|
|
EXPECT_TRUE(p->CanRequestURL(kRendererID, GURL("asdf:rockers")));
|
|
EXPECT_TRUE(p->CanRedirectToURL(GURL("asdf:rockers")));
|
|
if (AreAllSitesIsolatedForTesting() && IsCitadelProtectionEnabled()) {
|
|
// With site-per-process, all URLs (including the one below) will ask to be
|
|
// hosted in isolated processes. Since |p| is not locked, CanCommitURL
|
|
// should return false.
|
|
EXPECT_FALSE(p->CanCommitURL(kRendererID, GURL("asdf:rockers")));
|
|
|
|
// After locking the process, CanCommitURL should start returning true.
|
|
LockProcessIfNeeded(kRendererID, browser_context(), GURL("asdf:rockers"));
|
|
EXPECT_TRUE(p->CanCommitURL(kRendererID, GURL("asdf:rockers")));
|
|
} else {
|
|
EXPECT_TRUE(p->CanCommitURL(kRendererID, GURL("asdf:rockers")));
|
|
}
|
|
|
|
// Cleanup.
|
|
p->Remove(kRendererID);
|
|
}
|
|
|
|
TEST_F(ChildProcessSecurityPolicyTest, CanServiceCommandsTest) {
|
|
ChildProcessSecurityPolicyImpl* p =
|
|
ChildProcessSecurityPolicyImpl::GetInstance();
|
|
|
|
GURL file_url("file:///etc/passwd");
|
|
p->Add(kRendererID, browser_context());
|
|
LockProcessIfNeeded(kRendererID, browser_context(), file_url);
|
|
|
|
EXPECT_FALSE(p->CanRequestURL(kRendererID, GURL("file:///etc/passwd")));
|
|
EXPECT_TRUE(p->CanRedirectToURL(GURL("file:///etc/passwd")));
|
|
EXPECT_FALSE(p->CanCommitURL(kRendererID, GURL("file:///etc/passwd")));
|
|
p->GrantCommitURL(kRendererID, GURL("file:///etc/passwd"));
|
|
EXPECT_TRUE(p->CanRequestURL(kRendererID, GURL("file:///etc/passwd")));
|
|
EXPECT_TRUE(p->CanRedirectToURL(GURL("file:///etc/passwd")));
|
|
EXPECT_TRUE(p->CanCommitURL(kRendererID, GURL("file:///etc/passwd")));
|
|
|
|
// We should forget our state if we repeat a renderer id.
|
|
p->Remove(kRendererID);
|
|
p->Add(kRendererID, browser_context());
|
|
EXPECT_FALSE(p->CanRequestURL(kRendererID, GURL("file:///etc/passwd")));
|
|
EXPECT_TRUE(p->CanRedirectToURL(GURL("file:///etc/passwd")));
|
|
EXPECT_FALSE(p->CanCommitURL(kRendererID, GURL("file:///etc/passwd")));
|
|
p->Remove(kRendererID);
|
|
}
|
|
|
|
TEST_F(ChildProcessSecurityPolicyTest, ViewSource) {
|
|
ChildProcessSecurityPolicyImpl* p =
|
|
ChildProcessSecurityPolicyImpl::GetInstance();
|
|
|
|
p->Add(kRendererID, browser_context());
|
|
|
|
// Child processes cannot request view source URLs.
|
|
EXPECT_FALSE(p->CanRequestURL(kRendererID,
|
|
GURL("view-source:http://www.google.com/")));
|
|
EXPECT_FALSE(p->CanRequestURL(kRendererID,
|
|
GURL("view-source:file:///etc/passwd")));
|
|
EXPECT_FALSE(p->CanRequestURL(kRendererID, GURL("file:///etc/passwd")));
|
|
EXPECT_FALSE(p->CanRequestURL(
|
|
kRendererID, GURL("view-source:view-source:http://www.google.com/")));
|
|
|
|
// Child processes cannot be redirected to view source URLs.
|
|
EXPECT_FALSE(p->CanRedirectToURL(GURL("view-source:http://www.google.com/")));
|
|
EXPECT_FALSE(p->CanRedirectToURL(GURL("view-source:file:///etc/passwd")));
|
|
EXPECT_TRUE(p->CanRedirectToURL(GURL("file:///etc/passwd")));
|
|
EXPECT_FALSE(p->CanRedirectToURL(
|
|
GURL("view-source:view-source:http://www.google.com/")));
|
|
|
|
// View source URLs don't actually commit; the renderer is put into view
|
|
// source mode, and the inner URL commits.
|
|
EXPECT_FALSE(p->CanCommitURL(kRendererID,
|
|
GURL("view-source:http://www.google.com/")));
|
|
EXPECT_FALSE(p->CanCommitURL(kRendererID,
|
|
GURL("view-source:file:///etc/passwd")));
|
|
EXPECT_FALSE(p->CanCommitURL(kRendererID, GURL("file:///etc/passwd")));
|
|
EXPECT_FALSE(p->CanCommitURL(
|
|
kRendererID, GURL("view-source:view-source:http://www.google.com/")));
|
|
|
|
p->GrantCommitURL(kRendererID, GURL("view-source:file:///etc/passwd"));
|
|
EXPECT_FALSE(p->CanRequestURL(kRendererID, GURL("file:///etc/passwd")));
|
|
EXPECT_TRUE(p->CanRedirectToURL(GURL("file:///etc/passwd")));
|
|
EXPECT_FALSE(p->CanCommitURL(kRendererID, GURL("file:///etc/passwd")));
|
|
EXPECT_FALSE(
|
|
p->CanRequestURL(kRendererID, GURL("view-source:file:///etc/passwd")));
|
|
EXPECT_FALSE(p->CanRedirectToURL(GURL("view-source:file:///etc/passwd")));
|
|
EXPECT_FALSE(p->CanCommitURL(kRendererID,
|
|
GURL("view-source:file:///etc/passwd")));
|
|
p->Remove(kRendererID);
|
|
}
|
|
|
|
TEST_F(ChildProcessSecurityPolicyTest, GoogleChromeScheme) {
|
|
ChildProcessSecurityPolicyImpl* p =
|
|
ChildProcessSecurityPolicyImpl::GetInstance();
|
|
|
|
p->Add(kRendererID, browser_context());
|
|
|
|
GURL test_url("googlechrome://whatever");
|
|
|
|
EXPECT_FALSE(p->CanRequestURL(kRendererID, test_url));
|
|
EXPECT_FALSE(p->CanRedirectToURL(test_url));
|
|
EXPECT_FALSE(p->CanCommitURL(kRendererID, test_url));
|
|
|
|
p->Remove(kRendererID);
|
|
}
|
|
|
|
TEST_F(ChildProcessSecurityPolicyTest, GrantCommitURLToNonStandardScheme) {
|
|
ChildProcessSecurityPolicyImpl* p =
|
|
ChildProcessSecurityPolicyImpl::GetInstance();
|
|
|
|
const GURL url("httpxml://awesome");
|
|
const GURL url2("httpxml://also-awesome");
|
|
|
|
ASSERT_TRUE(url::Origin::Create(url).opaque());
|
|
ASSERT_TRUE(url::Origin::Create(url2).opaque());
|
|
RegisterTestScheme("httpxml");
|
|
|
|
p->Add(kRendererID, browser_context());
|
|
LockProcessIfNeeded(kRendererID, browser_context(), url);
|
|
|
|
EXPECT_FALSE(p->CanRequestURL(kRendererID, url));
|
|
EXPECT_FALSE(p->CanRequestURL(kRendererID, url2));
|
|
EXPECT_TRUE(p->CanRedirectToURL(url));
|
|
EXPECT_TRUE(p->CanRedirectToURL(url2));
|
|
EXPECT_FALSE(p->CanCommitURL(kRendererID, url));
|
|
EXPECT_FALSE(p->CanCommitURL(kRendererID, url2));
|
|
|
|
// GrantCommitURL with a non-standard scheme should grant commit access to the
|
|
// entire scheme.
|
|
p->GrantCommitURL(kRendererID, url);
|
|
|
|
EXPECT_TRUE(p->CanRequestURL(kRendererID, url));
|
|
EXPECT_TRUE(p->CanRequestURL(kRendererID, url2));
|
|
EXPECT_TRUE(p->CanRedirectToURL(url));
|
|
EXPECT_TRUE(p->CanRedirectToURL(url2));
|
|
EXPECT_TRUE(p->CanCommitURL(kRendererID, url));
|
|
EXPECT_TRUE(p->CanCommitURL(kRendererID, url2));
|
|
|
|
p->Remove(kRendererID);
|
|
}
|
|
|
|
TEST_F(ChildProcessSecurityPolicyTest, SpecificFile) {
|
|
ChildProcessSecurityPolicyImpl* p =
|
|
ChildProcessSecurityPolicyImpl::GetInstance();
|
|
|
|
GURL icon_url("file:///tmp/foo.png");
|
|
GURL sensitive_url("file:///etc/passwd");
|
|
|
|
p->Add(kRendererID, browser_context());
|
|
LockProcessIfNeeded(kRendererID, browser_context(), sensitive_url);
|
|
|
|
EXPECT_FALSE(p->CanRequestURL(kRendererID, icon_url));
|
|
EXPECT_FALSE(p->CanRequestURL(kRendererID, sensitive_url));
|
|
EXPECT_TRUE(p->CanRedirectToURL(icon_url));
|
|
EXPECT_TRUE(p->CanRedirectToURL(sensitive_url));
|
|
EXPECT_FALSE(p->CanCommitURL(kRendererID, icon_url));
|
|
EXPECT_FALSE(p->CanCommitURL(kRendererID, sensitive_url));
|
|
|
|
p->GrantRequestSpecificFileURL(kRendererID, icon_url);
|
|
EXPECT_TRUE(p->CanRequestURL(kRendererID, icon_url));
|
|
EXPECT_FALSE(p->CanRequestURL(kRendererID, sensitive_url));
|
|
EXPECT_TRUE(p->CanRedirectToURL(icon_url));
|
|
EXPECT_TRUE(p->CanRedirectToURL(sensitive_url));
|
|
EXPECT_TRUE(p->CanCommitURL(kRendererID, icon_url));
|
|
EXPECT_FALSE(p->CanCommitURL(kRendererID, sensitive_url));
|
|
|
|
p->GrantCommitURL(kRendererID, icon_url);
|
|
EXPECT_TRUE(p->CanRequestURL(kRendererID, icon_url));
|
|
EXPECT_TRUE(p->CanRequestURL(kRendererID, sensitive_url));
|
|
EXPECT_TRUE(p->CanRedirectToURL(icon_url));
|
|
EXPECT_TRUE(p->CanRedirectToURL(sensitive_url));
|
|
EXPECT_TRUE(p->CanCommitURL(kRendererID, icon_url));
|
|
EXPECT_TRUE(p->CanCommitURL(kRendererID, sensitive_url));
|
|
|
|
p->Remove(kRendererID);
|
|
}
|
|
|
|
TEST_F(ChildProcessSecurityPolicyTest, FileSystemGrantsTest) {
|
|
ChildProcessSecurityPolicyImpl* p =
|
|
ChildProcessSecurityPolicyImpl::GetInstance();
|
|
|
|
p->Add(kRendererID, browser_context());
|
|
std::string read_id =
|
|
storage::IsolatedContext::GetInstance()->RegisterFileSystemForVirtualPath(
|
|
storage::kFileSystemTypeTest, "read_filesystem", base::FilePath());
|
|
std::string read_write_id =
|
|
storage::IsolatedContext::GetInstance()->RegisterFileSystemForVirtualPath(
|
|
storage::kFileSystemTypeTest,
|
|
"read_write_filesystem",
|
|
base::FilePath());
|
|
std::string copy_into_id =
|
|
storage::IsolatedContext::GetInstance()->RegisterFileSystemForVirtualPath(
|
|
storage::kFileSystemTypeTest,
|
|
"copy_into_filesystem",
|
|
base::FilePath());
|
|
std::string delete_from_id =
|
|
storage::IsolatedContext::GetInstance()->RegisterFileSystemForVirtualPath(
|
|
storage::kFileSystemTypeTest,
|
|
"delete_from_filesystem",
|
|
base::FilePath());
|
|
|
|
// Test initially having no permissions.
|
|
CheckHasNoFileSystemPermission(p, read_id);
|
|
CheckHasNoFileSystemPermission(p, read_write_id);
|
|
CheckHasNoFileSystemPermission(p, copy_into_id);
|
|
CheckHasNoFileSystemPermission(p, delete_from_id);
|
|
|
|
// Testing varying combinations of grants and checks.
|
|
p->GrantReadFileSystem(kRendererID, read_id);
|
|
EXPECT_TRUE(p->CanReadFileSystem(kRendererID, read_id));
|
|
EXPECT_FALSE(p->CanReadWriteFileSystem(kRendererID, read_id));
|
|
EXPECT_FALSE(p->CanCopyIntoFileSystem(kRendererID, read_id));
|
|
EXPECT_FALSE(p->CanDeleteFromFileSystem(kRendererID, read_id));
|
|
|
|
p->GrantReadFileSystem(kRendererID, read_write_id);
|
|
p->GrantWriteFileSystem(kRendererID, read_write_id);
|
|
EXPECT_TRUE(p->CanReadFileSystem(kRendererID, read_write_id));
|
|
EXPECT_TRUE(p->CanReadWriteFileSystem(kRendererID, read_write_id));
|
|
EXPECT_FALSE(p->CanCopyIntoFileSystem(kRendererID, read_write_id));
|
|
EXPECT_FALSE(p->CanDeleteFromFileSystem(kRendererID, read_write_id));
|
|
|
|
p->GrantCopyIntoFileSystem(kRendererID, copy_into_id);
|
|
EXPECT_FALSE(p->CanReadFileSystem(kRendererID, copy_into_id));
|
|
EXPECT_FALSE(p->CanReadWriteFileSystem(kRendererID, copy_into_id));
|
|
EXPECT_TRUE(p->CanCopyIntoFileSystem(kRendererID, copy_into_id));
|
|
EXPECT_FALSE(p->CanDeleteFromFileSystem(kRendererID, copy_into_id));
|
|
|
|
p->GrantDeleteFromFileSystem(kRendererID, delete_from_id);
|
|
EXPECT_FALSE(p->CanReadFileSystem(kRendererID, delete_from_id));
|
|
EXPECT_FALSE(p->CanReadWriteFileSystem(kRendererID, delete_from_id));
|
|
EXPECT_FALSE(p->CanCopyIntoFileSystem(kRendererID, delete_from_id));
|
|
EXPECT_TRUE(p->CanDeleteFromFileSystem(kRendererID, delete_from_id));
|
|
|
|
// Test revoke permissions on renderer ID removal.
|
|
p->Remove(kRendererID);
|
|
CheckHasNoFileSystemPermission(p, read_id);
|
|
CheckHasNoFileSystemPermission(p, read_write_id);
|
|
CheckHasNoFileSystemPermission(p, copy_into_id);
|
|
CheckHasNoFileSystemPermission(p, delete_from_id);
|
|
|
|
// Test having no permissions upon re-adding same renderer ID.
|
|
p->Add(kRendererID, browser_context());
|
|
CheckHasNoFileSystemPermission(p, read_id);
|
|
CheckHasNoFileSystemPermission(p, read_write_id);
|
|
CheckHasNoFileSystemPermission(p, copy_into_id);
|
|
CheckHasNoFileSystemPermission(p, delete_from_id);
|
|
|
|
// Cleanup.
|
|
p->Remove(kRendererID);
|
|
storage::IsolatedContext::GetInstance()->RevokeFileSystem(read_id);
|
|
storage::IsolatedContext::GetInstance()->RevokeFileSystem(read_write_id);
|
|
storage::IsolatedContext::GetInstance()->RevokeFileSystem(copy_into_id);
|
|
storage::IsolatedContext::GetInstance()->RevokeFileSystem(delete_from_id);
|
|
}
|
|
|
|
TEST_F(ChildProcessSecurityPolicyTest, FilePermissionGrantingAndRevoking) {
|
|
ChildProcessSecurityPolicyImpl* p =
|
|
ChildProcessSecurityPolicyImpl::GetInstance();
|
|
|
|
p->RegisterFileSystemPermissionPolicy(
|
|
storage::kFileSystemTypeTest,
|
|
storage::FILE_PERMISSION_USE_FILE_PERMISSION);
|
|
|
|
p->Add(kRendererID, browser_context());
|
|
LockProcessIfNeeded(kRendererID, browser_context(), GURL("http://foo/"));
|
|
|
|
base::FilePath file(TEST_PATH("/dir/testfile"));
|
|
file = file.NormalizePathSeparators();
|
|
storage::FileSystemURL url = storage::FileSystemURL::CreateForTest(
|
|
url::Origin::Create(GURL("http://foo/")), storage::kFileSystemTypeTest,
|
|
file);
|
|
|
|
// Test initially having no permissions.
|
|
CheckHasNoFileSystemFilePermission(p, file, url);
|
|
|
|
// Testing every combination of permissions granting and revoking.
|
|
p->GrantReadFile(kRendererID, file);
|
|
EXPECT_TRUE(p->CanReadFile(kRendererID, file));
|
|
EXPECT_FALSE(p->CanCreateReadWriteFile(kRendererID, file));
|
|
EXPECT_TRUE(p->CanReadFileSystemFile(kRendererID, url));
|
|
EXPECT_FALSE(p->CanWriteFileSystemFile(kRendererID, url));
|
|
EXPECT_FALSE(p->CanCreateFileSystemFile(kRendererID, url));
|
|
EXPECT_FALSE(p->CanCreateReadWriteFileSystemFile(kRendererID, url));
|
|
EXPECT_FALSE(p->CanCopyIntoFileSystemFile(kRendererID, url));
|
|
EXPECT_FALSE(p->CanDeleteFileSystemFile(kRendererID, url));
|
|
p->RevokeAllPermissionsForFile(kRendererID, file);
|
|
CheckHasNoFileSystemFilePermission(p, file, url);
|
|
|
|
p->GrantCreateReadWriteFile(kRendererID, file);
|
|
EXPECT_TRUE(p->CanReadFile(kRendererID, file));
|
|
EXPECT_TRUE(p->CanCreateReadWriteFile(kRendererID, file));
|
|
EXPECT_TRUE(p->CanReadFileSystemFile(kRendererID, url));
|
|
EXPECT_TRUE(p->CanWriteFileSystemFile(kRendererID, url));
|
|
EXPECT_TRUE(p->CanCreateFileSystemFile(kRendererID, url));
|
|
EXPECT_TRUE(p->CanCreateReadWriteFileSystemFile(kRendererID, url));
|
|
EXPECT_TRUE(p->CanCopyIntoFileSystemFile(kRendererID, url));
|
|
EXPECT_TRUE(p->CanDeleteFileSystemFile(kRendererID, url));
|
|
p->RevokeAllPermissionsForFile(kRendererID, file);
|
|
CheckHasNoFileSystemFilePermission(p, file, url);
|
|
|
|
// Test revoke permissions on renderer ID removal.
|
|
p->GrantCreateReadWriteFile(kRendererID, file);
|
|
EXPECT_TRUE(p->CanReadFile(kRendererID, file));
|
|
EXPECT_TRUE(p->CanCreateReadWriteFile(kRendererID, file));
|
|
EXPECT_TRUE(p->CanReadFileSystemFile(kRendererID, url));
|
|
EXPECT_TRUE(p->CanWriteFileSystemFile(kRendererID, url));
|
|
EXPECT_TRUE(p->CanCreateFileSystemFile(kRendererID, url));
|
|
EXPECT_TRUE(p->CanCreateReadWriteFileSystemFile(kRendererID, url));
|
|
EXPECT_TRUE(p->CanCopyIntoFileSystemFile(kRendererID, url));
|
|
EXPECT_TRUE(p->CanDeleteFileSystemFile(kRendererID, url));
|
|
p->Remove(kRendererID);
|
|
CheckHasNoFileSystemFilePermission(p, file, url);
|
|
|
|
// Test having no permissions upon re-adding same renderer ID.
|
|
p->Add(kRendererID, browser_context());
|
|
CheckHasNoFileSystemFilePermission(p, file, url);
|
|
LockProcessIfNeeded(kRendererID, browser_context(), GURL("http://foo/"));
|
|
CheckHasNoFileSystemFilePermission(p, file, url);
|
|
|
|
// Cleanup.
|
|
p->Remove(kRendererID);
|
|
}
|
|
|
|
TEST_F(ChildProcessSecurityPolicyTest, FilePermissions) {
|
|
base::FilePath granted_file = base::FilePath(TEST_PATH("/home/joe"));
|
|
base::FilePath sibling_file = base::FilePath(TEST_PATH("/home/bob"));
|
|
base::FilePath child_file = base::FilePath(TEST_PATH("/home/joe/file"));
|
|
base::FilePath parent_file = base::FilePath(TEST_PATH("/home"));
|
|
base::FilePath parent_slash_file = base::FilePath(TEST_PATH("/home/"));
|
|
base::FilePath child_traversal1 =
|
|
base::FilePath(TEST_PATH("/home/joe/././file"));
|
|
base::FilePath child_traversal2 = base::FilePath(
|
|
TEST_PATH("/home/joe/file/../otherfile"));
|
|
base::FilePath evil_traversal1 =
|
|
base::FilePath(TEST_PATH("/home/joe/../../etc/passwd"));
|
|
base::FilePath evil_traversal2 = base::FilePath(
|
|
TEST_PATH("/home/joe/./.././../etc/passwd"));
|
|
base::FilePath self_traversal =
|
|
base::FilePath(TEST_PATH("/home/joe/../joe/file"));
|
|
base::FilePath relative_file = base::FilePath(FILE_PATH_LITERAL("home/joe"));
|
|
|
|
ChildProcessSecurityPolicyImpl* p =
|
|
ChildProcessSecurityPolicyImpl::GetInstance();
|
|
|
|
// Grant permissions for a file.
|
|
p->Add(kRendererID, browser_context());
|
|
EXPECT_FALSE(p->HasPermissionsForFile(kRendererID, granted_file,
|
|
base::File::FLAG_OPEN));
|
|
|
|
GrantPermissionsForFile(p, kRendererID, granted_file,
|
|
base::File::FLAG_OPEN |
|
|
base::File::FLAG_OPEN_TRUNCATED |
|
|
base::File::FLAG_READ |
|
|
base::File::FLAG_WRITE);
|
|
EXPECT_TRUE(p->HasPermissionsForFile(kRendererID, granted_file,
|
|
base::File::FLAG_OPEN |
|
|
base::File::FLAG_OPEN_TRUNCATED |
|
|
base::File::FLAG_READ |
|
|
base::File::FLAG_WRITE));
|
|
EXPECT_TRUE(p->HasPermissionsForFile(kRendererID, granted_file,
|
|
base::File::FLAG_OPEN |
|
|
base::File::FLAG_READ));
|
|
EXPECT_FALSE(p->HasPermissionsForFile(kRendererID, granted_file,
|
|
base::File::FLAG_CREATE));
|
|
EXPECT_FALSE(p->HasPermissionsForFile(kRendererID, granted_file, 0));
|
|
EXPECT_FALSE(p->HasPermissionsForFile(kRendererID, granted_file,
|
|
base::File::FLAG_CREATE |
|
|
base::File::FLAG_OPEN_TRUNCATED |
|
|
base::File::FLAG_READ |
|
|
base::File::FLAG_WRITE));
|
|
EXPECT_FALSE(p->HasPermissionsForFile(kRendererID, sibling_file,
|
|
base::File::FLAG_OPEN |
|
|
base::File::FLAG_READ));
|
|
EXPECT_FALSE(p->HasPermissionsForFile(kRendererID, parent_file,
|
|
base::File::FLAG_OPEN |
|
|
base::File::FLAG_READ));
|
|
EXPECT_TRUE(p->HasPermissionsForFile(kRendererID, child_file,
|
|
base::File::FLAG_OPEN |
|
|
base::File::FLAG_READ));
|
|
EXPECT_TRUE(p->HasPermissionsForFile(kRendererID, child_traversal1,
|
|
base::File::FLAG_OPEN |
|
|
base::File::FLAG_READ));
|
|
EXPECT_TRUE(p->HasPermissionsForFile(kRendererID, child_traversal2,
|
|
base::File::FLAG_OPEN |
|
|
base::File::FLAG_READ));
|
|
EXPECT_FALSE(p->HasPermissionsForFile(kRendererID, evil_traversal1,
|
|
base::File::FLAG_OPEN |
|
|
base::File::FLAG_READ));
|
|
EXPECT_FALSE(p->HasPermissionsForFile(kRendererID, evil_traversal2,
|
|
base::File::FLAG_OPEN |
|
|
base::File::FLAG_READ));
|
|
// CPSP doesn't allow this case for the sake of simplicity.
|
|
EXPECT_FALSE(p->HasPermissionsForFile(kRendererID, self_traversal,
|
|
base::File::FLAG_OPEN |
|
|
base::File::FLAG_READ));
|
|
p->Remove(kRendererID);
|
|
|
|
// Grant permissions for the directory the file is in.
|
|
p->Add(kRendererID, browser_context());
|
|
EXPECT_FALSE(p->HasPermissionsForFile(kRendererID, granted_file,
|
|
base::File::FLAG_OPEN));
|
|
GrantPermissionsForFile(p, kRendererID, parent_file,
|
|
base::File::FLAG_OPEN |
|
|
base::File::FLAG_READ);
|
|
EXPECT_TRUE(p->HasPermissionsForFile(kRendererID, granted_file,
|
|
base::File::FLAG_OPEN));
|
|
EXPECT_FALSE(p->HasPermissionsForFile(kRendererID, granted_file,
|
|
base::File::FLAG_READ |
|
|
base::File::FLAG_WRITE));
|
|
p->Remove(kRendererID);
|
|
|
|
// Grant permissions for the directory the file is in (with trailing '/').
|
|
p->Add(kRendererID, browser_context());
|
|
EXPECT_FALSE(p->HasPermissionsForFile(kRendererID, granted_file,
|
|
base::File::FLAG_OPEN));
|
|
GrantPermissionsForFile(p, kRendererID, parent_slash_file,
|
|
base::File::FLAG_OPEN |
|
|
base::File::FLAG_READ);
|
|
EXPECT_TRUE(p->HasPermissionsForFile(kRendererID, granted_file,
|
|
base::File::FLAG_OPEN));
|
|
EXPECT_FALSE(p->HasPermissionsForFile(kRendererID, granted_file,
|
|
base::File::FLAG_READ |
|
|
base::File::FLAG_WRITE));
|
|
|
|
// Grant permissions for the file (should overwrite the permissions granted
|
|
// for the directory).
|
|
GrantPermissionsForFile(p, kRendererID, granted_file,
|
|
base::File::FLAG_TEMPORARY);
|
|
EXPECT_FALSE(p->HasPermissionsForFile(kRendererID, granted_file,
|
|
base::File::FLAG_OPEN));
|
|
EXPECT_TRUE(p->HasPermissionsForFile(kRendererID, granted_file,
|
|
base::File::FLAG_TEMPORARY));
|
|
|
|
// Revoke all permissions for the file (it should inherit its permissions
|
|
// from the directory again).
|
|
p->RevokeAllPermissionsForFile(kRendererID, granted_file);
|
|
EXPECT_TRUE(p->HasPermissionsForFile(kRendererID, granted_file,
|
|
base::File::FLAG_OPEN |
|
|
base::File::FLAG_READ));
|
|
EXPECT_FALSE(p->HasPermissionsForFile(kRendererID, granted_file,
|
|
base::File::FLAG_TEMPORARY));
|
|
p->Remove(kRendererID);
|
|
|
|
p->Add(kRendererID, browser_context());
|
|
GrantPermissionsForFile(p, kRendererID, relative_file,
|
|
base::File::FLAG_OPEN);
|
|
EXPECT_FALSE(p->HasPermissionsForFile(kRendererID, relative_file,
|
|
base::File::FLAG_OPEN));
|
|
p->Remove(kRendererID);
|
|
}
|
|
|
|
TEST_F(ChildProcessSecurityPolicyTest, CanServiceWebUIBindings) {
|
|
ChildProcessSecurityPolicyImpl* p =
|
|
ChildProcessSecurityPolicyImpl::GetInstance();
|
|
|
|
const GURL url(GetWebUIURL("thumb/http://www.google.com/"));
|
|
const GURL other_url(GetWebUIURL("not-thumb/"));
|
|
const url::Origin origin = url::Origin::Create(url);
|
|
{
|
|
p->Add(kRendererID, browser_context());
|
|
LockProcessIfNeeded(kRendererID, browser_context(), url);
|
|
|
|
EXPECT_FALSE(p->HasWebUIBindings(kRendererID));
|
|
|
|
EXPECT_FALSE(p->CanRequestURL(kRendererID, url));
|
|
EXPECT_FALSE(p->CanCommitURL(kRendererID, url));
|
|
EXPECT_TRUE(p->CanRedirectToURL(url));
|
|
|
|
EXPECT_FALSE(p->CanRequestURL(kRendererID, other_url));
|
|
EXPECT_FALSE(p->CanCommitURL(kRendererID, other_url));
|
|
EXPECT_TRUE(p->CanRedirectToURL(other_url));
|
|
|
|
p->GrantWebUIBindings(kRendererID, BINDINGS_POLICY_WEB_UI);
|
|
|
|
EXPECT_TRUE(p->HasWebUIBindings(kRendererID));
|
|
|
|
EXPECT_FALSE(p->CanRequestURL(kRendererID, url));
|
|
EXPECT_FALSE(p->CanCommitURL(kRendererID, url));
|
|
EXPECT_TRUE(p->CanRedirectToURL(url));
|
|
|
|
EXPECT_FALSE(p->CanRequestURL(kRendererID, other_url));
|
|
EXPECT_FALSE(p->CanCommitURL(kRendererID, other_url));
|
|
EXPECT_TRUE(p->CanRedirectToURL(other_url));
|
|
|
|
p->GrantCommitOrigin(kRendererID, origin);
|
|
|
|
EXPECT_TRUE(p->CanRequestURL(kRendererID, url));
|
|
EXPECT_TRUE(p->CanCommitURL(kRendererID, url));
|
|
EXPECT_TRUE(p->CanRedirectToURL(url));
|
|
|
|
EXPECT_FALSE(p->CanRequestURL(kRendererID, other_url));
|
|
EXPECT_FALSE(p->CanCommitURL(kRendererID, other_url));
|
|
EXPECT_TRUE(p->CanRedirectToURL(other_url));
|
|
|
|
p->Remove(kRendererID);
|
|
}
|
|
|
|
{
|
|
p->Add(kRendererID, browser_context());
|
|
LockProcessIfNeeded(kRendererID, browser_context(), url);
|
|
|
|
EXPECT_FALSE(p->HasWebUIBindings(kRendererID));
|
|
|
|
EXPECT_FALSE(p->CanRequestURL(kRendererID, url));
|
|
EXPECT_FALSE(p->CanCommitURL(kRendererID, url));
|
|
EXPECT_TRUE(p->CanRedirectToURL(url));
|
|
|
|
EXPECT_FALSE(p->CanRequestURL(kRendererID, other_url));
|
|
EXPECT_FALSE(p->CanCommitURL(kRendererID, other_url));
|
|
EXPECT_TRUE(p->CanRedirectToURL(other_url));
|
|
|
|
p->GrantWebUIBindings(kRendererID, BINDINGS_POLICY_MOJO_WEB_UI);
|
|
|
|
EXPECT_TRUE(p->HasWebUIBindings(kRendererID));
|
|
|
|
EXPECT_FALSE(p->CanRequestURL(kRendererID, url));
|
|
EXPECT_FALSE(p->CanCommitURL(kRendererID, url));
|
|
EXPECT_TRUE(p->CanRedirectToURL(url));
|
|
|
|
EXPECT_FALSE(p->CanRequestURL(kRendererID, other_url));
|
|
EXPECT_FALSE(p->CanCommitURL(kRendererID, other_url));
|
|
EXPECT_TRUE(p->CanRedirectToURL(other_url));
|
|
|
|
p->GrantCommitOrigin(kRendererID, origin);
|
|
|
|
EXPECT_TRUE(p->CanRequestURL(kRendererID, url));
|
|
EXPECT_TRUE(p->CanCommitURL(kRendererID, url));
|
|
EXPECT_TRUE(p->CanRedirectToURL(url));
|
|
|
|
EXPECT_FALSE(p->CanRequestURL(kRendererID, other_url));
|
|
EXPECT_FALSE(p->CanCommitURL(kRendererID, other_url));
|
|
EXPECT_TRUE(p->CanRedirectToURL(other_url));
|
|
|
|
p->Remove(kRendererID);
|
|
}
|
|
|
|
{
|
|
p->Add(kRendererID, browser_context());
|
|
LockProcessIfNeeded(kRendererID, browser_context(), url);
|
|
|
|
EXPECT_FALSE(p->HasWebUIBindings(kRendererID));
|
|
|
|
EXPECT_FALSE(p->CanRequestURL(kRendererID, url));
|
|
EXPECT_FALSE(p->CanCommitURL(kRendererID, url));
|
|
EXPECT_TRUE(p->CanRedirectToURL(url));
|
|
|
|
EXPECT_FALSE(p->CanRequestURL(kRendererID, other_url));
|
|
EXPECT_FALSE(p->CanCommitURL(kRendererID, other_url));
|
|
EXPECT_TRUE(p->CanRedirectToURL(other_url));
|
|
|
|
p->GrantWebUIBindings(kRendererID,
|
|
BINDINGS_POLICY_WEB_UI | BINDINGS_POLICY_MOJO_WEB_UI);
|
|
|
|
EXPECT_TRUE(p->HasWebUIBindings(kRendererID));
|
|
|
|
EXPECT_FALSE(p->CanRequestURL(kRendererID, url));
|
|
EXPECT_FALSE(p->CanCommitURL(kRendererID, url));
|
|
EXPECT_TRUE(p->CanRedirectToURL(url));
|
|
|
|
EXPECT_FALSE(p->CanRequestURL(kRendererID, other_url));
|
|
EXPECT_FALSE(p->CanCommitURL(kRendererID, other_url));
|
|
EXPECT_TRUE(p->CanRedirectToURL(other_url));
|
|
|
|
p->GrantCommitOrigin(kRendererID, origin);
|
|
|
|
EXPECT_TRUE(p->CanRequestURL(kRendererID, url));
|
|
EXPECT_TRUE(p->CanCommitURL(kRendererID, url));
|
|
EXPECT_TRUE(p->CanRedirectToURL(url));
|
|
|
|
EXPECT_FALSE(p->CanRequestURL(kRendererID, other_url));
|
|
EXPECT_FALSE(p->CanCommitURL(kRendererID, other_url));
|
|
EXPECT_TRUE(p->CanRedirectToURL(other_url));
|
|
|
|
p->Remove(kRendererID);
|
|
}
|
|
}
|
|
|
|
TEST_F(ChildProcessSecurityPolicyTest, RemoveRace) {
|
|
ChildProcessSecurityPolicyImpl* p =
|
|
ChildProcessSecurityPolicyImpl::GetInstance();
|
|
|
|
GURL url("file:///etc/passwd");
|
|
base::FilePath file(TEST_PATH("/etc/passwd"));
|
|
|
|
p->Add(kRendererID, browser_context());
|
|
|
|
p->GrantCommitURL(kRendererID, url);
|
|
p->GrantReadFile(kRendererID, file);
|
|
p->GrantWebUIBindings(kRendererID,
|
|
BINDINGS_POLICY_WEB_UI | BINDINGS_POLICY_MOJO_WEB_UI);
|
|
|
|
EXPECT_TRUE(p->CanRequestURL(kRendererID, url));
|
|
EXPECT_TRUE(p->CanRedirectToURL(url));
|
|
EXPECT_TRUE(p->CanReadFile(kRendererID, file));
|
|
EXPECT_TRUE(p->HasWebUIBindings(kRendererID));
|
|
|
|
p->Remove(kRendererID);
|
|
|
|
// Renderers are added and removed on the UI thread, but the policy can be
|
|
// queried on the IO thread. The ChildProcessSecurityPolicy needs to be
|
|
// prepared to answer policy questions about renderers who no longer exist.
|
|
|
|
// In this case, we default to secure behavior.
|
|
EXPECT_FALSE(p->CanRequestURL(kRendererID, url));
|
|
EXPECT_TRUE(p->CanRedirectToURL(url));
|
|
EXPECT_FALSE(p->CanReadFile(kRendererID, file));
|
|
EXPECT_FALSE(p->HasWebUIBindings(kRendererID));
|
|
}
|
|
|
|
// Tests behavior of CanAccessDataForOrigin() during race conditions that
|
|
// can occur during Remove(). It verifies that permissions for a child ID are
|
|
// preserved after a Remove() call until the task, that Remove() has posted to
|
|
// the IO thread, has run.
|
|
//
|
|
// We use a combination of waitable events and extra tasks posted to the
|
|
// threads to capture permission state from the UI & IO threads during the
|
|
// removal process. It is intended to simulate pending tasks that could be
|
|
// run on each thread during removal.
|
|
TEST_F(ChildProcessSecurityPolicyTest, RemoveRace_CanAccessDataForOrigin) {
|
|
ChildProcessSecurityPolicyImpl* p =
|
|
ChildProcessSecurityPolicyImpl::GetInstance();
|
|
|
|
GURL url("file:///etc/passwd");
|
|
|
|
p->Add(kRendererID, browser_context());
|
|
LockProcessIfNeeded(kRendererID, browser_context(), url);
|
|
|
|
base::WaitableEvent ready_for_remove_event;
|
|
base::WaitableEvent remove_called_event;
|
|
base::WaitableEvent pending_remove_complete_event;
|
|
|
|
// Keep track of the return value for CanAccessDataForOrigin at various
|
|
// points in time during the test.
|
|
bool io_before_remove = false;
|
|
bool io_while_io_task_pending = false;
|
|
bool io_after_io_task_completed = false;
|
|
bool ui_before_remove = false;
|
|
bool ui_while_io_task_pending = false;
|
|
bool ui_after_io_task_completed = false;
|
|
|
|
// Post a task that will run on the IO thread before the task that
|
|
// Remove() will post to the IO thread.
|
|
base::PostTask(
|
|
FROM_HERE, {BrowserThread::IO}, base::BindLambdaForTesting([&]() {
|
|
// Capture state on the IO thread before Remove() is called.
|
|
io_before_remove = p->CanAccessDataForOrigin(kRendererID, url);
|
|
|
|
// Tell the UI thread we are ready for Remove() to be called.
|
|
ready_for_remove_event.Signal();
|
|
|
|
// Wait for Remove() to be called on the UI thread.
|
|
remove_called_event.Wait();
|
|
|
|
// Capture state after Remove() is called, but before its task on
|
|
// the IO thread runs.
|
|
io_while_io_task_pending = p->CanAccessDataForOrigin(kRendererID, url);
|
|
}));
|
|
|
|
ready_for_remove_event.Wait();
|
|
|
|
ui_before_remove = p->CanAccessDataForOrigin(kRendererID, url);
|
|
|
|
p->Remove(kRendererID);
|
|
|
|
// Post a task to run after the task Remove() posted on the IO thread.
|
|
base::PostTask(FROM_HERE, {BrowserThread::IO},
|
|
base::BindLambdaForTesting([&]() {
|
|
io_after_io_task_completed =
|
|
p->CanAccessDataForOrigin(kRendererID, url);
|
|
|
|
// Tell the UI thread that the task from Remove()
|
|
// has completed on the IO thread.
|
|
pending_remove_complete_event.Signal();
|
|
}));
|
|
|
|
// Capture state after Remove() has been called, but before its IO thread
|
|
// task has run. We know the IO thread task hasn't run yet because the
|
|
// task we posted before the Remove() call is waiting for us to signal
|
|
// |remove_called_event|.
|
|
ui_while_io_task_pending = p->CanAccessDataForOrigin(kRendererID, url);
|
|
|
|
// Unblock the IO thread so the pending remove events can run.
|
|
remove_called_event.Signal();
|
|
|
|
pending_remove_complete_event.Wait();
|
|
|
|
// Capture state after IO thread task has run.
|
|
ui_after_io_task_completed = p->CanAccessDataForOrigin(kRendererID, url);
|
|
|
|
// Run pending UI thread tasks.
|
|
base::RunLoop run_loop;
|
|
run_loop.RunUntilIdle();
|
|
|
|
bool ui_after_remove_complete = p->CanAccessDataForOrigin(kRendererID, url);
|
|
bool io_after_remove_complete = false;
|
|
base::WaitableEvent after_remove_complete_event;
|
|
|
|
base::PostTask(
|
|
FROM_HERE, {BrowserThread::IO}, base::BindLambdaForTesting([&]() {
|
|
io_after_remove_complete = p->CanAccessDataForOrigin(kRendererID, url);
|
|
|
|
// Tell the UI thread that this task has
|
|
// has completed on the IO thread.
|
|
after_remove_complete_event.Signal();
|
|
}));
|
|
|
|
// Wait for the task we just posted to the IO thread to complete.
|
|
after_remove_complete_event.Wait();
|
|
|
|
// Verify expected states at various parts of the removal.
|
|
// Note: IO thread is expected to keep pre-Remove() permissions until
|
|
// the task Remove() posted runs on the IO thread.
|
|
EXPECT_TRUE(io_before_remove);
|
|
EXPECT_TRUE(io_while_io_task_pending);
|
|
EXPECT_FALSE(io_after_io_task_completed);
|
|
|
|
EXPECT_TRUE(ui_before_remove);
|
|
EXPECT_FALSE(ui_while_io_task_pending);
|
|
EXPECT_FALSE(ui_after_io_task_completed);
|
|
|
|
EXPECT_FALSE(ui_after_remove_complete);
|
|
EXPECT_FALSE(io_after_remove_complete);
|
|
}
|
|
|
|
// This test is similar to the one above that verifies CanAccessDataForOrigin()
|
|
// behavior during process shutdown. This particular test verifies that a
|
|
// ChildProcessSecurityPolicyImpl::Handle extends the lifetime of the security
|
|
// state beyond the Remove() call. This represents the case where a Mojo service
|
|
// on the IO thread still receives calls after the RPHI that created it has
|
|
// been destroyed.
|
|
//
|
|
// We use a combination of waitable events and extra tasks posted to the
|
|
// threads to capture permission state from the UI & IO threads during the
|
|
// removal process. It is intended to simulate pending tasks that could be
|
|
// run on each thread during removal.
|
|
TEST_F(ChildProcessSecurityPolicyTest, HandleExtendsSecurityStateLifetime) {
|
|
ChildProcessSecurityPolicyImpl* p =
|
|
ChildProcessSecurityPolicyImpl::GetInstance();
|
|
|
|
GURL url("file:///etc/passwd");
|
|
|
|
p->Add(kRendererID, browser_context());
|
|
LockProcessIfNeeded(kRendererID, browser_context(), url);
|
|
|
|
auto handle = p->CreateHandle(kRendererID);
|
|
|
|
base::WaitableEvent ready_for_remove_event;
|
|
base::WaitableEvent remove_called_event;
|
|
base::WaitableEvent ready_for_handle_invalidation_event;
|
|
|
|
// Keep track of the return value for CanAccessDataForOrigin at various
|
|
// points in time during the test.
|
|
bool io_before_remove = false;
|
|
bool io_after_remove = false;
|
|
bool ui_before_remove = false;
|
|
bool ui_after_remove = false;
|
|
|
|
// Post a task that will run on the IO thread before the task that
|
|
// Remove() will post to the IO thread.
|
|
base::PostTask(FROM_HERE, {BrowserThread::IO},
|
|
base::BindLambdaForTesting([&]() {
|
|
// Capture state on the IO thread before Remove() is called.
|
|
io_before_remove = handle.CanAccessDataForOrigin(url);
|
|
|
|
// Tell the UI thread we are ready for Remove() to be called.
|
|
ready_for_remove_event.Signal();
|
|
}));
|
|
|
|
ready_for_remove_event.Wait();
|
|
|
|
ui_before_remove = handle.CanAccessDataForOrigin(url);
|
|
|
|
p->Remove(kRendererID);
|
|
|
|
ui_after_remove = handle.CanAccessDataForOrigin(url);
|
|
|
|
// Post a task to verify post-Remove() state on the IO thread.
|
|
base::PostTask(FROM_HERE, {BrowserThread::IO},
|
|
base::BindLambdaForTesting([&]() {
|
|
io_after_remove = handle.CanAccessDataForOrigin(url);
|
|
|
|
// Tell the UI thread that we are ready to invalidate the
|
|
// handle.
|
|
ready_for_handle_invalidation_event.Signal();
|
|
}));
|
|
|
|
ready_for_handle_invalidation_event.Wait();
|
|
|
|
// Invalidate the handle so it triggers destruction of the security state.
|
|
handle = ChildProcessSecurityPolicyImpl::Handle();
|
|
|
|
bool ui_after_handle_invalidation = handle.CanAccessDataForOrigin(url);
|
|
bool io_after_handle_invalidation = false;
|
|
base::WaitableEvent after_invalidation_complete_event;
|
|
|
|
base::PostTask(
|
|
FROM_HERE, {BrowserThread::IO}, base::BindLambdaForTesting([&]() {
|
|
io_after_handle_invalidation = handle.CanAccessDataForOrigin(url);
|
|
|
|
// Tell the UI thread that this task has
|
|
// has completed on the IO thread.
|
|
after_invalidation_complete_event.Signal();
|
|
}));
|
|
|
|
// Wait for the task we just posted to the IO thread to complete.
|
|
after_invalidation_complete_event.Wait();
|
|
|
|
// Verify expected states at various parts of the removal.
|
|
// Note: IO thread is expected to keep pre-Remove() permissions until
|
|
// |handle| is invalidated and the task RemoveProcessReferenceLocked() posted
|
|
// runs on the IO thread.
|
|
EXPECT_TRUE(io_before_remove);
|
|
EXPECT_TRUE(ui_before_remove);
|
|
|
|
EXPECT_TRUE(io_after_remove);
|
|
EXPECT_TRUE(ui_after_remove);
|
|
|
|
EXPECT_FALSE(io_after_handle_invalidation);
|
|
EXPECT_FALSE(ui_after_handle_invalidation);
|
|
}
|
|
|
|
TEST_F(ChildProcessSecurityPolicyTest, CanAccessDataForOrigin_URL) {
|
|
ChildProcessSecurityPolicyImpl* p =
|
|
ChildProcessSecurityPolicyImpl::GetInstance();
|
|
|
|
GURL file_url("file:///etc/passwd");
|
|
GURL foo_http_url("http://foo.com/index.html");
|
|
GURL foo_blob_url("blob:http://foo.com/43d75119-d7af-4471-a293-07c6b3d7e61a");
|
|
GURL foo_filesystem_url("filesystem:http://foo.com/temporary/test.html");
|
|
GURL bar_http_url("http://bar.com/index.html");
|
|
|
|
const std::vector<GURL> kAllTestUrls = {file_url, foo_http_url, foo_blob_url,
|
|
foo_filesystem_url, bar_http_url};
|
|
|
|
// Test invalid ID and invalid Handle cases.
|
|
auto handle = p->CreateHandle(kRendererID);
|
|
for (auto url : kAllTestUrls) {
|
|
EXPECT_FALSE(p->CanAccessDataForOrigin(kRendererID, url)) << url;
|
|
EXPECT_FALSE(handle.CanAccessDataForOrigin(bar_http_url)) << url;
|
|
}
|
|
|
|
TestBrowserContext browser_context;
|
|
p->Add(kRendererID, &browser_context);
|
|
|
|
// Replace the old invalid handle with a new valid handle.
|
|
handle = p->CreateHandle(kRendererID);
|
|
|
|
// Verify unlocked origin permissions.
|
|
for (auto url : kAllTestUrls) {
|
|
if (AreAllSitesIsolatedForTesting() && IsCitadelProtectionEnabled()) {
|
|
// A non-locked process cannot access URLs below (because with
|
|
// site-per-process all the URLs need to be isolated).
|
|
EXPECT_FALSE(p->CanAccessDataForOrigin(kRendererID, url)) << url;
|
|
EXPECT_FALSE(handle.CanAccessDataForOrigin(url)) << url;
|
|
} else {
|
|
EXPECT_TRUE(p->CanAccessDataForOrigin(kRendererID, url)) << url;
|
|
EXPECT_TRUE(handle.CanAccessDataForOrigin(url)) << url;
|
|
}
|
|
}
|
|
|
|
// Isolate |http_url| so we can't get a default SiteInstance.
|
|
p->AddIsolatedOrigins({url::Origin::Create(foo_http_url)},
|
|
IsolatedOriginSource::TEST, &browser_context);
|
|
|
|
// Lock process to |http_url| origin.
|
|
scoped_refptr<SiteInstanceImpl> foo_instance =
|
|
SiteInstanceImpl::CreateForURL(&browser_context, foo_http_url);
|
|
EXPECT_FALSE(foo_instance->IsDefaultSiteInstance());
|
|
LockProcessIfNeeded(kRendererID, &browser_context, foo_http_url);
|
|
|
|
// Verify that file access is no longer allowed.
|
|
EXPECT_FALSE(p->CanAccessDataForOrigin(kRendererID, file_url));
|
|
EXPECT_TRUE(p->CanAccessDataForOrigin(kRendererID, foo_http_url));
|
|
EXPECT_TRUE(p->CanAccessDataForOrigin(kRendererID, foo_blob_url));
|
|
EXPECT_TRUE(p->CanAccessDataForOrigin(kRendererID, foo_filesystem_url));
|
|
EXPECT_FALSE(p->CanAccessDataForOrigin(kRendererID, bar_http_url));
|
|
EXPECT_FALSE(handle.CanAccessDataForOrigin(file_url));
|
|
EXPECT_TRUE(handle.CanAccessDataForOrigin(foo_http_url));
|
|
EXPECT_TRUE(handle.CanAccessDataForOrigin(foo_blob_url));
|
|
EXPECT_TRUE(handle.CanAccessDataForOrigin(foo_filesystem_url));
|
|
EXPECT_FALSE(handle.CanAccessDataForOrigin(bar_http_url));
|
|
|
|
// Invalidate handle so it does not preserve security state beyond Remove().
|
|
handle = ChildProcessSecurityPolicyImpl::Handle();
|
|
|
|
p->Remove(kRendererID);
|
|
|
|
// Post a task to the IO loop that then posts a task to the UI loop.
|
|
// This should cause the |run_loop| to return after the removal has completed.
|
|
base::RunLoop run_loop;
|
|
base::PostTaskAndReply(FROM_HERE, {BrowserThread::IO}, base::DoNothing(),
|
|
run_loop.QuitClosure());
|
|
run_loop.Run();
|
|
|
|
// Verify invalid ID is rejected now that Remove() has completed.
|
|
for (auto url : kAllTestUrls) {
|
|
EXPECT_FALSE(p->CanAccessDataForOrigin(kRendererID, url)) << url;
|
|
EXPECT_FALSE(handle.CanAccessDataForOrigin(url)) << url;
|
|
}
|
|
}
|
|
|
|
TEST_F(ChildProcessSecurityPolicyTest, CanAccessDataForOrigin_Origin) {
|
|
ChildProcessSecurityPolicyImpl* p =
|
|
ChildProcessSecurityPolicyImpl::GetInstance();
|
|
|
|
const std::vector<const char*> foo_urls = {
|
|
"http://foo.com/index.html",
|
|
"blob:http://foo.com/43d75119-d7af-4471-a293-07c6b3d7e61a",
|
|
"filesystem:http://foo.com/temporary/test.html",
|
|
// Port differences considered equal.
|
|
"http://foo.com:1234/index.html",
|
|
"blob:http://foo.com:1234/43d75119-d7af-4471-a293-07c6b3d7e61a",
|
|
"filesystem:http://foo.com:1234/temporary/test.html",
|
|
// TODO(acolwell): data: should be in |non_foo_urls| in the long-term.
|
|
"data:text/html,Hello!"};
|
|
|
|
const std::vector<const char*> non_foo_urls = {
|
|
"file:///etc/passwd",
|
|
"http://bar.com/index.html",
|
|
"blob:http://bar.com/43d75119-d7af-4471-a293-07c6b3d7e61a",
|
|
"filesystem:http://bar.com/temporary/test.html",
|
|
// foo.com with a different scheme not considered equal.
|
|
"https://foo.com/index.html",
|
|
"blob:https://foo.com/43d75119-d7af-4471-a293-07c6b3d7e61a",
|
|
"filesystem:https://foo.com/temporary/test.html"};
|
|
|
|
std::vector<url::Origin> foo_origins;
|
|
std::vector<url::Origin> non_foo_origins;
|
|
std::vector<url::Origin> all_origins;
|
|
for (auto* url : foo_urls) {
|
|
auto origin = url::Origin::Create(GURL(url));
|
|
foo_origins.push_back(origin);
|
|
all_origins.push_back(origin);
|
|
}
|
|
auto foo_origin = url::Origin::Create(GURL("http://foo.com"));
|
|
auto opaque_with_foo_precursor = foo_origin.DeriveNewOpaqueOrigin();
|
|
foo_origins.push_back(opaque_with_foo_precursor);
|
|
all_origins.push_back(opaque_with_foo_precursor);
|
|
|
|
for (auto* url : non_foo_urls) {
|
|
auto origin = url::Origin::Create(GURL(url));
|
|
non_foo_origins.push_back(origin);
|
|
all_origins.push_back(origin);
|
|
}
|
|
url::Origin opaque_origin_without_precursor;
|
|
// TODO(acolwell): This should be in |non_foo_origins| in the long-term.
|
|
foo_origins.push_back(opaque_origin_without_precursor);
|
|
all_origins.push_back(opaque_origin_without_precursor);
|
|
|
|
auto opaque_with_bar_precursor =
|
|
url::Origin::Create(GURL("http://bar.com")).DeriveNewOpaqueOrigin();
|
|
non_foo_origins.push_back(opaque_with_bar_precursor);
|
|
all_origins.push_back(opaque_with_bar_precursor);
|
|
|
|
// Test invalid process ID for all cases.
|
|
for (const auto& origin : all_origins)
|
|
EXPECT_FALSE(p->CanAccessDataForOrigin(kRendererID, origin)) << origin;
|
|
|
|
TestBrowserContext browser_context;
|
|
p->Add(kRendererID, &browser_context);
|
|
|
|
// Verify unlocked process permissions.
|
|
for (const auto& origin : all_origins) {
|
|
if (AreAllSitesIsolatedForTesting() && IsCitadelProtectionEnabled()) {
|
|
if (origin.opaque() &&
|
|
!origin.GetTupleOrPrecursorTupleIfOpaque().IsValid()) {
|
|
EXPECT_TRUE(p->CanAccessDataForOrigin(kRendererID, origin)) << origin;
|
|
} else {
|
|
EXPECT_FALSE(p->CanAccessDataForOrigin(kRendererID, origin)) << origin;
|
|
}
|
|
} else {
|
|
EXPECT_TRUE(p->CanAccessDataForOrigin(kRendererID, origin)) << origin;
|
|
}
|
|
}
|
|
|
|
// Isolate |foo_origin| so we can't get a default SiteInstance.
|
|
p->AddIsolatedOrigins({foo_origin}, IsolatedOriginSource::TEST,
|
|
&browser_context);
|
|
|
|
// Lock process to |foo_origin| origin.
|
|
scoped_refptr<SiteInstanceImpl> foo_instance =
|
|
SiteInstanceImpl::CreateForURL(&browser_context, foo_origin.GetURL());
|
|
EXPECT_FALSE(foo_instance->IsDefaultSiteInstance());
|
|
LockProcessIfNeeded(kRendererID, &browser_context, foo_origin.GetURL());
|
|
|
|
// Verify that access is no longer allowed for origins that are not associated
|
|
// with foo.com.
|
|
for (const auto& origin : foo_origins)
|
|
EXPECT_TRUE(p->CanAccessDataForOrigin(kRendererID, origin)) << origin;
|
|
|
|
for (const auto& origin : non_foo_origins)
|
|
EXPECT_FALSE(p->CanAccessDataForOrigin(kRendererID, origin)) << origin;
|
|
|
|
p->Remove(kRendererID);
|
|
|
|
// Post a task to the IO loop that then posts a task to the UI loop.
|
|
// This should cause the |run_loop| to return after the removal has completed.
|
|
base::RunLoop run_loop;
|
|
base::PostTaskAndReply(FROM_HERE, {BrowserThread::IO}, base::DoNothing(),
|
|
run_loop.QuitClosure());
|
|
run_loop.Run();
|
|
|
|
// Verify invalid ID is rejected now that Remove() has completed.
|
|
for (const auto& origin : all_origins)
|
|
EXPECT_FALSE(p->CanAccessDataForOrigin(kRendererID, origin)) << origin;
|
|
}
|
|
|
|
// Test the granting of origin permissions, and their interactions with
|
|
// granting scheme permissions.
|
|
TEST_F(ChildProcessSecurityPolicyTest, OriginGranting) {
|
|
ChildProcessSecurityPolicyImpl* p =
|
|
ChildProcessSecurityPolicyImpl::GetInstance();
|
|
|
|
GURL url_foo1(GetWebUIURL("foo/resource1"));
|
|
GURL url_foo2(GetWebUIURL("foo/resource2"));
|
|
GURL url_bar(GetWebUIURL("bar/resource3"));
|
|
|
|
p->Add(kRendererID, browser_context());
|
|
LockProcessIfNeeded(kRendererID, browser_context(), url_foo1);
|
|
|
|
EXPECT_FALSE(p->CanRequestURL(kRendererID, url_foo1));
|
|
EXPECT_FALSE(p->CanRequestURL(kRendererID, url_foo2));
|
|
EXPECT_FALSE(p->CanRequestURL(kRendererID, url_bar));
|
|
EXPECT_TRUE(p->CanRedirectToURL(url_foo1));
|
|
EXPECT_TRUE(p->CanRedirectToURL(url_foo2));
|
|
EXPECT_TRUE(p->CanRedirectToURL(url_bar));
|
|
EXPECT_FALSE(p->CanCommitURL(kRendererID, url_foo1));
|
|
EXPECT_FALSE(p->CanCommitURL(kRendererID, url_foo2));
|
|
EXPECT_FALSE(p->CanCommitURL(kRendererID, url_bar));
|
|
|
|
p->GrantRequestOrigin(kRendererID, url::Origin::Create(url_foo1));
|
|
|
|
EXPECT_TRUE(p->CanRequestURL(kRendererID, url_foo1));
|
|
EXPECT_TRUE(p->CanRequestURL(kRendererID, url_foo2));
|
|
EXPECT_FALSE(p->CanRequestURL(kRendererID, url_bar));
|
|
EXPECT_TRUE(p->CanRedirectToURL(url_foo1));
|
|
EXPECT_TRUE(p->CanRedirectToURL(url_foo2));
|
|
EXPECT_TRUE(p->CanRedirectToURL(url_bar));
|
|
EXPECT_FALSE(p->CanCommitURL(kRendererID, url_foo1));
|
|
EXPECT_FALSE(p->CanCommitURL(kRendererID, url_foo2));
|
|
EXPECT_FALSE(p->CanCommitURL(kRendererID, url_bar));
|
|
|
|
p->GrantCommitOrigin(kRendererID, url::Origin::Create(url_foo1));
|
|
|
|
EXPECT_TRUE(p->CanRequestURL(kRendererID, url_foo1));
|
|
EXPECT_TRUE(p->CanRequestURL(kRendererID, url_foo2));
|
|
EXPECT_FALSE(p->CanRequestURL(kRendererID, url_bar));
|
|
EXPECT_TRUE(p->CanRedirectToURL(url_foo1));
|
|
EXPECT_TRUE(p->CanRedirectToURL(url_foo2));
|
|
EXPECT_TRUE(p->CanRedirectToURL(url_bar));
|
|
EXPECT_TRUE(p->CanCommitURL(kRendererID, url_foo1));
|
|
EXPECT_TRUE(p->CanCommitURL(kRendererID, url_foo2));
|
|
EXPECT_FALSE(p->CanCommitURL(kRendererID, url_bar));
|
|
|
|
// Make sure this doesn't overwrite the earlier commit grants.
|
|
p->GrantRequestOrigin(kRendererID, url::Origin::Create(url_foo1));
|
|
|
|
EXPECT_TRUE(p->CanRequestURL(kRendererID, url_foo1));
|
|
EXPECT_TRUE(p->CanRequestURL(kRendererID, url_foo2));
|
|
EXPECT_FALSE(p->CanRequestURL(kRendererID, url_bar));
|
|
EXPECT_TRUE(p->CanRedirectToURL(url_foo1));
|
|
EXPECT_TRUE(p->CanRedirectToURL(url_foo2));
|
|
EXPECT_TRUE(p->CanRedirectToURL(url_bar));
|
|
EXPECT_TRUE(p->CanCommitURL(kRendererID, url_foo1));
|
|
EXPECT_TRUE(p->CanCommitURL(kRendererID, url_foo2));
|
|
EXPECT_FALSE(p->CanCommitURL(kRendererID, url_bar));
|
|
|
|
p->Remove(kRendererID);
|
|
}
|
|
|
|
#define LOCKED_EXPECT_THAT(lock, value, matcher) \
|
|
do { \
|
|
base::AutoLock auto_lock(lock); \
|
|
EXPECT_THAT(value, matcher); \
|
|
} while (0);
|
|
|
|
// Verifies ChildProcessSecurityPolicyImpl::AddIsolatedOrigins method.
|
|
TEST_F(ChildProcessSecurityPolicyTest, AddIsolatedOrigins) {
|
|
url::Origin foo = url::Origin::Create(GURL("https://foo.com/"));
|
|
url::Origin bar = url::Origin::Create(GURL("https://bar.com/"));
|
|
url::Origin baz = url::Origin::Create(GURL("https://baz.com/"));
|
|
url::Origin quxfoo = url::Origin::Create(GURL("https://qux.foo.com/"));
|
|
url::Origin baz_http = url::Origin::Create(GURL("http://baz.com/"));
|
|
url::Origin baz_http_8000 = url::Origin::Create(GURL("http://baz.com:8000/"));
|
|
url::Origin baz_https_8000 =
|
|
url::Origin::Create(GURL("https://baz.com:8000/"));
|
|
url::Origin invalid_etld = url::Origin::Create(GURL("https://gov/"));
|
|
|
|
ChildProcessSecurityPolicyImpl* p =
|
|
ChildProcessSecurityPolicyImpl::GetInstance();
|
|
|
|
// Initially there should be no isolated origins.
|
|
LOCKED_EXPECT_THAT(p->isolated_origins_lock_, p->isolated_origins_,
|
|
testing::IsEmpty());
|
|
|
|
// Verify deduplication of the argument.
|
|
p->AddIsolatedOrigins({foo, bar, bar}, IsolatedOriginSource::TEST);
|
|
LOCKED_EXPECT_THAT(
|
|
p->isolated_origins_lock_, p->isolated_origins_,
|
|
testing::UnorderedElementsAre(GetIsolatedOriginEntry(foo),
|
|
GetIsolatedOriginEntry(bar)));
|
|
|
|
// Verify that the old set is extended (not replaced).
|
|
p->AddIsolatedOrigins({baz}, IsolatedOriginSource::TEST);
|
|
LOCKED_EXPECT_THAT(
|
|
p->isolated_origins_lock_, p->isolated_origins_,
|
|
testing::UnorderedElementsAre(GetIsolatedOriginEntry(foo),
|
|
GetIsolatedOriginEntry(bar),
|
|
GetIsolatedOriginEntry(baz)));
|
|
|
|
// Verify deduplication against the old set.
|
|
p->AddIsolatedOrigins({foo}, IsolatedOriginSource::TEST);
|
|
LOCKED_EXPECT_THAT(
|
|
p->isolated_origins_lock_, p->isolated_origins_,
|
|
testing::UnorderedElementsAre(GetIsolatedOriginEntry(foo),
|
|
GetIsolatedOriginEntry(bar),
|
|
GetIsolatedOriginEntry(baz)));
|
|
|
|
// Verify deduplication considers scheme and port differences. Note that
|
|
// origins that differ only in ports map to the same key.
|
|
p->AddIsolatedOrigins({baz, baz_http_8000, baz_https_8000},
|
|
IsolatedOriginSource::TEST);
|
|
LOCKED_EXPECT_THAT(
|
|
p->isolated_origins_lock_, p->isolated_origins_,
|
|
testing::UnorderedElementsAre(
|
|
GetIsolatedOriginEntry(foo), GetIsolatedOriginEntry(bar),
|
|
GetIsolatedOriginEntry(baz), GetIsolatedOriginEntry(baz_http)));
|
|
|
|
// Verify that adding an origin that is invalid for isolation will 1) log a
|
|
// warning and 2) won't CHECK or crash the browser process, 3) will not add
|
|
// the invalid origin, but will add the remaining origins passed to
|
|
// AddIsolatedOrigins. Note that the new |quxfoo| origin should map to the
|
|
// same key (i.e., the https://foo.com/ site URL) as the existing |foo|
|
|
// origin.
|
|
{
|
|
base::test::MockLog mock_log;
|
|
EXPECT_CALL(mock_log,
|
|
Log(::logging::LOG_ERROR, testing::_, testing::_, testing::_,
|
|
testing::HasSubstr(invalid_etld.Serialize())))
|
|
.Times(1);
|
|
|
|
mock_log.StartCapturingLogs();
|
|
p->AddIsolatedOrigins({quxfoo, invalid_etld}, IsolatedOriginSource::TEST);
|
|
LOCKED_EXPECT_THAT(
|
|
p->isolated_origins_lock_, p->isolated_origins_,
|
|
testing::UnorderedElementsAre(
|
|
GetIsolatedOriginEntry(foo, quxfoo), GetIsolatedOriginEntry(bar),
|
|
GetIsolatedOriginEntry(baz), GetIsolatedOriginEntry(baz_http)));
|
|
}
|
|
|
|
// Verify that adding invalid origins via the string variant of
|
|
// AddIsolatedOrigins() logs a warning.
|
|
{
|
|
base::test::MockLog mock_log;
|
|
EXPECT_CALL(mock_log, Log(::logging::LOG_ERROR, testing::_, testing::_,
|
|
testing::_, testing::HasSubstr("about:blank")))
|
|
.Times(1);
|
|
|
|
mock_log.StartCapturingLogs();
|
|
p->AddIsolatedOrigins("about:blank", IsolatedOriginSource::TEST);
|
|
}
|
|
|
|
p->RemoveIsolatedOriginForTesting(foo);
|
|
p->RemoveIsolatedOriginForTesting(quxfoo);
|
|
p->RemoveIsolatedOriginForTesting(bar);
|
|
p->RemoveIsolatedOriginForTesting(baz);
|
|
p->RemoveIsolatedOriginForTesting(baz_http);
|
|
|
|
// We should have removed all isolated origins at this point.
|
|
LOCKED_EXPECT_THAT(p->isolated_origins_lock_, p->isolated_origins_,
|
|
testing::IsEmpty());
|
|
}
|
|
|
|
TEST_F(ChildProcessSecurityPolicyTest, IsolateAllSuborigins) {
|
|
url::Origin qux = url::Origin::Create(GURL("https://qux.com/"));
|
|
IsolatedOriginPattern etld1_wild("https://[*.]foo.com");
|
|
IsolatedOriginPattern etld2_wild("https://[*.]bar.foo.com");
|
|
url::Origin etld1 = url::Origin::Create(GURL("https://foo.com"));
|
|
url::Origin etld2 = url::Origin::Create(GURL("https://bar.foo.com"));
|
|
|
|
ChildProcessSecurityPolicyImpl* p =
|
|
ChildProcessSecurityPolicyImpl::GetInstance();
|
|
|
|
// Check we can add a single wildcard origin.
|
|
p->AddIsolatedOrigins({etld1_wild}, IsolatedOriginSource::TEST);
|
|
|
|
LOCKED_EXPECT_THAT(
|
|
p->isolated_origins_lock_, p->isolated_origins_,
|
|
testing::UnorderedElementsAre(GetIsolatedOriginEntry(etld1, true)));
|
|
|
|
// Add a conventional origin and check they can live side by side.
|
|
p->AddIsolatedOrigins({qux}, IsolatedOriginSource::TEST);
|
|
LOCKED_EXPECT_THAT(
|
|
p->isolated_origins_lock_, p->isolated_origins_,
|
|
testing::UnorderedElementsAre(GetIsolatedOriginEntry(etld1, true),
|
|
GetIsolatedOriginEntry(qux, false)));
|
|
|
|
// Check that a wildcard domain within another wildcard domain can be added.
|
|
p->AddIsolatedOrigins({etld2_wild}, IsolatedOriginSource::TEST);
|
|
LOCKED_EXPECT_THAT(p->isolated_origins_lock_, p->isolated_origins_,
|
|
testing::UnorderedElementsAre(
|
|
GetIsolatedOriginEntry(etld1, etld2, true, true),
|
|
GetIsolatedOriginEntry(qux, false)));
|
|
|
|
// Check that removing a single wildcard domain, that contains another
|
|
// wildcard domain, doesn't affect the isolating behavior of the original
|
|
// wildcard domain.
|
|
p->RemoveIsolatedOriginForTesting(etld1);
|
|
LOCKED_EXPECT_THAT(
|
|
p->isolated_origins_lock_, p->isolated_origins_,
|
|
testing::UnorderedElementsAre(GetIsolatedOriginEntry(etld2, true),
|
|
GetIsolatedOriginEntry(qux, false)));
|
|
|
|
// Removing remaining domains.
|
|
p->RemoveIsolatedOriginForTesting(qux);
|
|
p->RemoveIsolatedOriginForTesting(etld2);
|
|
|
|
LOCKED_EXPECT_THAT(p->isolated_origins_lock_, p->isolated_origins_,
|
|
testing::IsEmpty());
|
|
}
|
|
|
|
// Verify that the isolation behavior for wildcard and non-wildcard origins,
|
|
// singly or in concert, behaves correctly via calls to GetSiteForURL().
|
|
TEST_F(ChildProcessSecurityPolicyTest, WildcardAndNonWildcardOrigins) {
|
|
ChildProcessSecurityPolicyImpl* p =
|
|
ChildProcessSecurityPolicyImpl::GetInstance();
|
|
|
|
// There should be no isolated origins before this test starts.
|
|
LOCKED_EXPECT_THAT(p->isolated_origins_lock_, p->isolated_origins_,
|
|
testing::IsEmpty());
|
|
|
|
// Construct a simple case, a single isolated origin.
|
|
// IsolatedOriginPattern isolated("https://isolated.com");
|
|
IsolatedOriginPattern inner_isolated("https://inner.isolated.com");
|
|
IsolatedOriginPattern wildcard("https://[*.]wildcard.com");
|
|
IsolatedOriginPattern inner_wildcard("https://[*.]inner.wildcard.com");
|
|
|
|
GURL isolated_url("https://isolated.com");
|
|
GURL inner_isolated_url("https://inner.isolated.com");
|
|
GURL host_inner_isolated_url("https://host.inner.isolated.com");
|
|
GURL wildcard_url("https://wildcard.com");
|
|
GURL inner_wildcard_url("https://inner.wildcard.com");
|
|
GURL host_inner_wildcard_url("https://host.inner.wildcard.com");
|
|
GURL unrelated_url("https://unrelated.com");
|
|
|
|
// Verify the isolation behavior of the test patterns before isolating any
|
|
// domains.
|
|
std::map<GURL, GURL> origins_site_test_map{
|
|
{isolated_url, isolated_url},
|
|
{inner_isolated_url, isolated_url},
|
|
{host_inner_isolated_url, isolated_url},
|
|
{wildcard_url, wildcard_url},
|
|
{inner_wildcard_url, wildcard_url},
|
|
{host_inner_wildcard_url, wildcard_url},
|
|
{unrelated_url, unrelated_url},
|
|
};
|
|
CheckGetSiteForURL(browser_context(), origins_site_test_map);
|
|
|
|
// Add |wildcard|, a wildcard origin from a different domain, then verify that
|
|
// the existing behavior of |isolated_url| and |inner_isolated_url| remains
|
|
// unaffected, while all subdomains of wildcard.com are returned as unique
|
|
// sites.
|
|
p->AddIsolatedOrigins({wildcard}, IsolatedOriginSource::TEST);
|
|
origins_site_test_map[inner_wildcard_url] = inner_wildcard_url;
|
|
origins_site_test_map[host_inner_wildcard_url] = host_inner_wildcard_url;
|
|
CheckGetSiteForURL(browser_context(), origins_site_test_map);
|
|
|
|
// Add |inner_isolated|, then verify that querying for |inner_isolated_url|
|
|
// returns |inner_isolated_url| while leaving the wildcard origins unaffected.
|
|
p->AddIsolatedOrigins({inner_isolated}, IsolatedOriginSource::TEST);
|
|
origins_site_test_map[inner_isolated_url] = inner_isolated_url;
|
|
origins_site_test_map[host_inner_isolated_url] = inner_isolated_url;
|
|
CheckGetSiteForURL(browser_context(), origins_site_test_map);
|
|
|
|
// Add |inner_wildcard|. This should not change the behavior of the test
|
|
// above as all subdomains of |inner_wildcard| are contained within
|
|
// |wildcard|.
|
|
p->AddIsolatedOrigins({inner_wildcard}, IsolatedOriginSource::TEST);
|
|
CheckGetSiteForURL(browser_context(), origins_site_test_map);
|
|
|
|
p->RemoveIsolatedOriginForTesting(wildcard.origin());
|
|
p->RemoveIsolatedOriginForTesting(inner_isolated.origin());
|
|
p->RemoveIsolatedOriginForTesting(inner_wildcard.origin());
|
|
|
|
LOCKED_EXPECT_THAT(p->isolated_origins_lock_, p->isolated_origins_,
|
|
testing::IsEmpty());
|
|
}
|
|
|
|
TEST_F(ChildProcessSecurityPolicyTest, WildcardAndNonWildcardEmbedded) {
|
|
ChildProcessSecurityPolicyImpl* p =
|
|
ChildProcessSecurityPolicyImpl::GetInstance();
|
|
|
|
// There should be no isolated origins before this test starts.
|
|
LOCKED_EXPECT_THAT(p->isolated_origins_lock_, p->isolated_origins_,
|
|
testing::IsEmpty());
|
|
|
|
{
|
|
// Test the behavior of a wildcard origin contained within a single
|
|
// isolated origin. Removing the isolated origin should have no effect on
|
|
// the wildcard origin.
|
|
IsolatedOriginPattern isolated("https://isolated.com");
|
|
IsolatedOriginPattern wildcard_isolated(
|
|
"https://[*.]wildcard.isolated.com");
|
|
|
|
GURL isolated_url("https://isolated.com");
|
|
GURL a_isolated_url("https://a.isolated.com");
|
|
GURL wildcard_isolated_url("https://wildcard.isolated.com");
|
|
GURL a_wildcard_isolated_url("https://a.wildcard.isolated.com");
|
|
|
|
p->AddIsolatedOrigins({isolated, wildcard_isolated},
|
|
IsolatedOriginSource::TEST);
|
|
std::map<GURL, GURL> origin_site_map{
|
|
{isolated_url, isolated_url},
|
|
{a_isolated_url, isolated_url},
|
|
{wildcard_isolated_url, wildcard_isolated_url},
|
|
{a_wildcard_isolated_url, a_wildcard_isolated_url},
|
|
};
|
|
|
|
CheckGetSiteForURL(browser_context(), origin_site_map);
|
|
|
|
p->RemoveIsolatedOriginForTesting(isolated.origin());
|
|
p->RemoveIsolatedOriginForTesting(wildcard_isolated.origin());
|
|
}
|
|
|
|
// No isolated origins should persist between tests.
|
|
LOCKED_EXPECT_THAT(p->isolated_origins_lock_, p->isolated_origins_,
|
|
testing::IsEmpty());
|
|
|
|
{
|
|
// A single isolated origin is nested within a wildcard origin. In this
|
|
// scenario the wildcard origin supersedes isolated origins.
|
|
IsolatedOriginPattern wildcard("https://[*.]wildcard.com");
|
|
IsolatedOriginPattern isolated_wildcard("https://isolated.wildcard.com");
|
|
|
|
GURL wildcard_url("https://wildcard.com");
|
|
GURL a_wildcard_url("https://a.wildcard.com");
|
|
GURL isolated_wildcard_url("https://isolated.wildcard.com");
|
|
GURL a_isolated_wildcard_url("https://a.isolated.wildcard.com");
|
|
|
|
p->AddIsolatedOrigins({wildcard, isolated_wildcard},
|
|
IsolatedOriginSource::TEST);
|
|
std::map<GURL, GURL> origin_site_map{
|
|
{wildcard_url, wildcard_url},
|
|
{a_wildcard_url, a_wildcard_url},
|
|
{isolated_wildcard_url, isolated_wildcard_url},
|
|
{a_isolated_wildcard_url, a_isolated_wildcard_url},
|
|
};
|
|
|
|
CheckGetSiteForURL(browser_context(), origin_site_map);
|
|
|
|
p->RemoveIsolatedOriginForTesting(wildcard.origin());
|
|
p->RemoveIsolatedOriginForTesting(isolated_wildcard.origin());
|
|
}
|
|
|
|
LOCKED_EXPECT_THAT(p->isolated_origins_lock_, p->isolated_origins_,
|
|
testing::IsEmpty());
|
|
|
|
{
|
|
// Nest wildcard isolated origins within each other. Verify that removing
|
|
// the outer wildcard origin doesn't affect the inner one.
|
|
IsolatedOriginPattern outer("https://[*.]outer.com");
|
|
IsolatedOriginPattern inner("https://[*.]inner.outer.com");
|
|
|
|
GURL outer_url("https://outer.com");
|
|
GURL a_outer_url("https://a.outer.com");
|
|
GURL inner_url("https://inner.outer.com");
|
|
GURL a_inner_url("https://a.inner.outer.com");
|
|
|
|
p->AddIsolatedOrigins({inner, outer}, IsolatedOriginSource::TEST);
|
|
|
|
std::map<GURL, GURL> origin_site_map{
|
|
{outer_url, outer_url},
|
|
{a_outer_url, a_outer_url},
|
|
{inner_url, inner_url},
|
|
{a_inner_url, a_inner_url},
|
|
};
|
|
|
|
CheckGetSiteForURL(browser_context(), origin_site_map);
|
|
p->RemoveIsolatedOriginForTesting(outer.origin());
|
|
p->RemoveIsolatedOriginForTesting(inner.origin());
|
|
}
|
|
|
|
LOCKED_EXPECT_THAT(p->isolated_origins_lock_, p->isolated_origins_,
|
|
testing::IsEmpty());
|
|
|
|
// Verify that adding a wildcard domain then a then a conventional domain
|
|
// doesn't affect the isolating behavior of the wildcard, i.e. whichever
|
|
// isolated domain is added entered 'wins'.
|
|
{
|
|
IsolatedOriginPattern wild("https://[*.]bar.foo.com");
|
|
IsolatedOriginPattern single("https://bar.foo.com");
|
|
|
|
GURL host_url("https://host.bar.foo.com");
|
|
|
|
p->AddIsolatedOrigins({wild}, IsolatedOriginSource::TEST);
|
|
std::map<GURL, GURL> origin_site_map{
|
|
{host_url, host_url},
|
|
};
|
|
|
|
CheckGetSiteForURL(browser_context(), origin_site_map);
|
|
|
|
p->AddIsolatedOrigins({single}, IsolatedOriginSource::TEST);
|
|
|
|
CheckGetSiteForURL(browser_context(), origin_site_map);
|
|
|
|
p->RemoveIsolatedOriginForTesting(wild.origin());
|
|
p->RemoveIsolatedOriginForTesting(single.origin());
|
|
}
|
|
|
|
LOCKED_EXPECT_THAT(p->isolated_origins_lock_, p->isolated_origins_,
|
|
testing::IsEmpty());
|
|
|
|
// Verify the first domain added remains dominant in the case of differing
|
|
// wildcard and non-wildcard statuses.
|
|
{
|
|
IsolatedOriginPattern wild("https://[*.]bar.foo.com");
|
|
IsolatedOriginPattern single("https://bar.foo.com");
|
|
|
|
GURL host_url("https://host.bar.foo.com");
|
|
GURL domain_url("https://bar.foo.com");
|
|
|
|
p->AddIsolatedOrigins({single}, IsolatedOriginSource::TEST);
|
|
std::map<GURL, GURL> origin_site_map{
|
|
{host_url, domain_url},
|
|
};
|
|
|
|
CheckGetSiteForURL(browser_context(), origin_site_map);
|
|
|
|
p->AddIsolatedOrigins({wild}, IsolatedOriginSource::TEST);
|
|
|
|
CheckGetSiteForURL(browser_context(), origin_site_map);
|
|
|
|
p->RemoveIsolatedOriginForTesting(wild.origin());
|
|
p->RemoveIsolatedOriginForTesting(single.origin());
|
|
}
|
|
|
|
LOCKED_EXPECT_THAT(p->isolated_origins_lock_, p->isolated_origins_,
|
|
testing::IsEmpty());
|
|
}
|
|
|
|
// Verifies that isolated origins only apply to future BrowsingInstances.
|
|
TEST_F(ChildProcessSecurityPolicyTest, DynamicIsolatedOrigins) {
|
|
url::Origin foo = url::Origin::Create(GURL("https://foo.com/"));
|
|
url::Origin bar = url::Origin::Create(GURL("https://bar.com/"));
|
|
url::Origin baz = url::Origin::Create(GURL("https://baz.com/"));
|
|
url::Origin qux = url::Origin::Create(GURL("https://qux.com/"));
|
|
ChildProcessSecurityPolicyImpl* p =
|
|
ChildProcessSecurityPolicyImpl::GetInstance();
|
|
|
|
// Initially there should be no isolated origins.
|
|
LOCKED_EXPECT_THAT(p->isolated_origins_lock_, p->isolated_origins_,
|
|
testing::IsEmpty());
|
|
|
|
// Save the next BrowsingInstance ID to be created. Because unit tests run
|
|
// in batches, this isn't guaranteed to always be 1, for example if a
|
|
// previous test in the same batch had already created a SiteInstance and
|
|
// BrowsingInstance.
|
|
int initial_id(SiteInstanceImpl::NextBrowsingInstanceId().GetUnsafeValue());
|
|
|
|
// Isolate foo.com and bar.com.
|
|
p->AddIsolatedOrigins({foo, bar}, IsolatedOriginSource::TEST);
|
|
LOCKED_EXPECT_THAT(
|
|
p->isolated_origins_lock_, p->isolated_origins_,
|
|
testing::UnorderedElementsAre(GetIsolatedOriginEntry(initial_id, foo),
|
|
GetIsolatedOriginEntry(initial_id, bar)));
|
|
|
|
// Isolating bar.com again should have no effect.
|
|
p->AddIsolatedOrigins({bar}, IsolatedOriginSource::TEST);
|
|
LOCKED_EXPECT_THAT(
|
|
p->isolated_origins_lock_, p->isolated_origins_,
|
|
testing::UnorderedElementsAre(GetIsolatedOriginEntry(initial_id, foo),
|
|
GetIsolatedOriginEntry(initial_id, bar)));
|
|
|
|
// Create a new BrowsingInstance. Its ID will be |initial_id|.
|
|
TestBrowserContext context;
|
|
scoped_refptr<SiteInstanceImpl> foo_instance =
|
|
SiteInstanceImpl::CreateForURL(&context, GURL("https://foo.com/"));
|
|
EXPECT_EQ(BrowsingInstanceId::FromUnsafeValue(initial_id),
|
|
foo_instance->GetIsolationContext().browsing_instance_id());
|
|
EXPECT_EQ(BrowsingInstanceId::FromUnsafeValue(initial_id + 1),
|
|
SiteInstanceImpl::NextBrowsingInstanceId());
|
|
|
|
// Isolate baz.com. This will apply to BrowsingInstances with IDs
|
|
// |initial_id + 1| and above.
|
|
p->AddIsolatedOrigins({baz}, IsolatedOriginSource::TEST);
|
|
LOCKED_EXPECT_THAT(p->isolated_origins_lock_, p->isolated_origins_,
|
|
testing::UnorderedElementsAre(
|
|
GetIsolatedOriginEntry(initial_id, foo),
|
|
GetIsolatedOriginEntry(initial_id, bar),
|
|
GetIsolatedOriginEntry(initial_id + 1, baz)));
|
|
|
|
// Isolating bar.com again should not update the old BrowsingInstance ID.
|
|
p->AddIsolatedOrigins({bar}, IsolatedOriginSource::TEST);
|
|
LOCKED_EXPECT_THAT(p->isolated_origins_lock_, p->isolated_origins_,
|
|
testing::UnorderedElementsAre(
|
|
GetIsolatedOriginEntry(initial_id, foo),
|
|
GetIsolatedOriginEntry(initial_id, bar),
|
|
GetIsolatedOriginEntry(initial_id + 1, baz)));
|
|
|
|
// Create another BrowsingInstance.
|
|
scoped_refptr<SiteInstanceImpl> bar_instance =
|
|
SiteInstanceImpl::CreateForURL(&context, GURL("https://bar.com/"));
|
|
EXPECT_EQ(BrowsingInstanceId::FromUnsafeValue(initial_id + 1),
|
|
bar_instance->GetIsolationContext().browsing_instance_id());
|
|
EXPECT_EQ(BrowsingInstanceId::FromUnsafeValue(initial_id + 2),
|
|
SiteInstanceImpl::NextBrowsingInstanceId());
|
|
|
|
// Isolate qux.com.
|
|
p->AddIsolatedOrigins({qux}, IsolatedOriginSource::TEST);
|
|
LOCKED_EXPECT_THAT(p->isolated_origins_lock_, p->isolated_origins_,
|
|
testing::UnorderedElementsAre(
|
|
GetIsolatedOriginEntry(initial_id, foo),
|
|
GetIsolatedOriginEntry(initial_id, bar),
|
|
GetIsolatedOriginEntry(initial_id + 1, baz),
|
|
GetIsolatedOriginEntry(initial_id + 2, qux)));
|
|
|
|
// Check IsIsolatedOrigin() only returns isolated origins if they apply to
|
|
// the provided BrowsingInstance. foo and bar should apply in
|
|
// BrowsingInstance ID |initial_id| and above, baz in IDs |initial_id + 1|
|
|
// and above, and qux in |initial_id + 2| and above.
|
|
EXPECT_TRUE(IsIsolatedOrigin(&context, initial_id, foo));
|
|
EXPECT_TRUE(IsIsolatedOrigin(&context, initial_id, bar));
|
|
EXPECT_FALSE(IsIsolatedOrigin(&context, initial_id, baz));
|
|
EXPECT_FALSE(IsIsolatedOrigin(&context, initial_id, qux));
|
|
|
|
EXPECT_TRUE(IsIsolatedOrigin(&context, initial_id + 1, foo));
|
|
EXPECT_TRUE(IsIsolatedOrigin(&context, initial_id + 1, bar));
|
|
EXPECT_TRUE(IsIsolatedOrigin(&context, initial_id + 1, baz));
|
|
EXPECT_FALSE(IsIsolatedOrigin(&context, initial_id + 1, qux));
|
|
|
|
EXPECT_TRUE(IsIsolatedOrigin(&context, initial_id + 2, foo));
|
|
EXPECT_TRUE(IsIsolatedOrigin(&context, initial_id + 2, bar));
|
|
EXPECT_TRUE(IsIsolatedOrigin(&context, initial_id + 2, baz));
|
|
EXPECT_TRUE(IsIsolatedOrigin(&context, initial_id + 2, qux));
|
|
|
|
EXPECT_TRUE(IsIsolatedOrigin(&context, initial_id + 42, foo));
|
|
EXPECT_TRUE(IsIsolatedOrigin(&context, initial_id + 42, bar));
|
|
EXPECT_TRUE(IsIsolatedOrigin(&context, initial_id + 42, baz));
|
|
EXPECT_TRUE(IsIsolatedOrigin(&context, initial_id + 42, qux));
|
|
|
|
// An IsolationContext constructed without a BrowsingInstance ID should
|
|
// return the latest available isolated origins.
|
|
EXPECT_TRUE(p->IsIsolatedOrigin(IsolationContext(&context), foo));
|
|
EXPECT_TRUE(p->IsIsolatedOrigin(IsolationContext(&context), bar));
|
|
EXPECT_TRUE(p->IsIsolatedOrigin(IsolationContext(&context), baz));
|
|
EXPECT_TRUE(p->IsIsolatedOrigin(IsolationContext(&context), qux));
|
|
|
|
p->RemoveIsolatedOriginForTesting(foo);
|
|
p->RemoveIsolatedOriginForTesting(bar);
|
|
p->RemoveIsolatedOriginForTesting(baz);
|
|
p->RemoveIsolatedOriginForTesting(qux);
|
|
}
|
|
|
|
// Check that an unsuccessful isolated origin lookup for a URL with an empty
|
|
// host doesn't crash. See https://crbug.com/882686.
|
|
TEST_F(ChildProcessSecurityPolicyTest, IsIsolatedOriginWithEmptyHost) {
|
|
ChildProcessSecurityPolicyImpl* p =
|
|
ChildProcessSecurityPolicyImpl::GetInstance();
|
|
TestBrowserContext context;
|
|
EXPECT_FALSE(p->IsIsolatedOrigin(IsolationContext(&context),
|
|
url::Origin::Create(GURL())));
|
|
EXPECT_FALSE(p->IsIsolatedOrigin(IsolationContext(&context),
|
|
url::Origin::Create(GURL("file:///foo"))));
|
|
}
|
|
|
|
// Verifies the API for restricting isolated origins to a specific
|
|
// BrowserContext (profile). Namely, the same origin may be added for
|
|
// different BrowserContexts, possibly with different BrowsingInstanceId
|
|
// cutoffs. Attempts to re-add an origin for the same profile should be
|
|
// ignored. Also, once an isolated origin is added globally for all profiles,
|
|
// future attempts to re-add it (for any profile) should also be ignored.
|
|
TEST_F(ChildProcessSecurityPolicyTest,
|
|
IsolatedOriginsForSpecificBrowserContexts) {
|
|
url::Origin foo = url::Origin::Create(GURL("https://foo.com/"));
|
|
url::Origin bar = url::Origin::Create(GURL("https://bar.com/"));
|
|
ChildProcessSecurityPolicyImpl* p =
|
|
ChildProcessSecurityPolicyImpl::GetInstance();
|
|
|
|
// Initially there should be no isolated origins.
|
|
LOCKED_EXPECT_THAT(p->isolated_origins_lock_, p->isolated_origins_,
|
|
testing::IsEmpty());
|
|
|
|
// Save the next BrowsingInstance ID to be created. Because unit tests run
|
|
// in batches, this isn't guaranteed to always be 1, for example if a
|
|
// previous test in the same batch had already created a SiteInstance and
|
|
// BrowsingInstance.
|
|
int initial_id(SiteInstanceImpl::NextBrowsingInstanceId().GetUnsafeValue());
|
|
|
|
// Isolate foo.com globally (for all BrowserContexts).
|
|
p->AddIsolatedOrigins({foo}, IsolatedOriginSource::TEST);
|
|
|
|
TestBrowserContext context1, context2;
|
|
|
|
// Isolate bar.com in |context1|.
|
|
p->AddIsolatedOrigins({bar}, IsolatedOriginSource::TEST, &context1);
|
|
|
|
// bar.com should be isolated for |context1|, but not |context2|. foo.com
|
|
// should be isolated for all contexts.
|
|
EXPECT_TRUE(IsIsolatedOrigin(&context1, initial_id, foo));
|
|
EXPECT_TRUE(IsIsolatedOrigin(&context2, initial_id, foo));
|
|
EXPECT_TRUE(IsIsolatedOrigin(&context1, initial_id, bar));
|
|
EXPECT_FALSE(IsIsolatedOrigin(&context2, initial_id, bar));
|
|
|
|
// Create a new BrowsingInstance. Its ID will be |initial_id|.
|
|
scoped_refptr<SiteInstanceImpl> foo_instance =
|
|
SiteInstanceImpl::CreateForURL(&context1, GURL("https://foo.com/"));
|
|
EXPECT_EQ(BrowsingInstanceId::FromUnsafeValue(initial_id),
|
|
foo_instance->GetIsolationContext().browsing_instance_id());
|
|
EXPECT_EQ(BrowsingInstanceId::FromUnsafeValue(initial_id + 1),
|
|
SiteInstanceImpl::NextBrowsingInstanceId());
|
|
EXPECT_EQ(&context1, foo_instance->GetIsolationContext()
|
|
.browser_or_resource_context()
|
|
.ToBrowserContext());
|
|
|
|
// Isolating foo.com in |context1| is allowed and should add a new
|
|
// IsolatedOriginEntry. This wouldn't introduce any additional isolation,
|
|
// since foo.com is already isolated globally, but the new entry is
|
|
// important, e.g. for persisting profile-specific isolated origins across
|
|
// restarts.
|
|
EXPECT_EQ(1, GetIsolatedOriginEntryCount(foo));
|
|
p->AddIsolatedOrigins({foo}, IsolatedOriginSource::TEST, &context1);
|
|
EXPECT_EQ(2, GetIsolatedOriginEntryCount(foo));
|
|
EXPECT_TRUE(IsIsolatedOrigin(&context1, initial_id, foo));
|
|
EXPECT_TRUE(IsIsolatedOrigin(&context2, initial_id, foo));
|
|
|
|
// Isolating bar.com in |context1| again should have no effect.
|
|
EXPECT_EQ(1, GetIsolatedOriginEntryCount(bar));
|
|
p->AddIsolatedOrigins({bar}, IsolatedOriginSource::TEST, &context1);
|
|
EXPECT_EQ(1, GetIsolatedOriginEntryCount(bar));
|
|
EXPECT_TRUE(IsIsolatedOrigin(&context1, initial_id, bar));
|
|
EXPECT_FALSE(IsIsolatedOrigin(&context2, initial_id, bar));
|
|
|
|
// Isolate bar.com for |context2|, which should add a new
|
|
// IsolatedOriginEntry. Verify that the isolation took effect for
|
|
// |initial_id + 1| (the current BrowsingInstance ID cutoff) only.
|
|
p->AddIsolatedOrigins({bar}, IsolatedOriginSource::TEST, &context2);
|
|
EXPECT_EQ(2, GetIsolatedOriginEntryCount(bar));
|
|
EXPECT_FALSE(IsIsolatedOrigin(&context2, initial_id, bar));
|
|
EXPECT_TRUE(IsIsolatedOrigin(&context2, initial_id + 1, bar));
|
|
|
|
// Verify the bar.com is still isolated in |context1| starting with
|
|
// |initial_id|.
|
|
EXPECT_TRUE(IsIsolatedOrigin(&context1, initial_id, bar));
|
|
EXPECT_TRUE(IsIsolatedOrigin(&context1, initial_id + 1, bar));
|
|
|
|
// Create another BrowserContext; only foo.com should be isolated there.
|
|
TestBrowserContext context3;
|
|
EXPECT_TRUE(IsIsolatedOrigin(&context3, initial_id, foo));
|
|
EXPECT_TRUE(IsIsolatedOrigin(&context3, initial_id + 1, foo));
|
|
EXPECT_FALSE(IsIsolatedOrigin(&context3, initial_id, bar));
|
|
EXPECT_FALSE(IsIsolatedOrigin(&context3, initial_id + 1, bar));
|
|
|
|
// Now, add bar.com as a globally isolated origin. This should make it apply
|
|
// to context3 as well, but only in initial_id + 1 (the current
|
|
// BrowsingInstance ID cutoff).
|
|
p->AddIsolatedOrigins({bar}, IsolatedOriginSource::TEST);
|
|
EXPECT_EQ(3, GetIsolatedOriginEntryCount(bar));
|
|
EXPECT_FALSE(IsIsolatedOrigin(&context3, initial_id, bar));
|
|
EXPECT_TRUE(IsIsolatedOrigin(&context3, initial_id + 1, bar));
|
|
|
|
// An attempt to re-add bar.com for a new profile should create a new
|
|
// IsolatedOriginEntry, though it wouldn't provide any additional isolation,
|
|
// since bar.com is already isolated globally.
|
|
TestBrowserContext context4;
|
|
p->AddIsolatedOrigins({bar}, IsolatedOriginSource::TEST, &context4);
|
|
EXPECT_EQ(4, GetIsolatedOriginEntryCount(bar));
|
|
|
|
p->RemoveIsolatedOriginForTesting(foo);
|
|
p->RemoveIsolatedOriginForTesting(bar);
|
|
}
|
|
|
|
// This test ensures that isolated origins associated with a specific
|
|
// BrowserContext are removed when that BrowserContext is destroyed.
|
|
TEST_F(ChildProcessSecurityPolicyTest,
|
|
IsolatedOriginsRemovedWhenBrowserContextDestroyed) {
|
|
url::Origin foo = url::Origin::Create(GURL("https://foo.com/"));
|
|
url::Origin sub_foo = url::Origin::Create(GURL("https://sub.foo.com/"));
|
|
url::Origin bar = url::Origin::Create(GURL("https://bar.com/"));
|
|
url::Origin baz = url::Origin::Create(GURL("https://baz.com/"));
|
|
ChildProcessSecurityPolicyImpl* p =
|
|
ChildProcessSecurityPolicyImpl::GetInstance();
|
|
|
|
// Initially there should be no isolated origins.
|
|
LOCKED_EXPECT_THAT(p->isolated_origins_lock_, p->isolated_origins_,
|
|
testing::IsEmpty());
|
|
|
|
// Save the next BrowsingInstance ID to be created. Because unit tests run
|
|
// in batches, this isn't guaranteed to always be 1, for example if a
|
|
// previous test in the same batch had already created a SiteInstance and
|
|
// BrowsingInstance.
|
|
int initial_id(SiteInstanceImpl::NextBrowsingInstanceId().GetUnsafeValue());
|
|
|
|
std::unique_ptr<TestBrowserContext> context1(new TestBrowserContext());
|
|
std::unique_ptr<TestBrowserContext> context2(new TestBrowserContext());
|
|
|
|
// Isolate foo.com in |context1|. Note that sub.foo.com should also be
|
|
// considered isolated in |context1|, since it's a subdomain of foo.com.
|
|
p->AddIsolatedOrigins({foo}, IsolatedOriginSource::TEST, context1.get());
|
|
EXPECT_EQ(1, GetIsolatedOriginEntryCount(foo));
|
|
EXPECT_TRUE(IsIsolatedOrigin(context1.get(), initial_id, foo));
|
|
EXPECT_TRUE(IsIsolatedOrigin(context1.get(), initial_id, sub_foo));
|
|
EXPECT_FALSE(IsIsolatedOrigin(context2.get(), initial_id, foo));
|
|
EXPECT_FALSE(IsIsolatedOrigin(context2.get(), initial_id, sub_foo));
|
|
|
|
// Isolate sub.foo.com and bar.com in |context2|.
|
|
p->AddIsolatedOrigins({sub_foo, bar}, IsolatedOriginSource::TEST,
|
|
context2.get());
|
|
EXPECT_EQ(1, GetIsolatedOriginEntryCount(sub_foo));
|
|
EXPECT_EQ(1, GetIsolatedOriginEntryCount(bar));
|
|
EXPECT_TRUE(IsIsolatedOrigin(context2.get(), initial_id, sub_foo));
|
|
EXPECT_TRUE(IsIsolatedOrigin(context2.get(), initial_id, bar));
|
|
EXPECT_FALSE(IsIsolatedOrigin(context2.get(), initial_id, foo));
|
|
|
|
// Isolate baz.com in both BrowserContexts.
|
|
p->AddIsolatedOrigins({baz}, IsolatedOriginSource::TEST, context1.get());
|
|
p->AddIsolatedOrigins({baz}, IsolatedOriginSource::TEST, context2.get());
|
|
|
|
EXPECT_EQ(2, GetIsolatedOriginEntryCount(baz));
|
|
EXPECT_TRUE(IsIsolatedOrigin(context1.get(), initial_id, baz));
|
|
EXPECT_TRUE(IsIsolatedOrigin(context2.get(), initial_id, baz));
|
|
|
|
// Remove |context1|. foo.com should no longer be in the isolated_origins_
|
|
// map, and the other origins should be isolated only in |context2|.
|
|
context1.reset();
|
|
|
|
EXPECT_EQ(0, GetIsolatedOriginEntryCount(foo));
|
|
EXPECT_EQ(1, GetIsolatedOriginEntryCount(sub_foo));
|
|
EXPECT_EQ(1, GetIsolatedOriginEntryCount(bar));
|
|
EXPECT_EQ(1, GetIsolatedOriginEntryCount(baz));
|
|
EXPECT_TRUE(IsIsolatedOrigin(context2.get(), initial_id, sub_foo));
|
|
EXPECT_TRUE(IsIsolatedOrigin(context2.get(), initial_id, bar));
|
|
EXPECT_TRUE(IsIsolatedOrigin(context2.get(), initial_id, baz));
|
|
|
|
// Remove |context2| and ensure the remaining entries are removed.
|
|
context2.reset();
|
|
LOCKED_EXPECT_THAT(p->isolated_origins_lock_, p->isolated_origins_,
|
|
testing::IsEmpty());
|
|
}
|
|
|
|
// Tests behavior of HasSecurityState() during race conditions that
|
|
// can occur during Remove(). It verifies that SecurityState for a child ID is
|
|
// preserved after a Remove() call until the task, that Remove() has posted to
|
|
// the IO thread, has run.
|
|
//
|
|
// We use a combination of waitable events and extra tasks posted to the
|
|
// threads to capture permission state from the UI & IO threads during the
|
|
// removal process. It is intended to simulate pending tasks that could be
|
|
// run on each thread during removal.
|
|
TEST_F(ChildProcessSecurityPolicyTest, HasSecurityState) {
|
|
ChildProcessSecurityPolicyImpl* p =
|
|
ChildProcessSecurityPolicyImpl::GetInstance();
|
|
|
|
GURL url("file:///etc/passwd");
|
|
|
|
EXPECT_FALSE(p->HasSecurityState(kRendererID));
|
|
|
|
p->Add(kRendererID, browser_context());
|
|
|
|
base::WaitableEvent ready_for_remove_event;
|
|
base::WaitableEvent remove_called_event;
|
|
base::WaitableEvent pending_remove_complete_event;
|
|
|
|
// Keep track of the return value for HasSecurityState() at various
|
|
// points in time during the test.
|
|
bool io_before_remove = false;
|
|
bool io_while_io_task_pending = false;
|
|
bool io_after_io_task_completed = false;
|
|
bool ui_before_remove = false;
|
|
bool ui_while_io_task_pending = false;
|
|
bool ui_after_io_task_completed = false;
|
|
|
|
// Post a task that will run on the IO thread before the task that
|
|
// Remove() will post to the IO thread.
|
|
base::PostTask(FROM_HERE, {BrowserThread::IO},
|
|
base::BindLambdaForTesting([&]() {
|
|
// Capture state on the IO thread before Remove() is called.
|
|
io_before_remove = p->HasSecurityState(kRendererID);
|
|
|
|
// Tell the UI thread we are ready for Remove() to be called.
|
|
ready_for_remove_event.Signal();
|
|
|
|
// Wait for Remove() to be called on the UI thread.
|
|
remove_called_event.Wait();
|
|
|
|
// Capture state after Remove() is called, but before its
|
|
// task on the IO thread runs.
|
|
io_while_io_task_pending = p->HasSecurityState(kRendererID);
|
|
}));
|
|
|
|
ready_for_remove_event.Wait();
|
|
|
|
ui_before_remove = p->HasSecurityState(kRendererID);
|
|
|
|
p->Remove(kRendererID);
|
|
|
|
// Post a task to run after the task Remove() posted on the IO thread.
|
|
base::PostTask(
|
|
FROM_HERE, {BrowserThread::IO}, base::BindLambdaForTesting([&]() {
|
|
io_after_io_task_completed = p->HasSecurityState(kRendererID);
|
|
|
|
// Tell the UI thread that the task from Remove()
|
|
// has completed on the IO thread.
|
|
pending_remove_complete_event.Signal();
|
|
}));
|
|
|
|
// Capture state after Remove() has been called, but before its IO thread
|
|
// task has run. We know the IO thread task hasn't run yet because the
|
|
// task we posted before the Remove() call is waiting for us to signal
|
|
// |remove_called_event|.
|
|
ui_while_io_task_pending = p->HasSecurityState(kRendererID);
|
|
|
|
// Unblock the IO thread so the pending remove events can run.
|
|
remove_called_event.Signal();
|
|
|
|
pending_remove_complete_event.Wait();
|
|
|
|
// Capture state after IO thread task has run.
|
|
ui_after_io_task_completed = p->HasSecurityState(kRendererID);
|
|
|
|
// Run pending UI thread tasks.
|
|
base::RunLoop run_loop;
|
|
run_loop.RunUntilIdle();
|
|
|
|
bool ui_after_remove_complete = p->HasSecurityState(kRendererID);
|
|
bool io_after_remove_complete = false;
|
|
base::WaitableEvent after_remove_complete_event;
|
|
|
|
base::PostTask(FROM_HERE, {BrowserThread::IO},
|
|
base::BindLambdaForTesting([&]() {
|
|
io_after_remove_complete = p->HasSecurityState(kRendererID);
|
|
|
|
// Tell the UI thread that this task has
|
|
// has completed on the IO thread.
|
|
after_remove_complete_event.Signal();
|
|
}));
|
|
|
|
// Wait for the task we just posted to the IO thread to complete.
|
|
after_remove_complete_event.Wait();
|
|
|
|
// Verify expected states at various parts of the removal.
|
|
// Note: IO thread is expected to keep pre-Remove() permissions until
|
|
// the task Remove() posted runs on the IO thread.
|
|
EXPECT_TRUE(io_before_remove);
|
|
EXPECT_TRUE(io_while_io_task_pending);
|
|
EXPECT_FALSE(io_after_io_task_completed);
|
|
|
|
EXPECT_TRUE(ui_before_remove);
|
|
EXPECT_FALSE(ui_while_io_task_pending);
|
|
EXPECT_FALSE(ui_after_io_task_completed);
|
|
|
|
EXPECT_FALSE(ui_after_remove_complete);
|
|
EXPECT_FALSE(io_after_remove_complete);
|
|
}
|
|
|
|
TEST_F(ChildProcessSecurityPolicyTest, IsolatedOriginPattern) {
|
|
const base::StringPiece etld1_wild("https://[*.]foo.com");
|
|
url::Origin etld1_wild_origin = url::Origin::Create(GURL("https://foo.com"));
|
|
IsolatedOriginPattern p(etld1_wild);
|
|
EXPECT_TRUE(p.isolate_all_subdomains());
|
|
EXPECT_TRUE(p.is_valid());
|
|
EXPECT_EQ(p.origin(), etld1_wild_origin);
|
|
|
|
const base::StringPiece etld2_wild("https://[*.]bar.foo.com");
|
|
url::Origin etld2_wild_origin =
|
|
url::Origin::Create(GURL("https://bar.foo.com"));
|
|
bool result = p.Parse(etld2_wild);
|
|
EXPECT_TRUE(result);
|
|
EXPECT_TRUE(p.isolate_all_subdomains());
|
|
EXPECT_TRUE(p.is_valid());
|
|
EXPECT_EQ(p.origin(), etld2_wild_origin);
|
|
EXPECT_FALSE(p.origin().opaque());
|
|
|
|
const base::StringPiece etld1("https://baz.com");
|
|
url::Origin etld1_origin = url::Origin::Create(GURL("https://baz.com"));
|
|
result = p.Parse(etld1);
|
|
EXPECT_TRUE(result);
|
|
EXPECT_FALSE(p.isolate_all_subdomains());
|
|
EXPECT_TRUE(p.is_valid());
|
|
EXPECT_EQ(p.origin(), etld1_origin);
|
|
EXPECT_FALSE(p.origin().opaque());
|
|
|
|
const base::StringPiece bad_scheme("ftp://foo.com");
|
|
result = p.Parse(bad_scheme);
|
|
EXPECT_FALSE(result);
|
|
EXPECT_FALSE(p.isolate_all_subdomains());
|
|
EXPECT_FALSE(p.is_valid());
|
|
EXPECT_TRUE(p.origin().opaque());
|
|
|
|
const base::StringPiece no_scheme_sep("httpsfoo.com");
|
|
result = p.Parse(no_scheme_sep);
|
|
EXPECT_FALSE(result);
|
|
EXPECT_FALSE(p.isolate_all_subdomains());
|
|
EXPECT_FALSE(p.is_valid());
|
|
EXPECT_TRUE(p.origin().opaque());
|
|
|
|
const base::StringPiece bad_registry("https://co.uk");
|
|
result = p.Parse(bad_registry);
|
|
EXPECT_FALSE(result);
|
|
EXPECT_FALSE(p.isolate_all_subdomains());
|
|
EXPECT_FALSE(p.is_valid());
|
|
EXPECT_TRUE(p.origin().opaque());
|
|
|
|
const base::StringPiece trailing_dot("https://bar.com.");
|
|
result = p.Parse(trailing_dot);
|
|
EXPECT_FALSE(result);
|
|
EXPECT_FALSE(p.isolate_all_subdomains());
|
|
EXPECT_FALSE(p.is_valid());
|
|
EXPECT_TRUE(p.origin().opaque());
|
|
|
|
const base::StringPiece ip_addr("https://10.20.30.40");
|
|
url::Origin ip_origin = url::Origin::Create(GURL("https://10.20.30.40"));
|
|
result = p.Parse(ip_addr);
|
|
EXPECT_TRUE(result);
|
|
EXPECT_FALSE(p.isolate_all_subdomains());
|
|
EXPECT_FALSE(p.origin().opaque());
|
|
EXPECT_TRUE(p.is_valid());
|
|
EXPECT_EQ(p.origin(), ip_origin);
|
|
|
|
const base::StringPiece wild_ip_addr("https://[*.]10.20.30.40");
|
|
result = p.Parse(wild_ip_addr);
|
|
EXPECT_FALSE(result);
|
|
EXPECT_FALSE(p.isolate_all_subdomains());
|
|
EXPECT_FALSE(p.is_valid());
|
|
|
|
const url::Origin bad_origin;
|
|
IsolatedOriginPattern bad_pattern(bad_origin);
|
|
EXPECT_FALSE(bad_pattern.isolate_all_subdomains());
|
|
EXPECT_TRUE(bad_pattern.origin().opaque());
|
|
EXPECT_FALSE(p.is_valid());
|
|
}
|
|
|
|
// This test adds isolated origins from various sources and verifies that
|
|
// GetIsolatedOrigins() properly restricts lookups by source.
|
|
TEST_F(ChildProcessSecurityPolicyTest, GetIsolatedOrigins) {
|
|
url::Origin foo = url::Origin::Create(GURL("https://foo.com/"));
|
|
url::Origin bar = url::Origin::Create(GURL("https://bar.com/"));
|
|
url::Origin baz = url::Origin::Create(GURL("https://baz.com/"));
|
|
url::Origin qux = url::Origin::Create(GURL("https://qux.com/"));
|
|
ChildProcessSecurityPolicyImpl* p =
|
|
ChildProcessSecurityPolicyImpl::GetInstance();
|
|
|
|
// Initially there should be no isolated origins.
|
|
EXPECT_THAT(p->GetIsolatedOrigins(), testing::IsEmpty());
|
|
|
|
// Add isolated origins from various sources, and verify that
|
|
// GetIsolatedOrigins properly restricts lookups by source.
|
|
p->AddIsolatedOrigins({foo}, IsolatedOriginSource::TEST);
|
|
p->AddIsolatedOrigins({bar}, IsolatedOriginSource::FIELD_TRIAL);
|
|
|
|
EXPECT_THAT(p->GetIsolatedOrigins(), testing::UnorderedElementsAre(foo, bar));
|
|
EXPECT_THAT(p->GetIsolatedOrigins(IsolatedOriginSource::TEST),
|
|
testing::UnorderedElementsAre(foo));
|
|
EXPECT_THAT(p->GetIsolatedOrigins(IsolatedOriginSource::FIELD_TRIAL),
|
|
testing::UnorderedElementsAre(bar));
|
|
|
|
p->AddIsolatedOrigins({baz}, IsolatedOriginSource::POLICY);
|
|
p->AddIsolatedOrigins({qux}, IsolatedOriginSource::COMMAND_LINE);
|
|
|
|
EXPECT_THAT(p->GetIsolatedOrigins(),
|
|
testing::UnorderedElementsAre(foo, bar, baz, qux));
|
|
EXPECT_THAT(p->GetIsolatedOrigins(IsolatedOriginSource::TEST),
|
|
testing::UnorderedElementsAre(foo));
|
|
EXPECT_THAT(p->GetIsolatedOrigins(IsolatedOriginSource::FIELD_TRIAL),
|
|
testing::UnorderedElementsAre(bar));
|
|
EXPECT_THAT(p->GetIsolatedOrigins(IsolatedOriginSource::POLICY),
|
|
testing::UnorderedElementsAre(baz));
|
|
EXPECT_THAT(p->GetIsolatedOrigins(IsolatedOriginSource::COMMAND_LINE),
|
|
testing::UnorderedElementsAre(qux));
|
|
|
|
p->RemoveIsolatedOriginForTesting(foo);
|
|
p->RemoveIsolatedOriginForTesting(bar);
|
|
p->RemoveIsolatedOriginForTesting(baz);
|
|
p->RemoveIsolatedOriginForTesting(qux);
|
|
EXPECT_THAT(p->GetIsolatedOrigins(), testing::IsEmpty());
|
|
}
|
|
|
|
// This test adds isolated origins from various sources as well as restricted
|
|
// to particular profiles, and verifies that GetIsolatedOrigins() properly
|
|
// restricts lookups by both source and profile.
|
|
TEST_F(ChildProcessSecurityPolicyTest, GetIsolatedOriginsWithProfile) {
|
|
url::Origin foo = url::Origin::Create(GURL("https://foo.com/"));
|
|
url::Origin bar = url::Origin::Create(GURL("https://bar.com/"));
|
|
url::Origin baz = url::Origin::Create(GURL("https://baz.com/"));
|
|
url::Origin qux = url::Origin::Create(GURL("https://qux.com/"));
|
|
ChildProcessSecurityPolicyImpl* p =
|
|
ChildProcessSecurityPolicyImpl::GetInstance();
|
|
TestBrowserContext context1, context2;
|
|
|
|
// Initially there should be no isolated origins.
|
|
EXPECT_THAT(p->GetIsolatedOrigins(), testing::IsEmpty());
|
|
|
|
// Add a global isolated origin. Note that since it applies to all profiles,
|
|
// GetIsolatedOrigins() should return it for any passed-in profile.
|
|
p->AddIsolatedOrigins({foo}, IsolatedOriginSource::TEST);
|
|
|
|
// Add some per-profile isolated origins.
|
|
p->AddIsolatedOrigins({bar}, IsolatedOriginSource::USER_TRIGGERED, &context1);
|
|
p->AddIsolatedOrigins({baz}, IsolatedOriginSource::POLICY, &context2);
|
|
p->AddIsolatedOrigins({qux}, IsolatedOriginSource::USER_TRIGGERED, &context1);
|
|
p->AddIsolatedOrigins({qux}, IsolatedOriginSource::USER_TRIGGERED, &context2);
|
|
|
|
EXPECT_THAT(p->GetIsolatedOrigins(), testing::UnorderedElementsAre(foo));
|
|
|
|
EXPECT_THAT(p->GetIsolatedOrigins(IsolatedOriginSource::TEST),
|
|
testing::UnorderedElementsAre(foo));
|
|
EXPECT_THAT(p->GetIsolatedOrigins(IsolatedOriginSource::TEST, &context1),
|
|
testing::UnorderedElementsAre(foo));
|
|
EXPECT_THAT(p->GetIsolatedOrigins(IsolatedOriginSource::TEST, &context2),
|
|
testing::UnorderedElementsAre(foo));
|
|
|
|
EXPECT_THAT(p->GetIsolatedOrigins(IsolatedOriginSource::USER_TRIGGERED),
|
|
testing::IsEmpty());
|
|
EXPECT_THAT(
|
|
p->GetIsolatedOrigins(IsolatedOriginSource::USER_TRIGGERED, &context1),
|
|
testing::UnorderedElementsAre(bar, qux));
|
|
EXPECT_THAT(
|
|
p->GetIsolatedOrigins(IsolatedOriginSource::USER_TRIGGERED, &context2),
|
|
testing::UnorderedElementsAre(qux));
|
|
|
|
EXPECT_THAT(p->GetIsolatedOrigins(IsolatedOriginSource::POLICY),
|
|
testing::IsEmpty());
|
|
EXPECT_THAT(p->GetIsolatedOrigins(IsolatedOriginSource::POLICY, &context1),
|
|
testing::IsEmpty());
|
|
EXPECT_THAT(p->GetIsolatedOrigins(IsolatedOriginSource::POLICY, &context2),
|
|
testing::UnorderedElementsAre(baz));
|
|
|
|
p->RemoveIsolatedOriginForTesting(foo);
|
|
p->RemoveIsolatedOriginForTesting(bar);
|
|
p->RemoveIsolatedOriginForTesting(baz);
|
|
p->RemoveIsolatedOriginForTesting(qux);
|
|
EXPECT_THAT(p->GetIsolatedOrigins(), testing::IsEmpty());
|
|
}
|
|
|
|
TEST_F(ChildProcessSecurityPolicyTest, IsolatedOriginPatternEquality) {
|
|
std::string foo("https://foo.com");
|
|
std::string foo_port("https://foo.com:8000");
|
|
std::string foo_path("https://foo.com/some/path");
|
|
|
|
EXPECT_EQ(IsolatedOriginPattern(foo), IsolatedOriginPattern(foo_port));
|
|
EXPECT_EQ(IsolatedOriginPattern(foo), IsolatedOriginPattern(foo_path));
|
|
|
|
std::string wild_foo("https://[*.]foo.com");
|
|
std::string wild_foo_port("https://[*.]foo.com:8000");
|
|
std::string wild_foo_path("https://[*.]foo.com/some/path");
|
|
|
|
EXPECT_EQ(IsolatedOriginPattern(wild_foo),
|
|
IsolatedOriginPattern(wild_foo_port));
|
|
EXPECT_EQ(IsolatedOriginPattern(wild_foo),
|
|
IsolatedOriginPattern(wild_foo_path));
|
|
|
|
EXPECT_FALSE(IsolatedOriginPattern(foo) == IsolatedOriginPattern(wild_foo));
|
|
}
|
|
|
|
// Verifies parsing logic in SiteIsolationPolicy::ParseIsolatedOrigins.
|
|
TEST_F(ChildProcessSecurityPolicyTest, ParseIsolatedOrigins) {
|
|
EXPECT_THAT(ChildProcessSecurityPolicyImpl::ParseIsolatedOrigins(""),
|
|
testing::IsEmpty());
|
|
|
|
// Single simple, valid origin.
|
|
EXPECT_THAT(
|
|
ChildProcessSecurityPolicyImpl::ParseIsolatedOrigins(
|
|
"http://isolated.foo.com"),
|
|
testing::ElementsAre(IsolatedOriginPattern("http://isolated.foo.com")));
|
|
|
|
// Multiple comma-separated origins.
|
|
EXPECT_THAT(
|
|
ChildProcessSecurityPolicyImpl::ParseIsolatedOrigins(
|
|
"http://a.com,https://b.com,,https://c.com:8000"),
|
|
testing::ElementsAre(IsolatedOriginPattern("http://a.com"),
|
|
IsolatedOriginPattern("https://b.com"),
|
|
IsolatedOriginPattern("https://c.com:8000")));
|
|
|
|
// ParseIsolatedOrigins should not do any deduplication (that is the job of
|
|
// ChildProcessSecurityPolicyImpl::AddIsolatedOrigins).
|
|
EXPECT_THAT(
|
|
ChildProcessSecurityPolicyImpl::ParseIsolatedOrigins(
|
|
"https://b.com,https://b.com,https://b.com:1234"),
|
|
testing::ElementsAre(IsolatedOriginPattern("https://b.com"),
|
|
IsolatedOriginPattern("https://b.com"),
|
|
IsolatedOriginPattern("https://b.com:1234")));
|
|
|
|
// A single wildcard origin.
|
|
EXPECT_THAT(
|
|
ChildProcessSecurityPolicyImpl::ParseIsolatedOrigins(
|
|
"https://[*.]wild.foo.com"),
|
|
testing::ElementsAre(IsolatedOriginPattern("https://[*.]wild.foo.com")));
|
|
|
|
// A mixture of wildcard and non-wildcard origins.
|
|
EXPECT_THAT(
|
|
ChildProcessSecurityPolicyImpl::ParseIsolatedOrigins(
|
|
"https://[*.]wild.foo.com,https://isolated.foo.com"),
|
|
testing::ElementsAre(IsolatedOriginPattern("https://[*.]wild.foo.com"),
|
|
IsolatedOriginPattern("https://isolated.foo.com")));
|
|
}
|
|
|
|
// Verify that the default port for an isolated origin's scheme is returned
|
|
// during a lookup, not the port of the origin requested.
|
|
TEST_F(ChildProcessSecurityPolicyTest, WildcardDefaultPort) {
|
|
ChildProcessSecurityPolicyImpl* p =
|
|
ChildProcessSecurityPolicyImpl::GetInstance();
|
|
EXPECT_THAT(p->GetIsolatedOrigins(), testing::IsEmpty());
|
|
|
|
url::Origin isolated_origin_with_port =
|
|
url::Origin::Create(GURL("https://isolated.com:1234"));
|
|
url::Origin isolated_origin =
|
|
url::Origin::Create(GURL("https://isolated.com"));
|
|
|
|
url::Origin wild_with_port =
|
|
url::Origin::Create(GURL("https://a.wild.com:5678"));
|
|
url::Origin wild_origin = url::Origin::Create(GURL("https://a.wild.com"));
|
|
IsolatedOriginPattern wild_pattern("https://[*.]wild.com:5678");
|
|
|
|
p->AddIsolatedOrigins({isolated_origin_with_port},
|
|
IsolatedOriginSource::TEST);
|
|
p->AddIsolatedOrigins({wild_pattern}, IsolatedOriginSource::TEST);
|
|
|
|
IsolationContext isolation_context(browser_context());
|
|
url::Origin lookup_origin;
|
|
|
|
// Requesting isolated_origin_with_port should return the same origin but with
|
|
// the default port for the scheme.
|
|
EXPECT_TRUE(p->GetMatchingIsolatedOrigin(
|
|
isolation_context, isolated_origin_with_port, &lookup_origin));
|
|
EXPECT_EQ(url::DefaultPortForScheme(lookup_origin.scheme().data(),
|
|
lookup_origin.scheme().length()),
|
|
lookup_origin.port());
|
|
EXPECT_EQ(isolated_origin, lookup_origin);
|
|
|
|
p->RemoveIsolatedOriginForTesting(isolated_origin);
|
|
|
|
// Similarly, looking up matching isolated origins for wildcard origins must
|
|
// also return the default port for the origin's scheme, not the report of the
|
|
// requested origin.
|
|
EXPECT_TRUE(p->GetMatchingIsolatedOrigin(isolation_context, wild_with_port,
|
|
&lookup_origin));
|
|
EXPECT_EQ(url::DefaultPortForScheme(lookup_origin.scheme().data(),
|
|
lookup_origin.scheme().length()),
|
|
lookup_origin.port());
|
|
EXPECT_EQ(wild_origin, lookup_origin);
|
|
|
|
p->RemoveIsolatedOriginForTesting(wild_pattern.origin());
|
|
|
|
EXPECT_THAT(p->GetIsolatedOrigins(), testing::IsEmpty());
|
|
}
|
|
|
|
} // namespace content
|