Skip to content

Commit

Permalink
Bug 1823877 - Part 1: Filter opaque results from fetch() in the paren…
Browse files Browse the repository at this point in the history
…t for ORB. r=sefeng,smaug,necko-reviewers,edenchuang,valentin

We make sure to not send any data to the content process in case of
fetching an opaque resource. This is way to remain more web
compatible, but is also in conflict with the ORB specification.

Differential Revision: https://phabricator.services.mozilla.com/D173454
  • Loading branch information
farre committed May 10, 2023
1 parent 94be15f commit 03e4a2e
Show file tree
Hide file tree
Showing 12 changed files with 421 additions and 45 deletions.
23 changes: 23 additions & 0 deletions modules/libpref/init/StaticPrefList.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -1737,6 +1737,29 @@
value: @IS_EARLY_BETA_OR_EARLIER@
mirror: always

# This pref controls how filtering of opaque responses for calls to `Window.fetch`.
# (and similar) is performed in the parent process. This is intended to make sure
# that data that would be filtered in a content process never actually reaches that
# content process.
# See https://fetch.spec.whatwg.org/#concept-filtered-response-opaque
# 0) Don't filter in the parent process at all, and let content processes handle
# opaque filtering. Regardless of if ORB is enabled or not. N.B. that if ORB
# is enabled opaque responses will be blocked.
# 1) If ORB is enabled, in the parent process, filter the responses that ORB allows.
# N.B. any responses ORB doesn't allow will not send data to a content process
# since they will return a NetworkError. If the request is allowed by ORB, the
# internal response will be intact and sent to the content process as is.
# 2) If ORB is enabled, in the parent process, filter the responses that ORB blocks,
# if they were issued by `Window.fetch` (and similar).
# 3) Filter all responses in the parent, regardless of if ORB is enabled or not.
# This means that opaque responses coming from `Window.fetch` won't even be
# considered for being blocked by ORB.
- name: browser.opaqueResponseBlocking.filterFetchResponse
type: uint32_t
value: 2
mirror: always
do_not_use_directly: true

# When this pref is enabled, <object> and <embed> elements will create
# synthetic documents when the resource type they're loading is an image.
- name: browser.opaqueResponseBlocking.syntheticBrowsingContext
Expand Down
155 changes: 119 additions & 36 deletions netwerk/protocol/http/HttpBaseChannel.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@
#include "nsContentSecurityManager.h"
#include "nsContentSecurityUtils.h"
#include "nsContentUtils.h"
#include "nsDebug.h"
#include "nsEscape.h"
#include "nsGlobalWindowOuter.h"
#include "nsHttpChannel.h"
Expand Down Expand Up @@ -104,9 +105,9 @@
#include "nsQueryObject.h"

using mozilla::dom::RequestMode;
extern mozilla::LazyLogModule gORBLog;
#define LOGORB(msg, ...) \
MOZ_LOG(gORBLog, LogLevel::Debug, \

#define LOGORB(msg, ...) \
MOZ_LOG(GetORBLog(), LogLevel::Debug, \
("%s: %p " msg, __func__, this, ##__VA_ARGS__))

namespace mozilla {
Expand Down Expand Up @@ -184,6 +185,17 @@ class AddHeadersToChannelVisitor final : public nsIHttpHeaderVisitor {

NS_IMPL_ISUPPORTS(AddHeadersToChannelVisitor, nsIHttpHeaderVisitor)

static OpaqueResponseFilterFetch ConfiguredFilterFetchResponseBehaviour() {
uint32_t pref = StaticPrefs::
browser_opaqueResponseBlocking_filterFetchResponse_DoNotUseDirectly();
if (NS_WARN_IF(pref >
static_cast<uint32_t>(OpaqueResponseFilterFetch::All))) {
return OpaqueResponseFilterFetch::All;
}

return static_cast<OpaqueResponseFilterFetch>(pref);
}

HttpBaseChannel::HttpBaseChannel()
: mReportCollector(new ConsoleReportCollector()),
mHttpHandler(gHttpHandler),
Expand Down Expand Up @@ -3017,6 +3029,19 @@ nsresult HttpBaseChannel::ValidateMIMEType() {
return NS_OK;
}

bool HttpBaseChannel::ShouldFilterOpaqueResponse(
OpaqueResponseFilterFetch aFilterType) const {
MOZ_DIAGNOSTIC_ASSERT(ShouldBlockOpaqueResponse());

if (!mLoadInfo || ConfiguredFilterFetchResponseBehaviour() != aFilterType) {
return false;
}

// We should filter a response in the parent if it is opaque and is the result
// of a fetch() function from the Fetch specification.
return mLoadInfo->InternalContentPolicyType() == nsIContentPolicy::TYPE_FETCH;
}

bool HttpBaseChannel::ShouldBlockOpaqueResponse() const {
if (!mURI || !mResponseHead || !mLoadInfo) {
// if there is no uri, no response head or no loadInfo, then there is
Expand All @@ -3036,6 +3061,7 @@ bool HttpBaseChannel::ShouldBlockOpaqueResponse() const {
// Check if the response is a opaque response, which means requestMode should
// be RequestMode::No_cors and responseType should be ResponseType::Opaque.
nsContentPolicyType contentPolicy = mLoadInfo->InternalContentPolicyType();

// Skip the RequestMode would be RequestMode::Navigate
if (contentPolicy == nsIContentPolicy::TYPE_DOCUMENT ||
contentPolicy == nsIContentPolicy::TYPE_SUBDOCUMENT ||
Expand Down Expand Up @@ -3102,6 +3128,41 @@ bool HttpBaseChannel::ShouldBlockOpaqueResponse() const {
return true;
}

OpaqueResponse HttpBaseChannel::BlockOrFilterOpaqueResponse(
OpaqueResponseBlocker* aORB, const nsAString& aReason, const char* aFormat,
...) {
const bool shouldFilter =
ShouldFilterOpaqueResponse(OpaqueResponseFilterFetch::BlockedByORB);

if (MOZ_UNLIKELY(MOZ_LOG_TEST(GetORBLog(), LogLevel::Debug))) {
va_list ap;
va_start(ap, aFormat);
nsVprintfCString logString(aFormat, ap);
va_end(ap);

LOGORB("%s: %s", shouldFilter ? "Filtered" : "Blocked", logString.get());
}

if (shouldFilter) {
// The existence of `mORB` depends on `BlockOrFilterOpaqueResponse` being
// called before or after sniffing has completed.
// Another requirement is that `OpaqueResponseFilter` must come after
// `OpaqueResponseBlocker`, which is why in the case of having an
// `OpaqueResponseBlocker` we let it handle creating an
// `OpaqueResponseFilter`.
if (aORB) {
MOZ_DIAGNOSTIC_ASSERT(!mORB || aORB == mORB);
aORB->FilterResponse();
} else {
mListener = new OpaqueResponseFilter(mListener);
}
return OpaqueResponse::Allow;
}

LogORBError(aReason);
return OpaqueResponse::Block;
}

// The specification for ORB is currently being written:
// https://whatpr.org/fetch/1442.html#orb-algorithm
// The `opaque-response-safelist check` is implemented in:
Expand All @@ -3113,15 +3174,42 @@ OpaqueResponse
HttpBaseChannel::PerformOpaqueResponseSafelistCheckBeforeSniff() {
MOZ_ASSERT(XRE_IsParentProcess());

if (!mCachedOpaqueResponseBlockingPref) {
// https://whatpr.org/fetch/1442.html#http-fetch, step 6.4
if (!ShouldBlockOpaqueResponse()) {
return OpaqueResponse::Allow;
}

// https://whatpr.org/fetch/1442.html#http-fetch, step 6.4
if (!ShouldBlockOpaqueResponse()) {
// Regardless of if ORB is enabled or not, we check if we should filter the
// response in the parent. This way data won't reach a content process that
// will create a filtered `Response` object. This is enabled when
// 'browser.opaqueResponseBlocking.filterFetchResponse' is
// `OpaqueResponseFilterFetch::All`.
// See https://fetch.spec.whatwg.org/#concept-filtered-response-opaque
if (ShouldFilterOpaqueResponse(OpaqueResponseFilterFetch::All)) {
mListener = new OpaqueResponseFilter(mListener);

// If we're filtering a response in the parent, there will be no data to
// determine if it should be blocked or not so the only option we have is to
// allow it.
return OpaqueResponse::Allow;
}

if (!mCachedOpaqueResponseBlockingPref) {
return OpaqueResponse::Allow;
}

// If ORB is enabled, we check if we should filter the response in the parent.
// This way data won't reach a content process that will create a filtered
// `Response` object. We allow ORB to determine if the response should be
// blocked or filtered, but regardless no data should reach the content
// process. This is enabled when
// 'browser.opaqueResponseBlocking.filterFetchResponse' is
// `OpaqueResponseFilterFetch::AllowedByORB`.
// See https://fetch.spec.whatwg.org/#concept-filtered-response-opaque
if (ShouldFilterOpaqueResponse(OpaqueResponseFilterFetch::AllowedByORB)) {
mListener = new OpaqueResponseFilter(mListener);
}

Telemetry::ScalarAdd(
Telemetry::ScalarID::
OPAQUE_RESPONSE_BLOCKING_CROSS_ORIGIN_OPAQUE_RESPONSE_COUNT,
Expand All @@ -3147,24 +3235,22 @@ HttpBaseChannel::PerformOpaqueResponseSafelistCheckBeforeSniff() {
// Step 3.1
return OpaqueResponse::Allow;
case OpaqueResponseBlockedReason::BLOCKED_BLOCKLISTED_NEVER_SNIFFED:
// Step 3.2
LOGORB("Blocked: BLOCKED_BLOCKLISTED_NEVER_SNIFFED");
LogORBError(
u"mimeType is an opaque-blocklisted-never-sniffed MIME type"_ns);
return OpaqueResponse::Block;
return BlockOrFilterOpaqueResponse(
mORB, u"mimeType is an opaque-blocklisted-never-sniffed MIME type"_ns,
"BLOCKED_BLOCKLISTED_NEVER_SNIFFED");
case OpaqueResponseBlockedReason::BLOCKED_206_AND_BLOCKLISTED:
// Step 3.3
LOGORB("Blocked: BLOCKED_206_AND_BLOCKEDLISTED");
LogORBError(
u"response's status is 206 and mimeType is an opaque-blocklisted MIME type"_ns);
return OpaqueResponse::Block;
return BlockOrFilterOpaqueResponse(
mORB,
u"response's status is 206 and mimeType is an opaque-blocklisted MIME type"_ns,
"BLOCKED_206_AND_BLOCKEDLISTED");
case OpaqueResponseBlockedReason::
BLOCKED_NOSNIFF_AND_EITHER_BLOCKLISTED_OR_TEXTPLAIN:
// Step 3.4
LOGORB("Blocked: BLOCKED_NOSNIFF_AND_EITHER_BLOCKLISTED_OR_TEXTPLAIN");
LogORBError(
u"nosniff is true and mimeType is an opaque-blocklisted MIME type or its essence is 'text/plain'"_ns);
return OpaqueResponse::Block;
return BlockOrFilterOpaqueResponse(
mORB,
u"nosniff is true and mimeType is an opaque-blocklisted MIME type or its essence is 'text/plain'"_ns,
"BLOCKED_NOSNIFF_AND_EITHER_BLOCKLISTED_OR_TEXTPLAIN");
default:
break;
}
Expand All @@ -3185,9 +3271,9 @@ HttpBaseChannel::PerformOpaqueResponseSafelistCheckBeforeSniff() {
// Step 5
if (mResponseHead->Status() == 206 &&
!IsFirstPartialResponse(*mResponseHead)) {
LOGORB("Blocked: Is not a valid partial response given 0");
LogORBError(u"response status is 206 and not first partial response"_ns);
return OpaqueResponse::Block;
return BlockOrFilterOpaqueResponse(
mORB, u"response status is 206 and not first partial response"_ns,
"Is not a valid partial response given 0");
}

// Setup for steps 6, 7, 8 and 10.
Expand Down Expand Up @@ -3242,25 +3328,22 @@ OpaqueResponse HttpBaseChannel::PerformOpaqueResponseSafelistCheckAfterSniff(
bool isMediaRequest;
mLoadInfo->GetIsMediaRequest(&isMediaRequest);
if (isMediaRequest) {
LOGORB("Blocked: media request");
LogORBError(u"after sniff: media request"_ns);
return OpaqueResponse::Block;
return BlockOrFilterOpaqueResponse(mORB, u"after sniff: media request"_ns,
"media request");
}

// Step 11
if (aNoSniff) {
LOGORB("Blocked: nosniff");
LogORBError(u"after sniff: nosniff is true"_ns);
return OpaqueResponse::Block;
return BlockOrFilterOpaqueResponse(mORB, u"after sniff: nosniff is true"_ns,
"nosniff");
}

// Step 12
if (mResponseHead &&
(mResponseHead->Status() < 200 || mResponseHead->Status() > 299)) {
LOGORB("Blocked: status code (%d) is not allowed ",
mResponseHead->Status());
LogORBError(u"after sniff: status code is not in allowed range"_ns);
return OpaqueResponse::Block;
return BlockOrFilterOpaqueResponse(
mORB, u"after sniff: status code is not in allowed range"_ns,
"status code (%d) is not allowed", mResponseHead->Status());
}

// Step 13
Expand All @@ -3273,10 +3356,10 @@ OpaqueResponse HttpBaseChannel::PerformOpaqueResponseSafelistCheckAfterSniff(
if (StringBeginsWith(aContentType, "image/"_ns) ||
StringBeginsWith(aContentType, "video/"_ns) ||
StringBeginsWith(aContentType, "audio/"_ns)) {
LOGORB("Blocked: ContentType is image/video/audio");
LogORBError(
u"after sniff: content-type declares image/video/audio, but sniffing fails"_ns);
return OpaqueResponse::Block;
return BlockOrFilterOpaqueResponse(
mORB,
u"after sniff: content-type declares image/video/audio, but sniffing fails"_ns,
"ContentType is image/video/audio");
}

return OpaqueResponse::Sniff;
Expand Down
12 changes: 10 additions & 2 deletions netwerk/protocol/http/HttpBaseChannel.h
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@

#include <utility>

#include "OpaqueResponseUtils.h"
#include "mozilla/AtomicBitfields.h"
#include "mozilla/Atomics.h"
#include "mozilla/dom/DOMTypes.h"
Expand Down Expand Up @@ -41,6 +42,7 @@
#include "nsIURI.h"
#include "nsIUploadChannel2.h"
#include "nsStringEnumerator.h"
#include "nsStringFwd.h"
#include "nsTArray.h"
#include "nsThreadUtils.h"

Expand Down Expand Up @@ -80,7 +82,9 @@ enum CacheDisposition : uint8_t {
kCacheUnknown = 5
};

enum class OpaqueResponse { Block, Allow, SniffCompressed, Sniff };
// These need to be kept in sync with
// "browser.opaqueResponseBlocking.filterFetchResponse"
enum class OpaqueResponseFilterFetch { Never, AllowedByORB, BlockedByORB, All };

/*
* This class is a partial implementation of nsIHttpChannel. It contains code
Expand Down Expand Up @@ -557,7 +561,7 @@ class HttpBaseChannel : public nsHashPropertyBag,
// https://fetch.spec.whatwg.org/#concept-request-tainted-origin
bool HasRedirectTaintedOrigin() { return LoadTaintedOriginFlag(); }

bool ChannelBlockedByOpaqueResponse() {
bool ChannelBlockedByOpaqueResponse() const {
return mChannelBlockedByOpaqueResponse;
}
bool CachedOpaqueResponseBlockingPref() const {
Expand Down Expand Up @@ -649,7 +653,11 @@ class HttpBaseChannel : public nsHashPropertyBag,

nsresult ValidateMIMEType();

bool ShouldFilterOpaqueResponse(OpaqueResponseFilterFetch aFilterType) const;
bool ShouldBlockOpaqueResponse() const;
OpaqueResponse BlockOrFilterOpaqueResponse(OpaqueResponseBlocker* aORB,
const nsAString& aReason,
const char* aFormat, ...);

OpaqueResponse PerformOpaqueResponseSafelistCheckBeforeSniff();

Expand Down
2 changes: 1 addition & 1 deletion netwerk/protocol/http/HttpChannelParent.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1211,7 +1211,7 @@ HttpChannelParent::OnStartRequest(nsIRequest* aRequest) {
responseHead = &cleanedUpResponseHead;
}

if (chan && chan->ChannelBlockedByOpaqueResponse() &&
if (chan->ChannelBlockedByOpaqueResponse() &&
chan->CachedOpaqueResponseBlockingPref()) {
responseHead->ClearHeaders();
}
Expand Down
Loading

0 comments on commit 03e4a2e

Please sign in to comment.