| /* |
| * Copyright (C) 2011 Google, Inc. All rights reserved. |
| * |
| * Redistribution and use in source and binary forms, with or without |
| * modification, are permitted provided that the following conditions |
| * are met: |
| * 1. Redistributions of source code must retain the above copyright |
| * notice, this list of conditions and the following disclaimer. |
| * 2. Redistributions in binary form must reproduce the above copyright |
| * notice, this list of conditions and the following disclaimer in the |
| * documentation and/or other materials provided with the distribution. |
| * |
| * THIS SOFTWARE IS PROVIDED BY GOOGLE INC. ``AS IS'' AND ANY |
| * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE |
| * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR |
| * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE COMPUTER, INC. OR |
| * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, |
| * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, |
| * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR |
| * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY |
| * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
| * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE |
| * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
| */ |
| |
| #include "core/frame/csp/ContentSecurityPolicy.h" |
| |
| #include <memory> |
| #include "bindings/core/v8/ScriptController.h" |
| #include "core/dom/DOMStringList.h" |
| #include "core/dom/Document.h" |
| #include "core/dom/Element.h" |
| #include "core/dom/SandboxFlags.h" |
| #include "core/dom/TaskRunnerHelper.h" |
| #include "core/events/EventQueue.h" |
| #include "core/events/SecurityPolicyViolationEvent.h" |
| #include "core/frame/FrameClient.h" |
| #include "core/frame/LocalDOMWindow.h" |
| #include "core/frame/LocalFrame.h" |
| #include "core/frame/LocalFrameClient.h" |
| #include "core/frame/UseCounter.h" |
| #include "core/frame/csp/CSPDirectiveList.h" |
| #include "core/frame/csp/CSPSource.h" |
| #include "core/frame/csp/MediaListDirective.h" |
| #include "core/frame/csp/SourceListDirective.h" |
| #include "core/html/HTMLScriptElement.h" |
| #include "core/inspector/ConsoleMessage.h" |
| #include "core/loader/DocumentLoader.h" |
| #include "core/loader/PingLoader.h" |
| #include "core/probe/CoreProbes.h" |
| #include "core/workers/WorkerGlobalScope.h" |
| #include "platform/RuntimeEnabledFeatures.h" |
| #include "platform/json/JSONValues.h" |
| #include "platform/loader/fetch/IntegrityMetadata.h" |
| #include "platform/loader/fetch/ResourceRequest.h" |
| #include "platform/loader/fetch/ResourceResponse.h" |
| #include "platform/network/ContentSecurityPolicyParsers.h" |
| #include "platform/network/ContentSecurityPolicyResponseHeaders.h" |
| #include "platform/network/EncodedFormData.h" |
| #include "platform/weborigin/KURL.h" |
| #include "platform/weborigin/KnownPorts.h" |
| #include "platform/weborigin/SecurityOrigin.h" |
| #include "public/platform/Platform.h" |
| #include "public/platform/WebAddressSpace.h" |
| #include "public/platform/WebURLRequest.h" |
| #include "wtf/NotFound.h" |
| #include "wtf/PtrUtil.h" |
| #include "wtf/StringHasher.h" |
| #include "wtf/text/ParsingUtilities.h" |
| #include "wtf/text/StringBuilder.h" |
| #include "wtf/text/StringUTF8Adaptor.h" |
| |
| namespace blink { |
| |
| bool ContentSecurityPolicy::isNonceableElement(const Element* element) { |
| if (RuntimeEnabledFeatures::hideNonceContentAttributeEnabled() && |
| isHTMLScriptElement(element)) { |
| if (toHTMLScriptElement(element)->nonce().isNull()) |
| return false; |
| } else if (!element->fastHasAttribute(HTMLNames::nonceAttr)) { |
| return false; |
| } |
| |
| bool nonceable = true; |
| |
| // To prevent an attacker from hijacking an existing nonce via a dangling |
| // markup injection, we walk through the attributes of each nonced script |
| // element: if their names or values contain "<script" or "<style", we won't |
| // apply the nonce when loading script. |
| // |
| // See http://blog.innerht.ml/csp-2015/#danglingmarkupinjection for an example |
| // of the kind of attack this is aimed at mitigating. |
| static const char scriptString[] = "<script"; |
| static const char styleString[] = "<style"; |
| for (const Attribute& attr : element->attributes()) { |
| AtomicString name = attr.localName().lowerASCII(); |
| AtomicString value = attr.value().lowerASCII(); |
| if (name.find(scriptString) != WTF::kNotFound || |
| name.find(styleString) != WTF::kNotFound || |
| value.find(scriptString) != WTF::kNotFound || |
| value.find(styleString) != WTF::kNotFound) { |
| nonceable = false; |
| break; |
| } |
| } |
| |
| UseCounter::count( |
| element->document(), |
| nonceable ? UseCounter::CleanScriptElementWithNonce |
| : UseCounter::PotentiallyInjectedScriptElementWithNonce); |
| |
| // This behavior is locked behind the experimental flag for the moment; if we |
| // decide to ship it, drop this check. https://crbug.com/639293 |
| return !RuntimeEnabledFeatures:: |
| experimentalContentSecurityPolicyFeaturesEnabled() || |
| nonceable; |
| } |
| |
| static UseCounter::Feature getUseCounterType( |
| ContentSecurityPolicyHeaderType type) { |
| switch (type) { |
| case ContentSecurityPolicyHeaderTypeEnforce: |
| return UseCounter::ContentSecurityPolicy; |
| case ContentSecurityPolicyHeaderTypeReport: |
| return UseCounter::ContentSecurityPolicyReportOnly; |
| } |
| ASSERT_NOT_REACHED(); |
| return UseCounter::NumberOfFeatures; |
| } |
| |
| ContentSecurityPolicy::ContentSecurityPolicy() |
| : m_executionContext(nullptr), |
| m_overrideInlineStyleAllowed(false), |
| m_scriptHashAlgorithmsUsed(ContentSecurityPolicyHashAlgorithmNone), |
| m_styleHashAlgorithmsUsed(ContentSecurityPolicyHashAlgorithmNone), |
| m_sandboxMask(0), |
| m_treatAsPublicAddress(false), |
| m_insecureRequestPolicy(kLeaveInsecureRequestsAlone) {} |
| |
| void ContentSecurityPolicy::bindToExecutionContext( |
| ExecutionContext* executionContext) { |
| m_executionContext = executionContext; |
| applyPolicySideEffectsToExecutionContext(); |
| } |
| |
| void ContentSecurityPolicy::setupSelf(const SecurityOrigin& securityOrigin) { |
| // Ensure that 'self' processes correctly. |
| m_selfProtocol = securityOrigin.protocol(); |
| m_selfSource = new CSPSource(this, m_selfProtocol, securityOrigin.host(), |
| securityOrigin.port(), String(), |
| CSPSource::NoWildcard, CSPSource::NoWildcard); |
| } |
| |
| void ContentSecurityPolicy::applyPolicySideEffectsToExecutionContext() { |
| DCHECK(m_executionContext && |
| m_executionContext->securityContext().getSecurityOrigin()); |
| |
| setupSelf(*m_executionContext->securityContext().getSecurityOrigin()); |
| |
| // If we're in a Document, set mixed content checking and sandbox |
| // flags, then dump all the parsing error messages, then poke at histograms. |
| if (Document* document = this->document()) { |
| if (m_sandboxMask != SandboxNone) { |
| UseCounter::count(document, UseCounter::SandboxViaCSP); |
| document->enforceSandboxFlags(m_sandboxMask); |
| } |
| if (m_treatAsPublicAddress) |
| document->setAddressSpace(WebAddressSpacePublic); |
| |
| document->enforceInsecureRequestPolicy(m_insecureRequestPolicy); |
| if (m_insecureRequestPolicy & kUpgradeInsecureRequests) { |
| UseCounter::count(document, UseCounter::UpgradeInsecureRequestsEnabled); |
| if (!document->url().host().isEmpty()) |
| document->addInsecureNavigationUpgrade( |
| document->url().host().impl()->hash()); |
| } |
| |
| for (const auto& consoleMessage : m_consoleMessages) |
| m_executionContext->addConsoleMessage(consoleMessage); |
| m_consoleMessages.clear(); |
| |
| for (const auto& policy : m_policies) { |
| UseCounter::count(*document, getUseCounterType(policy->headerType())); |
| if (policy->allowDynamic()) |
| UseCounter::count(*document, UseCounter::CSPWithStrictDynamic); |
| } |
| } |
| |
| // We disable 'eval()' even in the case of report-only policies, and rely on |
| // the check in the V8Initializer::codeGenerationCheckCallbackInMainThread |
| // callback to determine whether the call should execute or not. |
| if (!m_disableEvalErrorMessage.isNull()) |
| m_executionContext->disableEval(m_disableEvalErrorMessage); |
| } |
| |
| ContentSecurityPolicy::~ContentSecurityPolicy() {} |
| |
| DEFINE_TRACE(ContentSecurityPolicy) { |
| visitor->trace(m_executionContext); |
| visitor->trace(m_policies); |
| visitor->trace(m_consoleMessages); |
| visitor->trace(m_selfSource); |
| } |
| |
| Document* ContentSecurityPolicy::document() const { |
| return (m_executionContext && m_executionContext->isDocument()) |
| ? toDocument(m_executionContext) |
| : nullptr; |
| } |
| |
| void ContentSecurityPolicy::copyStateFrom(const ContentSecurityPolicy* other) { |
| ASSERT(m_policies.isEmpty()); |
| for (const auto& policy : other->m_policies) |
| addAndReportPolicyFromHeaderValue(policy->header(), policy->headerType(), |
| policy->headerSource()); |
| } |
| |
| void ContentSecurityPolicy::copyPluginTypesFrom( |
| const ContentSecurityPolicy* other) { |
| for (const auto& policy : other->m_policies) { |
| if (policy->hasPluginTypes()) { |
| addAndReportPolicyFromHeaderValue(policy->pluginTypesText(), |
| policy->headerType(), |
| policy->headerSource()); |
| } |
| } |
| } |
| |
| void ContentSecurityPolicy::didReceiveHeaders( |
| const ContentSecurityPolicyResponseHeaders& headers) { |
| if (!headers.contentSecurityPolicy().isEmpty()) |
| addAndReportPolicyFromHeaderValue(headers.contentSecurityPolicy(), |
| ContentSecurityPolicyHeaderTypeEnforce, |
| ContentSecurityPolicyHeaderSourceHTTP); |
| if (!headers.contentSecurityPolicyReportOnly().isEmpty()) |
| addAndReportPolicyFromHeaderValue(headers.contentSecurityPolicyReportOnly(), |
| ContentSecurityPolicyHeaderTypeReport, |
| ContentSecurityPolicyHeaderSourceHTTP); |
| } |
| |
| void ContentSecurityPolicy::didReceiveHeader( |
| const String& header, |
| ContentSecurityPolicyHeaderType type, |
| ContentSecurityPolicyHeaderSource source) { |
| addAndReportPolicyFromHeaderValue(header, type, source); |
| |
| // This might be called after we've been bound to an execution context. For |
| // example, a <meta> element might be injected after page load. |
| if (m_executionContext) |
| applyPolicySideEffectsToExecutionContext(); |
| } |
| |
| bool ContentSecurityPolicy::shouldEnforceEmbeddersPolicy( |
| const ResourceResponse& response, |
| SecurityOrigin* parentOrigin) { |
| if (response.url().isEmpty() || response.url().protocolIsAbout() || |
| response.url().protocolIsData() || response.url().protocolIs("blob") || |
| response.url().protocolIs("filesystem")) { |
| return true; |
| } |
| |
| if (parentOrigin->canAccess(SecurityOrigin::create(response.url()).get())) |
| return true; |
| |
| String header = response.httpHeaderField(HTTPNames::Allow_CSP_From); |
| header = header.stripWhiteSpace(); |
| if (header == "*") |
| return true; |
| if (RefPtr<SecurityOrigin> childOrigin = |
| SecurityOrigin::createFromString(header)) { |
| return parentOrigin->canAccess(childOrigin.get()); |
| } |
| |
| return false; |
| } |
| |
| void ContentSecurityPolicy::addPolicyFromHeaderValue( |
| const String& header, |
| ContentSecurityPolicyHeaderType type, |
| ContentSecurityPolicyHeaderSource source) { |
| // If this is a report-only header inside a <meta> element, bail out. |
| if (source == ContentSecurityPolicyHeaderSourceMeta && |
| type == ContentSecurityPolicyHeaderTypeReport) { |
| reportReportOnlyInMeta(header); |
| return; |
| } |
| |
| Vector<UChar> characters; |
| header.appendTo(characters); |
| |
| const UChar* begin = characters.data(); |
| const UChar* end = begin + characters.size(); |
| |
| // RFC2616, section 4.2 specifies that headers appearing multiple times can |
| // be combined with a comma. Walk the header string, and parse each comma |
| // separated chunk as a separate header. |
| const UChar* position = begin; |
| while (position < end) { |
| skipUntil<UChar>(position, end, ','); |
| |
| // header1,header2 OR header1 |
| // ^ ^ |
| Member<CSPDirectiveList> policy = |
| CSPDirectiveList::create(this, begin, position, type, source); |
| |
| if (!policy->allowEval( |
| 0, SecurityViolationReportingPolicy::SuppressReporting) && |
| m_disableEvalErrorMessage.isNull()) |
| m_disableEvalErrorMessage = policy->evalDisabledErrorMessage(); |
| |
| m_policies.push_back(policy.release()); |
| |
| // Skip the comma, and begin the next header from the current position. |
| ASSERT(position == end || *position == ','); |
| skipExactly<UChar>(position, end, ','); |
| begin = position; |
| } |
| } |
| |
| void ContentSecurityPolicy::reportAccumulatedHeaders( |
| LocalFrameClient* client) const { |
| // Notify the embedder about headers that have accumulated before the |
| // navigation got committed. See comments in |
| // addAndReportPolicyFromHeaderValue for more details and context. |
| DCHECK(client); |
| WebVector<WebContentSecurityPolicy> policies(m_policies.size()); |
| for (size_t i = 0; i < m_policies.size(); ++i) |
| policies[i] = m_policies[i]->exposeForNavigationalChecks(); |
| client->didAddContentSecurityPolicies(policies); |
| } |
| |
| void ContentSecurityPolicy::addAndReportPolicyFromHeaderValue( |
| const String& header, |
| ContentSecurityPolicyHeaderType type, |
| ContentSecurityPolicyHeaderSource source) { |
| size_t previousPolicyCount = m_policies.size(); |
| addPolicyFromHeaderValue(header, type, source); |
| if (document() && document()->frame()) { |
| // Notify about the new header, so that it can be reported back to the |
| // browser process. This is needed in order to: |
| // 1) replicate CSP directives (i.e. frame-src) to OOPIFs (only for now / |
| // short-term). |
| // 2) enforce CSP in the browser process (long-term - see |
| // https://crbug.com/376522). |
| // TODO(arthursonzogni): policies are actually replicated (1) and some of |
| // them are enforced on the browser process (2). Stop doing (1) when (2) is |
| // finished. |
| WebVector<WebContentSecurityPolicy> policies(m_policies.size() - |
| previousPolicyCount); |
| for (size_t i = previousPolicyCount; i < m_policies.size(); ++i) { |
| policies[i - previousPolicyCount] = |
| m_policies[i]->exposeForNavigationalChecks(); |
| } |
| document()->frame()->client()->didAddContentSecurityPolicies(policies); |
| } |
| } |
| |
| void ContentSecurityPolicy::setOverrideAllowInlineStyle(bool value) { |
| m_overrideInlineStyleAllowed = value; |
| } |
| |
| void ContentSecurityPolicy::setOverrideURLForSelf(const KURL& url) { |
| // Create a temporary CSPSource so that 'self' expressions can be resolved |
| // before we bind to an execution context (for 'frame-ancestor' resolution, |
| // for example). This CSPSource will be overwritten when we bind this object |
| // to an execution context. |
| RefPtr<SecurityOrigin> origin = SecurityOrigin::create(url); |
| m_selfProtocol = origin->protocol(); |
| m_selfSource = |
| new CSPSource(this, m_selfProtocol, origin->host(), origin->port(), |
| String(), CSPSource::NoWildcard, CSPSource::NoWildcard); |
| } |
| |
| std::unique_ptr<Vector<CSPHeaderAndType>> ContentSecurityPolicy::headers() |
| const { |
| std::unique_ptr<Vector<CSPHeaderAndType>> headers = |
| WTF::wrapUnique(new Vector<CSPHeaderAndType>); |
| for (const auto& policy : m_policies) { |
| CSPHeaderAndType headerAndType(policy->header(), policy->headerType()); |
| headers->push_back(headerAndType); |
| } |
| return headers; |
| } |
| |
| template <bool (CSPDirectiveList::*allowed)( |
| ScriptState* scriptState, |
| SecurityViolationReportingPolicy, |
| ContentSecurityPolicy::ExceptionStatus) const> |
| bool isAllowedByAll(const CSPDirectiveListVector& policies, |
| ScriptState* scriptState, |
| SecurityViolationReportingPolicy reportingPolicy, |
| ContentSecurityPolicy::ExceptionStatus exceptionStatus) { |
| bool isAllowed = true; |
| for (const auto& policy : policies) { |
| isAllowed &= |
| (policy.get()->*allowed)(scriptState, reportingPolicy, exceptionStatus); |
| } |
| return isAllowed; |
| } |
| |
| template <bool (CSPDirectiveList::*allowed)(Element*, |
| const String&, |
| const String&, |
| const WTF::OrdinalNumber&, |
| SecurityViolationReportingPolicy) |
| const> |
| bool isAllowedByAll(const CSPDirectiveListVector& policies, |
| Element* element, |
| const String& source, |
| const String& contextURL, |
| const WTF::OrdinalNumber& contextLine, |
| SecurityViolationReportingPolicy reportingPolicy) { |
| bool isAllowed = true; |
| for (const auto& policy : policies) { |
| isAllowed &= (policy.get()->*allowed)(element, source, contextURL, |
| contextLine, reportingPolicy); |
| } |
| return isAllowed; |
| } |
| |
| template <bool (CSPDirectiveList::*allowed)(Element*, |
| const String&, |
| const String&, |
| const WTF::OrdinalNumber&, |
| SecurityViolationReportingPolicy, |
| const String& content) const> |
| bool isAllowedByAll(const CSPDirectiveListVector& policies, |
| Element* element, |
| const String& contextURL, |
| const String& nonce, |
| const WTF::OrdinalNumber& contextLine, |
| SecurityViolationReportingPolicy reportingPolicy, |
| const String& content) { |
| bool isAllowed = true; |
| for (const auto& policy : policies) { |
| isAllowed &= (policy.get()->*allowed)( |
| element, contextURL, nonce, contextLine, reportingPolicy, content); |
| } |
| return isAllowed; |
| } |
| |
| template <bool (CSPDirectiveList::*allowed)(const CSPHashValue&, |
| ContentSecurityPolicy::InlineType) |
| const> |
| bool isAllowedByAll(const CSPDirectiveListVector& policies, |
| const CSPHashValue& hashValue, |
| ContentSecurityPolicy::InlineType type) { |
| bool isAllowed = true; |
| for (const auto& policy : policies) |
| isAllowed &= (policy.get()->*allowed)(hashValue, type); |
| return isAllowed; |
| } |
| |
| template <bool (CSPDirectiveList::*allowFromURL)( |
| const KURL&, |
| RedirectStatus, |
| SecurityViolationReportingPolicy) const> |
| bool isAllowedByAll(const CSPDirectiveListVector& policies, |
| const KURL& url, |
| RedirectStatus redirectStatus, |
| SecurityViolationReportingPolicy reportingPolicy) { |
| if (ContentSecurityPolicy::shouldBypassContentSecurityPolicy(url)) |
| return true; |
| |
| bool isAllowed = true; |
| for (const auto& policy : policies) { |
| isAllowed &= |
| (policy.get()->*allowFromURL)(url, redirectStatus, reportingPolicy); |
| } |
| |
| return isAllowed; |
| } |
| |
| template <bool (CSPDirectiveList::*allowFromURLWithNonce)( |
| const KURL&, |
| const String& nonce, |
| RedirectStatus, |
| SecurityViolationReportingPolicy) const> |
| bool isAllowedByAll(const CSPDirectiveListVector& policies, |
| const KURL& url, |
| const String& nonce, |
| RedirectStatus redirectStatus, |
| SecurityViolationReportingPolicy reportingPolicy) { |
| if (ContentSecurityPolicy::shouldBypassContentSecurityPolicy(url)) |
| return true; |
| |
| bool isAllowed = true; |
| for (const auto& policy : policies) { |
| isAllowed &= (policy.get()->*allowFromURLWithNonce)( |
| url, nonce, redirectStatus, reportingPolicy); |
| } |
| return isAllowed; |
| } |
| |
| template <bool (CSPDirectiveList::*allowFromURLWithNonceAndParser)( |
| const KURL&, |
| const String& nonce, |
| const IntegrityMetadataSet& hashes, |
| ParserDisposition parserDisposition, |
| RedirectStatus, |
| SecurityViolationReportingPolicy) const> |
| bool isAllowedByAll(const CSPDirectiveListVector& policies, |
| const KURL& url, |
| const String& nonce, |
| const IntegrityMetadataSet& hashes, |
| ParserDisposition parserDisposition, |
| RedirectStatus redirectStatus, |
| SecurityViolationReportingPolicy reportingPolicy) { |
| if (ContentSecurityPolicy::shouldBypassContentSecurityPolicy(url)) { |
| // If we're running experimental features, bypass CSP only for |
| // non-parser-inserted resources whose scheme otherwise bypasses CSP. If |
| // we're not running experimental features, bypass CSP for all resources |
| // regardless of parser state. Once we have more data via the |
| // 'ScriptWithCSPBypassingScheme*' metrics, make a decision about what |
| // behavior to ship. https://crbug.com/653521 |
| if (parserDisposition == NotParserInserted || |
| !RuntimeEnabledFeatures:: |
| experimentalContentSecurityPolicyFeaturesEnabled()) { |
| return true; |
| } |
| } |
| |
| bool isAllowed = true; |
| for (const auto& policy : policies) { |
| isAllowed &= (policy.get()->*allowFromURLWithNonceAndParser)( |
| url, nonce, hashes, parserDisposition, redirectStatus, reportingPolicy); |
| } |
| return isAllowed; |
| } |
| |
| template <bool (CSPDirectiveList::*allowed)(LocalFrame*, |
| const KURL&, |
| SecurityViolationReportingPolicy) |
| const> |
| bool isAllowedByAll(const CSPDirectiveListVector& policies, |
| LocalFrame* frame, |
| const KURL& url, |
| SecurityViolationReportingPolicy reportingPolicy) { |
| bool isAllowed = true; |
| for (const auto& policy : policies) |
| isAllowed &= (policy.get()->*allowed)(frame, url, reportingPolicy); |
| return isAllowed; |
| } |
| |
| template <bool (CSPDirectiveList::*allowed)(const CSPHashValue&, |
| ContentSecurityPolicy::InlineType) |
| const> |
| bool checkDigest(const String& source, |
| ContentSecurityPolicy::InlineType type, |
| uint8_t hashAlgorithmsUsed, |
| const CSPDirectiveListVector& policies) { |
| // Any additions or subtractions from this struct should also modify the |
| // respective entries in the kSupportedPrefixes array in |
| // SourceListDirective::parseHash(). |
| static const struct { |
| ContentSecurityPolicyHashAlgorithm cspHashAlgorithm; |
| HashAlgorithm algorithm; |
| } kAlgorithmMap[] = { |
| {ContentSecurityPolicyHashAlgorithmSha1, HashAlgorithmSha1}, |
| {ContentSecurityPolicyHashAlgorithmSha256, HashAlgorithmSha256}, |
| {ContentSecurityPolicyHashAlgorithmSha384, HashAlgorithmSha384}, |
| {ContentSecurityPolicyHashAlgorithmSha512, HashAlgorithmSha512}}; |
| |
| // Only bother normalizing the source/computing digests if there are any |
| // checks to be done. |
| if (hashAlgorithmsUsed == ContentSecurityPolicyHashAlgorithmNone) |
| return false; |
| |
| StringUTF8Adaptor utf8Source(source); |
| |
| for (const auto& algorithmMap : kAlgorithmMap) { |
| DigestValue digest; |
| if (algorithmMap.cspHashAlgorithm & hashAlgorithmsUsed) { |
| bool digestSuccess = |
| computeDigest(algorithmMap.algorithm, utf8Source.data(), |
| utf8Source.length(), digest); |
| if (digestSuccess && |
| isAllowedByAll<allowed>( |
| policies, CSPHashValue(algorithmMap.cspHashAlgorithm, digest), |
| type)) |
| return true; |
| } |
| } |
| |
| return false; |
| } |
| |
| bool ContentSecurityPolicy::allowJavaScriptURLs( |
| Element* element, |
| const String& source, |
| const String& contextURL, |
| const WTF::OrdinalNumber& contextLine, |
| SecurityViolationReportingPolicy reportingPolicy) const { |
| return isAllowedByAll<&CSPDirectiveList::allowJavaScriptURLs>( |
| m_policies, element, source, contextURL, contextLine, reportingPolicy); |
| } |
| |
| bool ContentSecurityPolicy::allowInlineEventHandler( |
| Element* element, |
| const String& source, |
| const String& contextURL, |
| const WTF::OrdinalNumber& contextLine, |
| SecurityViolationReportingPolicy reportingPolicy) const { |
| // Inline event handlers may be whitelisted by hash, if |
| // 'unsafe-hash-attributes' is present in a policy. Check against the digest |
| // of the |source| first before proceeding on to checking whether inline |
| // script is allowed. |
| if (checkDigest<&CSPDirectiveList::allowScriptHash>( |
| source, InlineType::Attribute, m_scriptHashAlgorithmsUsed, |
| m_policies)) |
| return true; |
| return isAllowedByAll<&CSPDirectiveList::allowInlineEventHandlers>( |
| m_policies, element, source, contextURL, contextLine, reportingPolicy); |
| } |
| |
| bool ContentSecurityPolicy::allowInlineScript( |
| Element* element, |
| const String& contextURL, |
| const String& nonce, |
| const WTF::OrdinalNumber& contextLine, |
| const String& scriptContent, |
| SecurityViolationReportingPolicy reportingPolicy) const { |
| DCHECK(element); |
| return isAllowedByAll<&CSPDirectiveList::allowInlineScript>( |
| m_policies, element, contextURL, nonce, contextLine, reportingPolicy, |
| scriptContent); |
| } |
| |
| bool ContentSecurityPolicy::allowInlineStyle( |
| Element* element, |
| const String& contextURL, |
| const String& nonce, |
| const WTF::OrdinalNumber& contextLine, |
| const String& styleContent, |
| SecurityViolationReportingPolicy reportingPolicy) const { |
| DCHECK(element); |
| if (m_overrideInlineStyleAllowed) |
| return true; |
| return isAllowedByAll<&CSPDirectiveList::allowInlineStyle>( |
| m_policies, element, contextURL, nonce, contextLine, reportingPolicy, |
| styleContent); |
| } |
| |
| bool ContentSecurityPolicy::allowEval( |
| ScriptState* scriptState, |
| SecurityViolationReportingPolicy reportingPolicy, |
| ContentSecurityPolicy::ExceptionStatus exceptionStatus) const { |
| return isAllowedByAll<&CSPDirectiveList::allowEval>( |
| m_policies, scriptState, reportingPolicy, exceptionStatus); |
| } |
| |
| String ContentSecurityPolicy::evalDisabledErrorMessage() const { |
| for (const auto& policy : m_policies) { |
| if (!policy->allowEval(0, |
| SecurityViolationReportingPolicy::SuppressReporting)) |
| return policy->evalDisabledErrorMessage(); |
| } |
| return String(); |
| } |
| |
| bool ContentSecurityPolicy::allowPluginType( |
| const String& type, |
| const String& typeAttribute, |
| const KURL& url, |
| SecurityViolationReportingPolicy reportingPolicy) const { |
| for (const auto& policy : m_policies) { |
| if (!policy->allowPluginType(type, typeAttribute, url, reportingPolicy)) |
| return false; |
| } |
| return true; |
| } |
| |
| bool ContentSecurityPolicy::allowPluginTypeForDocument( |
| const Document& document, |
| const String& type, |
| const String& typeAttribute, |
| const KURL& url, |
| SecurityViolationReportingPolicy reportingPolicy) const { |
| if (document.contentSecurityPolicy() && |
| !document.contentSecurityPolicy()->allowPluginType(type, typeAttribute, |
| url)) |
| return false; |
| |
| // CSP says that a plugin document in a nested browsing context should |
| // inherit the plugin-types of its parent. |
| // |
| // FIXME: The plugin-types directive should be pushed down into the |
| // current document instead of reaching up to the parent for it here. |
| LocalFrame* frame = document.frame(); |
| if (frame && frame->tree().parent() && document.isPluginDocument()) { |
| ContentSecurityPolicy* parentCSP = |
| frame->tree().parent()->securityContext()->contentSecurityPolicy(); |
| if (parentCSP && !parentCSP->allowPluginType(type, typeAttribute, url)) |
| return false; |
| } |
| |
| return true; |
| } |
| |
| bool ContentSecurityPolicy::allowScriptFromSource( |
| const KURL& url, |
| const String& nonce, |
| const IntegrityMetadataSet& hashes, |
| ParserDisposition parserDisposition, |
| RedirectStatus redirectStatus, |
| SecurityViolationReportingPolicy reportingPolicy) const { |
| if (shouldBypassContentSecurityPolicy(url)) { |
| UseCounter::count( |
| document(), |
| parserDisposition == ParserInserted |
| ? UseCounter::ScriptWithCSPBypassingSchemeParserInserted |
| : UseCounter::ScriptWithCSPBypassingSchemeNotParserInserted); |
| } |
| return isAllowedByAll<&CSPDirectiveList::allowScriptFromSource>( |
| m_policies, url, nonce, hashes, parserDisposition, redirectStatus, |
| reportingPolicy); |
| } |
| |
| bool ContentSecurityPolicy::allowScriptWithHash(const String& source, |
| InlineType type) const { |
| return checkDigest<&CSPDirectiveList::allowScriptHash>( |
| source, type, m_scriptHashAlgorithmsUsed, m_policies); |
| } |
| |
| bool ContentSecurityPolicy::allowStyleWithHash(const String& source, |
| InlineType type) const { |
| return checkDigest<&CSPDirectiveList::allowStyleHash>( |
| source, type, m_styleHashAlgorithmsUsed, m_policies); |
| } |
| |
| bool ContentSecurityPolicy::allowRequestWithoutIntegrity( |
| WebURLRequest::RequestContext context, |
| const KURL& url, |
| RedirectStatus redirectStatus, |
| SecurityViolationReportingPolicy reportingPolicy) const { |
| for (const auto& policy : m_policies) { |
| if (!policy->allowRequestWithoutIntegrity(context, url, redirectStatus, |
| reportingPolicy)) |
| return false; |
| } |
| return true; |
| } |
| |
| bool ContentSecurityPolicy::allowRequest( |
| WebURLRequest::RequestContext context, |
| const KURL& url, |
| const String& nonce, |
| const IntegrityMetadataSet& integrityMetadata, |
| ParserDisposition parserDisposition, |
| RedirectStatus redirectStatus, |
| SecurityViolationReportingPolicy reportingPolicy) const { |
| if (integrityMetadata.isEmpty() && |
| !allowRequestWithoutIntegrity(context, url, redirectStatus, |
| reportingPolicy)) |
| return false; |
| |
| switch (context) { |
| case WebURLRequest::RequestContextAudio: |
| case WebURLRequest::RequestContextTrack: |
| case WebURLRequest::RequestContextVideo: |
| return allowMediaFromSource(url, redirectStatus, reportingPolicy); |
| case WebURLRequest::RequestContextBeacon: |
| case WebURLRequest::RequestContextEventSource: |
| case WebURLRequest::RequestContextFetch: |
| case WebURLRequest::RequestContextXMLHttpRequest: |
| case WebURLRequest::RequestContextSubresource: |
| return allowConnectToSource(url, redirectStatus, reportingPolicy); |
| case WebURLRequest::RequestContextEmbed: |
| case WebURLRequest::RequestContextObject: |
| return allowObjectFromSource(url, redirectStatus, reportingPolicy); |
| case WebURLRequest::RequestContextFavicon: |
| case WebURLRequest::RequestContextImage: |
| case WebURLRequest::RequestContextImageSet: |
| return allowImageFromSource(url, redirectStatus, reportingPolicy); |
| case WebURLRequest::RequestContextFont: |
| return allowFontFromSource(url, redirectStatus, reportingPolicy); |
| case WebURLRequest::RequestContextForm: |
| return allowFormAction(url, redirectStatus, reportingPolicy); |
| case WebURLRequest::RequestContextFrame: |
| case WebURLRequest::RequestContextIframe: |
| return allowFrameFromSource(url, redirectStatus, reportingPolicy); |
| case WebURLRequest::RequestContextImport: |
| case WebURLRequest::RequestContextScript: |
| case WebURLRequest::RequestContextXSLT: |
| return allowScriptFromSource(url, nonce, integrityMetadata, |
| parserDisposition, redirectStatus, |
| reportingPolicy); |
| case WebURLRequest::RequestContextManifest: |
| return allowManifestFromSource(url, redirectStatus, reportingPolicy); |
| case WebURLRequest::RequestContextServiceWorker: |
| case WebURLRequest::RequestContextSharedWorker: |
| case WebURLRequest::RequestContextWorker: |
| return allowWorkerContextFromSource(url, redirectStatus, reportingPolicy); |
| case WebURLRequest::RequestContextStyle: |
| return allowStyleFromSource(url, nonce, redirectStatus, reportingPolicy); |
| case WebURLRequest::RequestContextCSPReport: |
| case WebURLRequest::RequestContextDownload: |
| case WebURLRequest::RequestContextHyperlink: |
| case WebURLRequest::RequestContextInternal: |
| case WebURLRequest::RequestContextLocation: |
| case WebURLRequest::RequestContextPing: |
| case WebURLRequest::RequestContextPlugin: |
| case WebURLRequest::RequestContextPrefetch: |
| case WebURLRequest::RequestContextUnspecified: |
| return true; |
| } |
| ASSERT_NOT_REACHED(); |
| return true; |
| } |
| |
| void ContentSecurityPolicy::usesScriptHashAlgorithms(uint8_t algorithms) { |
| m_scriptHashAlgorithmsUsed |= algorithms; |
| } |
| |
| void ContentSecurityPolicy::usesStyleHashAlgorithms(uint8_t algorithms) { |
| m_styleHashAlgorithmsUsed |= algorithms; |
| } |
| |
| bool ContentSecurityPolicy::allowObjectFromSource( |
| const KURL& url, |
| RedirectStatus redirectStatus, |
| SecurityViolationReportingPolicy reportingPolicy) const { |
| return isAllowedByAll<&CSPDirectiveList::allowObjectFromSource>( |
| m_policies, url, redirectStatus, reportingPolicy); |
| } |
| |
| bool ContentSecurityPolicy::allowFrameFromSource( |
| const KURL& url, |
| RedirectStatus redirectStatus, |
| SecurityViolationReportingPolicy reportingPolicy) const { |
| return isAllowedByAll<&CSPDirectiveList::allowFrameFromSource>( |
| m_policies, url, redirectStatus, reportingPolicy); |
| } |
| |
| bool ContentSecurityPolicy::allowImageFromSource( |
| const KURL& url, |
| RedirectStatus redirectStatus, |
| SecurityViolationReportingPolicy reportingPolicy) const { |
| if (shouldBypassContentSecurityPolicy(url, SchemeRegistry::PolicyAreaImage)) |
| return true; |
| return isAllowedByAll<&CSPDirectiveList::allowImageFromSource>( |
| m_policies, url, redirectStatus, reportingPolicy); |
| } |
| |
| bool ContentSecurityPolicy::allowStyleFromSource( |
| const KURL& url, |
| const String& nonce, |
| RedirectStatus redirectStatus, |
| SecurityViolationReportingPolicy reportingPolicy) const { |
| if (shouldBypassContentSecurityPolicy(url, SchemeRegistry::PolicyAreaStyle)) |
| return true; |
| return isAllowedByAll<&CSPDirectiveList::allowStyleFromSource>( |
| m_policies, url, nonce, redirectStatus, reportingPolicy); |
| } |
| |
| bool ContentSecurityPolicy::allowFontFromSource( |
| const KURL& url, |
| RedirectStatus redirectStatus, |
| SecurityViolationReportingPolicy reportingPolicy) const { |
| return isAllowedByAll<&CSPDirectiveList::allowFontFromSource>( |
| m_policies, url, redirectStatus, reportingPolicy); |
| } |
| |
| bool ContentSecurityPolicy::allowMediaFromSource( |
| const KURL& url, |
| RedirectStatus redirectStatus, |
| SecurityViolationReportingPolicy reportingPolicy) const { |
| return isAllowedByAll<&CSPDirectiveList::allowMediaFromSource>( |
| m_policies, url, redirectStatus, reportingPolicy); |
| } |
| |
| bool ContentSecurityPolicy::allowConnectToSource( |
| const KURL& url, |
| RedirectStatus redirectStatus, |
| SecurityViolationReportingPolicy reportingPolicy) const { |
| return isAllowedByAll<&CSPDirectiveList::allowConnectToSource>( |
| m_policies, url, redirectStatus, reportingPolicy); |
| } |
| |
| bool ContentSecurityPolicy::allowFormAction( |
| const KURL& url, |
| RedirectStatus redirectStatus, |
| SecurityViolationReportingPolicy reportingPolicy) const { |
| return isAllowedByAll<&CSPDirectiveList::allowFormAction>( |
| m_policies, url, redirectStatus, reportingPolicy); |
| } |
| |
| bool ContentSecurityPolicy::allowBaseURI( |
| const KURL& url, |
| RedirectStatus redirectStatus, |
| SecurityViolationReportingPolicy reportingPolicy) const { |
| return isAllowedByAll<&CSPDirectiveList::allowBaseURI>( |
| m_policies, url, redirectStatus, reportingPolicy); |
| } |
| |
| bool ContentSecurityPolicy::allowWorkerContextFromSource( |
| const KURL& url, |
| RedirectStatus redirectStatus, |
| SecurityViolationReportingPolicy reportingPolicy) const { |
| // CSP 1.1 moves workers from 'script-src' to the new 'child-src'. Measure the |
| // impact of this backwards-incompatible change. |
| if (Document* document = this->document()) { |
| UseCounter::count(*document, UseCounter::WorkerSubjectToCSP); |
| if (isAllowedByAll<&CSPDirectiveList::allowWorkerFromSource>( |
| m_policies, url, redirectStatus, |
| SecurityViolationReportingPolicy::SuppressReporting) && |
| !isAllowedByAll<&CSPDirectiveList::allowScriptFromSource>( |
| m_policies, url, AtomicString(), IntegrityMetadataSet(), |
| NotParserInserted, redirectStatus, |
| SecurityViolationReportingPolicy::SuppressReporting)) { |
| UseCounter::count(*document, |
| UseCounter::WorkerAllowedByChildBlockedByScript); |
| } |
| } |
| |
| return isAllowedByAll<&CSPDirectiveList::allowWorkerFromSource>( |
| m_policies, url, redirectStatus, reportingPolicy); |
| } |
| |
| bool ContentSecurityPolicy::allowManifestFromSource( |
| const KURL& url, |
| RedirectStatus redirectStatus, |
| SecurityViolationReportingPolicy reportingPolicy) const { |
| return isAllowedByAll<&CSPDirectiveList::allowManifestFromSource>( |
| m_policies, url, redirectStatus, reportingPolicy); |
| } |
| |
| bool ContentSecurityPolicy::allowAncestors( |
| LocalFrame* frame, |
| const KURL& url, |
| SecurityViolationReportingPolicy reportingPolicy) const { |
| return isAllowedByAll<&CSPDirectiveList::allowAncestors>( |
| m_policies, frame, url, reportingPolicy); |
| } |
| |
| bool ContentSecurityPolicy::isFrameAncestorsEnforced() const { |
| for (const auto& policy : m_policies) { |
| if (policy->isFrameAncestorsEnforced()) |
| return true; |
| } |
| return false; |
| } |
| |
| bool ContentSecurityPolicy::isActive() const { |
| return !m_policies.isEmpty(); |
| } |
| |
| const KURL ContentSecurityPolicy::url() const { |
| return m_executionContext->contextURL(); |
| } |
| |
| KURL ContentSecurityPolicy::completeURL(const String& url) const { |
| return m_executionContext->contextCompleteURL(url); |
| } |
| |
| void ContentSecurityPolicy::enforceSandboxFlags(SandboxFlags mask) { |
| m_sandboxMask |= mask; |
| } |
| |
| void ContentSecurityPolicy::treatAsPublicAddress() { |
| if (!RuntimeEnabledFeatures::corsRFC1918Enabled()) |
| return; |
| m_treatAsPublicAddress = true; |
| } |
| |
| void ContentSecurityPolicy::enforceStrictMixedContentChecking() { |
| m_insecureRequestPolicy |= kBlockAllMixedContent; |
| } |
| |
| void ContentSecurityPolicy::upgradeInsecureRequests() { |
| m_insecureRequestPolicy |= kUpgradeInsecureRequests; |
| } |
| |
| static String stripURLForUseInReport( |
| ExecutionContext* context, |
| const KURL& url, |
| RedirectStatus redirectStatus, |
| const ContentSecurityPolicy::DirectiveType& effectiveType) { |
| if (!url.isValid()) |
| return String(); |
| if (!url.isHierarchical() || url.protocolIs("file")) |
| return url.protocol(); |
| |
| // Until we're more careful about the way we deal with navigations in frames |
| // (and, by extension, in plugin documents), strip cross-origin 'frame-src' |
| // and 'object-src' violations down to an origin. https://crbug.com/633306 |
| bool canSafelyExposeURL = |
| context->getSecurityOrigin()->canRequest(url) || |
| (redirectStatus == RedirectStatus::NoRedirect && |
| effectiveType != ContentSecurityPolicy::DirectiveType::FrameSrc && |
| effectiveType != ContentSecurityPolicy::DirectiveType::ObjectSrc); |
| |
| if (canSafelyExposeURL) { |
| // 'KURL::strippedForUseAsReferrer()' dumps 'String()' for non-webby URLs. |
| // It's better for developers if we return the origin of those URLs rather |
| // than nothing. |
| if (url.protocolIsInHTTPFamily()) |
| return url.strippedForUseAsReferrer(); |
| } |
| return SecurityOrigin::create(url)->toString(); |
| } |
| |
| static void gatherSecurityPolicyViolationEventData( |
| SecurityPolicyViolationEventInit& init, |
| ExecutionContext* context, |
| const String& directiveText, |
| const ContentSecurityPolicy::DirectiveType& effectiveType, |
| const KURL& blockedURL, |
| const String& header, |
| RedirectStatus redirectStatus, |
| ContentSecurityPolicyHeaderType headerType, |
| ContentSecurityPolicy::ViolationType violationType, |
| std::unique_ptr<SourceLocation> sourceLocation, |
| const String& scriptSource) { |
| if (effectiveType == ContentSecurityPolicy::DirectiveType::FrameAncestors) { |
| // If this load was blocked via 'frame-ancestors', then the URL of |
| // |document| has not yet been initialized. In this case, we'll set both |
| // 'documentURI' and 'blockedURI' to the blocked document's URL. |
| String strippedURL = stripURLForUseInReport( |
| context, blockedURL, RedirectStatus::NoRedirect, |
| ContentSecurityPolicy::DirectiveType::DefaultSrc); |
| init.setDocumentURI(strippedURL); |
| init.setBlockedURI(strippedURL); |
| } else { |
| String strippedURL = stripURLForUseInReport( |
| context, context->url(), RedirectStatus::NoRedirect, |
| ContentSecurityPolicy::DirectiveType::DefaultSrc); |
| init.setDocumentURI(strippedURL); |
| switch (violationType) { |
| case ContentSecurityPolicy::InlineViolation: |
| init.setBlockedURI("inline"); |
| break; |
| case ContentSecurityPolicy::EvalViolation: |
| init.setBlockedURI("eval"); |
| break; |
| case ContentSecurityPolicy::URLViolation: |
| init.setBlockedURI(stripURLForUseInReport( |
| context, blockedURL, redirectStatus, effectiveType)); |
| break; |
| } |
| } |
| |
| String effectiveDirective = |
| ContentSecurityPolicy::getDirectiveName(effectiveType); |
| init.setViolatedDirective(effectiveDirective); |
| init.setEffectiveDirective(effectiveDirective); |
| init.setOriginalPolicy(header); |
| init.setDisposition(headerType == ContentSecurityPolicyHeaderTypeEnforce |
| ? "enforce" |
| : "report"); |
| init.setStatusCode(0); |
| |
| // TODO(mkwst): We only have referrer and status code information for |
| // Documents. It would be nice to get them for Workers as well. |
| if (context->isDocument()) { |
| Document* document = toDocument(context); |
| DCHECK(document); |
| init.setReferrer(document->referrer()); |
| if (!SecurityOrigin::isSecure(context->url()) && document->loader()) |
| init.setStatusCode(document->loader()->response().httpStatusCode()); |
| } |
| |
| // If no source location is provided, use the source location of the context. |
| if (!sourceLocation) |
| sourceLocation = SourceLocation::capture(context); |
| if (sourceLocation->lineNumber()) { |
| KURL source = KURL(ParsedURLString, sourceLocation->url()); |
| init.setSourceFile( |
| stripURLForUseInReport(context, source, redirectStatus, effectiveType)); |
| init.setLineNumber(sourceLocation->lineNumber()); |
| init.setColumnNumber(sourceLocation->columnNumber()); |
| } else { |
| init.setSourceFile(String()); |
| init.setLineNumber(0); |
| init.setColumnNumber(0); |
| } |
| |
| if (!scriptSource.isEmpty()) |
| init.setSample(scriptSource.stripWhiteSpace().left(40)); |
| } |
| |
| void ContentSecurityPolicy::reportViolation( |
| const String& directiveText, |
| const DirectiveType& effectiveType, |
| const String& consoleMessage, |
| const KURL& blockedURL, |
| const Vector<String>& reportEndpoints, |
| const String& header, |
| ContentSecurityPolicyHeaderType headerType, |
| ViolationType violationType, |
| std::unique_ptr<SourceLocation> sourceLocation, |
| LocalFrame* contextFrame, |
| RedirectStatus redirectStatus, |
| Element* element, |
| const String& source) { |
| ASSERT(violationType == URLViolation || blockedURL.isEmpty()); |
| |
| // TODO(lukasza): Support sending reports from OOPIFs - |
| // https://crbug.com/611232 (or move CSP child-src and frame-src checks to the |
| // browser process - see https://crbug.com/376522). |
| if (!m_executionContext && !contextFrame) { |
| DCHECK(effectiveType == DirectiveType::ChildSrc || |
| effectiveType == DirectiveType::FrameSrc || |
| effectiveType == DirectiveType::PluginTypes); |
| return; |
| } |
| |
| DCHECK((m_executionContext && !contextFrame) || |
| ((effectiveType == DirectiveType::FrameAncestors) && contextFrame)); |
| |
| SecurityPolicyViolationEventInit violationData; |
| |
| // If we're processing 'frame-ancestors', use |contextFrame|'s execution |
| // context to gather data. Otherwise, use the policy's execution context. |
| ExecutionContext* relevantContext = |
| contextFrame ? contextFrame->document() : m_executionContext; |
| DCHECK(relevantContext); |
| gatherSecurityPolicyViolationEventData( |
| violationData, relevantContext, directiveText, effectiveType, blockedURL, |
| header, redirectStatus, headerType, violationType, |
| std::move(sourceLocation), source); |
| |
| // TODO(mkwst): Obviously, we shouldn't hit this check, as extension-loaded |
| // resources should be allowed regardless. We apparently do, however, so |
| // we should at least stop spamming reporting endpoints. See |
| // https://crbug.com/524356 for detail. |
| if (!violationData.sourceFile().isEmpty() && |
| shouldBypassContentSecurityPolicy( |
| KURL(ParsedURLString, violationData.sourceFile()))) { |
| return; |
| } |
| |
| postViolationReport(violationData, contextFrame, reportEndpoints); |
| |
| // Fire a violation event if we're working within an execution context (e.g. |
| // we're not processing 'frame-ancestors'). |
| if (m_executionContext) { |
| TaskRunnerHelper::get(TaskType::Networking, m_executionContext) |
| ->postTask(BLINK_FROM_HERE, |
| WTF::bind(&ContentSecurityPolicy::dispatchViolationEvents, |
| wrapPersistent(this), violationData, |
| wrapPersistent(element))); |
| } |
| } |
| |
| void ContentSecurityPolicy::postViolationReport( |
| const SecurityPolicyViolationEventInit& violationData, |
| LocalFrame* contextFrame, |
| const Vector<String>& reportEndpoints) { |
| // TODO(mkwst): Support POSTing violation reports from a Worker. |
| Document* document = |
| contextFrame ? contextFrame->document() : this->document(); |
| if (!document) |
| return; |
| |
| // We need to be careful here when deciding what information to send to the |
| // report-uri. Currently, we send only the current document's URL and the |
| // directive that was violated. The document's URL is safe to send because |
| // it's the document itself that's requesting that it be sent. You could |
| // make an argument that we shouldn't send HTTPS document URLs to HTTP |
| // report-uris (for the same reasons that we supress the Referer in that |
| // case), but the Referer is sent implicitly whereas this request is only |
| // sent explicitly. As for which directive was violated, that's pretty |
| // harmless information. |
| // |
| // TODO(mkwst): This justification is BS. Insecure reports are mixed content, |
| // let's kill them. https://crbug.com/695363 |
| |
| std::unique_ptr<JSONObject> cspReport = JSONObject::create(); |
| cspReport->setString("document-uri", violationData.documentURI()); |
| cspReport->setString("referrer", violationData.referrer()); |
| cspReport->setString("violated-directive", violationData.violatedDirective()); |
| cspReport->setString("effective-directive", |
| violationData.effectiveDirective()); |
| cspReport->setString("original-policy", violationData.originalPolicy()); |
| cspReport->setString("disposition", violationData.disposition()); |
| cspReport->setString("blocked-uri", violationData.blockedURI()); |
| if (violationData.lineNumber()) |
| cspReport->setInteger("line-number", violationData.lineNumber()); |
| if (violationData.columnNumber()) |
| cspReport->setInteger("column-number", violationData.columnNumber()); |
| if (!violationData.sourceFile().isEmpty()) |
| cspReport->setString("source-file", violationData.sourceFile()); |
| cspReport->setInteger("status-code", violationData.statusCode()); |
| |
| cspReport->setString("script-sample", violationData.sample()); |
| |
| std::unique_ptr<JSONObject> reportObject = JSONObject::create(); |
| reportObject->setObject("csp-report", std::move(cspReport)); |
| String stringifiedReport = reportObject->toJSONString(); |
| |
| // Only POST unique reports to the external endpoint; repeated reports add no |
| // value on the server side, as they're indistinguishable. Note that we'll |
| // fire the DOM event for every violation, as the page has enough context to |
| // react in some reasonable way to each violation as it occurs. |
| if (shouldSendViolationReport(stringifiedReport)) { |
| didSendViolationReport(stringifiedReport); |
| |
| RefPtr<EncodedFormData> report = |
| EncodedFormData::create(stringifiedReport.utf8()); |
| |
| LocalFrame* frame = document->frame(); |
| if (!frame) |
| return; |
| |
| for (const String& endpoint : reportEndpoints) { |
| // If we have a context frame we're dealing with 'frame-ancestors' and we |
| // don't have our own execution context. Use the frame's document to |
| // complete the endpoint URL, overriding its URL with the blocked |
| // document's URL. |
| DCHECK(!contextFrame || !m_executionContext); |
| DCHECK(!contextFrame || |
| getDirectiveType(violationData.effectiveDirective()) == |
| DirectiveType::FrameAncestors); |
| KURL url = |
| contextFrame |
| ? frame->document()->completeURLWithOverride( |
| endpoint, KURL(ParsedURLString, violationData.blockedURI())) |
| : completeURL(endpoint); |
| PingLoader::sendViolationReport( |
| frame, url, report, PingLoader::ContentSecurityPolicyViolationReport); |
| } |
| } |
| } |
| |
| void ContentSecurityPolicy::dispatchViolationEvents( |
| const SecurityPolicyViolationEventInit& violationData, |
| Element* element) { |
| // If the context is detached or closed (thus clearing its event queue) |
| // between the violation occuring and this event dispatch, exit early. |
| EventQueue* queue = m_executionContext->getEventQueue(); |
| if (!queue) |
| return; |
| |
| SecurityPolicyViolationEvent* event = SecurityPolicyViolationEvent::create( |
| EventTypeNames::securitypolicyviolation, violationData); |
| DCHECK(event->bubbles()); |
| |
| if (m_executionContext->isDocument()) { |
| Document* document = toDocument(m_executionContext); |
| if (element && element->isConnected() && element->document() == document) |
| event->setTarget(element); |
| else |
| event->setTarget(document); |
| } else if (m_executionContext->isWorkerGlobalScope()) { |
| event->setTarget(toWorkerGlobalScope(m_executionContext)); |
| } |
| queue->enqueueEvent(event); |
| } |
| |
| void ContentSecurityPolicy::reportMixedContent(const KURL& mixedURL, |
| RedirectStatus redirectStatus) { |
| for (const auto& policy : m_policies) |
| policy->reportMixedContent(mixedURL, redirectStatus); |
| } |
| |
| void ContentSecurityPolicy::reportReportOnlyInMeta(const String& header) { |
| logToConsole("The report-only Content Security Policy '" + header + |
| "' was delivered via a <meta> element, which is disallowed. The " |
| "policy has been ignored."); |
| } |
| |
| void ContentSecurityPolicy::reportMetaOutsideHead(const String& header) { |
| logToConsole("The Content Security Policy '" + header + |
| "' was delivered via a <meta> element outside the document's " |
| "<head>, which is disallowed. The policy has been ignored."); |
| } |
| |
| void ContentSecurityPolicy::reportValueForEmptyDirective(const String& name, |
| const String& value) { |
| logToConsole("The Content Security Policy directive '" + name + |
| "' should be empty, but was delivered with a value of '" + |
| value + |
| "'. The directive has been applied, and the value ignored."); |
| } |
| |
| void ContentSecurityPolicy::reportInvalidInReportOnly(const String& name) { |
| logToConsole("The Content Security Policy directive '" + name + |
| "' is ignored when delivered in a report-only policy."); |
| } |
| |
| void ContentSecurityPolicy::reportInvalidDirectiveInMeta( |
| const String& directive) { |
| logToConsole( |
| "Content Security Policies delivered via a <meta> element may not " |
| "contain the " + |
| directive + " directive."); |
| } |
| |
| void ContentSecurityPolicy::reportUnsupportedDirective(const String& name) { |
| static const char allow[] = "allow"; |
| static const char options[] = "options"; |
| static const char policyURI[] = "policy-uri"; |
| static const char allowMessage[] = |
| "The 'allow' directive has been replaced with 'default-src'. Please use " |
| "that directive instead, as 'allow' has no effect."; |
| static const char optionsMessage[] = |
| "The 'options' directive has been replaced with 'unsafe-inline' and " |
| "'unsafe-eval' source expressions for the 'script-src' and 'style-src' " |
| "directives. Please use those directives instead, as 'options' has no " |
| "effect."; |
| static const char policyURIMessage[] = |
| "The 'policy-uri' directive has been removed from the " |
| "specification. Please specify a complete policy via " |
| "the Content-Security-Policy header."; |
| |
| String message = |
| "Unrecognized Content-Security-Policy directive '" + name + "'.\n"; |
| MessageLevel level = ErrorMessageLevel; |
| if (equalIgnoringCase(name, allow)) { |
| message = allowMessage; |
| } else if (equalIgnoringCase(name, options)) { |
| message = optionsMessage; |
| } else if (equalIgnoringCase(name, policyURI)) { |
| message = policyURIMessage; |
| } else if (getDirectiveType(name) != DirectiveType::Undefined) { |
| message = "The Content-Security-Policy directive '" + name + |
| "' is implemented behind a flag which is currently disabled.\n"; |
| level = InfoMessageLevel; |
| } |
| |
| logToConsole(message, level); |
| } |
| |
| void ContentSecurityPolicy::reportDirectiveAsSourceExpression( |
| const String& directiveName, |
| const String& sourceExpression) { |
| String message = "The Content Security Policy directive '" + directiveName + |
| "' contains '" + sourceExpression + |
| "' as a source expression. Did you mean '" + directiveName + |
| " ...; " + sourceExpression + "...' (note the semicolon)?"; |
| logToConsole(message); |
| } |
| |
| void ContentSecurityPolicy::reportDuplicateDirective(const String& name) { |
| String message = |
| "Ignoring duplicate Content-Security-Policy directive '" + name + "'.\n"; |
| logToConsole(message); |
| } |
| |
| void ContentSecurityPolicy::reportInvalidPluginTypes(const String& pluginType) { |
| String message; |
| if (pluginType.isNull()) |
| message = |
| "'plugin-types' Content Security Policy directive is empty; all " |
| "plugins will be blocked.\n"; |
| else if (pluginType == "'none'") |
| message = |
| "Invalid plugin type in 'plugin-types' Content Security Policy " |
| "directive: '" + |
| pluginType + |
| "'. Did you mean to set the object-src directive to 'none'?\n"; |
| else |
| message = |
| "Invalid plugin type in 'plugin-types' Content Security Policy " |
| "directive: '" + |
| pluginType + "'.\n"; |
| logToConsole(message); |
| } |
| |
| void ContentSecurityPolicy::reportInvalidSandboxFlags( |
| const String& invalidFlags) { |
| logToConsole( |
| "Error while parsing the 'sandbox' Content Security Policy directive: " + |
| invalidFlags); |
| } |
| |
| void ContentSecurityPolicy::reportInvalidRequireSRIForTokens( |
| const String& invalidTokens) { |
| logToConsole( |
| "Error while parsing the 'require-sri-for' Content Security Policy " |
| "directive: " + |
| invalidTokens); |
| } |
| |
| void ContentSecurityPolicy::reportInvalidDirectiveValueCharacter( |
| const String& directiveName, |
| const String& value) { |
| String message = "The value for Content Security Policy directive '" + |
| directiveName + "' contains an invalid character: '" + |
| value + |
| "'. Non-whitespace characters outside ASCII 0x21-0x7E must " |
| "be percent-encoded, as described in RFC 3986, section 2.1: " |
| "http://tools.ietf.org/html/rfc3986#section-2.1."; |
| logToConsole(message); |
| } |
| |
| void ContentSecurityPolicy::reportInvalidPathCharacter( |
| const String& directiveName, |
| const String& value, |
| const char invalidChar) { |
| ASSERT(invalidChar == '#' || invalidChar == '?'); |
| |
| String ignoring = |
| "The fragment identifier, including the '#', will be ignored."; |
| if (invalidChar == '?') |
| ignoring = "The query component, including the '?', will be ignored."; |
| String message = "The source list for Content Security Policy directive '" + |
| directiveName + |
| "' contains a source with an invalid path: '" + value + |
| "'. " + ignoring; |
| logToConsole(message); |
| } |
| |
| void ContentSecurityPolicy::reportInvalidSourceExpression( |
| const String& directiveName, |
| const String& source) { |
| String message = "The source list for Content Security Policy directive '" + |
| directiveName + "' contains an invalid source: '" + source + |
| "'. It will be ignored."; |
| if (equalIgnoringCase(source, "'none'")) |
| message = message + |
| " Note that 'none' has no effect unless it is the only " |
| "expression in the source list."; |
| logToConsole(message); |
| } |
| |
| void ContentSecurityPolicy::reportMissingReportURI(const String& policy) { |
| logToConsole("The Content Security Policy '" + policy + |
| "' was delivered in report-only mode, but does not specify a " |
| "'report-uri'; the policy will have no effect. Please either " |
| "add a 'report-uri' directive, or deliver the policy via the " |
| "'Content-Security-Policy' header."); |
| } |
| |
| void ContentSecurityPolicy::logToConsole(const String& message, |
| MessageLevel level) { |
| logToConsole(ConsoleMessage::create(SecurityMessageSource, level, message)); |
| } |
| |
| void ContentSecurityPolicy::logToConsole(ConsoleMessage* consoleMessage, |
| LocalFrame* frame) { |
| if (frame) |
| frame->document()->addConsoleMessage(consoleMessage); |
| else if (m_executionContext) |
| m_executionContext->addConsoleMessage(consoleMessage); |
| else |
| m_consoleMessages.push_back(consoleMessage); |
| } |
| |
| void ContentSecurityPolicy::reportBlockedScriptExecutionToInspector( |
| const String& directiveText) const { |
| probe::scriptExecutionBlockedByCSP(m_executionContext, directiveText); |
| } |
| |
| bool ContentSecurityPolicy::experimentalFeaturesEnabled() const { |
| return RuntimeEnabledFeatures:: |
| experimentalContentSecurityPolicyFeaturesEnabled(); |
| } |
| |
| bool ContentSecurityPolicy::shouldSendCSPHeader(Resource::Type type) const { |
| for (const auto& policy : m_policies) { |
| if (policy->shouldSendCSPHeader(type)) |
| return true; |
| } |
| return false; |
| } |
| |
| bool ContentSecurityPolicy::urlMatchesSelf(const KURL& url) const { |
| return m_selfSource->matches(url, RedirectStatus::NoRedirect); |
| } |
| |
| bool ContentSecurityPolicy::protocolEqualsSelf(const String& protocol) const { |
| return equalIgnoringCase(protocol, m_selfProtocol); |
| } |
| |
| const String& ContentSecurityPolicy::getSelfProtocol() const { |
| return m_selfProtocol; |
| } |
| |
| bool ContentSecurityPolicy::shouldBypassMainWorld( |
| const ExecutionContext* context) { |
| if (context && context->isDocument()) { |
| const Document* document = toDocument(context); |
| if (document->frame()) |
| return document->frame()->script().shouldBypassMainWorldCSP(); |
| } |
| return false; |
| } |
| |
| bool ContentSecurityPolicy::shouldSendViolationReport( |
| const String& report) const { |
| // Collisions have no security impact, so we can save space by storing only |
| // the string's hash rather than the whole report. |
| return !m_violationReportsSent.contains(report.impl()->hash()); |
| } |
| |
| void ContentSecurityPolicy::didSendViolationReport(const String& report) { |
| m_violationReportsSent.insert(report.impl()->hash()); |
| } |
| |
| const char* ContentSecurityPolicy::getDirectiveName(const DirectiveType& type) { |
| switch (type) { |
| case DirectiveType::BaseURI: |
| return "base-uri"; |
| case DirectiveType::BlockAllMixedContent: |
| return "block-all-mixed-content"; |
| case DirectiveType::ChildSrc: |
| return "child-src"; |
| case DirectiveType::ConnectSrc: |
| return "connect-src"; |
| case DirectiveType::DefaultSrc: |
| return "default-src"; |
| case DirectiveType::FrameAncestors: |
| return "frame-ancestors"; |
| case DirectiveType::FrameSrc: |
| return "frame-src"; |
| case DirectiveType::FontSrc: |
| return "font-src"; |
| case DirectiveType::FormAction: |
| return "form-action"; |
| case DirectiveType::ImgSrc: |
| return "img-src"; |
| case DirectiveType::ManifestSrc: |
| return "manifest-src"; |
| case DirectiveType::MediaSrc: |
| return "media-src"; |
| case DirectiveType::ObjectSrc: |
| return "object-src"; |
| case DirectiveType::PluginTypes: |
| return "plugin-types"; |
| case DirectiveType::ReportURI: |
| return "report-uri"; |
| case DirectiveType::RequireSRIFor: |
| return "require-sri-for"; |
| case DirectiveType::Sandbox: |
| return "sandbox"; |
| case DirectiveType::ScriptSrc: |
| return "script-src"; |
| case DirectiveType::StyleSrc: |
| return "style-src"; |
| case DirectiveType::TreatAsPublicAddress: |
| return "treat-as-public-address"; |
| case DirectiveType::UpgradeInsecureRequests: |
| return "upgrade-insecure-requests"; |
| case DirectiveType::WorkerSrc: |
| return "worker-src"; |
| case DirectiveType::Undefined: |
| NOTREACHED(); |
| return ""; |
| } |
| |
| NOTREACHED(); |
| return ""; |
| } |
| |
| ContentSecurityPolicy::DirectiveType ContentSecurityPolicy::getDirectiveType( |
| const String& name) { |
| if (name == "base-uri") |
| return DirectiveType::BaseURI; |
| if (name == "block-all-mixed-content") |
| return DirectiveType::BlockAllMixedContent; |
| if (name == "child-src") |
| return DirectiveType::ChildSrc; |
| if (name == "connect-src") |
| return DirectiveType::ConnectSrc; |
| if (name == "default-src") |
| return DirectiveType::DefaultSrc; |
| if (name == "frame-ancestors") |
| return DirectiveType::FrameAncestors; |
| if (name == "frame-src") |
| return DirectiveType::FrameSrc; |
| if (name == "font-src") |
| return DirectiveType::FontSrc; |
| if (name == "form-action") |
| return DirectiveType::FormAction; |
| if (name == "img-src") |
| return DirectiveType::ImgSrc; |
| if (name == "manifest-src") |
| return DirectiveType::ManifestSrc; |
| if (name == "media-src") |
| return DirectiveType::MediaSrc; |
| if (name == "object-src") |
| return DirectiveType::ObjectSrc; |
| if (name == "plugin-types") |
| return DirectiveType::PluginTypes; |
| if (name == "report-uri") |
| return DirectiveType::ReportURI; |
| if (name == "require-sri-for") |
| return DirectiveType::RequireSRIFor; |
| if (name == "sandbox") |
| return DirectiveType::Sandbox; |
| if (name == "script-src") |
| return DirectiveType::ScriptSrc; |
| if (name == "style-src") |
| return DirectiveType::StyleSrc; |
| if (name == "treat-as-public-address") |
| return DirectiveType::TreatAsPublicAddress; |
| if (name == "upgrade-insecure-requests") |
| return DirectiveType::UpgradeInsecureRequests; |
| if (name == "worker-src") |
| return DirectiveType::WorkerSrc; |
| |
| return DirectiveType::Undefined; |
| } |
| |
| bool ContentSecurityPolicy::subsumes(const ContentSecurityPolicy& other) const { |
| if (!m_policies.size() || !other.m_policies.size()) |
| return !m_policies.size(); |
| // Embedding-CSP must specify only one policy. |
| if (m_policies.size() != 1) |
| return false; |
| |
| CSPDirectiveListVector otherVector; |
| for (const auto& policy : other.m_policies) { |
| if (!policy->isReportOnly()) |
| otherVector.push_back(policy); |
| } |
| |
| return m_policies[0]->subsumes(otherVector); |
| } |
| |
| bool ContentSecurityPolicy::shouldBypassContentSecurityPolicy( |
| const KURL& url, |
| SchemeRegistry::PolicyAreas area) { |
| if (SecurityOrigin::shouldUseInnerURL(url)) { |
| return SchemeRegistry::schemeShouldBypassContentSecurityPolicy( |
| SecurityOrigin::extractInnerURL(url).protocol(), area); |
| } else { |
| return SchemeRegistry::schemeShouldBypassContentSecurityPolicy( |
| url.protocol(), area); |
| } |
| } |
| |
| } // namespace blink |