From 426e10f0e23a980fa86a315abd5ad46d376f8450 Mon Sep 17 00:00:00 2001 From: Alexandre Lissy Date: Thu, 24 Jun 2021 06:51:24 +0000 Subject: [PATCH] Bug 1652156 - Add RDD Process Testing to the Sandbox Testing Framework. r=handyman,mattwoodrow Differential Revision: https://phabricator.services.mozilla.com/D116894 --- dom/media/ipc/PRDD.ipdl | 8 ++ dom/media/ipc/RDDParent.cpp | 15 +++ dom/media/ipc/RDDParent.h | 4 + dom/media/ipc/moz.build | 5 +- security/sandbox/common/test/SandboxTest.cpp | 124 +++++++++++------- security/sandbox/common/test/SandboxTest.h | 7 + .../common/test/SandboxTestingChild.cpp | 18 +++ .../sandbox/common/test/SandboxTestingChild.h | 12 ++ .../common/test/SandboxTestingChildTests.h | 37 ++++-- security/sandbox/test/browser_sandbox_test.js | 2 +- 10 files changed, 173 insertions(+), 59 deletions(-) diff --git a/dom/media/ipc/PRDD.ipdl b/dom/media/ipc/PRDD.ipdl index ca513bdb7fad..2b763ea78706 100644 --- a/dom/media/ipc/PRDD.ipdl +++ b/dom/media/ipc/PRDD.ipdl @@ -11,6 +11,10 @@ include protocol PProfiler; include protocol PRemoteDecoderManager; include protocol PVideoBridge; +#if defined(MOZ_SANDBOX) && defined(MOZ_DEBUG) && defined(ENABLE_TESTS) +include protocol PSandboxTesting; +#endif + using mozilla::dom::NativeThreadId from "mozilla/dom/NativeThreadId.h"; [MoveOnly] using mozilla::UntrustedModulesData from "mozilla/UntrustedModulesData.h"; [MoveOnly] using mozilla::ModulePaths from "mozilla/UntrustedModulesData.h"; @@ -51,6 +55,10 @@ parent: async GetUntrustedModulesData() returns (UntrustedModulesData? data); +#if defined(MOZ_SANDBOX) && defined(MOZ_DEBUG) && defined(ENABLE_TESTS) + async InitSandboxTesting(Endpoint aEndpoint); +#endif + child: async InitCrashReporter(NativeThreadId threadId); diff --git a/dom/media/ipc/RDDParent.cpp b/dom/media/ipc/RDDParent.cpp index 5be2f4024168..9a79f2ad1280 100644 --- a/dom/media/ipc/RDDParent.cpp +++ b/dom/media/ipc/RDDParent.cpp @@ -50,6 +50,10 @@ #include "nsDebugImpl.h" #include "nsThreadManager.h" +#if defined(MOZ_SANDBOX) && defined(MOZ_DEBUG) && defined(ENABLE_TESTS) +# include "mozilla/SandboxTestingChild.h" +#endif + namespace mozilla { using namespace ipc; @@ -253,6 +257,17 @@ mozilla::ipc::IPCResult RDDParent::RecvPreferenceUpdate(const Pref& aPref) { return IPC_OK(); } +#if defined(MOZ_SANDBOX) && defined(MOZ_DEBUG) && defined(ENABLE_TESTS) +mozilla::ipc::IPCResult RDDParent::RecvInitSandboxTesting( + Endpoint&& aEndpoint) { + if (!SandboxTestingChild::Initialize(std::move(aEndpoint))) { + return IPC_FAIL( + this, "InitSandboxTesting failed to initialise the child process."); + } + return IPC_OK(); +} +#endif + void RDDParent::ActorDestroy(ActorDestroyReason aWhy) { if (AbnormalShutdown == aWhy) { NS_WARNING("Shutting down RDD process early due to a crash!"); diff --git a/dom/media/ipc/RDDParent.h b/dom/media/ipc/RDDParent.h index 975e5c6a7d82..7bb94d5ad76b 100644 --- a/dom/media/ipc/RDDParent.h +++ b/dom/media/ipc/RDDParent.h @@ -49,6 +49,10 @@ class RDDParent final : public PRDDParent { mozilla::ipc::IPCResult RecvPreferenceUpdate(const Pref& pref); mozilla::ipc::IPCResult RecvUpdateVar(const GfxVarUpdate& pref); +#if defined(MOZ_SANDBOX) && defined(MOZ_DEBUG) && defined(ENABLE_TESTS) + mozilla::ipc::IPCResult RecvInitSandboxTesting( + Endpoint&& aEndpoint); +#endif void ActorDestroy(ActorDestroyReason aWhy) override; private: diff --git a/dom/media/ipc/moz.build b/dom/media/ipc/moz.build index 031cebc41d86..bb783852eb45 100644 --- a/dom/media/ipc/moz.build +++ b/dom/media/ipc/moz.build @@ -7,11 +7,14 @@ IPDL_SOURCES += [ "PMediaDecoderParams.ipdlh", - "PRDD.ipdl", "PRemoteDecoder.ipdl", "PRemoteDecoderManager.ipdl", ] +PREPROCESSED_IPDL_SOURCES += [ + "PRDD.ipdl", +] + EXPORTS.mozilla += [ "RDDChild.h", "RDDParent.h", diff --git a/security/sandbox/common/test/SandboxTest.cpp b/security/sandbox/common/test/SandboxTest.cpp index 5353ed5f96b2..88272f27e802 100644 --- a/security/sandbox/common/test/SandboxTest.cpp +++ b/security/sandbox/common/test/SandboxTest.cpp @@ -14,6 +14,8 @@ #include "mozilla/gfx/GPUProcessManager.h" #include "mozilla/gfx/GPUChild.h" #include "mozilla/net/SocketProcessParent.h" +#include "mozilla/RDDProcessManager.h" +#include "mozilla/RDDChild.h" #include "mozilla/ipc/Endpoint.h" #include "nsIOService.h" @@ -25,6 +27,18 @@ namespace mozilla { NS_IMPL_ISUPPORTS(SandboxTest, mozISandboxTest) +inline void UnsetEnvVariable(const nsCString& aEnvVarName) { + nsCString aEnvVarNameFull = aEnvVarName + "="_ns; + int rv_unset = +#ifdef XP_UNIX + unsetenv(aEnvVarName.get()); +#endif // XP_UNIX +#ifdef XP_WIN + _putenv(aEnvVarNameFull.get()); +#endif // XP_WIN + MOZ_ASSERT(rv_unset == 0, "Error unsetting env var"); +} + GeckoProcessType GeckoProcessStringToType(const nsCString& aString) { for (GeckoProcessType type = GeckoProcessType(0); type < GeckoProcessType::GeckoProcessType_End; @@ -61,62 +75,77 @@ SandboxTest::StartTests(const nsTArray& aProcessesList) { return NS_ERROR_ILLEGAL_VALUE; } + RefPtr mProcessPromise = + MakeRefPtr(__func__); + switch (type) { case GeckoProcessType_Content: { nsTArray parents; ContentParent::GetAll(parents); - MOZ_ASSERT(parents.Length() > 0); - mSandboxTestingParents[type] = - InitializeSandboxTestingActors(parents[0]); + if (parents[0]) { + mProcessPromise->Resolve( + std::move(InitializeSandboxTestingActors(parents[0])), __func__); + } else { + mProcessPromise->Reject(NS_ERROR_FAILURE, __func__); + MOZ_ASSERT_UNREACHABLE("SandboxTest; failure to get Content process"); + } break; } case GeckoProcessType_GPU: { gfx::GPUProcessManager* gpuProc = gfx::GPUProcessManager::Get(); gfx::GPUChild* gpuChild = gpuProc ? gpuProc->GetGPUChild() : nullptr; - if (!gpuChild) { - // There is no GPU process for this OS. Report test done. - nsCOMPtr observerService = - mozilla::services::GetObserverService(); - MOZ_RELEASE_ASSERT(observerService); - observerService->NotifyObservers(nullptr, "sandbox-test-done", 0); - return NS_OK; + if (gpuChild) { + mProcessPromise->Resolve( + std::move(InitializeSandboxTestingActors(gpuChild)), __func__); + } else { + mProcessPromise->Reject(NS_OK, __func__); } + break; + } - mSandboxTestingParents[type] = InitializeSandboxTestingActors(gpuChild); + case GeckoProcessType_RDD: { + RDDProcessManager* rddProc = RDDProcessManager::Get(); + rddProc->LaunchRDDProcess()->Then( + GetMainThreadSerialEventTarget(), __func__, + [mProcessPromise, rddProc]() { + RDDChild* rddChild = rddProc ? rddProc->GetRDDChild() : nullptr; + if (rddChild) { + return mProcessPromise->Resolve( + std::move(InitializeSandboxTestingActors(rddChild)), + __func__); + } + return mProcessPromise->Reject(NS_ERROR_FAILURE, __func__); + }, + [mProcessPromise](nsresult aError) { + MOZ_ASSERT_UNREACHABLE("SandboxTest; failure to get RDD process"); + return mProcessPromise->Reject(aError, __func__); + }); break; } case GeckoProcessType_Socket: { // mochitest harness force this variable, but we actually do not want // that - int rv_unset = -#ifdef XP_UNIX - unsetenv("MOZ_DISABLE_SOCKET_PROCESS"); -#endif // XP_UNIX -#ifdef XP_WIN - _putenv("MOZ_DISABLE_SOCKET_PROCESS="); -#endif // XP_WIN - MOZ_ASSERT(rv_unset == 0, "Error unsetting env var"); + UnsetEnvVariable("MOZ_DISABLE_SOCKET_PROCESS"_ns); nsresult rv_pref = Preferences::SetBool("network.process.enabled", true); MOZ_ASSERT(rv_pref == NS_OK, "Error enforcing pref"); MOZ_ASSERT(net::gIOService, "No gIOService?"); - RefPtr self = this; - net::gIOService->CallOrWaitForSocketProcess([self, type]() { + + net::gIOService->CallOrWaitForSocketProcess([mProcessPromise]() { // If socket process was previously disabled by env, // nsIOService code will take some time before it creates the new // process and it triggers this callback - // - // If that happens, then by the time we reach the end of StartTests() - // the mSandboxTestingParents[type] might not yet have been updated - // this is why below we allow for a delayed dispatch to give a chance net::SocketProcessParent* parent = net::SocketProcessParent::GetSingleton(); - self->mSandboxTestingParents[type] = - InitializeSandboxTestingActors(parent); + if (parent) { + return mProcessPromise->Resolve( + std::move(InitializeSandboxTestingActors(parent)), __func__); + } + return mProcessPromise->Reject(NS_ERROR_FAILURE, __func__); }); break; } @@ -127,26 +156,27 @@ SandboxTest::StartTests(const nsTArray& aProcessesList) { return NS_ERROR_ILLEGAL_VALUE; } - if (!mSandboxTestingParents[type]) { - if (type == GeckoProcessType_Socket) { - // Give a chance to the socket process to be present and to the callback - // above to be ran. We delay by 5 seconds, but hopefully since it is all - // on the main thread, the value itself should not be important. - RefPtr self = this; - NS_DelayedDispatchToCurrentThread( - NS_NewRunnableFunction( - "SandboxTest::StartTests", - [self, type]() { - if (!self->mSandboxTestingParents[type]) { - MOZ_ASSERT_UNREACHABLE( - "SandboxTest failed to get a Parent"); - } - }), - 5e3 /* delay by five seconds */); - } else { - return NS_ERROR_FAILURE; - } - } + RefPtr self = this; + RefPtr aPromise(mProcessPromise); + aPromise->Then( + GetMainThreadSerialEventTarget(), __func__, + [self, type](SandboxTestingParent* aValue) { + self->mSandboxTestingParents[type] = std::move(aValue); + return NS_OK; + }, + [](nsresult aError) { + if (aError == NS_OK) { + // There is no such process for this OS. Report test done. + nsCOMPtr observerService = + mozilla::services::GetObserverService(); + MOZ_RELEASE_ASSERT(observerService); + observerService->NotifyObservers(nullptr, "sandbox-test-done", + nullptr); + return NS_OK; + } + MOZ_ASSERT_UNREACHABLE("SandboxTest; failure to get a process"); + return NS_ERROR_FAILURE; + }); } return NS_OK; } diff --git a/security/sandbox/common/test/SandboxTest.h b/security/sandbox/common/test/SandboxTest.h index 2fecf9e3fe89..9cfafd793013 100644 --- a/security/sandbox/common/test/SandboxTest.h +++ b/security/sandbox/common/test/SandboxTest.h @@ -9,12 +9,14 @@ #include "SandboxTestingParent.h" #include "mozISandboxTest.h" #include "mozilla/GfxMessageUtils.h" +#include "mozilla/MozPromise.h" #if !defined(MOZ_DEBUG) || !defined(ENABLE_TESTS) # error "This file should not be used outside of debug with tests" #endif namespace mozilla { + class SandboxTest : public mozISandboxTest { public: NS_DECL_ISUPPORTS @@ -22,6 +24,11 @@ class SandboxTest : public mozISandboxTest { SandboxTest() : mSandboxTestingParents{nullptr} {}; + // We allow nsresult to be rejected with values: + // - NS_ERROR_FAILURE in obvious case of error + // - NS_OK in case of success to complete the code but missing process (GPU) + using ProcessPromise = MozPromise; + private: virtual ~SandboxTest() = default; static constexpr size_t NumProcessTypes = diff --git a/security/sandbox/common/test/SandboxTestingChild.cpp b/security/sandbox/common/test/SandboxTestingChild.cpp index cd391882c4b2..e8d43dc3c29c 100644 --- a/security/sandbox/common/test/SandboxTestingChild.cpp +++ b/security/sandbox/common/test/SandboxTestingChild.cpp @@ -66,6 +66,10 @@ void SandboxTestingChild::Bind(Endpoint&& aEndpoint) { RunTestsContent(this); } + if (XRE_IsRDDProcess()) { + RunTestsRDD(this); + } + if (XRE_IsSocketProcess()) { RunTestsSocket(this); } @@ -96,6 +100,12 @@ bool SandboxTestingChild::RecvShutDown() { return true; } +void SandboxTestingChild::ReportNoTests() { + SendReportTestResults("dummy_test"_ns, /* shouldSucceed */ true, + /* didSucceed */ true, + "The test framework fails if there are no cases."_ns); +} + #ifdef XP_UNIX template void SandboxTestingChild::ErrnoTest(const nsCString& aName, bool aExpectSuccess, @@ -104,6 +114,14 @@ void SandboxTestingChild::ErrnoTest(const nsCString& aName, bool aExpectSuccess, PosixTest(aName, aExpectSuccess, status); } +template +void SandboxTestingChild::ErrnoValueTest(const nsCString& aName, + bool aExpectEquals, int aExpectedErrno, + F&& aFunction) { + int status = aFunction() >= 0 ? 0 : errno; + PosixTest(aName, aExpectEquals, status == aExpectedErrno); +} + void SandboxTestingChild::PosixTest(const nsCString& aName, bool aExpectSuccess, int aStatus) { bool succeeded = aStatus == 0; diff --git a/security/sandbox/common/test/SandboxTestingChild.h b/security/sandbox/common/test/SandboxTestingChild.h index 123b0dcb4736..adf9eb1330eb 100644 --- a/security/sandbox/common/test/SandboxTestingChild.h +++ b/security/sandbox/common/test/SandboxTestingChild.h @@ -40,6 +40,11 @@ class SandboxTestingChild : public PSandboxTestingChild { virtual bool RecvShutDown(); + // Helper to return that no test have been executed. Tests should make sure + // they have some fallback through that otherwise the framework will consider + // absence of test report as a failure. + inline void ReportNoTests(); + #ifdef XP_UNIX // For test cases that return an error number or 0, like newer POSIX APIs. void PosixTest(const nsCString& aName, bool aExpectSuccess, int aStatus); @@ -49,11 +54,18 @@ class SandboxTestingChild : public PSandboxTestingChild { // is used only in this function call (so `[&]` captures are safe). template void ErrnoTest(const nsCString& aName, bool aExpectSuccess, F&& aFunction); + + // Similar to ErrnoTest, except that we want to compare a specific `errno` + // being returned (or not). + template + void ErrnoValueTest(const nsCString& aName, bool aExpectEquals, + int aExpectedErrno, F&& aFunction); #endif private: explicit SandboxTestingChild(SandboxTestingThread* aThread, Endpoint&& aEndpoint); + void Bind(Endpoint&& aEndpoint); UniquePtr mThread; diff --git a/security/sandbox/common/test/SandboxTestingChildTests.h b/security/sandbox/common/test/SandboxTestingChildTests.h index 20a1b1ee1f82..4b3acbc67b78 100644 --- a/security/sandbox/common/test/SandboxTestingChildTests.h +++ b/security/sandbox/common/test/SandboxTestingChildTests.h @@ -13,6 +13,10 @@ # include # ifdef XP_LINUX # include +# include +# include +# include +# include # endif // XP_LINUX # include # include @@ -50,11 +54,7 @@ void RunTestsContent(SandboxTestingChild* child) { [&] { return clock_getres(CLOCK_REALTIME, &res); }); #else // XP_UNIX - child->SendReportTestResults( - "dummy_test"_ns, - /* shouldSucceed */ true, - /* didSucceed */ true, - "The test framework fails if there are no cases."_ns); + child->ReportNoTests(); #endif // XP_UNIX } @@ -84,12 +84,29 @@ void RunTestsSocket(SandboxTestingChild* child) { # endif // XP_LINUX #else // XP_UNIX - child->SendReportTestResults( - "dummy_test"_ns, - /* shouldSucceed */ true, - /* didSucceed */ true, - "The test framework fails if there are no cases."_ns); + child->ReportNoTests(); #endif // XP_UNIX } +void RunTestsRDD(SandboxTestingChild* child) { + MOZ_ASSERT(child, "No SandboxTestingChild*?"); + +#ifdef XP_UNIX +# ifdef XP_LINUX + child->ErrnoValueTest("ioctl_tiocsti"_ns, false, ENOSYS, [&] { + int rv = ioctl(1, TIOCSTI, "x"); + return rv; + }); + + struct rusage res; + child->ErrnoTest("getrusage"_ns, true, [&] { + int rv = getrusage(RUSAGE_SELF, &res); + return rv; + }); +# endif // XP_LINUX +#else // XP_UNIX + child->ReportNoTests(); +#endif +} + } // namespace mozilla diff --git a/security/sandbox/test/browser_sandbox_test.js b/security/sandbox/test/browser_sandbox_test.js index d9110ffbe350..cc0c91539d73 100644 --- a/security/sandbox/test/browser_sandbox_test.js +++ b/security/sandbox/test/browser_sandbox_test.js @@ -14,7 +14,7 @@ function test() { // GPU process might not run depending on the platform, so we need it to be // the last one of the list to allow the remainingTests logic below to work // as expected. - var processTypes = ["tab", "socket", "gpu"]; + var processTypes = ["tab", "socket", "rdd", "gpu"]; // A callback called after each test-result. let sandboxTestResult = (subject, topic, data) => {