Commit 054f5b91 authored by Devlin Cronin's avatar Devlin Cronin Committed by Commit Bot

[Blink Security Policy] Add origin access blacklists

Add an origin access blacklist that takes priority over the origin
access whitelist. If both the blacklist and the whitelist match an
origin, access is not granted.

This will allow us to specify a wildcard whitelist while maintaining
a list of exceptions which will be off-limits.

Add unittests for the same.

Bug: 826946
Change-Id: I651cc1e35d05bf20b8ea6e07f34b4b034c297844
Reviewed-on: https://chromium-review.googlesource.com/1011241Reviewed-by: default avatarDaniel Cheng <dcheng@chromium.org>
Reviewed-by: default avatarMike West <mkwst@chromium.org>
Commit-Queue: Devlin <rdevlin.cronin@chromium.org>
Cr-Commit-Position: refs/heads/master@{#550622}
parent 7c3d1d13
......@@ -78,6 +78,22 @@ class WebSecurityPolicy {
bool allow_destination_subdomains);
BLINK_EXPORT static void ResetOriginAccessWhitelists();
// Support for restricting the whitelists, in order to allow for broad
// whitelist access (e.g., "chromium.org") while protecting a subset of hosts
// (e.g., "secure.chromium.org"). If an origin is in both the whitelist and
// the blacklist, it is disallowed access.
BLINK_EXPORT static void AddOriginAccessBlacklistEntry(
const WebURL& source_origin,
const WebString& destination_protocol,
const WebString& destination_host,
bool disallow_destination_subdomains);
BLINK_EXPORT static void RemoveOriginAccessBlacklistEntry(
const WebURL& source_origin,
const WebString& destination_protocol,
const WebString& destination_host,
bool allow_destination_subdomains);
BLINK_EXPORT static void ResetOriginAccessBlacklists();
// Support for whitelisting origins to treat them as trustworthy.
BLINK_EXPORT static void AddOriginTrustworthyWhiteList(
const WebSecurityOrigin&);
......
......@@ -89,6 +89,30 @@ void WebSecurityPolicy::ResetOriginAccessWhitelists() {
SecurityPolicy::ResetOriginAccessWhitelists();
}
void WebSecurityPolicy::AddOriginAccessBlacklistEntry(
const WebURL& source_origin,
const WebString& destination_protocol,
const WebString& destination_host,
bool allow_destination_subdomains) {
SecurityPolicy::AddOriginAccessBlacklistEntry(
*SecurityOrigin::Create(source_origin), destination_protocol,
destination_host, allow_destination_subdomains);
}
void WebSecurityPolicy::RemoveOriginAccessBlacklistEntry(
const WebURL& source_origin,
const WebString& destination_protocol,
const WebString& destination_host,
bool allow_destination_subdomains) {
SecurityPolicy::RemoveOriginAccessBlacklistEntry(
*SecurityOrigin::Create(source_origin), destination_protocol,
destination_host, allow_destination_subdomains);
}
void WebSecurityPolicy::ResetOriginAccessBlacklists() {
SecurityPolicy::ResetOriginAccessBlacklists();
}
void WebSecurityPolicy::AddOriginTrustworthyWhiteList(
const WebSecurityOrigin& origin) {
SecurityPolicy::AddOriginTrustworthyWhiteList(*origin.Get());
......
......@@ -247,6 +247,18 @@ TEST_F(SecurityOriginTest, CanRequest) {
}
}
TEST_F(SecurityOriginTest, CanRequestWithWhitelistedAccess) {
scoped_refptr<const SecurityOrigin> origin =
SecurityOrigin::CreateFromString("https://chromium.org");
const blink::KURL url("https://example.com");
EXPECT_FALSE(origin->CanRequest(url));
// Adding the url to the access whitelist should allow the request.
SecurityPolicy::AddOriginAccessWhitelistEntry(*origin, "https", "example.com",
false);
EXPECT_TRUE(origin->CanRequest(url));
}
TEST_F(SecurityOriginTest, PortAndEffectivePortMethod) {
struct TestCase {
unsigned short port;
......
......@@ -44,13 +44,18 @@
namespace blink {
using OriginAccessWhiteList = Vector<OriginAccessEntry>;
using OriginAccessMap = HashMap<String, std::unique_ptr<OriginAccessWhiteList>>;
using OriginAccessList = Vector<OriginAccessEntry>;
using OriginAccessMap = HashMap<String, std::unique_ptr<OriginAccessList>>;
using OriginSet = HashSet<String>;
static OriginAccessMap& GetOriginAccessMap() {
DEFINE_STATIC_LOCAL(OriginAccessMap, origin_access_map, ());
return origin_access_map;
static OriginAccessMap& GetOriginAccessWhitelistMap() {
DEFINE_STATIC_LOCAL(OriginAccessMap, origin_access_whitelist_map, ());
return origin_access_whitelist_map;
}
static OriginAccessMap& GetOriginAccessBlacklistMap() {
DEFINE_STATIC_LOCAL(OriginAccessMap, origin_access_blacklist_map, ());
return origin_access_blacklist_map;
}
static OriginSet& TrustworthyOriginSet() {
......@@ -58,8 +63,77 @@ static OriginSet& TrustworthyOriginSet() {
return trustworthy_origin_set;
}
static void AddOriginAccessEntry(const SecurityOrigin& source_origin,
const String& destination_protocol,
const String& destination_domain,
bool allow_destination_subdomains,
OriginAccessMap& access_map) {
DCHECK(IsMainThread());
DCHECK(!source_origin.IsUnique());
if (source_origin.IsUnique())
return;
String source_string = source_origin.ToString();
OriginAccessMap::AddResult result = access_map.insert(source_string, nullptr);
if (result.is_new_entry)
result.stored_value->value = std::make_unique<OriginAccessList>();
OriginAccessList* list = result.stored_value->value.get();
list->push_back(OriginAccessEntry(
destination_protocol, destination_domain,
allow_destination_subdomains ? OriginAccessEntry::kAllowSubdomains
: OriginAccessEntry::kDisallowSubdomains));
}
static void RemoveOriginAccessEntry(const SecurityOrigin& source_origin,
const String& destination_protocol,
const String& destination_domain,
bool allow_destination_subdomains,
OriginAccessMap& access_map) {
DCHECK(IsMainThread());
DCHECK(!source_origin.IsUnique());
if (source_origin.IsUnique())
return;
String source_string = source_origin.ToString();
OriginAccessMap::iterator it = access_map.find(source_string);
if (it == access_map.end())
return;
OriginAccessList* list = it->value.get();
size_t index = list->Find(OriginAccessEntry(
destination_protocol, destination_domain,
allow_destination_subdomains ? OriginAccessEntry::kAllowSubdomains
: OriginAccessEntry::kDisallowSubdomains));
if (index == kNotFound)
return;
list->EraseAt(index);
if (list->IsEmpty())
access_map.erase(it);
}
static bool IsOriginPairInAccessMap(const SecurityOrigin* active_origin,
const SecurityOrigin* target_origin,
const OriginAccessMap& access_map) {
if (access_map.IsEmpty())
return false;
if (OriginAccessList* list = access_map.at(active_origin->ToString())) {
for (size_t i = 0; i < list->size(); ++i) {
if (list->at(i).MatchesOrigin(*target_origin) !=
OriginAccessEntry::kDoesNotMatchOrigin)
return true;
}
}
return false;
}
void SecurityPolicy::Init() {
GetOriginAccessMap();
GetOriginAccessWhitelistMap();
GetOriginAccessBlacklistMap();
TrustworthyOriginSet();
}
......@@ -197,17 +271,10 @@ bool SecurityPolicy::IsUrlWhiteListedTrustworthy(const KURL& url) {
bool SecurityPolicy::IsAccessWhiteListed(const SecurityOrigin* active_origin,
const SecurityOrigin* target_origin) {
const OriginAccessMap& map = GetOriginAccessMap();
if (map.IsEmpty())
return false;
if (OriginAccessWhiteList* list = map.at(active_origin->ToString())) {
for (size_t i = 0; i < list->size(); ++i) {
if (list->at(i).MatchesOrigin(*target_origin) !=
OriginAccessEntry::kDoesNotMatchOrigin)
return true;
}
}
return false;
return IsOriginPairInAccessMap(active_origin, target_origin,
GetOriginAccessWhitelistMap()) &&
!IsOriginPairInAccessMap(active_origin, target_origin,
GetOriginAccessBlacklistMap());
}
bool SecurityPolicy::IsAccessToURLWhiteListed(
......@@ -223,22 +290,9 @@ void SecurityPolicy::AddOriginAccessWhitelistEntry(
const String& destination_protocol,
const String& destination_domain,
bool allow_destination_subdomains) {
DCHECK(IsMainThread());
DCHECK(!source_origin.IsUnique());
if (source_origin.IsUnique())
return;
String source_string = source_origin.ToString();
OriginAccessMap::AddResult result =
GetOriginAccessMap().insert(source_string, nullptr);
if (result.is_new_entry)
result.stored_value->value = std::make_unique<OriginAccessWhiteList>();
OriginAccessWhiteList* list = result.stored_value->value.get();
list->push_back(OriginAccessEntry(
destination_protocol, destination_domain,
allow_destination_subdomains ? OriginAccessEntry::kAllowSubdomains
: OriginAccessEntry::kDisallowSubdomains));
AddOriginAccessEntry(source_origin, destination_protocol, destination_domain,
allow_destination_subdomains,
GetOriginAccessWhitelistMap());
}
void SecurityPolicy::RemoveOriginAccessWhitelistEntry(
......@@ -246,35 +300,39 @@ void SecurityPolicy::RemoveOriginAccessWhitelistEntry(
const String& destination_protocol,
const String& destination_domain,
bool allow_destination_subdomains) {
DCHECK(IsMainThread());
DCHECK(!source_origin.IsUnique());
if (source_origin.IsUnique())
return;
String source_string = source_origin.ToString();
OriginAccessMap& map = GetOriginAccessMap();
OriginAccessMap::iterator it = map.find(source_string);
if (it == map.end())
return;
OriginAccessWhiteList* list = it->value.get();
size_t index = list->Find(OriginAccessEntry(
destination_protocol, destination_domain,
allow_destination_subdomains ? OriginAccessEntry::kAllowSubdomains
: OriginAccessEntry::kDisallowSubdomains));
RemoveOriginAccessEntry(source_origin, destination_protocol,
destination_domain, allow_destination_subdomains,
GetOriginAccessWhitelistMap());
}
if (index == kNotFound)
return;
void SecurityPolicy::ResetOriginAccessWhitelists() {
DCHECK(IsMainThread());
GetOriginAccessWhitelistMap().clear();
}
list->EraseAt(index);
void SecurityPolicy::AddOriginAccessBlacklistEntry(
const SecurityOrigin& source_origin,
const String& destination_protocol,
const String& destination_domain,
bool allow_destination_subdomains) {
AddOriginAccessEntry(source_origin, destination_protocol, destination_domain,
allow_destination_subdomains,
GetOriginAccessBlacklistMap());
}
if (list->IsEmpty())
map.erase(it);
void SecurityPolicy::RemoveOriginAccessBlacklistEntry(
const SecurityOrigin& source_origin,
const String& destination_protocol,
const String& destination_domain,
bool allow_destination_subdomains) {
RemoveOriginAccessEntry(source_origin, destination_protocol,
destination_domain, allow_destination_subdomains,
GetOriginAccessBlacklistMap());
}
void SecurityPolicy::ResetOriginAccessWhitelists() {
void SecurityPolicy::ResetOriginAccessBlacklists() {
DCHECK(IsMainThread());
GetOriginAccessMap().clear();
GetOriginAccessBlacklistMap().clear();
}
bool SecurityPolicy::ReferrerPolicyFromString(
......
......@@ -76,6 +76,17 @@ class PLATFORM_EXPORT SecurityPolicy {
bool allow_destination_subdomains);
static void ResetOriginAccessWhitelists();
static void AddOriginAccessBlacklistEntry(const SecurityOrigin& source_origin,
const String& destination_protocol,
const String& destination_domain,
bool allow_destination_subdomains);
static void RemoveOriginAccessBlacklistEntry(
const SecurityOrigin& source_origin,
const String& destination_protocol,
const String& destination_domain,
bool allow_destination_subdomains);
static void ResetOriginAccessBlacklists();
static bool IsAccessWhiteListed(const SecurityOrigin* active_origin,
const SecurityOrigin* target_origin);
static bool IsAccessToURLWhiteListed(const SecurityOrigin* active_origin,
......
......@@ -287,4 +287,142 @@ TEST(SecurityPolicyTest, TrustworthyWhiteList) {
}
}
class SecurityPolicyAccessTest : public testing::Test {
public:
SecurityPolicyAccessTest() = default;
~SecurityPolicyAccessTest() override = default;
void SetUp() override {
https_example_origin_ =
SecurityOrigin::CreateFromString("https://example.com");
https_sub_example_origin_ =
SecurityOrigin::CreateFromString("https://sub.example.com");
http_example_origin_ =
SecurityOrigin::CreateFromString("http://example.com");
https_chromium_origin_ =
SecurityOrigin::CreateFromString("https://chromium.org");
https_google_origin_ =
SecurityOrigin::CreateFromString("https://google.com");
}
void TearDown() override {
SecurityPolicy::ResetOriginAccessWhitelists();
SecurityPolicy::ResetOriginAccessBlacklists();
}
const SecurityOrigin* https_example_origin() const {
return https_example_origin_.get();
}
const SecurityOrigin* https_sub_example_origin() const {
return https_sub_example_origin_.get();
}
const SecurityOrigin* http_example_origin() const {
return http_example_origin_.get();
}
const SecurityOrigin* https_chromium_origin() const {
return https_chromium_origin_.get();
}
const SecurityOrigin* https_google_origin() const {
return https_google_origin_.get();
}
private:
scoped_refptr<const SecurityOrigin> https_example_origin_;
scoped_refptr<const SecurityOrigin> https_sub_example_origin_;
scoped_refptr<const SecurityOrigin> http_example_origin_;
scoped_refptr<const SecurityOrigin> https_chromium_origin_;
scoped_refptr<const SecurityOrigin> https_google_origin_;
DISALLOW_COPY_AND_ASSIGN(SecurityPolicyAccessTest);
};
TEST_F(SecurityPolicyAccessTest, IsAccessWhiteListed) {
// By default, no access should be whitelisted.
EXPECT_FALSE(SecurityPolicy::IsAccessWhiteListed(https_chromium_origin(),
https_example_origin()));
EXPECT_FALSE(SecurityPolicy::IsAccessWhiteListed(https_chromium_origin(),
https_sub_example_origin()));
EXPECT_FALSE(SecurityPolicy::IsAccessWhiteListed(https_chromium_origin(),
http_example_origin()));
// Adding access for https://example.com should work, but should not grant
// access to subdomains or other schemes.
SecurityPolicy::AddOriginAccessWhitelistEntry(*https_chromium_origin(),
"https", "example.com", false);
EXPECT_TRUE(SecurityPolicy::IsAccessWhiteListed(https_chromium_origin(),
https_example_origin()));
EXPECT_FALSE(SecurityPolicy::IsAccessWhiteListed(https_chromium_origin(),
https_sub_example_origin()));
EXPECT_FALSE(SecurityPolicy::IsAccessWhiteListed(https_chromium_origin(),
http_example_origin()));
// Removing the entry should revoke access.
SecurityPolicy::RemoveOriginAccessWhitelistEntry(
*https_chromium_origin(), "https", "example.com", false);
EXPECT_FALSE(SecurityPolicy::IsAccessWhiteListed(https_chromium_origin(),
https_example_origin()));
EXPECT_FALSE(SecurityPolicy::IsAccessWhiteListed(https_chromium_origin(),
https_sub_example_origin()));
EXPECT_FALSE(SecurityPolicy::IsAccessWhiteListed(https_chromium_origin(),
http_example_origin()));
// Adding an entry that matches subdomains should grant access to any
// subdomains.
SecurityPolicy::AddOriginAccessWhitelistEntry(*https_chromium_origin(),
"https", "example.com", true);
EXPECT_TRUE(SecurityPolicy::IsAccessWhiteListed(https_chromium_origin(),
https_example_origin()));
EXPECT_TRUE(SecurityPolicy::IsAccessWhiteListed(https_chromium_origin(),
https_sub_example_origin()));
EXPECT_FALSE(SecurityPolicy::IsAccessWhiteListed(https_chromium_origin(),
http_example_origin()));
// Clearing the map should revoke all special access.
SecurityPolicy::ResetOriginAccessWhitelists();
EXPECT_FALSE(SecurityPolicy::IsAccessWhiteListed(https_chromium_origin(),
https_example_origin()));
EXPECT_FALSE(SecurityPolicy::IsAccessWhiteListed(https_chromium_origin(),
https_sub_example_origin()));
EXPECT_FALSE(SecurityPolicy::IsAccessWhiteListed(https_chromium_origin(),
http_example_origin()));
}
TEST_F(SecurityPolicyAccessTest, IsAccessWhiteListedWildCard) {
// An empty domain that matches subdomains results in matching every domain.
SecurityPolicy::AddOriginAccessWhitelistEntry(*https_chromium_origin(),
"https", "", true);
EXPECT_TRUE(SecurityPolicy::IsAccessWhiteListed(https_chromium_origin(),
https_example_origin()));
EXPECT_TRUE(SecurityPolicy::IsAccessWhiteListed(https_chromium_origin(),
https_google_origin()));
EXPECT_FALSE(SecurityPolicy::IsAccessWhiteListed(https_chromium_origin(),
http_example_origin()));
}
TEST_F(SecurityPolicyAccessTest, IsAccessWhiteListedWithBlacklistedEntries) {
// The blacklists take priority over the whitelist.
SecurityPolicy::AddOriginAccessWhitelistEntry(*https_chromium_origin(),
"https", "example.com", true);
SecurityPolicy::AddOriginAccessBlacklistEntry(*https_chromium_origin(),
"https", "example.com", false);
EXPECT_FALSE(SecurityPolicy::IsAccessWhiteListed(https_chromium_origin(),
https_example_origin()));
EXPECT_TRUE(SecurityPolicy::IsAccessWhiteListed(https_chromium_origin(),
https_sub_example_origin()));
}
TEST_F(SecurityPolicyAccessTest,
IsAccessWhiteListedWildcardWithBlacklistedEntries) {
SecurityPolicy::AddOriginAccessWhitelistEntry(*https_chromium_origin(),
"https", "", true);
SecurityPolicy::AddOriginAccessBlacklistEntry(*https_chromium_origin(),
"https", "google.com", false);
EXPECT_TRUE(SecurityPolicy::IsAccessWhiteListed(https_chromium_origin(),
https_example_origin()));
EXPECT_FALSE(SecurityPolicy::IsAccessWhiteListed(https_chromium_origin(),
https_google_origin()));
}
} // namespace blink
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment