diff options
Diffstat (limited to 'Source/WebCore/page')
12 files changed, 410 insertions, 46 deletions
diff --git a/Source/WebCore/page/Performance.cpp b/Source/WebCore/page/Performance.cpp index ed39a1d15..1c491da5d 100644 --- a/Source/WebCore/page/Performance.cpp +++ b/Source/WebCore/page/Performance.cpp @@ -233,7 +233,7 @@ void Performance::webkitClearMeasures(const String& measureName) double Performance::now() const { double nowSeconds = monotonicallyIncreasingTime() - m_referenceTime; - const double resolutionSeconds = 0.000005; + const double resolutionSeconds = 0.0001; return 1000.0 * floor(nowSeconds / resolutionSeconds) * resolutionSeconds; } diff --git a/Source/WebCore/page/Settings.in b/Source/WebCore/page/Settings.in index 84a9f0cd0..74d9e0709 100644 --- a/Source/WebCore/page/Settings.in +++ b/Source/WebCore/page/Settings.in @@ -79,7 +79,6 @@ needsSiteSpecificQuirks initial=false domTimersThrottlingEnabled initial=true webArchiveDebugModeEnabled initial=false, conditional=WEB_ARCHIVE localFileContentSniffingEnabled initial=false -offlineStorageDatabaseEnabled initial=false offlineWebApplicationCacheEnabled initial=false enforceCSSMIMETypeInNoQuirksMode initial=true usesEncodingDetector initial=false @@ -91,6 +90,9 @@ acceleratedFiltersEnabled initial=false useLegacyTextAlignPositionedElementBehavior initial=false javaScriptRuntimeFlags type=JSC::RuntimeFlags +# PLATFORM(QT) only +offlineStorageDatabaseEnabled initial=true + # FIXME: This should really be disabled by default as it makes platforms that don't support the feature download files # they can't use by. Leaving enabled for now to not change existing behavior. downloadableBinaryFontsEnabled initial=true diff --git a/Source/WebCore/page/csp/ContentSecurityPolicy.cpp b/Source/WebCore/page/csp/ContentSecurityPolicy.cpp index 19bf207fa..9e726d5fe 100644 --- a/Source/WebCore/page/csp/ContentSecurityPolicy.cpp +++ b/Source/WebCore/page/csp/ContentSecurityPolicy.cpp @@ -29,14 +29,17 @@ #include "ContentSecurityPolicyDirective.h" #include "ContentSecurityPolicyDirectiveList.h" +#include "ContentSecurityPolicyHash.h" #include "ContentSecurityPolicySource.h" #include "ContentSecurityPolicySourceList.h" +#include "CryptoDigest.h" #include "DOMStringList.h" #include "Document.h" #include "DocumentLoader.h" #include "FormData.h" #include "FormDataList.h" #include "Frame.h" +#include "HTMLParserIdioms.h" #include "InspectorInstrumentation.h" #include "JSMainThreadExecState.h" #include "ParsingUtilities.h" @@ -45,6 +48,7 @@ #include "SchemeRegistry.h" #include "SecurityOrigin.h" #include "SecurityPolicyViolationEvent.h" +#include "TextEncoding.h" #include <inspector/InspectorValues.h> #include <inspector/ScriptCallStack.h> #include <inspector/ScriptCallStackFactory.h> @@ -193,6 +197,48 @@ bool isAllowedByAllWithContext(const CSPDirectiveListVector& policies, const Str return true; } +template<bool (ContentSecurityPolicyDirectiveList::*allowed)(const String& nonce) const> +static bool isAllowedByAllWithNonce(const CSPDirectiveListVector& policies, const String& nonce) +{ + for (auto& policy : policies) { + if (!(policy.get()->*allowed)(nonce)) + return false; + } + return true; +} + +static CryptoDigest::Algorithm toCryptoDigestAlgorithm(ContentSecurityPolicyHashAlgorithm algorithm) +{ + switch (algorithm) { + case ContentSecurityPolicyHashAlgorithm::SHA_256: + return CryptoDigest::Algorithm::SHA_256; + case ContentSecurityPolicyHashAlgorithm::SHA_384: + return CryptoDigest::Algorithm::SHA_384; + case ContentSecurityPolicyHashAlgorithm::SHA_512: + return CryptoDigest::Algorithm::SHA_512; + } + ASSERT_NOT_REACHED(); + return CryptoDigest::Algorithm::SHA_512; +} + +template<bool (ContentSecurityPolicyDirectiveList::*allowed)(const ContentSecurityPolicyHash&) const> +bool isAllowedByAllWithHashFromContent(const CSPDirectiveListVector& policies, const String& content, const TextEncoding& encoding, OptionSet<ContentSecurityPolicyHashAlgorithm> algorithms) +{ + // FIXME: Compute the digest with respect to the raw bytes received from the page. + // See <https://bugs.webkit.org/show_bug.cgi?id=155184>. + CString contentCString = encoding.encode(content, EntitiesForUnencodables); + for (auto algorithm : algorithms) { + auto cryptoDigest = CryptoDigest::create(toCryptoDigestAlgorithm(algorithm)); + cryptoDigest->addBytes(contentCString.data(), contentCString.length()); + Vector<uint8_t> digest = cryptoDigest->computeHash(); + for (auto& policy : policies) { + if ((policy.get()->*allowed)(std::make_pair(algorithm, digest))) + return true; + } + } + return false; +} + template<bool (ContentSecurityPolicyDirectiveList::*allowFromURL)(const URL&, ContentSecurityPolicy::ReportingStatus) const> bool isAllowedByAllWithURL(const CSPDirectiveListVector& policies, const URL& url, ContentSecurityPolicy::ReportingStatus reportingStatus) { @@ -216,14 +262,61 @@ bool ContentSecurityPolicy::allowInlineEventHandlers(const String& contextURL, c return overrideContentSecurityPolicy || isAllowedByAllWithContext<&ContentSecurityPolicyDirectiveList::allowInlineEventHandlers>(m_policies, contextURL, contextLine, reportingStatus); } -bool ContentSecurityPolicy::allowInlineScript(const String& contextURL, const WTF::OrdinalNumber& contextLine, bool overrideContentSecurityPolicy, ContentSecurityPolicy::ReportingStatus reportingStatus) const +// FIXME: We should compute the document encoding once and cache it instead of computing it on each invocation. +const TextEncoding& ContentSecurityPolicy::documentEncoding() const { - return overrideContentSecurityPolicy || isAllowedByAllWithContext<&ContentSecurityPolicyDirectiveList::allowInlineScript>(m_policies, contextURL, contextLine, reportingStatus); + if (!is<Document>(m_scriptExecutionContext)) + return UTF8Encoding(); + Document& document = downcast<Document>(*m_scriptExecutionContext); + if (TextResourceDecoder* decoder = document.decoder()) + return decoder->encoding(); + return UTF8Encoding(); } -bool ContentSecurityPolicy::allowInlineStyle(const String& contextURL, const WTF::OrdinalNumber& contextLine, bool overrideContentSecurityPolicy, ContentSecurityPolicy::ReportingStatus reportingStatus) const +bool ContentSecurityPolicy::allowScriptWithNonce(const String& nonce, bool overrideContentSecurityPolicy) const { - return overrideContentSecurityPolicy || m_overrideInlineStyleAllowed || isAllowedByAllWithContext<&ContentSecurityPolicyDirectiveList::allowInlineStyle>(m_policies, contextURL, contextLine, reportingStatus); + if (overrideContentSecurityPolicy) + return true; + String strippedNonce = stripLeadingAndTrailingHTMLSpaces(nonce); + if (strippedNonce.isEmpty()) + return false; + if (isAllowedByAllWithNonce<&ContentSecurityPolicyDirectiveList::allowScriptWithNonce>(m_policies, strippedNonce)) + return true; + return false; +} + +bool ContentSecurityPolicy::allowStyleWithNonce(const String& nonce, bool overrideContentSecurityPolicy) const +{ + if (overrideContentSecurityPolicy) + return true; + String strippedNonce = stripLeadingAndTrailingHTMLSpaces(nonce); + if (strippedNonce.isEmpty()) + return false; + if (isAllowedByAllWithNonce<&ContentSecurityPolicyDirectiveList::allowStyleWithNonce>(m_policies, strippedNonce)) + return true; + return false; +} + +bool ContentSecurityPolicy::allowInlineScript(const String& contextURL, const WTF::OrdinalNumber& contextLine, const String& scriptContent, bool overrideContentSecurityPolicy, ContentSecurityPolicy::ReportingStatus reportingStatus) const +{ + if (overrideContentSecurityPolicy) + return true; + if (!m_hashAlgorithmsForInlineScripts.isEmpty() && !scriptContent.isEmpty() + && isAllowedByAllWithHashFromContent<&ContentSecurityPolicyDirectiveList::allowInlineScriptWithHash>(m_policies, scriptContent, documentEncoding(), m_hashAlgorithmsForInlineScripts)) + return true; + return isAllowedByAllWithContext<&ContentSecurityPolicyDirectiveList::allowInlineScript>(m_policies, contextURL, contextLine, reportingStatus); +} + +bool ContentSecurityPolicy::allowInlineStyle(const String& contextURL, const WTF::OrdinalNumber& contextLine, const String& styleContent, bool overrideContentSecurityPolicy, ContentSecurityPolicy::ReportingStatus reportingStatus) const +{ + if (overrideContentSecurityPolicy) + return true; + if (m_overrideInlineStyleAllowed) + return true; + if (!m_hashAlgorithmsForInlineStylesheets.isEmpty() && !styleContent.isEmpty() + && isAllowedByAllWithHashFromContent<&ContentSecurityPolicyDirectiveList::allowInlineStyleWithHash>(m_policies, styleContent, documentEncoding(), m_hashAlgorithmsForInlineStylesheets)) + return true; + return isAllowedByAllWithContext<&ContentSecurityPolicyDirectiveList::allowInlineStyle>(m_policies, contextURL, contextLine, reportingStatus); } bool ContentSecurityPolicy::allowEval(JSC::ExecState* state, bool overrideContentSecurityPolicy, ContentSecurityPolicy::ReportingStatus reportingStatus) const diff --git a/Source/WebCore/page/csp/ContentSecurityPolicy.h b/Source/WebCore/page/csp/ContentSecurityPolicy.h index 14c60f5c4..dcc05a173 100644 --- a/Source/WebCore/page/csp/ContentSecurityPolicy.h +++ b/Source/WebCore/page/csp/ContentSecurityPolicy.h @@ -29,6 +29,7 @@ #include "ContentSecurityPolicyResponseHeaders.h" #include "ScriptState.h" +#include <wtf/OptionSet.h> #include <wtf/Vector.h> #include <wtf/text/TextPosition.h> @@ -43,8 +44,11 @@ class ContentSecurityPolicySource; class DOMStringList; class ScriptExecutionContext; class SecurityOrigin; +class TextEncoding; class URL; +enum class ContentSecurityPolicyHashAlgorithm; + typedef Vector<std::unique_ptr<ContentSecurityPolicyDirectiveList>> CSPDirectiveListVector; typedef int SandboxFlags; @@ -82,8 +86,10 @@ public: }; bool allowJavaScriptURLs(const String& contextURL, const WTF::OrdinalNumber& contextLine, bool overrideContentSecurityPolicy = false, ContentSecurityPolicy::ReportingStatus = ContentSecurityPolicy::ReportingStatus::SendReport) const; bool allowInlineEventHandlers(const String& contextURL, const WTF::OrdinalNumber& contextLine, bool overrideContentSecurityPolicy = false, ContentSecurityPolicy::ReportingStatus = ContentSecurityPolicy::ReportingStatus::SendReport) const; - bool allowInlineScript(const String& contextURL, const WTF::OrdinalNumber& contextLine, bool overrideContentSecurityPolicy = false, ContentSecurityPolicy::ReportingStatus = ContentSecurityPolicy::ReportingStatus::SendReport) const; - bool allowInlineStyle(const String& contextURL, const WTF::OrdinalNumber& contextLine, bool overrideContentSecurityPolicy = false, ContentSecurityPolicy::ReportingStatus = ContentSecurityPolicy::ReportingStatus::SendReport) const; + bool allowScriptWithNonce(const String& nonce, bool overrideContentSecurityPolicy = false) const; + bool allowInlineScript(const String& contextURL, const WTF::OrdinalNumber& contextLine, const String& scriptContent, bool overrideContentSecurityPolicy = false, ContentSecurityPolicy::ReportingStatus = ContentSecurityPolicy::ReportingStatus::SendReport) const; + bool allowStyleWithNonce(const String& nonce, bool overrideContentSecurityPolicy = false) const; + bool allowInlineStyle(const String& contextURL, const WTF::OrdinalNumber& contextLine, const String& styleContent, bool overrideContentSecurityPolicy = false, ContentSecurityPolicy::ReportingStatus = ContentSecurityPolicy::ReportingStatus::SendReport) const; bool allowEval(JSC::ExecState* = nullptr, bool overrideContentSecurityPolicy = false, ContentSecurityPolicy::ReportingStatus = ContentSecurityPolicy::ReportingStatus::SendReport) const; bool allowPluginType(const String& type, const String& typeAttribute, const URL&, bool overrideContentSecurityPolicy = false, ContentSecurityPolicy::ReportingStatus = ContentSecurityPolicy::ReportingStatus::SendReport) const; bool allowScriptFromSource(const URL&, bool overrideContentSecurityPolicy = false, ContentSecurityPolicy::ReportingStatus = ContentSecurityPolicy::ReportingStatus::SendReport) const; @@ -134,6 +140,14 @@ public: void reportViolation(const String& directiveText, const String& effectiveDirective, const String& consoleMessage, const URL& blockedURL, const Vector<String>& reportURIs, const String& header, const String& contextURL = String(), const WTF::OrdinalNumber& contextLine = WTF::OrdinalNumber::beforeFirst(), JSC::ExecState* = nullptr) const; void reportBlockedScriptExecutionToInspector(const String& directiveText) const; void enforceSandboxFlags(SandboxFlags sandboxFlags) { m_sandboxFlags |= sandboxFlags; } + void addHashAlgorithmsForInlineScripts(OptionSet<ContentSecurityPolicyHashAlgorithm> hashAlgorithmsForInlineScripts) + { + m_hashAlgorithmsForInlineScripts |= hashAlgorithmsForInlineScripts; + } + void addHashAlgorithmsForInlineStylesheets(OptionSet<ContentSecurityPolicyHashAlgorithm> hashAlgorithmsForInlineStylesheets) + { + m_hashAlgorithmsForInlineStylesheets |= hashAlgorithmsForInlineStylesheets; + } // Used by ContentSecurityPolicySource bool protocolMatchesSelf(const URL&) const; @@ -145,6 +159,8 @@ private: void didReceiveHeader(const String&, ContentSecurityPolicyHeaderType, ContentSecurityPolicy::PolicyFrom); + const TextEncoding& documentEncoding() const; + ScriptExecutionContext* m_scriptExecutionContext { nullptr }; std::unique_ptr<ContentSecurityPolicySource> m_selfSource; String m_selfSourceProtocol; @@ -152,6 +168,8 @@ private: String m_lastPolicyEvalDisabledErrorMessage; SandboxFlags m_sandboxFlags; bool m_overrideInlineStyleAllowed { false }; + OptionSet<ContentSecurityPolicyHashAlgorithm> m_hashAlgorithmsForInlineScripts; + OptionSet<ContentSecurityPolicyHashAlgorithm> m_hashAlgorithmsForInlineStylesheets; }; } diff --git a/Source/WebCore/page/csp/ContentSecurityPolicyDirectiveList.cpp b/Source/WebCore/page/csp/ContentSecurityPolicyDirectiveList.cpp index 01cfa5134..47f5da456 100644 --- a/Source/WebCore/page/csp/ContentSecurityPolicyDirectiveList.cpp +++ b/Source/WebCore/page/csp/ContentSecurityPolicyDirectiveList.cpp @@ -105,6 +105,40 @@ static bool isNotASCIISpace(UChar c) return !isASCIISpace(c); } +static inline bool checkEval(ContentSecurityPolicySourceListDirective* directive) +{ + return !directive || directive->allowEval(); +} + +static inline bool checkInline(ContentSecurityPolicySourceListDirective* directive) +{ + return !directive || directive->allowInline(); +} + +static inline bool checkSource(ContentSecurityPolicySourceListDirective* directive, const URL& url) +{ + return !directive || directive->allows(url); +} + +static inline bool checkHash(ContentSecurityPolicySourceListDirective* directive, const ContentSecurityPolicyHash& hash) +{ + return !directive || directive->allows(hash); +} + +static inline bool checkNonce(ContentSecurityPolicySourceListDirective* directive, const String& nonce) +{ + return !directive || directive->allows(nonce); +} + +static inline bool checkMediaType(ContentSecurityPolicyMediaListDirective* directive, const String& type, const String& typeAttribute) +{ + if (!directive) + return true; + if (typeAttribute.isEmpty() || typeAttribute.stripWhiteSpace() != type) + return false; + return directive->allows(type); +} + ContentSecurityPolicyDirectiveList::ContentSecurityPolicyDirectiveList(ContentSecurityPolicy& policy, ContentSecurityPolicyHeaderType type) : m_policy(policy) , m_headerType(type) @@ -120,7 +154,7 @@ std::unique_ptr<ContentSecurityPolicyDirectiveList> ContentSecurityPolicyDirecti auto directives = std::make_unique<ContentSecurityPolicyDirectiveList>(policy, type); directives->parse(header, from); - if (!directives->checkEval(directives->operativeDirective(directives->m_scriptSrc.get()))) { + if (!checkEval(directives->operativeDirective(directives->m_scriptSrc.get()))) { String message = makeString("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->m_scriptSrc.get())->text(), "\".\n"); directives->setEvalDisabledErrorMessage(message); } @@ -137,30 +171,6 @@ void ContentSecurityPolicyDirectiveList::reportViolation(const String& directive m_policy.reportViolation(directiveText, effectiveDirective, message, blockedURL, m_reportURIs, m_header, contextURL, contextLine, state); } -bool ContentSecurityPolicyDirectiveList::checkEval(ContentSecurityPolicySourceListDirective* directive) const -{ - return !directive || directive->allowEval(); -} - -bool ContentSecurityPolicyDirectiveList::checkInline(ContentSecurityPolicySourceListDirective* directive) const -{ - return !directive || directive->allowInline(); -} - -bool ContentSecurityPolicyDirectiveList::checkSource(ContentSecurityPolicySourceListDirective* directive, const URL& url) const -{ - return !directive || directive->allows(url); -} - -bool ContentSecurityPolicyDirectiveList::checkMediaType(ContentSecurityPolicyMediaListDirective* directive, const String& type, const String& typeAttribute) const -{ - if (!directive) - return true; - if (typeAttribute.isEmpty() || typeAttribute.stripWhiteSpace() != type) - return false; - return directive->allows(type); -} - ContentSecurityPolicySourceListDirective* ContentSecurityPolicyDirectiveList::operativeDirective(ContentSecurityPolicySourceListDirective* directive) const { return directive ? directive : m_defaultSrc.get(); @@ -278,6 +288,16 @@ bool ContentSecurityPolicyDirectiveList::allowInlineScript(const String& context return m_reportOnly || checkInline(operativeDirective(m_scriptSrc.get())); } +bool ContentSecurityPolicyDirectiveList::allowInlineScriptWithHash(const ContentSecurityPolicyHash& hash) const +{ + return checkHash(operativeDirective(m_scriptSrc.get()), hash); +} + +bool ContentSecurityPolicyDirectiveList::allowScriptWithNonce(const String& nonce) const +{ + return checkNonce(operativeDirective(m_scriptSrc.get()), nonce); +} + bool ContentSecurityPolicyDirectiveList::allowInlineStyle(const String& contextURL, const WTF::OrdinalNumber& contextLine, ContentSecurityPolicy::ReportingStatus reportingStatus) const { static NeverDestroyed<String> consoleMessage(ASCIILiteral("Refused to apply inline style because it violates the following Content Security Policy directive: ")); @@ -286,6 +306,16 @@ bool ContentSecurityPolicyDirectiveList::allowInlineStyle(const String& contextU return m_reportOnly || checkInline(operativeDirective(m_styleSrc.get())); } +bool ContentSecurityPolicyDirectiveList::allowInlineStyleWithHash(const ContentSecurityPolicyHash& hash) const +{ + return checkHash(operativeDirective(m_styleSrc.get()), hash); +} + +bool ContentSecurityPolicyDirectiveList::allowStyleWithNonce(const String& nonce) const +{ + return checkNonce(operativeDirective(m_styleSrc.get()), nonce); +} + bool ContentSecurityPolicyDirectiveList::allowEval(JSC::ExecState* state, ContentSecurityPolicy::ReportingStatus reportingStatus) const { static NeverDestroyed<String> consoleMessage(ASCIILiteral("Refused to evaluate script because it violates the following Content Security Policy directive: ")); @@ -579,18 +609,22 @@ void ContentSecurityPolicyDirectiveList::addDirective(const String& name, const { ASSERT(!name.isEmpty()); - if (equalLettersIgnoringASCIICase(name, defaultSrc)) + if (equalLettersIgnoringASCIICase(name, defaultSrc)) { setCSPDirective<ContentSecurityPolicySourceListDirective>(name, value, m_defaultSrc); - else if (equalLettersIgnoringASCIICase(name, scriptSrc)) + m_policy.addHashAlgorithmsForInlineScripts(m_defaultSrc->hashAlgorithmsUsed()); + m_policy.addHashAlgorithmsForInlineStylesheets(m_defaultSrc->hashAlgorithmsUsed()); + } else if (equalLettersIgnoringASCIICase(name, scriptSrc)) { setCSPDirective<ContentSecurityPolicySourceListDirective>(name, value, m_scriptSrc); - else if (equalLettersIgnoringASCIICase(name, objectSrc)) + m_policy.addHashAlgorithmsForInlineScripts(m_scriptSrc->hashAlgorithmsUsed()); + } else if (equalLettersIgnoringASCIICase(name, styleSrc)) { + setCSPDirective<ContentSecurityPolicySourceListDirective>(name, value, m_styleSrc); + m_policy.addHashAlgorithmsForInlineStylesheets(m_styleSrc->hashAlgorithmsUsed()); + } else if (equalLettersIgnoringASCIICase(name, objectSrc)) setCSPDirective<ContentSecurityPolicySourceListDirective>(name, value, m_objectSrc); else if (equalLettersIgnoringASCIICase(name, frameSrc)) setCSPDirective<ContentSecurityPolicySourceListDirective>(name, value, m_frameSrc); else if (equalLettersIgnoringASCIICase(name, imgSrc)) setCSPDirective<ContentSecurityPolicySourceListDirective>(name, value, m_imgSrc); - else if (equalLettersIgnoringASCIICase(name, styleSrc)) - setCSPDirective<ContentSecurityPolicySourceListDirective>(name, value, m_styleSrc); else if (equalLettersIgnoringASCIICase(name, fontSrc)) setCSPDirective<ContentSecurityPolicySourceListDirective>(name, value, m_fontSrc); else if (equalLettersIgnoringASCIICase(name, mediaSrc)) diff --git a/Source/WebCore/page/csp/ContentSecurityPolicyDirectiveList.h b/Source/WebCore/page/csp/ContentSecurityPolicyDirectiveList.h index 2a98b2d09..997c2a5a6 100644 --- a/Source/WebCore/page/csp/ContentSecurityPolicyDirectiveList.h +++ b/Source/WebCore/page/csp/ContentSecurityPolicyDirectiveList.h @@ -28,6 +28,7 @@ #define ContentSecurityPolicyDirectiveList_h #include "ContentSecurityPolicy.h" +#include "ContentSecurityPolicyHash.h" #include "ContentSecurityPolicyMediaListDirective.h" #include "ContentSecurityPolicySourceListDirective.h" #include "URL.h" @@ -50,7 +51,11 @@ public: bool allowJavaScriptURLs(const String& contextURL, const WTF::OrdinalNumber& contextLine, ContentSecurityPolicy::ReportingStatus) const; bool allowInlineEventHandlers(const String& contextURL, const WTF::OrdinalNumber& contextLine, ContentSecurityPolicy::ReportingStatus) const; bool allowInlineScript(const String& contextURL, const WTF::OrdinalNumber& contextLine, ContentSecurityPolicy::ReportingStatus) const; + bool allowInlineScriptWithHash(const ContentSecurityPolicyHash&) const; + bool allowScriptWithNonce(const String& nonce) const; bool allowInlineStyle(const String& contextURL, const WTF::OrdinalNumber& contextLine, ContentSecurityPolicy::ReportingStatus) const; + bool allowInlineStyleWithHash(const ContentSecurityPolicyHash&) const; + bool allowStyleWithNonce(const String& nonce) const; bool allowEval(JSC::ExecState*, ContentSecurityPolicy::ReportingStatus) const; bool allowPluginType(const String& type, const String& typeAttribute, const URL&, ContentSecurityPolicy::ReportingStatus) const; @@ -87,11 +92,6 @@ private: ContentSecurityPolicySourceListDirective* operativeDirective(ContentSecurityPolicySourceListDirective*) const; void reportViolation(const String& directiveText, const String& effectiveDirective, const String& consoleMessage, const URL& blockedURL = URL(), const String& contextURL = String(), const WTF::OrdinalNumber& contextLine = WTF::OrdinalNumber::beforeFirst(), JSC::ExecState* = nullptr) const; - bool checkEval(ContentSecurityPolicySourceListDirective*) const; - bool checkInline(ContentSecurityPolicySourceListDirective*) const; - bool checkSource(ContentSecurityPolicySourceListDirective*, const URL&) const; - bool checkMediaType(ContentSecurityPolicyMediaListDirective*, const String& type, const String& typeAttribute) const; - void setEvalDisabledErrorMessage(const String& errorMessage) { m_evalDisabledErrorMessage = errorMessage; } bool checkEvalAndReportViolation(ContentSecurityPolicySourceListDirective*, const String& consoleMessage, const String& contextURL = String(), const WTF::OrdinalNumber& contextLine = WTF::OrdinalNumber::beforeFirst(), JSC::ExecState* = nullptr) const; diff --git a/Source/WebCore/page/csp/ContentSecurityPolicyHash.h b/Source/WebCore/page/csp/ContentSecurityPolicyHash.h new file mode 100644 index 000000000..12a94f261 --- /dev/null +++ b/Source/WebCore/page/csp/ContentSecurityPolicyHash.h @@ -0,0 +1,69 @@ +/* + * Copyright (C) 2016 Apple 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 APPLE INC. AND ITS CONTRIBUTORS ``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 INC. OR ITS 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. + */ + +#ifndef ContentSecurityPolicyHash_h +#define ContentSecurityPolicyHash_h + +#include <wtf/HashTraits.h> +#include <wtf/Hasher.h> +#include <wtf/Vector.h> + +namespace WebCore { + +// Keep this synchronized with the constant maximumContentSecurityPolicyDigestLength below. +enum class ContentSecurityPolicyHashAlgorithm { + SHA_256 = 1 << 0, + SHA_384 = 1 << 1, + SHA_512 = 1 << 2, +}; + +const size_t maximumContentSecurityPolicyDigestLength = 64; // bytes to hold SHA-512 digest + +typedef Vector<uint8_t> ContentSecurityPolicyDigest; +typedef std::pair<ContentSecurityPolicyHashAlgorithm, ContentSecurityPolicyDigest> ContentSecurityPolicyHash; + +} + +namespace WTF { + +template<> struct DefaultHash<WebCore::ContentSecurityPolicyHashAlgorithm> { typedef IntHash<WebCore::ContentSecurityPolicyHashAlgorithm> Hash; }; +template<> struct HashTraits<WebCore::ContentSecurityPolicyHashAlgorithm> : StrongEnumHashTraits<WebCore::ContentSecurityPolicyHashAlgorithm> { }; +template<> struct DefaultHash<WebCore::ContentSecurityPolicyDigest> { + struct Hash { + static unsigned hash(const WebCore::ContentSecurityPolicyDigest& digest) + { + return StringHasher::computeHashAndMaskTop8Bits(digest.data(), digest.size()); + } + static bool equal(const WebCore::ContentSecurityPolicyDigest& a, const WebCore::ContentSecurityPolicyDigest& b) + { + return a == b; + } + static const bool safeToCompareToEmptyOrDeleted = true; + }; +}; + +} + +#endif // ContentSecurityPolicyHash_h diff --git a/Source/WebCore/page/csp/ContentSecurityPolicySourceList.cpp b/Source/WebCore/page/csp/ContentSecurityPolicySourceList.cpp index 408b40e2b..8044ae8bf 100644 --- a/Source/WebCore/page/csp/ContentSecurityPolicySourceList.cpp +++ b/Source/WebCore/page/csp/ContentSecurityPolicySourceList.cpp @@ -33,6 +33,8 @@ #include "SecurityOrigin.h" #include "URL.h" #include <wtf/ASCIICType.h> +#include <wtf/NeverDestroyed.h> +#include <wtf/text/Base64.h> namespace WebCore { @@ -125,6 +127,16 @@ bool ContentSecurityPolicySourceList::matches(const URL& url) return false; } +bool ContentSecurityPolicySourceList::matches(const ContentSecurityPolicyHash& hash) const +{ + return m_hashes.contains(hash); +} + +bool ContentSecurityPolicySourceList::matches(const String& nonce) const +{ + return m_nonces.contains(nonce); +} + // source-list = *WSP [ source *( 1*WSP source ) *WSP ] // / *WSP "'none'" *WSP // @@ -145,6 +157,12 @@ void ContentSecurityPolicySourceList::parse(const UChar* begin, const UChar* end bool hostHasWildcard = false; bool portHasWildcard = false; + if (parseNonceSource(beginSource, position)) + continue; + + if (parseHashSource(beginSource, position)) + continue; + if (parseSource(beginSource, position, scheme, host, port, path, hostHasWildcard, portHasWildcard)) { // Wildcard hosts and keyword sources ('self', 'unsafe-inline', // etc.) aren't stored in m_list, but as attributes on the source @@ -385,4 +403,96 @@ bool ContentSecurityPolicySourceList::parsePort(const UChar* begin, const UChar* return ok; } +static bool isBase64Character(UChar c) +{ + return isASCIIAlphanumeric(c) || c == '+' || c == '/' || c == '-' || c == '_'; +} + +// Match Blink's behavior of allowing an equal sign to appear anywhere in the value of the nonce +// even though this does not match the behavior of Content Security Policy Level 3 spec., +// <https://w3c.github.io/webappsec-csp/> (29 February 2016). +static bool isNonceCharacter(UChar c) +{ + return isBase64Character(c) || c == '='; +} + +// nonce-source = "'nonce-" nonce-value "'" +// nonce-value = base64-value +bool ContentSecurityPolicySourceList::parseNonceSource(const UChar* begin, const UChar* end) +{ + static NeverDestroyed<String> noncePrefix("'nonce-", String::ConstructFromLiteral); + if (!StringView(begin, end - begin).startsWithIgnoringASCIICase(noncePrefix.get())) + return false; + const UChar* position = begin + noncePrefix.get().length(); + const UChar* beginNonceValue = position; + skipWhile<UChar, isNonceCharacter>(position, end); + if (position >= end || position == beginNonceValue || *position != '\'') + return false; + m_nonces.add(String(beginNonceValue, position - beginNonceValue)); + return true; +} + +static bool parseHashAlgorithmAdvancingPosition(const UChar*& position, size_t length, ContentSecurityPolicyHashAlgorithm& algorithm) +{ + static struct { + NeverDestroyed<String> label; + ContentSecurityPolicyHashAlgorithm algorithm; + } labelToHashAlgorithmTable[] { + { ASCIILiteral("sha256"), ContentSecurityPolicyHashAlgorithm::SHA_256 }, + { ASCIILiteral("sha384"), ContentSecurityPolicyHashAlgorithm::SHA_384 }, + { ASCIILiteral("sha512"), ContentSecurityPolicyHashAlgorithm::SHA_512 }, + }; + + StringView stringView(position, length); + for (auto& entry : labelToHashAlgorithmTable) { + String& label = entry.label.get(); + if (!stringView.startsWithIgnoringASCIICase(label)) + continue; + position += label.length(); + algorithm = entry.algorithm; + return true; + } + return false; +} + +// hash-source = "'" hash-algorithm "-" base64-value "'" +// hash-algorithm = "sha256" / "sha384" / "sha512" +// base64-value = 1*( ALPHA / DIGIT / "+" / "/" / "-" / "_" )*2( "=" ) +bool ContentSecurityPolicySourceList::parseHashSource(const UChar* begin, const UChar* end) +{ + if (begin == end) + return false; + + const UChar* position = begin; + if (!skipExactly<UChar>(position, end, '\'')) + return false; + + ContentSecurityPolicyHashAlgorithm algorithm; + if (!parseHashAlgorithmAdvancingPosition(position, end - position, algorithm)) + return false; + + if (!skipExactly<UChar>(position, end, '-')) + return false; + + const UChar* beginHashValue = position; + skipWhile<UChar, isBase64Character>(position, end); + skipExactly<UChar>(position, end, '='); + skipExactly<UChar>(position, end, '='); + if (position >= end || position == beginHashValue || *position != '\'') + return false; + Vector<uint8_t> digest; + StringView hashValue(beginHashValue, position - beginHashValue); // base64url or base64 encoded + // FIXME: Normalize Base64URL to Base64 instead of decoding twice. See <https://bugs.webkit.org/show_bug.cgi?id=155186>. + if (!base64Decode(hashValue.toStringWithoutCopying(), digest, Base64ValidatePadding)) { + if (!base64URLDecode(hashValue.toStringWithoutCopying(), digest)) + return false; + } + if (digest.size() > maximumContentSecurityPolicyDigestLength) + return false; + + m_hashes.add(std::make_pair(algorithm, digest)); + m_hashAlgorithmsUsed |= algorithm; + return true; +} + } // namespace WebCore diff --git a/Source/WebCore/page/csp/ContentSecurityPolicySourceList.h b/Source/WebCore/page/csp/ContentSecurityPolicySourceList.h index fdb9b7e09..811b6cec1 100644 --- a/Source/WebCore/page/csp/ContentSecurityPolicySourceList.h +++ b/Source/WebCore/page/csp/ContentSecurityPolicySourceList.h @@ -27,8 +27,11 @@ #ifndef ContentSecurityPolicySourceList_h #define ContentSecurityPolicySourceList_h +#include "ContentSecurityPolicyHash.h" #include "ContentSecurityPolicySource.h" -#include <wtf/Vector.h> +#include <wtf/HashSet.h> +#include <wtf/OptionSet.h> +#include <wtf/text/StringHash.h> #include <wtf/text/WTFString.h> namespace WebCore { @@ -41,8 +44,14 @@ public: ContentSecurityPolicySourceList(const ContentSecurityPolicy&, const String& directiveName); void parse(const String&); + bool matches(const URL&); - bool allowInline() const { return m_allowInline; } + bool matches(const ContentSecurityPolicyHash&) const; + bool matches(const String& nonce) const; + + OptionSet<ContentSecurityPolicyHashAlgorithm> hashAlgorithmsUsed() const { return m_hashAlgorithmsUsed; } + + bool allowInline() const { return m_allowInline && m_hashes.isEmpty() && m_nonces.isEmpty(); } bool allowEval() const { return m_allowEval; } bool allowSelf() const { return m_allowSelf; } @@ -55,10 +64,17 @@ private: bool parsePort(const UChar* begin, const UChar* end, int& port, bool& portHasWildcard); bool parsePath(const UChar* begin, const UChar* end, String& path); + bool parseNonceSource(const UChar* begin, const UChar* end); + bool isProtocolAllowedByStar(const URL&) const; + bool parseHashSource(const UChar* begin, const UChar* end); + const ContentSecurityPolicy& m_policy; Vector<ContentSecurityPolicySource> m_list; + HashSet<String> m_nonces; + HashSet<ContentSecurityPolicyHash> m_hashes; + OptionSet<ContentSecurityPolicyHashAlgorithm> m_hashAlgorithmsUsed; String m_directiveName; bool m_allowSelf { false }; bool m_allowStar { false }; diff --git a/Source/WebCore/page/csp/ContentSecurityPolicySourceListDirective.cpp b/Source/WebCore/page/csp/ContentSecurityPolicySourceListDirective.cpp index 89133d4eb..4c4f11c42 100644 --- a/Source/WebCore/page/csp/ContentSecurityPolicySourceListDirective.cpp +++ b/Source/WebCore/page/csp/ContentSecurityPolicySourceListDirective.cpp @@ -47,4 +47,14 @@ bool ContentSecurityPolicySourceListDirective::allows(const URL& url) return m_sourceList.matches(url); } +bool ContentSecurityPolicySourceListDirective::allows(const String& nonce) const +{ + return m_sourceList.matches(nonce); +} + +bool ContentSecurityPolicySourceListDirective::allows(const ContentSecurityPolicyHash& hash) const +{ + return m_sourceList.matches(hash); +} + } // namespace WebCore diff --git a/Source/WebCore/page/csp/ContentSecurityPolicySourceListDirective.h b/Source/WebCore/page/csp/ContentSecurityPolicySourceListDirective.h index 9d4e2114c..ac650052d 100644 --- a/Source/WebCore/page/csp/ContentSecurityPolicySourceListDirective.h +++ b/Source/WebCore/page/csp/ContentSecurityPolicySourceListDirective.h @@ -39,9 +39,13 @@ public: ContentSecurityPolicySourceListDirective(const String& name, const String& value, const ContentSecurityPolicy&); bool allows(const URL&); + bool allows(const ContentSecurityPolicyHash&) const; + bool allows(const String& nonce) const; bool allowInline() const { return m_sourceList.allowInline(); } bool allowEval() const { return m_sourceList.allowEval(); } + OptionSet<ContentSecurityPolicyHashAlgorithm> hashAlgorithmsUsed() const { return m_sourceList.hashAlgorithmsUsed(); } + private: ContentSecurityPolicySourceList m_sourceList; }; diff --git a/Source/WebCore/page/qt/EventHandlerQt.cpp b/Source/WebCore/page/qt/EventHandlerQt.cpp index 240ea75eb..b717b1f54 100644 --- a/Source/WebCore/page/qt/EventHandlerQt.cpp +++ b/Source/WebCore/page/qt/EventHandlerQt.cpp @@ -49,6 +49,7 @@ #include "PlatformWheelEvent.h" #include "RenderWidget.h" #include "Scrollbar.h" +#include <QCoreApplication> namespace WebCore { @@ -123,7 +124,14 @@ bool EventHandler::passMouseReleaseEventToSubframe(MouseEventWithHitTestResults& unsigned EventHandler::accessKeyModifiers() { #if OS(DARWIN) - return PlatformEvent::CtrlKey | PlatformEvent::AltKey; + // On macOS, the ControlModifier value corresponds + // to the Command keys on the keyboard, + // and the MetaModifier value corresponds to the Control keys. + // See http://doc.qt.io/qt-5/qt.html#KeyboardModifier-enum + if (UNLIKELY(QCoreApplication::testAttribute(Qt::AA_MacDontSwapCtrlAndMeta))) + return PlatformEvent::CtrlKey | PlatformEvent::AltKey; + else + return PlatformEvent::MetaKey | PlatformEvent::AltKey; #else return PlatformEvent::AltKey; #endif |