summaryrefslogtreecommitdiffstats
path: root/security/sandbox/common/test
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-21 11:44:51 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-21 11:44:51 +0000
commit9e3c08db40b8916968b9f30096c7be3f00ce9647 (patch)
treea68f146d7fa01f0134297619fbe7e33db084e0aa /security/sandbox/common/test
parentInitial commit. (diff)
downloadthunderbird-9e3c08db40b8916968b9f30096c7be3f00ce9647.tar.xz
thunderbird-9e3c08db40b8916968b9f30096c7be3f00ce9647.zip
Adding upstream version 1:115.7.0.upstream/1%115.7.0upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'security/sandbox/common/test')
-rw-r--r--security/sandbox/common/test/PSandboxTesting.ipdl20
-rw-r--r--security/sandbox/common/test/SandboxTest.cpp362
-rw-r--r--security/sandbox/common/test/SandboxTest.h45
-rw-r--r--security/sandbox/common/test/SandboxTestingChild.cpp194
-rw-r--r--security/sandbox/common/test/SandboxTestingChild.h86
-rw-r--r--security/sandbox/common/test/SandboxTestingChildTests.h876
-rw-r--r--security/sandbox/common/test/SandboxTestingParent.cpp125
-rw-r--r--security/sandbox/common/test/SandboxTestingParent.h53
-rw-r--r--security/sandbox/common/test/SandboxTestingThread.h53
-rw-r--r--security/sandbox/common/test/mozISandboxTest.idl28
10 files changed, 1842 insertions, 0 deletions
diff --git a/security/sandbox/common/test/PSandboxTesting.ipdl b/security/sandbox/common/test/PSandboxTesting.ipdl
new file mode 100644
index 0000000000..8498f4a235
--- /dev/null
+++ b/security/sandbox/common/test/PSandboxTesting.ipdl
@@ -0,0 +1,20 @@
+/* -*- 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 https://mozilla.org/MPL/2.0/. */
+
+namespace mozilla {
+
+sync protocol PSandboxTesting {
+parent:
+ async ReportTestResults(nsCString testName, bool passed, nsCString message);
+ async TestCompleted();
+
+ sync GetSpecialDirectory(nsCString aSpecialDirName) returns (nsString aDirPath);
+
+child:
+ async ShutDown();
+};
+
+} //namespace mozilla
diff --git a/security/sandbox/common/test/SandboxTest.cpp b/security/sandbox/common/test/SandboxTest.cpp
new file mode 100644
index 0000000000..0f1aa6c784
--- /dev/null
+++ b/security/sandbox/common/test/SandboxTest.cpp
@@ -0,0 +1,362 @@
+/* -*- 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 https://mozilla.org/MPL/2.0/. */
+
+#include "SandboxTest.h"
+
+#include "mozilla/Components.h"
+#include "mozilla/Preferences.h"
+#include "SandboxTestingParent.h"
+#include "SandboxTestingChild.h"
+#include "mozilla/dom/ContentParent.h"
+#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/UtilityProcessManager.h"
+#include "mozilla/ipc/UtilityProcessParent.h"
+#include "mozilla/ipc/UtilityProcessSandboxing.h"
+#include "GMPService.h"
+#include "mozilla/gmp/GMPTypes.h"
+#include "mozilla/ipc/Endpoint.h"
+#include "nsIOService.h"
+
+#ifdef XP_WIN
+# include "nsAppDirectoryServiceDefs.h"
+#endif
+
+using namespace mozilla;
+using namespace mozilla::ipc;
+using namespace mozilla::dom;
+
+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;
+ type = GeckoProcessType(type + 1)) {
+ if (aString == XRE_GeckoProcessTypeToString(type)) {
+ return type;
+ }
+ }
+ return GeckoProcessType::GeckoProcessType_Invalid;
+}
+
+// Set up tests on remote process connected to the given actor.
+// The actor must handle the InitSandboxTesting message.
+template <typename Actor>
+void InitializeSandboxTestingActors(
+ Actor* aActor,
+ const RefPtr<SandboxTest::ProcessPromise::Private>& aProcessPromise) {
+ MOZ_ASSERT(aActor, "Should have provided an IPC actor");
+ Endpoint<PSandboxTestingParent> sandboxTestingParentEnd;
+ Endpoint<PSandboxTestingChild> sandboxTestingChildEnd;
+ nsresult rv = PSandboxTesting::CreateEndpoints(&sandboxTestingParentEnd,
+ &sandboxTestingChildEnd);
+ if (NS_FAILED(rv)) {
+ aProcessPromise->Reject(NS_ERROR_FAILURE, __func__);
+ return;
+ }
+
+ // GMPlugin binds us to the GMP Thread, so we need IPC's Send to be done on
+ // the same thread
+ Unused << aActor->SendInitSandboxTesting(std::move(sandboxTestingChildEnd));
+ // But then the SandboxTestingParent::Create() call needs to be on the main
+ // thread
+ NS_DispatchToMainThread(NS_NewRunnableFunction(
+ "SandboxTestingParent::Create",
+ [stpE = std::move(sandboxTestingParentEnd), aProcessPromise]() mutable {
+ return aProcessPromise->Resolve(
+ SandboxTestingParent::Create(std::move(stpE)), __func__);
+ }));
+}
+
+NS_IMETHODIMP
+SandboxTest::StartTests(const nsTArray<nsCString>& aProcessesList) {
+ MOZ_ASSERT(NS_IsMainThread());
+
+#if defined(XP_WIN)
+ nsCOMPtr<nsIFile> testFile;
+ NS_GetSpecialDirectory(NS_APP_USER_PROFILE_50_DIR, getter_AddRefs(testFile));
+ MOZ_ASSERT(testFile);
+ nsCOMPtr<nsIFile> testChromeFile;
+ testFile->Clone(getter_AddRefs(testChromeFile));
+ testChromeFile->Append(u"chrome"_ns);
+ testChromeFile->Exists(&mChromeDirExisted);
+ testFile->Append(u"sandboxTest.txt"_ns);
+ testChromeFile->Append(u"sandboxTest.txt"_ns);
+ MOZ_ALWAYS_SUCCEEDS(testFile->Create(nsIFile::NORMAL_FILE_TYPE, 0666));
+ MOZ_ALWAYS_SUCCEEDS(testChromeFile->Create(nsIFile::NORMAL_FILE_TYPE, 0666));
+#endif
+
+ for (const auto& processTypeName : aProcessesList) {
+ SandboxingKind sandboxingKind = SandboxingKind::COUNT;
+ GeckoProcessType type = GeckoProcessType::GeckoProcessType_Invalid;
+ if (processTypeName.Find(":") != kNotFound) {
+ int32_t pos = processTypeName.Find(":");
+ nsCString processType = nsCString(Substring(processTypeName, 0, pos));
+ nsCString sandboxKindStr = nsCString(
+ Substring(processTypeName, pos + 1, processTypeName.Length()));
+
+ nsresult err;
+ uint64_t sbVal = (uint64_t)(sandboxKindStr.ToDouble(&err));
+ if (NS_FAILED(err)) {
+ NS_WARNING("Unable to get SandboxingKind");
+ return NS_ERROR_ILLEGAL_VALUE;
+ }
+
+ if (sbVal >= SandboxingKind::COUNT) {
+ NS_WARNING("Invalid sandboxing kind");
+ return NS_ERROR_ILLEGAL_VALUE;
+ }
+
+ if (!processType.Equals(
+ XRE_GeckoProcessTypeToString(GeckoProcessType_Utility))) {
+ NS_WARNING("Expected utility process type");
+ return NS_ERROR_ILLEGAL_VALUE;
+ }
+
+ sandboxingKind = (SandboxingKind)sbVal;
+ type = GeckoProcessType_Utility;
+ } else {
+ type = GeckoProcessStringToType(processTypeName);
+
+ if (type == GeckoProcessType::GeckoProcessType_Invalid) {
+ NS_WARNING("Invalid process type");
+ return NS_ERROR_ILLEGAL_VALUE;
+ }
+ }
+
+ RefPtr<ProcessPromise::Private> processPromise =
+ MakeRefPtr<ProcessPromise::Private>(__func__);
+
+ switch (type) {
+ case GeckoProcessType_Content: {
+ nsTArray<ContentParent*> parents;
+ ContentParent::GetAll(parents);
+ if (parents[0]) {
+ InitializeSandboxTestingActors(parents[0], processPromise);
+ } else {
+ processPromise->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) {
+ InitializeSandboxTestingActors(gpuChild, processPromise);
+ } else {
+ processPromise->Reject(NS_OK, __func__);
+ }
+ break;
+ }
+
+ case GeckoProcessType_RDD: {
+ RDDProcessManager* rddProc = RDDProcessManager::Get();
+ rddProc->LaunchRDDProcess()->Then(
+ GetMainThreadSerialEventTarget(), __func__,
+ [processPromise, rddProc]() {
+ RDDChild* rddChild = rddProc ? rddProc->GetRDDChild() : nullptr;
+ if (rddChild) {
+ return InitializeSandboxTestingActors(rddChild, processPromise);
+ }
+ return processPromise->Reject(NS_ERROR_FAILURE, __func__);
+ },
+ [processPromise](nsresult aError) {
+ MOZ_ASSERT_UNREACHABLE("SandboxTest; failure to get RDD process");
+ return processPromise->Reject(aError, __func__);
+ });
+ break;
+ }
+
+ case GeckoProcessType_GMPlugin: {
+ UnsetEnvVariable("MOZ_DISABLE_GMP_SANDBOX"_ns);
+ RefPtr<gmp::GeckoMediaPluginService> service =
+ gmp::GeckoMediaPluginService::GetGeckoMediaPluginService();
+ MOZ_ASSERT(service, "We have a GeckoMediaPluginService");
+
+ RefPtr<SandboxTest> self = this;
+ nsCOMPtr<nsISerialEventTarget> thread = service->GetGMPThread();
+ nsresult rv = thread->Dispatch(NS_NewRunnableFunction(
+ "SandboxTest::GMPlugin", [self, processPromise, service, thread]() {
+ service->GetContentParentForTest()->Then(
+ thread, __func__,
+ [self, processPromise](
+ const RefPtr<gmp::GMPContentParent::CloseBlocker>&
+ wrapper) {
+ RefPtr<gmp::GMPContentParent> parent = wrapper->mParent;
+ MOZ_ASSERT(parent,
+ "Wrapper should wrap a valid parent if we're in "
+ "this path.");
+ if (!parent) {
+ return processPromise->Reject(NS_ERROR_ILLEGAL_VALUE,
+ __func__);
+ }
+ NS_DispatchToMainThread(NS_NewRunnableFunction(
+ "SandboxTesting::Wrapper", [self, wrapper]() {
+ self->mGMPContentParentWrapper = wrapper;
+ }));
+ return InitializeSandboxTestingActors(parent.get(),
+ processPromise);
+ },
+ [processPromise](const MediaResult& rv) {
+ return processPromise->Reject(NS_ERROR_FAILURE, __func__);
+ });
+ }));
+ NS_ENSURE_SUCCESS(rv, rv);
+ break;
+ }
+
+ case GeckoProcessType_Socket: {
+ // mochitest harness force this variable, but we actually do not want
+ // that
+ 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?");
+
+ net::gIOService->CallOrWaitForSocketProcess([processPromise]() {
+ // 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
+ net::SocketProcessParent* parent =
+ net::SocketProcessParent::GetSingleton();
+ if (parent) {
+ return InitializeSandboxTestingActors(parent, processPromise);
+ }
+ return processPromise->Reject(NS_ERROR_FAILURE, __func__);
+ });
+ break;
+ }
+
+ case GeckoProcessType_Utility: {
+ RefPtr<UtilityProcessManager> utilityProc =
+ UtilityProcessManager::GetSingleton();
+ utilityProc->LaunchProcess(sandboxingKind)
+ ->Then(
+ GetMainThreadSerialEventTarget(), __func__,
+ [processPromise, utilityProc, sandboxingKind]() {
+ RefPtr<UtilityProcessParent> utilityParent =
+ utilityProc
+ ? utilityProc->GetProcessParent(sandboxingKind)
+ : nullptr;
+ if (utilityParent) {
+ return InitializeSandboxTestingActors(utilityParent.get(),
+ processPromise);
+ }
+ return processPromise->Reject(NS_ERROR_FAILURE, __func__);
+ },
+ [processPromise](nsresult aError) {
+ MOZ_ASSERT_UNREACHABLE(
+ "SandboxTest; failure to get Utility process");
+ return processPromise->Reject(aError, __func__);
+ });
+ break;
+ }
+
+ default:
+ MOZ_ASSERT_UNREACHABLE(
+ "SandboxTest does not yet support this process type");
+ return NS_ERROR_NOT_IMPLEMENTED;
+ }
+
+ RefPtr<SandboxTest> self = this;
+ RefPtr<ProcessPromise> aPromise(processPromise);
+ aPromise->Then(
+ GetMainThreadSerialEventTarget(), __func__,
+ [self](RefPtr<SandboxTestingParent> aValue) {
+ self->mSandboxTestingParents.AppendElement(std::move(aValue));
+ return NS_OK;
+ },
+ [](nsresult aError) {
+ if (aError == NS_OK) {
+ // There is no such process for this OS. Report test done.
+ nsCOMPtr<nsIObserverService> 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;
+}
+
+NS_IMETHODIMP
+SandboxTest::FinishTests() {
+ if (mGMPContentParentWrapper) {
+ RefPtr<gmp::GeckoMediaPluginService> service =
+ gmp::GeckoMediaPluginService::GetGeckoMediaPluginService();
+ MOZ_ASSERT(service, "We have a GeckoMediaPluginService");
+
+ nsCOMPtr<nsISerialEventTarget> thread = service->GetGMPThread();
+ nsresult rv = thread->Dispatch(NS_NewRunnableFunction(
+ "SandboxTest::FinishTests",
+ [wrapper = std::move(mGMPContentParentWrapper)]() {
+ // Release mGMPContentWrapper's reference. We hold this to keep an
+ // active reference on the CloseBlocker produced by GMPService,
+ // otherwise it would automatically shutdown the GMPlugin thread we
+ // started.
+ // If somehow it does not work as expected, then tests will fail
+ // because of leaks happening on GMPService and others.
+ }));
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ for (RefPtr<SandboxTestingParent>& stp : mSandboxTestingParents) {
+ SandboxTestingParent::Destroy(stp.forget());
+ }
+
+ // Make sure there is no leftover for test --verify to run without failure
+ mSandboxTestingParents.Clear();
+
+#if defined(XP_WIN)
+ nsCOMPtr<nsIFile> testFile;
+ NS_GetSpecialDirectory(NS_APP_USER_PROFILE_50_DIR, getter_AddRefs(testFile));
+ MOZ_ASSERT(testFile);
+ nsCOMPtr<nsIFile> testChromeFile;
+ testFile->Clone(getter_AddRefs(testChromeFile));
+ testChromeFile->Append(u"chrome"_ns);
+ testFile->Append(u"sandboxTest.txt"_ns);
+ if (mChromeDirExisted) {
+ // Chrome dir existed, just delete test file.
+ testChromeFile->Append(u"sandboxTest.txt"_ns);
+ }
+ testFile->Remove(false);
+ testChromeFile->Remove(true);
+#endif
+
+ return NS_OK;
+}
+
+} // namespace mozilla
+
+NS_IMPL_COMPONENT_FACTORY(mozISandboxTest) {
+ return MakeAndAddRef<SandboxTest>().downcast<nsISupports>();
+}
diff --git a/security/sandbox/common/test/SandboxTest.h b/security/sandbox/common/test/SandboxTest.h
new file mode 100644
index 0000000000..1d8c18c4b8
--- /dev/null
+++ b/security/sandbox/common/test/SandboxTest.h
@@ -0,0 +1,45 @@
+/* -*- 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 https://mozilla.org/MPL/2.0/. */
+#ifndef mozilla_SandboxTest_h
+#define mozilla_SandboxTest_h
+
+#include "SandboxTestingParent.h"
+#include "mozISandboxTest.h"
+#include "mozilla/GfxMessageUtils.h"
+#include "mozilla/MozPromise.h"
+#include "GMPService.h"
+#include "nsTArray.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_THREADSAFE_ISUPPORTS
+ NS_DECL_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<RefPtr<SandboxTestingParent>, nsresult, true>;
+
+ private:
+ virtual ~SandboxTest() = default;
+ nsTArray<RefPtr<SandboxTestingParent>> mSandboxTestingParents;
+ RefPtr<gmp::GMPContentParent::CloseBlocker> mGMPContentParentWrapper;
+#if defined(XP_WIN)
+ bool mChromeDirExisted = false;
+#endif
+};
+
+} // namespace mozilla
+#endif // mozilla_SandboxTest_h
diff --git a/security/sandbox/common/test/SandboxTestingChild.cpp b/security/sandbox/common/test/SandboxTestingChild.cpp
new file mode 100644
index 0000000000..3a9d61556c
--- /dev/null
+++ b/security/sandbox/common/test/SandboxTestingChild.cpp
@@ -0,0 +1,194 @@
+/* -*- 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 https://mozilla.org/MPL/2.0/. */
+
+#include "SandboxTestingChild.h"
+#include "SandboxTestingChildTests.h"
+#include "SandboxTestingThread.h"
+#include "mozilla/ipc/Endpoint.h"
+#include "mozilla/ipc/UtilityProcessSandboxing.h"
+#include "mozilla/ipc/UtilityProcessChild.h"
+
+#ifdef XP_LINUX
+# include "mozilla/Sandbox.h"
+#endif
+
+#include "nsXULAppAPI.h"
+
+namespace mozilla {
+
+StaticRefPtr<SandboxTestingChild> SandboxTestingChild::sInstance;
+
+bool SandboxTestingChild::IsTestThread() { return mThread->IsOnThread(); }
+
+void SandboxTestingChild::PostToTestThread(
+ already_AddRefed<nsIRunnable>&& runnable) {
+ mThread->Dispatch(std::move(runnable));
+}
+
+/* static */
+bool SandboxTestingChild::Initialize(
+ Endpoint<PSandboxTestingChild>&& aSandboxTestingEndpoint) {
+ MOZ_ASSERT(!sInstance);
+ SandboxTestingThread* thread = SandboxTestingThread::Create();
+ if (!thread) {
+ return false;
+ }
+ sInstance =
+ new SandboxTestingChild(thread, std::move(aSandboxTestingEndpoint));
+ thread->Dispatch(NewRunnableMethod<Endpoint<PSandboxTestingChild>&&>(
+ "SandboxTestingChild::Bind", sInstance.get(), &SandboxTestingChild::Bind,
+ std::move(aSandboxTestingEndpoint)));
+ return true;
+}
+
+/* static */
+SandboxTestingChild* SandboxTestingChild::GetInstance() {
+ MOZ_ASSERT(sInstance, "Must initialize SandboxTestingChild before using it");
+ return sInstance;
+}
+
+SandboxTestingChild::SandboxTestingChild(
+ SandboxTestingThread* aThread, Endpoint<PSandboxTestingChild>&& aEndpoint)
+ : mThread(aThread) {}
+
+SandboxTestingChild::~SandboxTestingChild() = default;
+
+void SandboxTestingChild::Bind(Endpoint<PSandboxTestingChild>&& aEndpoint) {
+ MOZ_RELEASE_ASSERT(mThread->IsOnThread());
+ DebugOnly<bool> ok = aEndpoint.Bind(this);
+ MOZ_ASSERT(ok);
+
+#ifdef XP_LINUX
+ bool sandboxCrashOnError = SetSandboxCrashOnError(false);
+#endif
+
+ if (XRE_IsContentProcess()) {
+ RunTestsContent(this);
+ }
+
+ if (XRE_IsRDDProcess()) {
+ RunTestsRDD(this);
+ }
+
+ if (XRE_IsGMPluginProcess()) {
+ RunTestsGMPlugin(this);
+ }
+
+ if (XRE_IsSocketProcess()) {
+ RunTestsSocket(this);
+ }
+
+ if (XRE_IsGPUProcess()) {
+ RunTestsGPU(this);
+ }
+
+ if (XRE_IsUtilityProcess()) {
+ RefPtr<ipc::UtilityProcessChild> s = ipc::UtilityProcessChild::Get();
+ MOZ_ASSERT(s, "Unable to grab a UtilityProcessChild");
+ switch (s->mSandbox) {
+ case ipc::SandboxingKind::GENERIC_UTILITY:
+ RunTestsGenericUtility(this);
+#ifdef MOZ_APPLEMEDIA
+ [[fallthrough]];
+ case ipc::SandboxingKind::UTILITY_AUDIO_DECODING_APPLE_MEDIA:
+#endif
+#ifdef XP_WIN
+ [[fallthrough]];
+ case ipc::SandboxingKind::UTILITY_AUDIO_DECODING_WMF:
+#endif
+ RunTestsUtilityAudioDecoder(this, s->mSandbox);
+ break;
+
+ default:
+ MOZ_ASSERT(false, "Invalid SandboxingKind");
+ break;
+ }
+ }
+
+#ifdef XP_LINUX
+ SetSandboxCrashOnError(sandboxCrashOnError);
+#endif
+
+ // Tell SandboxTest that this process is done with all tests.
+ SendTestCompleted();
+}
+
+void SandboxTestingChild::ActorDestroy(ActorDestroyReason aWhy) {
+ MOZ_ASSERT(mThread->IsOnThread());
+ NS_DispatchToMainThread(NS_NewRunnableFunction(
+ "SandboxChildDestroyer", []() { SandboxTestingChild::Destroy(); }));
+}
+
+void SandboxTestingChild::Destroy() {
+ MOZ_ASSERT(NS_IsMainThread());
+ MOZ_ASSERT(sInstance);
+ sInstance = nullptr;
+}
+
+ipc::IPCResult SandboxTestingChild::RecvShutDown() {
+ Close();
+ return IPC_OK();
+}
+
+void SandboxTestingChild::ReportNoTests() {
+ SendReportTestResults("dummy_test"_ns, /* passed */ true,
+ "The test framework fails if there are no cases."_ns);
+}
+
+template <typename F>
+void SandboxTestingChild::ErrnoTest(const nsCString& aName, bool aExpectSuccess,
+ F&& aFunction) {
+ int status = aFunction() >= 0 ? 0 : errno;
+ PosixTest(aName, aExpectSuccess, status);
+}
+
+template <typename F>
+void SandboxTestingChild::ErrnoValueTest(const nsCString& aName,
+ int aExpectedErrno, F&& aFunction) {
+ int status = aFunction() >= 0 ? 0 : errno;
+ PosixTest(aName, aExpectedErrno == 0, status, Some(aExpectedErrno));
+}
+
+void SandboxTestingChild::PosixTest(const nsCString& aName, bool aExpectSuccess,
+ int aStatus, Maybe<int> aExpectedError) {
+ nsAutoCString message;
+ bool passed;
+
+ // The "expected" arguments are a little redundant.
+ MOZ_ASSERT(!aExpectedError || aExpectSuccess == (*aExpectedError == 0));
+
+ // Decide whether the test passed, and stringify the actual result.
+ if (aStatus == 0) {
+ message = "Succeeded"_ns;
+ passed = aExpectSuccess;
+ } else {
+ message = "Error: "_ns;
+ message += strerror(aStatus);
+ if (aExpectedError) {
+ passed = aStatus == *aExpectedError;
+ } else {
+ passed = !aExpectSuccess;
+ }
+ }
+
+ // If something unexpected happened, mention the expected result.
+ if (!passed) {
+ message += "; expected ";
+ if (aExpectSuccess) {
+ message += "success";
+ } else {
+ message += "error";
+ if (aExpectedError) {
+ message += ": ";
+ message += strerror(*aExpectedError);
+ }
+ }
+ }
+
+ SendReportTestResults(aName, passed, message);
+}
+
+} // namespace mozilla
diff --git a/security/sandbox/common/test/SandboxTestingChild.h b/security/sandbox/common/test/SandboxTestingChild.h
new file mode 100644
index 0000000000..45c9893a63
--- /dev/null
+++ b/security/sandbox/common/test/SandboxTestingChild.h
@@ -0,0 +1,86 @@
+/* -*- 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 https://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_SandboxTestingChild_h
+#define mozilla_SandboxTestingChild_h
+
+#include "mozilla/PSandboxTestingChild.h"
+#include "mozilla/Maybe.h"
+#include "mozilla/Monitor.h"
+#include "mozilla/StaticPtr.h"
+#include "mozilla/UniquePtr.h"
+#include "nsISupports.h"
+
+#ifdef XP_UNIX
+# include "nsString.h"
+#endif
+
+#if !defined(MOZ_SANDBOX) || !defined(MOZ_DEBUG) || !defined(ENABLE_TESTS)
+# error "This file should not be used outside of debug with tests"
+#endif
+
+namespace mozilla {
+
+class SandboxTestingThread;
+
+/**
+ * Runs tests that check sandbox in child process, depending on process type.
+ */
+class SandboxTestingChild : public PSandboxTestingChild {
+ public:
+ static bool Initialize(
+ Endpoint<PSandboxTestingChild>&& aSandboxTestingEndpoint);
+ static SandboxTestingChild* GetInstance();
+ static void Destroy();
+
+ NS_INLINE_DECL_THREADSAFE_REFCOUNTING(SandboxTestingChild, override)
+
+ bool IsTestThread();
+ void PostToTestThread(already_AddRefed<nsIRunnable>&& runnable);
+
+ void ActorDestroy(ActorDestroyReason aWhy) override;
+
+ virtual ipc::IPCResult 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();
+
+ // For test cases that return an error number or 0, like newer POSIX
+ // APIs. If `aExpectSuccess` is true, the test passes if the status is
+ // 0; otherwise, the test requires a specific error if `aExpectedError`
+ // is `Some(n)` or any nonzero status if it's `Nothing()`.
+ void PosixTest(const nsCString& aName, bool aExpectSuccess, int aStatus,
+ Maybe<int> aExpectedError = Nothing());
+
+ // For test cases that return a negative number and set `errno` to
+ // indicate error, like classical Unix APIs; takes a callable, which
+ // is used only in this function call (so `[&]` captures are safe).
+ template <typename F>
+ void ErrnoTest(const nsCString& aName, bool aExpectSuccess, F&& aFunction);
+
+ // Similar to ErrnoTest, except that we want to compare a specific `errno`
+ // being returned.
+ template <typename F>
+ void ErrnoValueTest(const nsCString& aName, int aExpectedErrno,
+ F&& aFunction);
+
+ private:
+ explicit SandboxTestingChild(SandboxTestingThread* aThread,
+ Endpoint<PSandboxTestingChild>&& aEndpoint);
+ ~SandboxTestingChild();
+
+ void Bind(Endpoint<PSandboxTestingChild>&& aEndpoint);
+
+ UniquePtr<SandboxTestingThread> mThread;
+
+ static StaticRefPtr<SandboxTestingChild> sInstance;
+};
+
+} // namespace mozilla
+
+#endif // mozilla_SandboxTestingChild_h
diff --git a/security/sandbox/common/test/SandboxTestingChildTests.h b/security/sandbox/common/test/SandboxTestingChildTests.h
new file mode 100644
index 0000000000..3dd748daba
--- /dev/null
+++ b/security/sandbox/common/test/SandboxTestingChildTests.h
@@ -0,0 +1,876 @@
+/* -*- 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 https://mozilla.org/MPL/2.0/. */
+
+#include "SandboxTestingChild.h"
+
+#include "mozilla/StaticPrefs_security.h"
+#include "mozilla/ipc/UtilityProcessSandboxing.h"
+#ifdef XP_MACOSX
+# include "nsCocoaFeatures.h"
+#endif
+#include "nsXULAppAPI.h"
+
+#ifdef XP_UNIX
+# include <fcntl.h>
+# include <netdb.h>
+# ifdef XP_LINUX
+# include <linux/mempolicy.h>
+# include <sched.h>
+# include <sys/ioctl.h>
+# include <sys/prctl.h>
+# include <sys/resource.h>
+# include <sys/socket.h>
+# include <sys/statfs.h>
+# include <sys/syscall.h>
+# include <sys/sysmacros.h>
+# include <sys/time.h>
+# include <sys/un.h>
+# include <sys/utsname.h>
+# include <termios.h>
+# include "mozilla/ProcInfo_linux.h"
+# include "mozilla/UniquePtrExtensions.h"
+# ifdef MOZ_X11
+# include "X11/Xlib.h"
+# include "X11UndefineNone.h"
+# endif // MOZ_X11
+# endif // XP_LINUX
+# include <sys/socket.h>
+# include <sys/stat.h>
+# include <sys/types.h>
+# include <time.h>
+# include <unistd.h>
+#endif
+
+#ifdef XP_MACOSX
+# if defined(__SSE2__) || defined(_M_X64) || \
+ (defined(_M_IX86_FP) && _M_IX86_FP >= 2)
+# include "emmintrin.h"
+# endif
+# include <spawn.h>
+# include <CoreFoundation/CoreFoundation.h>
+# include <CoreGraphics/CoreGraphics.h>
+# include <AudioToolbox/AudioToolbox.h>
+namespace ApplicationServices {
+# include <ApplicationServices/ApplicationServices.h>
+}
+#endif
+
+#ifdef XP_WIN
+# include <stdio.h>
+# include <winternl.h>
+
+# include "mozilla/DynamicallyLinkedFunctionPtr.h"
+# include "nsAppDirectoryServiceDefs.h"
+# include "mozilla/WindowsProcessMitigations.h"
+#endif
+
+#ifdef XP_LINUX
+// Defined in <linux/watch_queue.h> which was added in 5.8
+# ifndef O_NOTIFICATION_PIPE
+# define O_NOTIFICATION_PIPE O_EXCL
+# endif
+#endif
+
+constexpr bool kIsDebug =
+#ifdef DEBUG
+ true;
+#else
+ false;
+#endif
+
+namespace mozilla {
+
+#ifdef XP_LINUX
+static void RunTestsSched(SandboxTestingChild* child) {
+ struct sched_param param_pid_0 = {};
+ child->ErrnoTest("sched_getparam(0)"_ns, true,
+ [&] { return sched_getparam(0, &param_pid_0); });
+
+ struct sched_param param_pid_tid = {};
+ child->ErrnoTest("sched_getparam(tid)"_ns, true, [&] {
+ return sched_getparam((pid_t)syscall(__NR_gettid), &param_pid_tid);
+ });
+
+ struct sched_param param_pid_Ntid = {};
+ child->ErrnoValueTest("sched_getparam(Ntid)"_ns, EPERM, [&] {
+ return sched_getparam((pid_t)(syscall(__NR_gettid) - 1), &param_pid_Ntid);
+ });
+}
+#endif
+
+// Tests that apply to every process type (more or less)
+static void RunGenericTests(SandboxTestingChild* child, bool aIsGMP = false) {
+#ifdef XP_LINUX
+ // Check ABI issues with 32-bit arguments on 64-bit platforms.
+ if (sizeof(void*) == 8) {
+ static constexpr uint64_t kHighBits = 0xDEADBEEF00000000;
+
+ struct timespec ts0, ts1;
+ child->ErrnoTest("high_bits_gettime"_ns, true, [&] {
+ return syscall(__NR_clock_gettime, kHighBits | CLOCK_MONOTONIC, &ts0);
+ });
+ // Try to make sure we got the correct clock by reading it again and
+ // comparing to see if the times are vaguely similar.
+ int rv = clock_gettime(CLOCK_MONOTONIC, &ts1);
+ MOZ_RELEASE_ASSERT(rv == 0);
+ MOZ_RELEASE_ASSERT(ts0.tv_sec <= ts1.tv_sec + 1);
+ MOZ_RELEASE_ASSERT(ts1.tv_sec <= ts0.tv_sec + 60);
+
+ // Check some non-zeroth arguments. (fcntl is convenient for
+ // this, but GMP has a stricter policy, so skip it there.)
+ if (!aIsGMP) {
+ int flags;
+ child->ErrnoTest("high_bits_fcntl_getfl"_ns, true, [&] {
+ flags = syscall(__NR_fcntl, 0, kHighBits | F_GETFL);
+ return flags;
+ });
+ MOZ_RELEASE_ASSERT(flags == fcntl(0, F_GETFL));
+
+ int fds[2];
+ rv = pipe(fds);
+ MOZ_RELEASE_ASSERT(rv >= 0);
+ child->ErrnoTest("high_bits_fcntl_setfl"_ns, true, [&] {
+ return syscall(__NR_fcntl, fds[0], kHighBits | F_SETFL,
+ kHighBits | O_NONBLOCK);
+ });
+ flags = fcntl(fds[0], F_GETFL);
+ MOZ_RELEASE_ASSERT(flags >= 0);
+ MOZ_RELEASE_ASSERT(flags & O_NONBLOCK);
+ }
+ }
+#endif // XP_LINUX
+}
+
+#ifdef XP_WIN
+/**
+ * Uses NtCreateFile directly to test file system brokering.
+ *
+ */
+static void FileTest(const nsCString& aName, const char* aSpecialDirName,
+ const nsString& aRelativeFilePath, ACCESS_MASK aAccess,
+ bool aExpectSuccess, SandboxTestingChild* aChild) {
+ static const StaticDynamicallyLinkedFunctionPtr<decltype(&NtCreateFile)>
+ pNtCreateFile(L"ntdll.dll", "NtCreateFile");
+ static const StaticDynamicallyLinkedFunctionPtr<decltype(&NtClose)> pNtClose(
+ L"ntdll.dll", "NtClose");
+
+ // Start the filename with the NT namespace
+ nsString testFilename(u"\\??\\"_ns);
+ nsString dirPath;
+ aChild->SendGetSpecialDirectory(nsDependentCString(aSpecialDirName),
+ &dirPath);
+ testFilename.Append(dirPath);
+ testFilename.AppendLiteral("\\");
+ testFilename.Append(aRelativeFilePath);
+
+ UNICODE_STRING uniFileName;
+ ::RtlInitUnicodeString(&uniFileName, testFilename.get());
+
+ OBJECT_ATTRIBUTES objectAttributes;
+ InitializeObjectAttributes(&objectAttributes, &uniFileName,
+ OBJ_CASE_INSENSITIVE, nullptr, nullptr);
+
+ HANDLE fileHandle = INVALID_HANDLE_VALUE;
+ IO_STATUS_BLOCK ioStatusBlock = {};
+
+ ULONG createOptions = StringEndsWith(testFilename, u"\\"_ns) ||
+ StringEndsWith(testFilename, u"/"_ns)
+ ? FILE_DIRECTORY_FILE
+ : FILE_NON_DIRECTORY_FILE;
+ NTSTATUS status = pNtCreateFile(
+ &fileHandle, aAccess, &objectAttributes, &ioStatusBlock, nullptr, 0, 0,
+ FILE_OPEN_IF, createOptions | FILE_SYNCHRONOUS_IO_NONALERT, nullptr, 0);
+
+ if (fileHandle != INVALID_HANDLE_VALUE) {
+ pNtClose(fileHandle);
+ }
+
+ nsCString accessString;
+ if ((aAccess & FILE_GENERIC_READ) == FILE_GENERIC_READ) {
+ accessString.AppendLiteral("r");
+ }
+ if ((aAccess & FILE_GENERIC_WRITE) == FILE_GENERIC_WRITE) {
+ accessString.AppendLiteral("w");
+ }
+ if ((aAccess & FILE_GENERIC_EXECUTE) == FILE_GENERIC_EXECUTE) {
+ accessString.AppendLiteral("e");
+ }
+
+ nsCString msgRelPath = NS_ConvertUTF16toUTF8(aRelativeFilePath);
+ for (size_t i = 0, j = 0; i < aRelativeFilePath.Length(); ++i, ++j) {
+ if (aRelativeFilePath[i] == u'\\') {
+ msgRelPath.Insert('\\', j++);
+ }
+ }
+
+ nsCString message;
+ message.AppendPrintf(
+ "Special dir: %s, file: %s, access: %s , returned status: %lx",
+ aSpecialDirName, msgRelPath.get(), accessString.get(), status);
+
+ aChild->SendReportTestResults(aName, aExpectSuccess == NT_SUCCESS(status),
+ message);
+}
+#endif
+
+#ifdef XP_MACOSX
+/*
+ * Test if this process can launch another process with posix_spawnp,
+ * exec, and LSOpenCFURLRef. All launches are expected to fail. In processes
+ * where the sandbox permits reading of file metadata (content processes at
+ * this time), we expect the posix_spawnp error to be EPERM. In processes
+ * without that permission, we expect ENOENT. Changing the sandbox policy
+ * may break this assumption, but the important aspect to test for is that the
+ * launch is not permitted.
+ */
+void RunMacTestLaunchProcess(SandboxTestingChild* child,
+ int aPosixSpawnExpectedError = ENOENT) {
+ // Test that posix_spawnp fails
+ char* argv[2];
+ argv[0] = const_cast<char*>("bash");
+ argv[1] = NULL;
+ int rv = posix_spawnp(NULL, "/bin/bash", NULL, NULL, argv, NULL);
+ nsPrintfCString posixSpawnMessage("posix_spawnp returned %d, expected %d", rv,
+ aPosixSpawnExpectedError);
+ child->SendReportTestResults("posix_spawnp test"_ns,
+ rv == aPosixSpawnExpectedError,
+ posixSpawnMessage);
+
+ // Test that exec fails
+ child->ErrnoTest("execv /bin/bash test"_ns, false, [&] {
+ char* argvp = NULL;
+ return execv("/bin/bash", &argvp);
+ });
+
+ // Test that launching an application using LSOpenCFURLRef fails
+ char* uri;
+ if (nsCocoaFeatures::OnCatalinaOrLater()) {
+ uri = const_cast<char*>("/System/Applications/Utilities/Console.app");
+ } else {
+ uri = const_cast<char*>("/Applications/Utilities/Console.app");
+ }
+ CFStringRef filePath = ::CFStringCreateWithCString(kCFAllocatorDefault, uri,
+ kCFStringEncodingUTF8);
+ CFURLRef urlRef = ::CFURLCreateWithFileSystemPath(
+ kCFAllocatorDefault, filePath, kCFURLPOSIXPathStyle, false);
+ if (!urlRef) {
+ child->SendReportTestResults("LSOpenCFURLRef"_ns, false,
+ "CFURLCreateWithFileSystemPath failed"_ns);
+ return;
+ }
+
+ OSStatus status = ApplicationServices::LSOpenCFURLRef(urlRef, NULL);
+ ::CFRelease(urlRef);
+ nsPrintfCString lsMessage(
+ "LSOpenCFURLRef returned %d, "
+ "expected kLSServerCommunicationErr (%d)",
+ status, ApplicationServices::kLSServerCommunicationErr);
+ child->SendReportTestResults(
+ "LSOpenCFURLRef"_ns,
+ status == ApplicationServices::kLSServerCommunicationErr, lsMessage);
+}
+
+/*
+ * Test if this process can connect to the macOS window server.
+ * When |aShouldHaveAccess| is true, the test passes if access is __permitted__.
+ * When |aShouldHaveAccess| is false, the test passes if access is __blocked__.
+ */
+void RunMacTestWindowServer(SandboxTestingChild* child,
+ bool aShouldHaveAccess = false) {
+ // CGSessionCopyCurrentDictionary() returns NULL when a
+ // connection to the window server is not available.
+ CFDictionaryRef windowServerDict = CGSessionCopyCurrentDictionary();
+ bool gotWindowServerDetails = (windowServerDict != nullptr);
+ bool testPassed = (gotWindowServerDetails == aShouldHaveAccess);
+ child->SendReportTestResults(
+ "CGSessionCopyCurrentDictionary"_ns, testPassed,
+ gotWindowServerDetails
+ ? "dictionary returned, access is permitted"_ns
+ : "no dictionary returned, access appears blocked"_ns);
+ if (windowServerDict != nullptr) {
+ CFRelease(windowServerDict);
+ }
+}
+
+/*
+ * Test if this process can get access to audio components on macOS.
+ * When |aShouldHaveAccess| is true, the test passes if access is __permitted__.
+ * When |aShouldHaveAccess| is false, the test passes if access is __blocked__.
+ */
+void RunMacTestAudioAPI(SandboxTestingChild* child,
+ bool aShouldHaveAccess = false) {
+ AudioStreamBasicDescription inputFormat;
+ inputFormat.mFormatID = kAudioFormatMPEG4AAC;
+ inputFormat.mSampleRate = 48000.0;
+ inputFormat.mChannelsPerFrame = 2;
+ inputFormat.mBitsPerChannel = 0;
+ inputFormat.mFormatFlags = 0;
+ inputFormat.mFramesPerPacket = 1024;
+ inputFormat.mBytesPerPacket = 0;
+
+ UInt32 inputFormatSize = sizeof(inputFormat);
+ OSStatus status = AudioFormatGetProperty(
+ kAudioFormatProperty_FormatInfo, 0, NULL, &inputFormatSize, &inputFormat);
+
+ bool gotAudioFormat = (status == 0);
+ bool testPassed = (gotAudioFormat == aShouldHaveAccess);
+ child->SendReportTestResults(
+ "AudioFormatGetProperty"_ns, testPassed,
+ gotAudioFormat ? "got audio format, access is permitted"_ns
+ : "no audio format, access appears blocked"_ns);
+}
+#endif /* XP_MACOSX */
+
+#ifdef XP_WIN
+void RunWinTestWin32k(SandboxTestingChild* child,
+ bool aShouldHaveAccess = true) {
+ bool isLockedDown = (IsWin32kLockedDown() == true);
+ bool testPassed = (isLockedDown == aShouldHaveAccess);
+ child->SendReportTestResults(
+ "Win32kLockdown"_ns, testPassed,
+ isLockedDown ? "got lockdown, access is blocked"_ns
+ : "no lockdown, access appears permitted"_ns);
+}
+#endif // XP_WIN
+
+void RunTestsContent(SandboxTestingChild* child) {
+ MOZ_ASSERT(child, "No SandboxTestingChild*?");
+
+ RunGenericTests(child);
+
+#ifdef XP_UNIX
+ struct stat st;
+ static const char kAllowedPath[] = "/usr/lib";
+
+ child->ErrnoTest("fstatat_as_stat"_ns, true,
+ [&] { return fstatat(AT_FDCWD, kAllowedPath, &st, 0); });
+ child->ErrnoTest("fstatat_as_lstat"_ns, true, [&] {
+ return fstatat(AT_FDCWD, kAllowedPath, &st, AT_SYMLINK_NOFOLLOW);
+ });
+
+# ifdef XP_LINUX
+ child->ErrnoTest("fstatat_as_fstat"_ns, true,
+ [&] { return fstatat(0, "", &st, AT_EMPTY_PATH); });
+
+ const struct timespec usec = {0, 1000};
+ child->ErrnoTest("nanosleep"_ns, true,
+ [&] { return nanosleep(&usec, nullptr); });
+
+ struct timespec res = {0, 0};
+ child->ErrnoTest("clock_getres"_ns, true,
+ [&] { return clock_getres(CLOCK_REALTIME, &res); });
+
+ // same process is allowed
+ struct timespec tproc = {0, 0};
+ clockid_t same_process = MAKE_PROCESS_CPUCLOCK(getpid(), CPUCLOCK_SCHED);
+ child->ErrnoTest("clock_gettime_same_process"_ns, true,
+ [&] { return clock_gettime(same_process, &tproc); });
+
+ // different process is blocked by sandbox (SIGSYS, kernel would return
+ // EINVAL)
+ struct timespec tprocd = {0, 0};
+ clockid_t diff_process = MAKE_PROCESS_CPUCLOCK(1, CPUCLOCK_SCHED);
+ child->ErrnoValueTest("clock_gettime_diff_process"_ns, ENOSYS,
+ [&] { return clock_gettime(diff_process, &tprocd); });
+
+ // thread is allowed
+ struct timespec tthread = {0, 0};
+ clockid_t thread =
+ MAKE_THREAD_CPUCLOCK((pid_t)syscall(__NR_gettid), CPUCLOCK_SCHED);
+ child->ErrnoTest("clock_gettime_thread"_ns, true,
+ [&] { return clock_gettime(thread, &tthread); });
+
+ // getcpu is allowed
+ // We're using syscall directly because:
+ // - sched_getcpu uses vdso and as a result doesn't go through the sandbox.
+ // - getcpu isn't defined in the header files we're using yet.
+ int c;
+ child->ErrnoTest("getcpu"_ns, true,
+ [&] { return syscall(SYS_getcpu, &c, NULL, NULL); });
+
+ // An abstract socket that does not starts with '/', so we don't want it to
+ // work.
+ // Checking ENETUNREACH should be thrown by SandboxBrokerClient::Connect()
+ // when it detects it does not starts with a '/'
+ child->ErrnoValueTest("connect_abstract_blocked"_ns, ENETUNREACH, [&] {
+ int sockfd;
+ struct sockaddr_un addr;
+ char str[] = "\0xyz"; // Abstract socket requires first byte to be NULL
+ size_t str_size = 4;
+
+ memset(&addr, 0, sizeof(struct sockaddr_un));
+ addr.sun_family = AF_UNIX;
+ memcpy(&addr.sun_path, str, str_size);
+
+ sockfd = socket(AF_UNIX, SOCK_STREAM, 0);
+ if (sockfd == -1) {
+ return -1;
+ }
+
+ int con_st = connect(sockfd, (struct sockaddr*)&addr,
+ sizeof(sa_family_t) + str_size);
+ return con_st;
+ });
+
+ // An abstract socket that does starts with /, so we do want it to work.
+ // Checking ECONNREFUSED because this is what the broker should get
+ // when trying to establish the connect call for us if it's allowed;
+ // otherwise we get EACCES, meaning that it was passed to the broker
+ // (unlike the previous test) but rejected.
+ const int errorForX =
+ StaticPrefs::security_sandbox_content_headless_AtStartup() ? EACCES
+ : ECONNREFUSED;
+ child->ErrnoValueTest("connect_abstract_permit"_ns, errorForX, [&] {
+ int sockfd;
+ struct sockaddr_un addr;
+ // we re-use actual X path, because this is what is allowed within
+ // SandboxBrokerPolicyFactory::InitContentPolicy()
+ // We can't just use any random path allowed, but one with CONNECT allowed.
+
+ // (Note that the real X11 sockets have names like `X0` for
+ // display `:0`; there shouldn't be anything named just `X`.)
+
+ // Abstract socket requires first byte to be NULL
+ char str[] = "\0/tmp/.X11-unix/X";
+ size_t str_size = 17;
+
+ memset(&addr, 0, sizeof(struct sockaddr_un));
+ addr.sun_family = AF_UNIX;
+ memcpy(&addr.sun_path, str, str_size);
+
+ sockfd = socket(AF_UNIX, SOCK_STREAM, 0);
+ if (sockfd == -1) {
+ return -1;
+ }
+
+ int con_st = connect(sockfd, (struct sockaddr*)&addr,
+ sizeof(sa_family_t) + str_size);
+ return con_st;
+ });
+
+ // Testing FIPS-relevant files, which need to be accessible
+ std::vector<std::pair<const char*, bool>> open_tests = {
+ {"/dev/random", true}};
+ // Not all systems have that file, so we only test access, if it exists
+ // in the first place
+ if (stat("/proc/sys/crypto/fips_enabled", &st) == 0) {
+ open_tests.push_back({"/proc/sys/crypto/fips_enabled", true});
+ }
+
+ for (const std::pair<const char*, bool>& to_open : open_tests) {
+ child->ErrnoTest("open("_ns + nsCString(to_open.first) + ")"_ns,
+ to_open.second, [&] {
+ int fd = open(to_open.first, O_RDONLY);
+ if (to_open.second && fd > 0) {
+ close(fd);
+ }
+ return fd;
+ });
+ }
+
+ child->ErrnoTest("statfs"_ns, true, [] {
+ struct statfs sf;
+ return statfs("/usr/share", &sf);
+ });
+
+ child->ErrnoTest("pipe2"_ns, true, [] {
+ int fds[2];
+ int rv = pipe2(fds, O_CLOEXEC);
+ int savedErrno = errno;
+ if (rv == 0) {
+ close(fds[0]);
+ close(fds[1]);
+ }
+ errno = savedErrno;
+ return rv;
+ });
+
+ child->ErrnoValueTest("chroot"_ns, ENOSYS, [] { return chroot("/"); });
+
+ child->ErrnoValueTest("pipe2_notif"_ns, ENOSYS, [] {
+ int fds[2];
+ return pipe2(fds, O_NOTIFICATION_PIPE);
+ });
+
+# ifdef MOZ_X11
+ // Check that X11 access is blocked (bug 1129492).
+ // This will fail if security.sandbox.content.headless is turned off.
+ if (PR_GetEnv("DISPLAY")) {
+ Display* disp = XOpenDisplay(nullptr);
+
+ child->SendReportTestResults(
+ "x11_access"_ns, !disp,
+ disp ? "XOpenDisplay succeeded"_ns : "XOpenDisplay failed"_ns);
+ if (disp) {
+ XCloseDisplay(disp);
+ }
+ }
+# endif // MOZ_X11
+
+ child->ErrnoTest("realpath localtime"_ns, true, [] {
+ char buf[PATH_MAX];
+ return realpath("/etc/localtime", buf) ? 0 : -1;
+ });
+
+# endif // XP_LINUX
+
+# ifdef XP_MACOSX
+ RunMacTestLaunchProcess(child, EPERM);
+ RunMacTestWindowServer(child);
+ RunMacTestAudioAPI(child, true);
+# endif
+
+#elif XP_WIN
+ FileTest("read from chrome"_ns, NS_APP_USER_CHROME_DIR, u"sandboxTest.txt"_ns,
+ FILE_GENERIC_READ, true, child);
+ FileTest("read from profile via relative path"_ns, NS_APP_USER_CHROME_DIR,
+ u"..\\sandboxTest.txt"_ns, FILE_GENERIC_READ, false, child);
+ // The profile dir is the parent of the chrome dir.
+ FileTest("read from chrome using forward slash"_ns,
+ NS_APP_USER_PROFILE_50_DIR, u"chrome/sandboxTest.txt"_ns,
+ FILE_GENERIC_READ, false, child);
+
+ // Note: these only pass in DEBUG builds because we allow write access to the
+ // temp dir for certain test logs and that is where the profile is created.
+ FileTest("read from profile"_ns, NS_APP_USER_PROFILE_50_DIR,
+ u"sandboxTest.txt"_ns, FILE_GENERIC_READ, kIsDebug, child);
+ FileTest("read/write from chrome"_ns, NS_APP_USER_CHROME_DIR,
+ u"sandboxTest.txt"_ns, FILE_GENERIC_READ | FILE_GENERIC_WRITE,
+ kIsDebug, child);
+#else
+ child->ReportNoTests();
+#endif
+}
+
+void RunTestsSocket(SandboxTestingChild* child) {
+ MOZ_ASSERT(child, "No SandboxTestingChild*?");
+
+ RunGenericTests(child);
+
+#ifdef XP_UNIX
+ child->ErrnoTest("getaddrinfo"_ns, true, [&] {
+ struct addrinfo* res;
+ int rv = getaddrinfo("localhost", nullptr, nullptr, &res);
+ if (res != nullptr) {
+ freeaddrinfo(res);
+ }
+ return rv;
+ });
+
+# ifdef XP_LINUX
+ child->ErrnoTest("prctl_allowed"_ns, true, [&] {
+ int rv = prctl(PR_SET_DUMPABLE, 0, 0, 0, 0);
+ return rv;
+ });
+
+ child->ErrnoTest("prctl_blocked"_ns, false, [&] {
+ int rv = prctl(PR_GET_SECCOMP, 0, 0, 0, 0);
+ return rv;
+ });
+
+ // Testing FIPS-relevant files, which need to be accessible
+ std::vector<std::pair<const char*, bool>> open_tests = {
+ {"/dev/random", true}};
+ // Not all systems have that file, so we only test access, if it exists
+ // in the first place
+ struct stat st;
+ if (stat("/proc/sys/crypto/fips_enabled", &st) == 0) {
+ open_tests.push_back({"/proc/sys/crypto/fips_enabled", true});
+ }
+
+ for (const std::pair<const char*, bool>& to_open : open_tests) {
+ child->ErrnoTest("open("_ns + nsCString(to_open.first) + ")"_ns,
+ to_open.second, [&] {
+ int fd = open(to_open.first, O_RDONLY);
+ if (to_open.second && fd > 0) {
+ close(fd);
+ }
+ return fd;
+ });
+ }
+
+ // getcpu is allowed
+ // We're using syscall directly because:
+ // - sched_getcpu uses vdso and as a result doesn't go through the sandbox.
+ // - getcpu isn't defined in the header files we're using yet.
+ int c;
+ child->ErrnoTest("getcpu"_ns, true,
+ [&] { return syscall(SYS_getcpu, &c, NULL, NULL); });
+# endif // XP_LINUX
+#elif XP_MACOSX
+ RunMacTestLaunchProcess(child);
+ RunMacTestWindowServer(child);
+ RunMacTestAudioAPI(child);
+#else // XP_UNIX
+ child->ReportNoTests();
+#endif // XP_UNIX
+}
+
+void RunTestsRDD(SandboxTestingChild* child) {
+ MOZ_ASSERT(child, "No SandboxTestingChild*?");
+
+ RunGenericTests(child);
+
+#ifdef XP_UNIX
+# ifdef XP_LINUX
+ child->ErrnoValueTest("ioctl_tiocsti"_ns, 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;
+ });
+
+ child->ErrnoValueTest("unlink"_ns, ENOENT, [&] {
+ int rv = unlink("");
+ return rv;
+ });
+
+ child->ErrnoValueTest("unlinkat"_ns, ENOENT, [&] {
+ int rv = unlinkat(AT_FDCWD, "", 0);
+ return rv;
+ });
+
+ RunTestsSched(child);
+
+ child->ErrnoTest("socket_inet"_ns, false,
+ [] { return socket(AF_INET, SOCK_STREAM, 0); });
+
+ child->ErrnoTest("socket_unix"_ns, false,
+ [] { return socket(AF_UNIX, SOCK_STREAM, 0); });
+
+ child->ErrnoTest("uname"_ns, true, [] {
+ struct utsname uts;
+ return uname(&uts);
+ });
+
+ child->ErrnoValueTest("ioctl_dma_buf"_ns, ENOTTY, [] {
+ // Apply the ioctl to the wrong kind of fd; it should fail with
+ // ENOTTY (rather than ENOSYS if it were blocked).
+ return ioctl(0, _IOW('b', 0, uint64_t), nullptr);
+ });
+
+ // getcpu is allowed
+ // We're using syscall directly because:
+ // - sched_getcpu uses vdso and as a result doesn't go through the sandbox.
+ // - getcpu isn't defined in the header files we're using yet.
+ int c;
+ child->ErrnoTest("getcpu"_ns, true,
+ [&] { return syscall(SYS_getcpu, &c, NULL, NULL); });
+
+ // The nvidia proprietary drivers will, in some cases, try to
+ // mknod their device files; we reject this politely.
+ child->ErrnoValueTest("mknod"_ns, EPERM, [] {
+ return mknod("/dev/null", S_IFCHR | 0666, makedev(1, 3));
+ });
+
+ // nvidia defines some ioctls with the type 0x46 ('F', otherwise
+ // used by fbdev) and numbers starting from 200 (0xc8).
+ child->ErrnoValueTest("ioctl_nvidia"_ns, ENOTTY,
+ [] { return ioctl(0, 0x46c8, nullptr); });
+
+ child->ErrnoTest("statfs"_ns, true, [] {
+ struct statfs sf;
+ return statfs("/usr/share", &sf);
+ });
+
+# elif XP_MACOSX
+ RunMacTestLaunchProcess(child);
+ RunMacTestWindowServer(child);
+ RunMacTestAudioAPI(child, true);
+# endif
+#else // XP_UNIX
+# ifdef XP_WIN
+ RunWinTestWin32k(child, false);
+# endif // XP_WIN
+ child->ReportNoTests();
+#endif
+}
+
+void RunTestsGMPlugin(SandboxTestingChild* child) {
+ MOZ_ASSERT(child, "No SandboxTestingChild*?");
+
+ RunGenericTests(child, /* aIsGMP = */ true);
+
+#ifdef XP_UNIX
+# ifdef XP_LINUX
+ struct utsname utsname_res = {};
+ child->ErrnoTest("uname_restricted"_ns, true, [&] {
+ int rv = uname(&utsname_res);
+
+ nsCString expectedSysname("Linux"_ns);
+ nsCString sysname(utsname_res.sysname);
+ nsCString expectedVersion("3"_ns);
+ nsCString version(utsname_res.version);
+ if ((sysname != expectedSysname) || (version != expectedVersion)) {
+ return -1;
+ }
+
+ return rv;
+ });
+
+ child->ErrnoTest("getuid"_ns, true, [&] { return getuid(); });
+ child->ErrnoTest("getgid"_ns, true, [&] { return getgid(); });
+ child->ErrnoTest("geteuid"_ns, true, [&] { return geteuid(); });
+ child->ErrnoTest("getegid"_ns, true, [&] { return getegid(); });
+
+ RunTestsSched(child);
+
+ std::vector<std::pair<const char*, bool>> open_tests = {
+ {"/etc/ld.so.cache", true},
+ {"/proc/cpuinfo", true},
+ {"/etc/hostname", false}};
+
+ for (const std::pair<const char*, bool>& to_open : open_tests) {
+ child->ErrnoTest("open("_ns + nsCString(to_open.first) + ")"_ns,
+ to_open.second, [&] {
+ int fd = open(to_open.first, O_RDONLY);
+ if (to_open.second && fd > 0) {
+ close(fd);
+ }
+ return fd;
+ });
+ }
+
+ child->ErrnoValueTest("readlink_exe"_ns, EINVAL, [] {
+ char pathBuf[PATH_MAX];
+ return readlink("/proc/self/exe", pathBuf, sizeof(pathBuf));
+ });
+
+ child->ErrnoTest("memfd_sizing"_ns, true, [] {
+ int fd = syscall(__NR_memfd_create, "sandbox-test", 0);
+ if (fd < 0) {
+ if (errno == ENOSYS) {
+ // Don't fail the test if the kernel is old.
+ return 0;
+ }
+ return -1;
+ }
+
+ int rv = ftruncate(fd, 4096);
+ int savedErrno = errno;
+ close(fd);
+ errno = savedErrno;
+ return rv;
+ });
+
+# elif XP_MACOSX // XP_LINUX
+ RunMacTestLaunchProcess(child);
+ /* The Mac GMP process requires access to the window server */
+ RunMacTestWindowServer(child, true /* aShouldHaveAccess */);
+ RunMacTestAudioAPI(child);
+# endif // XP_MACOSX
+#else // XP_UNIX
+ child->ReportNoTests();
+#endif
+}
+
+void RunTestsGenericUtility(SandboxTestingChild* child) {
+ MOZ_ASSERT(child, "No SandboxTestingChild*?");
+
+ RunGenericTests(child);
+
+#ifdef XP_UNIX
+# ifdef XP_LINUX
+ child->ErrnoValueTest("ioctl_tiocsti"_ns, 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;
+ });
+# elif XP_MACOSX // XP_LINUX
+ RunMacTestLaunchProcess(child);
+ RunMacTestWindowServer(child);
+ RunMacTestAudioAPI(child);
+# endif // XP_MACOSX
+#elif XP_WIN // XP_UNIX
+ child->ErrnoValueTest("write_only"_ns, EACCES, [&] {
+ FILE* rv = fopen("test_sandbox.txt", "w");
+ if (rv != nullptr) {
+ fclose(rv);
+ return 0;
+ }
+ return -1;
+ });
+ RunWinTestWin32k(child);
+#else // XP_UNIX
+ child->ReportNoTests();
+#endif // XP_MACOSX
+}
+
+void RunTestsUtilityAudioDecoder(SandboxTestingChild* child,
+ ipc::SandboxingKind aSandbox) {
+ MOZ_ASSERT(child, "No SandboxTestingChild*?");
+
+ RunGenericTests(child);
+
+#ifdef XP_UNIX
+# ifdef XP_LINUX
+ // getrusage is allowed in Generic Utility and on AudioDecoder
+ struct rusage res;
+ child->ErrnoTest("getrusage"_ns, true, [&] {
+ int rv = getrusage(RUSAGE_SELF, &res);
+ return rv;
+ });
+
+ // get_mempolicy is not allowed in Generic Utility but is on AudioDecoder
+ child->ErrnoTest("get_mempolicy"_ns, true, [&] {
+ int numa_node;
+ int test_val = 0;
+ // <numaif.h> not installed by default, let's call directly the syscall
+ long rv = syscall(SYS_get_mempolicy, &numa_node, NULL, 0, (void*)&test_val,
+ MPOL_F_NODE | MPOL_F_ADDR);
+ return rv;
+ });
+ // set_mempolicy is not allowed in Generic Utility but is on AudioDecoder
+ child->ErrnoValueTest("set_mempolicy"_ns, ENOSYS, [&] {
+ // <numaif.h> not installed by default, let's call directly the syscall
+ long rv = syscall(SYS_set_mempolicy, 0, NULL, 0);
+ return rv;
+ });
+# elif XP_MACOSX // XP_LINUX
+ RunMacTestLaunchProcess(child);
+ RunMacTestWindowServer(child);
+ RunMacTestAudioAPI(
+ child,
+ aSandbox == ipc::SandboxingKind::UTILITY_AUDIO_DECODING_APPLE_MEDIA);
+# endif // XP_MACOSX
+#else // XP_UNIX
+# ifdef XP_WIN
+ RunWinTestWin32k(child);
+# endif // XP_WIN
+ child->ReportNoTests();
+#endif // XP_UNIX
+}
+
+void RunTestsGPU(SandboxTestingChild* child) {
+ MOZ_ASSERT(child, "No SandboxTestingChild*?");
+
+ RunGenericTests(child);
+
+#if defined(XP_WIN)
+
+ FileTest("R/W access to shader-cache dir"_ns, NS_APP_USER_PROFILE_50_DIR,
+ u"shader-cache\\"_ns, FILE_GENERIC_READ | FILE_GENERIC_WRITE, true,
+ child);
+
+ FileTest("R/W access to shader-cache files"_ns, NS_APP_USER_PROFILE_50_DIR,
+ u"shader-cache\\sandboxTest.txt"_ns,
+ FILE_GENERIC_READ | FILE_GENERIC_WRITE, true, child);
+
+#else // defined(XP_WIN)
+ child->ReportNoTests();
+#endif // defined(XP_WIN)
+}
+
+} // namespace mozilla
diff --git a/security/sandbox/common/test/SandboxTestingParent.cpp b/security/sandbox/common/test/SandboxTestingParent.cpp
new file mode 100644
index 0000000000..dff2d03896
--- /dev/null
+++ b/security/sandbox/common/test/SandboxTestingParent.cpp
@@ -0,0 +1,125 @@
+/* -*- 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 https://mozilla.org/MPL/2.0/. */
+
+#include "SandboxTestingParent.h"
+#include "SandboxTestingThread.h"
+#include "nsIObserverService.h"
+#include "mozilla/ipc/Endpoint.h"
+#include "mozilla/Services.h"
+#include "mozilla/SyncRunnable.h"
+#include "nsDirectoryServiceUtils.h"
+
+namespace mozilla {
+
+/* static */
+already_AddRefed<SandboxTestingParent> SandboxTestingParent::Create(
+ Endpoint<PSandboxTestingParent>&& aParentEnd) {
+ SandboxTestingThread* thread = SandboxTestingThread::Create();
+ if (!thread) {
+ return nullptr;
+ }
+ RefPtr<SandboxTestingParent> instance = new SandboxTestingParent(thread);
+ thread->Dispatch(NewRunnableMethod<Endpoint<PSandboxTestingParent>&&>(
+ "SandboxTestingParent::Bind", instance, &SandboxTestingParent::Bind,
+ std::move(aParentEnd)));
+ return instance.forget();
+}
+
+SandboxTestingParent::SandboxTestingParent(SandboxTestingThread* aThread)
+ : mThread(aThread),
+ mMonitor("SandboxTestingParent Lock"),
+ mShutdownDone(false) {}
+
+SandboxTestingParent::~SandboxTestingParent() = default;
+
+void SandboxTestingParent::Bind(Endpoint<PSandboxTestingParent>&& aEnd) {
+ MOZ_RELEASE_ASSERT(mThread->IsOnThread());
+ DebugOnly<bool> ok = aEnd.Bind(this);
+ MOZ_ASSERT(ok);
+}
+
+void SandboxTestingParent::ShutdownSandboxTestThread() {
+ MOZ_ASSERT(mThread->IsOnThread());
+ Close();
+ // Notify waiting thread that we are done.
+ MonitorAutoLock lock(mMonitor);
+ mShutdownDone = true;
+ mMonitor.Notify();
+}
+
+void SandboxTestingParent::Destroy(
+ already_AddRefed<SandboxTestingParent> aInstance) {
+ MOZ_ASSERT(NS_IsMainThread());
+ RefPtr<SandboxTestingParent> instance(aInstance);
+ if (!instance) {
+ return;
+ }
+
+ {
+ // Hold the lock while we destroy the actor on the test thread.
+ MonitorAutoLock lock(instance->mMonitor);
+ instance->mThread->Dispatch(NewRunnableMethod(
+ "SandboxTestingParent::ShutdownSandboxTestThread", instance,
+ &SandboxTestingParent::ShutdownSandboxTestThread));
+
+ // Wait for test thread to complete destruction.
+ while (!instance->mShutdownDone) {
+ instance->mMonitor.Wait();
+ }
+ }
+}
+
+void SandboxTestingParent::ActorDestroy(ActorDestroyReason aWhy) {
+ MOZ_RELEASE_ASSERT(mThread->IsOnThread());
+}
+
+mozilla::ipc::IPCResult SandboxTestingParent::RecvReportTestResults(
+ const nsCString& testName, bool passed, const nsCString& resultMessage) {
+ NS_DispatchToMainThread(
+ NS_NewRunnableFunction("SandboxReportTestResults", [=]() {
+ nsCOMPtr<nsIObserverService> observerService =
+ mozilla::services::GetObserverService();
+ MOZ_RELEASE_ASSERT(observerService);
+ nsCString passedStr(passed ? "true"_ns : "false"_ns);
+ nsString json;
+ json += u"{ \"testid\" : \""_ns + NS_ConvertUTF8toUTF16(testName) +
+ u"\", \"passed\" : "_ns + NS_ConvertUTF8toUTF16(passedStr) +
+ u", \"message\" : \""_ns +
+ NS_ConvertUTF8toUTF16(resultMessage) + u"\" }"_ns;
+ observerService->NotifyObservers(nullptr, "sandbox-test-result",
+ json.BeginReading());
+ }));
+ return IPC_OK();
+}
+
+mozilla::ipc::IPCResult SandboxTestingParent::RecvTestCompleted() {
+ Unused << SendShutDown();
+ NS_DispatchToMainThread(
+ NS_NewRunnableFunction("SandboxReportTestResults", []() {
+ nsCOMPtr<nsIObserverService> observerService =
+ mozilla::services::GetObserverService();
+ MOZ_RELEASE_ASSERT(observerService);
+ observerService->NotifyObservers(nullptr, "sandbox-test-done", 0);
+ }));
+ return IPC_OK();
+}
+
+mozilla::ipc::IPCResult SandboxTestingParent::RecvGetSpecialDirectory(
+ const nsCString& aSpecialDirName, nsString* aDirPath) {
+ RefPtr<Runnable> runnable = NS_NewRunnableFunction(
+ "SandboxTestingParent::RecvGetSpecialDirectory", [&]() {
+ nsCOMPtr<nsIFile> dir;
+ NS_GetSpecialDirectory(aSpecialDirName.get(), getter_AddRefs(dir));
+ if (dir) {
+ dir->GetPath(*aDirPath);
+ }
+ });
+ SyncRunnable::DispatchToThread(GetMainThreadSerialEventTarget(), runnable,
+ /*aForceDispatch*/ true);
+ return IPC_OK();
+}
+
+} // namespace mozilla
diff --git a/security/sandbox/common/test/SandboxTestingParent.h b/security/sandbox/common/test/SandboxTestingParent.h
new file mode 100644
index 0000000000..5a8ef9145a
--- /dev/null
+++ b/security/sandbox/common/test/SandboxTestingParent.h
@@ -0,0 +1,53 @@
+/* -*- 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 https://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_SandboxTestingParent_h
+#define mozilla_SandboxTestingParent_h
+
+#include "mozilla/PSandboxTestingParent.h"
+#include "mozilla/Monitor.h"
+#include "mozilla/UniquePtr.h"
+
+#if !defined(MOZ_SANDBOX) || !defined(MOZ_DEBUG) || !defined(ENABLE_TESTS)
+# error "This file should not be used outside of debug with tests"
+#endif
+
+namespace mozilla {
+
+class SandboxTestingThread;
+
+class SandboxTestingParent : public PSandboxTestingParent {
+ public:
+ static already_AddRefed<SandboxTestingParent> Create(
+ Endpoint<PSandboxTestingParent>&& aParentEnd);
+ static void Destroy(already_AddRefed<SandboxTestingParent> aInstance);
+
+ NS_INLINE_DECL_THREADSAFE_REFCOUNTING(SandboxTestingParent, override)
+
+ void ActorDestroy(ActorDestroyReason aWhy) override;
+
+ mozilla::ipc::IPCResult RecvReportTestResults(const nsCString& testName,
+ bool passed,
+ const nsCString& resultMessage);
+ mozilla::ipc::IPCResult RecvTestCompleted();
+
+ mozilla::ipc::IPCResult RecvGetSpecialDirectory(
+ const nsCString& aSpecialDirName, nsString* aDirPath);
+
+ private:
+ explicit SandboxTestingParent(SandboxTestingThread* aThread);
+ virtual ~SandboxTestingParent();
+ void ShutdownSandboxTestThread();
+ void Bind(Endpoint<PSandboxTestingParent>&& aEnd);
+
+ UniquePtr<SandboxTestingThread> mThread;
+ Monitor mMonitor MOZ_UNANNOTATED;
+ bool mShutdownDone;
+};
+
+} // namespace mozilla
+
+#endif // mozilla_SandboxTestingParent_h
diff --git a/security/sandbox/common/test/SandboxTestingThread.h b/security/sandbox/common/test/SandboxTestingThread.h
new file mode 100644
index 0000000000..f85a017f94
--- /dev/null
+++ b/security/sandbox/common/test/SandboxTestingThread.h
@@ -0,0 +1,53 @@
+/* -*- 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 https://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_SandboxTestingThread_h
+#define mozilla_SandboxTestingThread_h
+
+#include "nsThreadManager.h"
+
+#if !defined(MOZ_SANDBOX) || !defined(MOZ_DEBUG) || !defined(ENABLE_TESTS)
+# error "This file should not be used outside of debug with tests"
+#endif
+
+namespace mozilla {
+
+class SandboxTestingThread {
+ public:
+ void Dispatch(already_AddRefed<nsIRunnable>&& aRunnable) {
+ mThread->Dispatch(std::move(aRunnable), nsIEventTarget::NS_DISPATCH_NORMAL);
+ }
+
+ bool IsOnThread() {
+ bool on;
+ return NS_SUCCEEDED(mThread->IsOnCurrentThread(&on)) && on;
+ }
+
+ static SandboxTestingThread* Create() {
+ MOZ_RELEASE_ASSERT(NS_IsMainThread());
+ nsCOMPtr<nsIThread> thread;
+ if (NS_FAILED(
+ NS_NewNamedThread("Sandbox Testing", getter_AddRefs(thread)))) {
+ return nullptr;
+ }
+ return new SandboxTestingThread(thread);
+ }
+
+ ~SandboxTestingThread() {
+ NS_DispatchToMainThread(NewRunnableMethod("~SandboxTestingThread", mThread,
+ &nsIThread::Shutdown));
+ }
+
+ private:
+ explicit SandboxTestingThread(nsIThread* aThread) : mThread(aThread) {
+ MOZ_ASSERT(mThread);
+ }
+
+ nsCOMPtr<nsIThread> mThread;
+};
+} // namespace mozilla
+
+#endif // mozilla_SandboxTestingThread_h
diff --git a/security/sandbox/common/test/mozISandboxTest.idl b/security/sandbox/common/test/mozISandboxTest.idl
new file mode 100644
index 0000000000..7cde1defcf
--- /dev/null
+++ b/security/sandbox/common/test/mozISandboxTest.idl
@@ -0,0 +1,28 @@
+/* -*- Mode: IDL; 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 "nsISupports.idl"
+
+// This interface is only for testing Sandbox.
+
+[scriptable, builtinclass, uuid(2306c118-3544-4674-9222-670b88dc07a9)]
+interface mozISandboxTest : nsISupports
+{
+ void startTests(in Array<ACString> aProcessesList);
+ void finishTests();
+};
+
+%{ C++
+
+#if defined(MOZ_SANDBOX) && defined(MOZ_DEBUG) && defined(ENABLE_TESTS)
+#define MOZ_SANDBOX_TEST_CID \
+ {0x989dda27, 0xb144, 0x45f9, {0x90, 0x39, 0x69, 0x74, 0x4e, 0xc6, dd0xd9, 0x12}}
+#define MOZ_SANDBOX_TEST_CONTRACTID \
+ "@mozilla.org/sandbox/sandbox-test;1"
+#else
+#error "This file should not be used outside of debug with tests"
+#endif
+%}