From 1ff4caafd782f35cd00e2a35dc693fb935d3a9e2 Mon Sep 17 00:00:00 2001 From: Tom Tung Date: Thu, 25 Mar 2021 12:09:39 +0000 Subject: [PATCH] Bug 1696111 - ORB core Implementation; r=necko-reviewers,dragana Differential Revision: https://phabricator.services.mozilla.com/D102448 --- image/build/components.conf | 5 +- image/imgLoader.cpp | 17 ++- image/moz.build | 2 + netwerk/protocol/http/HttpBaseChannel.cpp | 139 +++++++++++++++++- netwerk/protocol/http/HttpBaseChannel.h | 7 + .../protocol/http/InterceptedHttpChannel.cpp | 13 ++ netwerk/protocol/http/nsHttpChannel.cpp | 32 ++++ netwerk/protocol/http/nsHttpChannel.h | 3 + toolkit/components/mediasniffer/moz.build | 8 + .../mediasniffer/nsMediaSniffer.cpp | 17 +++ 10 files changed, 240 insertions(+), 3 deletions(-) diff --git a/image/build/components.conf b/image/build/components.conf index 3646c735f84d..69022a713774 100644 --- a/image/build/components.conf +++ b/image/build/components.conf @@ -25,7 +25,10 @@ Classes = [ 'type': 'imgLoader', 'headers': ['imgLoader.h'], 'init_method': 'Init', - 'categories': {'content-sniffing-services': '@mozilla.org/image/loader;1'}, + 'categories': { + 'content-sniffing-services': '@mozilla.org/image/loader;1', + 'net-content-sniffers': '@mozilla.org/image/loader;1', + }, }, { 'cid': '{f6fcd651-164b-4416-b001-9c8c393fd93b}', diff --git a/image/imgLoader.cpp b/image/imgLoader.cpp index 1a1d945dd059..3a5bcb1e7d06 100644 --- a/image/imgLoader.cpp +++ b/image/imgLoader.cpp @@ -37,6 +37,7 @@ #include "nsComponentManagerUtils.h" #include "nsContentPolicyUtils.h" #include "nsContentUtils.h" +#include "nsHttpChannel.h" #include "nsIApplicationCache.h" #include "nsIApplicationCacheContainer.h" #include "nsIAsyncVerifyRedirectCallback.h" @@ -2709,7 +2710,21 @@ imgLoader::GetMIMETypeFromContent(nsIRequest* aRequest, return NS_ERROR_NOT_AVAILABLE; } } - return GetMimeTypeFromContent((const char*)aContents, aLength, aContentType); + + nsresult rv = + GetMimeTypeFromContent((const char*)aContents, aLength, aContentType); + if (NS_SUCCEEDED(rv) && channel && XRE_IsParentProcess()) { + if (RefPtr httpChannel = + do_QueryObject(channel)) { + // If the image type pattern matching algorithm given bytes does not + // return undefined, then disable the further check and allow the + // response. + httpChannel->DisableIsOpaqueResponseAllowedAfterSniffCheck( + mozilla::net::nsHttpChannel::SnifferType::Image); + } + } + + return rv; } /* static */ diff --git a/image/moz.build b/image/moz.build index 427077f4c0ba..effddb03089a 100644 --- a/image/moz.build +++ b/image/moz.build @@ -119,6 +119,8 @@ LOCAL_INCLUDES += [ "/image/decoders", # For URI-related functionality "/netwerk/base", + # For nsHttpChannel.h + "/netwerk/protocol/http", # DecodePool uses thread-related facilities. "/xpcom/threads", ] diff --git a/netwerk/protocol/http/HttpBaseChannel.cpp b/netwerk/protocol/http/HttpBaseChannel.cpp index a7abc7695aad..64a6633ff44c 100644 --- a/netwerk/protocol/http/HttpBaseChannel.cpp +++ b/netwerk/protocol/http/HttpBaseChannel.cpp @@ -35,6 +35,7 @@ #include "mozilla/dom/Performance.h" #include "mozilla/dom/PerformanceStorage.h" #include "mozilla/dom/WindowGlobalParent.h" +#include "mozilla/net/OpaqueResponseUtils.h" #include "mozilla/net/PartiallySeekableInputStream.h" #include "mozilla/net/UrlClassifierCommon.h" #include "mozilla/net/UrlClassifierFeatureFactory.h" @@ -201,7 +202,10 @@ HttpBaseChannel::HttpBaseChannel() mPriority(PRIORITY_NORMAL), mRedirectionLimit(gHttpHandler->RedirectionLimit()), mRedirectCount(0), - mInternalRedirectCount(0) { + mInternalRedirectCount(0), + mCachedOpaqueResponseBlockingPref( + StaticPrefs::browser_opaqueResponseBlocking()), + mCheckIsOpaqueResponseAllowedAfterSniff(false) { StoreApplyConversion(true); StoreAllowSTS(true); StoreInheritApplicationCache(true); @@ -2774,6 +2778,139 @@ nsresult HttpBaseChannel::ValidateMIMEType() { return NS_OK; } +bool HttpBaseChannel::EnsureOpaqueResponseIsAllowed() { + MOZ_ASSERT(XRE_IsParentProcess()); + + if (!mCachedOpaqueResponseBlockingPref) { + return true; + } + + if (!mURI || !mResponseHead || !mLoadInfo) { + // if there is no uri, no response head or no loadInfo, then there is + // nothing to do + return true; + } + + nsCOMPtr principal = mLoadInfo->GetLoadingPrincipal(); + if (!principal || principal->IsSystemPrincipal()) { + // If it's a top-level load or a system principal, then there is nothing to + // do. + return true; + } + + // Check if it's cross-origin without CORS. + const bool isPrivateWin = + mLoadInfo->GetOriginAttributes().mPrivateBrowsingId > 0; + bool isSameOrigin = false; + principal->IsSameOrigin(mURI, isPrivateWin, &isSameOrigin); + if (isSameOrigin) { + return true; + } + + nsAutoCString corsOrigin; + nsresult rv = mResponseHead->GetHeader( + nsHttp::ResolveAtom("Access-Control-Allow-Origin"), corsOrigin); + if (NS_SUCCEEDED(rv)) { + if (corsOrigin.Equals("*")) { + return true; + } + + nsCOMPtr corsOriginURI; + rv = NS_NewURI(getter_AddRefs(corsOriginURI), corsOrigin); + if (NS_SUCCEEDED(rv)) { + bool isSameOrigin = false; + principal->IsSameOrigin(corsOriginURI, isPrivateWin, &isSameOrigin); + if (isSameOrigin) { + return true; + } + } + } + + nsAutoCString contentType; + mResponseHead->ContentType(contentType); + if (!contentType.IsEmpty()) { + if (IsOpaqueSafeListedMIMEType(contentType)) { + return true; + } + + if (IsOpaqueBlockListedNeverSniffedMIMEType(contentType)) { + // XXXtt: Report To Console. + return false; + } + + if (mResponseHead->Status() == 206 && + IsOpaqueBlockListedMIMEType(contentType)) { + // XXXtt: Report To Console. + return false; + } + + nsAutoCString contentTypeOptionsHeader; + if (mResponseHead->GetContentTypeOptionsHeader(contentTypeOptionsHeader) && + contentTypeOptionsHeader.EqualsIgnoreCase("nosniff") && + (IsOpaqueBlockListedMIMEType(contentType) || + contentType.EqualsLiteral(TEXT_PLAIN))) { + // XXXtt: Report To Console. + return false; + } + } + + mLoadFlags |= (nsIChannel::LOAD_CALL_CONTENT_SNIFFERS | + nsIChannel::LOAD_MEDIA_SNIFFER_OVERRIDES_CONTENT_TYPE); + mCheckIsOpaqueResponseAllowedAfterSniff = true; + + return true; +} + +Result +HttpBaseChannel::EnsureOpaqueResponseIsAllowedAfterSniff() { + MOZ_ASSERT(XRE_IsParentProcess()); + + if (!mCheckIsOpaqueResponseAllowedAfterSniff) { + return true; + } + + MOZ_ASSERT(mCachedOpaqueResponseBlockingPref); + + nsAutoCString contentType; + nsresult rv = GetContentType(contentType); + if (NS_FAILED(rv)) { + return Err(rv); + } + + if (!mResponseHead) { + return true; + } + + nsAutoCString contentTypeOptionsHeader; + if (mResponseHead->GetContentTypeOptionsHeader(contentTypeOptionsHeader) && + contentTypeOptionsHeader.EqualsIgnoreCase("nosniff")) { + // XXXtt: Report To Console. + return false; + } + + if (mResponseHead->Status() < 200 || mResponseHead->Status() > 299) { + // XXXtt: Report To Console. + return false; + } + + if (contentType.EqualsLiteral(UNKNOWN_CONTENT_TYPE) || + contentType.EqualsLiteral(APPLICATION_OCTET_STREAM)) { + return true; + } + + if (StringBeginsWith(contentType, "image/"_ns) || + StringBeginsWith(contentType, "video/"_ns) || + StringBeginsWith(contentType, "audio/"_ns)) { + // XXXtt: Report To Console. + return false; + } + + // XXXtt: If response's body parses as JavaScript and does not parse as JSON, + // then return true. + + return true; +} + NS_IMETHODIMP HttpBaseChannel::SetCookie(const nsACString& aCookieHeader) { if (mLoadFlags & LOAD_ANONYMOUS) return NS_OK; diff --git a/netwerk/protocol/http/HttpBaseChannel.h b/netwerk/protocol/http/HttpBaseChannel.h index c76e53863cfe..b763c5b5d606 100644 --- a/netwerk/protocol/http/HttpBaseChannel.h +++ b/netwerk/protocol/http/HttpBaseChannel.h @@ -616,6 +616,10 @@ class HttpBaseChannel : public nsHashPropertyBag, nsresult ValidateMIMEType(); + bool EnsureOpaqueResponseIsAllowed(); + + Result EnsureOpaqueResponseIsAllowedAfterSniff(); + friend class PrivateBrowsingChannel; friend class InterceptFailedOnStop; @@ -881,6 +885,9 @@ class HttpBaseChannel : public nsHashPropertyBag, // Number of internal redirects that has occurred. int8_t mInternalRedirectCount; + const bool mCachedOpaqueResponseBlockingPref; + bool mCheckIsOpaqueResponseAllowedAfterSniff; + // clang-format off MOZ_ATOMIC_BITFIELDS(mAtomicBitfields3, 8, ( (bool, AsyncOpenTimeOverriden, 1), diff --git a/netwerk/protocol/http/InterceptedHttpChannel.cpp b/netwerk/protocol/http/InterceptedHttpChannel.cpp index 114d11aedbd5..a1aa44dabad4 100644 --- a/netwerk/protocol/http/InterceptedHttpChannel.cpp +++ b/netwerk/protocol/http/InterceptedHttpChannel.cpp @@ -1016,10 +1016,23 @@ InterceptedHttpChannel::OnStartRequest(nsIRequest* aRequest) { GetCallback(mProgressSink); } + if (!EnsureOpaqueResponseIsAllowed()) { + // XXXtt: Return an error code or make the response body null. + // We silence the error result now because we only want to get how many + // response will get allowed or blocked by ORB. + } + if (mPump && mLoadFlags & LOAD_CALL_CONTENT_SNIFFERS) { mPump->PeekStream(CallTypeSniffers, static_cast(this)); } + auto isAllowedOrErr = EnsureOpaqueResponseIsAllowedAfterSniff(); + if (isAllowedOrErr.isErr() || !isAllowedOrErr.inspect()) { + // XXXtt: Return an error code or make the response body null. + // We silence the error result now because we only want to get how many + // response will get allowed or blocked by ORB. + } + nsresult rv = ProcessCrossOriginEmbedderPolicyHeader(); if (NS_FAILED(rv)) { mStatus = NS_ERROR_BLOCKED_BY_POLICY; diff --git a/netwerk/protocol/http/nsHttpChannel.cpp b/netwerk/protocol/http/nsHttpChannel.cpp index 054200c8f779..763d70cf0284 100644 --- a/netwerk/protocol/http/nsHttpChannel.cpp +++ b/netwerk/protocol/http/nsHttpChannel.cpp @@ -124,6 +124,7 @@ #include "mozilla/net/AsyncUrlChannelClassifier.h" #include "mozilla/net/CookieJarSettings.h" #include "mozilla/net/NeckoChannelParams.h" +#include "mozilla/net/OpaqueResponseUtils.h" #include "mozilla/net/UrlClassifierFeatureFactory.h" #include "HttpTrafficAnalyzer.h" #include "mozilla/net/SocketProcessParent.h" @@ -1618,6 +1619,16 @@ nsresult nsHttpChannel::CallOnStartRequest() { return mStatus; } + // EnsureOpaqueResponseIsAllowed and EnsureOpauqeResponseIsAllowedAfterSniff + // are the checks for Opaque Response Blocking to ensure that we block as many + // cross-origin responses with CORS headers as possible that are not either + // Javascript or media to avoid leaking their contents through side channels. + if (!EnsureOpaqueResponseIsAllowed()) { + // XXXtt: Return an error code or make the response body null. + // We silence the error result now because we only want to get how many + // response will get allowed or blocked by ORB. + } + // Allow consumers to override our content type if (mLoadFlags & LOAD_CALL_CONTENT_SNIFFERS) { // NOTE: We can have both a txn pump and a cache pump when the cache @@ -1646,6 +1657,13 @@ nsresult nsHttpChannel::CallOnStartRequest() { } } + auto isAllowedOrErr = EnsureOpaqueResponseIsAllowedAfterSniff(); + if (isAllowedOrErr.isErr() || !isAllowedOrErr.inspect()) { + // XXXtt: Return an error code or make the response body null. + // We silence the error result now because we only want to get how many + // response will get allowed or blocked by ORB. + } + // Note that the code below should be synced with the code in // HttpTransactionChild::CanSendODAToContentProcessDirectly(). We MUST make // sure HttpTransactionChild::CanSendODAToContentProcessDirectly() returns @@ -9963,6 +9981,20 @@ HttpChannelSecurityWarningReporter* nsHttpChannel::GetWarningReporter() { return mWarningReporter.get(); } +// Should only be called by nsMediaSniffer::GetMIMETypeFromContent and +// nsMediaSniffer::GetMIMETypeFromContent when the content type can be +// recognized by these sniffers. +void nsHttpChannel::DisableIsOpaqueResponseAllowedAfterSniffCheck( + SnifferType aType) { + MOZ_ASSERT(XRE_IsParentProcess()); + + if (mCheckIsOpaqueResponseAllowedAfterSniff) { + MOZ_ASSERT(mCachedOpaqueResponseBlockingPref); + + mCheckIsOpaqueResponseAllowedAfterSniff = false; + } +} + namespace { class CopyNonDefaultHeaderVisitor final : public nsIHttpHeaderVisitor { diff --git a/netwerk/protocol/http/nsHttpChannel.h b/netwerk/protocol/http/nsHttpChannel.h index ba761745931f..4f9f56e978e4 100644 --- a/netwerk/protocol/http/nsHttpChannel.h +++ b/netwerk/protocol/http/nsHttpChannel.h @@ -201,6 +201,9 @@ class nsHttpChannel final : public HttpBaseChannel, bool DataSentToChildProcess() { return LoadDataSentToChildProcess(); } + enum class SnifferType { Media, Image }; + void DisableIsOpaqueResponseAllowedAfterSniffCheck(SnifferType aType); + public: /* internal necko use only */ uint32_t GetRequestTime() const { return mRequestTime; } diff --git a/toolkit/components/mediasniffer/moz.build b/toolkit/components/mediasniffer/moz.build index 2e4ad98bc637..783b4555da0b 100644 --- a/toolkit/components/mediasniffer/moz.build +++ b/toolkit/components/mediasniffer/moz.build @@ -23,3 +23,11 @@ FINAL_LIBRARY = "xul" with Files("**"): BUG_COMPONENT = ("Core", "Audio/Video") + +include("/ipc/chromium/chromium-config.mozbuild") + +LOCAL_INCLUDES += [ + # For nsHttpChannel.h + "/netwerk/base", + "/netwerk/protocol/http", +] diff --git a/toolkit/components/mediasniffer/nsMediaSniffer.cpp b/toolkit/components/mediasniffer/nsMediaSniffer.cpp index f66af9fa769f..b2917b864ad4 100644 --- a/toolkit/components/mediasniffer/nsMediaSniffer.cpp +++ b/toolkit/components/mediasniffer/nsMediaSniffer.cpp @@ -10,10 +10,12 @@ #include "mozilla/ModuleUtils.h" #include "mp3sniff.h" #include "nestegg/nestegg.h" +#include "nsHttpChannel.h" #include "nsIClassInfoImpl.h" #include "nsIChannel.h" #include "nsMediaSniffer.h" #include "nsMimeTypes.h" +#include "nsQueryObject.h" #include "nsString.h" #include @@ -165,6 +167,19 @@ nsMediaSniffer::GetMIMETypeFromContent(nsIRequest* aRequest, const uint32_t clampedLength = std::min(aLength, MAX_BYTES_SNIFFED); + auto maybeUpdate = mozilla::MakeScopeExit([&channel]() { + if (channel && XRE_IsParentProcess()) { + if (RefPtr httpChannel = + do_QueryObject(channel)) { + // If the audio or video type pattern matching algorithm given bytes + // does not return undefined, then disable the further check and allow + // the response. + httpChannel->DisableIsOpaqueResponseAllowedAfterSniffCheck( + mozilla::net::nsHttpChannel::SnifferType::Media); + } + }; + }); + for (size_t i = 0; i < mozilla::ArrayLength(sSnifferEntries); ++i) { const nsMediaSnifferEntry& currentEntry = sSnifferEntries[i]; if (clampedLength < currentEntry.mLength || currentEntry.mLength == 0) { @@ -211,6 +226,8 @@ nsMediaSniffer::GetMIMETypeFromContent(nsIRequest* aRequest, return NS_OK; } + maybeUpdate.release(); + // Could not sniff the media type, we are required to set it to // application/octet-stream. aSniffedType.AssignLiteral(APPLICATION_OCTET_STREAM);