diff options
Diffstat (limited to 'dom/security')
67 files changed, 1929 insertions, 102 deletions
diff --git a/dom/security/nsCSPContext.cpp b/dom/security/nsCSPContext.cpp index 815c7734d..979bd915f 100644 --- a/dom/security/nsCSPContext.cpp +++ b/dom/security/nsCSPContext.cpp @@ -156,10 +156,13 @@ nsCSPContext::ShouldLoad(nsContentPolicyType aContentType, nsAutoString nonce; bool parserCreated = false; if (!isPreload) { - nsCOMPtr<nsIDOMHTMLElement> htmlElement = do_QueryInterface(aRequestContext); - if (htmlElement) { - rv = htmlElement->GetAttribute(NS_LITERAL_STRING("nonce"), nonce); - NS_ENSURE_SUCCESS(rv, rv); + if (aContentType == nsIContentPolicy::TYPE_SCRIPT || + aContentType == nsIContentPolicy::TYPE_STYLESHEET) { + nsCOMPtr<nsIDOMHTMLElement> htmlElement = do_QueryInterface(aRequestContext); + if (htmlElement) { + rv = htmlElement->GetAttribute(NS_LITERAL_STRING("nonce"), nonce); + NS_ENSURE_SUCCESS(rv, rv); + } } nsCOMPtr<nsIScriptElement> script = do_QueryInterface(aRequestContext); @@ -168,9 +171,10 @@ nsCSPContext::ShouldLoad(nsContentPolicyType aContentType, } } - // aExtra is only non-null if the channel got redirected. - bool wasRedirected = (aExtra != nullptr); + // aExtra holds the original URI of the channel if the + // channel got redirected (until we fix Bug 1332422). nsCOMPtr<nsIURI> originalURI = do_QueryInterface(aExtra); + bool wasRedirected = originalURI; bool permitted = permitsInternal(dir, aContentLocation, @@ -216,14 +220,6 @@ nsCSPContext::permitsInternal(CSPDirective aDir, nsAutoString violatedDirective; for (uint32_t p = 0; p < mPolicies.Length(); p++) { - - // According to the W3C CSP spec, frame-ancestors checks are ignored for - // report-only policies (when "monitoring"). - if (aDir == nsIContentSecurityPolicy::FRAME_ANCESTORS_DIRECTIVE && - mPolicies[p]->getReportOnlyFlag()) { - continue; - } - if (!mPolicies[p]->permits(aDir, aContentLocation, aNonce, @@ -343,6 +339,20 @@ nsCSPContext::GetBlockAllMixedContent(bool *outBlockAllMixedContent) } NS_IMETHODIMP +nsCSPContext::GetEnforcesFrameAncestors(bool *outEnforcesFrameAncestors) +{ + *outEnforcesFrameAncestors = false; + for (uint32_t i = 0; i < mPolicies.Length(); i++) { + if (!mPolicies[i]->getReportOnlyFlag() && + mPolicies[i]->hasDirective(nsIContentSecurityPolicy::FRAME_ANCESTORS_DIRECTIVE)) { + *outEnforcesFrameAncestors = true; + return NS_OK; + } + } + return NS_OK; +} + +NS_IMETHODIMP nsCSPContext::GetReferrerPolicy(uint32_t* outPolicy, bool* outIsSet) { *outIsSet = false; diff --git a/dom/security/nsCSPParser.cpp b/dom/security/nsCSPParser.cpp index a662c9cd1..a0eba6918 100644 --- a/dom/security/nsCSPParser.cpp +++ b/dom/security/nsCSPParser.cpp @@ -136,6 +136,9 @@ nsCSPParser::nsCSPParser(cspTokens& aTokens, , mUnsafeInlineKeywordSrc(nullptr) , mChildSrc(nullptr) , mFrameSrc(nullptr) + , mWorkerSrc(nullptr) + , mScriptSrc(nullptr) + , mParsingFrameAncestorsDir(false) , mTokens(aTokens) , mSelfURI(aSelfURI) , mPolicy(nullptr) @@ -531,7 +534,7 @@ nsCSPParser::keywordSource() // Special case handling for 'self' which is not stored internally as a keyword, // but rather creates a nsCSPHostSrc using the selfURI if (CSP_IsKeyword(mCurToken, CSP_SELF)) { - return CSP_CreateHostSrcFromURI(mSelfURI); + return CSP_CreateHostSrcFromSelfURI(mSelfURI); } if (CSP_IsKeyword(mCurToken, CSP_STRICT_DYNAMIC)) { @@ -807,6 +810,7 @@ nsCSPParser::sourceExpression() if (nsCSPHostSrc *cspHost = hostSource()) { // Do not forget to set the parsed scheme. cspHost->setScheme(parsedScheme); + cspHost->setWithinFrameAncestorsDir(mParsingFrameAncestorsDir); return cspHost; } // Error was reported in hostSource() @@ -1097,21 +1101,37 @@ nsCSPParser::directiveName() return new nsUpgradeInsecureDirective(CSP_StringToCSPDirective(mCurToken)); } - // child-src has it's own class to handle frame-src if necessary + // child-src by itself is deprecatd but will be enforced + // * for workers (if worker-src is not explicitly specified) + // * for frames (if frame-src is not explicitly specified) if (CSP_IsDirective(mCurToken, nsIContentSecurityPolicy::CHILD_SRC_DIRECTIVE)) { + const char16_t* params[] = { mCurToken.get() }; + logWarningErrorToConsole(nsIScriptError::warningFlag, + "deprecatedChildSrcDirective", + params, ArrayLength(params)); mChildSrc = new nsCSPChildSrcDirective(CSP_StringToCSPDirective(mCurToken)); return mChildSrc; } - // if we have a frame-src, cache it so we can decide whether to use child-src + // if we have a frame-src, cache it so we can discard child-src for frames if (CSP_IsDirective(mCurToken, nsIContentSecurityPolicy::FRAME_SRC_DIRECTIVE)) { - const char16_t* params[] = { mCurToken.get(), NS_LITERAL_STRING("child-src").get() }; - logWarningErrorToConsole(nsIScriptError::warningFlag, "deprecatedDirective", - params, ArrayLength(params)); mFrameSrc = new nsCSPDirective(CSP_StringToCSPDirective(mCurToken)); return mFrameSrc; } + // if we have a worker-src, cache it so we can discard child-src for workers + if (CSP_IsDirective(mCurToken, nsIContentSecurityPolicy::WORKER_SRC_DIRECTIVE)) { + mWorkerSrc = new nsCSPDirective(CSP_StringToCSPDirective(mCurToken)); + return mWorkerSrc; + } + + // if we have a script-src, cache it as a fallback for worker-src + // in case child-src is not present + if (CSP_IsDirective(mCurToken, nsIContentSecurityPolicy::SCRIPT_SRC_DIRECTIVE)) { + mScriptSrc = new nsCSPScriptSrcDirective(CSP_StringToCSPDirective(mCurToken)); + return mScriptSrc; + } + if (CSP_IsDirective(mCurToken, nsIContentSecurityPolicy::REQUIRE_SRI_FOR)) { return new nsRequireSRIForDirective(CSP_StringToCSPDirective(mCurToken)); } @@ -1209,6 +1229,9 @@ nsCSPParser::directive() mStrictDynamic = false; mUnsafeInlineKeywordSrc = nullptr; + mParsingFrameAncestorsDir = + CSP_IsDirective(mCurDir[0], nsIContentSecurityPolicy::FRAME_ANCESTORS_DIRECTIVE); + // Try to parse all the srcs by handing the array off to directiveValue nsTArray<nsCSPBaseSrc*> srcs; directiveValue(srcs); @@ -1285,9 +1308,22 @@ nsCSPParser::policy() directive(); } - if (mChildSrc && !mFrameSrc) { - // if we have a child-src, it handles frame-src too, unless frame-src is set - mChildSrc->setHandleFrameSrc(); + if (mChildSrc) { + if (!mFrameSrc) { + // if frame-src is specified explicitly for that policy than child-src should + // not restrict frames; if not, than child-src needs to restrict frames. + mChildSrc->setRestrictFrames(); + } + if (!mWorkerSrc) { + // if worker-src is specified explicitly for that policy than child-src should + // not restrict workers; if not, than child-src needs to restrict workers. + mChildSrc->setRestrictWorkers(); + } + } + // if script-src is specified, but not worker-src and also no child-src, then + // script-src has to govern workers. + if (mScriptSrc && !mWorkerSrc && !mChildSrc) { + mScriptSrc->setRestrictWorkers(); } return mPolicy; diff --git a/dom/security/nsCSPParser.h b/dom/security/nsCSPParser.h index 30954b10f..d500a1c18 100644 --- a/dom/security/nsCSPParser.h +++ b/dom/security/nsCSPParser.h @@ -243,14 +243,21 @@ class nsCSPParser { bool mStrictDynamic; // false, if 'strict-dynamic' is not defined nsCSPKeywordSrc* mUnsafeInlineKeywordSrc; // null, otherwise invlidate() - // cache variables for child-src and frame-src directive handling. - // frame-src is deprecated in favor of child-src, however if we - // see a frame-src directive, it takes precedence for frames and iframes. - // At the end of parsing, if we have a child-src directive, we need to - // decide whether it will handle frames, or if there is a frame-src we - // should honor instead. - nsCSPChildSrcDirective* mChildSrc; - nsCSPDirective* mFrameSrc; + // cache variables for child-src, frame-src and worker-src handling; + // in CSP 3 child-src is deprecated. For backwards compatibility + // child-src needs to restrict: + // (*) frames, in case frame-src is not expicitly specified + // (*) workers, in case worker-src is not expicitly specified + // If neither worker-src, nor child-src is present, then script-src + // needs to govern workers. + nsCSPChildSrcDirective* mChildSrc; + nsCSPDirective* mFrameSrc; + nsCSPDirective* mWorkerSrc; + nsCSPScriptSrcDirective* mScriptSrc; + + // cache variable to let nsCSPHostSrc know that it's within + // the frame-ancestors directive. + bool mParsingFrameAncestorsDir; cspTokens mTokens; nsIURI* mSelfURI; diff --git a/dom/security/nsCSPUtils.cpp b/dom/security/nsCSPUtils.cpp index 63b4aae2c..49832f8f4 100644 --- a/dom/security/nsCSPUtils.cpp +++ b/dom/security/nsCSPUtils.cpp @@ -230,7 +230,7 @@ CSP_ContentTypeToDirective(nsContentPolicyType aType) case nsIContentPolicy::TYPE_INTERNAL_WORKER: case nsIContentPolicy::TYPE_INTERNAL_SHARED_WORKER: case nsIContentPolicy::TYPE_INTERNAL_SERVICE_WORKER: - return nsIContentSecurityPolicy::CHILD_SRC_DIRECTIVE; + return nsIContentSecurityPolicy::WORKER_SRC_DIRECTIVE; case nsIContentPolicy::TYPE_SUBDOCUMENT: return nsIContentSecurityPolicy::FRAME_SRC_DIRECTIVE; @@ -266,20 +266,21 @@ CSP_ContentTypeToDirective(nsContentPolicyType aType) } nsCSPHostSrc* -CSP_CreateHostSrcFromURI(nsIURI* aURI) +CSP_CreateHostSrcFromSelfURI(nsIURI* aSelfURI) { // Create the host first nsCString host; - aURI->GetHost(host); + aSelfURI->GetAsciiHost(host); nsCSPHostSrc *hostsrc = new nsCSPHostSrc(NS_ConvertUTF8toUTF16(host)); + hostsrc->setGeneratedFromSelfKeyword(); // Add the scheme. nsCString scheme; - aURI->GetScheme(scheme); + aSelfURI->GetScheme(scheme); hostsrc->setScheme(NS_ConvertUTF8toUTF16(scheme)); int32_t port; - aURI->GetPort(&port); + aSelfURI->GetPort(&port); // Only add port if it's not default port. if (port > 0) { nsAutoString portStr; @@ -348,13 +349,17 @@ CSP_IsQuotelessKeyword(const nsAString& aKey) * @param aUpgradeInsecure * Whether the policy makes use of the directive * 'upgrade-insecure-requests'. + * @param aFromSelfURI + * Whether a scheme was generated from the keyword 'self' + * which then allows schemeless sources to match ws and wss. */ bool permitsScheme(const nsAString& aEnforcementScheme, nsIURI* aUri, bool aReportOnly, - bool aUpgradeInsecure) + bool aUpgradeInsecure, + bool aFromSelfURI) { nsAutoCString scheme; nsresult rv = aUri->GetScheme(scheme); @@ -373,8 +378,20 @@ permitsScheme(const nsAString& aEnforcementScheme, // allow scheme-less sources where the protected resource is http // and the load is https, see: // http://www.w3.org/TR/CSP2/#match-source-expression - if (aEnforcementScheme.EqualsASCII("http") && - scheme.EqualsASCII("https")) { + if (aEnforcementScheme.EqualsASCII("http")) { + if (scheme.EqualsASCII("https")) { + return true; + } + if ((scheme.EqualsASCII("ws") || scheme.EqualsASCII("wss")) && aFromSelfURI) { + return true; + } + } + if (aEnforcementScheme.EqualsASCII("https")) { + if (scheme.EqualsLiteral("wss") && aFromSelfURI) { + return true; + } + } + if (aEnforcementScheme.EqualsASCII("ws") && scheme.EqualsASCII("wss")) { return true; } @@ -483,7 +500,7 @@ nsCSPSchemeSrc::permits(nsIURI* aUri, const nsAString& aNonce, bool aWasRedirect if (mInvalidated) { return false; } - return permitsScheme(mScheme, aUri, aReportOnly, aUpgradeInsecure); + return permitsScheme(mScheme, aUri, aReportOnly, aUpgradeInsecure, false); } bool @@ -503,6 +520,8 @@ nsCSPSchemeSrc::toString(nsAString& outStr) const nsCSPHostSrc::nsCSPHostSrc(const nsAString& aHost) : mHost(aHost) + , mGeneratedFromSelfKeyword(false) + , mWithinFrameAncstorsDir(false) { ToLowerCase(mHost); } @@ -611,7 +630,7 @@ nsCSPHostSrc::permits(nsIURI* aUri, const nsAString& aNonce, bool aWasRedirected // http://www.w3.org/TR/CSP11/#match-source-expression // 4.3) scheme matching: Check if the scheme matches. - if (!permitsScheme(mScheme, aUri, aReportOnly, aUpgradeInsecure)) { + if (!permitsScheme(mScheme, aUri, aReportOnly, aUpgradeInsecure, mGeneratedFromSelfKeyword)) { return false; } @@ -642,7 +661,7 @@ nsCSPHostSrc::permits(nsIURI* aUri, const nsAString& aNonce, bool aWasRedirected // Before we can check if the host matches, we have to // extract the host part from aUri. nsAutoCString uriHost; - nsresult rv = aUri->GetHost(uriHost); + nsresult rv = aUri->GetAsciiHost(uriHost); NS_ENSURE_SUCCESS(rv, false); nsString decodedUriHost; @@ -686,6 +705,11 @@ nsCSPHostSrc::permits(nsIURI* aUri, const nsAString& aNonce, bool aWasRedirected rv = url->GetFilePath(uriPath); NS_ENSURE_SUCCESS(rv, false); + if (mWithinFrameAncstorsDir) { + // no path matching for frame-ancestors to not leak any path information. + return true; + } + nsString decodedUriPath; CSP_PercentDecodeStr(NS_ConvertUTF8toUTF16(uriPath), decodedUriPath); @@ -1160,6 +1184,11 @@ nsCSPDirective::toDomCSPStruct(mozilla::dom::CSP& outCSP) const outCSP.mSandbox.Value() = mozilla::Move(srcs); return; + case nsIContentSecurityPolicy::WORKER_SRC_DIRECTIVE: + outCSP.mWorker_src.Construct(); + outCSP.mWorker_src.Value() = mozilla::Move(srcs); + return; + // REFERRER_DIRECTIVE and REQUIRE_SRI_FOR are handled in nsCSPPolicy::toDomCSPStruct() default: @@ -1212,7 +1241,8 @@ bool nsCSPDirective::equals(CSPDirective aDirective) const nsCSPChildSrcDirective::nsCSPChildSrcDirective(CSPDirective aDirective) : nsCSPDirective(aDirective) - , mHandleFrameSrc(false) + , mRestrictFrames(false) + , mRestrictWorkers(false) { } @@ -1220,30 +1250,58 @@ nsCSPChildSrcDirective::~nsCSPChildSrcDirective() { } -void nsCSPChildSrcDirective::setHandleFrameSrc() -{ - mHandleFrameSrc = true; -} - bool nsCSPChildSrcDirective::restrictsContentType(nsContentPolicyType aContentType) const { if (aContentType == nsIContentPolicy::TYPE_SUBDOCUMENT) { - return mHandleFrameSrc; + return mRestrictFrames; } - - return (aContentType == nsIContentPolicy::TYPE_INTERNAL_WORKER - || aContentType == nsIContentPolicy::TYPE_INTERNAL_SHARED_WORKER - || aContentType == nsIContentPolicy::TYPE_INTERNAL_SERVICE_WORKER - ); + if (aContentType == nsIContentPolicy::TYPE_INTERNAL_WORKER || + aContentType == nsIContentPolicy::TYPE_INTERNAL_SHARED_WORKER || + aContentType == nsIContentPolicy::TYPE_INTERNAL_SERVICE_WORKER) { + return mRestrictWorkers; + } + return false; } bool nsCSPChildSrcDirective::equals(CSPDirective aDirective) const { if (aDirective == nsIContentSecurityPolicy::FRAME_SRC_DIRECTIVE) { - return mHandleFrameSrc; + return mRestrictFrames; } + if (aDirective == nsIContentSecurityPolicy::WORKER_SRC_DIRECTIVE) { + return mRestrictWorkers; + } + return (mDirective == aDirective); +} + +/* =============== nsCSPScriptSrcDirective ============= */ + +nsCSPScriptSrcDirective::nsCSPScriptSrcDirective(CSPDirective aDirective) + : nsCSPDirective(aDirective) + , mRestrictWorkers(false) +{ +} - return (aDirective == nsIContentSecurityPolicy::CHILD_SRC_DIRECTIVE); +nsCSPScriptSrcDirective::~nsCSPScriptSrcDirective() +{ +} + +bool nsCSPScriptSrcDirective::restrictsContentType(nsContentPolicyType aContentType) const +{ + if (aContentType == nsIContentPolicy::TYPE_INTERNAL_WORKER || + aContentType == nsIContentPolicy::TYPE_INTERNAL_SHARED_WORKER || + aContentType == nsIContentPolicy::TYPE_INTERNAL_SERVICE_WORKER) { + return mRestrictWorkers; + } + return mDirective == CSP_ContentTypeToDirective(aContentType); +} + +bool nsCSPScriptSrcDirective::equals(CSPDirective aDirective) const +{ + if (aDirective == nsIContentSecurityPolicy::WORKER_SRC_DIRECTIVE) { + return mRestrictWorkers; + } + return (mDirective == aDirective); } /* =============== nsBlockAllMixedContentDirective ============= */ diff --git a/dom/security/nsCSPUtils.h b/dom/security/nsCSPUtils.h index b33c8932a..91096712a 100644 --- a/dom/security/nsCSPUtils.h +++ b/dom/security/nsCSPUtils.h @@ -93,7 +93,8 @@ static const char* CSPStrDirectives[] = { "child-src", // CHILD_SRC_DIRECTIVE "block-all-mixed-content", // BLOCK_ALL_MIXED_CONTENT "require-sri-for", // REQUIRE_SRI_FOR - "sandbox" // SANDBOX_DIRECTIVE + "sandbox", // SANDBOX_DIRECTIVE + "worker-src" // WORKER_SRC_DIRECTIVE }; inline const char* CSP_CSPDirectiveToString(CSPDirective aDir) @@ -186,7 +187,7 @@ nsresult CSP_AppendCSPFromHeader(nsIContentSecurityPolicy* aCsp, class nsCSPHostSrc; -nsCSPHostSrc* CSP_CreateHostSrcFromURI(nsIURI* aURI); +nsCSPHostSrc* CSP_CreateHostSrcFromSelfURI(nsIURI* aSelfURI); bool CSP_IsValidDirective(const nsAString& aDir); bool CSP_IsDirective(const nsAString& aValue, CSPDirective aDir); bool CSP_IsKeyword(const nsAString& aValue, enum CSPKeyword aKey); @@ -256,6 +257,12 @@ class nsCSPHostSrc : public nsCSPBaseSrc { void setPort(const nsAString& aPort); void appendPath(const nsAString &aPath); + inline void setGeneratedFromSelfKeyword() const + { mGeneratedFromSelfKeyword = true;} + + inline void setWithinFrameAncestorsDir(bool aValue) const + { mWithinFrameAncstorsDir = aValue; } + inline void getScheme(nsAString& outStr) const { outStr.Assign(mScheme); }; @@ -273,6 +280,8 @@ class nsCSPHostSrc : public nsCSPBaseSrc { nsString mHost; nsString mPort; nsString mPath; + mutable bool mGeneratedFromSelfKeyword; + mutable bool mWithinFrameAncstorsDir; }; /* =============== nsCSPKeywordSrc ============ */ @@ -437,7 +446,7 @@ class nsCSPDirective { bool visitSrcs(nsCSPSrcVisitor* aVisitor) const; - private: + protected: CSPDirective mDirective; nsTArray<nsCSPBaseSrc*> mSrcs; }; @@ -445,26 +454,52 @@ class nsCSPDirective { /* =============== nsCSPChildSrcDirective ============= */ /* - * In CSP 2, the child-src directive covers both workers and - * subdocuments (i.e., frames and iframes). Workers were removed - * from script-src, but frames can be controlled by either child-src - * or frame-src directives, so child-src needs to know whether it should - * also restrict frames. When both are present the frame-src directive - * takes precedent. + * In CSP 3 child-src is deprecated. For backwards compatibility + * child-src needs to restrict: + * (*) frames, in case frame-src is not expicitly specified + * (*) workers, in case worker-src is not expicitly specified */ class nsCSPChildSrcDirective : public nsCSPDirective { public: explicit nsCSPChildSrcDirective(CSPDirective aDirective); virtual ~nsCSPChildSrcDirective(); - void setHandleFrameSrc(); + void setRestrictFrames() + { mRestrictFrames = true; } + + void setRestrictWorkers() + { mRestrictWorkers = true; } + + virtual bool restrictsContentType(nsContentPolicyType aContentType) const; + + virtual bool equals(CSPDirective aDirective) const; + + private: + bool mRestrictFrames; + bool mRestrictWorkers; +}; + +/* =============== nsCSPScriptSrcDirective ============= */ + +/* + * In CSP 3 worker-src restricts workers, for backwards compatibily + * script-src has to restrict workers as the ultimate fallback if + * neither worker-src nor child-src is present in a CSP. + */ +class nsCSPScriptSrcDirective : public nsCSPDirective { + public: + explicit nsCSPScriptSrcDirective(CSPDirective aDirective); + virtual ~nsCSPScriptSrcDirective(); + + void setRestrictWorkers() + { mRestrictWorkers = true; } virtual bool restrictsContentType(nsContentPolicyType aContentType) const; virtual bool equals(CSPDirective aDirective) const; private: - bool mHandleFrameSrc; + bool mRestrictWorkers; }; /* =============== nsBlockAllMixedContentDirective === */ diff --git a/dom/security/nsContentSecurityManager.cpp b/dom/security/nsContentSecurityManager.cpp index c4e1ed8e1..0cc4933fe 100644 --- a/dom/security/nsContentSecurityManager.cpp +++ b/dom/security/nsContentSecurityManager.cpp @@ -1,8 +1,10 @@ #include "nsContentSecurityManager.h" +#include "nsEscape.h" #include "nsIChannel.h" #include "nsIHttpChannelInternal.h" #include "nsIStreamListener.h" #include "nsILoadInfo.h" +#include "nsIOService.h" #include "nsContentUtils.h" #include "nsCORSListenerProxy.h" #include "nsIStreamListener.h" @@ -10,11 +12,86 @@ #include "nsMixedContentBlocker.h" #include "mozilla/dom/Element.h" +#include "mozilla/dom/TabChild.h" NS_IMPL_ISUPPORTS(nsContentSecurityManager, nsIContentSecurityManager, nsIChannelEventSink) +/* static */ bool +nsContentSecurityManager::AllowTopLevelNavigationToDataURI(nsIChannel* aChannel) +{ + // Let's block all toplevel document navigations to a data: URI. + // In all cases where the toplevel document is navigated to a + // data: URI the triggeringPrincipal is a codeBasePrincipal, or + // a NullPrincipal. In other cases, e.g. typing a data: URL into + // the URL-Bar, the triggeringPrincipal is a SystemPrincipal; + // we don't want to block those loads. Only exception, loads coming + // from an external applicaton (e.g. Thunderbird) don't load + // using a codeBasePrincipal, but we want to block those loads. + if (!mozilla::net::nsIOService::BlockToplevelDataUriNavigations()) { + return true; + } + nsCOMPtr<nsILoadInfo> loadInfo = aChannel->GetLoadInfo(); + if (!loadInfo) { + return true; + } + if (loadInfo->GetExternalContentPolicyType() != nsIContentPolicy::TYPE_DOCUMENT) { + return true; + } + if (loadInfo->GetForceAllowDataURI()) { + // if the loadinfo explicitly allows the data URI navigation, let's allow it now + return true; + } + nsCOMPtr<nsIURI> uri; + nsresult rv = NS_GetFinalChannelURI(aChannel, getter_AddRefs(uri)); + NS_ENSURE_SUCCESS(rv, true); + bool isDataURI = + (NS_SUCCEEDED(uri->SchemeIs("data", &isDataURI)) && isDataURI); + if (!isDataURI) { + return true; + } + // Whitelist data: images as long as they are not SVGs + nsAutoCString filePath; + uri->GetFilePath(filePath); + if (StringBeginsWith(filePath, NS_LITERAL_CSTRING("image/")) && + !StringBeginsWith(filePath, NS_LITERAL_CSTRING("image/svg+xml"))) { + return true; + } + // Whitelist data: PDFs and JSON + if (StringBeginsWith(filePath, NS_LITERAL_CSTRING("application/pdf")) || + StringBeginsWith(filePath, NS_LITERAL_CSTRING("application/json"))) { + return true; + } + // Redirecting to a toplevel data: URI is not allowed, hence we make + // sure the RedirectChain is empty. + if (!loadInfo->GetLoadTriggeredFromExternal() && + nsContentUtils::IsSystemPrincipal(loadInfo->TriggeringPrincipal()) && + loadInfo->RedirectChain().IsEmpty()) { + return true; + } + nsAutoCString dataSpec; + uri->GetSpec(dataSpec); + if (dataSpec.Length() > 50) { + dataSpec.Truncate(50); + dataSpec.AppendLiteral("..."); + } + nsCOMPtr<nsITabChild> tabChild = do_QueryInterface(loadInfo->ContextForTopLevelLoad()); + nsCOMPtr<nsIDocument> doc; + if (tabChild) { + doc = static_cast<mozilla::dom::TabChild*>(tabChild.get())->GetDocument(); + } + NS_ConvertUTF8toUTF16 specUTF16(NS_UnescapeURL(dataSpec)); + const char16_t* params[] = { specUTF16.get() }; + nsContentUtils::ReportToConsole(nsIScriptError::warningFlag, + NS_LITERAL_CSTRING("DATA_URI_BLOCKED"), + doc, + nsContentUtils::eSECURITY_PROPERTIES, + "BlockTopLevelDataURINavigation", + params, ArrayLength(params)); + return false; +} + static nsresult ValidateSecurityFlags(nsILoadInfo* aLoadInfo) { @@ -176,7 +253,7 @@ DoContentSecurityChecks(nsIChannel* aChannel, nsILoadInfo* aLoadInfo) nsContentPolicyType internalContentPolicyType = aLoadInfo->InternalContentPolicyType(); nsCString mimeTypeGuess; - nsCOMPtr<nsINode> requestingContext = nullptr; + nsCOMPtr<nsISupports> requestingContext = nullptr; #ifdef DEBUG // Don't enforce TYPE_DOCUMENT assertions for loads @@ -250,10 +327,13 @@ DoContentSecurityChecks(nsIChannel* aChannel, nsILoadInfo* aLoadInfo) case nsIContentPolicy::TYPE_XMLHTTPREQUEST: { // alias nsIContentPolicy::TYPE_DATAREQUEST: requestingContext = aLoadInfo->LoadingNode(); - MOZ_ASSERT(!requestingContext || - requestingContext->NodeType() == nsIDOMNode::DOCUMENT_NODE, - "type_xml requires requestingContext of type Document"); - +#ifdef DEBUG + { + nsCOMPtr<nsINode> node = do_QueryInterface(requestingContext); + MOZ_ASSERT(!node || node->NodeType() == nsIDOMNode::DOCUMENT_NODE, + "type_xml requires requestingContext of type Document"); + } +#endif // We're checking for the external TYPE_XMLHTTPREQUEST here in case // an addon creates a request with that type. if (internalContentPolicyType == @@ -274,18 +354,26 @@ DoContentSecurityChecks(nsIChannel* aChannel, nsILoadInfo* aLoadInfo) case nsIContentPolicy::TYPE_OBJECT_SUBREQUEST: { mimeTypeGuess = EmptyCString(); requestingContext = aLoadInfo->LoadingNode(); - MOZ_ASSERT(!requestingContext || - requestingContext->NodeType() == nsIDOMNode::ELEMENT_NODE, - "type_subrequest requires requestingContext of type Element"); +#ifdef DEBUG + { + nsCOMPtr<nsINode> node = do_QueryInterface(requestingContext); + MOZ_ASSERT(!node || node->NodeType() == nsIDOMNode::ELEMENT_NODE, + "type_subrequest requires requestingContext of type Element"); + } +#endif break; } case nsIContentPolicy::TYPE_DTD: { mimeTypeGuess = EmptyCString(); requestingContext = aLoadInfo->LoadingNode(); - MOZ_ASSERT(!requestingContext || - requestingContext->NodeType() == nsIDOMNode::DOCUMENT_NODE, - "type_dtd requires requestingContext of type Document"); +#ifdef DEBUG + { + nsCOMPtr<nsINode> node = do_QueryInterface(requestingContext); + MOZ_ASSERT(!node || node->NodeType() == nsIDOMNode::DOCUMENT_NODE, + "type_dtd requires requestingContext of type Document"); + } +#endif break; } @@ -303,9 +391,13 @@ DoContentSecurityChecks(nsIChannel* aChannel, nsILoadInfo* aLoadInfo) mimeTypeGuess = EmptyCString(); } requestingContext = aLoadInfo->LoadingNode(); - MOZ_ASSERT(!requestingContext || - requestingContext->NodeType() == nsIDOMNode::ELEMENT_NODE, - "type_media requires requestingContext of type Element"); +#ifdef DEBUG + { + nsCOMPtr<nsINode> node = do_QueryInterface(requestingContext); + MOZ_ASSERT(!node || node->NodeType() == nsIDOMNode::ELEMENT_NODE, + "type_media requires requestingContext of type Element"); + } +#endif break; } @@ -332,18 +424,26 @@ DoContentSecurityChecks(nsIChannel* aChannel, nsILoadInfo* aLoadInfo) case nsIContentPolicy::TYPE_XSLT: { mimeTypeGuess = NS_LITERAL_CSTRING("application/xml"); requestingContext = aLoadInfo->LoadingNode(); - MOZ_ASSERT(!requestingContext || - requestingContext->NodeType() == nsIDOMNode::DOCUMENT_NODE, - "type_xslt requires requestingContext of type Document"); +#ifdef DEBUG + { + nsCOMPtr<nsINode> node = do_QueryInterface(requestingContext); + MOZ_ASSERT(!node || node->NodeType() == nsIDOMNode::DOCUMENT_NODE, + "type_xslt requires requestingContext of type Document"); + } +#endif break; } case nsIContentPolicy::TYPE_BEACON: { mimeTypeGuess = EmptyCString(); requestingContext = aLoadInfo->LoadingNode(); - MOZ_ASSERT(!requestingContext || - requestingContext->NodeType() == nsIDOMNode::DOCUMENT_NODE, - "type_beacon requires requestingContext of type Document"); +#ifdef DEBUG + { + nsCOMPtr<nsINode> node = do_QueryInterface(requestingContext); + MOZ_ASSERT(!node || node->NodeType() == nsIDOMNode::DOCUMENT_NODE, + "type_beacon requires requestingContext of type Document"); + } +#endif break; } diff --git a/dom/security/nsContentSecurityManager.h b/dom/security/nsContentSecurityManager.h index 912c0e89f..bab847743 100644 --- a/dom/security/nsContentSecurityManager.h +++ b/dom/security/nsContentSecurityManager.h @@ -32,6 +32,8 @@ public: static nsresult doContentSecurityCheck(nsIChannel* aChannel, nsCOMPtr<nsIStreamListener>& aInAndOutListener); + static bool AllowTopLevelNavigationToDataURI(nsIChannel* aChannel); + private: static nsresult CheckChannel(nsIChannel* aChannel); diff --git a/dom/security/nsMixedContentBlocker.cpp b/dom/security/nsMixedContentBlocker.cpp index a9aca5333..4e80dce3f 100644 --- a/dom/security/nsMixedContentBlocker.cpp +++ b/dom/security/nsMixedContentBlocker.cpp @@ -337,7 +337,11 @@ nsMixedContentBlocker::AsyncOnChannelRedirect(nsIChannel* aOldChannel, nullptr, // aExtra requestingPrincipal, &decision); - NS_ENSURE_SUCCESS(rv, rv); + if (NS_FAILED(rv)) { + autoCallback.DontCallback(); + aOldChannel->Cancel(NS_ERROR_DOM_BAD_URI); + return NS_BINDING_FAILED; + } if (nsMixedContentBlocker::sSendHSTSPriming) { // The LoadInfo passed in is for the original channel, HSTS priming needs to @@ -358,6 +362,7 @@ nsMixedContentBlocker::AsyncOnChannelRedirect(nsIChannel* aOldChannel, // If the channel is about to load mixed content, abort the channel if (!NS_CP_ACCEPTED(decision)) { autoCallback.DontCallback(); + aOldChannel->Cancel(NS_ERROR_DOM_BAD_URI); return NS_BINDING_FAILED; } diff --git a/dom/security/test/csp/file_child-src_worker-redirect.html b/dom/security/test/csp/file_child-src_worker-redirect.html index 188f173b8..b0029935c 100644 --- a/dom/security/test/csp/file_child-src_worker-redirect.html +++ b/dom/security/test/csp/file_child-src_worker-redirect.html @@ -23,11 +23,8 @@ ); worker.onerror = function(error) { - var msg = error.message; - if (msg.match(/^NetworkError/) || msg.match(/Failed to load worker script/)) { - // this means CSP blocked it - msg = "blocked"; - } + // this means CSP blocked it + var msg = !("message" in error) ? "blocked" : e.message; window.parent.postMessage({id:page_id, message:msg}, 'http://mochi.test:8888'); error.preventDefault(); }; diff --git a/dom/security/test/csp/file_frame_ancestors_ro.html b/dom/security/test/csp/file_frame_ancestors_ro.html new file mode 100644 index 000000000..ff5ae9cf9 --- /dev/null +++ b/dom/security/test/csp/file_frame_ancestors_ro.html @@ -0,0 +1 @@ +<html><body>Child Document</body></html> diff --git a/dom/security/test/csp/file_frame_ancestors_ro.html^headers^ b/dom/security/test/csp/file_frame_ancestors_ro.html^headers^ new file mode 100644 index 000000000..d018af3a9 --- /dev/null +++ b/dom/security/test/csp/file_frame_ancestors_ro.html^headers^ @@ -0,0 +1 @@ +Content-Security-Policy-Report-Only: frame-ancestors 'none'; report-uri http://mochi.test:8888/foo.sjs diff --git a/dom/security/test/csp/file_frame_src.js b/dom/security/test/csp/file_frame_src.js new file mode 100644 index 000000000..8e81f0743 --- /dev/null +++ b/dom/security/test/csp/file_frame_src.js @@ -0,0 +1,14 @@ +let testframe = document.getElementById("testframe"); +testframe.onload = function() { + parent.postMessage({ + result: "frame-allowed", + href: document.location.href, + }, "*"); +} +testframe.onerror = function() { + parent.postMessage({ + result: "frame-blocked", + href: document.location.href, + }, "*"); +} +testframe.src = "file_frame_src_inner.html" diff --git a/dom/security/test/csp/file_frame_src_child_governs.html b/dom/security/test/csp/file_frame_src_child_governs.html new file mode 100644 index 000000000..a51cb75be --- /dev/null +++ b/dom/security/test/csp/file_frame_src_child_governs.html @@ -0,0 +1,10 @@ +<html> +<head> + <meta charset="utf-8"> + <meta http-equiv="Content-Security-Policy" content="child-src https://example.com">"; +</head> +<body> +<iframe id="testframe"></iframe> +<script type="text/javascript" src="file_frame_src.js"></script> +</body> +</html> diff --git a/dom/security/test/csp/file_frame_src_frame_governs.html b/dom/security/test/csp/file_frame_src_frame_governs.html new file mode 100644 index 000000000..2c5d5857f --- /dev/null +++ b/dom/security/test/csp/file_frame_src_frame_governs.html @@ -0,0 +1,10 @@ +<html> +<head> + <meta charset="utf-8"> + <meta http-equiv="Content-Security-Policy" content="frame-src https://example.com; child-src 'none'">"; +</head> +<body> +<iframe id="testframe"></iframe> +<script type="text/javascript" src="file_frame_src.js"></script> +</body> +</html> diff --git a/dom/security/test/csp/file_frame_src_inner.html b/dom/security/test/csp/file_frame_src_inner.html new file mode 100644 index 000000000..4a2fc6095 --- /dev/null +++ b/dom/security/test/csp/file_frame_src_inner.html @@ -0,0 +1,5 @@ +<html> +<body> +dummy iframe +</body> +</html> diff --git a/dom/security/test/csp/file_ignore_xfo.html b/dom/security/test/csp/file_ignore_xfo.html new file mode 100644 index 000000000..6746a3adb --- /dev/null +++ b/dom/security/test/csp/file_ignore_xfo.html @@ -0,0 +1,10 @@ +<!DOCTYPE HTML> +<html> +<head> + <meta charset="utf-8"> + <title>Bug 1024557: Ignore x-frame-options if CSP with frame-ancestors exists</title> +</head> +<body> +<div id="cspmessage">Ignoring XFO because of CSP</div> +</body> +</html> diff --git a/dom/security/test/csp/file_ignore_xfo.html^headers^ b/dom/security/test/csp/file_ignore_xfo.html^headers^ new file mode 100644 index 000000000..e93f9e3ec --- /dev/null +++ b/dom/security/test/csp/file_ignore_xfo.html^headers^ @@ -0,0 +1,3 @@ +Content-Security-Policy: frame-ancestors http://mochi.test:8888 +X-Frame-Options: deny +Cache-Control: no-cache diff --git a/dom/security/test/csp/file_image_nonce.html b/dom/security/test/csp/file_image_nonce.html new file mode 100644 index 000000000..5d57bb837 --- /dev/null +++ b/dom/security/test/csp/file_image_nonce.html @@ -0,0 +1,39 @@ +<!DOCTYPE HTML> +<html> + <head> + <meta charset='utf-8'> + <title>Bug 1355801: Nonce should not apply to images</title> + </head> +<body> + +<img id='matchingNonce' src='http://mochi.test:8888/tests/image/test/mochitest/blue.png?a' nonce='abc'></img> +<img id='nonMatchingNonce' src='http://mochi.test:8888/tests/image/test/mochitest/blue.png?b' nonce='bca'></img> +<img id='noNonce' src='http://mochi.test:8888/tests/image/test/mochitest/blue.png?c'></img> + +<script type='application/javascript'> + var matchingNonce = document.getElementById('matchingNonce'); + matchingNonce.onload = function(e) { + window.parent.postMessage({result: 'img-with-matching-nonce-loaded'}, '*'); + }; + matchingNonce.onerror = function(e) { + window.parent.postMessage({result: 'img-with-matching-nonce-blocked'}, '*'); + } + + var nonMatchingNonce = document.getElementById('nonMatchingNonce'); + nonMatchingNonce.onload = function(e) { + window.parent.postMessage({result: 'img-with_non-matching-nonce-loaded'}, '*'); + }; + nonMatchingNonce.onerror = function(e) { + window.parent.postMessage({result: 'img-with_non-matching-nonce-blocked'}, '*'); + } + + var noNonce = document.getElementById('noNonce'); + noNonce.onload = function(e) { + window.parent.postMessage({result: 'img-without-nonce-loaded'}, '*'); + }; + noNonce.onerror = function(e) { + window.parent.postMessage({result: 'img-without-nonce-blocked'}, '*'); + } +</script> +</body> +</html> diff --git a/dom/security/test/csp/file_image_nonce.html^headers^ b/dom/security/test/csp/file_image_nonce.html^headers^ new file mode 100644 index 000000000..0d63558c4 --- /dev/null +++ b/dom/security/test/csp/file_image_nonce.html^headers^ @@ -0,0 +1,2 @@ +Content-Security-Policy: img-src 'nonce-abc'; +Cache-Control: no-cache diff --git a/dom/security/test/csp/file_punycode_host_src.js b/dom/security/test/csp/file_punycode_host_src.js new file mode 100644 index 000000000..3505faf70 --- /dev/null +++ b/dom/security/test/csp/file_punycode_host_src.js @@ -0,0 +1,2 @@ +const LOADED = true; +parent.postMessage({result: 'script-allowed'}, "*");
\ No newline at end of file diff --git a/dom/security/test/csp/file_punycode_host_src.sjs b/dom/security/test/csp/file_punycode_host_src.sjs new file mode 100644 index 000000000..3189cc063 --- /dev/null +++ b/dom/security/test/csp/file_punycode_host_src.sjs @@ -0,0 +1,45 @@ +// custom *.sjs for Bug 1224225 +// Punycode in CSP host sources + +const HTML_PART1 = + "<!DOCTYPE HTML>" + + "<html><head><meta charset=\"utf-8\">" + + "<title>Bug 1224225 - CSP source matching should work for punycoded domain names</title>" + + "</head>" + + "<body>" + + "<script id='script' src='"; + +const TESTCASE1 = "http://sub2.ält.example.org/"; +const TESTCASE2 = "http://sub2.xn--lt-uia.example.org/" + +const HTML_PART2 = "tests/dom/security/test/csp/file_punycode_host_src.js'></script>" + + "</body>" + + "</html>"; + +function handleRequest(request, response) +{ + // avoid confusing cache behaviors + response.setHeader("Cache-Control", "no-cache", false); + response.setHeader("Content-Type", "text/html", false); + + Components.utils.importGlobalProperties(["URLSearchParams"]); + const query = new URLSearchParams(request.queryString); + + + if (query.get("csp")) { + response.setHeader("Content-Security-Policy", query.get("csp"), false); + } + if (query.get("action") == "script-unicode-csp-punycode") { + response.write(HTML_PART1 + TESTCASE1 + HTML_PART2); + return + } + if (query.get("action") == "script-punycode-csp-punycode") { + response.write(HTML_PART1 + TESTCASE2 + HTML_PART2); + return + } + + + // we should never get here, but just in case + // return something unexpected + response.write("do'h"); +} diff --git a/dom/security/test/csp/file_ro_ignore_xfo.html b/dom/security/test/csp/file_ro_ignore_xfo.html new file mode 100644 index 000000000..85e7f0092 --- /dev/null +++ b/dom/security/test/csp/file_ro_ignore_xfo.html @@ -0,0 +1,10 @@ +<!DOCTYPE HTML> +<html> +<head> + <meta charset="utf-8"> + <title>Bug 1024557: Ignore x-frame-options if CSP with frame-ancestors exists</title> +</head> +<body> +<div id="cspmessage">Ignoring XFO because of CSP_RO</div> +</body> +</html>
\ No newline at end of file diff --git a/dom/security/test/csp/file_ro_ignore_xfo.html^headers^ b/dom/security/test/csp/file_ro_ignore_xfo.html^headers^ new file mode 100644 index 000000000..ab8366f06 --- /dev/null +++ b/dom/security/test/csp/file_ro_ignore_xfo.html^headers^ @@ -0,0 +1,3 @@ +Content-Security-Policy-Report-Only: frame-ancestors http://mochi.test:8888 +X-Frame-Options: deny +Cache-Control: no-cache diff --git a/dom/security/test/csp/file_spawn_service_worker.js b/dom/security/test/csp/file_spawn_service_worker.js new file mode 100644 index 000000000..b262fa10a --- /dev/null +++ b/dom/security/test/csp/file_spawn_service_worker.js @@ -0,0 +1 @@ +// dummy file diff --git a/dom/security/test/csp/file_spawn_shared_worker.js b/dom/security/test/csp/file_spawn_shared_worker.js new file mode 100644 index 000000000..00063bc5c --- /dev/null +++ b/dom/security/test/csp/file_spawn_shared_worker.js @@ -0,0 +1,7 @@ +onconnect = function(e) { + var port = e.ports[0]; + port.addEventListener("message", function(e) { + port.postMessage("shared worker is executing"); + }); + port.start(); +} diff --git a/dom/security/test/csp/file_spawn_worker.js b/dom/security/test/csp/file_spawn_worker.js new file mode 100644 index 000000000..acde7408c --- /dev/null +++ b/dom/security/test/csp/file_spawn_worker.js @@ -0,0 +1 @@ +postMessage("worker is executing"); diff --git a/dom/security/test/csp/file_upgrade_insecure_navigation.sjs b/dom/security/test/csp/file_upgrade_insecure_navigation.sjs new file mode 100644 index 000000000..51afa39bf --- /dev/null +++ b/dom/security/test/csp/file_upgrade_insecure_navigation.sjs @@ -0,0 +1,79 @@ +// Custom *.sjs file specifically for the needs of +// https://bugzilla.mozilla.org/show_bug.cgi?id=1271173 + +"use strict"; +Components.utils.importGlobalProperties(["URLSearchParams"]); + +const TEST_NAVIGATIONAL_UPGRADE = ` + <!DOCTYPE html> + <html> + <head><meta charset="utf-8"></head> + <body> + <a href="http://example.com/tests/dom/security/test/csp/file_upgrade_insecure_navigation.sjs?action=framenav" id="testlink">clickme</a> + <script type="text/javascript"> + // before navigating the current frame we open the window and check that uir applies + var myWin = window.open("http://example.com/tests/dom/security/test/csp/file_upgrade_insecure_navigation.sjs?action=docnav"); + + window.addEventListener("message", receiveMessage, false); + function receiveMessage(event) { + myWin.close(); + var link = document.getElementById('testlink'); + link.click(); + } + </script> + </body> + </html>`; + +const FRAME_NAV = ` + <!DOCTYPE html> + <html> + <head><meta charset="utf-8"></head> + <body> + <script type="text/javascript"> + parent.postMessage({result: document.documentURI}, "*"); + </script> + </body> + </html>`; + +const DOC_NAV = ` + <!DOCTYPE html> + <html> + <head><meta charset="utf-8"></head> + <body> + <script type="text/javascript"> + // call back to the main testpage signaling whether the upgraded succeeded + window.opener.parent.postMessage({result: document.documentURI}, "*"); + // let the opener (iframe) now that we can now close the window and move on with the test. + window.opener.postMessage({result: "readyToMoveOn"}, "*"); + </script> + </body> + </html>`; + +function handleRequest(request, response) { + const query = new URLSearchParams(request.queryString); + + response.setHeader("Cache-Control", "no-cache", false); + response.setHeader("Content-Type", "text/html", false); + if (query.get("csp")) { + response.setHeader("Content-Security-Policy", query.get("csp"), false); + } + + if (query.get("action") === "perform_navigation") { + response.write(TEST_NAVIGATIONAL_UPGRADE); + return; + } + + if (query.get("action") === "framenav") { + response.write(FRAME_NAV); + return; + } + + if (query.get("action") === "docnav") { + response.write(DOC_NAV); + return; + } + + // we should never get here, but just in case + // return something unexpected + response.write("do'h"); +} diff --git a/dom/security/test/csp/file_websocket_explicit.html b/dom/security/test/csp/file_websocket_explicit.html new file mode 100644 index 000000000..51462ab74 --- /dev/null +++ b/dom/security/test/csp/file_websocket_explicit.html @@ -0,0 +1,31 @@ +<!DOCTYPE HTML>
+<html>
+<head>
+ <meta charset="utf-8">
+ <title>Bug 1345615: Allow websocket schemes when using 'self' in CSP</title>
+ <meta http-equiv="Content-Security-Policy" content="connect-src ws:">
+</head>
+<body>
+ <script type="application/javascript">
+ /* load socket using ws */
+ var wsSocket = new WebSocket("ws://example.com/tests/dom/security/test/csp/file_websocket_self");
+ wsSocket.onopen = function(e) {
+ window.parent.postMessage({result: "explicit-ws-loaded"}, "*");
+ wsSocket.close();
+ };
+ wsSocket.onerror = function(e) {
+ window.parent.postMessage({result: "explicit-ws-blocked"}, "*");
+ };
+
+ /* load socket using wss */
+ var wssSocket = new WebSocket("wss://example.com/tests/dom/security/test/csp/file_websocket_self");
+ wssSocket.onopen = function(e) {
+ window.parent.postMessage({result: "explicit-wss-loaded"}, "*");
+ wssSocket.close();
+ };
+ wssSocket.onerror = function(e) {
+ window.parent.postMessage({result: "explicit-wss-blocked"}, "*");
+ };
+ </script>
+</body>
+</html>
diff --git a/dom/security/test/csp/file_websocket_self.html b/dom/security/test/csp/file_websocket_self.html new file mode 100644 index 000000000..3ff5f0558 --- /dev/null +++ b/dom/security/test/csp/file_websocket_self.html @@ -0,0 +1,31 @@ +<!DOCTYPE HTML>
+<html>
+<head>
+ <meta charset="utf-8">
+ <title>Bug 1345615: Allow websocket schemes when using 'self' in CSP</title>
+ <meta http-equiv="Content-Security-Policy" content="connect-src 'self'">
+</head>
+<body>
+ <script type="application/javascript">
+ /* load socket using ws */
+ var wsSocket = new WebSocket("ws://example.com/tests/dom/security/test/csp/file_websocket_self");
+ wsSocket.onopen = function(e) {
+ window.parent.postMessage({result: "self-ws-loaded"}, "*");
+ wsSocket.close();
+ };
+ wsSocket.onerror = function(e) {
+ window.parent.postMessage({result: "self-ws-blocked"}, "*");
+ };
+
+ /* load socket using wss */
+ var wssSocket = new WebSocket("wss://example.com/tests/dom/security/test/csp/file_websocket_self");
+ wssSocket.onopen = function(e) {
+ window.parent.postMessage({result: "self-wss-loaded"}, "*");
+ wssSocket.close();
+ };
+ wssSocket.onerror = function(e) {
+ window.parent.postMessage({result: "self-wss-blocked"}, "*");
+ };
+ </script>
+</body>
+</html>
diff --git a/dom/security/test/csp/file_websocket_self_wsh.py b/dom/security/test/csp/file_websocket_self_wsh.py new file mode 100644 index 000000000..5fe508a91 --- /dev/null +++ b/dom/security/test/csp/file_websocket_self_wsh.py @@ -0,0 +1,7 @@ +from mod_pywebsocket import msgutil
+
+def web_socket_do_extra_handshake(request):
+ pass
+
+def web_socket_transfer_data(request):
+ pass
diff --git a/dom/security/test/csp/file_worker_src.js b/dom/security/test/csp/file_worker_src.js new file mode 100644 index 000000000..ad3ade6a6 --- /dev/null +++ b/dom/security/test/csp/file_worker_src.js @@ -0,0 +1,52 @@ + +let myWorker = new Worker("file_spawn_worker.js"); +myWorker.onmessage = function(event) { + parent.postMessage({ + result: "worker-allowed", + href: document.location.href, + }, "*"); +} +myWorker.onerror = function(event) { + parent.postMessage({ + result: "worker-blocked", + href: document.location.href, + }, "*"); +} + +// -------------------------------------------- + +var mySharedWorker = new SharedWorker('file_spawn_shared_worker.js'); +mySharedWorker.port.onmessage = function(ev) { + parent.postMessage({ + result: "shared-worker-allowed", + href: document.location.href, + }, "*"); +} +mySharedWorker.onerror = function(evt) { + evt.preventDefault(); + parent.postMessage({ + result: "shared-worker-blocked", + href: document.location.href, + }, "*"); +} +mySharedWorker.port.start(); +mySharedWorker.port.postMessage('foo'); + +// -------------------------------------------- + +navigator.serviceWorker.register('file_spawn_service_worker.js') +.then(function(reg) { + // registration worked + reg.unregister().then(function() { + parent.postMessage({ + result: "service-worker-allowed", + href: document.location.href, + }, "*"); + }); +}).catch(function(error) { + // registration failed + parent.postMessage({ + result: "service-worker-blocked", + href: document.location.href, + }, "*"); +}); diff --git a/dom/security/test/csp/file_worker_src_child_governs.html b/dom/security/test/csp/file_worker_src_child_governs.html new file mode 100644 index 000000000..ca8a683aa --- /dev/null +++ b/dom/security/test/csp/file_worker_src_child_governs.html @@ -0,0 +1,9 @@ +<html> +<head> + <meta charset="utf-8"> + <meta http-equiv="Content-Security-Policy" content="child-src https://example.com; script-src 'nonce-foo'">"; +</head> +<body> +<script type="text/javascript" src="file_worker_src.js" nonce="foo"></script> +</body> +</html> diff --git a/dom/security/test/csp/file_worker_src_script_governs.html b/dom/security/test/csp/file_worker_src_script_governs.html new file mode 100644 index 000000000..0385fee57 --- /dev/null +++ b/dom/security/test/csp/file_worker_src_script_governs.html @@ -0,0 +1,9 @@ +<html> +<head> + <meta charset="utf-8"> + <meta http-equiv="Content-Security-Policy" content="script-src 'nonce-foo' https://example.com">"; +</head> +<body> +<script type="text/javascript" src="file_worker_src.js" nonce="foo"></script> +</body> +</html> diff --git a/dom/security/test/csp/file_worker_src_worker_governs.html b/dom/security/test/csp/file_worker_src_worker_governs.html new file mode 100644 index 000000000..93c8f6122 --- /dev/null +++ b/dom/security/test/csp/file_worker_src_worker_governs.html @@ -0,0 +1,9 @@ +<html> +<head> + <meta charset="utf-8"> + <meta http-equiv="Content-Security-Policy" content="worker-src https://example.com; child-src 'none'; script-src 'nonce-foo'">"; +</head> +<body> +<script type="text/javascript" src="file_worker_src.js" nonce="foo"></script> +</body> +</html> diff --git a/dom/security/test/csp/mochitest.ini b/dom/security/test/csp/mochitest.ini index 8add999c3..33b112020 100644 --- a/dom/security/test/csp/mochitest.ini +++ b/dom/security/test/csp/mochitest.ini @@ -91,6 +91,8 @@ support-files = file_bug941404.html file_bug941404_xhr.html file_bug941404_xhr.html^headers^ + file_frame_ancestors_ro.html + file_frame_ancestors_ro.html^headers^ file_hash_source.html file_dual_header_testserver.sjs file_hash_source.html^headers^ @@ -206,6 +208,18 @@ support-files = file_iframe_srcdoc.sjs file_iframe_sandbox_srcdoc.html file_iframe_sandbox_srcdoc.html^headers^ + file_ignore_xfo.html + file_ignore_xfo.html^headers^ + file_ro_ignore_xfo.html + file_ro_ignore_xfo.html^headers^ + file_upgrade_insecure_navigation.sjs + file_image_nonce.html + file_image_nonce.html^headers^ + file_punycode_host_src.sjs + file_punycode_host_src.js + file_websocket_self.html + file_websocket_explicit.html + file_websocket_self_wsh.py [test_base-uri.html] [test_blob_data_schemes.html] @@ -228,6 +242,7 @@ skip-if = toolkit == 'android' # Times out, not sure why (bug 1008445) [test_bug910139.html] [test_bug909029.html] [test_bug1229639.html] +[test_frame_ancestors_ro.html] [test_policyuri_regression_from_multipolicy.html] [test_nonce_source.html] [test_bug941404.html] @@ -292,9 +307,30 @@ tags = mcb [test_strict_dynamic.html] [test_strict_dynamic_parser_inserted.html] [test_strict_dynamic_default_src.html] +[test_upgrade_insecure_navigation.html] [test_iframe_sandbox_srcdoc.html] [test_iframe_srcdoc.html] [test_sandbox_allow_scripts.html] support-files = file_sandbox_allow_scripts.html file_sandbox_allow_scripts.html^headers^ +[test_ignore_xfo.html] +[test_image_nonce.html] +[test_punycode_host_src.html] +[test_websocket_self.html] +skip-if = toolkit == 'android' +[test_worker_src.html] +support-files = + file_worker_src_worker_governs.html + file_worker_src_child_governs.html + file_worker_src_script_governs.html + file_worker_src.js + file_spawn_worker.js + file_spawn_shared_worker.js + file_spawn_service_worker.js +[test_frame_src.html] +support-files = + file_frame_src_frame_governs.html + file_frame_src_child_governs.html + file_frame_src.js + file_frame_src_inner.html diff --git a/dom/security/test/csp/test_child-src_worker.html b/dom/security/test/csp/test_child-src_worker.html index 7dcbd03f6..ea9e7b28e 100644 --- a/dom/security/test/csp/test_child-src_worker.html +++ b/dom/security/test/csp/test_child-src_worker.html @@ -83,19 +83,19 @@ id: "script-src-worker", file: WORKER_TEST_FILE, result : "blocked", - policy : "default-src 'none'; script-src 'self' 'unsafe-inline'" + policy : "default-src 'none'; script-src https://www.example.org 'unsafe-inline'" }, 'script-src-service_worker': { id: "script-src-service_worker", file: SERVICE_WORKER_TEST_FILE, result : "blocked", - policy : "default-src 'none'; script-src 'self' 'unsafe-inline'" + policy : "default-src 'none'; script-src https://www.example.org 'unsafe-inline'" }, 'script-src-self-shared_worker': { id: "script-src-self-shared_worker", file: SHARED_WORKER_TEST_FILE, result : "blocked", - policy : "default-src 'none'; script-src 'self' 'unsafe-inline'" + policy : "default-src 'none'; script-src https://www.example.org 'unsafe-inline'" }, }; diff --git a/dom/security/test/csp/test_frame_ancestors_ro.html b/dom/security/test/csp/test_frame_ancestors_ro.html new file mode 100644 index 000000000..90f68e25e --- /dev/null +++ b/dom/security/test/csp/test_frame_ancestors_ro.html @@ -0,0 +1,69 @@ +<!DOCTYPE HTML> +<html> +<head> + <title>Test for frame-ancestors support in Content-Security-Policy-Report-Only</title> + <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> +</head> +<body> +<iframe style="width: 100%" id="cspframe"></iframe> +<script type="text/javascript"> +const docUri = "http://mochi.test:8888/tests/dom/security/test/csp/file_frame_ancestors_ro.html"; +const frame = document.getElementById("cspframe"); + +let testResults = { + reportFired: false, + frameLoaded: false +}; + +function checkResults(reportObj) { + let cspReport = reportObj["csp-report"]; + is(cspReport["document-uri"], docUri, "Incorrect document-uri"); + + // we can not test for the whole referrer since it includes platform specific information + is(cspReport["referrer"], document.location.toString(), "Incorrect referrer"); + is(cspReport["blocked-uri"], document.location.toString(), "Incorrect blocked-uri"); + is(cspReport["violated-directive"], "frame-ancestors 'none'", "Incorrect violated-directive"); + is(cspReport["original-policy"], "frame-ancestors 'none'; report-uri http://mochi.test:8888/foo.sjs", "Incorrect original-policy"); + testResults.reportFired = true; +} + +let chromeScriptUrl = SimpleTest.getTestFileURL("file_report_chromescript.js"); +let script = SpecialPowers.loadChromeScript(chromeScriptUrl); + +script.addMessageListener('opening-request-completed', function ml(msg) { + if (msg.error) { + ok(false, "Could not query report (exception: " + msg.error + ")"); + } else { + try { + let reportObj = JSON.parse(msg.report); + // test for the proper values in the report object + checkResults(reportObj); + } catch (e) { + ok(false, "Error verifying report object (exception: " + e + ")"); + } + } + + script.removeMessageListener('opening-request-completed', ml); + script.sendAsyncMessage("finish"); + checkTestResults(); +}); + +frame.addEventListener( 'load', () => { + // Make sure the frame is still loaded + testResults.frameLoaded = true; + checkTestResults() +} ); + +function checkTestResults() { + if( testResults.reportFired && testResults.frameLoaded ) { + SimpleTest.finish(); + } +} + +SimpleTest.waitForExplicitFinish(); +frame.src = docUri; + +</script> +</body> +</html> diff --git a/dom/security/test/csp/test_frame_src.html b/dom/security/test/csp/test_frame_src.html new file mode 100644 index 000000000..07de90cfa --- /dev/null +++ b/dom/security/test/csp/test_frame_src.html @@ -0,0 +1,84 @@ +<!DOCTYPE HTML> +<html> +<head> + <meta charset="utf-8"> + <title>Bug 1302667 - Test frame-src</title> + <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> +</head> +<body> +<iframe style="width:100%;" id="testframe"></iframe> + +<script class="testbody" type="text/javascript"> + +SimpleTest.waitForExplicitFinish(); + +/* Description of the test: + * We load a page inlcuding a frame a CSP of: + * >> frame-src https://example.com; child-src 'none' + * and make sure that frame-src governs frames correctly. In addition, + * we make sure that child-src is discarded in case frame-src is specified. + */ + +const ORIGIN_1 = "https://example.com/tests/dom/security/test/csp/"; +const ORIGIN_2 = "https://test1.example.com/tests/dom/security/test/csp/"; + +let TESTS = [ + // frame-src tests + ORIGIN_1 + "file_frame_src_frame_governs.html", + ORIGIN_2 + "file_frame_src_frame_governs.html", + // child-src tests + ORIGIN_1 + "file_frame_src_child_governs.html", + ORIGIN_2 + "file_frame_src_child_governs.html", +]; + +let testIndex = 0; + +function checkFinish() { + if (testIndex >= TESTS.length) { + window.removeEventListener("message", receiveMessage); + SimpleTest.finish(); + return; + } + runNextTest(); +} + +window.addEventListener("message", receiveMessage); +function receiveMessage(event) { + let href = event.data.href; + let result = event.data.result; + + if (href.startsWith("https://example.com")) { + if (result == "frame-allowed") { + ok(true, "allowing frame from https://example.com (" + result + ")"); + } + else { + ok(false, "blocking frame from https://example.com (" + result + ")"); + } + } + else if (href.startsWith("https://test1.example.com")) { + if (result == "frame-blocked") { + ok(true, "blocking frame from https://test1.example.com (" + result + ")"); + } + else { + ok(false, "allowing frame from https://test1.example.com (" + result + ")"); + } + } + else { + // sanity check, we should never enter that branch, bust just in case... + ok(false, "unexpected result: " + result); + } + checkFinish(); +} + +function runNextTest() { + document.getElementById("testframe").src = TESTS[testIndex]; + testIndex++; +} + +// fire up the tests +runNextTest(); + +</script> +</body> +</html> diff --git a/dom/security/test/csp/test_ignore_xfo.html b/dom/security/test/csp/test_ignore_xfo.html new file mode 100644 index 000000000..fb3aadc6c --- /dev/null +++ b/dom/security/test/csp/test_ignore_xfo.html @@ -0,0 +1,59 @@ +<!DOCTYPE HTML> +<html> +<head> + <title>Bug 1024557: Ignore x-frame-options if CSP with frame-ancestors exists</title> + <!-- Including SimpleTest.js so we can use waitForExplicitFinish !--> + <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> +</head> +<body> +<iframe style="width:100%;" id="csp_testframe"></iframe> +<iframe style="width:100%;" id="csp_ro_testframe"></iframe> + +<script class="testbody" type="text/javascript"> + +/* + * We load two frames using: + * x-frame-options: deny + * where the first frame uses a csp and the second a csp_ro including frame-ancestors. + * We make sure that xfo is ignored for regular csp but not for csp_ro. + */ + +SimpleTest.waitForExplicitFinish(); + +var testcounter = 0; +function checkFinished() { + testcounter++; + if (testcounter < 2) { + return; + } + SimpleTest.finish(); +} + +// 1) test XFO with CSP +var csp_testframe = document.getElementById("csp_testframe"); +csp_testframe.onload = function() { + var msg = csp_testframe.contentWindow.document.getElementById("cspmessage"); + is(msg.innerHTML, "Ignoring XFO because of CSP", "Loading frame with with XFO and CSP"); + checkFinished(); +} +csp_testframe.onerror = function() { + ok(false, "sanity: should not fire onerror for csp_testframe"); +} +csp_testframe.src = "file_ignore_xfo.html"; + +// 2) test XFO with CSP_RO +var csp_ro_testframe = document.getElementById("csp_ro_testframe"); +csp_ro_testframe.onload = function() { + var msg = csp_ro_testframe.contentWindow.document.getElementById("cspmessage"); + is(msg, null, "Blocking frame with with XFO and CSP_RO"); + checkFinished(); +} +csp_ro_testframe.onerror = function() { + ok(false, "sanity: should not fire onerror for csp_ro_testframe"); +} +csp_ro_testframe.src = "file_ro_ignore_xfo.html"; + +</script> +</body> +</html> diff --git a/dom/security/test/csp/test_image_nonce.html b/dom/security/test/csp/test_image_nonce.html new file mode 100644 index 000000000..ff6d636b6 --- /dev/null +++ b/dom/security/test/csp/test_image_nonce.html @@ -0,0 +1,60 @@ +<!DOCTYPE HTML> +<html> +<head> + <meta charset="utf-8"> + <title>Bug 1139297 - Implement CSP upgrade-insecure-requests directive</title> + <!-- Including SimpleTest.js so we can use waitForExplicitFinish !--> + <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> +</head> +<body> +<iframe style="width:100%;" id="testframe"></iframe> + +<script class="testbody" type="text/javascript"> + +/* Description of the test: + * We load three images: (a) with a matching nonce, + (b) with a non matching nonce, + * (c) with no nonce + * and make sure that all three images get blocked because + * "img-src nonce-bla" should not allow an image load, not + * even if the nonce matches*. + */ + +SimpleTest.waitForExplicitFinish(); + +var counter = 0; + +function finishTest() { + window.removeEventListener("message", receiveMessage); + SimpleTest.finish(); +} + +function checkResults(aResult) { + counter++; + if (aResult === "img-with-matching-nonce-blocked" || + aResult === "img-with_non-matching-nonce-blocked" || + aResult === "img-without-nonce-blocked") { + ok (true, "correct result for: " + aResult); + } + else { + ok(false, "unexpected result: " + aResult + "\n\n"); + } + if (counter < 3) { + return; + } + finishTest(); +} + +// a postMessage handler that is used by sandboxed iframes without +// 'allow-same-origin' to bubble up results back to this main page. +window.addEventListener("message", receiveMessage); +function receiveMessage(event) { + checkResults(event.data.result); +} + +document.getElementById("testframe").src = "file_image_nonce.html"; + +</script> +</body> +</html> diff --git a/dom/security/test/csp/test_punycode_host_src.html b/dom/security/test/csp/test_punycode_host_src.html new file mode 100644 index 000000000..8d891725c --- /dev/null +++ b/dom/security/test/csp/test_punycode_host_src.html @@ -0,0 +1,81 @@ +<!DOCTYPE HTML> +<html> +<head> + <meta charset="utf-8"> + <title>Bug 1224225 - CSP source matching should work for punycoded domain names</title> + <!-- Including SimpleTest.js so we can use waitForExplicitFinish !--> + <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> +</head> +<body> +<iframe style="width:100%;" id="testframe"></iframe> + +<script class="testbody" type="text/javascript"> + +/* Description of the test: + * We load scripts within an iframe and make sure that the + * CSP matching is same for punycode domain names as well as IDNA. + */ + +SimpleTest.waitForExplicitFinish(); + + +var curTest; +var counter = -1; + +const tests = [ + { // test 1 + description: "loads script as sub2.ält.example.org, but whitelist in CSP as sub2.xn--lt-uia.example.org", + action: "script-unicode-csp-punycode", + csp: "script-src http://sub2.xn--lt-uia.example.org;", + expected: "script-allowed", + + }, + { // test 2 + description: "loads script as sub2.xn--lt-uia.example.org, and whitelist in CSP as sub2.xn--lt-uia.example.org", + action: "script-punycode-csp-punycode", + csp: "script-src http://sub2.xn--lt-uia.example.org;", + expected: "script-allowed", + + }, + { // test 3 + description: "loads script as sub2.xn--lt-uia.example.org, and whitelist in CSP as sub2.xn--lt-uia.example.org", + action: "script-punycode-csp-punycode", + csp: "script-src *.xn--lt-uia.example.org;", + expected: "script-allowed", + + }, + +]; + +function finishTest() { + window.removeEventListener("message", receiveMessage); + SimpleTest.finish(); +} + +function checkResults(result) { + is(result, curTest.expected, curTest.description); + loadNextTest(); +} + +window.addEventListener("message", receiveMessage); +function receiveMessage(event) { + checkResults(event.data.result); +} + +function loadNextTest() { + counter++; + if (counter == tests.length) { + finishTest(); + return; + } + curTest = tests[counter]; + var testframe = document.getElementById("testframe"); + testframe.src = `file_punycode_host_src.sjs?action=${curTest.action}&csp=${curTest.csp}`; +} + +loadNextTest(); + +</script> +</body> +</html> diff --git a/dom/security/test/csp/test_upgrade_insecure_navigation.html b/dom/security/test/csp/test_upgrade_insecure_navigation.html new file mode 100644 index 000000000..db6a6a1be --- /dev/null +++ b/dom/security/test/csp/test_upgrade_insecure_navigation.html @@ -0,0 +1,103 @@ +<!DOCTYPE HTML> +<html> +<head> + <title>Bug 1271173 - Missing spec on Upgrade Insecure Requests(Navigational Upgrades) </title> + <!-- Including SimpleTest.js so we can use waitForExplicitFinish !--> + <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> +</head> +<body> +<iframe style="width:100%;" id="testframe"></iframe> +<iframe style="width:100%;" id="sandboxedtestframe" + sandbox="allow-scripts allow-top-navigation allow-same-origin allow-pointer-lock allow-popups"></iframe> + +<script class="testbody" type="text/javascript"> +/* + * Description of the test: + * We load a page into an iframe that performs a navigational request. + * We make sure that upgrade-insecure-requests applies and the page + * gets upgraded to https if same origin. + * Please note that uir only applies to sandboxed iframes if + * the value 'allow-same-origin' is specified. + */ + +SimpleTest.waitForExplicitFinish(); + +var tests = [ + { + csp: "upgrade-insecure-requests;", + result: "https", + origin: "http://example.com", + desc: "upgrade-insecure-requests same origin should upgrade" + }, + { + csp: "", + result: "http", + origin: "http://example.com", + desc: "No upgrade-insecure-requests same origin should not upgrade" + }, + { + csp: "upgrade-insecure-requests;", + result: "http", + origin: "http://mochi.test:8888", + desc: "upgrade-insecure-requests cross origin should not upgrade" + }, + { + csp: "", + result: "http", + origin: "http://mochi.test:8888", + desc: "No upgrade-insecure-requests cross origin should not upgrade" + }, +]; + +// initializing to -1 so we start at index 0 when we start the test +var counter = -1; + +function finishTest() { + window.removeEventListener("message", receiveMessage, false); + SimpleTest.finish(); +} + +var subtests = 0; + +window.addEventListener("message", receiveMessage, false); +function receiveMessage(event) { + var result = event.data.result; + // query the scheme from the URL before comparing the result + var scheme = result.substring(0, result.indexOf(":")); + is(scheme, tests[counter].result, tests[counter].desc); + + // @hardcoded 4: + // each test run contains of two subtests (frame and top-level) + // and we load each test into a regular iframe and into a + // sandboxed iframe. only move on to the next test once all + // four results from the subtests have bubbled up. + subtests++; + if (subtests != 4) { + return; + } + subtests = 0; + loadNextTest(); +} + +function loadNextTest() { + counter++; + if (counter == tests.length) { + finishTest(); + return; + } + + var src = tests[counter].origin; + src += "/tests/dom/security/test/csp/file_upgrade_insecure_navigation.sjs"; + src += "?csp=" + escape(tests[counter].csp); + src += "&action=perform_navigation"; + document.getElementById("testframe").src = src; + document.getElementById("sandboxedtestframe").src = src; +} + +// start running the tests +loadNextTest(); + +</script> +</body> +</html> diff --git a/dom/security/test/csp/test_websocket_self.html b/dom/security/test/csp/test_websocket_self.html new file mode 100644 index 000000000..a03c32704 --- /dev/null +++ b/dom/security/test/csp/test_websocket_self.html @@ -0,0 +1,61 @@ +<!DOCTYPE HTML>
+<html>
+<head>
+ <meta charset="utf-8">
+ <title>Bug 1345615: Allow websocket schemes when using 'self' in CSP</title>
+ <!-- Including SimpleTest.js so we can use waitForExplicitFinish !-->
+ <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<iframe style="width:100%;" id="test_ws_self_frame"></iframe>
+<iframe style="width:100%;" id="test_ws_explicit_frame"></iframe>
+
+<script class="testbody" type="text/javascript">
+
+/* Description of the test:
+ * We load an iframe using connect-src 'self' and one
+ * iframe using connect-src ws: and make
+ * sure that in both cases ws: as well as wss: is allowed to load.
+ */
+
+SimpleTest.waitForExplicitFinish();
+
+function finishTest() {
+ window.removeEventListener("message", receiveMessage);
+ SimpleTest.finish();
+}
+
+const TOTAL_TESTS = 4;
+var counter = 0;
+
+function checkResults(result) {
+ counter++;
+ if (result === "self-ws-loaded" || result === "self-wss-loaded" ||
+ result === "explicit-ws-loaded" || result === "explicit-wss-loaded") {
+ ok(true, "Evaluating: " + result);
+ }
+ else {
+ ok(false, "Evaluating: " + result);
+ }
+ if (counter < TOTAL_TESTS) {
+ return;
+ }
+ finishTest();
+}
+
+window.addEventListener("message", receiveMessage);
+function receiveMessage(event) {
+ checkResults(event.data.result);
+}
+
+const HOST = "http://example.com/tests/dom/security/test/csp/";
+var test_ws_self_frame = document.getElementById("test_ws_self_frame");
+test_ws_self_frame.src = HOST + "file_websocket_self.html";
+
+var test_ws_explicit_frame = document.getElementById("test_ws_explicit_frame");
+test_ws_explicit_frame.src = HOST + "file_websocket_explicit.html";
+
+</script>
+</body>
+</html>
diff --git a/dom/security/test/csp/test_worker_src.html b/dom/security/test/csp/test_worker_src.html new file mode 100644 index 000000000..3f2b44c9f --- /dev/null +++ b/dom/security/test/csp/test_worker_src.html @@ -0,0 +1,94 @@ +<!DOCTYPE HTML> +<html> +<head> + <meta charset="utf-8"> + <title>Bug 1302667 - Test worker-src</title> + <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> +</head> +<body> +<iframe style="width:100%;" id="worker-testframe"></iframe> +<iframe style="width:100%;" id="child-testframe"></iframe> +<iframe style="width:100%;" id="script-testframe"></iframe> + +<script class="testbody" type="text/javascript"> + +SimpleTest.waitForExplicitFinish(); + +/* Description of the test: + * We load a page inlcuding a worker, a shared worker as well as a + * service worker with a CSP of: + * >> worker-src https://example.com; child-src 'none'; script-src 'nonce-foo' + * and make sure that worker-src governs these three kinds of workers correctly. + * In addition, we make sure that child-src as well as script-src is discarded + * in case worker-src is specified. Ideally we would use "script-src 'none'" but + * we have to whitelist the actual script that spawns the workers, hence the nonce. + */ + +let testRuns = 0; +let messageCounter = 0; +let numberSubTests = 9; // 3 workers * 3 frames = 9 + +function checkFinish() { + messageCounter = 0; + if (testRuns == 0) { + testRuns++; + runTests("https://test1.example.com/tests/dom/security/test/csp/") + return; + } + window.removeEventListener("message", receiveMessage); + SimpleTest.finish(); +} + +window.addEventListener("message", receiveMessage); +function receiveMessage(event) { + let href = event.data.href; + let result = event.data.result; + + if (href.startsWith("https://example.com")) { + if (result == "worker-allowed" || + result == "shared-worker-allowed" || + result == "service-worker-allowed") { + ok(true, "allowing worker from https://example.com (" + result + ")"); + } + else { + ok(false, "blocking worker from https://example.com (" + result + ")"); + } + } + else if (href.startsWith("https://test1.example.com")) { + if (result == "worker-blocked" || + result == "shared-worker-blocked" || + result == "service-worker-blocked") { + ok(true, "blocking worker from https://test1.example.com (" + result + ")"); + } + else { + ok(false, "allowing worker from https://test1.example.com (" + result + ")"); + } + } + else { + // sanity check, we should never enter that branch, bust just in case... + ok(false, "unexpected result: " + result); + } + messageCounter++; + if (messageCounter < numberSubTests) { + return; + } + checkFinish(); +} + +function runTests(aPath) { + document.getElementById("worker-testframe").src = aPath + "file_worker_src_worker_governs.html"; + document.getElementById("child-testframe").src = aPath + "file_worker_src_child_governs.html"; + document.getElementById("script-testframe").src = aPath + "file_worker_src_script_governs.html"; +} + +SpecialPowers.pushPrefEnv({"set": [ + ["dom.serviceWorkers.enabled", true], + ["dom.serviceWorkers.testing.enabled", true], +]}, function() { + runTests("https://example.com/tests/dom/security/test/csp/"); +}); + +</script> +</body> +</html> diff --git a/dom/security/test/general/browser.ini b/dom/security/test/general/browser.ini new file mode 100644 index 000000000..b00baa95d --- /dev/null +++ b/dom/security/test/general/browser.ini @@ -0,0 +1,14 @@ +[DEFAULT] +[browser_test_toplevel_data_navigations.js] +support-files = + file_toplevel_data_navigations.sjs + file_toplevel_data_meta_redirect.html +[browser_test_data_download.js] +support-files = + file_data_download.html +[browser_test_data_text_csv.js] +support-files = + file_data_text_csv.html +[browser_test_view_image_data_navigation.js] +support-files = + file_view_image_data_navigation.html diff --git a/dom/security/test/general/browser_test_data_download.js b/dom/security/test/general/browser_test_data_download.js new file mode 100644 index 000000000..1ee8d5844 --- /dev/null +++ b/dom/security/test/general/browser_test_data_download.js @@ -0,0 +1,37 @@ +"use strict"; + +const kTestPath = getRootDirectory(gTestPath) + .replace("chrome://mochitests/content", "http://example.com") +const kTestURI = kTestPath + "file_data_download.html"; + +function addWindowListener(aURL, aCallback) { + Services.wm.addListener({ + onOpenWindow(aXULWindow) { + info("window opened, waiting for focus"); + Services.wm.removeListener(this); + var domwindow = aXULWindow.QueryInterface(Ci.nsIInterfaceRequestor) + .getInterface(Ci.nsIDOMWindow); + waitForFocus(function() { + is(domwindow.document.location.href, aURL, "should have seen the right window open"); + aCallback(domwindow); + }, domwindow); + }, + onCloseWindow(aXULWindow) { }, + onWindowTitleChange(aXULWindow, aNewTitle) { } + }); +} + +function test() { + waitForExplicitFinish(); + Services.prefs.setBoolPref("security.data_uri.block_toplevel_data_uri_navigations", true); + registerCleanupFunction(function() { + Services.prefs.clearUserPref("security.data_uri.block_toplevel_data_uri_navigations"); + }); + addWindowListener("chrome://mozapps/content/downloads/unknownContentType.xul", function(win) { + is(win.document.getElementById("location").value, "data-foo.html", + "file name of download should match"); + win.close(); + finish(); + }); + gBrowser.loadURI(kTestURI); +} diff --git a/dom/security/test/general/browser_test_data_text_csv.js b/dom/security/test/general/browser_test_data_text_csv.js new file mode 100644 index 000000000..c45e40cc2 --- /dev/null +++ b/dom/security/test/general/browser_test_data_text_csv.js @@ -0,0 +1,37 @@ +"use strict"; + +const kTestPath = getRootDirectory(gTestPath) + .replace("chrome://mochitests/content", "http://example.com") +const kTestURI = kTestPath + "file_data_text_csv.html"; + +function addWindowListener(aURL, aCallback) { + Services.wm.addListener({ + onOpenWindow(aXULWindow) { + info("window opened, waiting for focus"); + Services.wm.removeListener(this); + var domwindow = aXULWindow.QueryInterface(Ci.nsIInterfaceRequestor) + .getInterface(Ci.nsIDOMWindow); + waitForFocus(function() { + is(domwindow.document.location.href, aURL, "should have seen the right window open"); + aCallback(domwindow); + }, domwindow); + }, + onCloseWindow(aXULWindow) { }, + onWindowTitleChange(aXULWindow, aNewTitle) { } + }); +} + +function test() { + waitForExplicitFinish(); + Services.prefs.setBoolPref("security.data_uri.block_toplevel_data_uri_navigations", true); + registerCleanupFunction(function() { + Services.prefs.clearUserPref("security.data_uri.block_toplevel_data_uri_navigations"); + }); + addWindowListener("chrome://mozapps/content/downloads/unknownContentType.xul", function(win) { + is(win.document.getElementById("location").value, "text/csv;foo,bar,foobar", + "file name of download should match"); + win.close(); + finish(); + }); + gBrowser.loadURI(kTestURI); +} diff --git a/dom/security/test/general/browser_test_toplevel_data_navigations.js b/dom/security/test/general/browser_test_toplevel_data_navigations.js new file mode 100644 index 000000000..a13a6350e --- /dev/null +++ b/dom/security/test/general/browser_test_toplevel_data_navigations.js @@ -0,0 +1,54 @@ +/* eslint-disable mozilla/no-arbitrary-setTimeout */ + +"use strict"; + +const kDataBody = "toplevel navigation to data: URI allowed"; +const kDataURI = "data:text/html,<body>" + kDataBody + "</body>"; +const kTestPath = getRootDirectory(gTestPath) + .replace("chrome://mochitests/content", "http://example.com") +const kRedirectURI = kTestPath + "file_toplevel_data_navigations.sjs"; +const kMetaRedirectURI = kTestPath + "file_toplevel_data_meta_redirect.html"; + +add_task(async function test_nav_data_uri() { + await SpecialPowers.pushPrefEnv({ + "set": [["security.data_uri.block_toplevel_data_uri_navigations", true]], + }); + await BrowserTestUtils.withNewTab(kDataURI, async function(browser) { + await ContentTask.spawn(gBrowser.selectedBrowser, {kDataBody}, async function({kDataBody}) { // eslint-disable-line + is(content.document.body.innerHTML, kDataBody, + "data: URI navigation from system should be allowed"); + }); + }); +}); + +add_task(async function test_nav_data_uri_redirect() { + await SpecialPowers.pushPrefEnv({ + "set": [["security.data_uri.block_toplevel_data_uri_navigations", true]], + }); + let tab = BrowserTestUtils.addTab(gBrowser, kRedirectURI); + registerCleanupFunction(async function() { + await BrowserTestUtils.removeTab(tab); + }); + // wait to make sure data: URI did not load before checking that it got blocked + await new Promise(resolve => setTimeout(resolve, 500)); + await ContentTask.spawn(gBrowser.selectedBrowser, {}, async function() { + is(content.document.body.innerHTML, "", + "data: URI navigation after server redirect should be blocked"); + }); +}); + +add_task(async function test_nav_data_uri_meta_redirect() { + await SpecialPowers.pushPrefEnv({ + "set": [["security.data_uri.block_toplevel_data_uri_navigations", true]], + }); + let tab = BrowserTestUtils.addTab(gBrowser, kMetaRedirectURI); + registerCleanupFunction(async function() { + await BrowserTestUtils.removeTab(tab); + }); + // wait to make sure data: URI did not load before checking that it got blocked + await new Promise(resolve => setTimeout(resolve, 500)); + await ContentTask.spawn(gBrowser.selectedBrowser, {}, async function() { + is(content.document.body.innerHTML, "", + "data: URI navigation after meta redirect should be blocked"); + }); +}); diff --git a/dom/security/test/general/browser_test_view_image_data_navigation.js b/dom/security/test/general/browser_test_view_image_data_navigation.js new file mode 100644 index 000000000..22de35894 --- /dev/null +++ b/dom/security/test/general/browser_test_view_image_data_navigation.js @@ -0,0 +1,30 @@ +"use strict"; + +const TEST_PAGE = getRootDirectory(gTestPath) + "file_view_image_data_navigation.html"; + +add_task(async function test_principal_right_click_open_link_in_new_tab() { + await SpecialPowers.pushPrefEnv({ + "set": [["security.data_uri.block_toplevel_data_uri_navigations", true]], + }); + + await BrowserTestUtils.withNewTab(TEST_PAGE, async function(browser) { + let loadPromise = BrowserTestUtils.browserLoaded(gBrowser.selectedBrowser, true); + + // simulate right-click->view-image + BrowserTestUtils.waitForEvent(document, "popupshown", false, event => { + // These are operations that must be executed synchronously with the event. + document.getElementById("context-viewimage").doCommand(); + event.target.hidePopup(); + return true; + }); + BrowserTestUtils.synthesizeMouseAtCenter("#testimage", + { type: "contextmenu", button: 2 }, + gBrowser.selectedBrowser); + await loadPromise; + + await ContentTask.spawn(gBrowser.selectedBrowser, {}, async function() { + ok(content.document.location.toString().startsWith("data:image/svg+xml;"), + "data:image/svg navigation allowed through right-click view-image") + }); + }); +}); diff --git a/dom/security/test/general/file_block_toplevel_data_navigation.html b/dom/security/test/general/file_block_toplevel_data_navigation.html new file mode 100644 index 000000000..5fbfdfdef --- /dev/null +++ b/dom/security/test/general/file_block_toplevel_data_navigation.html @@ -0,0 +1,14 @@ +<!DOCTYPE HTML> +<html> +<head> + <meta charset="utf-8"> + <title>Toplevel data navigation</title> +</head> +<body> +test1: clicking data: URI tries to navigate window<br/> +<a id="testlink" href="data:text/html,<body>toplevel data: URI navigations should be blocked</body>">click me</a> +<script> + document.getElementById('testlink').click(); +</script> +</body> +</html> diff --git a/dom/security/test/general/file_block_toplevel_data_navigation2.html b/dom/security/test/general/file_block_toplevel_data_navigation2.html new file mode 100644 index 000000000..e0308e1ae --- /dev/null +++ b/dom/security/test/general/file_block_toplevel_data_navigation2.html @@ -0,0 +1,29 @@ +<!DOCTYPE HTML> +<html> +<head> + <meta charset="utf-8"> + <title>Toplevel data navigation</title> +</head> +<body> +test2: data: URI in iframe tries to window.open(data:, _blank);<br/> +<iframe id="testFrame" src=""></iframe> +<script> + let DATA_URI = `data:text/html,<body><script> + var win = window.open("data:text/html,<body>toplevel data: URI navigations should be blocked</body>", "_blank"); + setTimeout(function () { + var result = win.document.body.innerHTML === "" ? "blocked" : "navigated"; + parent.postMessage(result, "*"); + win.close(); + }, 1000); + <\/script></body>`; + + window.addEventListener("message", receiveMessage); + function receiveMessage(event) { + window.removeEventListener("message", receiveMessage); + // propagate the information back to the caller + window.opener.postMessage(event.data, "*"); + } + document.getElementById('testFrame').src = DATA_URI; +</script> +</body> +</html> diff --git a/dom/security/test/general/file_block_toplevel_data_navigation3.html b/dom/security/test/general/file_block_toplevel_data_navigation3.html new file mode 100644 index 000000000..34aeddab3 --- /dev/null +++ b/dom/security/test/general/file_block_toplevel_data_navigation3.html @@ -0,0 +1,13 @@ +<!DOCTYPE HTML> +<html> +<head> + <meta charset="utf-8"> + <title>Toplevel data navigation</title> +</head> +<body> +test3: performing data: URI navigation through win.loc.href<br/> +<script> + window.location.href = "data:text/html,<body>toplevel data: URI navigations should be blocked</body>"; +</script> +</body> +</html> diff --git a/dom/security/test/general/file_block_toplevel_data_redirect.sjs b/dom/security/test/general/file_block_toplevel_data_redirect.sjs new file mode 100644 index 000000000..64e294cab --- /dev/null +++ b/dom/security/test/general/file_block_toplevel_data_redirect.sjs @@ -0,0 +1,14 @@ +// Custom *.sjs file specifically for the needs of Bug: +// Bug 1394554 - Block toplevel data: URI navigations after redirect + +var DATA_URI = + "<body>toplevel data: URI navigations after redirect should be blocked</body>"; + +function handleRequest(request, response) +{ + // avoid confusing cache behaviors + response.setHeader("Cache-Control", "no-cache", false); + + response.setStatusLine("1.1", 302, "Found"); + response.setHeader("Location", "data:text/html," + escape(DATA_URI), false); +} diff --git a/dom/security/test/general/file_data_download.html b/dom/security/test/general/file_data_download.html new file mode 100644 index 000000000..4cc92fe8f --- /dev/null +++ b/dom/security/test/general/file_data_download.html @@ -0,0 +1,14 @@ +<!DOCTYPE HTML> +<html> +<head> + <title>Test download attribute for data: URI</title> +</head> +<body> + <a href="data:text/html,<body>data download</body>" download="data-foo.html" id="testlink">download data</a> + <script> + // click the link to have the downoad panel appear + let testlink = document.getElementById("testlink"); + testlink.click(); + </script> + </body> +</html> diff --git a/dom/security/test/general/file_data_text_csv.html b/dom/security/test/general/file_data_text_csv.html new file mode 100644 index 000000000..a9ac369d1 --- /dev/null +++ b/dom/security/test/general/file_data_text_csv.html @@ -0,0 +1,14 @@ +<!DOCTYPE HTML> +<html> +<head> + <title>Test open data:text/csv</title> +</head> +<body> + <a href="data:text/csv;foo,bar,foobar" id="testlink">test text/csv</a> + <script> + // click the link to have the downoad panel appear + let testlink = document.getElementById("testlink"); + testlink.click(); + </script> + </body> +</html> diff --git a/dom/security/test/general/file_toplevel_data_meta_redirect.html b/dom/security/test/general/file_toplevel_data_meta_redirect.html new file mode 100644 index 000000000..f4f5deb52 --- /dev/null +++ b/dom/security/test/general/file_toplevel_data_meta_redirect.html @@ -0,0 +1,10 @@ +<html>
+<body>
+<head>
+ <meta http-equiv="refresh"
+ content="0; url='data:text/html,<body>toplevel meta redirect to data: URI should be blocked</body>'">
+</head>
+<body>
+Meta Redirect to data: URI
+</body>
+</html>
diff --git a/dom/security/test/general/file_toplevel_data_navigations.sjs b/dom/security/test/general/file_toplevel_data_navigations.sjs new file mode 100644 index 000000000..501b833e5 --- /dev/null +++ b/dom/security/test/general/file_toplevel_data_navigations.sjs @@ -0,0 +1,14 @@ +// Custom *.sjs file specifically for the needs of Bug: +// Bug 1394554 - Block toplevel data: URI navigations after redirect + +var DATA_URI = + "data:text/html,<body>toplevel data: URI navigations after redirect should be blocked</body>"; + +function handleRequest(request, response) +{ + // avoid confusing cache behaviors + response.setHeader("Cache-Control", "no-cache", false); + + response.setStatusLine("1.1", 302, "Found"); + response.setHeader("Location", DATA_URI, false); +} diff --git a/dom/security/test/general/file_view_image_data_navigation.html b/dom/security/test/general/file_view_image_data_navigation.html new file mode 100644 index 000000000..a3f9acfb4 --- /dev/null +++ b/dom/security/test/general/file_view_image_data_navigation.html @@ -0,0 +1,12 @@ +<!DOCTYPE HTML> +<html> +<head> + <meta charset="utf-8"> + <title>Bug 1407891: Test navigation for right-click view-image on data:image/svg</title> +</head> +<body> + +<img id="testimage" src="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxNiIgaGVpZ2h0PSIxNiIgdmlld0JveD0iMCAwIDE2IDE2Ij4KICA8cGF0aCBkPSJNOCwxMkwzLDcsNCw2bDQsNCw0LTQsMSwxWiIgZmlsbD0iIzZBNkE2QSIgLz4KPC9zdmc+Cg=="></img> + +</body> +</html> diff --git a/dom/security/test/general/mochitest.ini b/dom/security/test/general/mochitest.ini index 70c0c9fb6..f3bcca072 100644 --- a/dom/security/test/general/mochitest.ini +++ b/dom/security/test/general/mochitest.ini @@ -3,7 +3,19 @@ support-files = file_contentpolicytype_targeted_link_iframe.sjs file_nosniff_testserver.sjs file_block_script_wrong_mime_server.sjs + file_block_toplevel_data_navigation.html + file_block_toplevel_data_navigation2.html + file_block_toplevel_data_navigation3.html + file_block_toplevel_data_redirect.sjs [test_contentpolicytype_targeted_link_iframe.html] [test_nosniff.html] [test_block_script_wrong_mime.html] +[test_block_toplevel_data_navigation.html] +skip-if = toolkit == 'android' # intermittent failure +[test_block_toplevel_data_img_navigation.html] +skip-if = toolkit == 'android' # intermittent failure +[test_allow_opening_data_pdf.html] +skip-if = toolkit == 'android' +[test_allow_opening_data_json.html] +skip-if = toolkit == 'android' diff --git a/dom/security/test/general/test_allow_opening_data_json.html b/dom/security/test/general/test_allow_opening_data_json.html new file mode 100644 index 000000000..1530a24e8 --- /dev/null +++ b/dom/security/test/general/test_allow_opening_data_json.html @@ -0,0 +1,39 @@ +<!DOCTYPE HTML> +<html> +<head> + <meta charset="utf-8"> + <title>Bug 1403814: Allow toplevel data URI navigation data:application/json</title> + <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> +</head> +<body> +<script class="testbody" type="text/javascript"> + +SimpleTest.waitForExplicitFinish(); + +function test_toplevel_data_json() { + const DATA_JSON = "data:application/json,{'my_json_key':'my_json_value'}"; + + let win = window.open(DATA_JSON); + let wrappedWin = SpecialPowers.wrap(win); + + // Unfortunately we can't detect whether the JSON has loaded or not using some + // event, hence we are constantly polling location.href till we see that + // the data: URI appears. Test times out on failure. + var jsonLoaded = setInterval(function() { + if (wrappedWin.document.location.href.startsWith("data:application/json")) { + clearInterval(jsonLoaded); + ok(true, "navigating to data:application/json allowed"); + wrappedWin.close(); + SimpleTest.finish(); + } + }, 200); +} + +SpecialPowers.pushPrefEnv({ + set: [["security.data_uri.block_toplevel_data_uri_navigations", true]] +}, test_toplevel_data_json); + +</script> +</body> +</html> diff --git a/dom/security/test/general/test_allow_opening_data_pdf.html b/dom/security/test/general/test_allow_opening_data_pdf.html new file mode 100644 index 000000000..6b51fe57b --- /dev/null +++ b/dom/security/test/general/test_allow_opening_data_pdf.html @@ -0,0 +1,41 @@ +<!DOCTYPE HTML> +<html> +<head> + <meta charset="utf-8"> + <title>Bug 1398692: Allow toplevel navigation to a data:application/pdf</title> + <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> +</head> +<body> +<script class="testbody" type="text/javascript"> + +SimpleTest.waitForExplicitFinish(); + +function test_toplevel_data_pdf() { + // The PDF contains one page and it is a 3/72" square, the minimum allowed by the spec + const DATA_PDF = + "data:application/pdf;base64,JVBERi0xLjANCjEgMCBvYmo8PC9UeXBlL0NhdGFsb2cvUGFnZXMgMiAwIFI+PmVuZG9iaiAyIDAgb2JqPDwvVHlwZS9QYWdlcy9LaWRzWzMgMCBSXS9Db3VudCAxPj5lbmRvYmogMyAwIG9iajw8L1R5cGUvUGFnZS9NZWRpYUJveFswIDAgMyAzXT4+ZW5kb2JqDQp4cmVmDQowIDQNCjAwMDAwMDAwMDAgNjU1MzUgZg0KMDAwMDAwMDAxMCAwMDAwMCBuDQowMDAwMDAwMDUzIDAwMDAwIG4NCjAwMDAwMDAxMDIgMDAwMDAgbg0KdHJhaWxlcjw8L1NpemUgNC9Sb290IDEgMCBSPj4NCnN0YXJ0eHJlZg0KMTQ5DQolRU9G"; + + let win = window.open(DATA_PDF); + let wrappedWin = SpecialPowers.wrap(win); + + // Unfortunately we can't detect whether the PDF has loaded or not using some + // event, hence we are constantly polling location.href till we see that + // the data: URI appears. Test times out on failure. + var pdfLoaded = setInterval(function() { + if (wrappedWin.document.location.href.startsWith("data:application/pdf")) { + clearInterval(pdfLoaded); + ok(true, "navigating to data:application/pdf allowed"); + wrappedWin.close(); + SimpleTest.finish(); + } + }, 200); +} + +SpecialPowers.pushPrefEnv({ + set: [["security.data_uri.block_toplevel_data_uri_navigations", true]] +}, test_toplevel_data_pdf); + +</script> +</body> +</html> diff --git a/dom/security/test/general/test_block_script_wrong_mime.html b/dom/security/test/general/test_block_script_wrong_mime.html index f4da9c577..34d4b621b 100644 --- a/dom/security/test/general/test_block_script_wrong_mime.html +++ b/dom/security/test/general/test_block_script_wrong_mime.html @@ -53,9 +53,6 @@ function testWorker([mime, shouldLoad]) { }; worker.onerror = (error) => { ok(!shouldLoad, `worker with wrong mime '${mime}' should be blocked`); - let msg = error.message; - ok(msg.match(/^NetworkError/) || msg.match(/Failed to load worker script/), - "should gets correct error message"); error.preventDefault(); resolve(); } @@ -74,9 +71,6 @@ function testWorkerImportScripts([mime, shouldLoad]) { }; worker.onerror = (error) => { ok(!shouldLoad, `worker/importScripts with wrong mime '${mime}' should be blocked`); - let msg = error.message; - ok(msg.match(/^NetworkError/) || msg.match(/Failed to load worker script/), - "should gets correct error message"); error.preventDefault(); resolve(); } diff --git a/dom/security/test/general/test_block_toplevel_data_img_navigation.html b/dom/security/test/general/test_block_toplevel_data_img_navigation.html new file mode 100644 index 000000000..7f8dfc748 --- /dev/null +++ b/dom/security/test/general/test_block_toplevel_data_img_navigation.html @@ -0,0 +1,53 @@ +<!DOCTYPE HTML> +<html> +<head> + <meta charset="utf-8"> + <title>Bug 1396798: Do not block toplevel data: navigation to image (except svgs)</title> + <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> +</head> +<body> +<script class="testbody" type="text/javascript"> +SpecialPowers.setBoolPref("security.data_uri.block_toplevel_data_uri_navigations", true); +SimpleTest.registerCleanupFunction(() => { + SpecialPowers.clearUserPref("security.data_uri.block_toplevel_data_uri_navigations"); +}); + +SimpleTest.waitForExplicitFinish(); +SimpleTest.requestFlakyTimeout("have to test that top level data:image loading is blocked/allowed"); + +function test_toplevel_data_image() { + const DATA_PNG = + "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAUAAAAFCAYAAACNbyblAAAAHElEQVQI12P4//8/w38GIAXDIBKE0DHxgljNBAAO9TXL0Y4OHwAAAABJRU5ErkJggg=="; + let win1 = window.open(DATA_PNG); + let wrappedWin1 = SpecialPowers.wrap(win1); + setTimeout(function () { + let images = wrappedWin1.document.getElementsByTagName('img'); + is(images.length, 1, "Loading data:image/png should be allowed"); + is(images[0].src, DATA_PNG, "Sanity: img src matches"); + wrappedWin1.close(); + test_toplevel_data_image_svg(); + }, 1000); +} + +function test_toplevel_data_image_svg() { + const DATA_SVG = + "data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxNiIgaGVpZ2h0PSIxNiIgdmlld0JveD0iMCAwIDE2IDE2Ij4KICA8cGF0aCBkPSJNOCwxMkwzLDcsNCw2bDQsNCw0LTQsMSwxWiIgZmlsbD0iIzZBNkE2QSIgLz4KPC9zdmc+Cg=="; + let win2 = window.open(DATA_SVG); + // Unfortunately we can't detect whether the window was closed using some event, + // hence we are constantly polling till we see that win == null. + // Test times out on failure. + var win2Closed = setInterval(function() { + if (win2 == null || win2.closed) { + clearInterval(win2Closed); + ok(true, "Loading data:image/svg+xml should be blocked"); + SimpleTest.finish(); + } + }, 200); +} +// fire up the tests +test_toplevel_data_image(); + +</script> +</body> +</html> diff --git a/dom/security/test/general/test_block_toplevel_data_navigation.html b/dom/security/test/general/test_block_toplevel_data_navigation.html new file mode 100644 index 000000000..cef232b65 --- /dev/null +++ b/dom/security/test/general/test_block_toplevel_data_navigation.html @@ -0,0 +1,86 @@ +<!DOCTYPE HTML> +<html> +<head> + <meta charset="utf-8"> + <title>Bug 1331351 - Block top level window data: URI navigations</title> + <!-- Including SimpleTest.js so we can use waitForExplicitFinish !--> + <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> +</head> +<body> +<script class="testbody" type="text/javascript"> +SpecialPowers.setBoolPref("security.data_uri.block_toplevel_data_uri_navigations", true); +SimpleTest.registerCleanupFunction(() => { + SpecialPowers.clearUserPref("security.data_uri.block_toplevel_data_uri_navigations"); +}); + +SimpleTest.waitForExplicitFinish(); +SimpleTest.requestFlakyTimeout("have to test that top level data: URI navgiation is blocked"); + +function test1() { + // simple data: URI click navigation should be prevented + let TEST_FILE = "file_block_toplevel_data_navigation.html"; + let win1 = window.open(TEST_FILE); + setTimeout(function () { + ok(SpecialPowers.wrap(win1).document.body.innerHTML.indexOf("test1:") !== -1, + "toplevel data: URI navigation through click() should be blocked"); + win1.close(); + test2(); + }, 1000); +} + +function test2() { + // data: URI in iframe which opens data: URI in _blank should be blocked + let win2 = window.open("file_block_toplevel_data_navigation2.html"); + window.addEventListener("message", receiveMessage); + function receiveMessage(event) { + window.removeEventListener("message", receiveMessage); + is(event.data, "blocked", + "data: URI navigation using _blank from data: URI should be blocked"); + win2.close(); + test3(); + } +} + +function test3() { + // navigating to a data: URI using window.location.href should be blocked + let win3 = window.open("file_block_toplevel_data_navigation3.html"); + setTimeout(function () { + ok(win3.document.body.innerHTML.indexOf("test3:") !== -1, + "data: URI navigation through win.loc.href should be blocked"); + win3.close(); + test4(); + }, 1000); +} + +function test4() { + // navigating to a data: URI using window.open() should be blocked + let win4 = window.open("data:text/html,<body>toplevel data: URI navigations should be blocked</body>"); + setTimeout(function () { + // Please note that the data: URI will be displayed in the URL-Bar but not + // loaded, hence we rather rely on document.body than document.location + is(win4.document.body.innerHTML, "", + "navigating to a data: URI using window.open() should be blocked"); + test5(); + }, 1000); +} + +function test5() { + // navigating to a URI which redirects to a data: URI using window.open() should be blocked + let win5 = window.open("file_block_toplevel_data_redirect.sjs"); + setTimeout(function () { + // Please note that the data: URI will be displayed in the URL-Bar but not + // loaded, hence we rather rely on document.body than document.location + is(SpecialPowers.wrap(win5).document.body.innerHTML, "", + "navigating to URI which redirects to a data: URI using window.open() should be blocked"); + win5.close(); + SimpleTest.finish(); + }, 1000); +} + +// fire up the tests +test1(); + +</script> +</body> +</html> diff --git a/dom/security/test/general/test_contentpolicytype_targeted_link_iframe.html b/dom/security/test/general/test_contentpolicytype_targeted_link_iframe.html index 7b1ab72dc..3ef243824 100644 --- a/dom/security/test/general/test_contentpolicytype_targeted_link_iframe.html +++ b/dom/security/test/general/test_contentpolicytype_targeted_link_iframe.html @@ -61,6 +61,7 @@ var policy = { "content policy type should TYPESUBDOCUMENT"); categoryManager.deleteCategoryEntry("content-policy", POLICYNAME, false); SimpleTest.finish(); + return Ci.nsIContentPolicy.REJECT_REQUEST; } return Ci.nsIContentPolicy.ACCEPT; }, diff --git a/dom/security/test/gtest/TestCSPParser.cpp b/dom/security/test/gtest/TestCSPParser.cpp index fafa7b5d9..893e02db5 100644 --- a/dom/security/test/gtest/TestCSPParser.cpp +++ b/dom/security/test/gtest/TestCSPParser.cpp @@ -204,6 +204,8 @@ TEST(CSPParser, Directives) { static const PolicyTest policies[] = { + { "connect-src xn--mnchen-3ya.de", + "connect-src http://xn--mnchen-3ya.de"}, { "default-src http://www.example.com", "default-src http://www.example.com" }, { "script-src http://www.example.com", @@ -238,6 +240,10 @@ TEST(CSPParser, Directives) "script-src 'nonce-foo' 'strict-dynamic' 'unsafe-inline' https:" }, { "default-src 'sha256-siVR8' 'strict-dynamic' 'unsafe-inline' https: ", "default-src 'sha256-siVR8' 'unsafe-inline' https:" }, + { "worker-src https://example.com", + "worker-src https://example.com" }, + { "worker-src http://worker.com; frame-src http://frame.com; child-src http://child.com", + "worker-src http://worker.com; frame-src http://frame.com; child-src http://child.com" }, }; uint32_t policyCount = sizeof(policies) / sizeof(PolicyTest); diff --git a/dom/security/test/moz.build b/dom/security/test/moz.build index ddb4e9b89..946959dee 100644 --- a/dom/security/test/moz.build +++ b/dom/security/test/moz.build @@ -27,5 +27,6 @@ MOCHITEST_CHROME_MANIFESTS += [ BROWSER_CHROME_MANIFESTS += [ 'contentverifier/browser.ini', 'csp/browser.ini', + 'general/browser.ini', 'hsts/browser.ini', ] |