Commit e21b2cf3 authored by Antonio Sartori's avatar Antonio Sartori Committed by Commit Bot

Implement Subsume for CSP plugin-types in services/network

This CL adds one missing step to the Subsume algorithm of the
services/network Content-Security-Policy module, checking the
plugin-types directives

Although the plugin-types directive is ignored in the current version
of the spec https://w3c.github.io/webappsec-cspee, subsumption is
already being checked in the blink module, and we reimplement the same
logic here.

Bug: 1094909
Change-Id: Iae43bac96b111667623cad923f913165c8f02c42
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2346356
Commit-Queue: Antonio Sartori <antoniosartori@chromium.org>
Reviewed-by: default avatarArthur Sonzogni <arthursonzogni@chromium.org>
Reviewed-by: default avatarMike West <mkwst@chromium.org>
Cr-Commit-Position: refs/heads/master@{#804856}
parent c1fb43c3
......@@ -6,6 +6,7 @@
#include <sstream>
#include <string>
#include "base/containers/flat_set.h"
#include "base/strings/string_number_conversions.h"
#include "base/strings/string_piece.h"
#include "base/strings/string_split.h"
......@@ -859,6 +860,56 @@ std::pair<CSPDirectiveName, const mojom::CSPSourceList*> GetSourceList(
return std::make_pair(CSPDirectiveName::Unknown, nullptr);
}
// Check that all plugin-types allowed by the intersection of the policies in
// |policies_b| are also allowed by |policy_a|.
bool PluginTypesSubsumes(
const mojom::ContentSecurityPolicy& policy_a,
const std::vector<mojom::ContentSecurityPolicyPtr>& policies_b) {
// Note that `policy->plugin_types == base::nullopt` means all plugin-types
// are allowed, while if `policy->plugin_types` is the empty vector than no
// plugin-types are allowed.
if (!policy_a.plugin_types.has_value())
// |types_a| allows everything.
return true;
if (policies_b.empty())
return false;
// Compute the intersection of the allowed plugin-types from |policies_b|.
// First, find the first non-null plugin-types entry in |policies_b|.
base::Optional<base::flat_set<std::string>> types_b;
auto it = policies_b.begin();
for (; it != policies_b.end(); ++it) {
if ((*it)->plugin_types.has_value()) {
types_b = base::flat_set<std::string>((*it)->plugin_types.value());
break;
}
}
// If |types_b| is base::nullopt, then no policy in |policies_b| specified
// any plugin-types, so |policies_b| allows everything.
if (!types_b.has_value())
return false;
// Now complete the intersection by considering the remaining policies of
// |policies_b|.
for (; it != policies_b.end(); ++it) {
if ((*it)->plugin_types.has_value()) {
base::flat_set<std::string> set((*it)->plugin_types.value());
base::EraseIf(types_b.value(),
[&set](const auto& type) { return !set.contains(type); });
}
}
// Check that every plugin-type in |types_b| is allowed by |types_a|.
return util::ranges::all_of(types_b.value(), [&](const std::string& type_b) {
return util::ranges::any_of(
policy_a.plugin_types.value(),
[&](const std::string& type_a) { return type_a == type_b; });
});
}
} // namespace
void AddContentSecurityPolicyFromHeaders(
......@@ -1038,10 +1089,12 @@ bool IsValidRequiredCSPAttr(
bool Subsumes(const mojom::ContentSecurityPolicy& policy_a,
const std::vector<mojom::ContentSecurityPolicyPtr>& policies_b,
const url::Origin& origin_b) {
if (policy_a.directives.empty())
if (policy_a.header->type == mojom::ContentSecurityPolicyType::kReport)
return true;
if (policy_a.header->type == mojom::ContentSecurityPolicyType::kReport)
if (!PluginTypesSubsumes(policy_a, policies_b))
return false;
if (policy_a.directives.empty())
return true;
if (policies_b.empty())
......@@ -1080,8 +1133,8 @@ bool Subsumes(const mojom::ContentSecurityPolicy& policy_a,
if (source_list.second)
returned.push_back(source_list.second);
}
// TODO(amalika): Add checks for plugin-types, sandbox, disown-opener,
// navigation-to, worker-src.
// TODO(amalika): Add checks for sandbox, disown-opener,
// navigation-to.
return CSPSourceListSubsumes(*required.second, returned, required.first,
origin_b);
});
......
......@@ -1253,12 +1253,8 @@ TEST(ContentSecurityPolicy, Subsumes) {
};
for (auto& test : cases) {
std::vector<mojom::ContentSecurityPolicyPtr> required_csp;
auto required_csp_headers =
base::MakeRefCounted<net::HttpResponseHeaders>("HTTP/1.1 200 OK");
required_csp_headers->SetHeader("Content-Security-Policy", test.required);
AddContentSecurityPolicyFromHeaders(
*required_csp_headers, GURL("https://example.com/"), &required_csp);
std::vector<mojom::ContentSecurityPolicyPtr> required_csp =
ParseCSP(test.required);
auto returned_csp_headers =
base::MakeRefCounted<net::HttpResponseHeaders>("HTTP/1.1 200 OK");
......@@ -1277,4 +1273,76 @@ TEST(ContentSecurityPolicy, Subsumes) {
}
}
TEST(ContentSecurityPolicy, SubsumesPluginTypes) {
struct TestCase {
const char* policy_a;
const char* policies_b;
bool expected;
} cases[] = {
// `policyA` subsumes `policiesB`.
{"script-src 'unsafe-inline'",
"script-src , script-src http://example.com, plugin-types text/plain",
true},
{"script-src http://example.com",
"script-src http://example.com; plugin-types ", true},
{"script-src http://example.com",
"script-src http://example.com; plugin-types text/plain", true},
{"script-src http://example.com; plugin-types text/plain",
"script-src http://example.com; plugin-types text/plain", true},
{"script-src http://example.com; plugin-types text/plain",
"script-src http://example.com; plugin-types ", true},
{"script-src http://example.com; plugin-types text/plain",
"script-src http://example.com; plugin-types , plugin-types ", true},
{"plugin-types application/pdf text/plain",
"plugin-types application/pdf text/plain, plugin-types "
"application/x-blink-test-plugin",
true},
{"plugin-types application/pdf text/plain",
"plugin-types application/pdf text/plain,"
"plugin-types application/pdf text/plain "
"application/x-blink-test-plugin",
true},
{"plugin-types application/x-shockwave-flash application/pdf text/plain",
"plugin-types application/x-shockwave-flash application/pdf text/plain, "
"plugin-types application/x-shockwave-flash",
true},
{"plugin-types application/x-shockwave-flash",
"plugin-types application/x-shockwave-flash application/pdf text/plain, "
"plugin-types application/x-shockwave-flash",
true},
// `policyA` does not subsume `policiesB`.
{"script-src http://example.com; plugin-types text/plain", "", false},
{"script-src http://example.com; plugin-types text/plain",
"script-src http://example.com", false},
{"plugin-types random-value",
"script-src 'unsafe-inline', plugin-types text/plain", false},
{"plugin-types random-value",
"script-src http://example.com, script-src http://example.com", false},
{"plugin-types random-value",
"plugin-types text/plain, plugin-types text/plain", false},
{"script-src http://example.com; plugin-types text/plain",
"plugin-types , plugin-types ", false},
{"plugin-types application/pdf text/plain",
"plugin-types application/x-blink-test-plugin,"
"plugin-types application/x-blink-test-plugin",
false},
{"plugin-types application/pdf text/plain",
"plugin-types application/pdf application/x-blink-test-plugin, "
"plugin-types application/x-blink-test-plugin",
false},
};
for (const auto& test : cases) {
std::vector<mojom::ContentSecurityPolicyPtr> policy_a =
ParseCSP(test.policy_a);
std::vector<mojom::ContentSecurityPolicyPtr> policies_b =
ParseCSP(test.policies_b);
EXPECT_EQ(Subsumes(*policy_a[0], policies_b,
url::Origin::Create(GURL("https://a.com"))),
test.expected)
<< test.policy_a << " should " << (test.expected ? "" : "not ")
<< "subsume " << test.policies_b;
}
}
} // namespace network
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