1 // Copyright 2020 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4 
5 #include "third_party/blink/renderer/core/execution_context/security_context_init.h"
6 
7 #include "base/metrics/histogram_macros.h"
8 #include "services/network/public/cpp/web_sandbox_flags.h"
9 #include "third_party/blink/public/mojom/devtools/console_message.mojom-blink.h"
10 #include "third_party/blink/renderer/core/dom/element.h"
11 #include "third_party/blink/renderer/core/execution_context/agent.h"
12 #include "third_party/blink/renderer/core/feature_policy/document_policy_parser.h"
13 #include "third_party/blink/renderer/core/feature_policy/feature_policy_parser.h"
14 #include "third_party/blink/renderer/core/frame/csp/content_security_policy.h"
15 #include "third_party/blink/renderer/core/frame/local_dom_window.h"
16 #include "third_party/blink/renderer/core/frame/local_frame.h"
17 #include "third_party/blink/renderer/core/frame/local_frame_client.h"
18 #include "third_party/blink/renderer/core/frame/sandbox_flags.h"
19 #include "third_party/blink/renderer/core/frame/settings.h"
20 #include "third_party/blink/renderer/core/html/imports/html_imports_controller.h"
21 #include "third_party/blink/renderer/core/inspector/console_message.h"
22 #include "third_party/blink/renderer/core/loader/document_loader.h"
23 #include "third_party/blink/renderer/core/origin_trials/origin_trial_context.h"
24 #include "third_party/blink/renderer/core/page/chrome_client.h"
25 #include "third_party/blink/renderer/core/page/page.h"
26 #include "third_party/blink/renderer/platform/heap/heap.h"
27 
28 namespace blink {
29 namespace {
30 
31 // Helper function to filter out features that are not in origin trial in
32 // ParsedDocumentPolicy.
FilterByOriginTrial(const DocumentPolicy::ParsedDocumentPolicy & parsed_policy,ExecutionContext * context)33 DocumentPolicy::ParsedDocumentPolicy FilterByOriginTrial(
34     const DocumentPolicy::ParsedDocumentPolicy& parsed_policy,
35     ExecutionContext* context) {
36   DocumentPolicy::ParsedDocumentPolicy filtered_policy;
37   for (auto i = parsed_policy.feature_state.begin(),
38             last = parsed_policy.feature_state.end();
39        i != last;) {
40     if (!DisabledByOriginTrial(i->first, context))
41       filtered_policy.feature_state.insert(*i);
42     ++i;
43   }
44   for (auto i = parsed_policy.endpoint_map.begin(),
45             last = parsed_policy.endpoint_map.end();
46        i != last;) {
47     if (!DisabledByOriginTrial(i->first, context))
48       filtered_policy.endpoint_map.insert(*i);
49     ++i;
50   }
51   return filtered_policy;
52 }
53 
54 // Helper function: Merge the feature policy strings from HTTP headers and the
55 // origin policy (if any).
56 // Headers go first, which means that the per-page headers override the
57 // origin policy features.
58 //
59 // TODO(domenic): we want to treat origin policy feature policy as a single
60 // feature policy, not a header serialization, so it should be processed
61 // differently.
MergeFeaturesFromOriginPolicy(WTF::StringBuilder & feature_policy,const WebOriginPolicy & origin_policy)62 void MergeFeaturesFromOriginPolicy(WTF::StringBuilder& feature_policy,
63                                    const WebOriginPolicy& origin_policy) {
64   if (!origin_policy.feature_policy.IsNull()) {
65     if (!feature_policy.IsEmpty()) {
66       feature_policy.Append(',');
67     }
68     feature_policy.Append(origin_policy.feature_policy);
69   }
70 }
71 
72 }  // namespace
73 
74 // A helper class that allows the security context be initialized in the
75 // process of constructing the document.
SecurityContextInit(ExecutionContext * context)76 SecurityContextInit::SecurityContextInit(ExecutionContext* context)
77     : execution_context_(context) {}
78 
ApplyDocumentPolicy(DocumentPolicy::ParsedDocumentPolicy & document_policy,const String & report_only_document_policy_header)79 void SecurityContextInit::ApplyDocumentPolicy(
80     DocumentPolicy::ParsedDocumentPolicy& document_policy,
81     const String& report_only_document_policy_header) {
82   if (!RuntimeEnabledFeatures::DocumentPolicyEnabled(execution_context_))
83     return;
84 
85   // Because Document-Policy http header is parsed in DocumentLoader,
86   // when origin trial context is not initialized yet.
87   // Needs to filter out features that are not in origin trial after
88   // we have origin trial information available.
89   document_policy = FilterByOriginTrial(document_policy, execution_context_);
90   if (!document_policy.feature_state.empty()) {
91     UseCounter::Count(execution_context_, WebFeature::kDocumentPolicyHeader);
92     for (const auto& policy_entry : document_policy.feature_state) {
93       UMA_HISTOGRAM_ENUMERATION("Blink.UseCounter.DocumentPolicy.Header",
94                                 policy_entry.first);
95     }
96   }
97   execution_context_->GetSecurityContext().SetDocumentPolicy(
98       DocumentPolicy::CreateWithHeaderPolicy(document_policy));
99 
100   // Handle Report-Only-Document-Policy HTTP header.
101   // Console messages generated from logger are discarded, because currently
102   // there is no way to output them to console.
103   // Calling |Document::AddConsoleMessage| in
104   // |SecurityContextInit::ApplyPendingDataToDocument| will have no effect,
105   // because when the function is called, the document is not fully initialized
106   // yet (|document_| field in current frame is not yet initialized yet).
107   DocumentPolicy::ParsedDocumentPolicy report_only_document_policy;
108   PolicyParserMessageBuffer logger("%s", /* discard_message */ true);
109   base::Optional<DocumentPolicy::ParsedDocumentPolicy>
110       report_only_parsed_policy = DocumentPolicyParser::Parse(
111           report_only_document_policy_header, logger);
112   if (report_only_parsed_policy) {
113     report_only_document_policy =
114         FilterByOriginTrial(*report_only_parsed_policy, execution_context_);
115     if (!report_only_document_policy.feature_state.empty()) {
116       UseCounter::Count(execution_context_,
117                         WebFeature::kDocumentPolicyReportOnlyHeader);
118       execution_context_->GetSecurityContext().SetReportOnlyDocumentPolicy(
119           DocumentPolicy::CreateWithHeaderPolicy(report_only_document_policy));
120     }
121   }
122 }
123 
ApplyFeaturePolicy(LocalFrame * frame,const ResourceResponse & response,const base::Optional<WebOriginPolicy> & origin_policy,const FramePolicy & frame_policy)124 void SecurityContextInit::ApplyFeaturePolicy(
125     LocalFrame* frame,
126     const ResourceResponse& response,
127     const base::Optional<WebOriginPolicy>& origin_policy,
128     const FramePolicy& frame_policy) {
129   // If we are a HTMLViewSourceDocument we use container, header or
130   // inherited policies. https://crbug.com/898688.
131   if (frame->InViewSourceMode()) {
132     execution_context_->GetSecurityContext().SetFeaturePolicy(
133         FeaturePolicy::CreateFromParentPolicy(
134             nullptr, {},
135             execution_context_->GetSecurityOrigin()->ToUrlOrigin()));
136     return;
137   }
138 
139   const String& permissions_policy_header =
140       RuntimeEnabledFeatures::PermissionsPolicyHeaderEnabled()
141           ? response.HttpHeaderField(http_names::kPermissionsPolicy)
142           : g_empty_string;
143   const String& report_only_permissions_policy_header =
144       RuntimeEnabledFeatures::PermissionsPolicyHeaderEnabled()
145           ? response.HttpHeaderField(http_names::kPermissionsPolicyReportOnly)
146           : g_empty_string;
147 
148   PolicyParserMessageBuffer feature_policy_logger(
149       "Error with Feature-Policy header: ");
150   PolicyParserMessageBuffer report_only_feature_policy_logger(
151       "Error with Feature-Policy-Report-Only header: ");
152 
153   PolicyParserMessageBuffer permissions_policy_logger(
154       "Error with Permissions-Policy header: ");
155   PolicyParserMessageBuffer report_only_permissions_policy_logger(
156       "Error with Permissions-Policy-Report-Only header: ");
157 
158   WTF::StringBuilder policy_builder;
159   policy_builder.Append(response.HttpHeaderField(http_names::kFeaturePolicy));
160   if (origin_policy.has_value())
161     MergeFeaturesFromOriginPolicy(policy_builder, origin_policy.value());
162   String feature_policy_header = policy_builder.ToString();
163   if (!feature_policy_header.IsEmpty())
164     UseCounter::Count(execution_context_, WebFeature::kFeaturePolicyHeader);
165 
166   feature_policy_header_ = FeaturePolicyParser::ParseHeader(
167       feature_policy_header, permissions_policy_header,
168       execution_context_->GetSecurityOrigin(), feature_policy_logger,
169       permissions_policy_logger, execution_context_);
170 
171   ParsedFeaturePolicy report_only_feature_policy_header =
172       FeaturePolicyParser::ParseHeader(
173           response.HttpHeaderField(http_names::kFeaturePolicyReportOnly),
174           report_only_permissions_policy_header,
175           execution_context_->GetSecurityOrigin(),
176           report_only_feature_policy_logger,
177           report_only_permissions_policy_logger, execution_context_);
178 
179   if (!report_only_feature_policy_header.empty()) {
180     UseCounter::Count(execution_context_,
181                       WebFeature::kFeaturePolicyReportOnlyHeader);
182   }
183 
184   auto messages = Vector<PolicyParserMessageBuffer::Message>();
185   messages.AppendVector(feature_policy_logger.GetMessages());
186   messages.AppendVector(report_only_feature_policy_logger.GetMessages());
187   messages.AppendVector(permissions_policy_logger.GetMessages());
188   messages.AppendVector(report_only_permissions_policy_logger.GetMessages());
189 
190   for (const auto& message : messages) {
191     execution_context_->AddConsoleMessage(MakeGarbageCollected<ConsoleMessage>(
192         mojom::blink::ConsoleMessageSource::kSecurity, message.level,
193         message.content));
194   }
195 
196   // DocumentLoader applied the sandbox flags before calling this function, so
197   // they are accessible here.
198   auto sandbox_flags = execution_context_->GetSandboxFlags();
199   if (sandbox_flags != network::mojom::blink::WebSandboxFlags::kNone &&
200       RuntimeEnabledFeatures::FeaturePolicyForSandboxEnabled()) {
201     // The sandbox flags might have come from CSP header or the browser; in
202     // such cases the sandbox is not part of the container policy. They are
203     // added to the header policy (which specifically makes sense in the case
204     // of CSP sandbox).
205     ApplySandboxFlagsToParsedFeaturePolicy(sandbox_flags,
206                                            feature_policy_header_);
207   }
208 
209   ParsedFeaturePolicy container_policy;
210   if (frame && frame->Owner())
211     container_policy = frame_policy.container_policy;
212 
213   // TODO(icelland): This is problematic querying sandbox flags before
214   // feature policy is initialized.
215   if (RuntimeEnabledFeatures::BlockingFocusWithoutUserActivationEnabled() &&
216       frame && frame->Tree().Parent() &&
217       (sandbox_flags & network::mojom::blink::WebSandboxFlags::kNavigation) !=
218           network::mojom::blink::WebSandboxFlags::kNone) {
219     // Enforcing the policy for sandbox frames (for context see
220     // https://crbug.com/954349).
221     DisallowFeatureIfNotPresent(
222         mojom::blink::FeaturePolicyFeature::kFocusWithoutUserActivation,
223         container_policy);
224   }
225 
226   // Feature policy should either come from a parent in the case of an
227   // embedded child frame, or from an opener if any when a new window is
228   // created by an opener. A main frame without an opener would not have a
229   // parent policy nor an opener feature state.
230   // For a main frame, get inherited feature policy from the opener if any.
231   std::unique_ptr<FeaturePolicy> feature_policy;
232   if (!frame->IsMainFrame() || frame->OpenerFeatureState().empty() ||
233       !RuntimeEnabledFeatures::FeaturePolicyForSandboxEnabled()) {
234     auto* parent_feature_policy =
235         frame->Tree().Parent()
236             ? frame->Tree().Parent()->GetSecurityContext()->GetFeaturePolicy()
237             : nullptr;
238     feature_policy = FeaturePolicy::CreateFromParentPolicy(
239         parent_feature_policy, container_policy,
240         execution_context_->GetSecurityOrigin()->ToUrlOrigin());
241   } else {
242     feature_policy = FeaturePolicy::CreateWithOpenerPolicy(
243         frame->OpenerFeatureState(),
244         execution_context_->GetSecurityOrigin()->ToUrlOrigin());
245   }
246   feature_policy->SetHeaderPolicy(feature_policy_header_);
247   execution_context_->GetSecurityContext().SetFeaturePolicy(
248       std::move(feature_policy));
249 
250   // Report-only feature policy only takes effect when it is stricter than
251   // enforced feature policy, i.e. when enforced feature policy allows a feature
252   // while report-only feature policy do not. In such scenario, a report-only
253   // policy violation report will be generated, but the feature is still allowed
254   // to be used. Since child frames cannot loosen enforced feature policy, there
255   // is no need to inherit parent policy and container policy for report-only
256   // feature policy. For inherited policies, the behavior is dominated by
257   // enforced feature policy.
258   if (!report_only_feature_policy_header.empty()) {
259     std::unique_ptr<FeaturePolicy> report_only_policy =
260         FeaturePolicy::CreateFromParentPolicy(
261             nullptr /* parent_policy */, {} /* container_policy */,
262             execution_context_->GetSecurityOrigin()->ToUrlOrigin());
263     report_only_policy->SetHeaderPolicy(report_only_feature_policy_header);
264     execution_context_->GetSecurityContext().SetReportOnlyFeaturePolicy(
265         std::move(report_only_policy));
266   }
267 }
268 
269 }  // namespace blink
270