blob: 5de6cb784646ef0045cc41255baaf77410cdecd1 [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 "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/window_agent.h"
#include "third_party/blink/renderer/core/execution_context/window_agent_factory.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_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"
#include "third_party/blink/renderer/platform/web_test_support.h"
namespace blink {
// 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,
Agent* agent)
: security_origin_(std::move(origin)),
origin_trials_(origin_trials),
agent_(agent),
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) {
// Content Security Policy can provide sandbox flags. In CSP
// 'self' will be determined when the policy is bound. That occurs
// once the document is constructed.
InitializeContentSecurityPolicy(initializer);
// Sandbox flags can come from initializer, loader or CSP.
InitializeSandboxFlags(initializer);
// The origin can be opaque based on sandbox flags.
InitializeOrigin(initializer);
// 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);
// Initialize the agent. Depends on security origin.
InitializeAgent(initializer);
}
bool SecurityContextInit::FeaturePolicyFeatureObserved(
mojom::blink::FeaturePolicyFeature feature) {
if (parsed_feature_policies_.Contains(feature))
return true;
parsed_feature_policies_.insert(feature);
return false;
}
bool SecurityContextInit::FeatureEnabled(OriginTrialFeature feature) const {
return origin_trials_->IsFeatureEnabled(feature);
}
void SecurityContextInit::ApplyPendingDataToDocument(Document& document) const {
if (BindCSPImmediately()) {
document.GetContentSecurityPolicy()->BindToDelegate(
document.GetContentSecurityPolicyDelegate());
}
for (auto feature : feature_count_)
UseCounter::Count(document, feature);
for (auto feature : parsed_feature_policies_)
document.ToExecutionContext()->FeaturePolicyFeatureObserved(feature);
for (const auto& message : feature_policy_parse_messages_) {
document.AddConsoleMessage(MakeGarbageCollected<ConsoleMessage>(
mojom::ConsoleMessageSource::kSecurity,
mojom::ConsoleMessageLevel::kError,
"Error with Feature-Policy header: " + message));
}
}
void SecurityContextInit::InitializeContentSecurityPolicy(
const DocumentInit& initializer) {
auto* frame = initializer.GetFrame();
ContentSecurityPolicy* last_origin_document_csp =
frame ? frame->Loader().GetLastOriginDocumentCSP() : nullptr;
KURL url;
if (initializer.ShouldSetURL())
url = initializer.Url().IsEmpty() ? BlankURL() : initializer.Url();
// Alias certain security properties from |owner_document|. Used for the
// case of about:blank pages inheriting the security properties of their
// requestor context.
//
// Note that this is currently somewhat broken; Blink always inherits from
// the parent or opener, even though it should actually be inherited from
// the request initiator.
if (url.IsEmpty() && initializer.HasSecurityContext() &&
!initializer.OriginToCommit() && initializer.OwnerDocument()) {
last_origin_document_csp =
initializer.OwnerDocument()->GetContentSecurityPolicy();
}
csp_ = initializer.GetContentSecurityPolicy();
if (!csp_) {
if (initializer.ImportsController()) {
// If this document is an HTML import, grab a reference to its master
// document's Content Security Policy. We don't bind the CSP's delegate
// in 'InitSecurityPolicy' in this case, as we can't rebind the master
// document's policy object: The Content Security Policy's delegate
// needs to remain set to the master document.
csp_ =
initializer.ImportsController()->Master()->GetContentSecurityPolicy();
return;
}
csp_ = MakeGarbageCollected<ContentSecurityPolicy>();
bind_csp_immediately_ = true;
}
// We should inherit the navigation initiator CSP if the document is loaded
// using a local-scheme url.
//
// Note: about:srcdoc inherits CSP from its parent, not from its initiator.
// In this case, the initializer.GetContentSecurityPolicy() is used.
if (last_origin_document_csp && !url.IsAboutSrcdocURL() &&
(url.IsEmpty() || url.ProtocolIsAbout() || url.ProtocolIsData() ||
url.ProtocolIs("blob") || url.ProtocolIs("filesystem"))) {
csp_->CopyStateFrom(last_origin_document_csp);
}
if (initializer.GetType() == DocumentInit::Type::kPlugin) {
if (last_origin_document_csp) {
csp_->CopyPluginTypesFrom(last_origin_document_csp);
return;
}
// TODO(andypaicu): This should inherit the origin document's plugin types
// but because this could be a OOPIF document it might not have access. In
// this situation we fallback on using the parent/opener:
if (frame) {
Frame* inherit_from = frame->Tree().Parent() ? frame->Tree().Parent()
: frame->Client()->Opener();
if (inherit_from && frame != inherit_from) {
csp_->CopyPluginTypesFrom(
inherit_from->GetSecurityContext()->GetContentSecurityPolicy());
}
}
}
}
void SecurityContextInit::InitializeSandboxFlags(
const DocumentInit& initializer) {
sandbox_flags_ = initializer.GetSandboxFlags() | csp_->GetSandboxMask();
auto* frame = initializer.GetFrame();
if (frame && frame->Loader().GetDocumentLoader()->Archive()) {
// The URL of a Document loaded from a MHTML archive is controlled by
// the Content-Location header. This would allow UXSS, since
// Content-Location can be arbitrarily controlled to control the
// Document's URL and origin. Instead, force a Document loaded from a
// MHTML archive to be sandboxed, providing exceptions only for creating
// new windows.
sandbox_flags_ |= (mojom::blink::WebSandboxFlags::kAll &
~(mojom::blink::WebSandboxFlags::kPopups |
mojom::blink::WebSandboxFlags::
kPropagatesToAuxiliaryBrowsingContexts));
}
}
void SecurityContextInit::InitializeOrigin(const DocumentInit& initializer) {
scoped_refptr<SecurityOrigin> document_origin =
initializer.GetDocumentOrigin();
if ((sandbox_flags_ & mojom::blink::WebSandboxFlags::kOrigin) !=
mojom::blink::WebSandboxFlags::kNone) {
scoped_refptr<SecurityOrigin> sandboxed_origin =
initializer.OriginToCommit() ? initializer.OriginToCommit()
: document_origin->DeriveNewOpaqueOrigin();
// If we're supposed to inherit our security origin from our
// owner, but we're also sandboxed, the only things we inherit are
// the origin's potential trustworthiness and the ability to
// load local resources. The latter lets about:blank iframes in
// file:// URL documents load images and other resources from
// the file system.
//
// Note: Sandboxed about:srcdoc iframe without "allow-same-origin" aren't
// allowed to load user's file, even if its parent can.
if (initializer.OwnerDocument()) {
if (document_origin->IsPotentiallyTrustworthy())
sandboxed_origin->SetOpaqueOriginIsPotentiallyTrustworthy(true);
if (document_origin->CanLoadLocalResources() &&
!initializer.IsSrcdocDocument())
sandboxed_origin->GrantLoadLocalResources();
}
security_origin_ = sandboxed_origin;
} else {
security_origin_ = document_origin;
}
// If we are a page popup in LayoutTests ensure we use the popup
// owner's security origin so the tests can possibly access the
// document via internals API.
auto* frame = initializer.GetFrame();
if (frame && frame->GetPage()->GetChromeClient().IsPopup() &&
WebTestSupport::IsRunningWebTest()) {
security_origin_ = frame->PagePopupOwner()
->GetDocument()
.GetSecurityOrigin()
->IsolatedCopy();
}
if (initializer.HasSecurityContext()) {
if (Settings* settings = initializer.GetSettings()) {
if (!settings->GetWebSecurityEnabled()) {
// Web security is turned off. We should let this document access
// every other document. This is used primary by testing harnesses for
// web sites.
security_origin_->GrantUniversalAccess();
} else if (security_origin_->IsLocal()) {
if (settings->GetAllowUniversalAccessFromFileURLs()) {
// Some clients want local URLs to have universal access, but that
// setting is dangerous for other clients.
security_origin_->GrantUniversalAccess();
} else if (!settings->GetAllowFileAccessFromFileURLs()) {
// Some clients do not want local URLs to have access to other local
// URLs.
security_origin_->BlockLocalAccessFromLocalOrigin();
}
}
}
}
if (initializer.GrantLoadLocalResources())
security_origin_->GrantLoadLocalResources();
if (security_origin_->IsOpaque() && initializer.ShouldSetURL()) {
KURL url = initializer.Url().IsEmpty() ? BlankURL() : initializer.Url();
if (SecurityOrigin::Create(url)->IsPotentiallyTrustworthy())
security_origin_->SetOpaqueOriginIsPotentiallyTrustworthy(true);
}
}
void SecurityContextInit::InitializeDocumentPolicy(
const DocumentInit& initializer) {
// 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.
for (const auto& entry : initializer.GetDocumentPolicy()) {
if (!DisabledByOriginTrial(entry.first, this)) {
document_policy_.insert(entry);
}
}
}
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;
feature_policy_header_ = FeaturePolicyParser::ParseHeader(
initializer.FeaturePolicyHeader(), security_origin_,
&feature_policy_parse_messages_, this);
if (sandbox_flags_ != 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_ & mojom::blink::WebSandboxFlags::kNavigation) !=
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::CreateFeaturePolicy()
const {
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_);
}
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() != mojom::blink::WebSandboxFlags::kNone) {
feature_count_.insert(
is_secure ? WebFeature::kSecureContextCheckForSandboxedOriginPassed
: WebFeature::kSecureContextCheckForSandboxedOriginFailed);
}
feature_count_.insert(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(
security_origin_.get(),
secure_context_mode_ == SecureContextMode::kSecureContext, *tokens);
}
void SecurityContextInit::InitializeAgent(const DocumentInit& initializer) {
// If we are allowed to share our document with other windows then we need
// to look at the window agent factory, otherwise we should create our own
// window agent.
if (auto* window_agent_factory = initializer.GetWindowAgentFactory()) {
bool has_potential_universal_access_privilege = false;
if (auto* settings = initializer.GetSettingsForWindowAgentFactory()) {
// TODO(keishi): Also check if AllowUniversalAccessFromFileURLs might
// dynamically change.
if (!settings->GetWebSecurityEnabled() ||
settings->GetAllowUniversalAccessFromFileURLs())
has_potential_universal_access_privilege = true;
}
agent_ = window_agent_factory->GetAgentForOrigin(
has_potential_universal_access_privilege,
V8PerIsolateData::MainThreadIsolate(), security_origin_.get());
} else {
// ContextDocument is null only for Documents created in unit tests.
// In that case, use a throw away WindowAgent.
agent_ = MakeGarbageCollected<WindowAgent>(
V8PerIsolateData::MainThreadIsolate());
}
// Derive possibly a new security origin that contains the cluster id.
security_origin_ =
security_origin_->GetOriginForAgentCluster(agent_->cluster_id());
}
} // namespace blink