diff --git a/dom/base/WebSocket.cpp b/dom/base/WebSocket.cpp index e4250d3e1b..c77e7757a1 100644 --- a/dom/base/WebSocket.cpp +++ b/dom/base/WebSocket.cpp @@ -14,6 +14,8 @@ #include "mozilla/DOMEventTargetHelper.h" #include "mozilla/net/WebSocketChannel.h" #include "mozilla/dom/File.h" +#include "mozilla/dom/nsCSPContext.h" +#include "mozilla/dom/nsCSPUtils.h" #include "mozilla/dom/ScriptSettings.h" #include "mozilla/dom/WorkerPrivate.h" #include "mozilla/dom/WorkerRunnable.h" @@ -1506,6 +1508,67 @@ WebSocketImpl::Init(JSContext* aCx, } } + nsCOMPtr uri; + { + nsresult rv = NS_NewURI(getter_AddRefs(uri), mURI); + + // We crash here because we are sure that mURI is a valid URI, so either we + // are OOM'ing or something else bad is happening. + if (NS_WARN_IF(NS_FAILED(rv))) { + MOZ_CRASH(); + } + } + + // Check content policy. + int16_t shouldLoad = nsIContentPolicy::ACCEPT; + nsCOMPtr originDoc = nsContentUtils::GetDocumentFromScriptContext(sc); + mOriginDocument = do_GetWeakReference(originDoc); + aRv = NS_CheckContentLoadPolicy(nsIContentPolicy::TYPE_WEBSOCKET, + uri, + aPrincipal, + originDoc, + EmptyCString(), + nullptr, + &shouldLoad, + nsContentUtils::GetContentPolicy(), + nsContentUtils::GetSecurityManager()); + if (NS_WARN_IF(aRv.Failed())) { + return; + } + + if (NS_CP_REJECTED(shouldLoad)) { + // Disallowed by content policy. + aRv.Throw(NS_ERROR_CONTENT_BLOCKED); + return; + } + + // Potentially the page uses the CSP directive 'upgrade-insecure-requests'. + // In such a case we have to upgrade ws: to wss: and also update mSecure + // to reflect that upgrade. Please note that we can not upgrade from ws: + // to wss: before performing content policy checks because CSP needs to + // send reports in case the scheme is about to be upgraded. + if (!mSecure && originDoc && originDoc->GetUpgradeInsecureRequests()) { + // let's use the old specification before the upgrade for logging + NS_ConvertUTF8toUTF16 reportSpec(mURI); + + // upgrade the request from ws:// to wss:// and mark as secure + mURI.ReplaceSubstring("ws://", "wss://"); + if (NS_WARN_IF(mURI.Find("wss://") != 0)) { + return; + } + mSecure = true; + + const char16_t* params[] = { reportSpec.get(), NS_LITERAL_STRING("wss").get() }; + CSP_LogLocalizedStr(NS_LITERAL_STRING("upgradeInsecureRequest").get(), + params, ArrayLength(params), + EmptyString(), // aSourceFile + EmptyString(), // aScriptSample + 0, // aLineNumber + 0, // aColumnNumber + nsIScriptError::warningFlag, "CSP", + mInnerWindowID); + } + // Don't allow https:// to open ws:// if (!mSecure && !Preferences::GetBool("network.websocket.allowInsecureFromHTTPS", @@ -1558,40 +1621,6 @@ WebSocketImpl::Init(JSContext* aCx, AppendUTF16toUTF8(aProtocolArray[index], mRequestedProtocolList); } - nsCOMPtr uri; - { - nsresult rv = NS_NewURI(getter_AddRefs(uri), mURI); - - // We crash here because we are sure that mURI is a valid URI, so either we - // are OOM'ing or something else bad is happening. - if (NS_FAILED(rv)) { - MOZ_CRASH(); - } - } - - // Check content policy. - int16_t shouldLoad = nsIContentPolicy::ACCEPT; - nsCOMPtr originDoc = nsContentUtils::GetDocumentFromScriptContext(sc); - mOriginDocument = do_GetWeakReference(originDoc); - aRv = NS_CheckContentLoadPolicy(nsIContentPolicy::TYPE_WEBSOCKET, - uri, - aPrincipal, - originDoc, - EmptyCString(), - nullptr, - &shouldLoad, - nsContentUtils::GetContentPolicy(), - nsContentUtils::GetSecurityManager()); - if (NS_WARN_IF(aRv.Failed())) { - return; - } - - if (NS_CP_REJECTED(shouldLoad)) { - // Disallowed by content policy. - aRv.Throw(NS_ERROR_CONTENT_BLOCKED); - return; - } - // the constructor should throw a SYNTAX_ERROR only if it fails to parse the // url parameter, so don't throw if InitializeConnection fails, and call // onerror/onclose asynchronously diff --git a/dom/base/nsDocument.cpp b/dom/base/nsDocument.cpp index ad13cee240..012d68455c 100644 --- a/dom/base/nsDocument.cpp +++ b/dom/base/nsDocument.cpp @@ -231,6 +231,7 @@ #include "mozilla/dom/FontFaceSet.h" #include "mozilla/dom/BoxObject.h" #include "gfxVR.h" +#include "gfxPrefs.h" #include "nsISpeculativeConnect.h" @@ -1535,6 +1536,7 @@ nsIDocument::nsIDocument() : nsINode(nullNodeInfo), mReferrerPolicySet(false), mReferrerPolicy(mozilla::net::RP_Default), + mUpgradeInsecureRequests(false), mCharacterSet(NS_LITERAL_CSTRING("ISO-8859-1")), mNodeInfoManager(nullptr), mCompatMode(eCompatibility_FullStandards), @@ -2264,7 +2266,7 @@ nsDocument::Reset(nsIChannel* aChannel, nsILoadGroup* aLoadGroup) NS_GET_IID(nsIURI), getter_AddRefs(baseURI)); if (baseURI) { mDocumentBaseURI = baseURI; - mChromeXHRDocBaseURI = baseURI; + mChromeXHRDocBaseURI = nullptr; } } @@ -2346,7 +2348,7 @@ nsDocument::ResetToURI(nsIURI *aURI, nsILoadGroup *aLoadGroup, mOriginalURI = nullptr; SetDocumentURI(aURI); - mChromeXHRDocURI = aURI; + mChromeXHRDocURI = nullptr; // If mDocumentBaseURI is null, nsIDocument::GetBaseURI() returns // mDocumentURI. mDocumentBaseURI = nullptr; @@ -2686,6 +2688,19 @@ nsDocument::StartDocumentLoad(const char* aCommand, nsIChannel* aChannel, WarnIfSandboxIneffective(docShell, mSandboxFlags, GetChannel()); } + // The CSP directive upgrade-insecure-requests not only applies to the + // toplevel document, but also to nested documents. Let's propagate that + // flag from the parent to the nested document. + nsCOMPtr treeItem = this->GetDocShell(); + if (treeItem) { + nsCOMPtr sameTypeParent; + treeItem->GetSameTypeParent(getter_AddRefs(sameTypeParent)); + if (sameTypeParent) { + mUpgradeInsecureRequests = + sameTypeParent->GetDocument()->GetUpgradeInsecureRequests(); + } + } + // If this is not a data document, set CSP. if (!mLoadedAsData) { nsresult rv = InitCSP(aChannel); @@ -2968,6 +2983,13 @@ nsDocument::InitCSP(nsIChannel* aChannel) // speculative loads. } + // ------ Set flag for 'upgrade-insecure-requests' if not already + // inherited from the parent context. + if (!mUpgradeInsecureRequests) { + rv = csp->GetUpgradeInsecureRequests(&mUpgradeInsecureRequests); + NS_ENSURE_SUCCESS(rv, rv); + } + rv = principal->SetCsp(csp); NS_ENSURE_SUCCESS(rv, rv); MOZ_LOG(gCspPRLog, LogLevel::Debug, @@ -7900,7 +7922,7 @@ nsDocument::GetViewportInfo(const ScreenIntSize& aDisplaySize) /*allowDoubleTapZoom*/ true); } - if (!Preferences::GetBool("dom.meta-viewport.enabled", false)) { + if (!gfxPrefs::MetaViewportEnabled()) { return nsViewportInfo(aDisplaySize, defaultScale, /*allowZoom*/ false, @@ -11267,8 +11289,17 @@ ExitFullscreenInDocTree(nsIDocument* aMaybeNotARootDoc) if (!root) { return; } - NS_ASSERTION(root->IsFullScreenDoc(), - "Fullscreen root should be a fullscreen doc..."); + if (!root->IsFullScreenDoc()) { + // If a document was detached before exiting from fullscreen, it is + // possible that the root had left fullscreen state. In this case, + // we would not get anything from the ResetFullScreen() call. Root's + // not being a fullscreen doc also means the widget should have + // exited fullscreen state. It means even if we do not return here, + // we would actually do nothing below except crashing ourselves via + // dispatching the "MozDOMFullscreen:Exited" event to an nonexistent + // document. + return; + } // Stores a list of documents to which we must dispatch "mozfullscreenchange". // We're required by the spec to dispatch the events in leaf-to-root diff --git a/dom/base/nsIDocument.h b/dom/base/nsIDocument.h index fff07d1317..0da4813172 100644 --- a/dom/base/nsIDocument.h +++ b/dom/base/nsIDocument.h @@ -317,8 +317,16 @@ public: return GetReferrerPolicy(); } - void SetReferrer(const nsACString& aReferrer) { - mReferrer = aReferrer; + /** + * If true, this flag indicates that all subresource loads for this + * document need to be upgraded from http to https. + * This flag becomes true if the CSP of the document itself, or any + * of the document's ancestors up to the toplevel document makes use + * of the CSP directive 'upgrade-insecure-requests'. + */ + bool GetUpgradeInsecureRequests() const + { + return mUpgradeInsecureRequests; } /** @@ -2691,6 +2699,8 @@ protected: bool mReferrerPolicySet; ReferrerPolicyEnum mReferrerPolicy; + bool mUpgradeInsecureRequests; + mozilla::WeakPtr mDocumentContainer; nsCString mCharacterSet; diff --git a/dom/base/nsXMLHttpRequest.cpp b/dom/base/nsXMLHttpRequest.cpp index 54b29a01c8..fc794cff50 100644 --- a/dom/base/nsXMLHttpRequest.cpp +++ b/dom/base/nsXMLHttpRequest.cpp @@ -2218,9 +2218,6 @@ nsXMLHttpRequest::OnStartRequest(nsIRequest *request, nsISupports *ctxt) !(mState & XML_HTTP_REQUEST_USE_XSITE_AC)); NS_ENSURE_SUCCESS(rv, rv); - // the spec requires the response document.referrer to be the empty string - mResponseXML->SetReferrer(NS_LITERAL_CSTRING("")); - mXMLParserStreamListener = listener; rv = mXMLParserStreamListener->OnStartRequest(request, ctxt); NS_ENSURE_SUCCESS(rv, rv); diff --git a/dom/base/test/test_XHRDocURI.html b/dom/base/test/test_XHRDocURI.html index f80e4c9078..f42223b256 100644 --- a/dom/base/test/test_XHRDocURI.html +++ b/dom/base/test/test_XHRDocURI.html @@ -486,6 +486,14 @@ function runTest() { xhr.send(); yield undefined; + history.pushState({}, "pushStateTest", window.location.href + "/pushStateTest"); + ok(document.documentURI.indexOf("pushStateTest") > -1); + + var chromeDoc = SpecialPowers.wrap(document); + ok(chromeDoc.documentURI.indexOf("pushStateTest") > -1); + + history.back(); + SimpleTest.finish(); SpecialPowers.removePermission("systemXHR", document); yield undefined; diff --git a/dom/html/HTMLFormElement.cpp b/dom/html/HTMLFormElement.cpp index 89a0bcef96..edd7876334 100644 --- a/dom/html/HTMLFormElement.cpp +++ b/dom/html/HTMLFormElement.cpp @@ -12,6 +12,8 @@ #include "mozilla/EventStateManager.h" #include "mozilla/EventStates.h" #include "mozilla/dom/AutocompleteErrorEvent.h" +#include "mozilla/dom/nsCSPUtils.h" +#include "mozilla/dom/nsCSPContext.h" #include "mozilla/dom/HTMLFormControlsCollection.h" #include "mozilla/dom/HTMLFormElementBinding.h" #include "mozilla/Move.h" @@ -41,6 +43,7 @@ #include "nsCategoryManagerUtils.h" #include "nsISimpleEnumerator.h" #include "nsRange.h" +#include "nsIScriptError.h" #include "nsIScriptSecurityManager.h" #include "nsNetUtil.h" #include "nsIInterfaceRequestorUtils.h" @@ -1745,6 +1748,40 @@ HTMLFormElement::GetActionURL(nsIURI** aActionURL, } } + // Potentially the page uses the CSP directive 'upgrade-insecure-requests'. In + // such a case we have to upgrade the action url from http:// to https://. + // If the actionURL is not http, then there is nothing to do. + bool isHttpScheme = false; + rv = actionURL->SchemeIs("http", &isHttpScheme); + NS_ENSURE_SUCCESS(rv, rv); + if (isHttpScheme && document->GetUpgradeInsecureRequests()) { + // let's use the old specification before the upgrade for logging + nsAutoCString spec; + rv = actionURL->GetSpec(spec); + NS_ENSURE_SUCCESS(rv, rv); + NS_ConvertUTF8toUTF16 reportSpec(spec); + + // upgrade the actionURL from http:// to use https:// + rv = actionURL->SetScheme(NS_LITERAL_CSTRING("https")); + NS_ENSURE_SUCCESS(rv, rv); + + // let's log a message to the console that we are upgrading a request + nsAutoCString scheme; + rv = actionURL->GetScheme(scheme); + NS_ENSURE_SUCCESS(rv, rv); + NS_ConvertUTF8toUTF16 reportScheme(scheme); + + const char16_t* params[] = { reportSpec.get(), reportScheme.get() }; + CSP_LogLocalizedStr(NS_LITERAL_STRING("upgradeInsecureRequest").get(), + params, ArrayLength(params), + EmptyString(), // aSourceFile + EmptyString(), // aScriptSample + 0, // aLineNumber + 0, // aColumnNumber + nsIScriptError::warningFlag, "CSP", + document->InnerWindowID()); + } + // // Assign to the output // diff --git a/dom/html/HTMLSharedElement.cpp b/dom/html/HTMLSharedElement.cpp index bc7fc41c8c..0b8099b4aa 100644 --- a/dom/html/HTMLSharedElement.cpp +++ b/dom/html/HTMLSharedElement.cpp @@ -175,10 +175,9 @@ SetBaseURIUsingFirstBaseWithHref(nsIDocument* aDocument, nsIContent* aMustMatch) // Try to set our base URI. If that fails, try to set base URI to null nsresult rv = aDocument->SetBaseURI(newBaseURI); - aDocument->SetChromeXHRDocBaseURI(newBaseURI); + aDocument->SetChromeXHRDocBaseURI(nullptr); if (NS_FAILED(rv)) { aDocument->SetBaseURI(nullptr); - aDocument->SetChromeXHRDocBaseURI(nullptr); } return; } diff --git a/dom/interfaces/security/nsIContentSecurityPolicy.idl b/dom/interfaces/security/nsIContentSecurityPolicy.idl index 422558e7e5..592935b53a 100644 --- a/dom/interfaces/security/nsIContentSecurityPolicy.idl +++ b/dom/interfaces/security/nsIContentSecurityPolicy.idl @@ -20,7 +20,7 @@ interface nsIURI; typedef unsigned short CSPDirective; -[scriptable, uuid(059b94e3-45c2-4794-ac2a-5b66a66b5967)] +[scriptable, uuid(b622b0f8-ee51-4f7a-8c23-b3bce20e752e)] interface nsIContentSecurityPolicy : nsISerializable { /** @@ -32,24 +32,25 @@ interface nsIContentSecurityPolicy : nsISerializable * NOTE: When implementing a new directive, you will need to add it here but also * add it to the CSPStrDirectives array in nsCSPUtils.h. */ - const unsigned short NO_DIRECTIVE = 0; - const unsigned short DEFAULT_SRC_DIRECTIVE = 1; - const unsigned short SCRIPT_SRC_DIRECTIVE = 2; - const unsigned short OBJECT_SRC_DIRECTIVE = 3; - const unsigned short STYLE_SRC_DIRECTIVE = 4; - const unsigned short IMG_SRC_DIRECTIVE = 5; - const unsigned short MEDIA_SRC_DIRECTIVE = 6; - const unsigned short FRAME_SRC_DIRECTIVE = 7; - const unsigned short FONT_SRC_DIRECTIVE = 8; - const unsigned short CONNECT_SRC_DIRECTIVE = 9; - const unsigned short REPORT_URI_DIRECTIVE = 10; - const unsigned short FRAME_ANCESTORS_DIRECTIVE = 11; - const unsigned short REFLECTED_XSS_DIRECTIVE = 12; - const unsigned short BASE_URI_DIRECTIVE = 13; - const unsigned short FORM_ACTION_DIRECTIVE = 14; - const unsigned short REFERRER_DIRECTIVE = 15; - const unsigned short WEB_MANIFEST_SRC_DIRECTIVE = 16; - const unsigned short CHILD_SRC_DIRECTIVE = 17; + const unsigned short NO_DIRECTIVE = 0; + const unsigned short DEFAULT_SRC_DIRECTIVE = 1; + const unsigned short SCRIPT_SRC_DIRECTIVE = 2; + const unsigned short OBJECT_SRC_DIRECTIVE = 3; + const unsigned short STYLE_SRC_DIRECTIVE = 4; + const unsigned short IMG_SRC_DIRECTIVE = 5; + const unsigned short MEDIA_SRC_DIRECTIVE = 6; + const unsigned short FRAME_SRC_DIRECTIVE = 7; + const unsigned short FONT_SRC_DIRECTIVE = 8; + const unsigned short CONNECT_SRC_DIRECTIVE = 9; + const unsigned short REPORT_URI_DIRECTIVE = 10; + const unsigned short FRAME_ANCESTORS_DIRECTIVE = 11; + const unsigned short REFLECTED_XSS_DIRECTIVE = 12; + const unsigned short BASE_URI_DIRECTIVE = 13; + const unsigned short FORM_ACTION_DIRECTIVE = 14; + const unsigned short REFERRER_DIRECTIVE = 15; + const unsigned short WEB_MANIFEST_SRC_DIRECTIVE = 16; + const unsigned short UPGRADE_IF_INSECURE_DIRECTIVE = 17; + const unsigned short CHILD_SRC_DIRECTIVE = 18; /** * Accessor method for a read-only string version of the policy at a given @@ -63,6 +64,13 @@ interface nsIContentSecurityPolicy : nsISerializable */ readonly attribute unsigned long policyCount; + /** + * Returns whether this policy uses the directive upgrade-insecure-requests. + * Please note that upgrade-insecure-reqeusts also applies if the parent or + * including document (context) makes use of the directive. + */ + readonly attribute bool upgradeInsecureRequests; + /** * Obtains the referrer policy (as integer) for this browsing context as * specified in CSP. If there are multiple policies and... diff --git a/dom/locales/en-US/chrome/security/csp.properties b/dom/locales/en-US/chrome/security/csp.properties index 3580070505..1d5237c537 100644 --- a/dom/locales/en-US/chrome/security/csp.properties +++ b/dom/locales/en-US/chrome/security/csp.properties @@ -29,6 +29,13 @@ couldNotProcessUnknownDirective = Couldn't process unknown directive '%1$S' # LOCALIZATION NOTE (ignoringUnknownOption): # %1$S is the option that could not be understood ignoringUnknownOption = Ignoring unknown option %1$S +# LOCALIZATION NOTE (ignoringDuplicateSrc): +# %1$S defines the duplicate src +ignoringDuplicateSrc = Ignoring duplicate source %1$S +# LOCALIZATION NOTE (ignoringSrcWithinScriptSrc): +# %1$S is the ignored src +# script-src is a directive name and should not be localized +ignoringSrcWithinScriptSrc = Ignoring "%1$S" within script-src: nonce-source or hash-source specified # LOCALIZATION NOTE (reportURInotHttpsOrHttp2): # %1$S is the ETLD of the report URI that is not HTTP or HTTPS reportURInotHttpsOrHttp2 = The report URI (%1$S) should be an HTTP or HTTPS URI. @@ -47,6 +54,11 @@ inlineStyleBlocked = An attempt to apply inline style sheets has been blocked # LOCALIZATION NOTE (scriptFromStringBlocked): # eval is a name and should not be localized. scriptFromStringBlocked = An attempt to call JavaScript from a string (by calling a function like eval) has been blocked +# LOCALIZATION NOTE (upgradeInsecureRequest): +# %1$S is the URL of the upgraded request; %2$S is the upgraded scheme. +upgradeInsecureRequest = Upgrading insecure request '%1$S' to use '%2$S' +# LOCALIZATION NOTE (ignoreSrcForDirective): +ignoreSrcForDirective = Ignoring srcs for directive '%1$S' # LOCALIZATION NOTE (hostNameMightBeKeyword): # %1$S is the hostname in question and %2$S is the keyword hostNameMightBeKeyword = Interpreting %1$S as a hostname, not a keyword. If you intended this to be a keyword, use '%2$S' (wrapped in single quotes). diff --git a/dom/security/nsCORSListenerProxy.cpp b/dom/security/nsCORSListenerProxy.cpp index fc8fa90db1..a3c28cc9fa 100644 --- a/dom/security/nsCORSListenerProxy.cpp +++ b/dom/security/nsCORSListenerProxy.cpp @@ -819,6 +819,73 @@ nsCORSListenerProxy::OnRedirectVerifyCallback(nsresult result) return NS_OK; } +// Please note that the CSP directive 'upgrade-insecure-requests' relies +// on the promise that channels get updated from http: to https: before +// the channel fetches any data from the netwerk. Such channels should +// not be blocked by CORS and marked as cross origin requests. E.g.: +// toplevel page: https://www.example.com loads +// xhr: http://www.example.com/foo which gets updated to +// https://www.example.com/foo +// In such a case we should bail out of CORS and rely on the promise that +// nsHttpChannel::Connect() upgrades the request from http to https. +bool +CheckUpgradeInsecureRequestsPreventsCORS(nsIPrincipal* aRequestingPrincipal, + nsIChannel* aChannel) +{ + nsCOMPtr channelURI; + nsresult rv = NS_GetFinalChannelURI(aChannel, getter_AddRefs(channelURI)); + NS_ENSURE_SUCCESS(rv, false); + bool isHttpScheme = false; + rv = channelURI->SchemeIs("http", &isHttpScheme); + NS_ENSURE_SUCCESS(rv, false); + + // upgrade insecure requests is only applicable to http requests + if (!isHttpScheme) { + return false; + } + + nsCOMPtr principalURI; + rv = aRequestingPrincipal->GetURI(getter_AddRefs(principalURI)); + NS_ENSURE_SUCCESS(rv, false); + + // if the requestingPrincipal does not have a uri, there is nothing to do + if (!principalURI) { + return false; + } + + nsCOMPtroriginalURI; + rv = aChannel->GetOriginalURI(getter_AddRefs(originalURI)); + NS_ENSURE_SUCCESS(rv, false); + + nsAutoCString principalHost, channelHost, origChannelHost; + + // if we can not query a host from the uri, there is nothing to do + if (NS_FAILED(principalURI->GetAsciiHost(principalHost)) || + NS_FAILED(channelURI->GetAsciiHost(channelHost)) || + NS_FAILED(originalURI->GetAsciiHost(origChannelHost))) { + return false; + } + + // if the hosts do not match, there is nothing to do + if (!principalHost.EqualsIgnoreCase(channelHost.get())) { + return false; + } + + // also check that uri matches the one of the originalURI + if (!channelHost.EqualsIgnoreCase(origChannelHost.get())) { + return false; + } + + nsCOMPtr loadInfo; + rv = aChannel->GetLoadInfo(getter_AddRefs(loadInfo)); + NS_ENSURE_SUCCESS(rv, false); + + // lets see if the loadInfo indicates that the request will + // be upgraded before fetching any data from the netwerk. + return loadInfo->GetUpgradeInsecureRequests(); +} + + nsresult nsCORSListenerProxy::UpdateChannel(nsIChannel* aChannel, DataURIHandling aAllowDataURI) @@ -876,6 +943,17 @@ nsCORSListenerProxy::UpdateChannel(nsIChannel* aChannel, return NS_OK; } + // if the CSP directive 'upgrade-insecure-requests' is used then we should + // not incorrectly require CORS if the only difference of a subresource + // request and the main page is the scheme. + // e.g. toplevel page: https://www.example.com loads + // xhr: http://www.example.com/somefoo, + // then the xhr request will be upgraded to https before it fetches any data + // from the netwerk, hence we shouldn't require CORS in that specific case. + if (CheckUpgradeInsecureRequestsPreventsCORS(mRequestingPrincipal, aChannel)) { + return NS_OK; + } + // It's a cross site load mHasBeenCrossSite = true; diff --git a/dom/security/nsCORSListenerProxy.h b/dom/security/nsCORSListenerProxy.h index bce78aff7f..4ae5ef781e 100644 --- a/dom/security/nsCORSListenerProxy.h +++ b/dom/security/nsCORSListenerProxy.h @@ -83,6 +83,10 @@ private: nsCOMPtr mInterceptController; bool mWithCredentials; bool mRequestApproved; + // Please note that the member variable mHasBeenCrossSite may rely on the + // promise that the CSP directive 'upgrade-insecure-requests' upgrades + // an http: request to https: in nsHttpChannel::Connect() and hence + // a request might not be marked as cross site request based on that promise. bool mHasBeenCrossSite; bool mIsPreflight; #ifdef DEBUG diff --git a/dom/security/nsCSPContext.cpp b/dom/security/nsCSPContext.cpp index a4b3a604b6..4349317f1d 100644 --- a/dom/security/nsCSPContext.cpp +++ b/dom/security/nsCSPContext.cpp @@ -318,6 +318,19 @@ nsCSPContext::GetPolicyCount(uint32_t *outPolicyCount) return NS_OK; } +NS_IMETHODIMP +nsCSPContext::GetUpgradeInsecureRequests(bool *outUpgradeRequest) +{ + *outUpgradeRequest = false; + for (uint32_t i = 0; i < mPolicies.Length(); i++) { + if (mPolicies[i]->hasDirective(nsIContentSecurityPolicy::UPGRADE_IF_INSECURE_DIRECTIVE)) { + *outUpgradeRequest = true; + return NS_OK; + } + } + return NS_OK; +} + NS_IMETHODIMP nsCSPContext::GetReferrerPolicy(uint32_t* outPolicy, bool* outIsSet) { diff --git a/dom/security/nsCSPParser.cpp b/dom/security/nsCSPParser.cpp index 0eb96b88ac..a6c9f31ec2 100644 --- a/dom/security/nsCSPParser.cpp +++ b/dom/security/nsCSPParser.cpp @@ -123,7 +123,9 @@ nsCSPTokenizer::tokenizeCSPPolicy(const nsAString &aPolicyString, nsCSPParser::nsCSPParser(cspTokens& aTokens, nsIURI* aSelfURI, uint64_t aInnerWindowID) - : mTokens(aTokens) + : mHasHashOrNonce(false) + , mUnsafeInlineKeywordSrc(nullptr) + , mTokens(aTokens) , mSelfURI(aSelfURI) , mInnerWindowID(aInnerWindowID) { @@ -570,8 +572,22 @@ nsCSPParser::keywordSource() return CSP_CreateHostSrcFromURI(mSelfURI); } - if (CSP_IsKeyword(mCurToken, CSP_UNSAFE_INLINE) || - CSP_IsKeyword(mCurToken, CSP_UNSAFE_EVAL)) { + if (CSP_IsKeyword(mCurToken, CSP_UNSAFE_INLINE)) { + // make sure script-src only contains 'unsafe-inline' once; + // ignore duplicates and log warning + if (mUnsafeInlineKeywordSrc) { + const char16_t* params[] = { mCurToken.get() }; + logWarningErrorToConsole(nsIScriptError::warningFlag, "ignoringDuplicateSrc", + params, ArrayLength(params)); + return nullptr; + } + // cache if we encounter 'unsafe-inline' so we can invalidate (ignore) it in + // case that script-src directive also contains hash- or nonce-. + mUnsafeInlineKeywordSrc = new nsCSPKeywordSrc(CSP_KeywordToEnum(mCurToken)); + return mUnsafeInlineKeywordSrc; + } + + if (CSP_IsKeyword(mCurToken, CSP_UNSAFE_EVAL)) { return new nsCSPKeywordSrc(CSP_KeywordToEnum(mCurToken)); } return nullptr; @@ -676,6 +692,8 @@ nsCSPParser::nonceSource() if (dashIndex < 0) { return nullptr; } + // cache if encountering hash or nonce to invalidate unsafe-inline + mHasHashOrNonce = true; return new nsCSPNonceSrc(Substring(expr, dashIndex + 1, expr.Length() - dashIndex + 1)); @@ -708,6 +726,8 @@ nsCSPParser::hashSource() for (uint32_t i = 0; i < kHashSourceValidFnsLen; i++) { if (algo.LowerCaseEqualsASCII(kHashSourceValidFns[i])) { + // cache if encountering hash or nonce to invalidate unsafe-inline + mHasHashOrNonce = true; return new nsCSPHashSrc(algo, hash); } } @@ -784,10 +804,7 @@ nsCSPParser::sourceExpression() resetCurValue(); // If mCurToken does not provide a scheme (scheme-less source), we apply the scheme - // from selfURI but we also need to remember if the protected resource is http, in - // which case we should allow https loads, see: - // http://www.w3.org/TR/CSP2/#match-source-expression - bool allowHttps = false; + // from selfURI if (parsedScheme.IsEmpty()) { // Resetting internal helpers, because we might already have parsed some of the host // when trying to parse a scheme. @@ -795,14 +812,13 @@ nsCSPParser::sourceExpression() nsAutoCString selfScheme; mSelfURI->GetScheme(selfScheme); parsedScheme.AssignASCII(selfScheme.get()); - allowHttps = selfScheme.EqualsASCII("http"); } // At this point we are expecting a host to be parsed. // Trying to create a new nsCSPHost. if (nsCSPHostSrc *cspHost = hostSource()) { // Do not forget to set the parsed scheme. - cspHost->setScheme(parsedScheme, allowHttps); + cspHost->setScheme(parsedScheme); return cspHost; } // Error was reported in hostSource() @@ -968,6 +984,12 @@ nsCSPParser::directiveName() params, ArrayLength(params)); return nullptr; } + + // special case handling for upgrade-insecure-requests + if (CSP_IsDirective(mCurToken, nsIContentSecurityPolicy::UPGRADE_IF_INSECURE_DIRECTIVE)) { + return new nsUpgradeInsecureDirective(CSP_StringToCSPDirective(mCurToken)); + } + return new nsCSPDirective(CSP_StringToCSPDirective(mCurToken)); } @@ -1006,6 +1028,25 @@ nsCSPParser::directive() return; } + // special case handling for upgrade-insecure-requests, which is only specified + // by a directive name but does not include any srcs. + if (cspDir->equals(nsIContentSecurityPolicy::UPGRADE_IF_INSECURE_DIRECTIVE)) { + if (mCurDir.Length() > 1) { + const char16_t* params[] = { NS_LITERAL_STRING("upgrade-insecure-requests").get() }; + logWarningErrorToConsole(nsIScriptError::warningFlag, + "ignoreSrcForDirective", + params, ArrayLength(params)); + } + // add the directive and return + mPolicy->addUpgradeInsecDir(static_cast(cspDir)); + return; + } + + // make sure to reset cache variables when trying to invalidate unsafe-inline; + // unsafe-inline might not only appear in script-src, but also in default-src + mHasHashOrNonce = false; + mUnsafeInlineKeywordSrc = nullptr; + // Try to parse all the srcs by handing the array off to directiveValue nsTArray srcs; directiveValue(srcs); @@ -1017,6 +1058,18 @@ nsCSPParser::directive() srcs.AppendElement(keyword); } + // if a hash or nonce is specified within script-src, then + // unsafe-inline should be ignored, see: + // http://www.w3.org/TR/CSP2/#directive-script-src + if (cspDir->equals(nsIContentSecurityPolicy::SCRIPT_SRC_DIRECTIVE) && + mHasHashOrNonce && mUnsafeInlineKeywordSrc) { + mUnsafeInlineKeywordSrc->invalidate(); + // log to the console that unsafe-inline will be ignored + const char16_t* params[] = { NS_LITERAL_STRING("'unsafe-inline'").get() }; + logWarningErrorToConsole(nsIScriptError::warningFlag, "ignoringSrcWithinScriptSrc", + params, ArrayLength(params)); + } + // Add the newly created srcs to the directive and add the directive to the policy cspDir->addSrcs(srcs); mPolicy->addDirective(cspDir); diff --git a/dom/security/nsCSPParser.h b/dom/security/nsCSPParser.h index 5308395585..a4e34996a9 100644 --- a/dom/security/nsCSPParser.h +++ b/dom/security/nsCSPParser.h @@ -229,6 +229,10 @@ class nsCSPParser { nsString mCurToken; nsTArray mCurDir; + // cache variables to ignore unsafe-inline if hash or nonce is specified + bool mHasHashOrNonce; // false, if no hash or nonce is defined + nsCSPKeywordSrc* mUnsafeInlineKeywordSrc; // null, otherwise invlidate() + cspTokens mTokens; nsIURI* mSelfURI; nsCSPPolicy* mPolicy; diff --git a/dom/security/nsCSPUtils.cpp b/dom/security/nsCSPUtils.cpp index 595a990e28..c4643ea2e3 100644 --- a/dom/security/nsCSPUtils.cpp +++ b/dom/security/nsCSPUtils.cpp @@ -257,6 +257,59 @@ CSP_IsQuotelessKeyword(const nsAString& aKey) return false; } +/* + * Checks whether the current directive permits a specific + * scheme. This function is called from nsCSPSchemeSrc() and + * also nsCSPHostSrc. + * @param aEnforcementScheme + * The scheme that this directive allows + * @param aUri + * The uri of the subresource load. + * @param aReportOnly + * Whether the enforced policy is report only or not. + * @param aUpgradeInsecure + * Whether the policy makes use of the directive + * 'upgrade-insecure-requests'. + */ + +bool +permitsScheme(const nsAString& aEnforcementScheme, + nsIURI* aUri, + bool aReportOnly, + bool aUpgradeInsecure) +{ + nsAutoCString scheme; + nsresult rv = aUri->GetScheme(scheme); + NS_ENSURE_SUCCESS(rv, false); + + // no scheme to enforce, let's allow the load (e.g. script-src *) + if (aEnforcementScheme.IsEmpty()) { + return true; + } + + // if the scheme matches, all good - allow the load + if (aEnforcementScheme.EqualsASCII(scheme.get())) { + return true; + } + + // 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")) { + return true; + } + + // Allow the load when enforcing upgrade-insecure-requests with the + // promise the request gets upgraded from http to https and ws to wss. + // See nsHttpChannel::Connect() and also WebSocket.cpp. Please note, + // the report only policies should not allow the load and report + // the error back to the page. + return ((aUpgradeInsecure && !aReportOnly) && + ((scheme.EqualsASCII("http") && aEnforcementScheme.EqualsASCII("https")) || + (scheme.EqualsASCII("ws") && aEnforcementScheme.EqualsASCII("wss")))); +} + /* ===== nsCSPSrc ============================ */ nsCSPBaseSrc::nsCSPBaseSrc() @@ -271,7 +324,8 @@ nsCSPBaseSrc::~nsCSPBaseSrc() // nsCSPKeywordSrc and nsCSPHashSource fall back to this base class // implementation which will never allow the load. bool -nsCSPBaseSrc::permits(nsIURI* aUri, const nsAString& aNonce, bool aWasRedirected) const +nsCSPBaseSrc::permits(nsIURI* aUri, const nsAString& aNonce, bool aWasRedirected, + bool aReportOnly, bool aUpgradeInsecure) const { if (CSPUTILSLOGENABLED()) { nsAutoCString spec; @@ -306,19 +360,16 @@ nsCSPSchemeSrc::~nsCSPSchemeSrc() } bool -nsCSPSchemeSrc::permits(nsIURI* aUri, const nsAString& aNonce, bool aWasRedirected) const +nsCSPSchemeSrc::permits(nsIURI* aUri, const nsAString& aNonce, bool aWasRedirected, + bool aReportOnly, bool aUpgradeInsecure) const { if (CSPUTILSLOGENABLED()) { nsAutoCString spec; aUri->GetSpec(spec); CSPUTILSLOG(("nsCSPSchemeSrc::permits, aUri: %s", spec.get())); } - - NS_ASSERTION((!mScheme.EqualsASCII("")), "scheme can not be the empty string"); - nsAutoCString scheme; - nsresult rv = aUri->GetScheme(scheme); - NS_ENSURE_SUCCESS(rv, false); - return mScheme.EqualsASCII(scheme.get()); + MOZ_ASSERT((!mScheme.EqualsASCII("")), "scheme can not be the empty string"); + return permitsScheme(mScheme, aUri, aReportOnly, aUpgradeInsecure); } void @@ -332,7 +383,6 @@ nsCSPSchemeSrc::toString(nsAString& outStr) const nsCSPHostSrc::nsCSPHostSrc(const nsAString& aHost) : mHost(aHost) - , mAllowHttps(false) { ToLowerCase(mHost); } @@ -425,7 +475,8 @@ permitsPort(const nsAString& aEnforcementScheme, } bool -nsCSPHostSrc::permits(nsIURI* aUri, const nsAString& aNonce, bool aWasRedirected) const +nsCSPHostSrc::permits(nsIURI* aUri, const nsAString& aNonce, bool aWasRedirected, + bool aReportOnly, bool aUpgradeInsecure) const { if (CSPUTILSLOGENABLED()) { nsAutoCString spec; @@ -437,21 +488,8 @@ 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. - nsAutoCString scheme; - nsresult rv = aUri->GetScheme(scheme); - NS_ENSURE_SUCCESS(rv, false); - if (!mScheme.IsEmpty() && - !mScheme.EqualsASCII(scheme.get())) { - - // We should not return false for scheme-less sources where the protected resource - // is http and the load is https, see: - // http://www.w3.org/TR/CSP2/#match-source-expression - bool isHttpsScheme = - (NS_SUCCEEDED(aUri->SchemeIs("https", &isHttpsScheme)) && isHttpsScheme); - - if (!(isHttpsScheme && mAllowHttps)) { - return false; - } + if (!permitsScheme(mScheme, aUri, aReportOnly, aUpgradeInsecure)) { + return false; } // The host in nsCSpHostSrc should never be empty. In case we are enforcing @@ -466,7 +504,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; - rv = aUri->GetHost(uriHost); + nsresult rv = aUri->GetHost(uriHost); NS_ENSURE_SUCCESS(rv, false); // 4.5) host matching: Check if the allowed host starts with a wilcard. @@ -556,11 +594,10 @@ nsCSPHostSrc::toString(nsAString& outStr) const } void -nsCSPHostSrc::setScheme(const nsAString& aScheme, bool aAllowHttps) +nsCSPHostSrc::setScheme(const nsAString& aScheme) { mScheme = aScheme; ToLowerCase(mScheme); - mAllowHttps = aAllowHttps; } void @@ -578,10 +615,11 @@ nsCSPHostSrc::appendPath(const nsAString& aPath) /* ===== nsCSPKeywordSrc ===================== */ nsCSPKeywordSrc::nsCSPKeywordSrc(enum CSPKeyword aKeyword) + : mKeyword(aKeyword) + , mInvalidated(false) { NS_ASSERTION((aKeyword != CSP_SELF), "'self' should have been replaced in the parser"); - mKeyword = aKeyword; } nsCSPKeywordSrc::~nsCSPKeywordSrc() @@ -591,8 +629,16 @@ nsCSPKeywordSrc::~nsCSPKeywordSrc() bool nsCSPKeywordSrc::allows(enum CSPKeyword aKeyword, const nsAString& aHashOrNonce) const { - CSPUTILSLOG(("nsCSPKeywordSrc::allows, aKeyWord: %s, a HashOrNonce: %s", - CSP_EnumToKeyword(aKeyword), NS_ConvertUTF16toUTF8(aHashOrNonce).get())); + CSPUTILSLOG(("nsCSPKeywordSrc::allows, aKeyWord: %s, aHashOrNonce: %s, mInvalidated: %s", + CSP_EnumToKeyword(aKeyword), + NS_ConvertUTF16toUTF8(aHashOrNonce).get(), + mInvalidated ? "yes" : "false")); + // if unsafe-inline should be ignored, then bail early + if (mInvalidated) { + NS_ASSERTION(mKeyword == CSP_UNSAFE_INLINE, + "should only invalidate unsafe-inline within script-src"); + return false; + } return mKeyword == aKeyword; } @@ -602,6 +648,14 @@ nsCSPKeywordSrc::toString(nsAString& outStr) const outStr.AppendASCII(CSP_EnumToKeyword(mKeyword)); } +void +nsCSPKeywordSrc::invalidate() +{ + mInvalidated = true; + NS_ASSERTION(mInvalidated == CSP_UNSAFE_INLINE, + "invalidate 'unsafe-inline' only within script-src"); +} + /* ===== nsCSPNonceSrc ==================== */ nsCSPNonceSrc::nsCSPNonceSrc(const nsAString& aNonce) @@ -614,7 +668,8 @@ nsCSPNonceSrc::~nsCSPNonceSrc() } bool -nsCSPNonceSrc::permits(nsIURI* aUri, const nsAString& aNonce, bool aWasRedirected) const +nsCSPNonceSrc::permits(nsIURI* aUri, const nsAString& aNonce, bool aWasRedirected, + bool aReportOnly, bool aUpgradeInsecure) const { if (CSPUTILSLOGENABLED()) { nsAutoCString spec; @@ -742,7 +797,8 @@ nsCSPDirective::~nsCSPDirective() } bool -nsCSPDirective::permits(nsIURI* aUri, const nsAString& aNonce, bool aWasRedirected) const +nsCSPDirective::permits(nsIURI* aUri, const nsAString& aNonce, bool aWasRedirected, + bool aReportOnly, bool aUpgradeInsecure) const { if (CSPUTILSLOGENABLED()) { nsAutoCString spec; @@ -751,20 +807,13 @@ nsCSPDirective::permits(nsIURI* aUri, const nsAString& aNonce, bool aWasRedirect } for (uint32_t i = 0; i < mSrcs.Length(); i++) { - if (mSrcs[i]->permits(aUri, aNonce, aWasRedirected)) { + if (mSrcs[i]->permits(aUri, aNonce, aWasRedirected, aReportOnly, aUpgradeInsecure)) { return true; } } return false; } -bool -nsCSPDirective::permits(nsIURI* aUri) const -{ - nsString dummyNonce; - return permits(aUri, dummyNonce, false); -} - bool nsCSPDirective::allows(enum CSPKeyword aKeyword, const nsAString& aHashOrNonce) const { @@ -879,6 +928,11 @@ nsCSPDirective::toDomCSPStruct(mozilla::dom::CSP& outCSP) const outCSP.mForm_action.Value() = mozilla::Move(srcs); return; + case nsIContentSecurityPolicy::UPGRADE_IF_INSECURE_DIRECTIVE: + outCSP.mUpgrade_insecure_requests.Construct(); + // does not have any srcs + return; + // REFERRER_DIRECTIVE is handled in nsCSPPolicy::toDomCSPStruct() default: @@ -911,10 +965,29 @@ nsCSPDirective::getReportURIs(nsTArray &outReportURIs) const } } +/* =============== nsUpgradeInsecureDirective ============= */ + +nsUpgradeInsecureDirective::nsUpgradeInsecureDirective(CSPDirective aDirective) +: nsCSPDirective(aDirective) +{ +} + +nsUpgradeInsecureDirective::~nsUpgradeInsecureDirective() +{ +} + +void +nsUpgradeInsecureDirective::toString(nsAString& outStr) const +{ + outStr.AppendASCII(CSP_CSPDirectiveToString( + nsIContentSecurityPolicy::UPGRADE_IF_INSECURE_DIRECTIVE)); +} + /* ===== nsCSPPolicy ========================= */ nsCSPPolicy::nsCSPPolicy() - : mReportOnly(false) + : mUpgradeInsecDir(nullptr) + , mReportOnly(false) { CSPUTILSLOG(("nsCSPPolicy::nsCSPPolicy")); } @@ -960,7 +1033,7 @@ nsCSPPolicy::permits(CSPDirective aDir, // These directive arrays are short (1-5 elements), not worth using a hashtable. for (uint32_t i = 0; i < mDirectives.Length(); i++) { if (mDirectives[i]->equals(aDir)) { - if (!mDirectives[i]->permits(aUri, aNonce, aWasRedirected)) { + if (!mDirectives[i]->permits(aUri, aNonce, aWasRedirected, mReportOnly, mUpgradeInsecDir)) { mDirectives[i]->toString(outViolatedDirective); return false; } @@ -974,7 +1047,7 @@ nsCSPPolicy::permits(CSPDirective aDir, // If the above loop runs through, we haven't found a matching directive. // Avoid relooping, just store the result of default-src while looping. if (!aSpecific && defaultDir) { - if (!defaultDir->permits(aUri, aNonce, aWasRedirected)) { + if (!defaultDir->permits(aUri, aNonce, aWasRedirected, mReportOnly, mUpgradeInsecDir)) { defaultDir->toString(outViolatedDirective); return false; } diff --git a/dom/security/nsCSPUtils.h b/dom/security/nsCSPUtils.h index 1ee0d4a696..8aa0955579 100644 --- a/dom/security/nsCSPUtils.h +++ b/dom/security/nsCSPUtils.h @@ -70,24 +70,25 @@ void CSP_LogMessage(const nsAString& aMessage, // Order of elements below important! Make sure it matches the order as in // nsIContentSecurityPolicy.idl static const char* CSPStrDirectives[] = { - "-error-", // NO_DIRECTIVE - "default-src", // DEFAULT_SRC_DIRECTIVE - "script-src", // SCRIPT_SRC_DIRECTIVE - "object-src", // OBJECT_SRC_DIRECTIVE - "style-src", // STYLE_SRC_DIRECTIVE - "img-src", // IMG_SRC_DIRECTIVE - "media-src", // MEDIA_SRC_DIRECTIVE - "frame-src", // FRAME_SRC_DIRECTIVE - "font-src", // FONT_SRC_DIRECTIVE - "connect-src", // CONNECT_SRC_DIRECTIVE - "report-uri", // REPORT_URI_DIRECTIVE - "frame-ancestors", // FRAME_ANCESTORS_DIRECTIVE - "reflected-xss", // REFLECTED_XSS_DIRECTIVE - "base-uri", // BASE_URI_DIRECTIVE - "form-action", // FORM_ACTION_DIRECTIVE - "referrer", // REFERRER_DIRECTIVE - "manifest-src" // MANIFEST_SRC_DIRECTIVE - "child-src" // CHILD_SRC_DIRECTIVE + "-error-", // NO_DIRECTIVE + "default-src", // DEFAULT_SRC_DIRECTIVE + "script-src", // SCRIPT_SRC_DIRECTIVE + "object-src", // OBJECT_SRC_DIRECTIVE + "style-src", // STYLE_SRC_DIRECTIVE + "img-src", // IMG_SRC_DIRECTIVE + "media-src", // MEDIA_SRC_DIRECTIVE + "frame-src", // FRAME_SRC_DIRECTIVE + "font-src", // FONT_SRC_DIRECTIVE + "connect-src", // CONNECT_SRC_DIRECTIVE + "report-uri", // REPORT_URI_DIRECTIVE + "frame-ancestors", // FRAME_ANCESTORS_DIRECTIVE + "reflected-xss", // REFLECTED_XSS_DIRECTIVE + "base-uri", // BASE_URI_DIRECTIVE + "form-action", // FORM_ACTION_DIRECTIVE + "referrer", // REFERRER_DIRECTIVE + "manifest-src", // MANIFEST_SRC_DIRECTIVE + "upgrade-insecure-requests" // UPGRADE_IF_INSECURE_DIRECTIVE + "child-src" // CHILD_SRC_DIRECTIVE }; inline const char* CSP_CSPDirectiveToString(CSPDirective aDir) @@ -183,7 +184,8 @@ class nsCSPBaseSrc { nsCSPBaseSrc(); virtual ~nsCSPBaseSrc(); - virtual bool permits(nsIURI* aUri, const nsAString& aNonce, bool aWasRedirected) const; + virtual bool permits(nsIURI* aUri, const nsAString& aNonce, bool aWasRedirected, + bool aReportOnly, bool aUpgradeInsecure) const; virtual bool allows(enum CSPKeyword aKeyword, const nsAString& aHashOrNonce) const; virtual void toString(nsAString& outStr) const = 0; }; @@ -195,7 +197,8 @@ class nsCSPSchemeSrc : public nsCSPBaseSrc { explicit nsCSPSchemeSrc(const nsAString& aScheme); virtual ~nsCSPSchemeSrc(); - bool permits(nsIURI* aUri, const nsAString& aNonce, bool aWasRedirected) const; + bool permits(nsIURI* aUri, const nsAString& aNonce, bool aWasRedirected, + bool aReportOnly, bool aUpgradeInsecure) const; void toString(nsAString& outStr) const; private: @@ -209,10 +212,11 @@ class nsCSPHostSrc : public nsCSPBaseSrc { explicit nsCSPHostSrc(const nsAString& aHost); virtual ~nsCSPHostSrc(); - bool permits(nsIURI* aUri, const nsAString& aNonce, bool aWasRedirected) const; + bool permits(nsIURI* aUri, const nsAString& aNonce, bool aWasRedirected, + bool aReportOnly, bool aUpgradeInsecure) const; void toString(nsAString& outStr) const; - void setScheme(const nsAString& aScheme, bool aAllowHttps = false); + void setScheme(const nsAString& aScheme); void setPort(const nsAString& aPort); void appendPath(const nsAString &aPath); @@ -221,7 +225,6 @@ class nsCSPHostSrc : public nsCSPBaseSrc { nsString mHost; nsString mPort; nsString mPath; - bool mAllowHttps; }; /* =============== nsCSPKeywordSrc ============ */ @@ -233,9 +236,12 @@ class nsCSPKeywordSrc : public nsCSPBaseSrc { bool allows(enum CSPKeyword aKeyword, const nsAString& aHashOrNonce) const; void toString(nsAString& outStr) const; + void invalidate(); private: CSPKeyword mKeyword; + // invalidate 'unsafe-inline' if nonce- or hash-source specified + bool mInvalidated; }; /* =============== nsCSPNonceSource =========== */ @@ -245,7 +251,8 @@ class nsCSPNonceSrc : public nsCSPBaseSrc { explicit nsCSPNonceSrc(const nsAString& aNonce); virtual ~nsCSPNonceSrc(); - bool permits(nsIURI* aUri, const nsAString& aNonce, bool aWasRedirected) const; + bool permits(nsIURI* aUri, const nsAString& aNonce, bool aWasRedirected, + bool aReportOnly, bool aUpgradeInsecure) const; bool allows(enum CSPKeyword aKeyword, const nsAString& aHashOrNonce) const; void toString(nsAString& outStr) const; @@ -285,17 +292,16 @@ class nsCSPReportURI : public nsCSPBaseSrc { class nsCSPDirective { public: - nsCSPDirective(); explicit nsCSPDirective(CSPDirective aDirective); virtual ~nsCSPDirective(); - bool permits(nsIURI* aUri, const nsAString& aNonce, bool aWasRedirected) const; - bool permits(nsIURI* aUri) const; - bool allows(enum CSPKeyword aKeyword, const nsAString& aHashOrNonce) const; - void toString(nsAString& outStr) const; + virtual bool permits(nsIURI* aUri, const nsAString& aNonce, bool aWasRedirected, + bool aReportOnly, bool aUpgradeInsecure) const; + virtual bool allows(enum CSPKeyword aKeyword, const nsAString& aHashOrNonce) const; + virtual void toString(nsAString& outStr) const; void toDomCSPStruct(mozilla::dom::CSP& outCSP) const; - inline void addSrcs(const nsTArray& aSrcs) + virtual void addSrcs(const nsTArray& aSrcs) { mSrcs = aSrcs; } bool restrictsContentType(nsContentPolicyType aContentType) const; @@ -313,6 +319,58 @@ class nsCSPDirective { nsTArray mSrcs; }; +/* =============== nsUpgradeInsecureDirective === */ + +/* + * Upgrading insecure requests includes the following actors: + * (1) CSP: + * The CSP implementation whitelists the http-request + * in case the policy is executed in enforcement mode. + * The CSP implementation however does not allow http + * requests to succeed if executed in report-only mode. + * In such a case the CSP implementation reports the + * error back to the page. + * + * (2) MixedContent: + * The evalution of MixedContent whitelists all http + * requests with the promise that the http requests + * gets upgraded to https before any data is fetched + * from the network. + * + * (3) CORS: + * Does not consider the http request to be of a + * different origin in case the scheme is the only + * difference in otherwise matching URIs. + * + * (4) nsHttpChannel: + * Before connecting, the channel gets redirected + * to use https. + * + * (5) WebSocketChannel: + * Similar to the httpChannel, the websocketchannel + * gets upgraded from ws to wss. + */ +class nsUpgradeInsecureDirective : public nsCSPDirective { + public: + explicit nsUpgradeInsecureDirective(CSPDirective aDirective); + ~nsUpgradeInsecureDirective(); + + bool permits(nsIURI* aUri, const nsAString& aNonce, bool aWasRedirected, + bool aReportOnly, bool aUpgradeInsecure) const + { return false; } + + bool permits(nsIURI* aUri) const + { return false; } + + bool allows(enum CSPKeyword aKeyword, const nsAString& aHashOrNonce) const + { return false; } + + void toString(nsAString& outStr) const; + + void addSrcs(const nsTArray& aSrcs) + { MOZ_ASSERT(false, "upgrade-insecure-requests does not hold any srcs"); } +}; + /* =============== nsCSPPolicy ================== */ class nsCSPPolicy { @@ -340,6 +398,12 @@ class nsCSPPolicy { inline void addDirective(nsCSPDirective* aDir) { mDirectives.AppendElement(aDir); } + inline void addUpgradeInsecDir(nsUpgradeInsecureDirective* aDir) + { + mUpgradeInsecDir = aDir; + addDirective(aDir); + } + bool hasDirective(CSPDirective aDir) const; inline void setReportOnlyFlag(bool aFlag) @@ -365,9 +429,10 @@ class nsCSPPolicy { { return mDirectives.Length(); } private: - nsTArray mDirectives; - bool mReportOnly; - nsString mReferrerPolicy; + nsUpgradeInsecureDirective* mUpgradeInsecDir; + nsTArray mDirectives; + bool mReportOnly; + nsString mReferrerPolicy; }; #endif /* nsCSPUtils_h___ */ diff --git a/dom/security/nsMixedContentBlocker.cpp b/dom/security/nsMixedContentBlocker.cpp index 9b886c0ea3..0c32dff3a6 100644 --- a/dom/security/nsMixedContentBlocker.cpp +++ b/dom/security/nsMixedContentBlocker.cpp @@ -7,6 +7,7 @@ #include "nsMixedContentBlocker.h" #include "nsContentPolicyUtils.h" +#include "nsCSPContext.h" #include "nsThreadUtils.h" #include "nsINode.h" #include "nsCOMPtr.h" @@ -594,6 +595,26 @@ nsMixedContentBlocker::ShouldLoad(bool aHadInsecureImageRedirect, // Determine if the rootDoc is https and if the user decided to allow Mixed Content nsCOMPtr docShell = NS_CP_GetDocShellFromContext(aRequestingContext); NS_ENSURE_TRUE(docShell, NS_OK); + + // The page might have set the CSP directive 'upgrade-insecure-requests'. In such + // a case allow the http: load to succeed with the promise that the channel will + // get upgraded to https before fetching any data from the netwerk. + // Please see: nsHttpChannel::Connect() + // + // Please note that the CSP directive 'upgrade-insecure-requests' only applies to + // http: and ws: (for websockets). Websockets are not subject to mixed content + // blocking since insecure websockets are not allowed within secure pages. Hence, + // we only have to check against http: here. Skip mixed content blocking if the + // subresource load uses http: and the CSP directive 'upgrade-insecure-requests' + // is present on the page. + bool isHttpScheme = false; + rv = aContentLocation->SchemeIs("http", &isHttpScheme); + NS_ENSURE_SUCCESS(rv, rv); + if (isHttpScheme && docShell->GetDocument()->GetUpgradeInsecureRequests()) { + *aDecision = ACCEPT; + return NS_OK; + } + bool rootHasSecureConnection = false; bool allowMixedContent = false; bool isRootDocShell = false; @@ -603,7 +624,6 @@ nsMixedContentBlocker::ShouldLoad(bool aHadInsecureImageRedirect, return rv; } - // Get the sameTypeRoot tree item from the docshell nsCOMPtr sameTypeRoot; docShell->GetSameTypeRootTreeItem(getter_AddRefs(sameTypeRoot)); diff --git a/dom/security/test/TestCSPParser.cpp b/dom/security/test/TestCSPParser.cpp index 40da352444..b086da240e 100644 --- a/dom/security/test/TestCSPParser.cpp +++ b/dom/security/test/TestCSPParser.cpp @@ -211,7 +211,9 @@ nsresult TestDirectives() { { "script-src 'nonce-correctscriptnonce'", "script-src 'nonce-correctscriptnonce'" }, { "script-src 'sha256-siVR8vAcqP06h2ppeNwqgjr0yZ6yned4X2VF84j4GmI='", - "script-src 'sha256-siVR8vAcqP06h2ppeNwqgjr0yZ6yned4X2VF84j4GmI='" } + "script-src 'sha256-siVR8vAcqP06h2ppeNwqgjr0yZ6yned4X2VF84j4GmI='" }, + { "referrer no-referrer", + "referrer no-referrer" } }; uint32_t policyCount = sizeof(policies) / sizeof(PolicyTest); @@ -273,7 +275,11 @@ nsresult TestIgnoreUpperLowerCasePolicies() { { "script-src 'NoncE-NONCENEEDSTOBEUPPERCASE'", "script-src 'nonce-NONCENEEDSTOBEUPPERCASE'" }, { "script-src 'SHA256-siVR8vAcqP06h2ppeNwqgjr0yZ6yned4X2VF84j4GmI='", - "script-src 'sha256-siVR8vAcqP06h2ppeNwqgjr0yZ6yned4X2VF84j4GmI='" } + "script-src 'sha256-siVR8vAcqP06h2ppeNwqgjr0yZ6yned4X2VF84j4GmI='" }, + { "refERRer No-refeRRer", + "referrer No-refeRRer" }, + { "upgrade-INSECURE-requests", + "upgrade-insecure-requests" } }; uint32_t policyCount = sizeof(policies) / sizeof(PolicyTest); @@ -443,7 +449,11 @@ nsresult TestSimplePolicies() { { "default-src -; ", "default-src http://-" }, { "script-src 1", - "script-src http://1" } + "script-src http://1" }, + { "upgrade-insecure-requests", + "upgrade-insecure-requests" }, + { "upgrade-insecure-requests https:", + "upgrade-insecure-requests" } }; uint32_t policyCount = sizeof(policies) / sizeof(PolicyTest); diff --git a/dom/security/test/csp/file_upgrade_insecure.html b/dom/security/test/csp/file_upgrade_insecure.html new file mode 100644 index 0000000000..d104a3a240 --- /dev/null +++ b/dom/security/test/csp/file_upgrade_insecure.html @@ -0,0 +1,78 @@ + + + + + Bug 1139297 - Implement CSP upgrade-insecure-requests directive + + + + + + + + + + + + + + + + + + + + + + + + +
foo
+ + + + + + + + + + + + +
+ + +
+ + + + diff --git a/dom/security/test/csp/file_upgrade_insecure_cors.html b/dom/security/test/csp/file_upgrade_insecure_cors.html new file mode 100644 index 0000000000..e675c62e9f --- /dev/null +++ b/dom/security/test/csp/file_upgrade_insecure_cors.html @@ -0,0 +1,49 @@ + + + + + Bug 1139297 - Implement CSP upgrade-insecure-requests directive + + + + + + + diff --git a/dom/security/test/csp/file_upgrade_insecure_cors_server.sjs b/dom/security/test/csp/file_upgrade_insecure_cors_server.sjs new file mode 100644 index 0000000000..33f6c3b234 --- /dev/null +++ b/dom/security/test/csp/file_upgrade_insecure_cors_server.sjs @@ -0,0 +1,62 @@ +// Custom *.sjs file specifically for the needs of Bug: +// Bug 1139297 - Implement CSP upgrade-insecure-requests directive + +function handleRequest(request, response) +{ + // avoid confusing cache behaviors + response.setHeader("Cache-Control", "no-cache", false); + + // perform sanity check and make sure that all requests get upgraded to use https + if (request.scheme !== "https") { + response.write("request not https"); + return; + } + + var queryString = request.queryString; + + // TEST 1 + if (queryString === "test1") { + var newLocation = + "http://test1.example.com/tests/dom/security/test/csp/file_upgrade_insecure_cors_server.sjs?redir-test1"; + response.setStatusLine("1.1", 302, "Found"); + response.setHeader("Location", newLocation, false); + return; + } + if (queryString === "redir-test1") { + response.write("test1-no-cors-ok"); + return; + } + + // TEST 2 + if (queryString === "test2") { + var newLocation = + "http://test1.example.com:443/tests/dom/security/test/csp/file_upgrade_insecure_cors_server.sjs?redir-test2"; + response.setStatusLine("1.1", 302, "Found"); + response.setHeader("Location", newLocation, false); + return; + } + if (queryString === "redir-test2") { + response.write("test2-no-cors-diffport-ok"); + return; + } + + // TEST 3 + response.setHeader("Access-Control-Allow-Headers", "content-type", false); + response.setHeader("Access-Control-Allow-Methods", "POST, GET", false); + response.setHeader("Access-Control-Allow-Origin", "*", false); + + if (queryString === "test3") { + var newLocation = + "http://test1.example.com/tests/dom/security/test/csp/file_upgrade_insecure_cors_server.sjs?redir-test3"; + response.setStatusLine("1.1", 302, "Found"); + response.setHeader("Location", newLocation, false); + return; + } + if (queryString === "redir-test3") { + response.write("test3-cors-ok"); + return; + } + + // we should not get here, but just in case return something unexpected + response.write("d'oh"); +} diff --git a/dom/security/test/csp/file_upgrade_insecure_referrer.html b/dom/security/test/csp/file_upgrade_insecure_referrer.html new file mode 100644 index 0000000000..83741821bb --- /dev/null +++ b/dom/security/test/csp/file_upgrade_insecure_referrer.html @@ -0,0 +1,13 @@ + + + + + Bug 1139297 - Implement CSP upgrade-insecure-requests directive + + + + + + + + diff --git a/dom/security/test/csp/file_upgrade_insecure_referrer_server.sjs b/dom/security/test/csp/file_upgrade_insecure_referrer_server.sjs new file mode 100644 index 0000000000..be1e6da0ca --- /dev/null +++ b/dom/security/test/csp/file_upgrade_insecure_referrer_server.sjs @@ -0,0 +1,56 @@ +// Custom *.sjs file specifically for the needs of Bug: +// Bug 1139297 - Implement CSP upgrade-insecure-requests directive + +// small red image +const IMG_BYTES = atob( + "iVBORw0KGgoAAAANSUhEUgAAAAUAAAAFCAYAAACNbyblAAAAHElEQVQI12" + + "P4//8/w38GIAXDIBKE0DHxgljNBAAO9TXL0Y4OHwAAAABJRU5ErkJggg=="); + +function handleRequest(request, response) +{ + // avoid confusing cache behaviors + response.setHeader("Cache-Control", "no-cache", false); + var queryString = request.queryString; + + // (1) lets process the queryresult request async and + // wait till we have received the image request. + if (queryString == "queryresult") { + response.processAsync(); + setObjectState("queryResult", response); + return; + } + + // (2) Handle the image request and return the referrer + // result back to the stored queryresult request. + if (request.queryString == "img") { + response.setHeader("Content-Type", "image/png"); + response.write(IMG_BYTES); + + let referrer = ""; + try { + referrer = request.getHeader("referer"); + } catch (e) { + referrer = ""; + } + // make sure the received image request was upgraded to https, + // otherwise we return not only the referrer but also indicate + // that the request was not upgraded to https. Note, that + // all upgrades happen in the browser before any non-secure + // request hits the wire. + referrer += (request.scheme == "https") ? + "" : " but request is not https"; + + getObjectState("queryResult", function(queryResponse) { + if (!queryResponse) { + return; + } + queryResponse.write(referrer); + queryResponse.finish(); + }); + return; + } + + // we should not get here ever, but just in case return + // something unexpected. + response.write("doh!"); +} diff --git a/dom/security/test/csp/file_upgrade_insecure_reporting.html b/dom/security/test/csp/file_upgrade_insecure_reporting.html new file mode 100644 index 0000000000..c78e9a784d --- /dev/null +++ b/dom/security/test/csp/file_upgrade_insecure_reporting.html @@ -0,0 +1,23 @@ + + + + + Bug 1139297 - Implement CSP upgrade-insecure-requests directive + + + + + + + + + + diff --git a/dom/security/test/csp/file_upgrade_insecure_reporting_server.sjs b/dom/security/test/csp/file_upgrade_insecure_reporting_server.sjs new file mode 100644 index 0000000000..b9940a7fd6 --- /dev/null +++ b/dom/security/test/csp/file_upgrade_insecure_reporting_server.sjs @@ -0,0 +1,80 @@ +// Custom *.sjs specifically for the needs of Bug +// Bug 1139297 - Implement CSP upgrade-insecure-requests directive + +Components.utils.import("resource://gre/modules/NetUtil.jsm"); + +// small red image +const IMG_BYTES = atob( + "iVBORw0KGgoAAAANSUhEUgAAAAUAAAAFCAYAAACNbyblAAAAHElEQVQI12" + + "P4//8/w38GIAXDIBKE0DHxgljNBAAO9TXL0Y4OHwAAAABJRU5ErkJggg=="); + +const REPORT_URI = "https://example.com/tests/dom/security/test/csp/file_upgrade_insecure_reporting_server.sjs?report"; +const POLICY = "upgrade-insecure-requests; default-src https: 'unsafe-inline'"; +const POLICY_RO = "default-src https: 'unsafe-inline'; report-uri " + REPORT_URI; + +function loadHTMLFromFile(path) { + // Load the HTML to return in the response from file. + // Since it's relative to the cwd of the test runner, we start there and + // append to get to the actual path of the file. + var testHTMLFile = + Components.classes["@mozilla.org/file/directory_service;1"]. + getService(Components.interfaces.nsIProperties). + get("CurWorkD", Components.interfaces.nsILocalFile); + var dirs = path.split("/"); + for (var i = 0; i < dirs.length; i++) { + testHTMLFile.append(dirs[i]); + } + var testHTMLFileStream = + Components.classes["@mozilla.org/network/file-input-stream;1"]. + createInstance(Components.interfaces.nsIFileInputStream); + testHTMLFileStream.init(testHTMLFile, -1, 0, 0); + var testHTML = NetUtil.readInputStreamToString(testHTMLFileStream, testHTMLFileStream.available()); + return testHTML; +} + + +function handleRequest(request, response) +{ + // avoid confusing cache behaviors + response.setHeader("Cache-Control", "no-cache", false); + + // (1) Store the query that will report back whether the violation report was received + if (request.queryString == "queryresult") { + response.processAsync(); + setObjectState("queryResult", response); + return; + } + + // (2) We load a page using a CSP and a report only CSP + if (request.queryString == "toplevel") { + response.setHeader("Content-Security-Policy", POLICY, false); + response.setHeader("Content-Security-Policy-Report-Only", POLICY_RO, false); + response.setHeader("Content-Type", "text/html", false); + response.write(loadHTMLFromFile("tests/dom/security/test/csp/file_upgrade_insecure_reporting.html")); + return; + } + + // (3) Return the image back to the client + if (request.queryString == "img") { + response.setHeader("Content-Type", "image/png"); + response.write(IMG_BYTES); + return; + } + + // (4) Finally we receive the report, let's return the request from (1) + // signaling that we received the report correctly + if (request.queryString == "report") { + getObjectState("queryResult", function(queryResponse) { + if (!queryResponse) { + return; + } + queryResponse.write("report-ok"); + queryResponse.finish(); + }); + return; + } + + // we should never get here, but just in case ... + response.setHeader("Content-Type", "text/plain"); + response.write("doh!"); +} diff --git a/dom/security/test/csp/file_upgrade_insecure_server.sjs b/dom/security/test/csp/file_upgrade_insecure_server.sjs new file mode 100644 index 0000000000..e27b97830d --- /dev/null +++ b/dom/security/test/csp/file_upgrade_insecure_server.sjs @@ -0,0 +1,102 @@ +// Custom *.sjs file specifically for the needs of Bug: +// Bug 1139297 - Implement CSP upgrade-insecure-requests directive + +const TOTAL_EXPECTED_REQUESTS = 11; + +const IFRAME_CONTENT = + "" + + "" + + "" + + "Bug 1139297 - Implement CSP upgrade-insecure-requests directive" + + "" + + "" + + "" + + "" + + ""; + +const expectedQueries = [ "script", "style", "img", "iframe", "form", "xhr", + "media", "object", "font", "img-redir", "nested-img"]; + +function handleRequest(request, response) +{ + // avoid confusing cache behaviors + response.setHeader("Cache-Control", "no-cache", false); + var queryString = request.queryString; + + // initialize server variables and save the object state + // of the initial request, which returns async once the + // server has processed all requests. + if (queryString == "queryresult") { + setState("totaltests", TOTAL_EXPECTED_REQUESTS.toString()); + setState("receivedQueries", ""); + response.processAsync(); + setObjectState("queryResult", response); + return; + } + + // handle img redirect (https->http) + if (queryString == "redirect-image") { + var newLocation = + "http://example.com/tests/dom/security/test/csp/file_upgrade_insecure_server.sjs?img-redir"; + response.setStatusLine("1.1", 302, "Found"); + response.setHeader("Location", newLocation, false); + return; + } + + // just in case error handling for unexpected queries + if (expectedQueries.indexOf(queryString) == -1) { + response.write("doh!"); + return; + } + + // make sure all the requested queries are indeed https + queryString += (request.scheme == "https") ? "-ok" : "-error"; + + var receivedQueries = getState("receivedQueries"); + + // images, scripts, etc. get queried twice, do not + // confuse the server by storing the preload as + // well as the actual load. If either the preload + // or the actual load is not https, then we would + // append "-error" in the array and the test would + // fail at the end. + if (receivedQueries.includes(queryString)) { + return; + } + + // append the result to the total query string array + if (receivedQueries != "") { + receivedQueries += ","; + } + receivedQueries += queryString; + setState("receivedQueries", receivedQueries); + + // keep track of how many more requests the server + // is expecting + var totaltests = parseInt(getState("totaltests")); + totaltests -= 1; + setState("totaltests", totaltests.toString()); + + // return content (img) for the nested iframe to test + // that subresource requests within nested contexts + // get upgraded as well. We also have to return + // the iframe context in case of an error so we + // can test both, using upgrade-insecure as well + // as the base case of not using upgrade-insecure. + if ((queryString == "iframe-ok") || (queryString == "iframe-error")) { + response.write(IFRAME_CONTENT); + } + + // if we have received all the requests, we return + // the result back. + if (totaltests == 0) { + getObjectState("queryResult", function(queryResponse) { + if (!queryResponse) { + return; + } + var receivedQueries = getState("receivedQueries"); + queryResponse.write(receivedQueries); + queryResponse.finish(); + }); + } +} diff --git a/dom/security/test/csp/file_upgrade_insecure_wsh.py b/dom/security/test/csp/file_upgrade_insecure_wsh.py new file mode 100644 index 0000000000..0ae161bff1 --- /dev/null +++ b/dom/security/test/csp/file_upgrade_insecure_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/mochitest.ini b/dom/security/test/csp/mochitest.ini index efd3cd9197..53ea996acd 100644 --- a/dom/security/test/csp/mochitest.ini +++ b/dom/security/test/csp/mochitest.ini @@ -119,6 +119,15 @@ support-files = file_worker_redirect.sjs file_referrerdirective.html referrerdirective.sjs + file_upgrade_insecure.html + file_upgrade_insecure_server.sjs + file_upgrade_insecure_wsh.py + file_upgrade_insecure_reporting.html + file_upgrade_insecure_reporting_server.sjs + file_upgrade_insecure_referrer.html + file_upgrade_insecure_referrer_server.sjs + file_upgrade_insecure_cors.html + file_upgrade_insecure_cors_server.sjs [test_base-uri.html] [test_blob_data_schemes.html] @@ -173,3 +182,12 @@ skip-if = buildapp == 'b2g' # intermittent orange (bug 1028490) [test_referrerdirective.html] skip-if = buildapp == 'b2g' #no ssl support [test_worker_redirect.html] +[test_upgrade_insecure.html] +# no ssl support as well as websocket tests do not work (see test_websocket.html) +skip-if = buildapp == 'b2g' || buildapp == 'mulet' || toolkit == 'gonk' || toolkit == 'android' +[test_upgrade_insecure_reporting.html] +skip-if = buildapp == 'b2g' || buildapp == 'mulet' || toolkit == 'gonk' || toolkit == 'android' +[test_upgrade_insecure_referrer.html] +skip-if = buildapp == 'b2g' || buildapp == 'mulet' || toolkit == 'gonk' || toolkit == 'android' +[test_upgrade_insecure_cors.html] +skip-if = buildapp == 'b2g' || buildapp == 'mulet' || toolkit == 'gonk' || toolkit == 'android' diff --git a/dom/security/test/csp/test_upgrade_insecure.html b/dom/security/test/csp/test_upgrade_insecure.html new file mode 100644 index 0000000000..c6a62172c7 --- /dev/null +++ b/dom/security/test/csp/test_upgrade_insecure.html @@ -0,0 +1,159 @@ + + + + + Bug 1139297 - Implement CSP upgrade-insecure-requests directive + + + + + + + + + + diff --git a/dom/security/test/csp/test_upgrade_insecure_cors.html b/dom/security/test/csp/test_upgrade_insecure_cors.html new file mode 100644 index 0000000000..af296983c4 --- /dev/null +++ b/dom/security/test/csp/test_upgrade_insecure_cors.html @@ -0,0 +1,86 @@ + + + + + Bug 1139297 - Implement CSP upgrade-insecure-requests directive + + + + + + + + + + diff --git a/dom/security/test/csp/test_upgrade_insecure_referrer.html b/dom/security/test/csp/test_upgrade_insecure_referrer.html new file mode 100644 index 0000000000..f71be50ea4 --- /dev/null +++ b/dom/security/test/csp/test_upgrade_insecure_referrer.html @@ -0,0 +1,79 @@ + + + + + Bug 1139297 - Implement CSP upgrade-insecure-requests directive + + + + + + + + + + diff --git a/dom/security/test/csp/test_upgrade_insecure_reporting.html b/dom/security/test/csp/test_upgrade_insecure_reporting.html new file mode 100644 index 0000000000..9676541796 --- /dev/null +++ b/dom/security/test/csp/test_upgrade_insecure_reporting.html @@ -0,0 +1,69 @@ + + + + + Bug 1139297 - Implement CSP upgrade-insecure-requests directive + + + + + + + + + + diff --git a/dom/webidl/CSPDictionaries.webidl b/dom/webidl/CSPDictionaries.webidl index ae0f02363b..518ea2faf9 100644 --- a/dom/webidl/CSPDictionaries.webidl +++ b/dom/webidl/CSPDictionaries.webidl @@ -25,6 +25,7 @@ dictionary CSP { sequence form-action; sequence referrer; sequence manifest-src; + sequence upgrade-insecure-requests; }; dictionary CSPPolicies { diff --git a/gfx/thebes/gfxPrefs.h b/gfx/thebes/gfxPrefs.h index 60fc186403..8063738313 100644 --- a/gfx/thebes/gfxPrefs.h +++ b/gfx/thebes/gfxPrefs.h @@ -184,6 +184,7 @@ private: DECL_GFX_PREF(Live, "apz.y_stationary_size_multiplier", APZYStationarySizeMultiplier, float, 3.5f); DECL_GFX_PREF(Live, "apz.zoom_animation_duration_ms", APZZoomAnimationDuration, int32_t, 250); + DECL_GFX_PREF(Live, "dom.meta-viewport.enabled", MetaViewportEnabled, bool, false); DECL_GFX_PREF(Once, "dom.vr.enabled", VREnabled, bool, false); DECL_GFX_PREF(Once, "dom.vr.add-test-devices", VRAddTestDevices, int32_t, 1); DECL_GFX_PREF(Live, "dom.w3c_pointer_events.enabled", PointerEventsEnabled, bool, false); diff --git a/ipc/glue/BackgroundUtils.cpp b/ipc/glue/BackgroundUtils.cpp index 79b8c603c7..c546d09b2f 100644 --- a/ipc/glue/BackgroundUtils.cpp +++ b/ipc/glue/BackgroundUtils.cpp @@ -233,6 +233,7 @@ LoadInfoToLoadInfoArgs(nsILoadInfo *aLoadInfo, NS_ENSURE_SUCCESS(rv, rv); aLoadInfoArgs->securityFlags() = aLoadInfo->GetSecurityFlags(); aLoadInfoArgs->contentPolicyType() = aLoadInfo->InternalContentPolicyType(); + aLoadInfoArgs->upgradeInsecureRequests() = aLoadInfo->GetUpgradeInsecureRequests(); aLoadInfoArgs->innerWindowID() = aLoadInfo->GetInnerWindowID(); aLoadInfoArgs->outerWindowID() = aLoadInfo->GetOuterWindowID(); aLoadInfoArgs->parentOuterWindowID() = aLoadInfo->GetParentOuterWindowID(); @@ -248,6 +249,7 @@ LoadInfoToLoadInfoArgs(nsILoadInfo *aLoadInfo, NS_ENSURE_SUCCESS(rv, rv); aLoadInfoArgs->securityFlags() = nsILoadInfo::SEC_NORMAL; aLoadInfoArgs->contentPolicyType() = nsIContentPolicy::TYPE_OTHER; + aLoadInfoArgs->upgradeInsecureRequests() = false; aLoadInfoArgs->innerWindowID() = 0; aLoadInfoArgs->outerWindowID() = 0; aLoadInfoArgs->parentOuterWindowID() = 0; @@ -271,6 +273,7 @@ LoadInfoArgsToLoadInfo(const mozilla::net::LoadInfoArgs& aLoadInfoArgs, triggeringPrincipal, aLoadInfoArgs.securityFlags(), aLoadInfoArgs.contentPolicyType(), + aLoadInfoArgs.upgradeInsecureRequests(), aLoadInfoArgs.innerWindowID(), aLoadInfoArgs.outerWindowID(), aLoadInfoArgs.parentOuterWindowID()); diff --git a/layout/base/nsRefreshDriver.cpp b/layout/base/nsRefreshDriver.cpp index 1ef6b69778..f6f71bfddc 100644 --- a/layout/base/nsRefreshDriver.cpp +++ b/layout/base/nsRefreshDriver.cpp @@ -136,9 +136,9 @@ public: { MOZ_ASSERT(NS_IsMainThread()); - for (size_t i = 0; i < mRefreshDrivers.Length(); i++) { - aNewTimer->AddRefreshDriver(mRefreshDrivers[i]); - mRefreshDrivers[i]->mActiveTimer = aNewTimer; + for (nsRefreshDriver* driver : mRefreshDrivers) { + aNewTimer->AddRefreshDriver(driver); + driver->mActiveTimer = aNewTimer; } mRefreshDrivers.Clear(); @@ -176,13 +176,13 @@ protected: nsTArray > drivers(mRefreshDrivers); // RD is short for RefreshDriver profiler_tracing("Paint", "RD", TRACING_INTERVAL_START); - for (size_t i = 0; i < drivers.Length(); ++i) { + for (nsRefreshDriver* driver : drivers) { // don't poke this driver if it's in test mode - if (drivers[i]->IsTestControllingRefreshesEnabled()) { + if (driver->IsTestControllingRefreshesEnabled()) { continue; } - TickDriver(drivers[i], jsnow, now); + TickDriver(driver, jsnow, now); } profiler_tracing("Paint", "RD", TRACING_INTERVAL_END); LOG("[%p] done.", this); @@ -1044,8 +1044,8 @@ nsRefreshDriver::~nsRefreshDriver() mRootRefresh->RemoveRefreshObserver(this, Flush_Style); mRootRefresh = nullptr; } - for (uint32_t i = 0; i < mPresShellsToInvalidateIfHidden.Length(); i++) { - mPresShellsToInvalidateIfHidden[i]->InvalidatePresShellIfHidden(); + for (nsIPresShell* shell : mPresShellsToInvalidateIfHidden) { + shell->InvalidatePresShellIfHidden(); } mPresShellsToInvalidateIfHidden.Clear(); @@ -1526,34 +1526,34 @@ nsRefreshDriver::RunFrameRequestCallbacks(int64_t aNowEpoch, TimeStamp aNowTime) // Reset mFrameRequestCallbackDocs so they can be readded as needed. mFrameRequestCallbackDocs.Clear(); - profiler_tracing("Paint", "Scripts", TRACING_INTERVAL_START); - int64_t eventTime = aNowEpoch / PR_USEC_PER_MSEC; - for (uint32_t i = 0; i < frameRequestCallbacks.Length(); ++i) { - const DocumentFrameCallbacks& docCallbacks = frameRequestCallbacks[i]; - // XXXbz Bug 863140: GetInnerWindow can return the outer - // window in some cases. - nsPIDOMWindow* innerWindow = docCallbacks.mDocument->GetInnerWindow(); - DOMHighResTimeStamp timeStamp = 0; - if (innerWindow && innerWindow->IsInnerWindow()) { - nsPerformance* perf = innerWindow->GetPerformance(); - if (perf) { - timeStamp = perf->GetDOMTiming()->TimeStampToDOMHighRes(aNowTime); + if (!frameRequestCallbacks.IsEmpty()) { + profiler_tracing("Paint", "Scripts", TRACING_INTERVAL_START); + int64_t eventTime = aNowEpoch / PR_USEC_PER_MSEC; + for (const DocumentFrameCallbacks& docCallbacks : frameRequestCallbacks) { + // XXXbz Bug 863140: GetInnerWindow can return the outer + // window in some cases. + nsPIDOMWindow* innerWindow = docCallbacks.mDocument->GetInnerWindow(); + DOMHighResTimeStamp timeStamp = 0; + if (innerWindow && innerWindow->IsInnerWindow()) { + nsPerformance* perf = innerWindow->GetPerformance(); + if (perf) { + timeStamp = perf->GetDOMTiming()->TimeStampToDOMHighRes(aNowTime); + } + // else window is partially torn down already } - // else window is partially torn down already - } - for (uint32_t j = 0; j < docCallbacks.mCallbacks.Length(); ++j) { - const nsIDocument::FrameRequestCallbackHolder& holder = - docCallbacks.mCallbacks[j]; - nsAutoMicroTask mt; - if (holder.HasWebIDLCallback()) { - ErrorResult ignored; - holder.GetWebIDLCallback()->Call(timeStamp, ignored); - } else { - holder.GetXPCOMCallback()->Sample(eventTime); + for (const nsIDocument::FrameRequestCallbackHolder& holder : + docCallbacks.mCallbacks) { + nsAutoMicroTask mt; + if (holder.HasWebIDLCallback()) { + ErrorResult ignored; + holder.GetWebIDLCallback()->Call(timeStamp, ignored); + } else { + holder.GetXPCOMCallback()->Sample(eventTime); + } } } + profiler_tracing("Paint", "Scripts", TRACING_INTERVAL_END); } - profiler_tracing("Paint", "Scripts", TRACING_INTERVAL_END); } void @@ -1755,19 +1755,18 @@ nsRefreshDriver::Tick(int64_t aNowEpoch, TimeStamp aNowTime) } } - for (uint32_t i = 0; i < mPresShellsToInvalidateIfHidden.Length(); i++) { - mPresShellsToInvalidateIfHidden[i]->InvalidatePresShellIfHidden(); + for (nsIPresShell* shell : mPresShellsToInvalidateIfHidden) { + shell->InvalidatePresShellIfHidden(); } mPresShellsToInvalidateIfHidden.Clear(); if (mViewManagerFlushIsPending) { nsTArray profilingDocShells; GetProfileTimelineSubDocShells(GetDocShell(mPresContext), profilingDocShells); - for (uint32_t i = 0; i < profilingDocShells.Length(); i ++) { + for (nsDocShell* docShell : profilingDocShells) { // For the sake of the profile timeline's simplicity, this is flagged as // paint even if it includes creating display lists - profilingDocShells[i]->AddProfileTimelineMarker("Paint", - TRACING_INTERVAL_START); + docShell->AddProfileTimelineMarker("Paint", TRACING_INTERVAL_START); } profiler_tracing("Paint", "DisplayList", TRACING_INTERVAL_START); #ifdef MOZ_DUMP_PAINTING @@ -1784,9 +1783,8 @@ nsRefreshDriver::Tick(int64_t aNowEpoch, TimeStamp aNowTime) printf_stderr("Ending ProcessPendingUpdates\n"); } #endif - for (uint32_t i = 0; i < profilingDocShells.Length(); i ++) { - profilingDocShells[i]->AddProfileTimelineMarker("Paint", - TRACING_INTERVAL_END); + for (nsDocShell* docShell : profilingDocShells) { + docShell->AddProfileTimelineMarker("Paint", TRACING_INTERVAL_END); } profiler_tracing("Paint", "DisplayList", TRACING_INTERVAL_END); @@ -1800,8 +1798,10 @@ nsRefreshDriver::Tick(int64_t aNowEpoch, TimeStamp aNowTime) mozilla::Telemetry::AccumulateTimeDelta(mozilla::Telemetry::REFRESH_DRIVER_TICK, mTickStart); #endif - for (uint32_t i = 0; i < mPostRefreshObservers.Length(); ++i) { - mPostRefreshObservers[i]->DidRefresh(); + nsTObserverArray::ForwardIterator iter(mPostRefreshObservers); + while (iter.HasMore()) { + nsAPostRefreshObserver* observer = iter.GetNext(); + observer->DidRefresh(); } // Check if we should exit high precision timer mode. diff --git a/layout/base/nsRefreshDriver.h b/layout/base/nsRefreshDriver.h index f2f73d7fd1..a325898947 100644 --- a/layout/base/nsRefreshDriver.h +++ b/layout/base/nsRefreshDriver.h @@ -17,6 +17,7 @@ #include "nsTObserverArray.h" #include "nsTArray.h" #include "nsTHashtable.h" +#include "nsTObserverArray.h" #include "nsClassHashtable.h" #include "nsHashKeys.h" #include "mozilla/Attributes.h" @@ -398,7 +399,7 @@ private: // nsTArray on purpose, because we want to be able to swap. nsTArray mFrameRequestCallbackDocs; nsTArray mThrottledFrameRequestCallbackDocs; - nsTArray mPostRefreshObservers; + nsTObserverArray mPostRefreshObservers; // Helper struct for processing image requests struct ImageRequestParameters { diff --git a/netwerk/base/LoadInfo.cpp b/netwerk/base/LoadInfo.cpp index 0b0c5dc6ba..14053148ec 100644 --- a/netwerk/base/LoadInfo.cpp +++ b/netwerk/base/LoadInfo.cpp @@ -14,6 +14,7 @@ #include "nsIFrameLoader.h" #include "nsISupportsImpl.h" #include "nsISupportsUtils.h" +#include "nsContentUtils.h" namespace mozilla { @@ -31,6 +32,7 @@ LoadInfo::LoadInfo(nsIPrincipal* aLoadingPrincipal, , mSecurityFlags(aSecurityFlags) , mInternalContentPolicyType(aContentPolicyType) , mBaseURI(aBaseURI) + , mUpgradeInsecureRequests(false) , mInnerWindowID(0) , mOuterWindowID(0) , mParentOuterWindowID(0) @@ -74,6 +76,8 @@ LoadInfo::LoadInfo(nsIPrincipal* aLoadingPrincipal, nsCOMPtr parent = outerWindow->GetScriptableParent(); mParentOuterWindowID = parent->WindowID(); } + + mUpgradeInsecureRequests = aLoadingContext->OwnerDoc()->GetUpgradeInsecureRequests(); } } @@ -81,6 +85,7 @@ LoadInfo::LoadInfo(nsIPrincipal* aLoadingPrincipal, nsIPrincipal* aTriggeringPrincipal, nsSecurityFlags aSecurityFlags, nsContentPolicyType aContentPolicyType, + bool aUpgradeInsecureRequests, uint64_t aInnerWindowID, uint64_t aOuterWindowID, uint64_t aParentOuterWindowID) @@ -88,6 +93,7 @@ LoadInfo::LoadInfo(nsIPrincipal* aLoadingPrincipal, , mTriggeringPrincipal(aTriggeringPrincipal) , mSecurityFlags(aSecurityFlags) , mInternalContentPolicyType(aContentPolicyType) + , mUpgradeInsecureRequests(aUpgradeInsecureRequests) , mInnerWindowID(aInnerWindowID) , mOuterWindowID(aOuterWindowID) , mParentOuterWindowID(aParentOuterWindowID) @@ -204,6 +210,13 @@ LoadInfo::BaseURI() return mBaseURI; } +NS_IMETHODIMP +LoadInfo::GetUpgradeInsecureRequests(bool* aResult) +{ + *aResult = mUpgradeInsecureRequests; + return NS_OK; +} + NS_IMETHODIMP LoadInfo::GetInnerWindowID(uint64_t* aResult) { diff --git a/netwerk/base/LoadInfo.h b/netwerk/base/LoadInfo.h index 02c8caf801..db04f5b613 100644 --- a/netwerk/base/LoadInfo.h +++ b/netwerk/base/LoadInfo.h @@ -61,6 +61,7 @@ private: nsIPrincipal* aTriggeringPrincipal, nsSecurityFlags aSecurityFlags, nsContentPolicyType aContentPolicyType, + bool aUpgradeInsecureRequests, uint64_t aInnerWindowID, uint64_t aOuterWindowID, uint64_t aParentOuterWindowID); @@ -73,13 +74,14 @@ private: nsCOMPtr mLoadingPrincipal; nsCOMPtr mTriggeringPrincipal; - nsWeakPtr mLoadingContext; - nsSecurityFlags mSecurityFlags; - nsContentPolicyType mInternalContentPolicyType; - nsCOMPtr mBaseURI; - uint64_t mInnerWindowID; - uint64_t mOuterWindowID; - uint64_t mParentOuterWindowID; + nsWeakPtr mLoadingContext; + nsSecurityFlags mSecurityFlags; + nsContentPolicyType mInternalContentPolicyType; + nsCOMPtr mBaseURI; + bool mUpgradeInsecureRequests; + uint64_t mInnerWindowID; + uint64_t mOuterWindowID; + uint64_t mParentOuterWindowID; // Is true if this load was triggered by processing the attributes of the // browsing context container. diff --git a/netwerk/base/nsILoadInfo.idl b/netwerk/base/nsILoadInfo.idl index 2e744882d9..73b27dc312 100644 --- a/netwerk/base/nsILoadInfo.idl +++ b/netwerk/base/nsILoadInfo.idl @@ -17,7 +17,7 @@ typedef unsigned long nsSecurityFlags; /** * An nsILoadOwner represents per-load information about who started the load. */ -[scriptable, builtinclass, uuid(dcf54f49-2d63-4c34-9da1-54df235f354c)] +[scriptable, builtinclass, uuid(6b7f8798-3c28-44fc-8b1a-cd613eb826c5)] interface nsILoadInfo : nsISupports { /** @@ -184,7 +184,6 @@ interface nsILoadInfo : nsISupports [noscript, notxpcom] nsContentPolicyType internalContentPolicyType(); - /** * A base URI for use in situations where it cannot otherwise be inferred. * This attribute may be null. The value of this attribute may be @@ -198,6 +197,27 @@ interface nsILoadInfo : nsISupports [noscript, notxpcom, nostdcall, binaryname(BaseURI)] nsIURI binaryBaseURI(); + /** + * Returns true if document or any of the documents ancestors + * up to the toplevel document make use of the CSP directive + * 'upgrade-insecure-requests'. Used to identify upgrade + * requests in e10s where the loadingDocument is not available. + * + * Warning: If the loadingDocument is null, then the + * upgradeInsecureRequests is false. + */ + readonly attribute boolean upgradeInsecureRequests; + +%{ C++ + inline bool GetUpgradeInsecureRequests() + { + bool result; + mozilla::DebugOnly rv = GetUpgradeInsecureRequests(&result); + MOZ_ASSERT(NS_SUCCEEDED(rv)); + return result; + } +%} + /** * Typically these are the window IDs of the window in which the element being * loaded lives. However, if the element being loaded is SchemeIs("https", &isHttps); NS_ENSURE_SUCCESS(rv,rv); - if (mAllowSTS && !isHttps) { + if (!isHttps) { + // If any of the documents up the chain to the root doucment makes use of + // the CSP directive 'upgrade-insecure-requests', then it's time to fulfill + // the promise to CSP and mixed content blocking to upgrade the channel + // from http to https. + if (mLoadInfo && mLoadInfo->GetUpgradeInsecureRequests()) { + // Please note that cross origin top level navigations are not subject + // to upgrade-insecure-requests, see: + // http://www.w3.org/TR/upgrade-insecure-requests/#examples + nsCOMPtr resultPrincipal; + nsContentUtils::GetSecurityManager()-> + GetChannelResultPrincipal(this, getter_AddRefs(resultPrincipal)); + bool crossOriginNavigation = + (mLoadInfo->GetExternalContentPolicyType() == nsIContentPolicy::TYPE_DOCUMENT) && + (!resultPrincipal->Equals(mLoadInfo->LoadingPrincipal())); + + if (!crossOriginNavigation) { + // let's log a message to the console that we are upgrading a request + nsAutoCString spec, scheme; + mURI->GetSpec(spec); + mURI->GetScheme(scheme); + // append the additional 's' for security to the scheme :-) + scheme.AppendASCII("s"); + NS_ConvertUTF8toUTF16 reportSpec(spec); + NS_ConvertUTF8toUTF16 reportScheme(scheme); + + const char16_t* params[] = { reportSpec.get(), reportScheme.get() }; + uint32_t innerWindowId = mLoadInfo ? mLoadInfo->GetInnerWindowID() : 0; + CSP_LogLocalizedStr(NS_LITERAL_STRING("upgradeInsecureRequest").get(), + params, ArrayLength(params), + EmptyString(), // aSourceFile + EmptyString(), // aScriptSample + 0, // aLineNumber + 0, // aColumnNumber + nsIScriptError::warningFlag, "CSP", + innerWindowId); + + //Telemetry::Accumulate(Telemetry::HTTP_SCHEME_UPGRADE, 4); + return AsyncCall(&nsHttpChannel::HandleAsyncRedirectChannelToHttps); + } + } + // enforce Strict-Transport-Security nsISiteSecurityService* sss = gHttpHandler->GetSSService(); NS_ENSURE_TRUE(sss, NS_ERROR_OUT_OF_MEMORY); @@ -326,8 +369,17 @@ nsHttpChannel::Connect() if (isStsHost) { LOG(("nsHttpChannel::Connect() STS permissions found\n")); - return AsyncCall(&nsHttpChannel::HandleAsyncRedirectChannelToHttps); + if (mAllowSTS) { + //Telemetry::Accumulate(Telemetry::HTTP_SCHEME_UPGRADE, 3); + return AsyncCall(&nsHttpChannel::HandleAsyncRedirectChannelToHttps); + } else { + //Telemetry::Accumulate(Telemetry::HTTP_SCHEME_UPGRADE, 2); + } + } else { + //Telemetry::Accumulate(Telemetry::HTTP_SCHEME_UPGRADE, 1); } + } else { + //Telemetry::Accumulate(Telemetry::HTTP_SCHEME_UPGRADE, 0); } // ensure that we are using a valid hostname @@ -2386,7 +2438,7 @@ nsHttpChannel::ProcessPartialContent() LOG(("nsHttpChannel::ProcessPartialContent [this=%p] " "206 has different total entity size than the content length " "of the original partially cached entity.\n", this)); - + mCacheEntry->AsyncDoom(nullptr); Cancel(NS_ERROR_CORRUPTED_CONTENT); return CallOnStartRequest(); @@ -2821,7 +2873,7 @@ nsHttpChannel::OpenCacheEntry(bool isHttps) } if (!mPostID && mApplicationCache) { - rv = cacheStorageService->AppCacheStorage(info, + rv = cacheStorageService->AppCacheStorage(info, mApplicationCache, getter_AddRefs(cacheStorage)); } @@ -5872,7 +5924,7 @@ nsHttpChannel::OnDataAvailable(nsIRequest *request, nsISupports *ctxt, } mLogicalOffset += count; } - + return rv; } diff --git a/toolkit/components/telemetry/Histograms.json b/toolkit/components/telemetry/Histograms.json index 94838aa614..5d73155259 100644 --- a/toolkit/components/telemetry/Histograms.json +++ b/toolkit/components/telemetry/Histograms.json @@ -1247,6 +1247,12 @@ "kind": "boolean", "description": "Whether a WAP content type response is served to the browser." }, + "HTTP_SCHEME_UPGRADE": { + "expires_in_version": "never", + "kind": "enumerated", + "n_values": 10, + "description": "Was the URL upgraded to HTTPS? (0=already HTTPS, 1=no reason to upgrade, 2=STS upgrade blocked by pref, 3=upgraded with STS, 4=upgraded with CSP)" + }, "SSL_HANDSHAKE_VERSION": { "expires_in_version": "never", "kind": "enumerated",