Bug 1696111 - ORB core Implementation; r=necko-reviewers,dragana

Differential Revision: https://phabricator.services.mozilla.com/D102448
This commit is contained in:
Tom Tung 2021-03-25 12:09:39 +00:00
parent 612d68756f
commit 1ff4caafd7
10 changed files with 240 additions and 3 deletions

View File

@ -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}',

View File

@ -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<mozilla::net::nsHttpChannel> 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 */

View File

@ -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",
]

View File

@ -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<nsIPrincipal> 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<nsIURI> 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<bool, nsresult>
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;

View File

@ -616,6 +616,10 @@ class HttpBaseChannel : public nsHashPropertyBag,
nsresult ValidateMIMEType();
bool EnsureOpaqueResponseIsAllowed();
Result<bool, nsresult> EnsureOpaqueResponseIsAllowedAfterSniff();
friend class PrivateBrowsingChannel<HttpBaseChannel>;
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),

View File

@ -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<nsIChannel*>(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;

View File

@ -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 {

View File

@ -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; }

View File

@ -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",
]

View File

@ -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 <algorithm>
@ -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<mozilla::net::nsHttpChannel> 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);