blob: 24bb90a7d5792ab484f63a1ab3d3c011c962793b [file] [log] [blame]
// Copyright 2020 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "third_party/blink/renderer/core/execution_context/security_context_init.h"
#include "base/metrics/histogram_macros.h"
#include "services/network/public/cpp/web_sandbox_flags.h"
#include "third_party/blink/public/mojom/devtools/console_message.mojom-blink.h"
#include "third_party/blink/renderer/core/dom/element.h"
#include "third_party/blink/renderer/core/execution_context/agent.h"
#include "third_party/blink/renderer/core/feature_policy/document_policy_parser.h"
#include "third_party/blink/renderer/core/feature_policy/feature_policy_parser.h"
#include "third_party/blink/renderer/core/frame/csp/content_security_policy.h"
#include "third_party/blink/renderer/core/frame/local_dom_window.h"
#include "third_party/blink/renderer/core/frame/local_frame.h"
#include "third_party/blink/renderer/core/frame/local_frame_client.h"
#include "third_party/blink/renderer/core/frame/settings.h"
#include "third_party/blink/renderer/core/html/imports/html_imports_controller.h"
#include "third_party/blink/renderer/core/inspector/console_message.h"
#include "third_party/blink/renderer/core/loader/document_loader.h"
#include "third_party/blink/renderer/core/origin_trials/origin_trial_context.h"
#include "third_party/blink/renderer/core/page/chrome_client.h"
#include "third_party/blink/renderer/core/page/page.h"
#include "third_party/blink/renderer/platform/heap/heap.h"
namespace blink {
namespace {
// Helper function to filter out features that are not in origin trial in
// ParsedDocumentPolicy.
DocumentPolicy::ParsedDocumentPolicy FilterByOriginTrial(
const DocumentPolicy::ParsedDocumentPolicy& parsed_policy,
ExecutionContext* context) {
DocumentPolicy::ParsedDocumentPolicy filtered_policy;
for (auto i = parsed_policy.feature_state.begin(),
last = parsed_policy.feature_state.end();
i != last;) {
if (!DisabledByOriginTrial(i->first, context))
filtered_policy.feature_state.insert(*i);
++i;
}
for (auto i = parsed_policy.endpoint_map.begin(),
last = parsed_policy.endpoint_map.end();
i != last;) {
if (!DisabledByOriginTrial(i->first, context))
filtered_policy.endpoint_map.insert(*i);
++i;
}
return filtered_policy;
}
// Helper function: Merge the feature policy strings from HTTP headers and the
// origin policy (if any).
// Headers go first, which means that the per-page headers override the
// origin policy features.
//
// TODO(domenic): we want to treat origin policy feature policy as a single
// feature policy, not a header serialization, so it should be processed
// differently.
void MergeFeaturesFromOriginPolicy(WTF::StringBuilder& feature_policy,
const WebOriginPolicy& origin_policy) {
if (!origin_policy.feature_policy.IsNull()) {
if (!feature_policy.IsEmpty()) {
feature_policy.Append(',');
}
feature_policy.Append(origin_policy.feature_policy);
}
}
} // namespace
// A helper class that allows the security context be initialized in the
// process of constructing the document.
SecurityContextInit::SecurityContextInit(ExecutionContext* context)
: execution_context_(context) {}
void SecurityContextInit::ApplyDocumentPolicy(
DocumentPolicy::ParsedDocumentPolicy& document_policy,
const String& report_only_document_policy_header) {
if (!RuntimeEnabledFeatures::DocumentPolicyEnabled())
return;
// Because Document-Policy http header is parsed in DocumentLoader,
// when origin trial context is not initialized yet.
// Needs to filter out features that are not in origin trial after
// we have origin trial information available.
document_policy = FilterByOriginTrial(document_policy, execution_context_);
if (!document_policy.feature_state.empty()) {
UseCounter::Count(execution_context_, WebFeature::kDocumentPolicyHeader);
for (const auto& policy_entry : document_policy.feature_state) {
UMA_HISTOGRAM_ENUMERATION("Blink.UseCounter.DocumentPolicy.Header",
policy_entry.first);
}
}
execution_context_->GetSecurityContext().SetDocumentPolicy(
DocumentPolicy::CreateWithHeaderPolicy(document_policy));
// Handle Report-Only-Document-Policy HTTP header.
// Console messages generated from logger are discarded, because currently
// there is no way to output them to console.
// Calling |Document::AddConsoleMessage| in
// |SecurityContextInit::ApplyPendingDataToDocument| will have no effect,
// because when the function is called, the document is not fully initialized
// yet (|document_| field in current frame is not yet initialized yet).
DocumentPolicy::ParsedDocumentPolicy report_only_document_policy;
PolicyParserMessageBuffer logger("%s", /* discard_message */ true);
base::Optional<DocumentPolicy::ParsedDocumentPolicy>
report_only_parsed_policy = DocumentPolicyParser::Parse(
report_only_document_policy_header, logger);
if (report_only_parsed_policy) {
report_only_document_policy =
FilterByOriginTrial(*report_only_parsed_policy, execution_context_);
if (!report_only_document_policy.feature_state.empty()) {
UseCounter::Count(execution_context_,
WebFeature::kDocumentPolicyReportOnlyHeader);
execution_context_->GetSecurityContext().SetReportOnlyDocumentPolicy(
DocumentPolicy::CreateWithHeaderPolicy(report_only_document_policy));
}
}
}
void SecurityContextInit::ApplyFeaturePolicy(
LocalFrame* frame,
const ResourceResponse& response,
const base::Optional<WebOriginPolicy>& origin_policy,
const FramePolicy& frame_policy) {
// If we are a HTMLViewSourceDocument we use container, header or
// inherited policies. https://crbug.com/898688.
if (frame->InViewSourceMode()) {
execution_context_->GetSecurityContext().SetFeaturePolicy(
FeaturePolicy::CreateFromParentPolicy(
nullptr, {},
execution_context_->GetSecurityOrigin()->ToUrlOrigin()));
return;
}
const String& permissions_policy_header =
RuntimeEnabledFeatures::PermissionsPolicyHeaderEnabled()
? response.HttpHeaderField(http_names::kPermissionsPolicy)
: g_empty_string;
const String& report_only_permissions_policy_header =
RuntimeEnabledFeatures::PermissionsPolicyHeaderEnabled()
? response.HttpHeaderField(http_names::kPermissionsPolicyReportOnly)
: g_empty_string;
PolicyParserMessageBuffer feature_policy_logger(
"Error with Feature-Policy header: ");
PolicyParserMessageBuffer report_only_feature_policy_logger(
"Error with Feature-Policy-Report-Only header: ");
PolicyParserMessageBuffer permissions_policy_logger(
"Error with Permissions-Policy header: ");
PolicyParserMessageBuffer report_only_permissions_policy_logger(
"Error with Permissions-Policy-Report-Only header: ");
WTF::StringBuilder policy_builder;
policy_builder.Append(response.HttpHeaderField(http_names::kFeaturePolicy));
if (origin_policy.has_value())
MergeFeaturesFromOriginPolicy(policy_builder, origin_policy.value());
String feature_policy_header = policy_builder.ToString();
if (!feature_policy_header.IsEmpty())
UseCounter::Count(execution_context_, WebFeature::kFeaturePolicyHeader);
feature_policy_header_ = FeaturePolicyParser::ParseHeader(
feature_policy_header, permissions_policy_header,
execution_context_->GetSecurityOrigin(), feature_policy_logger,
permissions_policy_logger, execution_context_);
ParsedFeaturePolicy report_only_feature_policy_header =
FeaturePolicyParser::ParseHeader(
response.HttpHeaderField(http_names::kFeaturePolicyReportOnly),
report_only_permissions_policy_header,
execution_context_->GetSecurityOrigin(),
report_only_feature_policy_logger,
report_only_permissions_policy_logger, execution_context_);
if (!report_only_feature_policy_header.empty()) {
UseCounter::Count(execution_context_,
WebFeature::kFeaturePolicyReportOnlyHeader);
}
auto messages = Vector<PolicyParserMessageBuffer::Message>();
messages.AppendVector(feature_policy_logger.GetMessages());
messages.AppendVector(report_only_feature_policy_logger.GetMessages());
messages.AppendVector(permissions_policy_logger.GetMessages());
messages.AppendVector(report_only_permissions_policy_logger.GetMessages());
for (const auto& message : messages) {
execution_context_->AddConsoleMessage(MakeGarbageCollected<ConsoleMessage>(
mojom::blink::ConsoleMessageSource::kSecurity, message.level,
message.content));
}
ParsedFeaturePolicy container_policy;
if (frame && frame->Owner())
container_policy = frame_policy.container_policy;
// DocumentLoader applied the sandbox flags before calling this function, so
// they are accessible here.
auto sandbox_flags = execution_context_->GetSandboxFlags();
if (RuntimeEnabledFeatures::BlockingFocusWithoutUserActivationEnabled() &&
frame && frame->Tree().Parent() &&
(sandbox_flags & network::mojom::blink::WebSandboxFlags::kNavigation) !=
network::mojom::blink::WebSandboxFlags::kNone) {
// Enforcing the policy for sandbox frames (for context see
// https://crbug.com/954349).
DisallowFeatureIfNotPresent(
mojom::blink::FeaturePolicyFeature::kFocusWithoutUserActivation,
container_policy);
}
std::unique_ptr<FeaturePolicy> feature_policy;
auto* parent_feature_policy =
frame->Tree().Parent()
? frame->Tree().Parent()->GetSecurityContext()->GetFeaturePolicy()
: nullptr;
feature_policy = FeaturePolicy::CreateFromParentPolicy(
parent_feature_policy, container_policy,
execution_context_->GetSecurityOrigin()->ToUrlOrigin());
feature_policy->SetHeaderPolicy(feature_policy_header_);
execution_context_->GetSecurityContext().SetFeaturePolicy(
std::move(feature_policy));
// Report-only feature policy only takes effect when it is stricter than
// enforced feature policy, i.e. when enforced feature policy allows a feature
// while report-only feature policy do not. In such scenario, a report-only
// policy violation report will be generated, but the feature is still allowed
// to be used. Since child frames cannot loosen enforced feature policy, there
// is no need to inherit parent policy and container policy for report-only
// feature policy. For inherited policies, the behavior is dominated by
// enforced feature policy.
if (!report_only_feature_policy_header.empty()) {
std::unique_ptr<FeaturePolicy> report_only_policy =
FeaturePolicy::CreateFromParentPolicy(
nullptr /* parent_policy */, {} /* container_policy */,
execution_context_->GetSecurityOrigin()->ToUrlOrigin());
report_only_policy->SetHeaderPolicy(report_only_feature_policy_header);
execution_context_->GetSecurityContext().SetReportOnlyFeaturePolicy(
std::move(report_only_policy));
}
}
void SecurityContextInit::InitFeaturePolicyFrom(const SecurityContext& other) {
auto& security_context = execution_context_->GetSecurityContext();
security_context.SetFeaturePolicy(
FeaturePolicy::CopyStateFrom(other.GetFeaturePolicy()));
security_context.SetReportOnlyFeaturePolicy(
FeaturePolicy::CopyStateFrom(other.GetReportOnlyFeaturePolicy()));
}
void SecurityContextInit::InitDocumentPolicyFrom(const SecurityContext& other) {
auto& security_context = execution_context_->GetSecurityContext();
security_context.SetDocumentPolicy(
DocumentPolicy::CopyStateFrom(other.GetDocumentPolicy()));
security_context.SetReportOnlyDocumentPolicy(
DocumentPolicy::CopyStateFrom(other.GetReportOnlyDocumentPolicy()));
}
} // namespace blink