Commit 14162d16 authored by amalika's avatar amalika Committed by Commit bot

Part 2.1: Is policy list subsumed under subsuming policy?

This is part of an experimental feature Embedding-CSP.

In particular this is the second CL devoted to implementing
*3.3. Is policy list subsumed under subsuming policy?

In this patch we try to find normalized list of CSPSources
from the two given vectors of CSPSources.

Example:
A: http://*.example.com
B: http://example.com/index.html
In this case, the latter contains more information because
it provides a more specific path and no host wildcard. Thus,
their normalization would be equivalent to B.

Example:
A: http://
B: http://example.com/index.html
Scheme-source/ host-source expression normalization should
be equivalent to B.

Example:
A: https://
B: http://example.com/index.html
Scheme-source/ host-source expression normalization should
return https://example.com/index.html.

BUG=647588

Review-Url: https://codereview.chromium.org/2470083002
Cr-Commit-Position: refs/heads/master@{#430267}
parent bb2aaf9a
......@@ -124,6 +124,40 @@ bool CSPSource::subsumes(CSPSource* other) {
return hostSubsumes && portSubsumes && pathSubsumes;
}
bool CSPSource::isSimilar(CSPSource* other) {
bool schemesMatch =
schemeMatches(other->m_scheme) || other->schemeMatches(m_scheme);
if (!schemesMatch || isSchemeOnly() || other->isSchemeOnly())
return schemesMatch;
bool hostsMatch = (m_host == other->m_host) || hostMatches(other->m_host) ||
other->hostMatches(m_host);
bool portsMatch = (other->m_portWildcard == HasWildcard) ||
portMatches(other->m_port, other->m_scheme);
bool pathsMatch = pathMatches(other->m_path) || other->pathMatches(m_path);
if (hostsMatch && portsMatch && pathsMatch)
return true;
return false;
}
CSPSource* CSPSource::intersect(CSPSource* other) {
if (!isSimilar(other))
return nullptr;
String scheme = other->schemeMatches(m_scheme) ? m_scheme : other->m_scheme;
String host = m_hostWildcard == NoWildcard ? m_host : other->m_host;
String path = other->pathMatches(m_path) ? m_path : other->m_path;
int port = (other->m_portWildcard == HasWildcard || !other->m_port)
? m_port
: other->m_port;
WildcardDisposition hostWildcard =
(m_hostWildcard == HasWildcard) ? other->m_hostWildcard : m_hostWildcard;
WildcardDisposition portWildcard =
(m_portWildcard == HasWildcard) ? other->m_portWildcard : m_portWildcard;
return new CSPSource(m_policy, scheme, host, port, path, hostWildcard,
portWildcard);
}
bool CSPSource::isSchemeOnly() const {
return m_host.isEmpty();
}
......
......@@ -35,16 +35,23 @@ class CORE_EXPORT CSPSource : public GarbageCollectedFinalized<CSPSource> {
// Returns true if this CSPSource subsumes the other, as defined by the
// algorithm at https://w3c.github.io/webappsec-csp/embedded/#subsume-policy
bool subsumes(CSPSource*);
// Retrieve the most restrictive information from the two CSPSources if
// isSimilar is true for the two. Otherwise, return nullptr.
CSPSource* intersect(CSPSource*);
DECLARE_TRACE();
private:
FRIEND_TEST_ALL_PREFIXES(CSPSourceTest, IsSimilar);
FRIEND_TEST_ALL_PREFIXES(SourceListDirectiveTest, GetIntersectCSPSources);
bool schemeMatches(const String&) const;
bool hostMatches(const String&) const;
bool pathMatches(const String&) const;
// Protocol is necessary to determine default port if it is zero.
bool portMatches(int port, const String& protocol) const;
bool isSchemeOnly() const;
bool isSimilar(CSPSource* other);
Member<ContentSecurityPolicy> m_policy;
String m_scheme;
......
......@@ -344,4 +344,105 @@ TEST_F(CSPSourceTest, SchemesOnlySubsumes) {
}
}
TEST_F(CSPSourceTest, IsSimilar) {
struct Source {
const char* scheme;
const char* host;
const char* path;
const int port;
};
struct TestCase {
const Source a;
const Source b;
bool isSimilar;
} cases[] = {
// Similar
{{"http", "example.com", "/", 0}, {"http", "example.com", "/", 0}, true},
// Schemes
{{"https", "example.com", "/", 0},
{"https", "example.com", "/", 0},
true},
{{"https", "example.com", "/", 0}, {"http", "example.com", "/", 0}, true},
{{"ws", "example.com", "/", 0}, {"wss", "example.com", "/", 0}, true},
// Ports
{{"http", "example.com", "/", 90},
{"http", "example.com", "/", 90},
true},
{{"wss", "example.com", "/", 0},
{"wss", "example.com", "/", 0},
true}, // use default port
{{"http", "example.com", "/", 80}, {"http", "example.com", "/", 0}, true},
// Paths
{{"http", "example.com", "/", 0},
{"http", "example.com", "/1.html", 0},
true},
{{"http", "example.com", "/", 0}, {"http", "example.com", "", 0}, true},
{{"http", "example.com", "/", 0},
{"http", "example.com", "/a/b/", 0},
true},
{{"http", "example.com", "/a/", 0},
{"http", "example.com", "/a/", 0},
true},
{{"http", "example.com", "/a/", 0},
{"http", "example.com", "/a/b/", 0},
true},
{{"http", "example.com", "/a/", 0},
{"http", "example.com", "/a/b/1.html", 0},
true},
{{"http", "example.com", "/1.html", 0},
{"http", "example.com", "/1.html", 0},
true},
// Mixed
{{"http", "example.com", "/1.html", 90},
{"http", "example.com", "/", 90},
true},
{{"https", "example.com", "/", 0}, {"http", "example.com", "/", 0}, true},
{{"http", "example.com", "/a/", 90},
{"https", "example.com", "", 90},
true},
{{"wss", "example.com", "/a/", 90},
{"ws", "example.com", "/a/b/", 90},
true},
{{"https", "example.com", "/a/", 90},
{"https", "example.com", "/a/b/", 90},
true},
// Not Similar
{{"http", "example.com", "/a/", 0},
{"https", "example.com", "", 90},
false},
{{"https", "example.com", "/", 0},
{"https", "example.com", "/", 90},
false},
{{"http", "example.com", "/", 0}, {"http", "another.com", "/", 0}, false},
{{"wss", "example.com", "/", 0}, {"http", "example.com", "/", 0}, false},
{{"wss", "example.com", "/", 0}, {"about", "example.com", "/", 0}, false},
{{"http", "example.com", "/", 0},
{"about", "example.com", "/", 0},
false},
{{"http", "example.com", "/1.html", 0},
{"http", "example.com", "/2.html", 0},
false},
{{"http", "example.com", "/a/1.html", 0},
{"http", "example.com", "/a/b/", 0},
false},
{{"http", "example.com", "/", 443},
{"about", "example.com", "/", 800},
false},
};
for (const auto& test : cases) {
CSPSource* returned = new CSPSource(
csp.get(), test.a.scheme, test.a.host, test.a.port, test.a.path,
CSPSource::NoWildcard, CSPSource::NoWildcard);
CSPSource* required = new CSPSource(
csp.get(), test.b.scheme, test.b.host, test.b.port, test.b.path,
CSPSource::NoWildcard, CSPSource::NoWildcard);
EXPECT_EQ(returned->isSimilar(required), test.isSimilar);
// Verify the same test with a and b swapped.
EXPECT_EQ(required->isSimilar(returned), test.isSimilar);
}
}
} // namespace blink
......@@ -569,6 +569,21 @@ bool SourceListDirective::hasSourceMatchInList(
return false;
}
HeapVector<Member<CSPSource>> SourceListDirective::getIntersectCSPSources(
HeapVector<Member<CSPSource>> otherVector) {
HeapVector<Member<CSPSource>> normalized;
for (const auto& aCspSource : m_list) {
Member<CSPSource> matchedCspSource(nullptr);
for (const auto& bCspSource : otherVector) {
if ((matchedCspSource = bCspSource->intersect(aCspSource)))
break;
}
if (matchedCspSource)
normalized.append(matchedCspSource);
}
return normalized;
}
DEFINE_TRACE(SourceListDirective) {
visitor->trace(m_policy);
visitor->trace(m_list);
......
......@@ -47,6 +47,8 @@ class CORE_EXPORT SourceListDirective final : public CSPDirective {
uint8_t hashAlgorithmsUsed() const;
private:
FRIEND_TEST_ALL_PREFIXES(SourceListDirectiveTest, GetIntersectCSPSources);
bool parseSource(const UChar* begin,
const UChar* end,
String& scheme,
......@@ -82,6 +84,8 @@ class CORE_EXPORT SourceListDirective final : public CSPDirective {
const DigestValue& hash);
bool hasSourceMatchInList(const KURL&, ResourceRequest::RedirectStatus) const;
HeapVector<Member<CSPSource>> getIntersectCSPSources(
HeapVector<Member<CSPSource>> other);
Member<ContentSecurityPolicy> m_policy;
HeapVector<Member<CSPSource>> m_list;
......
......@@ -20,6 +20,15 @@ class SourceListDirectiveTest : public ::testing::Test {
SourceListDirectiveTest() : csp(ContentSecurityPolicy::create()) {}
protected:
struct Source {
String scheme;
String host;
const int port;
String path;
CSPSource::WildcardDisposition hostWildcard;
CSPSource::WildcardDisposition portWildcard;
};
virtual void SetUp() {
KURL secureURL(ParsedURLString, "https://example.test/image.png");
RefPtr<SecurityOrigin> secureOrigin(SecurityOrigin::create(secureURL));
......@@ -28,6 +37,12 @@ class SourceListDirectiveTest : public ::testing::Test {
csp->bindToExecutionContext(document.get());
}
bool equalSources(const Source& a, const Source& b) {
return a.scheme == b.scheme && a.host == b.host && a.port == b.port &&
a.path == b.path && a.hostWildcard == b.hostWildcard &&
a.portWildcard == b.portWildcard;
}
Persistent<ContentSecurityPolicy> csp;
Persistent<Document> document;
};
......@@ -213,4 +228,69 @@ TEST_F(SourceListDirectiveTest, RedirectMatching) {
ResourceRequest::RedirectStatus::FollowedRedirect));
}
TEST_F(SourceListDirectiveTest, GetIntersectCSPSources) {
KURL base;
String sources =
"http://example1.com/foo/ http://*.example2.com/bar/ "
"http://*.example3.com:*/bar/";
SourceListDirective sourceList("script-src", sources, csp.get());
struct TestCase {
String sources;
String expected;
} cases[] = {
{"http://example1.com/foo/ http://example2.com/bar/",
"http://example1.com/foo/ http://example2.com/bar/"},
// Normalizing schemes.
{"https://example1.com/foo/ http://example2.com/bar/",
"https://example1.com/foo/ http://example2.com/bar/"},
{"https://example1.com/foo/ https://example2.com/bar/",
"https://example1.com/foo/ https://example2.com/bar/"},
{"https://example1.com/foo/ wss://example2.com/bar/",
"https://example1.com/foo/"},
// Normalizing hosts.
{"http://*.example1.com/foo/ http://*.example2.com/bar/",
"http://example1.com/foo/ http://*.example2.com/bar/"},
{"http://*.example1.com/foo/ http://foo.example2.com/bar/",
"http://example1.com/foo/ http://foo.example2.com/bar/"},
// Normalizing ports.
{"http://example1.com:80/foo/ http://example2.com/bar/",
"http://example1.com:80/foo/ http://example2.com/bar/"},
{"http://example1.com/foo/ http://example2.com:90/bar/",
"http://example1.com/foo/"},
{"http://example1.com:*/foo/ http://example2.com/bar/",
"http://example1.com/foo/ http://example2.com/bar/"},
{"http://*.example3.com:100/bar/ http://example1.com/foo/",
"http://example1.com/foo/ http://*.example3.com:100/bar/"},
// Normalizing paths.
{"http://example1.com/ http://example2.com/",
"http://example1.com/foo/ http://example2.com/bar/"},
{"http://example1.com/foo/index.html http://example2.com/bar/",
"http://example1.com/foo/index.html http://example2.com/bar/"},
{"http://example1.com/bar http://example2.com/bar/",
"http://example2.com/bar/"},
// Not similar to be normalized
{"http://non-example1.com/foo/ http://non-example2.com/bar/", ""},
{"https://non-example1.com/foo/ wss://non-example2.com/bar/", ""},
};
for (const auto& test : cases) {
SourceListDirective secondList("script-src", test.sources, csp.get());
HeapVector<Member<CSPSource>> normalized =
sourceList.getIntersectCSPSources(secondList.m_list);
SourceListDirective helperSourceList("script-src", test.expected,
csp.get());
HeapVector<Member<CSPSource>> expected = helperSourceList.m_list;
EXPECT_EQ(normalized.size(), expected.size());
for (size_t i = 0; i < normalized.size(); i++) {
Source a = {normalized[i]->m_scheme, normalized[i]->m_host,
normalized[i]->m_port, normalized[i]->m_path,
normalized[i]->m_hostWildcard, normalized[i]->m_portWildcard};
Source b = {expected[i]->m_scheme, expected[i]->m_host,
expected[i]->m_port, expected[i]->m_path,
expected[i]->m_hostWildcard, expected[i]->m_portWildcard};
EXPECT_TRUE(equalSources(a, b));
}
}
}
} // 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