Bug 1809518 - Use stencil parsing to do the Javascript check for ORB in Utility Process r=farre,smaug,tcampbell

This patch allows JS Validator to parse the incoming data into
stencil to verify if its a Javascript file.

Differential Revision: https://phabricator.services.mozilla.com/D166484
This commit is contained in:
Sean Feng 2023-02-22 14:07:46 +00:00
Родитель 709890985d
Коммит 561a15fdc2
10 изменённых файлов: 152 добавлений и 41 удалений

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

@ -15,6 +15,28 @@ using namespace mozilla::dom;
static mozilla::StaticRefPtr<JSOracleChild> sOracleSingletonChild;
static mozilla::StaticAutoPtr<JSContextHolder> sJSContextHolder;
/* static */
void JSContextHolder::MaybeInit() {
if (!sJSContextHolder) {
sJSContextHolder = new JSContextHolder();
ClearOnShutdown(&sJSContextHolder);
}
}
/* static */
JSContext* JSOracleChild::JSContext() {
MOZ_ASSERT(sJSContextHolder);
return sJSContextHolder->mCx;
}
/* static */
JSObject* JSOracleChild::JSObject() {
MOZ_ASSERT(sJSContextHolder);
return sJSContextHolder->mGlobal;
}
JSOracleChild* JSOracleChild::GetSingleton() {
MOZ_ASSERT(NS_IsMainThread());
if (!sOracleSingletonChild) {
@ -30,5 +52,6 @@ already_AddRefed<PJSValidatorChild> JSOracleChild::AllocPJSValidatorChild() {
void JSOracleChild::Start(Endpoint<PJSOracleChild>&& aEndpoint) {
DebugOnly<bool> ok = std::move(aEndpoint).Bind(this);
JSContextHolder::MaybeInit();
MOZ_ASSERT(ok);
}

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

@ -9,10 +9,60 @@
#include "mozilla/dom/PJSOracleChild.h"
#include "js/CharacterEncoding.h"
#include "js/HeapAPI.h"
#include "js/Initialization.h"
#include "jsapi.h"
#include "js/CompilationAndEvaluation.h"
#include "js/Context.h"
namespace mozilla::ipc {
class UtilityProcessParent;
}
namespace mozilla::dom {
struct JSContextHolder {
JSContextHolder() {
MOZ_RELEASE_ASSERT(JS_IsInitialized(),
"UtilityProcessChild::Init should have JS initialized");
mCx = JS_NewContext(JS::DefaultHeapMaxBytes);
if (!mCx) {
MOZ_CRASH("Failed to create JS Context");
return;
}
if (!JS::InitSelfHostedCode(mCx)) {
MOZ_CRASH("Failed to initialize the runtime's self-hosted code");
return;
}
static JSClass jsValidatorGlobalClass = {
"JSValidatorGlobal", JSCLASS_GLOBAL_FLAGS, &JS::DefaultGlobalClassOps};
JS::Rooted<JSObject*> global(
mCx, JS_NewGlobalObject(mCx, &jsValidatorGlobalClass, nullptr,
JS::FireOnNewGlobalHook, JS::RealmOptions()));
if (!global) {
MOZ_CRASH("Failed to create the global");
return;
}
mGlobal.init(mCx, global);
}
~JSContextHolder() {
if (mCx) {
JS_DestroyContext(mCx);
}
}
static void MaybeInit();
JSContext* mCx;
JS::PersistentRooted<JSObject*> mGlobal;
};
class PJSValidatorChild;
@ -24,6 +74,9 @@ class JSOracleChild final : public PJSOracleChild {
void Start(Endpoint<PJSOracleChild>&& aEndpoint);
static JSContext* JSContext();
static JSObject* JSObject();
private:
~JSOracleChild() = default;

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

@ -5,8 +5,18 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "mozilla/dom/JSValidatorChild.h"
#include "js/JSON.h"
#include "mozilla/dom/JSOracleChild.h"
#include "mozilla/ipc/Endpoint.h"
#include "js/experimental/JSStencil.h"
#include "js/SourceText.h"
#include "js/Exception.h"
#include "js/GlobalObject.h"
#include "js/CompileOptions.h"
#include "js/RealmOptions.h"
using namespace mozilla::dom;
mozilla::ipc::IPCResult JSValidatorChild::RecvIsOpaqueResponseAllowed(
@ -72,16 +82,42 @@ void JSValidatorChild::Resolve(ValidatorResult aResult) {
}
JSValidatorChild::ValidatorResult JSValidatorChild::ShouldAllowJS() const {
// mSourceBytes could be empty when
// 1. No OnDataAvailable calls
// 2. Failed to allocate shmem
// The empty document parses as JavaScript, so for clarity we have a condition
// separately for that.
if (mSourceBytes.IsEmpty()) {
return ValidatorResult::JavaScript;
}
JSContext* cx = JSOracleChild::JSContext();
if (!cx) {
return ValidatorResult::Failure;
}
JSObject* global = JSOracleChild::JSObject();
if (!global) {
return ValidatorResult::Failure;
}
JS::SourceText<Utf8Unit> srcBuf;
if (!srcBuf.init(cx, mSourceBytes.BeginReading(), mSourceBytes.Length(),
JS::SourceOwnership::Borrowed)) {
JS_ClearPendingException(cx);
return ValidatorResult::Failure;
}
JSAutoRealm ar(cx, global);
// Parse to JavaScript
RefPtr<JS::Stencil> stencil =
CompileGlobalScriptToStencil(cx, JS::CompileOptions(cx), srcBuf);
if (!stencil) {
JS_ClearPendingException(cx);
return ValidatorResult::Other;
}
// The stencil parsing lacks the JSON support, so we have to do the
// JSON parsing separately.
if (StringBeginsWith(NS_ConvertUTF8toUTF16(mSourceBytes), u"{"_ns)) {
return ValidatorResult::JSON;
}

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

@ -39,6 +39,9 @@ void JSValidatorParent::IsOpaqueResponseAllowed(
Tie(data, result) = aResult.ResolveValue();
aCallback(std::move(data), result);
} else {
// For cases like the Utility Process crashes, the promise will be
// rejected due to sending failures, and we'll block the request
// since we can't validate it.
aCallback(Nothing(), ValidatorResult::Failure);
}
});

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

@ -110,6 +110,15 @@ bool UtilityProcessChild::Init(mozilla::ipc::UntypedEndpoint&& aEndpoint,
mSandbox = (SandboxingKind)aSandboxingKind;
// At the moment, only ORB uses JSContext in the
// Utility Process and ORB uses GENERIC_UTILITY
if (mSandbox == SandboxingKind::GENERIC_UTILITY) {
JS::DisableJitBackend();
if (!JS_Init()) {
return false;
}
}
profiler_set_process_name(nsCString("Utility Process"));
// Notify the parent process that we have finished our init and that it can
@ -117,9 +126,12 @@ bool UtilityProcessChild::Init(mozilla::ipc::UntypedEndpoint&& aEndpoint,
SendInitCompleted();
RunOnShutdown(
[] {
[sandboxKind = mSandbox] {
StaticMutexAutoLock lock(sUtilityProcessChildMutex);
sUtilityProcessChild = nullptr;
if (sandboxKind == SandboxingKind::GENERIC_UTILITY) {
JS_ShutDown();
}
},
ShutdownPhase::XPCOMShutdownFinal);

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

@ -11,6 +11,7 @@
#include "mozilla/RandomNum.h"
#include "mozilla/TaggedAnonymousMemory.h"
#include "jit/JitOptions.h"
#include "js/HeapAPI.h"
#include "js/Utility.h"
#include "util/Memory.h"
@ -402,10 +403,12 @@ void InitMemorySubsystem() {
numAddressBits = 32;
#endif
#ifdef RLIMIT_AS
rlimit as_limit;
if (getrlimit(RLIMIT_AS, &as_limit) == 0 &&
as_limit.rlim_max != RLIM_INFINITY) {
virtualMemoryLimit = as_limit.rlim_max;
if (jit::HasJitBackend()) {
rlimit as_limit;
if (getrlimit(RLIMIT_AS, &as_limit) == 0 &&
as_limit.rlim_max != RLIM_INFINITY) {
virtualMemoryLimit = as_limit.rlim_max;
}
}
#endif
}

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

@ -1 +1 @@
prefs: [browser.opaqueResponseBlocking:true]
prefs: [browser.opaqueResponseBlocking:true, browser.opaqueResponseBlocking.javascriptValidator:true]

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

@ -1,30 +0,0 @@
[known-mime-type.sub.any.worker.html]
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:
if not debug and (os == "mac"): [PASS, FAIL]
if not debug and (os == "linux"): [PASS, FAIL]
if not debug and (os == "android"): [PASS, FAIL]
[known-mime-type.sub.any.html]
prefs: [browser.opaqueResponseBlocking.javascriptValidator:true]
expected:
if (os == "android") and fission: TIMEOUT
[ORB should block opaque font/ttf]
expected: FAIL
[ORB should block opaque text/plain]
expected: FAIL
[ORB should block opaque application/json]
expected:
if not debug and (os == "linux"): [PASS, FAIL]
if not debug and (os == "android"): [PASS, FAIL]
if not debug and (os == "mac"): [PASS, FAIL]

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

@ -0,0 +1 @@
{}

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

@ -29,7 +29,17 @@ promise_test(
TypeError,
fetchORB(`${path}/data.json`, null, contentType("application/json"))
),
"ORB should block opaque application/json"
"ORB should block opaque application/json (non-empty)"
);
promise_test(
t =>
promise_rejects_js(
t,
TypeError,
fetchORB(`${path}/empty.json`, null, contentType("application/json"))
),
"ORB should block opaque application/json (empty)"
);
promise_test(async () => {