diff --git a/dom/ipc/JSOracleChild.cpp b/dom/ipc/JSOracleChild.cpp index 216d23178016..abfd0d1b002e 100644 --- a/dom/ipc/JSOracleChild.cpp +++ b/dom/ipc/JSOracleChild.cpp @@ -15,6 +15,28 @@ using namespace mozilla::dom; static mozilla::StaticRefPtr sOracleSingletonChild; +static mozilla::StaticAutoPtr 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 JSOracleChild::AllocPJSValidatorChild() { void JSOracleChild::Start(Endpoint&& aEndpoint) { DebugOnly ok = std::move(aEndpoint).Bind(this); + JSContextHolder::MaybeInit(); MOZ_ASSERT(ok); } diff --git a/dom/ipc/JSOracleChild.h b/dom/ipc/JSOracleChild.h index 8743730556eb..1663f6a5f247 100644 --- a/dom/ipc/JSOracleChild.h +++ b/dom/ipc/JSOracleChild.h @@ -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 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 mGlobal; +}; class PJSValidatorChild; @@ -24,6 +74,9 @@ class JSOracleChild final : public PJSOracleChild { void Start(Endpoint&& aEndpoint); + static JSContext* JSContext(); + static JSObject* JSObject(); + private: ~JSOracleChild() = default; diff --git a/dom/ipc/JSValidatorChild.cpp b/dom/ipc/JSValidatorChild.cpp index f84f8a107143..efff4619f08b 100644 --- a/dom/ipc/JSValidatorChild.cpp +++ b/dom/ipc/JSValidatorChild.cpp @@ -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 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 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; } diff --git a/dom/ipc/JSValidatorParent.cpp b/dom/ipc/JSValidatorParent.cpp index a7f98cecaa4b..e46b4b11c3fe 100644 --- a/dom/ipc/JSValidatorParent.cpp +++ b/dom/ipc/JSValidatorParent.cpp @@ -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); } }); diff --git a/ipc/glue/UtilityProcessChild.cpp b/ipc/glue/UtilityProcessChild.cpp index 22e5e4275ee7..78ddf2e57877 100644 --- a/ipc/glue/UtilityProcessChild.cpp +++ b/ipc/glue/UtilityProcessChild.cpp @@ -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); diff --git a/js/src/gc/Memory.cpp b/js/src/gc/Memory.cpp index d0cf152e9aca..8463306f89dd 100644 --- a/js/src/gc/Memory.cpp +++ b/js/src/gc/Memory.cpp @@ -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 } diff --git a/testing/web-platform/meta/fetch/orb/tentative/__dir__.ini b/testing/web-platform/meta/fetch/orb/tentative/__dir__.ini index 0d1a3a30113d..903a93027f4b 100644 --- a/testing/web-platform/meta/fetch/orb/tentative/__dir__.ini +++ b/testing/web-platform/meta/fetch/orb/tentative/__dir__.ini @@ -1 +1 @@ -prefs: [browser.opaqueResponseBlocking:true] +prefs: [browser.opaqueResponseBlocking:true, browser.opaqueResponseBlocking.javascriptValidator:true] diff --git a/testing/web-platform/meta/fetch/orb/tentative/known-mime-type.sub.any.js.ini b/testing/web-platform/meta/fetch/orb/tentative/known-mime-type.sub.any.js.ini deleted file mode 100644 index 09fe054276ea..000000000000 --- a/testing/web-platform/meta/fetch/orb/tentative/known-mime-type.sub.any.js.ini +++ /dev/null @@ -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] diff --git a/testing/web-platform/tests/fetch/orb/resources/empty.json b/testing/web-platform/tests/fetch/orb/resources/empty.json new file mode 100644 index 000000000000..0967ef424bce --- /dev/null +++ b/testing/web-platform/tests/fetch/orb/resources/empty.json @@ -0,0 +1 @@ +{} diff --git a/testing/web-platform/tests/fetch/orb/tentative/known-mime-type.sub.any.js b/testing/web-platform/tests/fetch/orb/tentative/known-mime-type.sub.any.js index a7bb66305834..d0954c835559 100644 --- a/testing/web-platform/tests/fetch/orb/tentative/known-mime-type.sub.any.js +++ b/testing/web-platform/tests/fetch/orb/tentative/known-mime-type.sub.any.js @@ -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 () => {