| // 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 |