362 lines
14 KiB
C++
362 lines
14 KiB
C++
/* -*- 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::GMPContentParentCloseBlocker>&
|
|
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
|
|
RefPtr<net::SocketProcessParent> parent =
|
|
net::SocketProcessParent::GetSingleton();
|
|
if (parent) {
|
|
return InitializeSandboxTestingActors(parent.get(), 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](LaunchError const&) {
|
|
MOZ_ASSERT_UNREACHABLE(
|
|
"SandboxTest; failure to get Utility process");
|
|
return processPromise->Reject(NS_ERROR_FAILURE, __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>();
|
|
}
|