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