Bug 1532644 - Implement the initial version of the Javascript Validator for ORB r=farre,smaug

For things that can be parsed as Javascript, we need to figure out
if they are JSON, and we want to block opaque JSON resources for ORB.

This initial version just checks the first byte of the response, and
blocks it if it's a curly bracket.

Differential Revision: https://phabricator.services.mozilla.com/D163283
This commit is contained in:
Sean Feng 2023-01-09 15:27:18 +00:00
Родитель cd0c6d1e15
Коммит 18ee907a90
28 изменённых файлов: 711 добавлений и 51 удалений

Просмотреть файл

@ -1071,7 +1071,7 @@ static WebIDLUtilityActorName UtilityActorNameToWebIDL(
mozilla::UtilityActorName aType) {
// Max is the value of the last enum, not the length, so add one.
static_assert(WebIDLUtilityActorNameValues::Count ==
static_cast<size_t>(UtilityActorName::MfMediaEngineCDM) + 1,
static_cast<size_t>(UtilityActorName::JSOracle) + 1,
"In order for this static cast to be okay, "
"UtilityActorName must match UtilityActorName exactly");
@ -1083,6 +1083,7 @@ static WebIDLUtilityActorName UtilityActorNameToWebIDL(
AudioDecoder_AppleMedia);
UTILITYACTORNAME_TO_WEBIDL_CASE(AudioDecoder_WMF, AudioDecoder_WMF);
UTILITYACTORNAME_TO_WEBIDL_CASE(MfMediaEngineCDM, MfMediaEngineCDM);
UTILITYACTORNAME_TO_WEBIDL_CASE(JSOracle, JSOracle);
}
MOZ_ASSERT(false, "Unhandled case in WebIDLUtilityActorName");

Просмотреть файл

@ -727,6 +727,7 @@ enum WebIDLUtilityActorName {
"audioDecoder_AppleMedia",
"audioDecoder_WMF",
"mfMediaEngineCDM",
"jSOracle",
};
dictionary UtilityActorsDictionary {

34
dom/ipc/JSOracleChild.cpp Normal file
Просмотреть файл

@ -0,0 +1,34 @@
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "mozilla/dom/JSOracleChild.h"
#include "mozilla/ClearOnShutdown.h"
#include "mozilla/dom/JSValidatorChild.h"
#include "mozilla/dom/PJSValidatorChild.h"
#include "mozilla/ipc/Endpoint.h"
using namespace mozilla::dom;
static mozilla::StaticRefPtr<JSOracleChild> sOracleSingletonChild;
JSOracleChild* JSOracleChild::GetSingleton() {
MOZ_ASSERT(NS_IsMainThread());
if (!sOracleSingletonChild) {
sOracleSingletonChild = new JSOracleChild();
ClearOnShutdown(&sOracleSingletonChild);
}
return sOracleSingletonChild;
}
already_AddRefed<PJSValidatorChild> JSOracleChild::AllocPJSValidatorChild() {
return MakeAndAddRef<JSValidatorChild>();
}
void JSOracleChild::Start(Endpoint<PJSOracleChild>&& aEndpoint) {
DebugOnly<bool> ok = std::move(aEndpoint).Bind(this);
MOZ_ASSERT(ok);
}

34
dom/ipc/JSOracleChild.h Normal file
Просмотреть файл

@ -0,0 +1,34 @@
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#ifndef mozilla_dom_JSOracleChild
#define mozilla_dom_JSOracleChild
#include "mozilla/dom/PJSOracleChild.h"
namespace mozilla::ipc {
class UtilityProcessParent;
}
namespace mozilla::dom {
class PJSValidatorChild;
class JSOracleChild final : public PJSOracleChild {
public:
NS_INLINE_DECL_THREADSAFE_REFCOUNTING(JSOracleChild, override);
already_AddRefed<PJSValidatorChild> AllocPJSValidatorChild();
void Start(Endpoint<PJSOracleChild>&& aEndpoint);
private:
~JSOracleChild() = default;
static JSOracleChild* GetSingleton();
};
} // namespace mozilla::dom
#endif // defined(mozilla_dom_JSOracleChild)

Просмотреть файл

@ -0,0 +1,72 @@
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "mozilla/dom/JSOracleParent.h"
#include "mozilla/ClearOnShutdown.h"
#include "mozilla/RefPtr.h"
#include "mozilla/dom/PJSOracle.h"
#include "mozilla/ipc/Endpoint.h"
#include "mozilla/ipc/UtilityProcessManager.h"
using namespace mozilla;
using namespace mozilla::dom;
static StaticRefPtr<JSOracleParent> sOracleSingleton;
/* static */
void JSOracleParent::WithJSOracle(
const std::function<void(JSOracleParent* aParent)>& aCallback) {
GetSingleton()->StartJSOracle()->Then(
GetMainThreadSerialEventTarget(), __func__,
[aCallback](const JSOraclePromise::ResolveOrRejectValue& aResult) {
aCallback(aResult.IsReject() || !aResult.ResolveValue()
? nullptr
: GetSingleton());
});
}
void JSOracleParent::ActorDestroy(ActorDestroyReason aReason) {}
/* static */
JSOracleParent* JSOracleParent::GetSingleton() {
if (!sOracleSingleton) {
sOracleSingleton = new JSOracleParent();
ClearOnShutdown(&sOracleSingleton);
}
return sOracleSingleton;
}
RefPtr<JSOracleParent::JSOraclePromise> JSOracleParent::StartJSOracle() {
using namespace mozilla::ipc;
RefPtr<JSOracleParent> parent = JSOracleParent::GetSingleton();
return UtilityProcessManager::GetSingleton()->StartJSOracle(parent);
}
nsresult JSOracleParent::BindToUtilityProcess(
const RefPtr<mozilla::ipc::UtilityProcessParent>& aUtilityParent) {
Endpoint<PJSOracleParent> parentEnd;
Endpoint<PJSOracleChild> childEnd;
MOZ_ASSERT(aUtilityParent);
if (NS_FAILED(PJSOracle::CreateEndpoints(base::GetCurrentProcId(),
aUtilityParent->OtherPid(),
&parentEnd, &childEnd))) {
return NS_ERROR_FAILURE;
}
if (!aUtilityParent->SendStartJSOracleService(std::move(childEnd))) {
return NS_ERROR_FAILURE;
}
Bind(std::move(parentEnd));
return NS_OK;
}
void JSOracleParent::Bind(Endpoint<PJSOracleParent>&& aEndpoint) {
DebugOnly<bool> ok = aEndpoint.Bind(this);
MOZ_ASSERT(ok);
}

48
dom/ipc/JSOracleParent.h Normal file
Просмотреть файл

@ -0,0 +1,48 @@
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#ifndef mozilla_dom_JSOracleParent
#define mozilla_dom_JSOracleParent
#include "mozilla/MozPromise.h"
#include "mozilla/ProcInfo.h"
#include "mozilla/dom/PJSOracleParent.h"
namespace mozilla::ipc {
class UtilityProcessParent;
}
namespace mozilla::dom {
class JSOracleParent final : public PJSOracleParent {
public:
JSOracleParent() = default;
NS_INLINE_DECL_THREADSAFE_REFCOUNTING(JSOracleParent, override);
static void WithJSOracle(
const std::function<void(JSOracleParent* aParent)>& aCallback);
UtilityActorName GetActorName() { return UtilityActorName::JSOracle; }
void ActorDestroy(ActorDestroyReason aReason) override;
nsresult BindToUtilityProcess(
const RefPtr<mozilla::ipc::UtilityProcessParent>& aUtilityParent);
void Bind(Endpoint<PJSOracleParent>&& aEndpoint);
private:
~JSOracleParent() = default;
static JSOracleParent* GetSingleton();
using JSOraclePromise = GenericNonExclusivePromise;
RefPtr<JSOraclePromise> StartJSOracle();
};
} // namespace mozilla::dom
#endif // defined(mozilla_dom_JSOracleParent)

Просмотреть файл

@ -0,0 +1,81 @@
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "mozilla/dom/JSValidatorChild.h"
#include "mozilla/ipc/Endpoint.h"
using namespace mozilla::dom;
mozilla::ipc::IPCResult JSValidatorChild::RecvIsOpaqueResponseAllowed(
IsOpaqueResponseAllowedResolver&& aResolver) {
mResolver.emplace(aResolver);
return IPC_OK();
}
mozilla::ipc::IPCResult JSValidatorChild::RecvOnDataAvailable(Shmem&& aData) {
MOZ_ASSERT(mResolver);
if (!mSourceBytes.Append(Span(aData.get<char>(), aData.Size<char>()),
mozilla::fallible)) {
// To prevent an attacker from flood the validation process,
// we don't validate here.
Resolve(false);
}
DeallocShmem(aData);
return IPC_OK();
}
mozilla::ipc::IPCResult JSValidatorChild::RecvOnStopRequest(
const nsresult& aReason) {
if (!mResolver) {
return IPC_OK();
}
if (NS_FAILED(aReason)) {
Resolve(false);
} else {
Resolve(ShouldAllowJS());
}
return IPC_OK();
}
void JSValidatorChild::ActorDestroy(ActorDestroyReason aReason) {
if (mResolver) {
Resolve(false);
}
};
void JSValidatorChild::Resolve(bool aAllow) {
MOZ_ASSERT(mResolver);
Maybe<Shmem> data = Nothing();
if (aAllow) {
if (!mSourceBytes.IsEmpty()) {
Shmem sharedData;
nsresult rv =
JSValidatorUtils::CopyCStringToShmem(this, mSourceBytes, sharedData);
if (NS_SUCCEEDED(rv)) {
data = Some(std::move(sharedData));
}
}
}
mResolver.ref()(
Tuple<const bool&, mozilla::Maybe<Shmem>&&>(aAllow, std::move(data)));
mResolver = Nothing();
}
bool JSValidatorChild::ShouldAllowJS() const {
// mSourceBytes could be empty when
// 1. No OnDataAvailable calls
// 2. Failed to allocate shmem
//
// TODO(sefeng): THIS IS A VERY TEMPORARY SOLUTION
return !mSourceBytes.IsEmpty()
? !StringBeginsWith(NS_ConvertUTF8toUTF16(mSourceBytes), u"{"_ns)
: true;
}

Просмотреть файл

@ -0,0 +1,39 @@
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#ifndef mozilla_dom_JSValidatorChild
#define mozilla_dom_JSValidatorChild
#include "mozilla/ProcInfo.h"
#include "mozilla/dom/PJSValidatorChild.h"
#include "mozilla/dom/JSValidatorUtils.h"
namespace mozilla::dom {
class JSValidatorChild final : public PJSValidatorChild {
public:
NS_INLINE_DECL_THREADSAFE_REFCOUNTING(JSValidatorChild, override);
mozilla::ipc::IPCResult RecvIsOpaqueResponseAllowed(
IsOpaqueResponseAllowedResolver&& aResolver);
mozilla::ipc::IPCResult RecvOnDataAvailable(Shmem&& aData);
mozilla::ipc::IPCResult RecvOnStopRequest(const nsresult& aReason);
void ActorDestroy(ActorDestroyReason aReason) override;
private:
virtual ~JSValidatorChild() = default;
void Resolve(bool aAllow);
bool ShouldAllowJS() const;
nsCString mSourceBytes;
Maybe<IsOpaqueResponseAllowedResolver> mResolver;
};
} // namespace mozilla::dom
#endif // defined(mozilla_dom_JSValidatorChild)

Просмотреть файл

@ -0,0 +1,79 @@
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "mozilla/net/OpaqueResponseUtils.h"
#include "mozilla/dom/JSValidatorParent.h"
#include "mozilla/dom/JSValidatorUtils.h"
#include "mozilla/dom/JSOracleParent.h"
#include "mozilla/RefPtr.h"
namespace mozilla::dom {
/* static */
already_AddRefed<JSValidatorParent> JSValidatorParent::Create() {
RefPtr<JSValidatorParent> validator = new JSValidatorParent();
JSOracleParent::WithJSOracle([validator](JSOracleParent* aParent) {
MOZ_ASSERT_IF(aParent, aParent->CanSend());
if (aParent) {
MOZ_ALWAYS_TRUE(aParent->SendPJSValidatorConstructor(validator));
}
});
return validator.forget();
}
void JSValidatorParent::IsOpaqueResponseAllowed(
const std::function<void(bool, Maybe<Shmem>)>& aCallback) {
JSOracleParent::WithJSOracle([=, self = RefPtr{this}](const auto* aParent) {
if (aParent) {
MOZ_DIAGNOSTIC_ASSERT(self->CanSend());
self->SendIsOpaqueResponseAllowed()->Then(
GetMainThreadSerialEventTarget(), __func__,
[aCallback](
const IsOpaqueResponseAllowedPromise::ResolveOrRejectValue&
aResult) {
if (aResult.IsResolve()) {
const Tuple<bool, Maybe<Shmem>>& result = aResult.ResolveValue();
aCallback(Get<0>(result), Get<1>(aResult.ResolveValue()));
} else {
aCallback(false, Nothing());
}
});
} else {
aCallback(false, Nothing());
}
});
}
void JSValidatorParent::OnDataAvailable(const nsACString& aData) {
JSOracleParent::WithJSOracle(
[self = RefPtr{this}, data = nsCString{aData}](const auto* aParent) {
if (!aParent) {
return;
}
if (self->CanSend()) {
Shmem sharedData;
nsresult rv =
JSValidatorUtils::CopyCStringToShmem(self, data, sharedData);
if (NS_FAILED(rv)) {
return;
}
Unused << self->SendOnDataAvailable(std::move(sharedData));
}
});
}
void JSValidatorParent::OnStopRequest(nsresult aResult) {
JSOracleParent::WithJSOracle(
[self = RefPtr{this}, aResult](const auto* aParent) {
if (!aParent) {
return;
}
if (self->CanSend()) {
Unused << self->SendOnStopRequest(aResult);
}
});
}
} // namespace mozilla::dom

Просмотреть файл

@ -0,0 +1,39 @@
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#ifndef mozilla_dom_JSValidatorParent
#define mozilla_dom_JSValidatorParent
#include "mozilla/MozPromise.h"
#include "mozilla/ProcInfo.h"
#include "mozilla/dom/PJSValidatorParent.h"
namespace mozilla::ipc {
class UtilityProcessParent;
class IProtocol;
} // namespace mozilla::ipc
namespace mozilla::dom {
class JSValidatorParent final : public PJSValidatorParent {
public:
NS_INLINE_DECL_THREADSAFE_REFCOUNTING(JSValidatorParent, override);
static already_AddRefed<JSValidatorParent> Create();
void IsOpaqueResponseAllowed(
const std::function<void(bool, Maybe<mozilla::ipc::Shmem>)>& aCallback);
void OnDataAvailable(const nsACString& aData);
void OnStopRequest(nsresult aResult);
private:
virtual ~JSValidatorParent() = default;
};
} // namespace mozilla::dom
#endif // defined(mozilla_dom_JSValidatorParent)

Просмотреть файл

@ -0,0 +1,25 @@
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "mozilla/dom/JSValidatorUtils.h"
#include "mozilla/ipc/ProtocolUtils.h"
#include "mozilla/ipc/Shmem.h"
#include "nsStringFwd.h"
using namespace mozilla::ipc;
namespace mozilla::dom {
/* static */
nsresult JSValidatorUtils::CopyCStringToShmem(IProtocol* aActor,
const nsCString& aCString,
Shmem& aMem) {
if (!aActor->AllocShmem(aCString.Length(), &aMem)) {
return NS_ERROR_FAILURE;
}
memcpy(aMem.get<char>(), aCString.BeginReading(), aCString.Length());
return NS_OK;
}
} // namespace mozilla::dom

Просмотреть файл

@ -0,0 +1,26 @@
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#ifndef mozilla_dom_JSValidatorUtils
#define mozilla_dom_JSValidatorUtils
#include "ErrorList.h"
#include "nsStringFwd.h"
namespace mozilla::ipc {
class IProtocol;
class Shmem;
} // namespace mozilla::ipc
using namespace mozilla::ipc;
namespace mozilla::dom {
class JSValidatorUtils final {
public:
static nsresult CopyCStringToShmem(IProtocol* aActor,
const nsCString& aCString, Shmem& aMem);
};
}; // namespace mozilla::dom
#endif

20
dom/ipc/PJSOracle.ipdl Normal file
Просмотреть файл

@ -0,0 +1,20 @@
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
include protocol PJSValidator;
namespace mozilla {
namespace dom {
// PJSOracle is a top-level actor which manages PJSValidator
async protocol PJSOracle {
manages PJSValidator;
child:
async PJSValidator();
};
} // namespace dom
} // namespace mozilla

25
dom/ipc/PJSValidator.ipdl Normal file
Просмотреть файл

@ -0,0 +1,25 @@
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
include protocol PJSOracle;
namespace mozilla {
namespace dom {
async protocol PJSValidator {
manager PJSOracle;
child:
async IsOpaqueResponseAllowed() returns(bool aAllowed, Shmem? aMem);
async OnDataAvailable(Shmem aData);
async OnStopRequest(nsresult aReason);
async __delete__();
};
} // namespace dom
} // namespace mozilla

Просмотреть файл

@ -58,6 +58,11 @@ EXPORTS.mozilla.dom += [
"FilePickerParent.h",
"InProcessChild.h",
"InProcessParent.h",
"JSOracleChild.h",
"JSOracleParent.h",
"JSValidatorChild.h",
"JSValidatorParent.h",
"JSValidatorUtils.h",
"LoginDetectionService.h",
"MaybeDiscarded.h",
"MemoryReportRequest.h",
@ -115,6 +120,11 @@ UNIFIED_SOURCES += [
"DocShellMessageUtils.cpp",
"FilePickerParent.cpp",
"InProcessImpl.cpp",
"JSOracleChild.cpp",
"JSOracleParent.cpp",
"JSValidatorChild.cpp",
"JSValidatorParent.cpp",
"JSValidatorUtils.cpp",
"LoginDetectionService.cpp",
"MemMapSnapshot.cpp",
"MemoryReportRequest.cpp",
@ -164,6 +174,8 @@ IPDL_SOURCES += [
"PCycleCollectWithLogs.ipdl",
"PFilePicker.ipdl",
"PInProcess.ipdl",
"PJSOracle.ipdl",
"PJSValidator.ipdl",
"PLoginReputation.ipdl",
"PProcessHangMonitor.ipdl",
"PrefsTypes.ipdlh",

Просмотреть файл

@ -8,6 +8,7 @@ include PrefsTypes;
include protocol PProfiler;
include protocol PUtilityAudioDecoder;
include protocol PJSOracle;
#if defined(MOZ_SANDBOX) && defined(MOZ_DEBUG) && defined(ENABLE_TESTS)
include protocol PSandboxTesting;
@ -82,6 +83,8 @@ child:
async StartUtilityAudioDecoderService(Endpoint<PUtilityAudioDecoderParent> aEndpoint);
async StartJSOracleService(Endpoint<PJSOracleChild> aEndpoint);
#if defined(MOZ_SANDBOX) && defined(MOZ_DEBUG) && defined(ENABLE_TESTS)
async InitSandboxTesting(Endpoint<PSandboxTestingChild> aEndpoint);
#endif

Просмотреть файл

@ -8,6 +8,7 @@
#include "mozilla/ipc/UtilityProcessManager.h"
#include "mozilla/ipc/UtilityProcessSandboxing.h"
#include "mozilla/dom/ContentParent.h"
#include "mozilla/dom/JSOracleChild.h"
#include "mozilla/dom/MemoryReportRequest.h"
#include "mozilla/ipc/CrashReporterClient.h"
#include "mozilla/ipc/Endpoint.h"
@ -237,6 +238,17 @@ UtilityProcessChild::RecvStartUtilityAudioDecoderService(
return IPC_OK();
}
mozilla::ipc::IPCResult UtilityProcessChild::RecvStartJSOracleService(
Endpoint<PJSOracleChild>&& aEndpoint) {
mJSOracleInstance = new mozilla::dom::JSOracleChild();
if (!mJSOracleInstance) {
return IPC_FAIL(this, "Failing to create JSOracleParent");
}
mJSOracleInstance->Start(std::move(aEndpoint));
return IPC_OK();
}
void UtilityProcessChild::ActorDestroy(ActorDestroyReason aWhy) {
if (AbnormalShutdown == aWhy) {
NS_WARNING("Shutting down Utility process early due to a crash!");

Просмотреть файл

@ -13,6 +13,11 @@
#include "mozilla/PRemoteDecoderManagerParent.h"
#include "mozilla/ipc/AsyncBlockers.h"
#include "mozilla/dom/JSOracleChild.h"
namespace mozilla::dom {
class PJSOracleChild;
} // namespace mozilla::dom
namespace mozilla::ipc {
@ -55,6 +60,9 @@ class UtilityProcessChild final : public PUtilityProcessChild {
mozilla::ipc::IPCResult RecvStartUtilityAudioDecoderService(
Endpoint<PUtilityAudioDecoderParent>&& aEndpoint);
mozilla::ipc::IPCResult RecvStartJSOracleService(
Endpoint<dom::PJSOracleChild>&& aEndpoint);
AsyncBlockers& AsyncShutdownService() { return mShutdownBlockers; }
void ActorDestroy(ActorDestroyReason aWhy) override;
@ -71,6 +79,7 @@ class UtilityProcessChild final : public PUtilityProcessChild {
private:
RefPtr<ChildProfilerController> mProfilerController;
RefPtr<UtilityAudioDecoderParent> mUtilityAudioDecoderInstance{};
RefPtr<dom::JSOracleChild> mJSOracleInstance{};
AsyncBlockers mShutdownBlockers;
};

Просмотреть файл

@ -5,6 +5,7 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "UtilityProcessManager.h"
#include "JSOracleParent.h"
#include "mozilla/ipc/UtilityProcessHost.h"
#include "mozilla/MemoryReportingProcess.h"
#include "mozilla/Preferences.h"
@ -350,6 +351,11 @@ UtilityProcessManager::StartProcessForRemoteMediaDecoding(
});
}
RefPtr<UtilityProcessManager::JSOraclePromise>
UtilityProcessManager::StartJSOracle(dom::JSOracleParent* aParent) {
return StartUtility(RefPtr{aParent}, SandboxingKind::GENERIC_UTILITY);
}
bool UtilityProcessManager::IsProcessLaunching(SandboxingKind aSandbox) {
MOZ_ASSERT(NS_IsMainThread());

Просмотреть файл

@ -18,6 +18,10 @@ namespace mozilla {
class MemoryReportingProcess;
namespace dom {
class JSOracleParent;
}
namespace ipc {
class UtilityProcessParent;
@ -31,6 +35,7 @@ class UtilityProcessManager final : public UtilityProcessHost::Listener {
public:
using StartRemoteDecodingUtilityPromise =
MozPromise<Endpoint<PRemoteDecoderManagerChild>, nsresult, true>;
using JSOraclePromise = GenericNonExclusivePromise;
static void Initialize();
static void Shutdown();
@ -49,6 +54,8 @@ class UtilityProcessManager final : public UtilityProcessHost::Listener {
RefPtr<StartRemoteDecodingUtilityPromise> StartProcessForRemoteMediaDecoding(
base::ProcessId aOtherProcess, SandboxingKind aSandbox);
RefPtr<JSOraclePromise> StartJSOracle(mozilla::dom::JSOracleParent* aParent);
void OnProcessUnexpectedShutdown(UtilityProcessHost* aHost);
// Returns the platform pid for this utility sandbox process.

Просмотреть файл

@ -1672,6 +1672,13 @@
#endif
mirror: always
# When this pref is enabled, the JS validator will be enabled for
# ORB.
- name: browser.opaqueResponseBlocking.javascriptValidator
type: bool
value: @IS_NIGHTLY_BUILD@
mirror: always
# 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

Просмотреть файл

@ -7,6 +7,7 @@
#include "mozilla/net/OpaqueResponseUtils.h"
#include "mozilla/dom/Document.h"
#include "mozilla/StaticPrefs_browser.h"
#include "ErrorList.h"
#include "nsContentUtils.h"
#include "nsHttpResponseHead.h"
@ -239,8 +240,6 @@ OpaqueResponseBlocker::OnStartRequest(nsIRequest* aRequest) {
Unused << EnsureOpaqueResponseIsAllowedAfterSniff(aRequest);
MOZ_ASSERT(mState != State::Sniffing);
nsresult rv = mNext->OnStartRequest(aRequest);
return NS_SUCCEEDED(mStatus) ? rv : mStatus;
}
@ -256,6 +255,13 @@ OpaqueResponseBlocker::OnStopRequest(nsIRequest* aRequest,
statusForStop = mStatus;
}
if (mState == State::Sniffing) {
MOZ_ASSERT(mJSValidator);
mPendingOnStopRequestStatus = Some(aStatusCode);
mJSValidator->OnStopRequest(aStatusCode);
return NS_OK;
}
return mNext->OnStopRequest(aRequest, statusForStop);
}
@ -265,7 +271,6 @@ OpaqueResponseBlocker::OnDataAvailable(nsIRequest* aRequest,
uint64_t aOffset, uint32_t aCount) {
LOGORB();
MOZ_ASSERT(mState == State::Allowed || mState == State::Blocked);
if (mState == State::Allowed) {
return mNext->OnDataAvailable(aRequest, aInputStream, aOffset, aCount);
}
@ -274,6 +279,23 @@ OpaqueResponseBlocker::OnDataAvailable(nsIRequest* aRequest,
return NS_ERROR_FAILURE;
}
MOZ_ASSERT(mState == State::Sniffing);
nsCString data;
if (!data.SetLength(aCount, fallible)) {
return NS_ERROR_OUT_OF_MEMORY;
}
uint32_t read;
nsresult rv = aInputStream->Read(data.BeginWriting(), aCount, &read);
if (NS_FAILED(rv)) {
return rv;
}
MOZ_ASSERT(mJSValidator);
mJSValidator->OnDataAvailable(data);
return NS_OK;
}
@ -282,7 +304,11 @@ nsresult OpaqueResponseBlocker::EnsureOpaqueResponseIsAllowedAfterSniff(
nsCOMPtr<HttpBaseChannel> httpBaseChannel = do_QueryInterface(aRequest);
MOZ_ASSERT(httpBaseChannel);
if (mState != State::Sniffing) {
// The `AfterSniff` check shouldn't be run when
// 1. We have made a decision already
// 2. The JS validator is running, so we should wait
// for its result.
if (mState != State::Sniffing || mJSValidator) {
return NS_OK;
}
@ -317,7 +343,8 @@ nsresult OpaqueResponseBlocker::EnsureOpaqueResponseIsAllowedAfterSniff(
break;
}
return ValidateJavaScript(httpBaseChannel);
MOZ_ASSERT(mState == State::Sniffing);
return ValidateJavaScript(httpBaseChannel, uri, loadInfo);
}
// The specification for ORB is currently being written:
@ -327,8 +354,18 @@ nsresult OpaqueResponseBlocker::EnsureOpaqueResponseIsAllowedAfterSniff(
// * `nsHttpChannel::DisableIsOpaqueResponseAllowedAfterSniffCheck`
// * `HttpBaseChannel::OpaqueResponseSafelistCheckAfterSniff`
// * `OpaqueResponseBlocker::ValidateJavaScript`
nsresult OpaqueResponseBlocker::ValidateJavaScript(HttpBaseChannel* aChannel) {
nsresult OpaqueResponseBlocker::ValidateJavaScript(HttpBaseChannel* aChannel,
nsIURI* aURI,
nsILoadInfo* aLoadInfo) {
MOZ_DIAGNOSTIC_ASSERT(aChannel);
MOZ_ASSERT(aURI && aLoadInfo);
if (!StaticPrefs::browser_opaqueResponseBlocking_javascriptValidator()) {
LOGORB("Allowed: JS Validator is disabled");
AllowResponse();
return NS_OK;
}
int64_t contentLength;
nsresult rv = aChannel->GetContentLength(&contentLength);
if (NS_FAILED(rv)) {
@ -337,13 +374,31 @@ nsresult OpaqueResponseBlocker::ValidateJavaScript(HttpBaseChannel* aChannel) {
return rv;
}
// XXX(farre): this intentionally allowing responses, because we don't know
// how to block the correct ones until bug 1532644 is completed.
LOGORB("Allowed (After Sniff): passes all checks");
AllowResponse();
LOGORB("Send %s to the validator", aURI->GetSpecOrDefault().get());
// https://whatpr.org/fetch/1442.html#orb-algorithm, step 15
// XXX(farre): Start JavaScript validation.
mJSValidator = dom::JSValidatorParent::Create();
mJSValidator->IsOpaqueResponseAllowed(
[self = RefPtr{this}, channel = nsCOMPtr{aChannel}, uri = nsCOMPtr{aURI},
loadInfo = nsCOMPtr{aLoadInfo}](bool aAllowed,
Maybe<ipc::Shmem> aSharedData) {
MOZ_LOG(gORBLog, LogLevel::Debug,
("JSValidator resolved for %s with %s",
uri->GetSpecOrDefault().get(),
aSharedData.isSome() ? "true" : "false"));
if (aAllowed) {
self->AllowResponse();
} else {
self->BlockResponse(channel, NS_ERROR_FAILURE);
LogORBError(loadInfo, uri);
}
self->ResolveAndProcessData(channel, aAllowed, aSharedData);
if (aSharedData.isSome()) {
self->mJSValidator->DeallocShmem(aSharedData.ref());
}
Unused << dom::PJSValidatorParent::Send__delete__(self->mJSValidator);
self->mJSValidator = nullptr;
});
return NS_OK;
}
@ -354,12 +409,14 @@ bool OpaqueResponseBlocker::IsSniffing() const {
void OpaqueResponseBlocker::AllowResponse() {
LOGORB("Sniffer is done, allow response, this=%p", this);
MOZ_ASSERT(mState == State::Sniffing);
mState = State::Allowed;
}
void OpaqueResponseBlocker::BlockResponse(HttpBaseChannel* aChannel,
nsresult aReason) {
LOGORB("Sniffer is done, block response, this=%p", this);
MOZ_ASSERT(mState == State::Sniffing);
mState = State::Blocked;
mStatus = aReason;
aChannel->SetChannelBlockedByOpaqueResponse();
@ -367,6 +424,48 @@ void OpaqueResponseBlocker::BlockResponse(HttpBaseChannel* aChannel,
"OpaqueResponseBlocker::BlockResponse"_ns);
}
void OpaqueResponseBlocker::ResolveAndProcessData(
HttpBaseChannel* aChannel, bool aAllowed, Maybe<ipc::Shmem>& aSharedData) {
if (!aAllowed) {
MOZ_ASSERT(mState == State::Blocked);
MaybeRunOnStopRequest(aChannel);
return;
}
MOZ_ASSERT(mState == State::Allowed);
if (aSharedData.isNothing()) {
MaybeRunOnStopRequest(aChannel);
return;
}
const ipc::Shmem& mem = aSharedData.ref();
nsCOMPtr<nsIInputStream> input;
nsresult rv = NS_NewByteInputStream(getter_AddRefs(input),
Span(mem.get<char>(), mem.Size<char>()),
NS_ASSIGNMENT_DEPEND);
if (NS_WARN_IF(NS_FAILED(rv))) {
BlockResponse(aChannel, rv);
MaybeRunOnStopRequest(aChannel);
return;
}
// When this line reaches, the state is either State::Allowed or
// State::Blocked. The OnDataAvailable call will either call
// the next listener or reject the request.
OnDataAvailable(aChannel, input, 0, mem.Size<char>());
MaybeRunOnStopRequest(aChannel);
}
void OpaqueResponseBlocker::MaybeRunOnStopRequest(HttpBaseChannel* aChannel) {
MOZ_ASSERT(mState != State::Sniffing);
if (mPendingOnStopRequestStatus.isSome()) {
OnStopRequest(aChannel, mPendingOnStopRequestStatus.value());
}
}
NS_IMPL_ISUPPORTS(OpaqueResponseBlocker, nsIStreamListener, nsIRequestObserver)
} // namespace mozilla::net

Просмотреть файл

@ -8,6 +8,7 @@
#ifndef mozilla_net_OpaqueResponseUtils_h
#define mozilla_net_OpaqueResponseUtils_h
#include "mozilla/dom/JSValidatorParent.h"
#include "nsIContentPolicy.h"
#include "nsIStreamListener.h"
#include "nsUnknownDecoder.h"
@ -72,7 +73,14 @@ class OpaqueResponseBlocker final : public nsIStreamListener {
private:
virtual ~OpaqueResponseBlocker() = default;
nsresult ValidateJavaScript(HttpBaseChannel* aChannel);
nsresult ValidateJavaScript(HttpBaseChannel* aChannel, nsIURI* aURI,
nsILoadInfo* aLoadInfo);
void ResolveAndProcessData(HttpBaseChannel* aChannel,
bool aAllowed,
Maybe<ipc::Shmem>& aSharedData);
void MaybeRunOnStopRequest(HttpBaseChannel* aChannel);
nsCOMPtr<nsIStreamListener> mNext;
@ -81,6 +89,10 @@ class OpaqueResponseBlocker final : public nsIStreamListener {
State mState = State::Sniffing;
nsresult mStatus = NS_OK;
RefPtr<dom::JSValidatorParent> mJSValidator;
Maybe<nsresult> mPendingOnStopRequestStatus{Nothing()};
};
class nsCompressedAudioVideoImageDetector : public nsUnknownDecoder {

Просмотреть файл

@ -1601,7 +1601,6 @@ nsresult nsHttpChannel::CallOnStartRequest() {
} else if (opaqueResponse == OpaqueResponse::Sniff) {
MOZ_DIAGNOSTIC_ASSERT(mORB);
nsresult rv = mORB->EnsureOpaqueResponseIsAllowedAfterSniff(this);
MOZ_DIAGNOSTIC_ASSERT(!mORB->IsSniffing());
if (NS_FAILED(rv)) {
return rv;

Просмотреть файл

@ -1,31 +1,13 @@
[script-resource-with-json-parser-breaker.tentative.sub.html]
prefs: [browser.opaqueResponseBlocking:true]
prefs: [browser.opaqueResponseBlocking:true, browser.opaqueResponseBlocking.javascriptValidator:true]
expected:
if (os == "android") and fission: [OK, TIMEOUT]
[CORB-blocks 'text/json' that starts with the following JSON parser breaker: {} &&]
expected: FAIL
[CORB-blocks 'text/xml' that starts with the following JSON parser breaker: {} &&]
expected: FAIL
[CORB-blocks 'text/html' that starts with the following JSON parser breaker: )\]}']
expected: FAIL
[CORB-blocks 'text/json' that starts with the following JSON parser breaker: {}&&]
expected: FAIL
[CORB-blocks 'application/javascript' that starts with the following JSON parser breaker: )\]}']
expected: FAIL
[CORB-blocks 'text/html' that starts with the following JSON parser breaker: {} &&]
expected: FAIL
[CORB-blocks 'text/xml' that starts with the following JSON parser breaker: {}&&]
expected: FAIL
[CORB-blocks 'text/html' that starts with the following JSON parser breaker: {}&&]
expected: FAIL
[CORB-blocks 'text/xml' that starts with the following JSON parser breaker: )\]}']
expected: FAIL
@ -38,11 +20,5 @@
[CORB-blocks 'application/javascript' that starts with the following JSON parser breaker: {}&&]
expected: FAIL
[CORB-blocks 'text/plain' that starts with the following JSON parser breaker: {}&&]
expected: FAIL
[CORB-blocks 'text/plain' that starts with the following JSON parser breaker: {} &&]
expected: FAIL
[CORB-blocks 'application/javascript' that starts with the following JSON parser breaker: {} &&]
expected: FAIL

Просмотреть файл

@ -1,24 +1,15 @@
[known-mime-type.sub.any.worker.html]
expected:
if (os == "android") and fission: [OK, TIMEOUT]
prefs: [browser.opaqueResponseBlocking.javascriptValidator:true]
[ORB should block opaque font/ttf]
expected: FAIL
[ORB should block opaque text/plain]
expected: FAIL
[ORB should block opaque application/json]
expected: FAIL
[known-mime-type.sub.any.html]
expected:
if (os == "android") and fission: [OK, TIMEOUT]
prefs: [browser.opaqueResponseBlocking.javascriptValidator:true]
[ORB should block opaque font/ttf]
expected: FAIL
[ORB should block opaque text/plain]
expected: FAIL
[ORB should block opaque application/json]
expected: FAIL

Просмотреть файл

@ -74,6 +74,7 @@ enum class UtilityActorName {
AudioDecoder_AppleMedia,
AudioDecoder_WMF,
MfMediaEngineCDM,
JSOracle,
};
// String that will be used e.g. to annotate crash reports

Просмотреть файл

@ -53,6 +53,8 @@ nsCString GetUtilityActorName(const UtilityActorName aActorName) {
return "audio-decoder-wmf"_ns;
case UtilityActorName::MfMediaEngineCDM:
return "mf-media-engine"_ns;
case UtilityActorName::JSOracle:
return "js-oracle"_ns;
}
return "unknown"_ns;
}