Commit 751422f6 authored by Antonio Sartori's avatar Antonio Sartori Committed by Commit Bot

Implement CSP source intersection algorithm in services/network

This CL implements the part computing the intersection of two sources
of the Content Security Policy: Embedded Enforcement subsumption
algorithm following
https://w3c.github.io/webappsec-cspee/#intersection-source-expressions
in the services/network Content Security Policy module.

This is part of a series of CL implementing the whole subsumption
algorithm according to that spec.

Bug: 1094909
Change-Id: I835c42632cacfd884321ee43a2f2a06c31abab8a
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2315687Reviewed-by: default avatarMike West <mkwst@chromium.org>
Reviewed-by: default avatarArthur Sonzogni <arthursonzogni@chromium.org>
Commit-Queue: Antonio Sartori <antoniosartori@chromium.org>
Cr-Commit-Position: refs/heads/master@{#803121}
parent 8a54469c
......@@ -204,6 +204,73 @@ bool CheckCSPSource(const mojom::CSPSourcePtr& source,
SourceAllowPath(source, url, has_followed_redirect);
}
mojom::CSPSourcePtr CSPSourcesIntersect(const mojom::CSPSourcePtr& source_a,
const mojom::CSPSourcePtr& source_b) {
// If the original source expressions didn't have a scheme, we should have
// filled that already with origin's scheme.
DCHECK(!source_a->scheme.empty());
DCHECK(!source_b->scheme.empty());
auto result = mojom::CSPSource::New();
if (MatchScheme(source_a->scheme, source_b->scheme) !=
SchemeMatchingResult::NotMatching) {
result->scheme = source_b->scheme;
} else if (MatchScheme(source_b->scheme, source_a->scheme) !=
SchemeMatchingResult::NotMatching) {
result->scheme = source_a->scheme;
} else {
return nullptr;
}
if (IsSchemeOnly(source_a)) {
auto new_result = source_b->Clone();
new_result->scheme = result->scheme;
return new_result;
} else if (IsSchemeOnly(source_b)) {
auto new_result = source_a->Clone();
new_result->scheme = result->scheme;
return new_result;
}
const std::string host_a =
(source_a->is_host_wildcard ? "*." : "") + source_a->host;
const std::string host_b =
(source_b->is_host_wildcard ? "*." : "") + source_b->host;
if (SourceAllowHost(source_a, host_b)) {
result->host = source_b->host;
result->is_host_wildcard = source_b->is_host_wildcard;
} else if (SourceAllowHost(source_b, host_a)) {
result->host = source_a->host;
result->is_host_wildcard = source_a->is_host_wildcard;
} else {
return nullptr;
}
if (SourceAllowPort(source_a, source_b->port, source_b->scheme) !=
PortMatchingResult::NotMatching &&
// If port_a is explicitly specified but port_b is omitted, then we should
// take port_a instead of port_b, since port_a is stricter.
!(source_a->port != url::PORT_UNSPECIFIED &&
source_b->port == url::PORT_UNSPECIFIED)) {
result->port = source_b->port;
result->is_port_wildcard = source_b->is_port_wildcard;
} else if (SourceAllowPort(source_b, source_a->port, source_a->scheme) !=
PortMatchingResult::NotMatching) {
result->port = source_a->port;
} else {
return nullptr;
}
if (SourceAllowPath(source_a, source_b->path))
result->path = source_b->path;
else if (SourceAllowPath(source_b, source_a->path))
result->path = source_a->path;
else
return nullptr;
return result;
}
// Check whether |source_a| subsumes |source_b|.
bool CSPSourceSubsumes(const mojom::CSPSourcePtr& source_a,
const mojom::CSPSourcePtr& source_b) {
......
......@@ -22,6 +22,12 @@ bool CheckCSPSource(const mojom::CSPSourcePtr& source,
CSPContext* context,
bool has_followed_redirect = false);
// Compute the source intersection of |source_a| and |source_b|.
// https://w3c.github.io/webappsec-cspee/#intersection-source-expressions
COMPONENT_EXPORT(NETWORK_CPP)
mojom::CSPSourcePtr CSPSourcesIntersect(const mojom::CSPSourcePtr& source_a,
const mojom::CSPSourcePtr& source_b);
// Check if |source_a| subsumes |source_b| according to
// https://w3c.github.io/webappsec-cspee/#subsume-source-expressions
COMPONENT_EXPORT(NETWORK_CPP)
......
......@@ -323,6 +323,74 @@ TEST(CSPSourceTest, RedirectMatching) {
EXPECT_FALSE(Allow(source, GURL("http://a.com:9000/foo/"), &context, false));
}
TEST(CSPSourceTest, Intersect) {
struct TestCase {
const char* a;
const char* b;
const char* intersection;
} cases[]{
// Scheme only.
{"http:", "https:", "https:"},
{"http:", "http:", "http:"},
// b is stronger than a.
{"http:", "http://example.org/page.html", "http://example.org/page.html"},
{"http://example.org", "http://example.org/page.html",
"http://example.org/page.html"},
{"http://example.org", "http://example.org/page.html",
"http://example.org/page.html"},
{"http://example.org/page.html", "http://example.org/page.html",
"http://example.org/page.html"},
{"http:", "https://example.org/page.html",
"https://example.org/page.html"},
{"http://example.org:80", "http://example.org", "http://example.org:80"},
{"http://example.org:80", "http://example.org:*",
"http://example.org:80"},
{"http://example.org:90", "http://example.org:*",
"http://example.org:90"},
// Nontrivial intersection.
{"https:", "http://example.org/page.html",
"https://example.org/page.html"},
{"https://*.org/page.html", "https://example.org/",
"https://example.org/page.html"},
{"http://*.org/page.html", "https://example.org/",
"https://example.org/page.html"},
{"http://example.org:*/page.html", "https://example.org/",
"https://example.org/page.html"},
{"http://example.org:*/page.html", "https://example.org/",
"https://example.org/page.html"},
// Empty intersection
{"data:", "http:", nullptr},
{"data:", "http://example.org", nullptr},
{"data://example.org", "http://example.org", nullptr},
{"http://example.com", "http://example.org", nullptr},
{"http://example.org:90", "http://example.org", nullptr},
{"http://example.org/page.html", "http://example.org/about.html",
nullptr},
};
for (const auto& test : cases) {
auto a = CSPSource(test.a);
auto b = CSPSource(test.b);
auto a_intersect_b = CSPSourcesIntersect(a, b);
auto b_intersect_a = CSPSourcesIntersect(a, b);
if (test.intersection) {
EXPECT_EQ(test.intersection, ToString(a_intersect_b))
<< "The intersection of " << test.a << " and " << test.b
<< " should be " << test.intersection;
// Intersection should be symmetric.
EXPECT_EQ(test.intersection, ToString(b_intersect_a))
<< "The intersection of " << test.b << " and " << test.a
<< " should be " << test.intersection;
} else {
EXPECT_FALSE(a_intersect_b) << "The intersection of " << test.a << " and "
<< test.b << " should be empty.";
EXPECT_FALSE(b_intersect_a) << "The intersection of " << test.b << " and "
<< test.a << " should be empty.";
}
}
}
TEST(CSPSourceTest, DoesNotSubsume) {
struct TestCase {
const char* a;
......
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