Commit d0d26288 authored by Lucas Gadani's avatar Lucas Gadani Committed by Commit Bot

CSP: Parse and report violations from Content-Security-Policy-Report-Only

headers in the browser process.

Bug: 759184
Change-Id: Iefb33fa8ec2264f3e224d5a3ab92417160848fdf
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/1958692Reviewed-by: default avatarArthur Sonzogni <arthursonzogni@chromium.org>
Reviewed-by: default avatarAlex Moshchuk <alexmos@chromium.org>
Reviewed-by: default avatarDaniel Cheng <dcheng@chromium.org>
Commit-Queue: Lucas Gadani <lfg@chromium.org>
Cr-Commit-Position: refs/heads/master@{#726618}
parent 7e4c17e7
...@@ -177,7 +177,8 @@ ContentSecurityPolicy::ContentSecurityPolicy( ...@@ -177,7 +177,8 @@ ContentSecurityPolicy::ContentSecurityPolicy(
// struct. // struct.
ContentSecurityPolicy::ContentSecurityPolicy( ContentSecurityPolicy::ContentSecurityPolicy(
network::mojom::ContentSecurityPolicyPtr csp) network::mojom::ContentSecurityPolicyPtr csp)
: report_endpoints(std::move(csp->report_endpoints)), : header("", csp->type, csp->source),
report_endpoints(std::move(csp->report_endpoints)),
use_reporting_api(csp->use_reporting_api) { use_reporting_api(csp->use_reporting_api) {
for (auto& directive : csp->directives) for (auto& directive : csp->directives)
directives.emplace_back(std::move(directive)); directives.emplace_back(std::move(directive));
......
...@@ -34,6 +34,7 @@ int LLVMFuzzerInitialize(int* argc, char*** argv) { ...@@ -34,6 +34,7 @@ int LLVMFuzzerInitialize(int* argc, char*** argv) {
int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) { int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) {
ContentSecurityPolicy policy; ContentSecurityPolicy policy;
policy.Parse(GURL("https://example.com/"), policy.Parse(GURL("https://example.com/"),
network::mojom::ContentSecurityPolicyType::kEnforce,
std::string(reinterpret_cast<const char*>(data), size)); std::string(reinterpret_cast<const char*>(data), size));
return 0; return 0;
......
...@@ -359,14 +359,24 @@ bool ContentSecurityPolicy::Parse(const GURL& base_url, ...@@ -359,14 +359,24 @@ bool ContentSecurityPolicy::Parse(const GURL& base_url,
std::string header_value; std::string header_value;
while (headers.EnumerateHeader(&iter, "content-security-policy", while (headers.EnumerateHeader(&iter, "content-security-policy",
&header_value)) { &header_value)) {
if (!Parse(base_url, header_value)) if (!Parse(base_url, network::mojom::ContentSecurityPolicyType::kEnforce,
header_value))
return false;
}
iter = 0;
while (headers.EnumerateHeader(&iter, "content-security-policy-report-only",
&header_value)) {
if (!Parse(base_url, network::mojom::ContentSecurityPolicyType::kReport,
header_value))
return false; return false;
} }
return true; return true;
} }
bool ContentSecurityPolicy::Parse(const GURL& base_url, bool ContentSecurityPolicy::Parse(
base::StringPiece header_value) { const GURL& base_url,
network::mojom::ContentSecurityPolicyType type,
base::StringPiece header_value) {
// RFC7230, section 3.2.2 specifies that headers appearing multiple times can // RFC7230, section 3.2.2 specifies that headers appearing multiple times can
// be combined with a comma. Walk the header string, and parse each comma // be combined with a comma. Walk the header string, and parse each comma
// separated chunk as a separate header. // separated chunk as a separate header.
...@@ -375,6 +385,7 @@ bool ContentSecurityPolicy::Parse(const GURL& base_url, ...@@ -375,6 +385,7 @@ bool ContentSecurityPolicy::Parse(const GURL& base_url,
base::SPLIT_WANT_NONEMPTY)) { base::SPLIT_WANT_NONEMPTY)) {
DirectivesMap directives = ParseHeaderValue(header); DirectivesMap directives = ParseHeaderValue(header);
auto content_security_policy_ptr = mojom::ContentSecurityPolicy::New(); auto content_security_policy_ptr = mojom::ContentSecurityPolicy::New();
content_security_policy_ptr->type = type;
auto frame_ancestors = directives.find("frame-ancestors"); auto frame_ancestors = directives.find("frame-ancestors");
if (frame_ancestors != directives.end()) { if (frame_ancestors != directives.end()) {
......
...@@ -33,7 +33,9 @@ class COMPONENT_EXPORT(NETWORK_CPP) ContentSecurityPolicy { ...@@ -33,7 +33,9 @@ class COMPONENT_EXPORT(NETWORK_CPP) ContentSecurityPolicy {
bool Parse(const GURL& request_url, const net::HttpResponseHeaders& headers); bool Parse(const GURL& request_url, const net::HttpResponseHeaders& headers);
// Parses a Content-Security-Policy |header|. // Parses a Content-Security-Policy |header|.
bool Parse(const GURL& base_url, base::StringPiece header); bool Parse(const GURL& base_url,
network::mojom::ContentSecurityPolicyType type,
base::StringPiece header);
const std::vector<mojom::ContentSecurityPolicyPtr>& const std::vector<mojom::ContentSecurityPolicyPtr>&
content_security_policies() { content_security_policies() {
......
...@@ -73,6 +73,12 @@ struct CSPDirective { ...@@ -73,6 +73,12 @@ struct CSPDirective {
struct ContentSecurityPolicy { struct ContentSecurityPolicy {
array<CSPDirective> directives; array<CSPDirective> directives;
// When set to kReport, this CSP will not enforce the directives, just report
// the violations.
ContentSecurityPolicyType type = kEnforce;
ContentSecurityPolicySource source = kHTTP;
// Whether this CSP policy uses the new reporting API. // Whether this CSP policy uses the new reporting API.
// https://w3c.github.io/reporting/ // https://w3c.github.io/reporting/
bool use_reporting_api = false; bool use_reporting_api = false;
......
...@@ -62,8 +62,8 @@ blink::ContentSecurityPolicyPtr ConvertToBlink(ContentSecurityPolicyPtr csp) { ...@@ -62,8 +62,8 @@ blink::ContentSecurityPolicyPtr ConvertToBlink(ContentSecurityPolicyPtr csp) {
for (auto& endpoint : csp->report_endpoints) for (auto& endpoint : csp->report_endpoints)
report_endpoints.push_back(String::FromUTF8(endpoint)); report_endpoints.push_back(String::FromUTF8(endpoint));
return blink::ContentSecurityPolicy::New(std::move(directives), return blink::ContentSecurityPolicy::New(std::move(directives), csp->type,
csp->use_reporting_api, csp->source, csp->use_reporting_api,
std::move(report_endpoints)); std::move(report_endpoints));
} }
...@@ -336,12 +336,26 @@ mojom::blink::FetchAPIResponsePtr FetchResponseData::PopulateFetchAPIResponse( ...@@ -336,12 +336,26 @@ mojom::blink::FetchAPIResponsePtr FetchResponseData::PopulateFetchAPIResponse(
content_security_policy_header)) { content_security_policy_header)) {
network::ContentSecurityPolicy policy; network::ContentSecurityPolicy policy;
if (policy.Parse(request_url, if (policy.Parse(request_url,
network::mojom::ContentSecurityPolicyType::kEnforce,
StringUTF8Adaptor(content_security_policy_header) StringUTF8Adaptor(content_security_policy_header)
.AsStringPiece())) { .AsStringPiece())) {
response->content_security_policy = response->content_security_policy =
ConvertToBlink(policy.TakeContentSecurityPolicy()); ConvertToBlink(policy.TakeContentSecurityPolicy());
} }
} }
if (HeaderList()->Get("content-security-policy-report-only",
content_security_policy_header)) {
network::ContentSecurityPolicy policy;
if (policy.Parse(request_url,
network::mojom::ContentSecurityPolicyType::kReport,
StringUTF8Adaptor(content_security_policy_header)
.AsStringPiece())) {
auto blink_policies =
ConvertToBlink(policy.TakeContentSecurityPolicy());
for (auto& policy : blink_policies)
response->content_security_policy.push_back(std::move(policy));
}
}
} }
return response; return response;
} }
......
...@@ -4,6 +4,8 @@ ...@@ -4,6 +4,8 @@
#include "third_party/blink/renderer/core/fetch/fetch_response_data.h" #include "third_party/blink/renderer/core/fetch/fetch_response_data.h"
#include "base/test/scoped_feature_list.h"
#include "services/network/public/cpp/features.h"
#include "testing/gtest/include/gtest/gtest.h" #include "testing/gtest/include/gtest/gtest.h"
#include "third_party/blink/public/mojom/fetch/fetch_api_response.mojom-blink.h" #include "third_party/blink/public/mojom/fetch/fetch_api_response.mojom-blink.h"
#include "third_party/blink/renderer/core/fetch/fetch_header_list.h" #include "third_party/blink/renderer/core/fetch/fetch_header_list.h"
...@@ -263,4 +265,23 @@ TEST_F(FetchResponseDataTest, DefaultResponseTime) { ...@@ -263,4 +265,23 @@ TEST_F(FetchResponseDataTest, DefaultResponseTime) {
EXPECT_FALSE(internal_response->ResponseTime().is_null()); EXPECT_FALSE(internal_response->ResponseTime().is_null());
} }
TEST_F(FetchResponseDataTest, ContentSecurityPolicy) {
base::test::ScopedFeatureList scoped_feature_list;
scoped_feature_list.InitAndEnableFeature(
network::features::kOutOfBlinkFrameAncestors);
FetchResponseData* internal_response = CreateInternalResponse();
internal_response->HeaderList()->Append("content-security-policy",
"frame-ancestors 'none'");
internal_response->HeaderList()->Append("content-security-policy-report-only",
"frame-ancestors 'none'");
mojom::blink::FetchAPIResponsePtr fetch_api_response =
internal_response->PopulateFetchAPIResponse(KURL());
auto& csp = fetch_api_response->content_security_policy;
EXPECT_EQ(csp.size(), 2U);
EXPECT_EQ(csp[0]->type, network::mojom::ContentSecurityPolicyType::kEnforce);
EXPECT_EQ(csp[1]->type, network::mojom::ContentSecurityPolicyType::kReport);
}
} // namespace blink } // namespace blink
<!DOCTYPE html>
<html>
<meta name="timeout" content="long">
<head>
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
<title>Blocked frames are reported correctly</title>
</head>
<body>
<iframe src="support/content-security-policy-report-only.sub.html?policy=report-uri%20../../support/report.py%3Fop=put%26reportID={{$id:uuid()}}%3B%20frame-ancestors%20'none'"></iframe>
<script async defer src="../support/checkReport.sub.js?reportField=violated-directive&reportValue=frame-ancestors%20'none'&reportID={{$id}}"></script>
</body>
</html>
<!DOCTYPE html>
<html>
<body>
<p>This is an IFrame sending a Content-Security-Policy-Report-Only header containing "{{GET[policy]}}".</p>
</body>
</html>
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