summaryrefslogtreecommitdiff
path: root/chromium/third_party/blink/renderer/core/execution_context/security_context_init.cc
blob: dacd09c383427b0e25c3be843350896f676da1b1 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
// 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/document_init.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/sandbox_flags.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,
    SecurityContextInit* init) {
  DocumentPolicy::ParsedDocumentPolicy filtered_policy;
  for (auto i = parsed_policy.feature_state.begin(),
            last = parsed_policy.feature_state.end();
       i != last;) {
    if (!DisabledByOriginTrial(i->first, init))
      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, init))
      filtered_policy.endpoint_map.insert(*i);
    ++i;
  }
  return filtered_policy;
}

}  // namespace

// This is the constructor used by RemoteSecurityContext
SecurityContextInit::SecurityContextInit()
    : SecurityContextInit(nullptr, nullptr) {}

// This constructor is used for non-Document contexts (i.e., workers and tests).
// This does a simpler check than Documents to set secure_context_mode_. This
// is only sufficient until there are APIs that are available in workers or
// worklets that require a privileged context test that checks ancestors.
SecurityContextInit::SecurityContextInit(scoped_refptr<SecurityOrigin> origin,
                                         OriginTrialContext* origin_trials)
    : security_origin_(std::move(origin)),
      origin_trials_(origin_trials),
      secure_context_mode_(security_origin_ &&
                                   security_origin_->IsPotentiallyTrustworthy()
                               ? SecureContextMode::kSecureContext
                               : SecureContextMode::kInsecureContext) {}

// A helper class that allows the security context be initialized in the
// process of constructing the document.
SecurityContextInit::SecurityContextInit(const DocumentInit& initializer)
    : execution_context_(initializer.GetExecutionContext()),
      csp_(initializer.GetContentSecurityPolicy()),
      sandbox_flags_(initializer.GetSandboxFlags()),
      security_origin_(initializer.GetDocumentOrigin()) {
  // Derive possibly a new security origin that contains the agent cluster id.
  if (execution_context_) {
    security_origin_ = security_origin_->GetOriginForAgentCluster(
        execution_context_->GetAgent()->cluster_id());
  }

  // The secure context state is based on the origin.
  InitializeSecureContextMode(initializer);

  // Initialize origin trials, requires the post sandbox flags
  // security origin and secure context state.
  InitializeOriginTrials(initializer);

  // Initialize feature policy, depends on origin trials.
  InitializeFeaturePolicy(initializer);

  // Initialize document policy.
  InitializeDocumentPolicy(initializer);
}

void SecurityContextInit::CountFeaturePolicyUsage(
    mojom::blink::WebFeature feature) {
  if (execution_context_)
    execution_context_->CountFeaturePolicyUsage(feature);
}

bool SecurityContextInit::FeaturePolicyFeatureObserved(
    mojom::blink::FeaturePolicyFeature feature) {
  return execution_context_ &&
         execution_context_->FeaturePolicyFeatureObserved(feature);
}

bool SecurityContextInit::FeatureEnabled(OriginTrialFeature feature) const {
  return origin_trials_->IsFeatureEnabled(feature);
}

void SecurityContextInit::InitializeDocumentPolicy(
    const DocumentInit& initializer) {
  if (!RuntimeEnabledFeatures::DocumentPolicyEnabled(this))
    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(initializer.GetDocumentPolicy(), this);
  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);
    }
  }

  // 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).
  PolicyParserMessageBuffer logger("%s", /* discard_message */ true);
  base::Optional<DocumentPolicy::ParsedDocumentPolicy>
      report_only_parsed_policy = DocumentPolicyParser::Parse(
          initializer.ReportOnlyDocumentPolicyHeader(), logger);
  if (report_only_parsed_policy) {
    report_only_document_policy_ =
        FilterByOriginTrial(*report_only_parsed_policy, this);
    if (!report_only_document_policy_.feature_state.empty()) {
      UseCounter::Count(execution_context_,
                        WebFeature::kDocumentPolicyReportOnlyHeader);
    }
  }
}

void SecurityContextInit::InitializeFeaturePolicy(
    const DocumentInit& initializer) {
  initialized_feature_policy_state_ = true;
  // If we are a HTMLViewSourceDocument we use container, header or
  // inherited policies. https://crbug.com/898688. Don't set any from the
  // initializer or frame below.
  if (initializer.GetType() == DocumentInit::Type::kViewSource)
    return;

  auto* frame = initializer.GetFrame();
  // For a main frame, get inherited feature policy from the opener if any.
  if (frame && frame->IsMainFrame() && !frame->OpenerFeatureState().empty())
    frame_for_opener_feature_state_ = frame;

  PolicyParserMessageBuffer feature_policy_logger(
      "Error with Feature-Policy header: ");
  PolicyParserMessageBuffer report_only_feature_policy_logger(
      "Error with Report-Only-Feature-Policy header: ");

  feature_policy_header_ = FeaturePolicyParser::ParseHeader(
      initializer.FeaturePolicyHeader(), security_origin_,
      feature_policy_logger, this);

  report_only_feature_policy_header_ = FeaturePolicyParser::ParseHeader(
      initializer.ReportOnlyFeaturePolicyHeader(), security_origin_,
      report_only_feature_policy_logger, this);

  if (!report_only_feature_policy_header_.empty()) {
    UseCounter::Count(execution_context_,
                      WebFeature::kFeaturePolicyReportOnlyHeader);
  }

  if (execution_context_) {
    for (const auto& message : feature_policy_logger.GetMessages()) {
      execution_context_->AddConsoleMessage(
          MakeGarbageCollected<ConsoleMessage>(
              mojom::blink::ConsoleMessageSource::kSecurity, message.level,
              message.content));
    }
    for (const auto& message :
         report_only_feature_policy_logger.GetMessages()) {
      execution_context_->AddConsoleMessage(
          MakeGarbageCollected<ConsoleMessage>(
              mojom::blink::ConsoleMessageSource::kSecurity, message.level,
              message.content));
    }
  }

  if (sandbox_flags_ != network::mojom::blink::WebSandboxFlags::kNone &&
      RuntimeEnabledFeatures::FeaturePolicyForSandboxEnabled()) {
    // The sandbox flags might have come from CSP header or the browser; in
    // such cases the sandbox is not part of the container policy. They are
    // added to the header policy (which specifically makes sense in the case
    // of CSP sandbox).
    ApplySandboxFlagsToParsedFeaturePolicy(sandbox_flags_,
                                           feature_policy_header_);
  }

  if (frame && frame->Owner()) {
    container_policy_ =
        initializer.GetFramePolicy().value_or(FramePolicy()).container_policy;
  }

  // TODO(icelland): This is problematic querying sandbox flags before
  // feature policy is initialized.
  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_);
  }

  if (frame && !frame->IsMainFrame())
    parent_frame_ = frame->Tree().Parent();
}

std::unique_ptr<FeaturePolicy>
SecurityContextInit::CreateReportOnlyFeaturePolicy() const {
  // For non-Document initialization, returns nullptr directly.
  if (!initialized_feature_policy_state_)
    return nullptr;

  // If header not present, returns nullptr directly.
  if (report_only_feature_policy_header_.empty())
    return nullptr;

  // 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.
  DCHECK(security_origin_);
  std::unique_ptr<FeaturePolicy> report_only_policy =
      FeaturePolicy::CreateFromParentPolicy(nullptr /* parent_policy */,
                                            {} /* container_policy */,
                                            security_origin_->ToUrlOrigin());
  report_only_policy->SetHeaderPolicy(report_only_feature_policy_header_);
  return report_only_policy;
}

std::unique_ptr<FeaturePolicy> SecurityContextInit::CreateFeaturePolicy()
    const {
  // For non-Document initialization, returns nullptr directly.
  if (!initialized_feature_policy_state_)
    return nullptr;

  // Feature policy should either come from a parent in the case of an
  // embedded child frame, or from an opener if any when a new window is
  // created by an opener. A main frame without an opener would not have a
  // parent policy nor an opener feature state.
  DCHECK(!parent_frame_ || !frame_for_opener_feature_state_);
  std::unique_ptr<FeaturePolicy> feature_policy;
  if (!frame_for_opener_feature_state_ ||
      !RuntimeEnabledFeatures::FeaturePolicyForSandboxEnabled()) {
    auto* parent_feature_policy =
        parent_frame_ ? parent_frame_->GetSecurityContext()->GetFeaturePolicy()
                      : nullptr;
    feature_policy = FeaturePolicy::CreateFromParentPolicy(
        parent_feature_policy, container_policy_,
        security_origin_->ToUrlOrigin());
  } else {
    DCHECK(!parent_frame_);
    feature_policy = FeaturePolicy::CreateWithOpenerPolicy(
        frame_for_opener_feature_state_->OpenerFeatureState(),
        security_origin_->ToUrlOrigin());
  }
  feature_policy->SetHeaderPolicy(feature_policy_header_);
  return feature_policy;
}

std::unique_ptr<DocumentPolicy> SecurityContextInit::CreateDocumentPolicy()
    const {
  return DocumentPolicy::CreateWithHeaderPolicy(document_policy_);
}

std::unique_ptr<DocumentPolicy>
SecurityContextInit::CreateReportOnlyDocumentPolicy() const {
  return report_only_document_policy_.feature_state.empty()
             ? nullptr
             : DocumentPolicy::CreateWithHeaderPolicy(
                   report_only_document_policy_);
}

void SecurityContextInit::InitializeSecureContextMode(
    const DocumentInit& initializer) {
  auto* frame = initializer.GetFrame();
  if (!security_origin_->IsPotentiallyTrustworthy()) {
    secure_context_mode_ = SecureContextMode::kInsecureContext;
  } else if (SchemeRegistry::SchemeShouldBypassSecureContextCheck(
                 security_origin_->Protocol())) {
    secure_context_mode_ = SecureContextMode::kSecureContext;
  } else if (frame) {
    Frame* parent = frame->Tree().Parent();
    while (parent) {
      if (!parent->GetSecurityContext()
               ->GetSecurityOrigin()
               ->IsPotentiallyTrustworthy()) {
        secure_context_mode_ = SecureContextMode::kInsecureContext;
        break;
      }
      parent = parent->Tree().Parent();
    }
    if (!secure_context_mode_.has_value())
      secure_context_mode_ = SecureContextMode::kSecureContext;
  } else {
    secure_context_mode_ = SecureContextMode::kInsecureContext;
  }
  bool is_secure = secure_context_mode_ == SecureContextMode::kSecureContext;
  if (GetSandboxFlags() != network::mojom::blink::WebSandboxFlags::kNone) {
    UseCounter::Count(
        execution_context_,
        is_secure ? WebFeature::kSecureContextCheckForSandboxedOriginPassed
                  : WebFeature::kSecureContextCheckForSandboxedOriginFailed);
  }

  UseCounter::Count(execution_context_,
                    is_secure ? WebFeature::kSecureContextCheckPassed
                              : WebFeature::kSecureContextCheckFailed);
}

void SecurityContextInit::InitializeOriginTrials(
    const DocumentInit& initializer) {
  DCHECK(secure_context_mode_.has_value());
  origin_trials_ = MakeGarbageCollected<OriginTrialContext>();

  const String& header_value = initializer.OriginTrialsHeader();

  if (header_value.IsEmpty())
    return;
  std::unique_ptr<Vector<String>> tokens(
      OriginTrialContext::ParseHeaderValue(header_value));
  if (!tokens)
    return;
  origin_trials_->AddTokens(
      *tokens, security_origin_.get(),
      secure_context_mode_ == SecureContextMode::kSecureContext);
}

}  // namespace blink