Commit bfc48732 authored by Lucas Furukawa Gadani's avatar Lucas Furukawa Gadani Committed by Commit Bot

CSP: Parse CSP coming from requests generated in the browser process.

This CL adds support for AncestorThrottle to parse CSP headers coming
from requests generated in the browser process, such as requests coming
from signed exchanges or AppCache.

Bug: 1042627
Change-Id: I7364cfc5b1c15181cce3e69cc254d7aea435124e
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2020405
Commit-Queue: Lucas Gadani <lfg@chromium.org>
Reviewed-by: default avatarKinuko Yasuda <kinuko@chromium.org>
Reviewed-by: default avatarArthur Sonzogni <arthursonzogni@chromium.org>
Cr-Commit-Position: refs/heads/master@{#741559}
parent 6d926eb9
......@@ -11,12 +11,15 @@
#include "content/browser/frame_host/frame_tree.h"
#include "content/browser/frame_host/frame_tree_node.h"
#include "content/browser/frame_host/navigation_request.h"
#include "content/browser/frame_host/navigator.h"
#include "content/browser/frame_host/render_frame_host_impl.h"
#include "content/browser/renderer_host/render_view_host_delegate.h"
#include "content/browser/renderer_host/render_view_host_impl.h"
#include "content/public/browser/browser_context.h"
#include "content/public/browser/browser_thread.h"
#include "content/public/browser/navigation_handle.h"
#include "content/public/browser/navigation_throttle.h"
#include "content/public/browser/storage_partition.h"
#include "net/http/http_response_headers.h"
#include "services/network/public/cpp/content_security_policy/csp_context.h"
#include "services/network/public/cpp/features.h"
......@@ -77,18 +80,24 @@ void RecordXFrameOptionsUsage(XFrameOptionsHistogram usage) {
XFrameOptionsHistogram::XFRAMEOPTIONS_HISTOGRAM_MAX);
}
bool HeadersContainFrameAncestorsCSP(const net::HttpResponseHeaders* headers) {
size_t iter = 0;
std::string value;
while (headers->EnumerateHeader(&iter, "content-security-policy", &value)) {
// A content-security-policy is a semicolon-separated list of directives.
for (const auto& directive : base::SplitStringPiece(
value, ";", base::TRIM_WHITESPACE, base::SPLIT_WANT_NONEMPTY)) {
// The trailing " " is intentional; we'd otherwise match
// "frame-ancestors-is-not-this-directive".
if (base::StartsWith(directive, "frame-ancestors ",
base::CompareCase::INSENSITIVE_ASCII))
return true;
bool HeadersContainFrameAncestorsCSP(const net::HttpResponseHeaders* headers,
bool include_report_only) {
std::vector<std::string> header_names = {"content-security-policy"};
if (include_report_only)
header_names.push_back("content-security-policy-report-only");
for (const auto& header : header_names) {
size_t iter = 0;
std::string value;
while (headers->EnumerateHeader(&iter, header, &value)) {
// A content-security-policy is a semicolon-separated list of directives.
for (const auto& directive : base::SplitStringPiece(
value, ";", base::TRIM_WHITESPACE, base::SPLIT_WANT_NONEMPTY)) {
// The trailing " " is intentional; we'd otherwise match
// "frame-ancestors-is-not-this-directive".
if (base::StartsWith(directive, "frame-ancestors ",
base::CompareCase::INSENSITIVE_ASCII))
return true;
}
}
}
return false;
......@@ -257,37 +266,37 @@ NavigationThrottle::ThrottleCheckResult AncestorThrottle::ProcessResponseImpl(
// Evaluate whether the navigation should be allowed or blocked based on
// existing content-security-policy on the response.
if (is_response_check && base::FeatureList::IsEnabled(
network::features::kOutOfBlinkFrameAncestors)) {
// TODO(lfg): If the initiating document is known and correspond to the
// navigating frame's current document, consider using:
// navigation_request().common_params().source_location here instead.
auto empty_source_location = network::mojom::SourceLocation::New();
// CSP frame-ancestors are checked against the URL of every parent and
// are reported to the navigating frame.
FrameAncestorCSPContext csp_context(
NavigationRequest::From(navigation_handle())->GetRenderFrameHost(),
mojo::Clone(request->response()->content_security_policy));
csp_context.SetSelf(url::Origin::Create(navigation_handle()->GetURL()));
// Check CSP frame-ancestors against every parent.
// We enforce frame-ancestors in the outer delegate for portals, but not
// for other uses of inner/outer WebContents (GuestViews).
RenderFrameHostImpl* parent =
ParentForAncestorThrottle(request->GetRenderFrameHost());
while (parent) {
if (!csp_context.IsAllowedByCsp(
network::mojom::CSPDirectiveName::FrameAncestors,
parent->GetLastCommittedOrigin().GetURL(),
navigation_handle()->WasServerRedirect(),
true /* is_response_check */, empty_source_location,
network::CSPContext::CheckCSPDisposition::CHECK_ALL_CSP,
navigation_handle()->IsFormSubmission())) {
return NavigationThrottle::BLOCK_RESPONSE;
}
parent = ParentForAncestorThrottle(parent);
if (is_response_check && request->GetResponseHeaders() &&
HeadersContainFrameAncestorsCSP(request->GetResponseHeaders(), true) &&
base::FeatureList::IsEnabled(
network::features::kOutOfBlinkFrameAncestors)) {
if (!request->response()->content_security_policy.empty()) {
return EvaluateContentSecurityPolicy(
mojo::Clone(request->response()->content_security_policy));
}
BrowserContext* browser_context = request->frame_tree_node()
->navigator()
->GetController()
->GetBrowserContext();
StoragePartition* partition = BrowserContext::GetStoragePartition(
browser_context, request->GetRenderFrameHost()->GetSiteInstance());
partition->GetNetworkContext()->ParseContentSecurityPolicy(
request->GetURL(), request->response()->headers,
base::BindOnce(
[](AncestorThrottle* ancestor_throttle, NavigationRequest* request,
std::vector<network::mojom::ContentSecurityPolicyPtr>
content_security_policy) {
auto result = ancestor_throttle->EvaluateContentSecurityPolicy(
mojo::Clone(content_security_policy));
if (result == NavigationThrottle::PROCEED) {
ancestor_throttle->Resume();
} else {
ancestor_throttle->CancelDeferredNavigation(result);
}
},
this, request));
return NavigationThrottle::DEFER;
}
return NavigationThrottle::PROCEED;
......@@ -349,6 +358,44 @@ void AncestorThrottle::ConsoleError(HeaderDisposition disposition) {
blink::mojom::ConsoleMessageLevel::kError, message);
}
NavigationThrottle::ThrottleAction
AncestorThrottle::EvaluateContentSecurityPolicy(
std::vector<network::mojom::ContentSecurityPolicyPtr>
content_security_policy) {
// TODO(lfg): If the initiating document is known and correspond to the
// navigating frame's current document, consider using:
// navigation_request().common_params().source_location here instead.
auto empty_source_location = network::mojom::SourceLocation::New();
// CSP frame-ancestors are checked against the URL of every parent and are
// reported to the navigating frame.
FrameAncestorCSPContext csp_context(
NavigationRequest::From(navigation_handle())->GetRenderFrameHost(),
std::move(content_security_policy));
csp_context.SetSelf(url::Origin::Create(navigation_handle()->GetURL()));
// Check CSP frame-ancestors against every parent.
// We enforce frame-ancestors in the outer delegate for portals, but not
// for other uses of inner/outer WebContents (GuestViews).
RenderFrameHostImpl* parent =
ParentForAncestorThrottle(static_cast<RenderFrameHostImpl*>(
navigation_handle()->GetRenderFrameHost()));
while (parent) {
if (!csp_context.IsAllowedByCsp(
network::mojom::CSPDirectiveName::FrameAncestors,
parent->GetLastCommittedOrigin().GetURL(),
navigation_handle()->WasServerRedirect(),
true /* is_response_check */, empty_source_location,
network::CSPContext::CheckCSPDisposition::CHECK_ALL_CSP,
navigation_handle()->IsFormSubmission())) {
return NavigationThrottle::BLOCK_RESPONSE;
}
parent = ParentForAncestorThrottle(parent);
}
return NavigationThrottle::PROCEED;
}
AncestorThrottle::HeaderDisposition AncestorThrottle::ParseHeader(
const net::HttpResponseHeaders* headers,
std::string* header_value) {
......@@ -393,7 +440,7 @@ AncestorThrottle::HeaderDisposition AncestorThrottle::ParseHeader(
// https://www.w3.org/TR/CSP/#frame-ancestors-and-frame-options
if (result != HeaderDisposition::NONE &&
result != HeaderDisposition::ALLOWALL &&
HeadersContainFrameAncestorsCSP(headers)) {
HeadersContainFrameAncestorsCSP(headers, false)) {
// TODO(mkwst): 'frame-ancestors' is currently handled in Blink. We should
// handle it here instead. Until then, don't block the request, and let
// Blink handle it. https://crbug.com/555418
......
......@@ -11,6 +11,7 @@
#include "base/gtest_prod_util.h"
#include "base/macros.h"
#include "content/public/browser/navigation_throttle.h"
#include "services/network/public/mojom/content_security_policy.mojom-forward.h"
namespace net {
class HttpResponseHeaders;
......@@ -56,6 +57,9 @@ class CONTENT_EXPORT AncestorThrottle : public NavigationThrottle {
bool is_response_check);
void ParseError(const std::string& value, HeaderDisposition disposition);
void ConsoleError(HeaderDisposition disposition);
NavigationThrottle::ThrottleAction EvaluateContentSecurityPolicy(
std::vector<network::mojom::ContentSecurityPolicyPtr>
content_security_policy);
// Parses an 'X-Frame-Options' header. If the result is either CONFLICT
// or INVALID, |header_value| will be populated with the value which caused
......
......@@ -4,11 +4,16 @@
#include "base/strings/string_util.h"
#include "base/test/scoped_feature_list.h"
#include "content/browser/frame_host/frame_tree_node.h"
#include "content/browser/web_contents/web_contents_impl.h"
#include "content/public/test/browser_test_utils.h"
#include "content/public/test/content_browser_test.h"
#include "content/public/test/content_browser_test_utils.h"
#include "content/public/test/content_mock_cert_verifier.h"
#include "content/public/test/signed_exchange_browser_test_helper.h"
#include "content/public/test/test_navigation_observer.h"
#include "content/shell/browser/shell.h"
#include "content/test/content_browser_test_utils_internal.h"
#include "net/base/escape.h"
#include "net/dns/mock_host_resolver.h"
#include "net/test/embedded_test_server/embedded_test_server.h"
......@@ -25,18 +30,14 @@ namespace {
class AncestorThrottleTest : public ContentBrowserTest,
public ::testing::WithParamInterface<bool> {
public:
AncestorThrottleTest() {}
protected:
void SetUpCommandLine(base::CommandLine* command_line) override {
ContentBrowserTest::SetUpCommandLine(command_line);
AncestorThrottleTest() {
if (GetParam()) {
feature_list_.InitAndEnableFeature(
network::features::kOutOfBlinkFrameAncestors);
}
}
protected:
void SetUpOnMainThread() override {
host_resolver()->AddRule("*", "127.0.0.1");
ASSERT_TRUE(embedded_test_server()->Start());
......@@ -133,7 +134,61 @@ IN_PROC_BROWSER_TEST_P(AncestorThrottleTest, Response204CSP) {
// Not crashing means that the test succeeded.
}
class AncestorThrottleSXGTest : public AncestorThrottleTest {
public:
AncestorThrottleSXGTest() { net::EmbeddedTestServer::RegisterTestCerts(); }
protected:
void SetUpCommandLine(base::CommandLine* command_line) override {
AncestorThrottleTest::SetUpCommandLine(command_line);
mock_cert_verifier_.SetUpCommandLine(command_line);
}
void SetUp() override {
sxg_test_helper_.SetUp();
AncestorThrottleTest::SetUp();
}
void SetUpInProcessBrowserTestFixture() override {
AncestorThrottleTest::SetUpInProcessBrowserTestFixture();
mock_cert_verifier_.SetUpInProcessBrowserTestFixture();
}
void TearDownOnMainThread() override {
sxg_test_helper_.TearDownOnMainThread();
AncestorThrottleTest::TearDownOnMainThread();
}
void TearDownInProcessBrowserTestFixture() override {
mock_cert_verifier_.TearDownInProcessBrowserTestFixture();
AncestorThrottleTest::TearDownInProcessBrowserTestFixture();
}
protected:
ContentMockCertVerifier mock_cert_verifier_;
SignedExchangeBrowserTestHelper sxg_test_helper_;
};
IN_PROC_BROWSER_TEST_P(AncestorThrottleSXGTest, SXGWithCSP) {
sxg_test_helper_.InstallMockCert(mock_cert_verifier_.mock_cert_verifier());
sxg_test_helper_.InstallMockCertChainInterceptor();
WebContentsImpl* web_contents =
static_cast<WebContentsImpl*>(shell()->web_contents());
EXPECT_TRUE(NavigateToURL(
web_contents, embedded_test_server()->GetURL("/page_with_iframe.html")));
GURL url = embedded_test_server()->GetURL("/sxg/test.example.org_csp.sxg");
FrameTreeNode* iframe_node =
web_contents->GetFrameTree()->root()->child_at(0);
NavigateFrameToURL(iframe_node, url);
EXPECT_TRUE(
iframe_node->current_frame_host()->GetLastCommittedOrigin().opaque());
}
INSTANTIATE_TEST_SUITE_P(All, AncestorThrottleTest, ::testing::Bool());
INSTANTIATE_TEST_SUITE_P(All, AncestorThrottleSXGTest, ::testing::Bool());
} // namespace
......
......@@ -3168,6 +3168,7 @@ void NavigationRequest::CancelDeferredNavigationInternal(
DCHECK(processing_navigation_throttle_);
DCHECK(result.action() == NavigationThrottle::CANCEL_AND_IGNORE ||
result.action() == NavigationThrottle::CANCEL ||
result.action() == NavigationThrottle::BLOCK_RESPONSE ||
result.action() == NavigationThrottle::BLOCK_REQUEST_AND_COLLAPSE);
DCHECK(result.action() != NavigationThrottle::BLOCK_REQUEST_AND_COLLAPSE ||
state_ == WILL_START_REQUEST || state_ == WILL_REDIRECT_REQUEST);
......
......@@ -222,6 +222,22 @@ gen-signedexchange \
-o test.example.org_fr_variant.sxg \
-miRecordSize 100
# Generate the signed exchange with CSP.
gen-signedexchange \
-version 1b3 \
-uri https://test.example.org/test/ \
-status 200 \
-content test.html \
-certificate prime256v1-sha256.public.pem \
-certUrl https://cert.example.org/cert.msg \
-validityUrl https://test.example.org/resource.validity.msg \
-privateKey prime256v1.key \
-date $signature_date \
-expire 168h \
-responseHeader "content-security-policy: frame-ancestors 'none'" \
-o test.example.org_csp.sxg \
-miRecordSize 100
echo "Update the test signatures in "
echo "signed_exchange_signature_verifier_unittest.cc with the followings:"
echo "===="
......
HTTP/1.1 200 OK
Content-Type: application/signed-exchange;v=b3
X-Content-Type-Options: nosniff
......@@ -81,6 +81,7 @@
#include "services/network/proxy_config_service_mojo.h"
#include "services/network/proxy_lookup_request.h"
#include "services/network/proxy_resolving_socket_factory_mojo.h"
#include "services/network/public/cpp/content_security_policy/content_security_policy.h"
#include "services/network/public/cpp/features.h"
#include "services/network/public/cpp/network_switches.h"
#include "services/network/quic_transport.h"
......@@ -1244,6 +1245,15 @@ void NetworkContext::VerifyCertForSignedExchange(
OnCertVerifyForSignedExchangeComplete(cert_verify_id, result);
}
void NetworkContext::ParseContentSecurityPolicy(
const GURL& base_url,
const scoped_refptr<net::HttpResponseHeaders>& headers,
ParseContentSecurityPolicyCallback callback) {
std::vector<mojom::ContentSecurityPolicyPtr> policy;
AddContentSecurityPolicyFromHeaders(*headers, base_url, &policy);
std::move(callback).Run(std::move(policy));
}
void NetworkContext::NotifyExternalCacheHit(
const GURL& url,
const std::string& http_method,
......
......@@ -316,6 +316,10 @@ class COMPONENT_EXPORT(NETWORK_SERVICE) NetworkContext
const std::string& ocsp_result,
const std::string& sct_list,
VerifyCertForSignedExchangeCallback callback) override;
void ParseContentSecurityPolicy(
const GURL& base_url,
const scoped_refptr<net::HttpResponseHeaders>& headers,
ParseContentSecurityPolicyCallback callback) override;
void AddHSTS(const std::string& host,
base::Time expiry,
bool include_subdomains,
......
......@@ -11,6 +11,7 @@ import "mojo/public/mojom/base/time.mojom";
import "mojo/public/mojom/base/unguessable_token.mojom";
import "mojo/public/mojom/base/values.mojom";
import "services/network/public/mojom/address_list.mojom";
import "services/network/public/mojom/content_security_policy.mojom";
import "services/network/public/mojom/cookie_manager.mojom";
import "services/network/public/mojom/default_credentials.mojom";
import "services/network/public/mojom/cors_origin_pattern.mojom";
......@@ -1226,6 +1227,12 @@ interface NetworkContext {
CertVerifyResult cv_result,
CTVerifyResult ct_result);
// Parses a Content Security Policy originating from the browser. This is
// used by Signed Exchanges and Appcache requests, where the browser
// parses the header contents.
ParseContentSecurityPolicy(url.mojom.Url url, HttpResponseHeaders headers)
=> (array<ContentSecurityPolicy> content_security_policy);
// Adds explicitly-specified data as if it was processed from an
// HSTS header. Used by tests and implementation of chrome://net-internals.
AddHSTS(string host, mojo_base.mojom.Time expiry,
......
......@@ -189,6 +189,10 @@ class TestNetworkContext : public mojom::NetworkContext {
const std::string& ocsp_result,
const std::string& sct_list,
VerifyCertForSignedExchangeCallback callback) override {}
void ParseContentSecurityPolicy(
const GURL& base_url,
const scoped_refptr<net::HttpResponseHeaders>& headers,
ParseContentSecurityPolicyCallback callback) override {}
void IsHSTSActiveForHost(const std::string& host,
IsHSTSActiveForHostCallback callback) override {}
void SetCorsOriginAccessListsForOrigin(
......
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