| // Copyright 2014 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 "core/frame/csp/CSPDirectiveList.h" |
| |
| #include "bindings/core/v8/SourceLocation.h" |
| #include "core/dom/Document.h" |
| #include "core/dom/SecurityContext.h" |
| #include "core/dom/SpaceSplitString.h" |
| #include "core/frame/Deprecation.h" |
| #include "core/frame/LocalFrame.h" |
| #include "core/frame/UseCounter.h" |
| #include "core/html/HTMLScriptElement.h" |
| #include "core/inspector/ConsoleMessage.h" |
| #include "platform/Crypto.h" |
| #include "platform/RuntimeEnabledFeatures.h" |
| #include "platform/network/ContentSecurityPolicyParsers.h" |
| #include "platform/weborigin/KURL.h" |
| #include "platform/wtf/text/Base64.h" |
| #include "platform/wtf/text/ParsingUtilities.h" |
| #include "platform/wtf/text/StringBuilder.h" |
| #include "platform/wtf/text/StringUTF8Adaptor.h" |
| #include "platform/wtf/text/WTFString.h" |
| |
| namespace blink { |
| |
| namespace { |
| |
| String GetSha256String(const String& content) { |
| DigestValue digest; |
| StringUTF8Adaptor utf8_content(content); |
| bool digest_success = ComputeDigest(kHashAlgorithmSha256, utf8_content.Data(), |
| utf8_content.length(), digest); |
| if (!digest_success) { |
| return "sha256-..."; |
| } |
| |
| return "sha256-" + Base64Encode(reinterpret_cast<char*>(digest.data()), |
| digest.size(), kBase64DoNotInsertLFs); |
| } |
| |
| ContentSecurityPolicyHashAlgorithm ConvertHashAlgorithmToCSPHashAlgorithm( |
| HashAlgorithm algorithm) { |
| switch (algorithm) { |
| case kHashAlgorithmSha1: |
| // Sha1 is not supported. |
| return kContentSecurityPolicyHashAlgorithmNone; |
| case kHashAlgorithmSha256: |
| return kContentSecurityPolicyHashAlgorithmSha256; |
| case kHashAlgorithmSha384: |
| return kContentSecurityPolicyHashAlgorithmSha384; |
| case kHashAlgorithmSha512: |
| return kContentSecurityPolicyHashAlgorithmSha512; |
| } |
| NOTREACHED(); |
| return kContentSecurityPolicyHashAlgorithmNone; |
| } |
| |
| // IntegrityMetadata (from SRI) has base64-encoded digest values, but CSP uses |
| // binary format. This converts from the former to the latter. |
| bool ParseBase64Digest(String base64, DigestValue& hash) { |
| Vector<char> hash_vector; |
| // We accept base64url-encoded data here by normalizing it to base64. |
| if (!Base64Decode(NormalizeToBase64(base64), hash_vector)) |
| return false; |
| if (hash_vector.IsEmpty() || hash_vector.size() > kMaxDigestSize) |
| return false; |
| hash.Append(reinterpret_cast<uint8_t*>(hash_vector.data()), |
| hash_vector.size()); |
| return true; |
| } |
| |
| } // namespace |
| |
| CSPDirectiveList::CSPDirectiveList(ContentSecurityPolicy* policy, |
| ContentSecurityPolicyHeaderType type, |
| ContentSecurityPolicyHeaderSource source) |
| : policy_(policy), |
| header_type_(type), |
| header_source_(source), |
| has_sandbox_policy_(false), |
| strict_mixed_content_checking_enforced_(false), |
| upgrade_insecure_requests_(false), |
| treat_as_public_address_(false), |
| require_sri_for_(RequireSRIForToken::kNone) {} |
| |
| CSPDirectiveList* CSPDirectiveList::Create( |
| ContentSecurityPolicy* policy, |
| const UChar* begin, |
| const UChar* end, |
| ContentSecurityPolicyHeaderType type, |
| ContentSecurityPolicyHeaderSource source) { |
| CSPDirectiveList* directives = new CSPDirectiveList(policy, type, source); |
| directives->Parse(begin, end); |
| |
| if (!directives->CheckEval( |
| directives->OperativeDirective(directives->script_src_.Get()))) { |
| String message = |
| "Refused to evaluate a string as JavaScript because 'unsafe-eval' is " |
| "not an allowed source of script in the following Content Security " |
| "Policy directive: \"" + |
| directives->OperativeDirective(directives->script_src_.Get()) |
| ->GetText() + |
| "\".\n"; |
| directives->SetEvalDisabledErrorMessage(message); |
| } |
| |
| if (directives->IsReportOnly() && |
| source != kContentSecurityPolicyHeaderSourceMeta && |
| directives->ReportEndpoints().IsEmpty()) |
| policy->ReportMissingReportURI(String(begin, end - begin)); |
| |
| return directives; |
| } |
| |
| void CSPDirectiveList::ReportViolation( |
| const String& directive_text, |
| const ContentSecurityPolicy::DirectiveType& effective_type, |
| const String& console_message, |
| const KURL& blocked_url, |
| ResourceRequest::RedirectStatus redirect_status) const { |
| String message = |
| IsReportOnly() ? "[Report Only] " + console_message : console_message; |
| policy_->LogToConsole(ConsoleMessage::Create(kSecurityMessageSource, |
| kErrorMessageLevel, message)); |
| policy_->ReportViolation(directive_text, effective_type, message, blocked_url, |
| report_endpoints_, header_, header_type_, |
| ContentSecurityPolicy::kURLViolation, |
| std::unique_ptr<SourceLocation>(), |
| nullptr, // localFrame |
| redirect_status); |
| } |
| |
| void CSPDirectiveList::ReportViolationWithFrame( |
| const String& directive_text, |
| const ContentSecurityPolicy::DirectiveType& effective_type, |
| const String& console_message, |
| const KURL& blocked_url, |
| LocalFrame* frame) const { |
| String message = |
| IsReportOnly() ? "[Report Only] " + console_message : console_message; |
| policy_->LogToConsole(ConsoleMessage::Create(kSecurityMessageSource, |
| kErrorMessageLevel, message), |
| frame); |
| policy_->ReportViolation(directive_text, effective_type, message, blocked_url, |
| report_endpoints_, header_, header_type_, |
| ContentSecurityPolicy::kURLViolation, |
| std::unique_ptr<SourceLocation>(), frame); |
| } |
| |
| void CSPDirectiveList::ReportViolationWithLocation( |
| const String& directive_text, |
| const ContentSecurityPolicy::DirectiveType& effective_type, |
| const String& console_message, |
| const KURL& blocked_url, |
| const String& context_url, |
| const WTF::OrdinalNumber& context_line, |
| Element* element, |
| const String& source) const { |
| String message = |
| IsReportOnly() ? "[Report Only] " + console_message : console_message; |
| std::unique_ptr<SourceLocation> source_location = |
| SourceLocation::Capture(context_url, context_line.OneBasedInt(), 0); |
| policy_->LogToConsole(ConsoleMessage::Create(kSecurityMessageSource, |
| kErrorMessageLevel, message, |
| source_location->Clone())); |
| policy_->ReportViolation(directive_text, effective_type, message, blocked_url, |
| report_endpoints_, header_, header_type_, |
| ContentSecurityPolicy::kInlineViolation, |
| std::move(source_location), nullptr, // localFrame |
| RedirectStatus::kNoRedirect, element, source); |
| } |
| |
| void CSPDirectiveList::ReportViolationWithState( |
| const String& directive_text, |
| const ContentSecurityPolicy::DirectiveType& effective_type, |
| const String& message, |
| const KURL& blocked_url, |
| ScriptState* script_state, |
| const ContentSecurityPolicy::ExceptionStatus exception_status) const { |
| String report_message = IsReportOnly() ? "[Report Only] " + message : message; |
| // Print a console message if it won't be redundant with a |
| // JavaScript exception that the caller will throw. (Exceptions will |
| // never get thrown in report-only mode because the caller won't see |
| // a violation.) |
| if (IsReportOnly() || |
| exception_status == ContentSecurityPolicy::kWillNotThrowException) { |
| ConsoleMessage* console_message = ConsoleMessage::Create( |
| kSecurityMessageSource, kErrorMessageLevel, report_message); |
| policy_->LogToConsole(console_message); |
| } |
| policy_->ReportViolation(directive_text, effective_type, message, blocked_url, |
| report_endpoints_, header_, header_type_, |
| ContentSecurityPolicy::kEvalViolation, |
| std::unique_ptr<SourceLocation>()); |
| } |
| |
| bool CSPDirectiveList::CheckEval(SourceListDirective* directive) const { |
| return !directive || directive->AllowEval(); |
| } |
| |
| bool CSPDirectiveList::IsMatchingNoncePresent(SourceListDirective* directive, |
| const String& nonce) const { |
| return directive && directive->AllowNonce(nonce); |
| } |
| |
| bool CSPDirectiveList::AreAllMatchingHashesPresent( |
| SourceListDirective* directive, |
| const IntegrityMetadataSet& hashes) const { |
| if (!directive || hashes.IsEmpty()) |
| return false; |
| for (const std::pair<WTF::String, HashAlgorithm>& hash : hashes) { |
| // Convert the hash from integrity metadata format to CSP format. |
| CSPHashValue csp_hash; |
| csp_hash.first = ConvertHashAlgorithmToCSPHashAlgorithm(hash.second); |
| if (!ParseBase64Digest(hash.first, csp_hash.second)) |
| return false; |
| // All integrity hashes must be listed in the CSP. |
| if (!directive->AllowHash(csp_hash)) |
| return false; |
| } |
| return true; |
| } |
| |
| bool CSPDirectiveList::CheckHash(SourceListDirective* directive, |
| const CSPHashValue& hash_value) const { |
| return !directive || directive->AllowHash(hash_value); |
| } |
| |
| bool CSPDirectiveList::CheckHashedAttributes( |
| SourceListDirective* directive) const { |
| return !directive || directive->AllowHashedAttributes(); |
| } |
| |
| bool CSPDirectiveList::CheckDynamic(SourceListDirective* directive) const { |
| return !directive || directive->AllowDynamic(); |
| } |
| |
| void CSPDirectiveList::ReportMixedContent( |
| const KURL& mixed_url, |
| ResourceRequest::RedirectStatus redirect_status) const { |
| if (StrictMixedContentChecking()) { |
| policy_->ReportViolation( |
| ContentSecurityPolicy::GetDirectiveName( |
| ContentSecurityPolicy::DirectiveType::kBlockAllMixedContent), |
| ContentSecurityPolicy::DirectiveType::kBlockAllMixedContent, String(), |
| mixed_url, report_endpoints_, header_, header_type_, |
| ContentSecurityPolicy::kURLViolation, std::unique_ptr<SourceLocation>(), |
| nullptr, // contextFrame, |
| redirect_status); |
| } |
| } |
| |
| bool CSPDirectiveList::CheckSource( |
| SourceListDirective* directive, |
| const KURL& url, |
| ResourceRequest::RedirectStatus redirect_status) const { |
| // If |url| is empty, fall back to the policy URL to ensure that <object>'s |
| // without a `src` can be blocked/allowed, as they can still load plugins |
| // even though they don't actually have a URL. |
| return !directive || directive->Allows(url.IsEmpty() ? policy_->Url() : url, |
| redirect_status); |
| } |
| |
| bool CSPDirectiveList::CheckAncestors(SourceListDirective* directive, |
| LocalFrame* frame) const { |
| if (!frame || !directive) |
| return true; |
| |
| for (Frame* current = frame->Tree().Parent(); current; |
| current = current->Tree().Parent()) { |
| // The |current| frame might be a remote frame which has no URL, so use |
| // its origin instead. This should suffice for this check since it |
| // doesn't do path comparisons. See https://crbug.com/582544. |
| // |
| // TODO(mkwst): Move this check up into the browser process. See |
| // https://crbug.com/555418. |
| KURL url(KURL(), |
| current->GetSecurityContext()->GetSecurityOrigin()->ToString()); |
| if (!directive->Allows(url, ResourceRequest::RedirectStatus::kNoRedirect)) |
| return false; |
| } |
| return true; |
| } |
| |
| bool CSPDirectiveList::CheckRequestWithoutIntegrity( |
| WebURLRequest::RequestContext context) const { |
| if (require_sri_for_ == RequireSRIForToken::kNone) |
| return true; |
| // SRI specification |
| // (https://w3c.github.io/webappsec-subresource-integrity/#apply-algorithm-to-request) |
| // says to match token with request's destination with the token. |
| // Keep this logic aligned with ContentSecurityPolicy::allowRequest |
| if ((require_sri_for_ & RequireSRIForToken::kScript) && |
| (context == WebURLRequest::kRequestContextScript || |
| context == WebURLRequest::kRequestContextImport || |
| context == WebURLRequest::kRequestContextServiceWorker || |
| context == WebURLRequest::kRequestContextSharedWorker || |
| context == WebURLRequest::kRequestContextWorker)) { |
| return false; |
| } |
| if ((require_sri_for_ & RequireSRIForToken::kStyle) && |
| context == WebURLRequest::kRequestContextStyle) |
| return false; |
| return true; |
| } |
| |
| bool CSPDirectiveList::CheckRequestWithoutIntegrityAndReportViolation( |
| WebURLRequest::RequestContext context, |
| const KURL& url, |
| ResourceRequest::RedirectStatus redirect_status) const { |
| if (CheckRequestWithoutIntegrity(context)) |
| return true; |
| String resource_type; |
| switch (context) { |
| case WebURLRequest::kRequestContextScript: |
| case WebURLRequest::kRequestContextImport: |
| resource_type = "script"; |
| break; |
| case WebURLRequest::kRequestContextStyle: |
| resource_type = "stylesheet"; |
| break; |
| case WebURLRequest::kRequestContextServiceWorker: |
| resource_type = "service worker"; |
| break; |
| case WebURLRequest::kRequestContextSharedWorker: |
| resource_type = "shared worker"; |
| break; |
| case WebURLRequest::kRequestContextWorker: |
| resource_type = "worker"; |
| break; |
| default: |
| break; |
| } |
| |
| ReportViolation(ContentSecurityPolicy::GetDirectiveName( |
| ContentSecurityPolicy::DirectiveType::kRequireSRIFor), |
| ContentSecurityPolicy::DirectiveType::kRequireSRIFor, |
| "Refused to load the " + resource_type + " '" + |
| url.ElidedString() + |
| "' because 'require-sri-for' directive requires " |
| "integrity attribute be present for all " + |
| resource_type + "s.", |
| url, redirect_status); |
| return DenyIfEnforcingPolicy(); |
| } |
| |
| bool CSPDirectiveList::AllowRequestWithoutIntegrity( |
| WebURLRequest::RequestContext context, |
| const KURL& url, |
| ResourceRequest::RedirectStatus redirect_status, |
| SecurityViolationReportingPolicy reporting_policy) const { |
| if (reporting_policy == SecurityViolationReportingPolicy::kReport) |
| return CheckRequestWithoutIntegrityAndReportViolation(context, url, |
| redirect_status); |
| return DenyIfEnforcingPolicy() || CheckRequestWithoutIntegrity(context); |
| } |
| |
| bool CSPDirectiveList::CheckMediaType(MediaListDirective* directive, |
| const String& type, |
| const String& type_attribute) const { |
| if (!directive) |
| return true; |
| if (type_attribute.IsEmpty() || type_attribute.StripWhiteSpace() != type) |
| return false; |
| return directive->Allows(type); |
| } |
| |
| SourceListDirective* CSPDirectiveList::OperativeDirective( |
| SourceListDirective* directive) const { |
| return directive ? directive : default_src_.Get(); |
| } |
| |
| SourceListDirective* CSPDirectiveList::OperativeDirective( |
| SourceListDirective* directive, |
| SourceListDirective* override) const { |
| return directive ? directive : override; |
| } |
| |
| bool CSPDirectiveList::CheckEvalAndReportViolation( |
| SourceListDirective* directive, |
| const String& console_message, |
| ScriptState* script_state, |
| ContentSecurityPolicy::ExceptionStatus exception_status) const { |
| if (CheckEval(directive)) |
| return true; |
| |
| String suffix = String(); |
| if (directive == default_src_) |
| suffix = |
| " Note that 'script-src' was not explicitly set, so 'default-src' is " |
| "used as a fallback."; |
| |
| ReportViolationWithState( |
| directive->GetText(), ContentSecurityPolicy::DirectiveType::kScriptSrc, |
| console_message + "\"" + directive->GetText() + "\"." + suffix + "\n", |
| KURL(), script_state, exception_status); |
| if (!IsReportOnly()) { |
| policy_->ReportBlockedScriptExecutionToInspector(directive->GetText()); |
| return false; |
| } |
| return true; |
| } |
| |
| bool CSPDirectiveList::CheckMediaTypeAndReportViolation( |
| MediaListDirective* directive, |
| const String& type, |
| const String& type_attribute, |
| const String& console_message) const { |
| if (CheckMediaType(directive, type, type_attribute)) |
| return true; |
| |
| String message = console_message + "\'" + directive->GetText() + "\'."; |
| if (type_attribute.IsEmpty()) |
| message = message + |
| " When enforcing the 'plugin-types' directive, the plugin's " |
| "media type must be explicitly declared with a 'type' attribute " |
| "on the containing element (e.g. '<object type=\"[TYPE GOES " |
| "HERE]\" ...>')."; |
| |
| // 'RedirectStatus::NoRedirect' is safe here, as we do the media type check |
| // before actually loading data; this means that we shouldn't leak redirect |
| // targets, as we won't have had a chance to redirect yet. |
| ReportViolation( |
| directive->GetText(), ContentSecurityPolicy::DirectiveType::kPluginTypes, |
| message + "\n", KURL(), ResourceRequest::RedirectStatus::kNoRedirect); |
| return DenyIfEnforcingPolicy(); |
| } |
| |
| bool CSPDirectiveList::CheckInlineAndReportViolation( |
| SourceListDirective* directive, |
| const String& console_message, |
| Element* element, |
| const String& source, |
| const String& context_url, |
| const WTF::OrdinalNumber& context_line, |
| bool is_script, |
| const String& hash_value) const { |
| if (!directive || directive->AllowAllInline()) |
| return true; |
| |
| String suffix = String(); |
| if (directive->AllowInline() && directive->IsHashOrNoncePresent()) { |
| // If inline is allowed, but a hash or nonce is present, we ignore |
| // 'unsafe-inline'. Throw a reasonable error. |
| suffix = |
| " Note that 'unsafe-inline' is ignored if either a hash or nonce value " |
| "is present in the source list."; |
| } else { |
| suffix = |
| " Either the 'unsafe-inline' keyword, a hash ('" + hash_value + |
| "'), or a nonce ('nonce-...') is required to enable inline execution."; |
| if (directive == default_src_) |
| suffix = suffix + " Note also that '" + |
| String(is_script ? "script" : "style") + |
| "-src' was not explicitly set, so 'default-src' is used as a " |
| "fallback."; |
| } |
| |
| ReportViolationWithLocation( |
| directive->GetText(), |
| is_script ? ContentSecurityPolicy::DirectiveType::kScriptSrc |
| : ContentSecurityPolicy::DirectiveType::kStyleSrc, |
| console_message + "\"" + directive->GetText() + "\"." + suffix + "\n", |
| KURL(), context_url, context_line, element, |
| directive->AllowReportSample() ? source : g_empty_string); |
| |
| if (!IsReportOnly()) { |
| if (is_script) |
| policy_->ReportBlockedScriptExecutionToInspector(directive->GetText()); |
| return false; |
| } |
| return true; |
| } |
| |
| bool CSPDirectiveList::CheckSourceAndReportViolation( |
| SourceListDirective* directive, |
| const KURL& url, |
| const ContentSecurityPolicy::DirectiveType& effective_type, |
| ResourceRequest::RedirectStatus redirect_status) const { |
| if (!directive) |
| return true; |
| |
| // We ignore URL-based whitelists if we're allowing dynamic script injection. |
| if (CheckSource(directive, url, redirect_status) && !CheckDynamic(directive)) |
| return true; |
| |
| // We should never have a violation against `child-src` or `default-src` |
| // directly; the effective directive should always be one of the explicit |
| // fetch directives. |
| DCHECK_NE(ContentSecurityPolicy::DirectiveType::kChildSrc, effective_type); |
| DCHECK_NE(ContentSecurityPolicy::DirectiveType::kDefaultSrc, effective_type); |
| |
| String prefix; |
| if (ContentSecurityPolicy::DirectiveType::kBaseURI == effective_type) |
| prefix = "Refused to set the document's base URI to '"; |
| else if (ContentSecurityPolicy::DirectiveType::kWorkerSrc == effective_type) |
| prefix = "Refused to create a worker from '"; |
| else if (ContentSecurityPolicy::DirectiveType::kConnectSrc == effective_type) |
| prefix = "Refused to connect to '"; |
| else if (ContentSecurityPolicy::DirectiveType::kFontSrc == effective_type) |
| prefix = "Refused to load the font '"; |
| else if (ContentSecurityPolicy::DirectiveType::kFormAction == effective_type) |
| prefix = "Refused to send form data to '"; |
| else if (ContentSecurityPolicy::DirectiveType::kFrameSrc == effective_type) |
| prefix = "Refused to frame '"; |
| else if (ContentSecurityPolicy::DirectiveType::kImgSrc == effective_type) |
| prefix = "Refused to load the image '"; |
| else if (ContentSecurityPolicy::DirectiveType::kMediaSrc == effective_type) |
| prefix = "Refused to load media from '"; |
| else if (ContentSecurityPolicy::DirectiveType::kManifestSrc == effective_type) |
| prefix = "Refused to load manifest from '"; |
| else if (ContentSecurityPolicy::DirectiveType::kObjectSrc == effective_type) |
| prefix = "Refused to load plugin data from '"; |
| else if (ContentSecurityPolicy::DirectiveType::kScriptSrc == effective_type) |
| prefix = "Refused to load the script '"; |
| else if (ContentSecurityPolicy::DirectiveType::kStyleSrc == effective_type) |
| prefix = "Refused to load the stylesheet '"; |
| |
| String suffix = String(); |
| if (CheckDynamic(directive)) |
| suffix = |
| " 'strict-dynamic' is present, so host-based whitelisting is disabled."; |
| |
| String directive_name = directive->GetName(); |
| String effective_directive_name = |
| ContentSecurityPolicy::GetDirectiveName(effective_type); |
| if (directive_name != effective_directive_name) { |
| suffix = suffix + " Note that '" + effective_directive_name + |
| "' was not explicitly set, so '" + directive_name + |
| "' is used as a fallback."; |
| } |
| |
| ReportViolation(directive->GetText(), effective_type, |
| prefix + url.ElidedString() + |
| "' because it violates the following Content Security " |
| "Policy directive: \"" + |
| directive->GetText() + "\"." + suffix + "\n", |
| url, redirect_status); |
| return DenyIfEnforcingPolicy(); |
| } |
| |
| bool CSPDirectiveList::CheckAncestorsAndReportViolation( |
| SourceListDirective* directive, |
| LocalFrame* frame, |
| const KURL& url) const { |
| if (CheckAncestors(directive, frame)) |
| return true; |
| |
| ReportViolationWithFrame( |
| directive->GetText(), |
| ContentSecurityPolicy::DirectiveType::kFrameAncestors, |
| "Refused to display '" + url.ElidedString() + |
| "' in a frame because an ancestor violates the " |
| "following Content Security Policy directive: " |
| "\"" + |
| directive->GetText() + "\".", |
| url, frame); |
| return DenyIfEnforcingPolicy(); |
| } |
| |
| bool CSPDirectiveList::AllowJavaScriptURLs( |
| Element* element, |
| const String& source, |
| const String& context_url, |
| const WTF::OrdinalNumber& context_line, |
| SecurityViolationReportingPolicy reporting_policy) const { |
| SourceListDirective* directive = OperativeDirective(script_src_.Get()); |
| if (reporting_policy == SecurityViolationReportingPolicy::kReport) { |
| return CheckInlineAndReportViolation( |
| directive, |
| "Refused to execute JavaScript URL because it violates the following " |
| "Content Security Policy directive: ", |
| element, source, context_url, context_line, true, "sha256-..."); |
| } |
| |
| return !directive || directive->AllowAllInline(); |
| } |
| |
| bool CSPDirectiveList::AllowInlineEventHandlers( |
| Element* element, |
| const String& source, |
| const String& context_url, |
| const WTF::OrdinalNumber& context_line, |
| SecurityViolationReportingPolicy reporting_policy) const { |
| SourceListDirective* directive = OperativeDirective(script_src_.Get()); |
| if (reporting_policy == SecurityViolationReportingPolicy::kReport) { |
| return CheckInlineAndReportViolation( |
| OperativeDirective(script_src_.Get()), |
| "Refused to execute inline event handler because it violates the " |
| "following Content Security Policy directive: ", |
| element, source, context_url, context_line, true, "sha256-..."); |
| } |
| |
| return !directive || directive->AllowAllInline(); |
| } |
| |
| bool CSPDirectiveList::AllowInlineScript( |
| Element* element, |
| const String& context_url, |
| const String& nonce, |
| const WTF::OrdinalNumber& context_line, |
| SecurityViolationReportingPolicy reporting_policy, |
| const String& content) const { |
| SourceListDirective* directive = OperativeDirective(script_src_.Get()); |
| if (IsMatchingNoncePresent(directive, nonce)) |
| return true; |
| if (element && isHTMLScriptElement(element) && |
| !toHTMLScriptElement(element)->Loader()->IsParserInserted() && |
| AllowDynamic()) { |
| return true; |
| } |
| if (reporting_policy == SecurityViolationReportingPolicy::kReport) { |
| return CheckInlineAndReportViolation( |
| directive, |
| "Refused to execute inline script because it violates the following " |
| "Content Security Policy directive: ", |
| element, content, context_url, context_line, true, |
| GetSha256String(content)); |
| } |
| |
| return !directive || directive->AllowAllInline(); |
| } |
| |
| bool CSPDirectiveList::AllowInlineStyle( |
| Element* element, |
| const String& context_url, |
| const String& nonce, |
| const WTF::OrdinalNumber& context_line, |
| SecurityViolationReportingPolicy reporting_policy, |
| const String& content) const { |
| SourceListDirective* directive = OperativeDirective(style_src_.Get()); |
| if (IsMatchingNoncePresent(directive, nonce)) |
| return true; |
| if (reporting_policy == SecurityViolationReportingPolicy::kReport) { |
| return CheckInlineAndReportViolation( |
| directive, |
| "Refused to apply inline style because it violates the following " |
| "Content Security Policy directive: ", |
| element, content, context_url, context_line, false, |
| GetSha256String(content)); |
| } |
| |
| return !directive || directive->AllowAllInline(); |
| } |
| |
| bool CSPDirectiveList::AllowEval( |
| ScriptState* script_state, |
| SecurityViolationReportingPolicy reporting_policy, |
| ContentSecurityPolicy::ExceptionStatus exception_status) const { |
| if (reporting_policy == SecurityViolationReportingPolicy::kReport) { |
| return CheckEvalAndReportViolation( |
| OperativeDirective(script_src_.Get()), |
| "Refused to evaluate a string as JavaScript because 'unsafe-eval' is " |
| "not an allowed source of script in the following Content Security " |
| "Policy directive: ", |
| script_state, exception_status); |
| } |
| return CheckEval(OperativeDirective(script_src_.Get())); |
| } |
| |
| bool CSPDirectiveList::AllowPluginType( |
| const String& type, |
| const String& type_attribute, |
| const KURL& url, |
| SecurityViolationReportingPolicy reporting_policy) const { |
| return reporting_policy == SecurityViolationReportingPolicy::kReport |
| ? CheckMediaTypeAndReportViolation( |
| plugin_types_.Get(), type, type_attribute, |
| "Refused to load '" + url.ElidedString() + "' (MIME type '" + |
| type_attribute + |
| "') because it violates the following Content Security " |
| "Policy Directive: ") |
| : CheckMediaType(plugin_types_.Get(), type, type_attribute); |
| } |
| |
| bool CSPDirectiveList::AllowScriptFromSource( |
| const KURL& url, |
| const String& nonce, |
| const IntegrityMetadataSet& hashes, |
| ParserDisposition parser_disposition, |
| ResourceRequest::RedirectStatus redirect_status, |
| SecurityViolationReportingPolicy reporting_policy) const { |
| SourceListDirective* directive = OperativeDirective(script_src_.Get()); |
| if (IsMatchingNoncePresent(directive, nonce)) |
| return true; |
| if (parser_disposition == kNotParserInserted && AllowDynamic()) |
| return true; |
| if (AreAllMatchingHashesPresent(directive, hashes)) |
| return true; |
| return reporting_policy == SecurityViolationReportingPolicy::kReport |
| ? CheckSourceAndReportViolation( |
| directive, url, |
| ContentSecurityPolicy::DirectiveType::kScriptSrc, |
| redirect_status) |
| : CheckSource(directive, url, redirect_status); |
| } |
| |
| bool CSPDirectiveList::AllowObjectFromSource( |
| const KURL& url, |
| ResourceRequest::RedirectStatus redirect_status, |
| SecurityViolationReportingPolicy reporting_policy) const { |
| if (url.ProtocolIsAbout()) |
| return true; |
| return reporting_policy == SecurityViolationReportingPolicy::kReport |
| ? CheckSourceAndReportViolation( |
| OperativeDirective(object_src_.Get()), url, |
| ContentSecurityPolicy::DirectiveType::kObjectSrc, |
| redirect_status) |
| : CheckSource(OperativeDirective(object_src_.Get()), url, |
| redirect_status); |
| } |
| |
| bool CSPDirectiveList::AllowFrameFromSource( |
| const KURL& url, |
| ResourceRequest::RedirectStatus redirect_status, |
| SecurityViolationReportingPolicy reporting_policy) const { |
| if (url.ProtocolIsAbout()) |
| return true; |
| |
| // 'frame-src' overrides 'child-src', which overrides the default |
| // sources. So, we do this nested set of calls to 'operativeDirective()' to |
| // grab 'frame-src' if it exists, 'child-src' if it doesn't, and 'defaut-src' |
| // if neither are available. |
| SourceListDirective* which_directive = OperativeDirective( |
| frame_src_.Get(), OperativeDirective(child_src_.Get())); |
| |
| return reporting_policy == SecurityViolationReportingPolicy::kReport |
| ? CheckSourceAndReportViolation( |
| which_directive, url, |
| ContentSecurityPolicy::DirectiveType::kFrameSrc, |
| redirect_status) |
| : CheckSource(which_directive, url, redirect_status); |
| } |
| |
| bool CSPDirectiveList::AllowImageFromSource( |
| const KURL& url, |
| ResourceRequest::RedirectStatus redirect_status, |
| SecurityViolationReportingPolicy reporting_policy) const { |
| return reporting_policy == SecurityViolationReportingPolicy::kReport |
| ? CheckSourceAndReportViolation( |
| OperativeDirective(img_src_.Get()), url, |
| ContentSecurityPolicy::DirectiveType::kImgSrc, |
| redirect_status) |
| : CheckSource(OperativeDirective(img_src_.Get()), url, |
| redirect_status); |
| } |
| |
| bool CSPDirectiveList::AllowStyleFromSource( |
| const KURL& url, |
| const String& nonce, |
| ResourceRequest::RedirectStatus redirect_status, |
| SecurityViolationReportingPolicy reporting_policy) const { |
| if (IsMatchingNoncePresent(OperativeDirective(style_src_.Get()), nonce)) |
| return true; |
| return reporting_policy == SecurityViolationReportingPolicy::kReport |
| ? CheckSourceAndReportViolation( |
| OperativeDirective(style_src_.Get()), url, |
| ContentSecurityPolicy::DirectiveType::kStyleSrc, |
| redirect_status) |
| : CheckSource(OperativeDirective(style_src_.Get()), url, |
| redirect_status); |
| } |
| |
| bool CSPDirectiveList::AllowFontFromSource( |
| const KURL& url, |
| ResourceRequest::RedirectStatus redirect_status, |
| SecurityViolationReportingPolicy reporting_policy) const { |
| return reporting_policy == SecurityViolationReportingPolicy::kReport |
| ? CheckSourceAndReportViolation( |
| OperativeDirective(font_src_.Get()), url, |
| ContentSecurityPolicy::DirectiveType::kFontSrc, |
| redirect_status) |
| : CheckSource(OperativeDirective(font_src_.Get()), url, |
| redirect_status); |
| } |
| |
| bool CSPDirectiveList::AllowMediaFromSource( |
| const KURL& url, |
| ResourceRequest::RedirectStatus redirect_status, |
| SecurityViolationReportingPolicy reporting_policy) const { |
| return reporting_policy == SecurityViolationReportingPolicy::kReport |
| ? CheckSourceAndReportViolation( |
| OperativeDirective(media_src_.Get()), url, |
| ContentSecurityPolicy::DirectiveType::kMediaSrc, |
| redirect_status) |
| : CheckSource(OperativeDirective(media_src_.Get()), url, |
| redirect_status); |
| } |
| |
| bool CSPDirectiveList::AllowManifestFromSource( |
| const KURL& url, |
| ResourceRequest::RedirectStatus redirect_status, |
| SecurityViolationReportingPolicy reporting_policy) const { |
| return reporting_policy == SecurityViolationReportingPolicy::kReport |
| ? CheckSourceAndReportViolation( |
| OperativeDirective(manifest_src_.Get()), url, |
| ContentSecurityPolicy::DirectiveType::kManifestSrc, |
| redirect_status) |
| : CheckSource(OperativeDirective(manifest_src_.Get()), url, |
| redirect_status); |
| } |
| |
| bool CSPDirectiveList::AllowConnectToSource( |
| const KURL& url, |
| ResourceRequest::RedirectStatus redirect_status, |
| SecurityViolationReportingPolicy reporting_policy) const { |
| return reporting_policy == SecurityViolationReportingPolicy::kReport |
| ? CheckSourceAndReportViolation( |
| OperativeDirective(connect_src_.Get()), url, |
| ContentSecurityPolicy::DirectiveType::kConnectSrc, |
| redirect_status) |
| : CheckSource(OperativeDirective(connect_src_.Get()), url, |
| redirect_status); |
| } |
| |
| bool CSPDirectiveList::AllowFormAction( |
| const KURL& url, |
| ResourceRequest::RedirectStatus redirect_status, |
| SecurityViolationReportingPolicy reporting_policy) const { |
| return reporting_policy == SecurityViolationReportingPolicy::kReport |
| ? CheckSourceAndReportViolation( |
| form_action_.Get(), url, |
| ContentSecurityPolicy::DirectiveType::kFormAction, |
| redirect_status) |
| : CheckSource(form_action_.Get(), url, redirect_status); |
| } |
| |
| bool CSPDirectiveList::AllowBaseURI( |
| const KURL& url, |
| ResourceRequest::RedirectStatus redirect_status, |
| SecurityViolationReportingPolicy reporting_policy) const { |
| bool result = |
| reporting_policy == SecurityViolationReportingPolicy::kReport |
| ? CheckSourceAndReportViolation( |
| base_uri_.Get(), url, |
| ContentSecurityPolicy::DirectiveType::kBaseURI, redirect_status) |
| : CheckSource(base_uri_.Get(), url, redirect_status); |
| |
| if (result && |
| !CheckSource(OperativeDirective(base_uri_.Get()), url, redirect_status)) { |
| UseCounter::Count(policy_->GetDocument(), |
| UseCounter::kBaseWouldBeBlockedByDefaultSrc); |
| } |
| |
| return result; |
| } |
| |
| bool CSPDirectiveList::AllowWorkerFromSource( |
| const KURL& url, |
| ResourceRequest::RedirectStatus redirect_status, |
| SecurityViolationReportingPolicy reporting_policy) const { |
| SourceListDirective* worker_src = OperativeDirective( |
| worker_src_.Get(), OperativeDirective(script_src_.Get())); |
| |
| if (AllowDynamicWorker()) |
| return true; |
| |
| // In CSP2, workers are controlled via 'child-src'. CSP3 moves them to |
| // 'script-src'. In order to avoid breaking sites that allowed workers via |
| // 'child-src' that would have been blocked via 'script-src', we'll |
| // temporarily check whether a worker blocked via 'script-src' would have been |
| // allowed under 'child-src'. If the new 'worker-src' directive is present, |
| // however, we'll assume that the developer knows what they're asking for, and |
| // skip the extra fallback. |
| // |
| // That is, we'll block 'https://example.com/worker' given the policy |
| // "worker-src 'none'", "worker-src 'none'; child-src https://example.com", |
| // but we'll allow it given the policy |
| // "script-src https://not-example.com; child-src https://example.com" |
| // (because 'child-src' allows it) or "child-src https://not-example.com" |
| // (because the absent 'script-src' allows it). |
| // |
| // TODO(mkwst): Remove this once other vendors follow suit. |
| // https://crbug.com/662930 |
| if (!CheckSource(worker_src, url, redirect_status) && !worker_src_ && |
| child_src_ && CheckSource(child_src_, url, redirect_status)) { |
| Deprecation::CountDeprecation( |
| policy_->GetDocument(), |
| UseCounter::kChildSrcAllowedWorkerThatScriptSrcBlocked); |
| return true; |
| } |
| |
| return reporting_policy == SecurityViolationReportingPolicy::kReport |
| ? CheckSourceAndReportViolation( |
| worker_src, url, |
| ContentSecurityPolicy::DirectiveType::kWorkerSrc, |
| redirect_status) |
| : CheckSource(worker_src, url, redirect_status); |
| } |
| |
| bool CSPDirectiveList::AllowAncestors( |
| LocalFrame* frame, |
| const KURL& url, |
| SecurityViolationReportingPolicy reporting_policy) const { |
| return reporting_policy == SecurityViolationReportingPolicy::kReport |
| ? CheckAncestorsAndReportViolation(frame_ancestors_.Get(), frame, |
| url) |
| : CheckAncestors(frame_ancestors_.Get(), frame); |
| } |
| |
| bool CSPDirectiveList::AllowScriptHash( |
| const CSPHashValue& hash_value, |
| ContentSecurityPolicy::InlineType type) const { |
| if (type == ContentSecurityPolicy::InlineType::kAttribute) { |
| if (!policy_->ExperimentalFeaturesEnabled()) |
| return false; |
| if (!CheckHashedAttributes(OperativeDirective(script_src_.Get()))) |
| return false; |
| } |
| return CheckHash(OperativeDirective(script_src_.Get()), hash_value); |
| } |
| |
| bool CSPDirectiveList::AllowStyleHash( |
| const CSPHashValue& hash_value, |
| ContentSecurityPolicy::InlineType type) const { |
| if (type != ContentSecurityPolicy::InlineType::kBlock) |
| return false; |
| return CheckHash(OperativeDirective(style_src_.Get()), hash_value); |
| } |
| |
| bool CSPDirectiveList::AllowDynamic() const { |
| return CheckDynamic(OperativeDirective(script_src_.Get())); |
| } |
| |
| bool CSPDirectiveList::AllowDynamicWorker() const { |
| SourceListDirective* worker_src = OperativeDirective( |
| worker_src_.Get(), OperativeDirective(script_src_.Get())); |
| return CheckDynamic(worker_src); |
| } |
| |
| const String& CSPDirectiveList::PluginTypesText() const { |
| DCHECK(HasPluginTypes()); |
| return plugin_types_->GetText(); |
| } |
| |
| bool CSPDirectiveList::ShouldSendCSPHeader(Resource::Type type) const { |
| // TODO(mkwst): Revisit this once the CORS prefetch issue with the 'CSP' |
| // header is worked out, one way or another: |
| // https://github.com/whatwg/fetch/issues/52 |
| return false; |
| } |
| |
| // policy = directive-list |
| // directive-list = [ directive *( ";" [ directive ] ) ] |
| // |
| void CSPDirectiveList::Parse(const UChar* begin, const UChar* end) { |
| header_ = String(begin, end - begin).StripWhiteSpace(); |
| |
| if (begin == end) |
| return; |
| |
| const UChar* position = begin; |
| while (position < end) { |
| const UChar* directive_begin = position; |
| skipUntil<UChar>(position, end, ';'); |
| |
| String name, value; |
| if (ParseDirective(directive_begin, position, name, value)) { |
| DCHECK(!name.IsEmpty()); |
| AddDirective(name, value); |
| } |
| |
| DCHECK(position == end || *position == ';'); |
| skipExactly<UChar>(position, end, ';'); |
| } |
| } |
| |
| // directive = *WSP [ directive-name [ WSP directive-value ] ] |
| // directive-name = 1*( ALPHA / DIGIT / "-" ) |
| // directive-value = *( WSP / <VCHAR except ";"> ) |
| // |
| bool CSPDirectiveList::ParseDirective(const UChar* begin, |
| const UChar* end, |
| String& name, |
| String& value) { |
| DCHECK(name.IsEmpty()); |
| DCHECK(value.IsEmpty()); |
| |
| const UChar* position = begin; |
| skipWhile<UChar, IsASCIISpace>(position, end); |
| |
| // Empty directive (e.g. ";;;"). Exit early. |
| if (position == end) |
| return false; |
| |
| const UChar* name_begin = position; |
| skipWhile<UChar, IsCSPDirectiveNameCharacter>(position, end); |
| |
| // The directive-name must be non-empty. |
| if (name_begin == position) { |
| skipWhile<UChar, IsNotASCIISpace>(position, end); |
| policy_->ReportUnsupportedDirective( |
| String(name_begin, position - name_begin)); |
| return false; |
| } |
| |
| name = String(name_begin, position - name_begin); |
| |
| if (position == end) |
| return true; |
| |
| if (!skipExactly<UChar, IsASCIISpace>(position, end)) { |
| skipWhile<UChar, IsNotASCIISpace>(position, end); |
| policy_->ReportUnsupportedDirective( |
| String(name_begin, position - name_begin)); |
| return false; |
| } |
| |
| skipWhile<UChar, IsASCIISpace>(position, end); |
| |
| const UChar* value_begin = position; |
| skipWhile<UChar, IsCSPDirectiveValueCharacter>(position, end); |
| |
| if (position != end) { |
| policy_->ReportInvalidDirectiveValueCharacter( |
| name, String(value_begin, end - value_begin)); |
| return false; |
| } |
| |
| // The directive-value may be empty. |
| if (value_begin == position) |
| return true; |
| |
| value = String(value_begin, position - value_begin); |
| return true; |
| } |
| |
| void CSPDirectiveList::ParseRequireSRIFor(const String& name, |
| const String& value) { |
| if (require_sri_for_ != 0) { |
| policy_->ReportDuplicateDirective(name); |
| return; |
| } |
| StringBuilder token_errors; |
| unsigned number_of_token_errors = 0; |
| Vector<UChar> characters; |
| value.AppendTo(characters); |
| |
| const UChar* position = characters.data(); |
| const UChar* end = position + characters.size(); |
| |
| while (position < end) { |
| skipWhile<UChar, IsASCIISpace>(position, end); |
| |
| const UChar* token_begin = position; |
| skipWhile<UChar, IsNotASCIISpace>(position, end); |
| |
| if (token_begin < position) { |
| String token = String(token_begin, position - token_begin); |
| if (EqualIgnoringASCIICase(token, "script")) { |
| require_sri_for_ |= RequireSRIForToken::kScript; |
| } else if (EqualIgnoringASCIICase(token, "style")) { |
| require_sri_for_ |= RequireSRIForToken::kStyle; |
| } else { |
| if (number_of_token_errors) |
| token_errors.Append(", \'"); |
| else |
| token_errors.Append('\''); |
| token_errors.Append(token); |
| token_errors.Append('\''); |
| number_of_token_errors++; |
| } |
| } |
| } |
| |
| if (number_of_token_errors == 0) |
| return; |
| |
| String invalid_tokens_error_message; |
| if (number_of_token_errors > 1) |
| token_errors.Append(" are invalid 'require-sri-for' tokens."); |
| else |
| token_errors.Append(" is an invalid 'require-sri-for' token."); |
| |
| invalid_tokens_error_message = token_errors.ToString(); |
| |
| DCHECK(!invalid_tokens_error_message.IsEmpty()); |
| |
| policy_->ReportInvalidRequireSRIForTokens(invalid_tokens_error_message); |
| } |
| |
| void CSPDirectiveList::ParseReportURI(const String& name, const String& value) { |
| if (!report_endpoints_.IsEmpty()) { |
| policy_->ReportDuplicateDirective(name); |
| return; |
| } |
| |
| // Remove report-uri in meta policies, per |
| // https://www.w3.org/TR/CSP2/#delivery-html-meta-element. |
| if (header_source_ == kContentSecurityPolicyHeaderSourceMeta) { |
| policy_->ReportInvalidDirectiveInMeta(name); |
| return; |
| } |
| |
| Vector<UChar> characters; |
| value.AppendTo(characters); |
| |
| const UChar* position = characters.data(); |
| const UChar* end = position + characters.size(); |
| |
| while (position < end) { |
| skipWhile<UChar, IsASCIISpace>(position, end); |
| |
| const UChar* url_begin = position; |
| skipWhile<UChar, IsNotASCIISpace>(position, end); |
| |
| if (url_begin < position) { |
| String url = String(url_begin, position - url_begin); |
| report_endpoints_.push_back(url); |
| } |
| } |
| } |
| |
| template <class CSPDirectiveType> |
| void CSPDirectiveList::SetCSPDirective(const String& name, |
| const String& value, |
| Member<CSPDirectiveType>& directive) { |
| if (directive) { |
| policy_->ReportDuplicateDirective(name); |
| return; |
| } |
| |
| // Remove frame-ancestors directives in meta policies, per |
| // https://www.w3.org/TR/CSP2/#delivery-html-meta-element. |
| if (header_source_ == kContentSecurityPolicyHeaderSourceMeta && |
| ContentSecurityPolicy::GetDirectiveType(name) == |
| ContentSecurityPolicy::DirectiveType::kFrameAncestors) { |
| policy_->ReportInvalidDirectiveInMeta(name); |
| return; |
| } |
| |
| directive = new CSPDirectiveType(name, value, policy_); |
| } |
| |
| void CSPDirectiveList::ApplySandboxPolicy(const String& name, |
| const String& sandbox_policy) { |
| // Remove sandbox directives in meta policies, per |
| // https://www.w3.org/TR/CSP2/#delivery-html-meta-element. |
| if (header_source_ == kContentSecurityPolicyHeaderSourceMeta) { |
| policy_->ReportInvalidDirectiveInMeta(name); |
| return; |
| } |
| if (IsReportOnly()) { |
| policy_->ReportInvalidInReportOnly(name); |
| return; |
| } |
| if (has_sandbox_policy_) { |
| policy_->ReportDuplicateDirective(name); |
| return; |
| } |
| has_sandbox_policy_ = true; |
| String invalid_tokens; |
| SpaceSplitString policy_tokens = |
| SpaceSplitString(AtomicString(sandbox_policy)); |
| policy_->EnforceSandboxFlags( |
| ParseSandboxPolicy(policy_tokens, invalid_tokens)); |
| if (!invalid_tokens.IsNull()) |
| policy_->ReportInvalidSandboxFlags(invalid_tokens); |
| } |
| |
| void CSPDirectiveList::TreatAsPublicAddress(const String& name, |
| const String& value) { |
| if (IsReportOnly()) { |
| policy_->ReportInvalidInReportOnly(name); |
| return; |
| } |
| if (treat_as_public_address_) { |
| policy_->ReportDuplicateDirective(name); |
| return; |
| } |
| treat_as_public_address_ = true; |
| policy_->TreatAsPublicAddress(); |
| if (!value.IsEmpty()) |
| policy_->ReportValueForEmptyDirective(name, value); |
| } |
| |
| void CSPDirectiveList::EnforceStrictMixedContentChecking(const String& name, |
| const String& value) { |
| if (strict_mixed_content_checking_enforced_) { |
| policy_->ReportDuplicateDirective(name); |
| return; |
| } |
| if (!value.IsEmpty()) |
| policy_->ReportValueForEmptyDirective(name, value); |
| |
| strict_mixed_content_checking_enforced_ = true; |
| |
| if (!IsReportOnly()) |
| policy_->EnforceStrictMixedContentChecking(); |
| } |
| |
| void CSPDirectiveList::EnableInsecureRequestsUpgrade(const String& name, |
| const String& value) { |
| if (IsReportOnly()) { |
| policy_->ReportInvalidInReportOnly(name); |
| return; |
| } |
| if (upgrade_insecure_requests_) { |
| policy_->ReportDuplicateDirective(name); |
| return; |
| } |
| upgrade_insecure_requests_ = true; |
| |
| policy_->UpgradeInsecureRequests(); |
| if (!value.IsEmpty()) |
| policy_->ReportValueForEmptyDirective(name, value); |
| } |
| |
| void CSPDirectiveList::AddDirective(const String& name, const String& value) { |
| DCHECK(!name.IsEmpty()); |
| |
| ContentSecurityPolicy::DirectiveType type = |
| ContentSecurityPolicy::GetDirectiveType(name); |
| if (type == ContentSecurityPolicy::DirectiveType::kDefaultSrc) { |
| SetCSPDirective<SourceListDirective>(name, value, default_src_); |
| // TODO(mkwst) It seems unlikely that developers would use different |
| // algorithms for scripts and styles. We may want to combine the |
| // usesScriptHashAlgorithms() and usesStyleHashAlgorithms. |
| policy_->UsesScriptHashAlgorithms(default_src_->HashAlgorithmsUsed()); |
| policy_->UsesStyleHashAlgorithms(default_src_->HashAlgorithmsUsed()); |
| } else if (type == ContentSecurityPolicy::DirectiveType::kScriptSrc) { |
| SetCSPDirective<SourceListDirective>(name, value, script_src_); |
| policy_->UsesScriptHashAlgorithms(script_src_->HashAlgorithmsUsed()); |
| } else if (type == ContentSecurityPolicy::DirectiveType::kObjectSrc) { |
| SetCSPDirective<SourceListDirective>(name, value, object_src_); |
| } else if (type == ContentSecurityPolicy::DirectiveType::kFrameAncestors) { |
| SetCSPDirective<SourceListDirective>(name, value, frame_ancestors_); |
| } else if (type == ContentSecurityPolicy::DirectiveType::kFrameSrc) { |
| SetCSPDirective<SourceListDirective>(name, value, frame_src_); |
| } else if (type == ContentSecurityPolicy::DirectiveType::kImgSrc) { |
| SetCSPDirective<SourceListDirective>(name, value, img_src_); |
| } else if (type == ContentSecurityPolicy::DirectiveType::kStyleSrc) { |
| SetCSPDirective<SourceListDirective>(name, value, style_src_); |
| policy_->UsesStyleHashAlgorithms(style_src_->HashAlgorithmsUsed()); |
| } else if (type == ContentSecurityPolicy::DirectiveType::kFontSrc) { |
| SetCSPDirective<SourceListDirective>(name, value, font_src_); |
| } else if (type == ContentSecurityPolicy::DirectiveType::kMediaSrc) { |
| SetCSPDirective<SourceListDirective>(name, value, media_src_); |
| } else if (type == ContentSecurityPolicy::DirectiveType::kConnectSrc) { |
| SetCSPDirective<SourceListDirective>(name, value, connect_src_); |
| } else if (type == ContentSecurityPolicy::DirectiveType::kSandbox) { |
| ApplySandboxPolicy(name, value); |
| } else if (type == ContentSecurityPolicy::DirectiveType::kReportURI) { |
| ParseReportURI(name, value); |
| } else if (type == ContentSecurityPolicy::DirectiveType::kBaseURI) { |
| SetCSPDirective<SourceListDirective>(name, value, base_uri_); |
| } else if (type == ContentSecurityPolicy::DirectiveType::kChildSrc) { |
| SetCSPDirective<SourceListDirective>(name, value, child_src_); |
| } else if (type == ContentSecurityPolicy::DirectiveType::kWorkerSrc) { |
| SetCSPDirective<SourceListDirective>(name, value, worker_src_); |
| } else if (type == ContentSecurityPolicy::DirectiveType::kFormAction) { |
| SetCSPDirective<SourceListDirective>(name, value, form_action_); |
| } else if (type == ContentSecurityPolicy::DirectiveType::kPluginTypes) { |
| SetCSPDirective<MediaListDirective>(name, value, plugin_types_); |
| } else if (type == |
| ContentSecurityPolicy::DirectiveType::kUpgradeInsecureRequests) { |
| EnableInsecureRequestsUpgrade(name, value); |
| } else if (type == |
| ContentSecurityPolicy::DirectiveType::kBlockAllMixedContent) { |
| EnforceStrictMixedContentChecking(name, value); |
| } else if (type == ContentSecurityPolicy::DirectiveType::kManifestSrc) { |
| SetCSPDirective<SourceListDirective>(name, value, manifest_src_); |
| } else if (type == |
| ContentSecurityPolicy::DirectiveType::kTreatAsPublicAddress) { |
| TreatAsPublicAddress(name, value); |
| } else if (type == ContentSecurityPolicy::DirectiveType::kRequireSRIFor && |
| policy_->ExperimentalFeaturesEnabled()) { |
| ParseRequireSRIFor(name, value); |
| } else { |
| policy_->ReportUnsupportedDirective(name); |
| } |
| } |
| |
| SourceListDirective* CSPDirectiveList::OperativeDirective( |
| const ContentSecurityPolicy::DirectiveType& type) const { |
| switch (type) { |
| // Directives that do not have a default directive. |
| case ContentSecurityPolicy::DirectiveType::kBaseURI: |
| return base_uri_.Get(); |
| case ContentSecurityPolicy::DirectiveType::kDefaultSrc: |
| return default_src_.Get(); |
| case ContentSecurityPolicy::DirectiveType::kFrameAncestors: |
| return frame_ancestors_.Get(); |
| case ContentSecurityPolicy::DirectiveType::kFormAction: |
| return form_action_.Get(); |
| // Directives that have one default directive. |
| case ContentSecurityPolicy::DirectiveType::kChildSrc: |
| return OperativeDirective(child_src_.Get()); |
| case ContentSecurityPolicy::DirectiveType::kConnectSrc: |
| return OperativeDirective(connect_src_.Get()); |
| case ContentSecurityPolicy::DirectiveType::kFontSrc: |
| return OperativeDirective(font_src_.Get()); |
| case ContentSecurityPolicy::DirectiveType::kImgSrc: |
| return OperativeDirective(img_src_.Get()); |
| case ContentSecurityPolicy::DirectiveType::kManifestSrc: |
| return OperativeDirective(manifest_src_.Get()); |
| case ContentSecurityPolicy::DirectiveType::kMediaSrc: |
| return OperativeDirective(media_src_.Get()); |
| case ContentSecurityPolicy::DirectiveType::kObjectSrc: |
| return OperativeDirective(object_src_.Get()); |
| case ContentSecurityPolicy::DirectiveType::kScriptSrc: |
| return OperativeDirective(script_src_.Get()); |
| case ContentSecurityPolicy::DirectiveType::kStyleSrc: |
| return OperativeDirective(style_src_.Get()); |
| // Directives that default to 'child-src' (which defaults to 'default-src') |
| case ContentSecurityPolicy::DirectiveType::kFrameSrc: |
| return OperativeDirective(frame_src_.Get(), |
| OperativeDirective(child_src_.Get())); |
| // Directives that default to 'script-src' (which defaults to 'default-src') |
| case ContentSecurityPolicy::DirectiveType::kWorkerSrc: |
| return OperativeDirective(worker_src_.Get(), |
| OperativeDirective(script_src_.Get())); |
| default: |
| return nullptr; |
| } |
| } |
| |
| SourceListDirectiveVector CSPDirectiveList::GetSourceVector( |
| const ContentSecurityPolicy::DirectiveType& type, |
| const CSPDirectiveListVector& policies) { |
| SourceListDirectiveVector source_list_directives; |
| for (const auto& policy : policies) { |
| if (SourceListDirective* directive = policy->OperativeDirective(type)) { |
| if (directive->IsNone()) |
| return SourceListDirectiveVector(1, directive); |
| source_list_directives.push_back(directive); |
| } |
| } |
| |
| return source_list_directives; |
| } |
| |
| bool CSPDirectiveList::Subsumes(const CSPDirectiveListVector& other) { |
| // A white-list of directives that we consider for subsumption. |
| // See more about source lists here: |
| // https://w3c.github.io/webappsec-csp/#framework-directive-source-list |
| ContentSecurityPolicy::DirectiveType directives[] = { |
| ContentSecurityPolicy::DirectiveType::kChildSrc, |
| ContentSecurityPolicy::DirectiveType::kConnectSrc, |
| ContentSecurityPolicy::DirectiveType::kFontSrc, |
| ContentSecurityPolicy::DirectiveType::kFrameSrc, |
| ContentSecurityPolicy::DirectiveType::kImgSrc, |
| ContentSecurityPolicy::DirectiveType::kManifestSrc, |
| ContentSecurityPolicy::DirectiveType::kMediaSrc, |
| ContentSecurityPolicy::DirectiveType::kObjectSrc, |
| ContentSecurityPolicy::DirectiveType::kScriptSrc, |
| ContentSecurityPolicy::DirectiveType::kStyleSrc, |
| ContentSecurityPolicy::DirectiveType::kWorkerSrc, |
| ContentSecurityPolicy::DirectiveType::kBaseURI, |
| ContentSecurityPolicy::DirectiveType::kFrameAncestors, |
| ContentSecurityPolicy::DirectiveType::kFormAction}; |
| |
| for (const auto& directive : directives) { |
| // There should only be one SourceListDirective for each directive in |
| // Embedding-CSP. |
| SourceListDirectiveVector required_list = |
| GetSourceVector(directive, CSPDirectiveListVector(1, this)); |
| if (!required_list.size()) |
| continue; |
| SourceListDirective* required = required_list[0]; |
| // Aggregate all serialized source lists of the returned CSP into a vector |
| // based on a directive type, defaulting accordingly (for example, to |
| // `default-src`). |
| SourceListDirectiveVector returned = GetSourceVector(directive, other); |
| // TODO(amalika): Add checks for plugin-types, sandbox, disown-opener, |
| // navigation-to, worker-src. |
| if (!required->Subsumes(returned)) |
| return false; |
| } |
| |
| if (!HasPluginTypes()) |
| return true; |
| |
| HeapVector<Member<MediaListDirective>> plugin_types_other; |
| for (const auto& policy : other) { |
| if (policy->HasPluginTypes()) |
| plugin_types_other.push_back(policy->plugin_types_); |
| } |
| |
| return plugin_types_->Subsumes(plugin_types_other); |
| } |
| |
| WebContentSecurityPolicy CSPDirectiveList::ExposeForNavigationalChecks() const { |
| WebContentSecurityPolicy policy; |
| policy.disposition = static_cast<WebContentSecurityPolicyType>(header_type_); |
| policy.source = static_cast<WebContentSecurityPolicySource>(header_source_); |
| std::vector<WebContentSecurityPolicyDirective> directives; |
| for (const auto& directive : |
| {child_src_, default_src_, form_action_, frame_src_}) { |
| if (directive) { |
| directives.push_back(WebContentSecurityPolicyDirective{ |
| directive->DirectiveName(), |
| directive->ExposeForNavigationalChecks()}); |
| } |
| } |
| policy.directives = directives; |
| policy.report_endpoints = ReportEndpoints(); |
| policy.header = Header(); |
| |
| return policy; |
| } |
| |
| DEFINE_TRACE(CSPDirectiveList) { |
| visitor->Trace(policy_); |
| visitor->Trace(plugin_types_); |
| visitor->Trace(base_uri_); |
| visitor->Trace(child_src_); |
| visitor->Trace(connect_src_); |
| visitor->Trace(default_src_); |
| visitor->Trace(font_src_); |
| visitor->Trace(form_action_); |
| visitor->Trace(frame_ancestors_); |
| visitor->Trace(frame_src_); |
| visitor->Trace(img_src_); |
| visitor->Trace(media_src_); |
| visitor->Trace(manifest_src_); |
| visitor->Trace(object_src_); |
| visitor->Trace(script_src_); |
| visitor->Trace(style_src_); |
| visitor->Trace(worker_src_); |
| } |
| |
| } // namespace blink |