blob: 3ca01aa7d456d914a9700be6bb80f07df9a041ec [file] [log] [blame]
// Copyright 2018 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/frame/csp/execution_context_csp_delegate.h"
#include "services/network/public/cpp/web_sandbox_flags.h"
#include "services/network/public/mojom/web_sandbox_flags.mojom-blink.h"
#include "third_party/blink/public/common/security_context/insecure_request_policy.h"
#include "third_party/blink/public/mojom/devtools/inspector_issue.mojom-blink.h"
#include "third_party/blink/public/mojom/security_context/insecure_request_policy.mojom-blink.h"
#include "third_party/blink/renderer/bindings/core/v8/source_location.h"
#include "third_party/blink/renderer/core/dom/document.h"
#include "third_party/blink/renderer/core/events/security_policy_violation_event.h"
#include "third_party/blink/renderer/core/execution_context/execution_context.h"
#include "third_party/blink/renderer/core/execution_context/security_context.h"
#include "third_party/blink/renderer/core/frame/csp/csp_violation_report_body.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/report.h"
#include "third_party/blink/renderer/core/frame/reporting_context.h"
#include "third_party/blink/renderer/core/loader/document_loader.h"
#include "third_party/blink/renderer/core/loader/ping_loader.h"
#include "third_party/blink/renderer/core/probe/core_probes.h"
#include "third_party/blink/renderer/core/workers/worker_global_scope.h"
#include "third_party/blink/renderer/platform/instrumentation/use_counter.h"
#include "third_party/blink/renderer/platform/network/encoded_form_data.h"
#include "third_party/blink/renderer/platform/runtime_enabled_features.h"
#include "third_party/blink/renderer/platform/weborigin/security_origin.h"
namespace blink {
ExecutionContextCSPDelegate::ExecutionContextCSPDelegate(
ExecutionContext& execution_context)
: execution_context_(&execution_context) {}
void ExecutionContextCSPDelegate::Trace(Visitor* visitor) const {
visitor->Trace(execution_context_);
ContentSecurityPolicyDelegate::Trace(visitor);
}
const SecurityOrigin* ExecutionContextCSPDelegate::GetSecurityOrigin() {
return execution_context_->GetSecurityOrigin();
}
const KURL& ExecutionContextCSPDelegate::Url() const {
return execution_context_->Url();
}
void ExecutionContextCSPDelegate::SetSandboxFlags(
network::mojom::blink::WebSandboxFlags mask) {
// Ideally sandbox flags are determined at construction time since
// sandbox flags influence the security origin and that influences
// the Agent that is assigned for the ExecutionContext. Changing
// an ExecutionContext's agent in the middle of an object lifecycle
// is not permitted.
// Since Workers and Worklets don't share agents (each one is unique)
// we allow them to apply new sandbox flags on top of the current ones.
WorkerOrWorkletGlobalScope* worklet_or_worker =
DynamicTo<WorkerOrWorkletGlobalScope>(execution_context_.Get());
if (worklet_or_worker) {
worklet_or_worker->SetSandboxFlags(mask);
}
// Just check that all the sandbox flags that are set by CSP have
// already been set on the security context. Meta tags can't set them
// and we should have already constructed the document with the correct
// sandbox flags from CSP already.
network::mojom::blink::WebSandboxFlags flags =
execution_context_->GetSandboxFlags();
CHECK_EQ(flags | mask, flags);
}
void ExecutionContextCSPDelegate::SetRequireTrustedTypes() {
GetSecurityContext().SetRequireTrustedTypes();
}
void ExecutionContextCSPDelegate::AddInsecureRequestPolicy(
mojom::blink::InsecureRequestPolicy policy) {
SecurityContext& security_context = GetSecurityContext();
auto* window = DynamicTo<LocalDOMWindow>(execution_context_.Get());
// Step 2. Set settings’s insecure requests policy to Upgrade. [spec text]
// Upgrade Insecure Requests: Update the policy.
security_context.SetInsecureRequestPolicy(
security_context.GetInsecureRequestPolicy() | policy);
if (window && window->GetFrame()) {
window->GetFrame()->GetLocalFrameHostRemote().EnforceInsecureRequestPolicy(
security_context.GetInsecureRequestPolicy());
}
// Upgrade Insecure Requests: Update the set of insecure URLs to upgrade.
if ((policy &
mojom::blink::InsecureRequestPolicy::kUpgradeInsecureRequests) !=
mojom::blink::InsecureRequestPolicy::kLeaveInsecureRequestsAlone) {
// Spec: Enforcing part of:
// https://w3c.github.io/webappsec-upgrade-insecure-requests/#delivery
// Step 3. Let tuple be a tuple of the protected resource’s URL's host and
// port. [spec text]
// Step 4. Insert tuple into settings’s upgrade insecure navigations set.
// [spec text]
Count(WebFeature::kUpgradeInsecureRequestsEnabled);
// We don't add the hash if |window| is null, to prevent
// WorkerGlobalScope::Url() before it's ready. https://crbug.com/861564
// This should be safe, because the insecure navigations set is not used
// in non-Document contexts.
if (window && !Url().Host().IsEmpty()) {
uint32_t hash = Url().Host().Impl()->GetHash();
security_context.AddInsecureNavigationUpgrade(hash);
if (auto* frame = window->GetFrame()) {
frame->GetLocalFrameHostRemote().EnforceInsecureNavigationsSet(
SecurityContext::SerializeInsecureNavigationSet(
GetSecurityContext().InsecureNavigationsToUpgrade()));
}
}
}
}
std::unique_ptr<SourceLocation>
ExecutionContextCSPDelegate::GetSourceLocation() {
return SourceLocation::Capture(execution_context_);
}
base::Optional<uint16_t> ExecutionContextCSPDelegate::GetStatusCode() {
base::Optional<uint16_t> status_code;
// TODO(mkwst): We only have status code information for Documents. It would
// be nice to get them for Workers as well.
// TODO(crbug.com/1153336) Use network::IsUrlPotentiallyTrustworthy().
Document* document = GetDocument();
if (document && !SecurityOrigin::IsSecure(document->Url()) &&
document->Loader()) {
status_code = document->Loader()->GetResponse().HttpStatusCode();
}
return status_code;
}
String ExecutionContextCSPDelegate::GetDocumentReferrer() {
String referrer;
// TODO(mkwst): We only have referrer information for Documents. It would be
// nice to get them for Workers as well.
if (Document* document = GetDocument())
referrer = document->referrer();
return referrer;
}
void ExecutionContextCSPDelegate::DispatchViolationEvent(
const SecurityPolicyViolationEventInit& violation_data,
Element* element) {
execution_context_->GetTaskRunner(TaskType::kNetworking)
->PostTask(
FROM_HERE,
WTF::Bind(
&ExecutionContextCSPDelegate::DispatchViolationEventInternal,
WrapPersistent(this), WrapPersistent(&violation_data),
WrapPersistent(element)));
}
void ExecutionContextCSPDelegate::PostViolationReport(
const SecurityPolicyViolationEventInit& violation_data,
const String& stringified_report,
bool is_frame_ancestors_violation,
const Vector<String>& report_endpoints,
bool use_reporting_api) {
DCHECK_EQ(is_frame_ancestors_violation,
network::mojom::blink::CSPDirectiveName::FrameAncestors ==
ContentSecurityPolicy::GetDirectiveType(
violation_data.effectiveDirective()));
// TODO(crbug/929370): Support POSTing violation reports from a Worker.
Document* document = GetDocument();
if (!document)
return;
LocalFrame* frame = document->GetFrame();
if (!frame)
return;
scoped_refptr<EncodedFormData> report =
EncodedFormData::Create(stringified_report.Utf8());
// Construct and route the report to the ReportingContext, to be observed
// by any ReportingObservers.
auto* body = MakeGarbageCollected<CSPViolationReportBody>(violation_data);
Report* observed_report = MakeGarbageCollected<Report>(
ReportType::kCSPViolation, Url().GetString(), body);
ReportingContext::From(execution_context_.Get())
->QueueReport(observed_report,
use_reporting_api ? report_endpoints : Vector<String>());
if (use_reporting_api)
return;
for (const auto& report_endpoint : report_endpoints) {
// Use the frame's document to complete the endpoint URL, overriding its URL
// with the blocked document's URL.
// https://w3c.github.io/webappsec-csp/#report-violation
// Step 3.4.2.1. Let endpoint be the result of executing the URL parser with
// token as the input, and violation’s url as the base URL. [spec text]
KURL url = is_frame_ancestors_violation
? document->CompleteURLWithOverride(
report_endpoint, KURL(violation_data.blockedURI()))
// We use the FallbackBaseURL to ensure that we don't
// respect base elements when determining the report
// endpoint URL.
// Note: According to Step 3.4.2.1 mentioned above, the base
// URL is "violation’s url" which should be violation's
// global object's URL. So using FallbackBaseURL() might be
// inconsistent.
: document->CompleteURLWithOverride(
report_endpoint, document->FallbackBaseURL());
PingLoader::SendViolationReport(frame, url, report);
}
}
void ExecutionContextCSPDelegate::Count(WebFeature feature) {
UseCounter::Count(execution_context_, feature);
}
void ExecutionContextCSPDelegate::AddConsoleMessage(
ConsoleMessage* console_message) {
execution_context_->AddConsoleMessage(console_message);
}
void ExecutionContextCSPDelegate::AddInspectorIssue(
mojom::blink::InspectorIssueInfoPtr info) {
execution_context_->AddInspectorIssue(std::move(info));
}
void ExecutionContextCSPDelegate::DisableEval(const String& error_message) {
execution_context_->DisableEval(error_message);
}
void ExecutionContextCSPDelegate::ReportBlockedScriptExecutionToInspector(
const String& directive_text) {
probe::ScriptExecutionBlockedByCSP(execution_context_, directive_text);
}
void ExecutionContextCSPDelegate::DidAddContentSecurityPolicies(
WTF::Vector<network::mojom::blink::ContentSecurityPolicyPtr> policies) {
auto* window = DynamicTo<LocalDOMWindow>(execution_context_.Get());
if (!window)
return;
LocalFrame* frame = window->GetFrame();
if (!frame)
return;
// Record what source was used to find main frame CSP.
if (frame->IsMainFrame()) {
for (const auto& policy : policies) {
switch (policy->header->source) {
case network::mojom::ContentSecurityPolicySource::kHTTP:
Count(WebFeature::kMainFrameCSPViaHTTP);
break;
case network::mojom::ContentSecurityPolicySource::kMeta:
Count(WebFeature::kMainFrameCSPViaMeta);
break;
case network::mojom::ContentSecurityPolicySource::kOriginPolicy:
Count(WebFeature::kMainFrameCSPViaOriginPolicy);
break;
}
}
}
frame->GetLocalFrameHostRemote().DidAddContentSecurityPolicies(
std::move(policies));
}
SecurityContext& ExecutionContextCSPDelegate::GetSecurityContext() {
return execution_context_->GetSecurityContext();
}
Document* ExecutionContextCSPDelegate::GetDocument() {
auto* window = DynamicTo<LocalDOMWindow>(execution_context_.Get());
return window ? window->document() : nullptr;
}
void ExecutionContextCSPDelegate::DispatchViolationEventInternal(
const SecurityPolicyViolationEventInit* violation_data,
Element* element) {
// Worklets don't support Events in general.
if (execution_context_->IsWorkletGlobalScope())
return;
// https://w3c.github.io/webappsec-csp/#report-violation.
// Step 3.1. If target is not null, and global is a Window, and target’s
// shadow-including root is not global’s associated Document, set target to
// null. [spec text]
// Step 3.2. If target is null:
// Step 3.2.1. Set target be violation’s global object.
// Step 3.2.2. If target is a Window, set target to target’s associated
// Document. [spec text]
// Step 3.3. Fire an event named securitypolicyviolation that uses the
// SecurityPolicyViolationEvent interface at target.. [spec text]
SecurityPolicyViolationEvent& event = *SecurityPolicyViolationEvent::Create(
event_type_names::kSecuritypolicyviolation, violation_data);
DCHECK(event.bubbles());
if (auto* document = GetDocument()) {
if (element && element->isConnected() && element->GetDocument() == document)
element->EnqueueEvent(event, TaskType::kInternalDefault);
else
document->EnqueueEvent(event, TaskType::kInternalDefault);
} else if (auto* scope = DynamicTo<WorkerGlobalScope>(*execution_context_)) {
scope->EnqueueEvent(event, TaskType::kInternalDefault);
}
}
} // namespace blink