diff options
Diffstat (limited to 'security/sandbox/win')
20 files changed, 3317 insertions, 0 deletions
diff --git a/security/sandbox/win/SandboxInitialization.cpp b/security/sandbox/win/SandboxInitialization.cpp new file mode 100644 index 0000000000..8ba8b4e69a --- /dev/null +++ b/security/sandbox/win/SandboxInitialization.cpp @@ -0,0 +1,202 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=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 "SandboxInitialization.h" + +#include "base/memory/ref_counted.h" +#include "nsWindowsDllInterceptor.h" +#include "sandbox/win/src/process_mitigations.h" +#include "sandbox/win/src/sandbox_factory.h" +#include "mozilla/DebugOnly.h" +#include "mozilla/WindowsProcessMitigations.h" + +namespace mozilla { +namespace sandboxing { + +typedef BOOL(WINAPI* CloseHandle_func)(HANDLE hObject); +static WindowsDllInterceptor::FuncHookType<CloseHandle_func> stub_CloseHandle; + +typedef BOOL(WINAPI* DuplicateHandle_func)( + HANDLE hSourceProcessHandle, HANDLE hSourceHandle, + HANDLE hTargetProcessHandle, LPHANDLE lpTargetHandle, DWORD dwDesiredAccess, + BOOL bInheritHandle, DWORD dwOptions); +static WindowsDllInterceptor::FuncHookType<DuplicateHandle_func> + stub_DuplicateHandle; + +static BOOL WINAPI patched_CloseHandle(HANDLE hObject) { + // Check all handles being closed against the sandbox's tracked handles. + base::win::OnHandleBeingClosed(hObject); + return stub_CloseHandle(hObject); +} + +static BOOL WINAPI patched_DuplicateHandle( + HANDLE hSourceProcessHandle, HANDLE hSourceHandle, + HANDLE hTargetProcessHandle, LPHANDLE lpTargetHandle, DWORD dwDesiredAccess, + BOOL bInheritHandle, DWORD dwOptions) { + // If closing a source handle from our process check it against the sandbox's + // tracked handles. + if ((dwOptions & DUPLICATE_CLOSE_SOURCE) && + (GetProcessId(hSourceProcessHandle) == ::GetCurrentProcessId())) { + base::win::OnHandleBeingClosed(hSourceHandle); + } + + return stub_DuplicateHandle(hSourceProcessHandle, hSourceHandle, + hTargetProcessHandle, lpTargetHandle, + dwDesiredAccess, bInheritHandle, dwOptions); +} + +typedef BOOL(WINAPI* ApiSetQueryApiSetPresence_func)(PCUNICODE_STRING, + PBOOLEAN); +static WindowsDllInterceptor::FuncHookType<ApiSetQueryApiSetPresence_func> + stub_ApiSetQueryApiSetPresence; + +static const WCHAR gApiSetNtUserWindowStation[] = + L"ext-ms-win-ntuser-windowstation-l1-1-0"; + +static BOOL WINAPI patched_ApiSetQueryApiSetPresence( + PCUNICODE_STRING aNamespace, PBOOLEAN aPresent) { + if (aNamespace && aPresent && + !wcsncmp(aNamespace->Buffer, gApiSetNtUserWindowStation, + aNamespace->Length / sizeof(WCHAR))) { + *aPresent = FALSE; + return TRUE; + } + + return stub_ApiSetQueryApiSetPresence(aNamespace, aPresent); +} + +static WindowsDllInterceptor Kernel32Intercept; +static WindowsDllInterceptor gApiQueryIntercept; + +static bool EnableHandleCloseMonitoring() { + Kernel32Intercept.Init("kernel32.dll"); + bool hooked = stub_CloseHandle.Set(Kernel32Intercept, "CloseHandle", + &patched_CloseHandle); + if (!hooked) { + return false; + } + + hooked = stub_DuplicateHandle.Set(Kernel32Intercept, "DuplicateHandle", + &patched_DuplicateHandle); + if (!hooked) { + return false; + } + + return true; +} + +/** + * There is a bug in COM that causes its initialization to fail when user32.dll + * is loaded but Win32k lockdown is enabled. COM uses ApiSetQueryApiSetPresence + * to make this check. When we are under Win32k lockdown, we hook + * ApiSetQueryApiSetPresence and force it to tell the caller that the DLL of + * interest is not present. + */ +static void EnableApiQueryInterception() { + if (!IsWin32kLockedDown()) { + return; + } + + gApiQueryIntercept.Init(L"Api-ms-win-core-apiquery-l1-1-0.dll"); + DebugOnly<bool> hookSetOk = stub_ApiSetQueryApiSetPresence.Set( + gApiQueryIntercept, "ApiSetQueryApiSetPresence", + &patched_ApiSetQueryApiSetPresence); + MOZ_ASSERT(hookSetOk); +} + +static bool ShouldDisableHandleVerifier() { +#if defined(_X86_) && (defined(EARLY_BETA_OR_EARLIER) || defined(DEBUG)) + // Chromium only has the verifier enabled for 32-bit and our close monitoring + // hooks cause debug assertions for 64-bit anyway. + // For x86 keep the verifier enabled by default only for Nightly or debug. + return false; +#else + return !getenv("MOZ_ENABLE_HANDLE_VERIFIER"); +#endif +} + +static void InitializeHandleVerifier() { + // Disable the handle verifier if we don't want it or can't enable the close + // monitoring hooks. + if (ShouldDisableHandleVerifier() || !EnableHandleCloseMonitoring()) { + base::win::DisableHandleVerifier(); + } +} + +static sandbox::TargetServices* InitializeTargetServices() { + // This might disable the verifier, so we want to do it before it is used. + InitializeHandleVerifier(); + + EnableApiQueryInterception(); + + sandbox::TargetServices* targetServices = + sandbox::SandboxFactory::GetTargetServices(); + if (!targetServices) { + return nullptr; + } + + if (targetServices->Init() != sandbox::SBOX_ALL_OK) { + return nullptr; + } + + return targetServices; +} + +sandbox::TargetServices* GetInitializedTargetServices() { + static sandbox::TargetServices* sInitializedTargetServices = + InitializeTargetServices(); + + return sInitializedTargetServices; +} + +void LowerSandbox() { GetInitializedTargetServices()->LowerToken(); } + +static sandbox::BrokerServices* InitializeBrokerServices() { + // This might disable the verifier, so we want to do it before it is used. + InitializeHandleVerifier(); + + sandbox::BrokerServices* brokerServices = + sandbox::SandboxFactory::GetBrokerServices(); + if (!brokerServices) { + return nullptr; + } + + if (brokerServices->Init() != sandbox::SBOX_ALL_OK) { + return nullptr; + } + + // Comment below copied from Chromium code. + // Precreate the desktop and window station used by the renderers. + // IMPORTANT: This piece of code needs to run as early as possible in the + // process because it will initialize the sandbox broker, which requires + // the process to swap its window station. During this time all the UI + // will be broken. This has to run before threads and windows are created. + scoped_refptr<sandbox::TargetPolicy> policy = brokerServices->CreatePolicy(); + policy->CreateAlternateDesktop(true); + + // Ensure the relevant mitigations are enforced. + mozilla::sandboxing::ApplyParentProcessMitigations(); + + return brokerServices; +} + +sandbox::BrokerServices* GetInitializedBrokerServices() { + static sandbox::BrokerServices* sInitializedBrokerServices = + InitializeBrokerServices(); + + return sInitializedBrokerServices; +} + +void ApplyParentProcessMitigations() { + // The main reason for this call is for the token hardening, but chromium code + // also ensures DEP without ATL thunk so we do the same. + sandbox::ApplyProcessMitigationsToCurrentProcess( + sandbox::MITIGATION_DEP | sandbox::MITIGATION_DEP_NO_ATL_THUNK | + sandbox::MITIGATION_HARDEN_TOKEN_IL_POLICY); +} + +} // namespace sandboxing +} // namespace mozilla diff --git a/security/sandbox/win/SandboxInitialization.h b/security/sandbox/win/SandboxInitialization.h new file mode 100644 index 0000000000..1d49c0d899 --- /dev/null +++ b/security/sandbox/win/SandboxInitialization.h @@ -0,0 +1,52 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=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/. */ + +#ifndef mozilla_sandboxing_SandboxInitialization_h +#define mozilla_sandboxing_SandboxInitialization_h + +namespace sandbox { +class BrokerServices; +class TargetServices; +} // namespace sandbox + +// Things that use this file will probably want access to the IsSandboxedProcess +// function defined in one of the Chromium sandbox cc files. +extern "C" bool IsSandboxedProcess(); + +namespace mozilla { +// Note the Chromium code just uses a bare sandbox namespace, which makes using +// sandbox for our namespace painful. +namespace sandboxing { + +/** + * Initializes (if required) and returns the Chromium sandbox TargetServices. + * + * @return the TargetServices or null if the creation or initialization failed. + */ +sandbox::TargetServices* GetInitializedTargetServices(); + +/** + * Lowers the permissions on the process sandbox. + * Provided because the GMP sandbox needs to be lowered from the executable. + */ +void LowerSandbox(); + +/** + * Initializes (if required) and returns the Chromium sandbox BrokerServices. + * + * @return the BrokerServices or null if the creation or initialization failed. + */ +sandbox::BrokerServices* GetInitializedBrokerServices(); + +/** + * Apply mitigations for parent processes. + */ +void ApplyParentProcessMitigations(); + +} // namespace sandboxing +} // namespace mozilla + +#endif // mozilla_sandboxing_SandboxInitialization_h diff --git a/security/sandbox/win/src/remotesandboxbroker/PRemoteSandboxBroker.ipdl b/security/sandbox/win/src/remotesandboxbroker/PRemoteSandboxBroker.ipdl new file mode 100644 index 0000000000..7057a2ca42 --- /dev/null +++ b/security/sandbox/win/src/remotesandboxbroker/PRemoteSandboxBroker.ipdl @@ -0,0 +1,36 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* 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/. */ + +using mozilla::dom::NativeThreadId from "mozilla/dom/NativeThreadId.h"; + +namespace mozilla { + +struct EnvVar { + nsString name; + nsString value; +}; + +struct LaunchParameters { + nsString path; + nsString args; + EnvVar[] env; + uint32_t processType; + uint32_t sandboxLevel; + nsString[] allowedReadFiles; + uint64_t[] shareHandles; + bool enableLogging; +}; + +[NeedsOtherPid, NestedUpTo=inside_sync] +sync protocol PRemoteSandboxBroker +{ +parent: + async InitCrashReporter(NativeThreadId threadId); +child: + [Nested=inside_sync] sync LaunchApp(LaunchParameters params) + returns (bool ok, uint64_t handle); +}; + +} // namespace mozilla diff --git a/security/sandbox/win/src/remotesandboxbroker/RemoteSandboxBrokerChild.cpp b/security/sandbox/win/src/remotesandboxbroker/RemoteSandboxBrokerChild.cpp new file mode 100644 index 0000000000..0ce2a60a8e --- /dev/null +++ b/security/sandbox/win/src/remotesandboxbroker/RemoteSandboxBrokerChild.cpp @@ -0,0 +1,97 @@ +/* -*- 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 "RemoteSandboxBrokerChild.h" +#include "chrome/common/ipc_channel.h" +#include "mozilla/ipc/CrashReporterClient.h" +#include "nsDebugImpl.h" +#include "mozilla/ipc/CrashReporterClient.h" +#include "RemoteSandboxBrokerProcessChild.h" + +using namespace mozilla::ipc; + +namespace mozilla { + +RemoteSandboxBrokerChild::RemoteSandboxBrokerChild() { + nsDebugImpl::SetMultiprocessMode("RemoteSandboxBroker"); +} + +RemoteSandboxBrokerChild::~RemoteSandboxBrokerChild() {} + +bool RemoteSandboxBrokerChild::Init(mozilla::ipc::UntypedEndpoint&& aEndpoint) { + if (NS_WARN_IF(!aEndpoint.Bind(this))) { + return false; + } + CrashReporterClient::InitSingleton(this); + return true; +} + +void RemoteSandboxBrokerChild::ActorDestroy(ActorDestroyReason aWhy) { + if (AbnormalShutdown == aWhy) { + NS_WARNING("Abnormal shutdown of GMP process!"); + ipc::ProcessChild::QuickExit(); + } + CrashReporterClient::DestroySingleton(); + XRE_ShutdownChildProcess(); +} + +mozilla::ipc::IPCResult RemoteSandboxBrokerChild::RecvLaunchApp( + LaunchParameters&& aParams, bool* aOutOk, uint64_t* aOutHandle) { + auto towstring = [](const nsString& s) { + return std::wstring(s.get(), s.Length()); + }; + + base::EnvironmentMap envmap; + for (const EnvVar& env : aParams.env()) { + envmap[towstring(env.name())] = towstring(env.value()); + } + + // We need to add our parent as a target peer, so that the sandboxed child can + // duplicate handles to it for crash reporting. AddTargetPeer duplicates the + // handle, so we use a ScopedProcessHandle to automatically close ours. + ipc::ScopedProcessHandle parentProcHandle; + if (!base::OpenProcessHandle(OtherPid(), &parentProcHandle.rwget())) { + *aOutOk = false; + return IPC_OK(); + } + mSandboxBroker.AddTargetPeer(parentProcHandle); + + if (!mSandboxBroker.SetSecurityLevelForGMPlugin( + AbstractSandboxBroker::SandboxLevel(aParams.sandboxLevel()), + /* aIsRemoteLaunch */ true)) { + *aOutOk = false; + return IPC_OK(); + } + + for (const auto& path : aParams.allowedReadFiles()) { + if (!mSandboxBroker.AllowReadFile(path.get())) { + *aOutOk = false; + return IPC_OK(); + } + } + + for (const auto& handle : aParams.shareHandles()) { + mSandboxBroker.AddHandleToShare(HANDLE(handle)); + } + + HANDLE p; + mozilla::Result<mozilla::Ok, LaunchError> err = + mSandboxBroker.LaunchApp(aParams.path().get(), aParams.args().get(), + envmap, GeckoProcessType(aParams.processType()), + aParams.enableLogging(), nullptr, (void**)&p); + *aOutOk = err.isOk(); + if (*aOutOk) { + *aOutHandle = uint64_t(p); + } + + for (const auto& handle : aParams.shareHandles()) { + CloseHandle(HANDLE(handle)); + } + + return IPC_OK(); +} + +} // namespace mozilla diff --git a/security/sandbox/win/src/remotesandboxbroker/RemoteSandboxBrokerChild.h b/security/sandbox/win/src/remotesandboxbroker/RemoteSandboxBrokerChild.h new file mode 100644 index 0000000000..cc707609df --- /dev/null +++ b/security/sandbox/win/src/remotesandboxbroker/RemoteSandboxBrokerChild.h @@ -0,0 +1,35 @@ +/* -*- 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 RemoteSandboxBrokerChild_h_ +#define RemoteSandboxBrokerChild_h_ + +#include "mozilla/PRemoteSandboxBrokerChild.h" +#include "sandboxBroker.h" + +namespace mozilla { + +class RemoteSandboxBrokerChild : public PRemoteSandboxBrokerChild { + friend class PRemoteSandboxBrokerChild; + + public: + NS_INLINE_DECL_REFCOUNTING(RemoteSandboxBrokerChild, override) + + RemoteSandboxBrokerChild(); + bool Init(mozilla::ipc::UntypedEndpoint&& aEndpoint); + + private: + virtual ~RemoteSandboxBrokerChild(); + mozilla::ipc::IPCResult RecvLaunchApp(LaunchParameters&& aParams, + bool* aOutOk, uint64_t* aOutHandle); + + void ActorDestroy(ActorDestroyReason aWhy); + SandboxBroker mSandboxBroker; +}; + +} // namespace mozilla + +#endif diff --git a/security/sandbox/win/src/remotesandboxbroker/RemoteSandboxBrokerParent.cpp b/security/sandbox/win/src/remotesandboxbroker/RemoteSandboxBrokerParent.cpp new file mode 100644 index 0000000000..7306f4f7bf --- /dev/null +++ b/security/sandbox/win/src/remotesandboxbroker/RemoteSandboxBrokerParent.cpp @@ -0,0 +1,83 @@ +/* -*- 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 "RemoteSandboxBrokerParent.h" +#include "RemoteSandboxBrokerProcessParent.h" +#include "mozilla/Telemetry.h" +#include <windows.h> + +namespace mozilla { + +RefPtr<GenericPromise> RemoteSandboxBrokerParent::Launch( + uint32_t aLaunchArch, const nsTArray<uint64_t>& aHandlesToShare, + nsISerialEventTarget* aThread) { + MOZ_ASSERT(!mProcess); + if (mProcess) { + // Don't re-init. + return GenericPromise::CreateAndReject(NS_ERROR_FAILURE, __func__); + } + + mProcess = new RemoteSandboxBrokerProcessParent(); +#ifdef ALLOW_GECKO_CHILD_PROCESS_ARCH + mProcess->SetLaunchArchitecture(aLaunchArch); +#endif + for (uint64_t handle : aHandlesToShare) { + mProcess->AddHandleToShare(HANDLE(handle)); + } + + auto resolve = [self = RefPtr{this}](base::ProcessHandle handle) { + self->mOpened = self->mProcess->TakeInitialEndpoint().Bind(self); + if (!self->mOpened) { + self->mProcess->Destroy(); + self->mProcess = nullptr; + return GenericPromise::CreateAndReject(NS_ERROR_FAILURE, __func__); + } + return GenericPromise::CreateAndResolve(true, __func__); + }; + + auto reject = [self = RefPtr{this}]() { + NS_ERROR("failed to launch child in the parent"); + if (self->mProcess) { + self->mProcess->Destroy(); + self->mProcess = nullptr; + } + return GenericPromise::CreateAndReject(NS_ERROR_FAILURE, __func__); + }; + + return mProcess->AsyncLaunch()->Then(aThread, __func__, std::move(resolve), + std::move(reject)); +} + +bool RemoteSandboxBrokerParent::DuplicateFromLauncher(HANDLE aLauncherHandle, + LPHANDLE aOurHandle) { + return ::DuplicateHandle(mProcess->GetChildProcessHandle(), aLauncherHandle, + ::GetCurrentProcess(), aOurHandle, 0, false, + DUPLICATE_SAME_ACCESS | DUPLICATE_CLOSE_SOURCE); +} + +void RemoteSandboxBrokerParent::ActorDestroy(ActorDestroyReason aWhy) { + if (AbnormalShutdown == aWhy) { + Telemetry::Accumulate(Telemetry::SUBPROCESS_ABNORMAL_ABORT, + nsDependentCString(XRE_GeckoProcessTypeToString( + GeckoProcessType_RemoteSandboxBroker)), + 1); + GenerateCrashReport(OtherPid()); + } + Shutdown(); +} + +void RemoteSandboxBrokerParent::Shutdown() { + if (mOpened) { + mOpened = false; + Close(); + } + if (mProcess) { + mProcess->Destroy(); + mProcess = nullptr; + } +} + +} // namespace mozilla diff --git a/security/sandbox/win/src/remotesandboxbroker/RemoteSandboxBrokerParent.h b/security/sandbox/win/src/remotesandboxbroker/RemoteSandboxBrokerParent.h new file mode 100644 index 0000000000..7645b68923 --- /dev/null +++ b/security/sandbox/win/src/remotesandboxbroker/RemoteSandboxBrokerParent.h @@ -0,0 +1,48 @@ +/* -*- 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 RemoteSandboxBrokerParent_h_ +#define RemoteSandboxBrokerParent_h_ + +#include "mozilla/PRemoteSandboxBrokerParent.h" +#include "RemoteSandboxBrokerProcessParent.h" +#include "mozilla/ipc/CrashReporterHelper.h" + +namespace mozilla { + +class RemoteSandboxBrokerParent + : public PRemoteSandboxBrokerParent, + public ipc::CrashReporterHelper<GeckoProcessType_RemoteSandboxBroker> { + friend class PRemoteSandboxBrokerParent; + + public: + NS_INLINE_DECL_REFCOUNTING(RemoteSandboxBrokerParent, override) + + bool DuplicateFromLauncher(HANDLE aLauncherHandle, LPHANDLE aOurHandle); + + void Shutdown(); + + // Asynchronously launches the launcher process. + // Note: we rely on the caller to keep this instance alive + // until this promise resolves. + // aThread is the thread to use to resolve the promise on if needed. + RefPtr<GenericPromise> Launch(uint32_t aLaunchArch, + const nsTArray<uint64_t>& aHandlesToShare, + nsISerialEventTarget* aThread); + + private: + ~RemoteSandboxBrokerParent() = default; + + void ActorDestroy(ActorDestroyReason aWhy) override; + + RemoteSandboxBrokerProcessParent* mProcess = nullptr; + + bool mOpened = false; +}; + +} // namespace mozilla + +#endif // RemoteSandboxBrokerParent_h_ diff --git a/security/sandbox/win/src/remotesandboxbroker/RemoteSandboxBrokerProcessChild.cpp b/security/sandbox/win/src/remotesandboxbroker/RemoteSandboxBrokerProcessChild.cpp new file mode 100644 index 0000000000..32e7d3b1f4 --- /dev/null +++ b/security/sandbox/win/src/remotesandboxbroker/RemoteSandboxBrokerProcessChild.cpp @@ -0,0 +1,27 @@ +/* -*- 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 "RemoteSandboxBrokerProcessChild.h" + +#include "mozilla/ipc/IOThreadChild.h" +#include "mozilla/BackgroundHangMonitor.h" + +using mozilla::ipc::IOThreadChild; + +namespace mozilla { + +RemoteSandboxBrokerProcessChild::~RemoteSandboxBrokerProcessChild() {} + +bool RemoteSandboxBrokerProcessChild::Init(int aArgc, char* aArgv[]) { + BackgroundHangMonitor::Startup(); + return mSandboxBrokerChild->Init(TakeInitialEndpoint()); +} + +void RemoteSandboxBrokerProcessChild::CleanUp() { + BackgroundHangMonitor::Shutdown(); +} + +} // namespace mozilla diff --git a/security/sandbox/win/src/remotesandboxbroker/RemoteSandboxBrokerProcessChild.h b/security/sandbox/win/src/remotesandboxbroker/RemoteSandboxBrokerProcessChild.h new file mode 100644 index 0000000000..7b47f1e828 --- /dev/null +++ b/security/sandbox/win/src/remotesandboxbroker/RemoteSandboxBrokerProcessChild.h @@ -0,0 +1,33 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* 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/. */ + +#ifndef RemoteSandboxBrokerProcessChild_h_ +#define RemoteSandboxBrokerProcessChild_h_ + +#include "mozilla/ipc/ProcessChild.h" +#include "RemoteSandboxBrokerChild.h" + +namespace mozilla { + +class RemoteSandboxBrokerProcessChild final + : public mozilla::ipc::ProcessChild { + protected: + typedef mozilla::ipc::ProcessChild ProcessChild; + + public: + using ProcessChild::ProcessChild; + ~RemoteSandboxBrokerProcessChild(); + + bool Init(int aArgc, char* aArgv[]) override; + void CleanUp() override; + + private: + RefPtr<RemoteSandboxBrokerChild> mSandboxBrokerChild = + new RemoteSandboxBrokerChild; +}; + +} // namespace mozilla + +#endif // GMPProcessChild_h_ diff --git a/security/sandbox/win/src/remotesandboxbroker/RemoteSandboxBrokerProcessParent.cpp b/security/sandbox/win/src/remotesandboxbroker/RemoteSandboxBrokerProcessParent.cpp new file mode 100644 index 0000000000..218753bb52 --- /dev/null +++ b/security/sandbox/win/src/remotesandboxbroker/RemoteSandboxBrokerProcessParent.cpp @@ -0,0 +1,35 @@ +/* -*- 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 "RemoteSandboxBrokerProcessParent.h" +#include <windows.h> + +#include "mozilla/ipc/LaunchError.h" + +using mozilla::ipc::GeckoChildProcessHost; +using mozilla::ipc::LaunchError; +using mozilla::ipc::ProcessHandlePromise; + +namespace mozilla { + +RemoteSandboxBrokerProcessParent::RemoteSandboxBrokerProcessParent() + : GeckoChildProcessHost(GeckoProcessType_RemoteSandboxBroker) { + MOZ_COUNT_CTOR(RemoteSandboxBrokerProcessParent); +} + +RemoteSandboxBrokerProcessParent::~RemoteSandboxBrokerProcessParent() { + MOZ_COUNT_DTOR(RemoteSandboxBrokerProcessParent); +} + +RefPtr<ProcessHandlePromise> RemoteSandboxBrokerProcessParent::AsyncLaunch() { + if (!GeckoChildProcessHost::AsyncLaunch()) { + return ProcessHandlePromise::CreateAndReject( + LaunchError("RSBPP::AsyncLaunch"), __func__); + } + return WhenProcessHandleReady(); +} + +} // namespace mozilla diff --git a/security/sandbox/win/src/remotesandboxbroker/RemoteSandboxBrokerProcessParent.h b/security/sandbox/win/src/remotesandboxbroker/RemoteSandboxBrokerProcessParent.h new file mode 100644 index 0000000000..6b298ee9c6 --- /dev/null +++ b/security/sandbox/win/src/remotesandboxbroker/RemoteSandboxBrokerProcessParent.h @@ -0,0 +1,41 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * vim: sw=2 ts=4 et : + * 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/. */ + +#ifndef RemoteSandboxBrokerProcessParent_h_ +#define RemoteSandboxBrokerProcessParent_h_ + +#include "mozilla/Attributes.h" +#include "base/basictypes.h" +#include "base/file_path.h" +#include "base/thread.h" +#include "chrome/common/child_process_host.h" +#include "mozilla/ipc/GeckoChildProcessHost.h" + +class nsIRunnable; + +namespace mozilla { + +class RemoteSandboxBrokerProcessParent final + : public mozilla::ipc::GeckoChildProcessHost { + public: + RemoteSandboxBrokerProcessParent(); + + RefPtr<ipc::ProcessHandlePromise> AsyncLaunch(); + + bool CanShutdown() override { return true; } + + using mozilla::ipc::GeckoChildProcessHost::GetChannel; + using mozilla::ipc::GeckoChildProcessHost::GetChildProcessHandle; + + private: + ~RemoteSandboxBrokerProcessParent(); + + DISALLOW_COPY_AND_ASSIGN(RemoteSandboxBrokerProcessParent); +}; + +} // namespace mozilla + +#endif // ifndef GMPProcessParent_h diff --git a/security/sandbox/win/src/remotesandboxbroker/moz.build b/security/sandbox/win/src/remotesandboxbroker/moz.build new file mode 100644 index 0000000000..6d661ace4c --- /dev/null +++ b/security/sandbox/win/src/remotesandboxbroker/moz.build @@ -0,0 +1,30 @@ +# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*- +# vim: set filetype=python: +# 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/. + +SOURCES += [ + "remoteSandboxBroker.cpp", + "RemoteSandboxBrokerChild.cpp", + "RemoteSandboxBrokerParent.cpp", + "RemoteSandboxBrokerProcessChild.cpp", + "RemoteSandboxBrokerProcessParent.cpp", +] + +EXPORTS.mozilla += [ + "remoteSandboxBroker.h", + "RemoteSandboxBrokerChild.h", + "RemoteSandboxBrokerParent.h", + "RemoteSandboxBrokerProcessChild.h", + "RemoteSandboxBrokerProcessParent.h", +] + +for var in ("UNICODE", "_UNICODE"): + DEFINES[var] = True + +FINAL_LIBRARY = "xul" + +IPDL_SOURCES += ["PRemoteSandboxBroker.ipdl"] + +include("/ipc/chromium/chromium-config.mozbuild") diff --git a/security/sandbox/win/src/remotesandboxbroker/remoteSandboxBroker.cpp b/security/sandbox/win/src/remotesandboxbroker/remoteSandboxBroker.cpp new file mode 100644 index 0000000000..d1148e985a --- /dev/null +++ b/security/sandbox/win/src/remotesandboxbroker/remoteSandboxBroker.cpp @@ -0,0 +1,170 @@ +/* -*- 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 "remoteSandboxBroker.h" + +#include "RemoteSandboxBrokerParent.h" +#include "mozilla/SpinEventLoopUntil.h" +#include "nsIThread.h" + +namespace mozilla { + +RemoteSandboxBroker::RemoteSandboxBroker(uint32_t aLaunchArch) + : mParent(new RemoteSandboxBrokerParent), mLaunchArch(aLaunchArch) {} + +RemoteSandboxBroker::~RemoteSandboxBroker() { + MOZ_ASSERT( + mShutdown, + "Shutdown must be called on RemoteSandboxBroker before destruction!"); +} + +void RemoteSandboxBroker::Shutdown() { + MOZ_ASSERT(!mShutdown, "Don't call Shutdown() twice!"); + mShutdown = true; + + if (!mIPCLaunchThread) { + // Can't have launched child process, nothing to shutdown. + return; + } + + RefPtr<RemoteSandboxBroker> self = this; + mIPCLaunchThread->Dispatch( + NS_NewRunnableFunction("Remote Sandbox Launch", [self, this]() { + // Note: `self` here should be the last reference to this instance. + mParent->Shutdown(); + mIPCLaunchThread = nullptr; + })); +} + +Result<Ok, mozilla::ipc::LaunchError> RemoteSandboxBroker::LaunchApp( + const wchar_t* aPath, const wchar_t* aArguments, + base::EnvironmentMap& aEnvironment, GeckoProcessType aProcessType, + const bool aEnableLogging, const IMAGE_THUNK_DATA*, void** aProcessHandle) { + // Note: we expect to be called on the IPC launch thread from + // GeckoChildProcesHost while it's launching a child process. The IPC launch + // thread is a TaskQueue. We can't run a synchronous launch here as that + // blocks the calling thread while it dispatches a task to the IPC launch + // thread to spawn the process. Since our calling thread is the IPC launch + // thread, we'd then be deadlocked. So instead we do an async launch, and spin + // the event loop until the process launch succeeds. + + // We should be on the IPC launch thread. We're shutdown on the IO thread, + // so save a ref to the IPC launch thread here, so we can close the channel + // on the IPC launch thread on shutdown. + mIPCLaunchThread = GetCurrentSerialEventTarget(); + + mParameters.path() = nsDependentString(aPath); + mParameters.args() = nsDependentString(aArguments); + + auto toNsString = [](const std::wstring& s) { + return nsDependentString(s.c_str()); + }; + for (auto itr : aEnvironment) { + mParameters.env().AppendElement( + EnvVar(toNsString(itr.first), toNsString(itr.second))); + } + + mParameters.processType() = uint32_t(aProcessType); + mParameters.enableLogging() = aEnableLogging; + + enum Result { Pending, Succeeded, Failed }; + Result res = Pending; + auto resolve = [&](bool ok) { + res = Succeeded; + return GenericPromise::CreateAndResolve(ok, __func__); + }; + + auto reject = [&](nsresult) { + res = Failed; + return GenericPromise::CreateAndReject(NS_ERROR_FAILURE, __func__); + }; + + // We need to wait on the current thread for the process to launch which will + // block the running IPC Launch taskqueue. We cannot use + // GetCurrentSerialEventTarget() (as this returns the currently running + // TaskQueue) to resolve our promise as it will be blocked until we return + // from this function. + nsCOMPtr<nsISerialEventTarget> target = NS_GetCurrentThread(); + mParent->Launch(mLaunchArch, mParameters.shareHandles(), target) + ->Then(target, __func__, std::move(resolve), std::move(reject)); + + // Spin the event loop while the sandbox launcher process launches. + SpinEventLoopUntil("RemoteSandboxBroker::LaunchApp"_ns, + [&]() { return res != Pending; }); + + if (res == Failed) { + return Err(mozilla::ipc::LaunchError("RSB::LaunchApp")); + } + + uint64_t handle = 0; + bool ok = false; + bool rv = mParent->SendLaunchApp(std::move(mParameters), &ok, &handle) && ok; + mParameters.shareHandles().Clear(); + if (!rv) { + mParent->Shutdown(); + return Err(mozilla::ipc::LaunchError("RSB::SendLaunchApp")); + } + + // Duplicate the handle of the child process that the launcher launched from + // the launcher process's space into this process' space. + HANDLE ourChildHandle = 0; + bool dh = mParent->DuplicateFromLauncher((HANDLE)handle, &ourChildHandle); + if (!dh) { + mParent->Shutdown(); + return Err(mozilla::ipc::LaunchError("RSB::DuplicateFromLauncher")); + } + + *aProcessHandle = (void**)(ourChildHandle); + + base::ProcessHandle process = *aProcessHandle; + SandboxBroker::AddTargetPeer(process); + + return Ok(); +} + +bool RemoteSandboxBroker::SetSecurityLevelForGMPlugin(SandboxLevel aLevel, + bool aIsRemoteLaunch) { + mParameters.sandboxLevel() = uint32_t(aLevel); + return true; +} + +bool RemoteSandboxBroker::AllowReadFile(wchar_t const* aFile) { + mParameters.allowedReadFiles().AppendElement(nsDependentString(aFile)); + return true; +} + +void RemoteSandboxBroker::AddHandleToShare(HANDLE aHandle) { + mParameters.shareHandles().AppendElement(uint64_t(aHandle)); +} + +void RemoteSandboxBroker::SetSecurityLevelForContentProcess( + int32_t aSandboxLevel, bool aIsFileProcess) { + MOZ_CRASH( + "RemoteSandboxBroker::SetSecurityLevelForContentProcess not Implemented"); +} + +void RemoteSandboxBroker::SetSecurityLevelForGPUProcess(int32_t aSandboxLevel) { + MOZ_CRASH( + "RemoteSandboxBroker::SetSecurityLevelForGPUProcess not Implemented"); +} + +bool RemoteSandboxBroker::SetSecurityLevelForRDDProcess() { + MOZ_CRASH( + "RemoteSandboxBroker::SetSecurityLevelForRDDProcess not Implemented"); +} + +bool RemoteSandboxBroker::SetSecurityLevelForSocketProcess() { + MOZ_CRASH( + "RemoteSandboxBroker::SetSecurityLevelForSocketProcess not Implemented"); +} + +bool RemoteSandboxBroker::SetSecurityLevelForUtilityProcess( + mozilla::ipc::SandboxingKind aSandbox) { + MOZ_CRASH( + "RemoteSandboxBroker::SetSecurityLevelForUtilityProcess not Implemented"); +} + +} // namespace mozilla diff --git a/security/sandbox/win/src/remotesandboxbroker/remoteSandboxBroker.h b/security/sandbox/win/src/remotesandboxbroker/remoteSandboxBroker.h new file mode 100644 index 0000000000..bb02470e0e --- /dev/null +++ b/security/sandbox/win/src/remotesandboxbroker/remoteSandboxBroker.h @@ -0,0 +1,75 @@ +/* -*- 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 __REMOTE_SANDBOXBROKER_H__ +#define __REMOTE_SANDBOXBROKER_H__ + +#include "sandboxBroker.h" +#include "RemoteSandboxBrokerParent.h" + +#include "mozilla/Result.h" +#include "mozilla/ipc/LaunchError.h" + +namespace mozilla { + +// To make sandboxing an x86 plugin-container process on Windows on ARM64, +// we launch an x86 child process which in turn launches and sandboxes the x86 +// plugin-container child. This means the sandbox broker (in the remote +// x86 sandbox launcher process) can be same-arch with the process that it's +// sandboxing, which means all the sandbox's assumptions about things being +// same arch still hold. +class RemoteSandboxBroker : public AbstractSandboxBroker { + public: + explicit RemoteSandboxBroker(uint32_t aLaunchArch); + + void Shutdown() override; + + // Note: This should be called on the IPC launch thread, and this spins + // the event loop. So this means potentially another IPC launch could occur + // re-entrantly while calling this. + Result<Ok, mozilla::ipc::LaunchError> LaunchApp( + const wchar_t* aPath, const wchar_t* aArguments, + base::EnvironmentMap& aEnvironment, GeckoProcessType aProcessType, + const bool aEnableLogging, const IMAGE_THUNK_DATA*, + void** aProcessHandle) override; + + // Security levels for different types of processes + void SetSecurityLevelForContentProcess(int32_t aSandboxLevel, + bool aIsFileProcess) override; + void SetSecurityLevelForGPUProcess(int32_t aSandboxLevel) override; + bool SetSecurityLevelForRDDProcess() override; + bool SetSecurityLevelForSocketProcess() override; + bool SetSecurityLevelForGMPlugin(SandboxLevel aLevel, + bool aIsRemoteLaunch = false) override; + bool SetSecurityLevelForUtilityProcess( + mozilla::ipc::SandboxingKind aSandbox) override; + bool AllowReadFile(wchar_t const* file) override; + void AddHandleToShare(HANDLE aHandle) override; + + bool IsWin32kLockedDown() final { return false; }; + + private: + virtual ~RemoteSandboxBroker(); + + // Parameters that we use to launch the child process. + LaunchParameters mParameters; + + RefPtr<RemoteSandboxBrokerParent> mParent; + + // We bind the RemoteSandboxBrokerParent to the IPC launch thread. + // As such, we must close its channel on the same thread. So we save + // a reference to the IPC launch thread here. + nsCOMPtr<nsISerialEventTarget> mIPCLaunchThread; + + // True if we've been shutdown. + bool mShutdown = false; + + uint32_t mLaunchArch; +}; + +} // namespace mozilla + +#endif // __REMOTE_SANDBOXBROKER_H__ diff --git a/security/sandbox/win/src/sandboxbroker/moz.build b/security/sandbox/win/src/sandboxbroker/moz.build new file mode 100644 index 0000000000..5eaf0de937 --- /dev/null +++ b/security/sandbox/win/src/sandboxbroker/moz.build @@ -0,0 +1,21 @@ +# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*- +# vim: set filetype=python: +# 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/. + +SOURCES += [ + "sandboxBroker.cpp", +] + +EXPORTS += [ + "sandboxBroker.h", +] + +for var in ("UNICODE", "_UNICODE"): + DEFINES[var] = True + +LOCAL_INCLUDES += ["/security/sandbox/chromium-shim"] +LOCAL_INCLUDES += ["/security/sandbox/chromium"] + +FINAL_LIBRARY = "xul" diff --git a/security/sandbox/win/src/sandboxbroker/sandboxBroker.cpp b/security/sandbox/win/src/sandboxbroker/sandboxBroker.cpp new file mode 100644 index 0000000000..bd154d3c78 --- /dev/null +++ b/security/sandbox/win/src/sandboxbroker/sandboxBroker.cpp @@ -0,0 +1,2029 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=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/. */ + +#define MOZ_USE_LAUNCHER_ERROR + +#include "sandboxBroker.h" + +#include <aclapi.h> +#include <shlobj.h> +#include <string> + +#include "base/win/windows_version.h" +#include "mozilla/Assertions.h" +#include "mozilla/ClearOnShutdown.h" +#include "mozilla/ImportDir.h" +#include "mozilla/Logging.h" +#include "mozilla/NSPRLogModulesParser.h" +#include "mozilla/Omnijar.h" +#include "mozilla/Preferences.h" +#include "mozilla/SandboxSettings.h" +#include "mozilla/SHA1.h" +#include "mozilla/StaticPrefs_network.h" +#include "mozilla/StaticPrefs_security.h" +#include "mozilla/StaticPrefs_widget.h" +#include "mozilla/UniquePtr.h" +#include "mozilla/Telemetry.h" +#include "mozilla/WinDllServices.h" +#include "mozilla/WindowsVersion.h" +#include "mozilla/WinHeaderOnlyUtils.h" +#include "mozilla/ipc/LaunchError.h" +#include "nsAppDirectoryServiceDefs.h" +#include "nsCOMPtr.h" +#include "nsDirectoryServiceDefs.h" +#include "nsIFile.h" +#include "nsIProperties.h" +#include "nsIXULRuntime.h" +#include "nsServiceManagerUtils.h" +#include "nsString.h" +#include "nsTArray.h" +#include "nsTHashtable.h" +#include "sandbox/win/src/app_container_profile.h" +#include "sandbox/win/src/sandbox.h" +#include "sandbox/win/src/security_level.h" +#include "WinUtils.h" + +namespace mozilla { + +constexpr wchar_t kLpacFirefoxInstallFiles[] = L"lpacFirefoxInstallFiles"; + +sandbox::BrokerServices* sBrokerService = nullptr; + +// This is set to true in Initialize when our exe file name has a drive type of +// DRIVE_REMOTE, so that we can tailor the sandbox policy as some settings break +// fundamental things when running from a network drive. We default to false in +// case those checks fail as that gives us the strongest policy. +bool SandboxBroker::sRunningFromNetworkDrive = false; + +// Cached special directories used for adding policy rules. +static UniquePtr<nsString> sBinDir; +static UniquePtr<nsString> sProfileDir; +static UniquePtr<nsString> sLocalAppDataDir; +#ifdef ENABLE_SYSTEM_EXTENSION_DIRS +static UniquePtr<nsString> sUserExtensionsDir; +#endif + +static LazyLogModule sSandboxBrokerLog("SandboxBroker"); + +#define LOG_E(...) MOZ_LOG(sSandboxBrokerLog, LogLevel::Error, (__VA_ARGS__)) +#define LOG_W(...) MOZ_LOG(sSandboxBrokerLog, LogLevel::Warning, (__VA_ARGS__)) +#define LOG_D(...) MOZ_LOG(sSandboxBrokerLog, LogLevel::Debug, (__VA_ARGS__)) + +// Used to store whether we have accumulated an error combination for this +// session. +static UniquePtr<nsTHashtable<nsCStringHashKey>> sLaunchErrors; + +// This helper function is our version of SandboxWin::AddWin32kLockdownPolicy +// of Chromium, making sure the MITIGATION_WIN32K_DISABLE flag is set before +// adding the SUBSYS_WIN32K_LOCKDOWN rule which is required by +// PolicyBase::AddRuleInternal. +static sandbox::ResultCode AddWin32kLockdownPolicy( + sandbox::TargetPolicy* aPolicy, bool aEnableOpm) { + // On Windows 7, where Win32k lockdown is not supported, the Chromium + // sandbox does something weird that breaks COM instantiation. + if (!IsWin8OrLater()) { + return sandbox::SBOX_ALL_OK; + } + + sandbox::MitigationFlags flags = aPolicy->GetProcessMitigations(); + MOZ_ASSERT(flags, + "Mitigations should be set before AddWin32kLockdownPolicy."); + MOZ_ASSERT(!(flags & sandbox::MITIGATION_WIN32K_DISABLE), + "Check not enabling twice. Should not happen."); + + flags |= sandbox::MITIGATION_WIN32K_DISABLE; + sandbox::ResultCode result = aPolicy->SetProcessMitigations(flags); + if (result != sandbox::SBOX_ALL_OK) { + return result; + } + + result = + aPolicy->AddRule(sandbox::TargetPolicy::SUBSYS_WIN32K_LOCKDOWN, + aEnableOpm ? sandbox::TargetPolicy::IMPLEMENT_OPM_APIS + : sandbox::TargetPolicy::FAKE_USER_GDI_INIT, + nullptr); + if (result != sandbox::SBOX_ALL_OK) { + return result; + } + if (aEnableOpm) { + aPolicy->SetEnableOPMRedirection(); + } + + return result; +} + +/* static */ +void SandboxBroker::Initialize(sandbox::BrokerServices* aBrokerServices) { + sBrokerService = aBrokerServices; + + sRunningFromNetworkDrive = widget::WinUtils::RunningFromANetworkDrive(); +} + +static void CacheDirAndAutoClear(nsIProperties* aDirSvc, const char* aDirKey, + UniquePtr<nsString>* cacheVar) { + nsCOMPtr<nsIFile> dirToCache; + nsresult rv = + aDirSvc->Get(aDirKey, NS_GET_IID(nsIFile), getter_AddRefs(dirToCache)); + if (NS_FAILED(rv)) { + // This can only be an NS_WARNING, because it can fail for xpcshell tests. + NS_WARNING("Failed to get directory to cache."); + LOG_E("Failed to get directory to cache, key: %s.", aDirKey); + return; + } + + *cacheVar = MakeUnique<nsString>(); + ClearOnShutdown(cacheVar); + MOZ_ALWAYS_SUCCEEDS(dirToCache->GetPath(**cacheVar)); + + // Convert network share path to format for sandbox policy. + if (Substring(**cacheVar, 0, 2).Equals(u"\\\\"_ns)) { + (*cacheVar)->InsertLiteral(u"??\\UNC", 1); + } +} + +/* static */ +void SandboxBroker::GeckoDependentInitialize() { + MOZ_ASSERT(NS_IsMainThread()); + + bool haveXPCOM = XRE_GetProcessType() != GeckoProcessType_RemoteSandboxBroker; + if (haveXPCOM) { + // Cache directory paths for use in policy rules, because the directory + // service must be called on the main thread. + nsresult rv; + nsCOMPtr<nsIProperties> dirSvc = + do_GetService(NS_DIRECTORY_SERVICE_CONTRACTID, &rv); + if (NS_FAILED(rv)) { + MOZ_ASSERT(false, + "Failed to get directory service, cannot cache directories " + "for rules."); + LOG_E( + "Failed to get directory service, cannot cache directories for " + "rules."); + return; + } + + CacheDirAndAutoClear(dirSvc, NS_GRE_DIR, &sBinDir); + CacheDirAndAutoClear(dirSvc, NS_APP_USER_PROFILE_50_DIR, &sProfileDir); + CacheDirAndAutoClear(dirSvc, NS_WIN_LOCAL_APPDATA_DIR, &sLocalAppDataDir); +#ifdef ENABLE_SYSTEM_EXTENSION_DIRS + CacheDirAndAutoClear(dirSvc, XRE_USER_SYS_EXTENSION_DIR, + &sUserExtensionsDir); +#endif + } + + // Create sLaunchErrors up front because ClearOnShutdown must be called on the + // main thread. + sLaunchErrors = MakeUnique<nsTHashtable<nsCStringHashKey>>(); + ClearOnShutdown(&sLaunchErrors); +} + +SandboxBroker::SandboxBroker() { + if (sBrokerService) { + scoped_refptr<sandbox::TargetPolicy> policy = + sBrokerService->CreatePolicy(); + mPolicy = policy.get(); + mPolicy->AddRef(); + if (sRunningFromNetworkDrive) { + mPolicy->SetDoNotUseRestrictingSIDs(); + } + } else { + mPolicy = nullptr; + } +} + +#define WSTRING(STRING) L"" STRING + +static void AddMozLogRulesToPolicy(sandbox::TargetPolicy* aPolicy, + const base::EnvironmentMap& aEnvironment) { + auto it = aEnvironment.find(ENVIRONMENT_LITERAL("MOZ_LOG_FILE")); + if (it == aEnvironment.end()) { + it = aEnvironment.find(ENVIRONMENT_LITERAL("NSPR_LOG_FILE")); + } + if (it == aEnvironment.end()) { + return; + } + + char const* logFileModules = getenv("MOZ_LOG"); + if (!logFileModules) { + return; + } + + // MOZ_LOG files have a standard file extension appended. + std::wstring logFileName(it->second); + logFileName.append(WSTRING(MOZ_LOG_FILE_EXTENSION)); + + // Allow for rotation number if rotate is on in the MOZ_LOG settings. + bool rotate = false; + NSPRLogModulesParser( + logFileModules, + [&rotate](const char* aName, LogLevel aLevel, int32_t aValue) { + if (strcmp(aName, "rotate") == 0) { + // Less or eq zero means to turn rotate off. + rotate = aValue > 0; + } + }); + if (rotate) { + logFileName.append(L".?"); + } + + // Allow for %PID token in the filename. We don't allow it in the dir path, if + // specified, because we have to use a wildcard as we don't know the PID yet. + auto pidPos = logFileName.find(WSTRING(MOZ_LOG_PID_TOKEN)); + auto lastSlash = logFileName.find_last_of(L"/\\"); + if (pidPos != std::wstring::npos && + (lastSlash == std::wstring::npos || lastSlash < pidPos)) { + logFileName.replace(pidPos, strlen(MOZ_LOG_PID_TOKEN), L"*"); + } + + aPolicy->AddRule(sandbox::TargetPolicy::SUBSYS_FILES, + sandbox::TargetPolicy::FILES_ALLOW_ANY, logFileName.c_str()); +} + +static void AddDeveloperRepoDirToPolicy(sandbox::TargetPolicy* aPolicy) { + const wchar_t* developer_repo_dir = + _wgetenv(WSTRING("MOZ_DEVELOPER_REPO_DIR")); + if (!developer_repo_dir) { + return; + } + + std::wstring repoPath(developer_repo_dir); + std::replace(repoPath.begin(), repoPath.end(), '/', '\\'); + repoPath.append(WSTRING("\\*")); + + aPolicy->AddRule(sandbox::TargetPolicy::SUBSYS_FILES, + sandbox::TargetPolicy::FILES_ALLOW_READONLY, + repoPath.c_str()); +} + +#undef WSTRING + +Result<Ok, mozilla::ipc::LaunchError> SandboxBroker::LaunchApp( + const wchar_t* aPath, const wchar_t* aArguments, + base::EnvironmentMap& aEnvironment, GeckoProcessType aProcessType, + const bool aEnableLogging, const IMAGE_THUNK_DATA* aCachedNtdllThunk, + void** aProcessHandle) { + if (!sBrokerService) { + return Err(mozilla::ipc::LaunchError("SB::LA::sBrokerService")); + } + + if (!mPolicy) { + return Err(mozilla::ipc::LaunchError("SB::LA::mPolicy")); + } + + // Set stdout and stderr, to allow inheritance for logging. + mPolicy->SetStdoutHandle(::GetStdHandle(STD_OUTPUT_HANDLE)); + mPolicy->SetStderrHandle(::GetStdHandle(STD_ERROR_HANDLE)); + + // If logging enabled, set up the policy. + if (aEnableLogging) { + ApplyLoggingPolicy(); + } + +#if defined(DEBUG) + // Allow write access to TEMP directory in debug builds for logging purposes. + // The path from GetTempPathW can have a length up to MAX_PATH + 1, including + // the null, so we need MAX_PATH + 2, so we can add an * to the end. + wchar_t tempPath[MAX_PATH + 2]; + uint32_t pathLen = ::GetTempPathW(MAX_PATH + 1, tempPath); + if (pathLen > 0) { + // GetTempPath path ends with \ and returns the length without the null. + tempPath[pathLen] = L'*'; + tempPath[pathLen + 1] = L'\0'; + mPolicy->AddRule(sandbox::TargetPolicy::SUBSYS_FILES, + sandbox::TargetPolicy::FILES_ALLOW_ANY, tempPath); + } +#endif + + // Enable the child process to write log files when setup + AddMozLogRulesToPolicy(mPolicy, aEnvironment); + + if (!mozilla::IsPackagedBuild()) { + AddDeveloperRepoDirToPolicy(mPolicy); + } + + // Create the sandboxed process + PROCESS_INFORMATION targetInfo = {0}; + sandbox::ResultCode result; + sandbox::ResultCode last_warning = sandbox::SBOX_ALL_OK; + DWORD last_error = ERROR_SUCCESS; + result = sBrokerService->SpawnTarget(aPath, aArguments, aEnvironment, mPolicy, + &last_warning, &last_error, &targetInfo); + if (sandbox::SBOX_ALL_OK != result) { + nsAutoCString key; + key.AppendASCII(XRE_GeckoProcessTypeToString(aProcessType)); + key.AppendLiteral("/0x"); + key.AppendInt(static_cast<uint32_t>(last_error), 16); + + // Only accumulate for each combination once per session. + if (sLaunchErrors) { + if (!sLaunchErrors->Contains(key)) { + Telemetry::Accumulate(Telemetry::SANDBOX_FAILED_LAUNCH_KEYED, key, + result); + sLaunchErrors->PutEntry(key); + } + } else { + // If sLaunchErrors not created yet then always accumulate. + Telemetry::Accumulate(Telemetry::SANDBOX_FAILED_LAUNCH_KEYED, key, + result); + } + + LOG_E( + "Failed (ResultCode %d) to SpawnTarget with last_error=%lu, " + "last_warning=%d", + result, last_error, last_warning); + + return Err(mozilla::ipc::LaunchError("SB::LA::SpawnTarget", last_error)); + } else if (sandbox::SBOX_ALL_OK != last_warning) { + // If there was a warning (but the result was still ok), log it and proceed. + LOG_W("Warning on SpawnTarget with last_error=%lu, last_warning=%d", + last_error, last_warning); + } + +#ifdef MOZ_THUNDERBIRD + // In Thunderbird, mInitDllBlocklistOOP is null, so InitDllBlocklistOOP would + // hit MOZ_RELEASE_ASSERT. + constexpr bool isThunderbird = true; +#else + constexpr bool isThunderbird = false; +#endif + + if (!isThunderbird && + XRE_GetChildProcBinPathType(aProcessType) == BinPathType::Self) { + RefPtr<DllServices> dllSvc(DllServices::Get()); + LauncherVoidResultWithLineInfo blocklistInitOk = + dllSvc->InitDllBlocklistOOP(aPath, targetInfo.hProcess, + aCachedNtdllThunk, aProcessType); + if (blocklistInitOk.isErr()) { + dllSvc->HandleLauncherError(blocklistInitOk.unwrapErr(), + XRE_GeckoProcessTypeToString(aProcessType)); + LOG_E("InitDllBlocklistOOP failed at %s:%d with HRESULT 0x%08lX", + blocklistInitOk.unwrapErr().mFile, + blocklistInitOk.unwrapErr().mLine, + blocklistInitOk.unwrapErr().mError.AsHResult()); + TerminateProcess(targetInfo.hProcess, 1); + CloseHandle(targetInfo.hThread); + CloseHandle(targetInfo.hProcess); + return Err(mozilla::ipc::LaunchError( + "InitDllBlocklistOOP", + blocklistInitOk.unwrapErr().mError.AsHResult())); + } + } else { + // Load the child executable as a datafile so that we can examine its + // headers without doing a full load with dependencies and such. + nsModuleHandle moduleHandle( + ::LoadLibraryExW(aPath, nullptr, LOAD_LIBRARY_AS_DATAFILE)); + if (moduleHandle) { + nt::CrossExecTransferManager transferMgr(targetInfo.hProcess, + moduleHandle); + if (!!transferMgr) { + LauncherVoidResult importsRestored = + RestoreImportDirectory(aPath, transferMgr); + if (importsRestored.isErr()) { + RefPtr<DllServices> dllSvc(DllServices::Get()); + dllSvc->HandleLauncherError( + importsRestored.unwrapErr(), + XRE_GeckoProcessTypeToString(aProcessType)); + LOG_E("Failed to restore import directory with HRESULT 0x%08lX", + importsRestored.unwrapErr().mError.AsHResult()); + TerminateProcess(targetInfo.hProcess, 1); + CloseHandle(targetInfo.hThread); + CloseHandle(targetInfo.hProcess); + return Err(mozilla::ipc::LaunchError( + "RestoreImportDirectory", + importsRestored.unwrapErr().mError.AsHResult())); + } + } + } + } + + // The sandboxed process is started in a suspended state, resume it now that + // we've set things up. + ResumeThread(targetInfo.hThread); + CloseHandle(targetInfo.hThread); + + // Return the process handle to the caller + *aProcessHandle = targetInfo.hProcess; + + return Ok(); +} + +static void AddCachedDirRule(sandbox::TargetPolicy* aPolicy, + sandbox::TargetPolicy::Semantics aAccess, + const UniquePtr<nsString>& aBaseDir, + const nsLiteralString& aRelativePath) { + if (!aBaseDir) { + // This can only be an NS_WARNING, because it can null for xpcshell tests. + NS_WARNING("Tried to add rule with null base dir."); + LOG_E("Tried to add rule with null base dir. Relative path: %S, Access: %d", + static_cast<const wchar_t*>(aRelativePath.get()), aAccess); + return; + } + + nsAutoString rulePath(*aBaseDir); + rulePath.Append(aRelativePath); + + sandbox::ResultCode result = aPolicy->AddRule( + sandbox::TargetPolicy::SUBSYS_FILES, aAccess, rulePath.get()); + if (sandbox::SBOX_ALL_OK != result) { + NS_ERROR("Failed to add file policy rule."); + LOG_E("Failed (ResultCode %d) to add %d access to: %S", result, aAccess, + static_cast<const wchar_t*>(rulePath.get())); + } +} + +// This function caches and returns an array of NT paths of the executable's +// dependent modules. +// If this returns Nothing(), it means the retrieval of the modules failed +// (e.g. when the launcher process is disabled), so the process should not +// enable pre-spawn CIG. +static const Maybe<Vector<const wchar_t*>>& GetPrespawnCigExceptionModules() { + // We enable pre-spawn CIG only in Nightly for now + // because it caused a compat issue (bug 1682304 and 1704373). +#if defined(NIGHTLY_BUILD) + // The shared section contains a list of dependent modules as a + // null-delimited string. We convert it to a string vector and + // cache it to avoid converting the same data every time. + static Maybe<Vector<const wchar_t*>> sDependentModules = + []() -> Maybe<Vector<const wchar_t*>> { + RefPtr<DllServices> dllSvc(DllServices::Get()); + auto sharedSection = dllSvc->GetSharedSection(); + if (!sharedSection) { + return Nothing(); + } + + Span<const wchar_t> dependentModules = sharedSection->GetDependentModules(); + if (dependentModules.IsEmpty()) { + return Nothing(); + } + + // Convert a null-delimited string set to a string vector. + Vector<const wchar_t*> paths; + for (const wchar_t* p = dependentModules.data(); + (p - dependentModules.data() < + static_cast<long long>(dependentModules.size()) && + *p);) { + Unused << paths.append(p); + while (*p) { + ++p; + } + ++p; + } + + return Some(std::move(paths)); + }(); + + return sDependentModules; +#else + static const Maybe<Vector<const wchar_t*>> sNothing = Nothing(); + return sNothing; +#endif +} + +static sandbox::ResultCode AllowProxyLoadFromBinDir( + sandbox::TargetPolicy* aPolicy) { + // Allow modules in the directory containing the executable such as + // mozglue.dll, nss3.dll, etc. + static UniquePtr<nsString> sInstallDir; + if (!sInstallDir) { + // Since this function can be called before sBinDir is initialized, + // we cache the install path by ourselves. + UniquePtr<wchar_t[]> appDirStr; + if (GetInstallDirectory(appDirStr)) { + sInstallDir = MakeUnique<nsString>(appDirStr.get()); + sInstallDir->Append(u"\\*"); + + auto setClearOnShutdown = [ptr = &sInstallDir]() -> void { + ClearOnShutdown(ptr); + }; + if (NS_IsMainThread()) { + setClearOnShutdown(); + } else { + SchedulerGroup::Dispatch( + TaskCategory::Other, + NS_NewRunnableFunction("InitSignedPolicyRulesToBypassCig", + std::move(setClearOnShutdown))); + } + } + + if (!sInstallDir) { + return sandbox::SBOX_ERROR_GENERIC; + } + } + return aPolicy->AddRule(sandbox::TargetPolicy::SUBSYS_SIGNED_BINARY, + sandbox::TargetPolicy::SIGNED_ALLOW_LOAD, + sInstallDir->get()); +} + +static sandbox::ResultCode AddCigToPolicy( + sandbox::TargetPolicy* aPolicy, bool aAlwaysProxyBinDirLoading = false) { + const Maybe<Vector<const wchar_t*>>& exceptionModules = + GetPrespawnCigExceptionModules(); + if (exceptionModules.isNothing()) { + sandbox::MitigationFlags delayedMitigations = + aPolicy->GetDelayedProcessMitigations(); + MOZ_ASSERT(delayedMitigations, + "Delayed mitigations should be set before AddCigToPolicy."); + MOZ_ASSERT(!(delayedMitigations & sandbox::MITIGATION_FORCE_MS_SIGNED_BINS), + "AddCigToPolicy should not be called twice."); + + delayedMitigations |= sandbox::MITIGATION_FORCE_MS_SIGNED_BINS; + sandbox::ResultCode result = + aPolicy->SetDelayedProcessMitigations(delayedMitigations); + if (result != sandbox::SBOX_ALL_OK) { + return result; + } + + if (aAlwaysProxyBinDirLoading) { + result = AllowProxyLoadFromBinDir(aPolicy); + } + return result; + } + + sandbox::MitigationFlags mitigations = aPolicy->GetProcessMitigations(); + MOZ_ASSERT(mitigations, "Mitigations should be set before AddCigToPolicy."); + MOZ_ASSERT(!(mitigations & sandbox::MITIGATION_FORCE_MS_SIGNED_BINS), + "AddCigToPolicy should not be called twice."); + + mitigations |= sandbox::MITIGATION_FORCE_MS_SIGNED_BINS; + sandbox::ResultCode result = aPolicy->SetProcessMitigations(mitigations); + if (result != sandbox::SBOX_ALL_OK) { + return result; + } + + result = AllowProxyLoadFromBinDir(aPolicy); + if (result != sandbox::SBOX_ALL_OK) { + return result; + } + + for (const wchar_t* path : exceptionModules.ref()) { + result = aPolicy->AddRule(sandbox::TargetPolicy::SUBSYS_SIGNED_BINARY, + sandbox::TargetPolicy::SIGNED_ALLOW_LOAD, path); + if (result != sandbox::SBOX_ALL_OK) { + return result; + } + } + + return sandbox::SBOX_ALL_OK; +} + +// Checks whether we can use a job object as part of the sandbox. +static bool CanUseJob() { + // Windows 8 and later allows nested jobs, no need for further checks. + if (IsWin8OrLater()) { + return true; + } + + BOOL inJob = true; + // If we can't determine if we are in a job then assume we can use one. + if (!::IsProcessInJob(::GetCurrentProcess(), nullptr, &inJob)) { + return true; + } + + // If there is no job then we are fine to use one. + if (!inJob) { + return true; + } + + JOBOBJECT_EXTENDED_LIMIT_INFORMATION job_info = {}; + // If we can't get the job object flags then again assume we can use a job. + if (!::QueryInformationJobObject(nullptr, JobObjectExtendedLimitInformation, + &job_info, sizeof(job_info), nullptr)) { + return true; + } + + // If we can break away from the current job then we are free to set our own. + if (job_info.BasicLimitInformation.LimitFlags & + JOB_OBJECT_LIMIT_BREAKAWAY_OK) { + return true; + } + + // Chromium added a command line flag to allow no job to be used, which was + // originally supposed to only be used for remote sessions. If you use runas + // to start Firefox then this also uses a separate job and we would fail to + // start on Windows 7. An unknown number of people use (or used to use) runas + // with Firefox for some security benefits (see bug 1228880). This is now a + // counterproductive technique, but allowing both the remote and local case + // for now and adding telemetry to see if we can restrict this to just remote. + nsAutoString localRemote(::GetSystemMetrics(SM_REMOTESESSION) ? u"remote" + : u"local"); + Telemetry::ScalarSet(Telemetry::ScalarID::SANDBOX_NO_JOB, localRemote, true); + + // Allow running without the job object in this case. This slightly reduces + // the ability of the sandbox to protect its children from spawning new + // processes or preventing them from shutting down Windows or accessing the + // clipboard. + return false; +} + +// Returns the most strict dynamic code mitigation flag that is compatible with +// system libraries MSAudDecMFT.dll and msmpeg2vdec.dll. This depends on the +// Windows version and the architecture. See bug 1783223 comment 27. +// +// Use the result with SetDelayedProcessMitigations. Using non-delayed ACG +// results in incompatibility with third-party antivirus software, the Windows +// internal Shim Engine mechanism, parts of our own DLL blocklist code, and +// AddressSanitizer initialization code. See bug 1783223. +static sandbox::MitigationFlags DynamicCodeFlagForSystemMediaLibraries() { + static auto dynamicCodeFlag = []() { +#ifdef _M_X64 + if (IsWin10CreatorsUpdateOrLater()) { + return sandbox::MITIGATION_DYNAMIC_CODE_DISABLE; + } +#endif // _M_X64 + + if (IsWin10AnniversaryUpdateOrLater()) { + return sandbox::MITIGATION_DYNAMIC_CODE_DISABLE_WITH_OPT_OUT; + } + + return sandbox::MitigationFlags{}; + }(); + return dynamicCodeFlag; +} + +static sandbox::ResultCode SetJobLevel(sandbox::TargetPolicy* aPolicy, + sandbox::JobLevel aJobLevel, + uint32_t aUiExceptions) { + static bool sCanUseJob = CanUseJob(); + if (sCanUseJob) { + return aPolicy->SetJobLevel(aJobLevel, aUiExceptions); + } + + return aPolicy->SetJobLevel(sandbox::JOB_NONE, 0); +} + +static void HexEncode(const Span<const uint8_t>& aBytes, nsACString& aEncoded) { + static const char kHexChars[] = "0123456789abcdef"; + + // Each input byte creates two output hex characters. + char* encodedPtr; + aEncoded.GetMutableData(&encodedPtr, aBytes.size() * 2); + + for (auto byte : aBytes) { + *(encodedPtr++) = kHexChars[byte >> 4]; + *(encodedPtr++) = kHexChars[byte & 0xf]; + } +} + +// This is left as a void because we might fail to set the permission for some +// reason and yet the LPAC permission is already granted. So returning success +// or failure isn't really that useful. +static void EnsureLpacPermsissionsOnBinDir() { + BYTE sidBytes[SECURITY_MAX_SID_SIZE]; + PSID lpacFirefoxInstallFilesSid = static_cast<PSID>(sidBytes); + if (!sBrokerService->DeriveCapabilitySidFromName(kLpacFirefoxInstallFiles, + lpacFirefoxInstallFilesSid, + sizeof(sidBytes))) { + LOG_E("Failed to derive Firefox install files capability SID."); + return; + } + + HANDLE hBinDir = + ::CreateFileW(sBinDir->get(), WRITE_DAC | READ_CONTROL, 0, NULL, + OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, NULL); + if (hBinDir == INVALID_HANDLE_VALUE) { + LOG_W("Unable to get binary directory handle."); + return; + } + + UniquePtr<HANDLE, CloseHandleDeleter> autoHandleCloser(hBinDir); + PACL pBinDirAcl = nullptr; + PSECURITY_DESCRIPTOR pSD = nullptr; + DWORD result = + ::GetSecurityInfo(hBinDir, SE_FILE_OBJECT, DACL_SECURITY_INFORMATION, + nullptr, nullptr, &pBinDirAcl, nullptr, &pSD); + if (result != ERROR_SUCCESS) { + LOG_E("Failed to get DACL for binary directory."); + return; + } + + UniquePtr<VOID, LocalFreeDeleter> autoFreeSecDesc(pSD); + if (!pBinDirAcl) { + LOG_E("DACL for binary directory was null."); + return; + } + + for (DWORD i = 0; i < pBinDirAcl->AceCount; ++i) { + VOID* pAce = nullptr; + if (!::GetAce(pBinDirAcl, i, &pAce) || + static_cast<PACE_HEADER>(pAce)->AceType != ACCESS_ALLOWED_ACE_TYPE) { + continue; + } + + auto* pAllowedAce = static_cast<ACCESS_ALLOWED_ACE*>(pAce); + if ((pAllowedAce->Mask & (GENERIC_READ | GENERIC_EXECUTE)) != + (GENERIC_READ | GENERIC_EXECUTE)) { + continue; + } + + PSID aceSID = reinterpret_cast<PSID>(&(pAllowedAce->SidStart)); + if (::EqualSid(aceSID, lpacFirefoxInstallFilesSid)) { + LOG_D("Firefox install files permission found on binary directory."); + return; + } + } + + EXPLICIT_ACCESS_W newAccess = {0}; + newAccess.grfAccessMode = GRANT_ACCESS; + newAccess.grfAccessPermissions = GENERIC_READ | GENERIC_EXECUTE; + newAccess.grfInheritance = SUB_CONTAINERS_AND_OBJECTS_INHERIT; + ::BuildTrusteeWithSidW(&newAccess.Trustee, lpacFirefoxInstallFilesSid); + PACL newDacl = nullptr; + if (ERROR_SUCCESS != + ::SetEntriesInAclW(1, &newAccess, pBinDirAcl, &newDacl)) { + LOG_E("Failed to create new DACL with Firefox install files SID."); + return; + } + + UniquePtr<ACL, LocalFreeDeleter> autoFreeAcl(newDacl); + if (ERROR_SUCCESS != ::SetSecurityInfo(hBinDir, SE_FILE_OBJECT, + DACL_SECURITY_INFORMATION, nullptr, + nullptr, newDacl, nullptr)) { + LOG_E("Failed to set new DACL on binary directory."); + } + + LOG_D("Firefox install files permission granted on binary directory."); +} + +static bool IsLowPrivilegedAppContainerSupported() { + // Chromium doesn't support adding an LPAC before this version due to + // incompatibility with some process mitigations. + return IsWin10Sep2018UpdateOrLater(); +} + +// AddAndConfigureAppContainerProfile deliberately fails if it is called on an +// unsupported version. This is because for some process types the LPAC is +// required to provide a sufficiently strong sandbox. Processes where the use of +// an LPAC is an optional extra should use IsLowPrivilegedAppContainerSupported +// to check support first. +static sandbox::ResultCode AddAndConfigureAppContainerProfile( + sandbox::TargetPolicy* aPolicy, const nsAString& aPackagePrefix, + const nsTArray<sandbox::WellKnownCapabilities>& aWellKnownCapabilites, + const nsTArray<const wchar_t*>& aNamedCapabilites) { + // CreateAppContainerProfile requires that the profile name is at most 64 + // characters but 50 on WCOS systems. The size of sha1 is a constant 40, + // so validate that the base names are sufficiently short that the total + // length is valid on all systems. + MOZ_ASSERT(aPackagePrefix.Length() <= 10U, + "AppContainer Package prefix too long."); + + if (!IsLowPrivilegedAppContainerSupported()) { + return sandbox::SBOX_ERROR_UNSUPPORTED; + } + + static nsAutoString uniquePackageStr = []() { + // userenv.dll may not have been loaded and some of the chromium sandbox + // AppContainer code assumes that it is. Done here to load once. + ::LoadLibraryW(L"userenv.dll"); + + // Done during the package string initialization so we only do it once. + EnsureLpacPermsissionsOnBinDir(); + + // This mirrors Edge's use of the exe path for the SHA1 hash to give a + // machine unique name per install. + nsAutoString ret; + char exePathBuf[MAX_PATH]; + DWORD pathSize = ::GetModuleFileNameA(nullptr, exePathBuf, MAX_PATH); + if (!pathSize) { + return ret; + } + + SHA1Sum sha1Sum; + SHA1Sum::Hash sha1Hash; + sha1Sum.update(exePathBuf, pathSize); + sha1Sum.finish(sha1Hash); + + nsAutoCString hexEncoded; + HexEncode(sha1Hash, hexEncoded); + ret = NS_ConvertUTF8toUTF16(hexEncoded); + return ret; + }(); + + if (uniquePackageStr.IsEmpty()) { + return sandbox::SBOX_ERROR_CREATE_APPCONTAINER_PROFILE; + } + + // The bool parameter is called create_profile, but in fact it tries to create + // and then opens if it already exists. So always passing true is fine. + bool createOrOpenProfile = true; + nsAutoString packageName = aPackagePrefix + uniquePackageStr; + sandbox::ResultCode result = + aPolicy->AddAppContainerProfile(packageName.get(), createOrOpenProfile); + if (result != sandbox::SBOX_ALL_OK) { + return result; + } + + // This looks odd, but unfortunately holding a scoped_refptr and + // dereferencing has DCHECKs that cause a linking problem. + sandbox::AppContainerProfile* profile = + aPolicy->GetAppContainerProfile().get(); + profile->SetEnableLowPrivilegeAppContainer(true); + + for (auto wkCap : aWellKnownCapabilites) { + if (!profile->AddCapability(wkCap)) { + return sandbox::SBOX_ERROR_CREATE_APPCONTAINER_PROFILE_CAPABILITY; + } + } + + for (auto namedCap : aNamedCapabilites) { + if (!profile->AddCapability(namedCap)) { + return sandbox::SBOX_ERROR_CREATE_APPCONTAINER_PROFILE_CAPABILITY; + } + } + + return sandbox::SBOX_ALL_OK; +} + +void SandboxBroker::SetSecurityLevelForContentProcess(int32_t aSandboxLevel, + bool aIsFileProcess) { + MOZ_RELEASE_ASSERT(mPolicy, "mPolicy must be set before this call."); + + sandbox::JobLevel jobLevel; + sandbox::TokenLevel accessTokenLevel; + sandbox::IntegrityLevel initialIntegrityLevel; + sandbox::IntegrityLevel delayedIntegrityLevel; + + // The setting of these levels is pretty arbitrary, but they are a useful (if + // crude) tool while we are tightening the policy. Gaps are left to try and + // avoid changing their meaning. + MOZ_RELEASE_ASSERT(aSandboxLevel >= 1, + "Should not be called with aSandboxLevel < 1"); + if (aSandboxLevel >= 20) { + jobLevel = sandbox::JOB_LOCKDOWN; + accessTokenLevel = sandbox::USER_LOCKDOWN; + initialIntegrityLevel = sandbox::INTEGRITY_LEVEL_LOW; + delayedIntegrityLevel = sandbox::INTEGRITY_LEVEL_UNTRUSTED; + } else if (aSandboxLevel >= 4) { + jobLevel = sandbox::JOB_LOCKDOWN; + accessTokenLevel = sandbox::USER_LIMITED; + initialIntegrityLevel = sandbox::INTEGRITY_LEVEL_LOW; + delayedIntegrityLevel = sandbox::INTEGRITY_LEVEL_LOW; + } else if (aSandboxLevel >= 3) { + jobLevel = sandbox::JOB_RESTRICTED; + accessTokenLevel = sandbox::USER_LIMITED; + initialIntegrityLevel = sandbox::INTEGRITY_LEVEL_LOW; + delayedIntegrityLevel = sandbox::INTEGRITY_LEVEL_LOW; + } else if (aSandboxLevel == 2) { + jobLevel = sandbox::JOB_INTERACTIVE; + accessTokenLevel = sandbox::USER_INTERACTIVE; + initialIntegrityLevel = sandbox::INTEGRITY_LEVEL_LOW; + delayedIntegrityLevel = sandbox::INTEGRITY_LEVEL_LOW; + } else { + MOZ_ASSERT(aSandboxLevel == 1); + + jobLevel = sandbox::JOB_NONE; + accessTokenLevel = sandbox::USER_NON_ADMIN; + initialIntegrityLevel = sandbox::INTEGRITY_LEVEL_LOW; + delayedIntegrityLevel = sandbox::INTEGRITY_LEVEL_LOW; + } + + // If the process will handle file: URLs, don't allow settings that + // block reads. + if (aIsFileProcess) { + if (accessTokenLevel < sandbox::USER_NON_ADMIN) { + accessTokenLevel = sandbox::USER_NON_ADMIN; + } + if (delayedIntegrityLevel > sandbox::INTEGRITY_LEVEL_LOW) { + delayedIntegrityLevel = sandbox::INTEGRITY_LEVEL_LOW; + } + } + +#if defined(DEBUG) + // This is required for a MOZ_ASSERT check in WindowsMessageLoop.cpp + // WinEventHook, see bug 1366694 for details. + DWORD uiExceptions = JOB_OBJECT_UILIMIT_HANDLES; +#else + DWORD uiExceptions = 0; +#endif + sandbox::ResultCode result = SetJobLevel(mPolicy, jobLevel, uiExceptions); + MOZ_RELEASE_ASSERT(sandbox::SBOX_ALL_OK == result, + "Setting job level failed, have you set memory limit when " + "jobLevel == JOB_NONE?"); + + // If the delayed access token is not restricted we don't want the initial one + // to be either, because it can interfere with running from a network drive. + sandbox::TokenLevel initialAccessTokenLevel = + (accessTokenLevel == sandbox::USER_UNPROTECTED || + accessTokenLevel == sandbox::USER_NON_ADMIN) + ? sandbox::USER_UNPROTECTED + : sandbox::USER_RESTRICTED_SAME_ACCESS; + + result = mPolicy->SetTokenLevel(initialAccessTokenLevel, accessTokenLevel); + MOZ_RELEASE_ASSERT(sandbox::SBOX_ALL_OK == result, + "Lockdown level cannot be USER_UNPROTECTED or USER_LAST " + "if initial level was USER_RESTRICTED_SAME_ACCESS"); + + result = mPolicy->SetIntegrityLevel(initialIntegrityLevel); + MOZ_RELEASE_ASSERT(sandbox::SBOX_ALL_OK == result, + "SetIntegrityLevel should never fail, what happened?"); + result = mPolicy->SetDelayedIntegrityLevel(delayedIntegrityLevel); + MOZ_RELEASE_ASSERT( + sandbox::SBOX_ALL_OK == result, + "SetDelayedIntegrityLevel should never fail, what happened?"); + + if (aSandboxLevel > 5) { + mPolicy->SetLockdownDefaultDacl(); + mPolicy->AddRestrictingRandomSid(); + } + + if (aSandboxLevel > 4) { + // Alternate winstation breaks native theming. + bool useAlternateWinstation = + StaticPrefs::widget_non_native_theme_enabled(); + result = mPolicy->SetAlternateDesktop(useAlternateWinstation); + if (NS_WARN_IF(result != sandbox::SBOX_ALL_OK)) { + LOG_W("SetAlternateDesktop failed, result: %i, last error: %lx", result, + ::GetLastError()); + } + } + + sandbox::MitigationFlags mitigations = + sandbox::MITIGATION_BOTTOM_UP_ASLR | sandbox::MITIGATION_HEAP_TERMINATE | + sandbox::MITIGATION_SEHOP | sandbox::MITIGATION_DEP_NO_ATL_THUNK | + sandbox::MITIGATION_DEP | sandbox::MITIGATION_EXTENSION_POINT_DISABLE | + sandbox::MITIGATION_IMAGE_LOAD_PREFER_SYS32; + +#if defined(_M_ARM64) + // Disable CFG on older versions of ARM64 Windows to avoid a crash in COM. + if (!IsWin10Sep2018UpdateOrLater()) { + mitigations |= sandbox::MITIGATION_CONTROL_FLOW_GUARD_DISABLE; + } +#endif + + if (aSandboxLevel > 3) { + // If we're running from a network drive then we can't block loading from + // remote locations. Strangely using MITIGATION_IMAGE_LOAD_NO_LOW_LABEL in + // this situation also means the process fails to start (bug 1423296). + if (!sRunningFromNetworkDrive) { + mitigations |= sandbox::MITIGATION_IMAGE_LOAD_NO_REMOTE | + sandbox::MITIGATION_IMAGE_LOAD_NO_LOW_LABEL; + } + } + + if (StaticPrefs::security_sandbox_content_shadow_stack_enabled()) { + mitigations |= sandbox::MITIGATION_CET_COMPAT_MODE; + } + + result = mPolicy->SetProcessMitigations(mitigations); + MOZ_RELEASE_ASSERT(sandbox::SBOX_ALL_OK == result, + "Invalid flags for SetProcessMitigations."); + + nsIXULRuntime::ContentWin32kLockdownState win32kLockdownState = + GetContentWin32kLockdownState(); + + LOG_W("Win32k Lockdown State: '%s'", + ContentWin32kLockdownStateToString(win32kLockdownState)); + + if (GetContentWin32kLockdownEnabled()) { + result = AddWin32kLockdownPolicy(mPolicy, false); + MOZ_RELEASE_ASSERT(result == sandbox::SBOX_ALL_OK, + "Failed to add the win32k lockdown policy"); + } + + mitigations = sandbox::MITIGATION_STRICT_HANDLE_CHECKS | + sandbox::MITIGATION_DLL_SEARCH_ORDER; + + result = mPolicy->SetDelayedProcessMitigations(mitigations); + MOZ_RELEASE_ASSERT(sandbox::SBOX_ALL_OK == result, + "Invalid flags for SetDelayedProcessMitigations."); + + // We still have edge cases where the child at low integrity can't read some + // files, so add a rule to allow read access to everything when required. + if (aSandboxLevel == 1 || aIsFileProcess) { + result = + mPolicy->AddRule(sandbox::TargetPolicy::SUBSYS_FILES, + sandbox::TargetPolicy::FILES_ALLOW_READONLY, L"*"); + MOZ_RELEASE_ASSERT(sandbox::SBOX_ALL_OK == result, + "With these static arguments AddRule should never fail, " + "what happened?"); + } else { + // Add rule to allow access to user specific fonts. + AddCachedDirRule(mPolicy, sandbox::TargetPolicy::FILES_ALLOW_READONLY, + sLocalAppDataDir, u"\\Microsoft\\Windows\\Fonts\\*"_ns); + + // Add rule to allow read access to installation directory. + AddCachedDirRule(mPolicy, sandbox::TargetPolicy::FILES_ALLOW_READONLY, + sBinDir, u"\\*"_ns); + + // Add rule to allow read access to the chrome directory within profile. + AddCachedDirRule(mPolicy, sandbox::TargetPolicy::FILES_ALLOW_READONLY, + sProfileDir, u"\\chrome\\*"_ns); + + // Add rule to allow read access to the extensions directory within profile. + AddCachedDirRule(mPolicy, sandbox::TargetPolicy::FILES_ALLOW_READONLY, + sProfileDir, u"\\extensions\\*"_ns); + +#ifdef ENABLE_SYSTEM_EXTENSION_DIRS + // Add rule to allow read access to the per-user extensions directory. + AddCachedDirRule(mPolicy, sandbox::TargetPolicy::FILES_ALLOW_READONLY, + sUserExtensionsDir, u"\\*"_ns); +#endif + } + + // Add the policy for the client side of a pipe. It is just a file + // in the \pipe\ namespace. We restrict it to pipes that start with + // "chrome." so the sandboxed process cannot connect to system services. + result = mPolicy->AddRule(sandbox::TargetPolicy::SUBSYS_FILES, + sandbox::TargetPolicy::FILES_ALLOW_ANY, + L"\\??\\pipe\\chrome.*"); + MOZ_RELEASE_ASSERT( + sandbox::SBOX_ALL_OK == result, + "With these static arguments AddRule should never fail, what happened?"); + + // Add the policy for the client side of the crash server pipe. + result = mPolicy->AddRule(sandbox::TargetPolicy::SUBSYS_FILES, + sandbox::TargetPolicy::FILES_ALLOW_ANY, + L"\\??\\pipe\\gecko-crash-server-pipe.*"); + MOZ_RELEASE_ASSERT( + sandbox::SBOX_ALL_OK == result, + "With these static arguments AddRule should never fail, what happened?"); + + // The content process needs to be able to duplicate named pipes back to the + // broker and other child processes, which are File type handles. + result = mPolicy->AddRule(sandbox::TargetPolicy::SUBSYS_HANDLES, + sandbox::TargetPolicy::HANDLES_DUP_BROKER, L"File"); + MOZ_RELEASE_ASSERT( + sandbox::SBOX_ALL_OK == result, + "With these static arguments AddRule should never fail, what happened?"); + result = mPolicy->AddRule(sandbox::TargetPolicy::SUBSYS_HANDLES, + sandbox::TargetPolicy::HANDLES_DUP_ANY, L"File"); + MOZ_RELEASE_ASSERT( + sandbox::SBOX_ALL_OK == result, + "With these static arguments AddRule should never fail, what happened?"); + + // The content process needs to be able to duplicate shared memory handles, + // which are Section handles, to the broker process and other child processes. + result = + mPolicy->AddRule(sandbox::TargetPolicy::SUBSYS_HANDLES, + sandbox::TargetPolicy::HANDLES_DUP_BROKER, L"Section"); + MOZ_RELEASE_ASSERT( + sandbox::SBOX_ALL_OK == result, + "With these static arguments AddRule should never fail, what happened?"); + result = mPolicy->AddRule(sandbox::TargetPolicy::SUBSYS_HANDLES, + sandbox::TargetPolicy::HANDLES_DUP_ANY, L"Section"); + MOZ_RELEASE_ASSERT( + sandbox::SBOX_ALL_OK == result, + "With these static arguments AddRule should never fail, what happened?"); + + // The content process needs to be able to duplicate semaphore handles, + // to the broker process and other child processes. + result = + mPolicy->AddRule(sandbox::TargetPolicy::SUBSYS_HANDLES, + sandbox::TargetPolicy::HANDLES_DUP_BROKER, L"Semaphore"); + MOZ_RELEASE_ASSERT( + sandbox::SBOX_ALL_OK == result, + "With these static arguments AddRule should never fail, what happened?"); + result = + mPolicy->AddRule(sandbox::TargetPolicy::SUBSYS_HANDLES, + sandbox::TargetPolicy::HANDLES_DUP_ANY, L"Semaphore"); + MOZ_RELEASE_ASSERT( + sandbox::SBOX_ALL_OK == result, + "With these static arguments AddRule should never fail, what happened?"); + + // Allow content processes to use complex line breaking brokering. + result = mPolicy->AddRule(sandbox::TargetPolicy::SUBSYS_LINE_BREAK, + sandbox::TargetPolicy::LINE_BREAK_ALLOW, nullptr); + MOZ_RELEASE_ASSERT( + sandbox::SBOX_ALL_OK == result, + "With these static arguments AddRule should never fail, what happened?"); + + if (aSandboxLevel >= 20) { + // Content process still needs to be able to read fonts. + wchar_t* fontsPath; + if (SUCCEEDED( + ::SHGetKnownFolderPath(FOLDERID_Fonts, 0, nullptr, &fontsPath))) { + std::wstring fontsStr = fontsPath; + ::CoTaskMemFree(fontsPath); + result = mPolicy->AddRule(sandbox::TargetPolicy::SUBSYS_FILES, + sandbox::TargetPolicy::FILES_ALLOW_READONLY, + fontsStr.c_str()); + if (sandbox::SBOX_ALL_OK != result) { + NS_ERROR("Failed to add fonts dir read access policy rule."); + LOG_E("Failed (ResultCode %d) to add read access to: %S", result, + fontsStr.c_str()); + } + + fontsStr += L"\\*"; + result = mPolicy->AddRule(sandbox::TargetPolicy::SUBSYS_FILES, + sandbox::TargetPolicy::FILES_ALLOW_READONLY, + fontsStr.c_str()); + if (sandbox::SBOX_ALL_OK != result) { + NS_ERROR("Failed to add fonts read access policy rule."); + LOG_E("Failed (ResultCode %d) to add read access to: %S", result, + fontsStr.c_str()); + } + } + + // We still currently create IPC named pipes in the content process. + result = mPolicy->AddRule(sandbox::TargetPolicy::SUBSYS_NAMED_PIPES, + sandbox::TargetPolicy::NAMEDPIPES_ALLOW_ANY, + L"\\\\.\\pipe\\chrome.*"); + MOZ_RELEASE_ASSERT( + sandbox::SBOX_ALL_OK == result, + "With these static arguments AddRule should never fail."); + } +} + +void SandboxBroker::SetSecurityLevelForGPUProcess(int32_t aSandboxLevel) { + MOZ_RELEASE_ASSERT(mPolicy, "mPolicy must be set before this call."); + + sandbox::JobLevel jobLevel; + sandbox::TokenLevel accessTokenLevel; + sandbox::IntegrityLevel initialIntegrityLevel; + sandbox::IntegrityLevel delayedIntegrityLevel; + + // The setting of these levels is pretty arbitrary, but they are a useful (if + // crude) tool while we are tightening the policy. Gaps are left to try and + // avoid changing their meaning. + if (aSandboxLevel >= 2) { + jobLevel = sandbox::JOB_NONE; + accessTokenLevel = sandbox::USER_LIMITED; + initialIntegrityLevel = sandbox::INTEGRITY_LEVEL_LOW; + delayedIntegrityLevel = sandbox::INTEGRITY_LEVEL_LOW; + } else { + MOZ_RELEASE_ASSERT(aSandboxLevel >= 1, + "Should not be called with aSandboxLevel < 1"); + jobLevel = sandbox::JOB_NONE; + accessTokenLevel = sandbox::USER_NON_ADMIN; + initialIntegrityLevel = sandbox::INTEGRITY_LEVEL_LOW; + delayedIntegrityLevel = sandbox::INTEGRITY_LEVEL_LOW; + } + + sandbox::ResultCode result = + SetJobLevel(mPolicy, jobLevel, 0 /* ui_exceptions */); + MOZ_RELEASE_ASSERT(sandbox::SBOX_ALL_OK == result, + "Setting job level failed, have you set memory limit when " + "jobLevel == JOB_NONE?"); + + // If the delayed access token is not restricted we don't want the initial one + // to be either, because it can interfere with running from a network drive. + sandbox::TokenLevel initialAccessTokenLevel = + (accessTokenLevel == sandbox::USER_UNPROTECTED || + accessTokenLevel == sandbox::USER_NON_ADMIN) + ? sandbox::USER_UNPROTECTED + : sandbox::USER_RESTRICTED_SAME_ACCESS; + + result = mPolicy->SetTokenLevel(initialAccessTokenLevel, accessTokenLevel); + MOZ_RELEASE_ASSERT(sandbox::SBOX_ALL_OK == result, + "Lockdown level cannot be USER_UNPROTECTED or USER_LAST " + "if initial level was USER_RESTRICTED_SAME_ACCESS"); + + result = mPolicy->SetIntegrityLevel(initialIntegrityLevel); + MOZ_RELEASE_ASSERT(sandbox::SBOX_ALL_OK == result, + "SetIntegrityLevel should never fail, what happened?"); + result = mPolicy->SetDelayedIntegrityLevel(delayedIntegrityLevel); + MOZ_RELEASE_ASSERT( + sandbox::SBOX_ALL_OK == result, + "SetDelayedIntegrityLevel should never fail, what happened?"); + + mPolicy->SetLockdownDefaultDacl(); + mPolicy->AddRestrictingRandomSid(); + + sandbox::MitigationFlags mitigations = + sandbox::MITIGATION_BOTTOM_UP_ASLR | sandbox::MITIGATION_HEAP_TERMINATE | + sandbox::MITIGATION_SEHOP | sandbox::MITIGATION_DEP_NO_ATL_THUNK | + sandbox::MITIGATION_DEP; + + if (StaticPrefs::security_sandbox_gpu_shadow_stack_enabled()) { + mitigations |= sandbox::MITIGATION_CET_COMPAT_MODE; + } + + result = mPolicy->SetProcessMitigations(mitigations); + MOZ_RELEASE_ASSERT(sandbox::SBOX_ALL_OK == result, + "Invalid flags for SetProcessMitigations."); + + mitigations = sandbox::MITIGATION_STRICT_HANDLE_CHECKS | + sandbox::MITIGATION_DLL_SEARCH_ORDER; + + result = mPolicy->SetDelayedProcessMitigations(mitigations); + MOZ_RELEASE_ASSERT(sandbox::SBOX_ALL_OK == result, + "Invalid flags for SetDelayedProcessMitigations."); + + // Add the policy for the client side of a pipe. It is just a file + // in the \pipe\ namespace. We restrict it to pipes that start with + // "chrome." so the sandboxed process cannot connect to system services. + result = mPolicy->AddRule(sandbox::TargetPolicy::SUBSYS_FILES, + sandbox::TargetPolicy::FILES_ALLOW_ANY, + L"\\??\\pipe\\chrome.*"); + MOZ_RELEASE_ASSERT( + sandbox::SBOX_ALL_OK == result, + "With these static arguments AddRule should never fail, what happened?"); + + // Add the policy for the client side of the crash server pipe. + result = mPolicy->AddRule(sandbox::TargetPolicy::SUBSYS_FILES, + sandbox::TargetPolicy::FILES_ALLOW_ANY, + L"\\??\\pipe\\gecko-crash-server-pipe.*"); + MOZ_RELEASE_ASSERT( + sandbox::SBOX_ALL_OK == result, + "With these static arguments AddRule should never fail, what happened?"); + + // The GPU process needs to write to a shader cache for performance reasons + if (sProfileDir) { + AddCachedDirRule(mPolicy, sandbox::TargetPolicy::FILES_ALLOW_DIR_ANY, + sProfileDir, u"\\shader-cache"_ns); + + AddCachedDirRule(mPolicy, sandbox::TargetPolicy::FILES_ALLOW_ANY, + sProfileDir, u"\\shader-cache\\*"_ns); + } + + // The process needs to be able to duplicate shared memory handles, + // which are Section handles, to the broker process and other child processes. + result = + mPolicy->AddRule(sandbox::TargetPolicy::SUBSYS_HANDLES, + sandbox::TargetPolicy::HANDLES_DUP_BROKER, L"Section"); + MOZ_RELEASE_ASSERT( + sandbox::SBOX_ALL_OK == result, + "With these static arguments AddRule should never fail, what happened?"); + result = mPolicy->AddRule(sandbox::TargetPolicy::SUBSYS_HANDLES, + sandbox::TargetPolicy::HANDLES_DUP_ANY, L"Section"); + MOZ_RELEASE_ASSERT( + sandbox::SBOX_ALL_OK == result, + "With these static arguments AddRule should never fail, what happened?"); +} + +#define SANDBOX_ENSURE_SUCCESS(result, message) \ + do { \ + MOZ_ASSERT(sandbox::SBOX_ALL_OK == result, message); \ + if (sandbox::SBOX_ALL_OK != result) return false; \ + } while (0) + +bool SandboxBroker::SetSecurityLevelForRDDProcess() { + if (!mPolicy) { + return false; + } + + auto result = + SetJobLevel(mPolicy, sandbox::JOB_LOCKDOWN, 0 /* ui_exceptions */); + SANDBOX_ENSURE_SUCCESS( + result, + "SetJobLevel should never fail with these arguments, what happened?"); + + result = mPolicy->SetTokenLevel(sandbox::USER_RESTRICTED_SAME_ACCESS, + sandbox::USER_LIMITED); + SANDBOX_ENSURE_SUCCESS( + result, + "SetTokenLevel should never fail with these arguments, what happened?"); + + result = mPolicy->SetAlternateDesktop(true); + if (NS_WARN_IF(result != sandbox::SBOX_ALL_OK)) { + LOG_W("SetAlternateDesktop failed, result: %i, last error: %lx", result, + ::GetLastError()); + } + + result = mPolicy->SetIntegrityLevel(sandbox::INTEGRITY_LEVEL_LOW); + SANDBOX_ENSURE_SUCCESS(result, + "SetIntegrityLevel should never fail with these " + "arguments, what happened?"); + + result = mPolicy->SetDelayedIntegrityLevel(sandbox::INTEGRITY_LEVEL_LOW); + SANDBOX_ENSURE_SUCCESS(result, + "SetDelayedIntegrityLevel should never fail with " + "these arguments, what happened?"); + + mPolicy->SetLockdownDefaultDacl(); + mPolicy->AddRestrictingRandomSid(); + + sandbox::MitigationFlags mitigations = + sandbox::MITIGATION_BOTTOM_UP_ASLR | sandbox::MITIGATION_HEAP_TERMINATE | + sandbox::MITIGATION_SEHOP | sandbox::MITIGATION_EXTENSION_POINT_DISABLE | + sandbox::MITIGATION_DEP_NO_ATL_THUNK | sandbox::MITIGATION_DEP | + sandbox::MITIGATION_IMAGE_LOAD_PREFER_SYS32; + + if (StaticPrefs::security_sandbox_rdd_shadow_stack_enabled()) { + mitigations |= sandbox::MITIGATION_CET_COMPAT_MODE; + } + + result = mPolicy->SetProcessMitigations(mitigations); + SANDBOX_ENSURE_SUCCESS(result, "Invalid flags for SetProcessMitigations."); + + mitigations = sandbox::MITIGATION_STRICT_HANDLE_CHECKS | + sandbox::MITIGATION_DLL_SEARCH_ORDER; + + if (StaticPrefs::security_sandbox_rdd_acg_enabled()) { + // The RDD process depends on msmpeg2vdec.dll. + mitigations |= DynamicCodeFlagForSystemMediaLibraries(); + } + + result = mPolicy->SetDelayedProcessMitigations(mitigations); + SANDBOX_ENSURE_SUCCESS(result, + "Invalid flags for SetDelayedProcessMitigations."); + + result = AddCigToPolicy(mPolicy); + SANDBOX_ENSURE_SUCCESS(result, "Failed to initialize signed policy rules."); + + // Add the policy for the client side of a pipe. It is just a file + // in the \pipe\ namespace. We restrict it to pipes that start with + // "chrome." so the sandboxed process cannot connect to system services. + result = mPolicy->AddRule(sandbox::TargetPolicy::SUBSYS_FILES, + sandbox::TargetPolicy::FILES_ALLOW_ANY, + L"\\??\\pipe\\chrome.*"); + SANDBOX_ENSURE_SUCCESS( + result, + "With these static arguments AddRule should never fail, what happened?"); + + // Add the policy for the client side of the crash server pipe. + result = mPolicy->AddRule(sandbox::TargetPolicy::SUBSYS_FILES, + sandbox::TargetPolicy::FILES_ALLOW_ANY, + L"\\??\\pipe\\gecko-crash-server-pipe.*"); + SANDBOX_ENSURE_SUCCESS( + result, + "With these static arguments AddRule should never fail, what happened?"); + + // The process needs to be able to duplicate shared memory handles, + // which are Section handles, to the content processes. + result = mPolicy->AddRule(sandbox::TargetPolicy::SUBSYS_HANDLES, + sandbox::TargetPolicy::HANDLES_DUP_ANY, L"Section"); + SANDBOX_ENSURE_SUCCESS( + result, + "With these static arguments AddRule should never fail, what happened?"); + + // This section is needed to avoid an assert during crash reporting code + // when running mochitests. The assertion is here: + // toolkit/crashreporter/nsExceptionHandler.cpp:2041 + result = + mPolicy->AddRule(sandbox::TargetPolicy::SUBSYS_HANDLES, + sandbox::TargetPolicy::HANDLES_DUP_BROKER, L"Section"); + SANDBOX_ENSURE_SUCCESS( + result, + "With these static arguments AddRule should never fail, what happened?"); + + return true; +} + +bool SandboxBroker::SetSecurityLevelForSocketProcess() { + if (!mPolicy) { + return false; + } + + auto result = + SetJobLevel(mPolicy, sandbox::JOB_LOCKDOWN, 0 /* ui_exceptions */); + SANDBOX_ENSURE_SUCCESS( + result, + "SetJobLevel should never fail with these arguments, what happened?"); + + result = mPolicy->SetTokenLevel(sandbox::USER_RESTRICTED_SAME_ACCESS, + sandbox::USER_LIMITED); + SANDBOX_ENSURE_SUCCESS( + result, + "SetTokenLevel should never fail with these arguments, what happened?"); + + result = mPolicy->SetAlternateDesktop(true); + if (NS_WARN_IF(result != sandbox::SBOX_ALL_OK)) { + LOG_W("SetAlternateDesktop failed, result: %i, last error: %lx", result, + ::GetLastError()); + } + + result = mPolicy->SetIntegrityLevel(sandbox::INTEGRITY_LEVEL_LOW); + SANDBOX_ENSURE_SUCCESS(result, + "SetIntegrityLevel should never fail with these " + "arguments, what happened?"); + + result = + mPolicy->SetDelayedIntegrityLevel(sandbox::INTEGRITY_LEVEL_UNTRUSTED); + SANDBOX_ENSURE_SUCCESS(result, + "SetDelayedIntegrityLevel should never fail with " + "these arguments, what happened?"); + + mPolicy->SetLockdownDefaultDacl(); + mPolicy->AddRestrictingRandomSid(); + + sandbox::MitigationFlags mitigations = + sandbox::MITIGATION_BOTTOM_UP_ASLR | sandbox::MITIGATION_HEAP_TERMINATE | + sandbox::MITIGATION_SEHOP | sandbox::MITIGATION_EXTENSION_POINT_DISABLE | + sandbox::MITIGATION_DEP_NO_ATL_THUNK | sandbox::MITIGATION_DEP | + sandbox::MITIGATION_IMAGE_LOAD_PREFER_SYS32; + + if (StaticPrefs::security_sandbox_socket_shadow_stack_enabled()) { + mitigations |= sandbox::MITIGATION_CET_COMPAT_MODE; + } + + result = mPolicy->SetProcessMitigations(mitigations); + SANDBOX_ENSURE_SUCCESS(result, "Invalid flags for SetProcessMitigations."); + + if (StaticPrefs::security_sandbox_socket_win32k_disable()) { + result = AddWin32kLockdownPolicy(mPolicy, false); + SANDBOX_ENSURE_SUCCESS(result, "Failed to add the win32k lockdown policy"); + } + + mitigations = sandbox::MITIGATION_STRICT_HANDLE_CHECKS | + sandbox::MITIGATION_DLL_SEARCH_ORDER | + sandbox::MITIGATION_DYNAMIC_CODE_DISABLE; + + result = mPolicy->SetDelayedProcessMitigations(mitigations); + SANDBOX_ENSURE_SUCCESS(result, + "Invalid flags for SetDelayedProcessMitigations."); + + result = AddCigToPolicy(mPolicy); + SANDBOX_ENSURE_SUCCESS(result, "Failed to initialize signed policy rules."); + + // Add the policy for the client side of a pipe. It is just a file + // in the \pipe\ namespace. We restrict it to pipes that start with + // "chrome." so the sandboxed process cannot connect to system services. + result = mPolicy->AddRule(sandbox::TargetPolicy::SUBSYS_FILES, + sandbox::TargetPolicy::FILES_ALLOW_ANY, + L"\\??\\pipe\\chrome.*"); + SANDBOX_ENSURE_SUCCESS( + result, + "With these static arguments AddRule should never fail, what happened?"); + + // Add the policy for the client side of the crash server pipe. + result = mPolicy->AddRule(sandbox::TargetPolicy::SUBSYS_FILES, + sandbox::TargetPolicy::FILES_ALLOW_ANY, + L"\\??\\pipe\\gecko-crash-server-pipe.*"); + SANDBOX_ENSURE_SUCCESS( + result, + "With these static arguments AddRule should never fail, what happened?"); + + // This section is needed to avoid an assert during crash reporting code + // when running mochitests. The assertion is here: + // toolkit/crashreporter/nsExceptionHandler.cpp:2041 + result = + mPolicy->AddRule(sandbox::TargetPolicy::SUBSYS_HANDLES, + sandbox::TargetPolicy::HANDLES_DUP_BROKER, L"Section"); + SANDBOX_ENSURE_SUCCESS( + result, + "With these static arguments AddRule should never fail, what happened?"); + + return true; +} + +// A strict base sandbox for utility sandboxes to adapt. +struct UtilitySandboxProps { + sandbox::JobLevel mJobLevel = sandbox::JOB_LOCKDOWN; + + sandbox::TokenLevel mInitialTokenLevel = sandbox::USER_RESTRICTED_SAME_ACCESS; + sandbox::TokenLevel mDelayedTokenLevel = sandbox::USER_LOCKDOWN; + + sandbox::IntegrityLevel mInitialIntegrityLevel = // before lockdown + sandbox::INTEGRITY_LEVEL_LOW; + sandbox::IntegrityLevel mDelayedIntegrityLevel = // after lockdown + sandbox::INTEGRITY_LEVEL_UNTRUSTED; + + bool mUseAlternateWindowStation = true; + bool mUseAlternateDesktop = true; + bool mLockdownDefaultDacl = true; + bool mAddRestrictingRandomSid = true; + bool mUseWin32kLockdown = true; + bool mUseCig = true; + + sandbox::MitigationFlags mInitialMitigations = + sandbox::MITIGATION_BOTTOM_UP_ASLR | sandbox::MITIGATION_HEAP_TERMINATE | + sandbox::MITIGATION_SEHOP | sandbox::MITIGATION_EXTENSION_POINT_DISABLE | + sandbox::MITIGATION_DEP_NO_ATL_THUNK | sandbox::MITIGATION_DEP | + sandbox::MITIGATION_IMAGE_LOAD_PREFER_SYS32 | + sandbox::MITIGATION_CET_COMPAT_MODE; + + sandbox::MitigationFlags mDelayedMitigations = + sandbox::MITIGATION_STRICT_HANDLE_CHECKS | + sandbox::MITIGATION_DLL_SEARCH_ORDER | + sandbox::MITIGATION_DYNAMIC_CODE_DISABLE; + + // Low Privileged Application Container settings; + nsString mPackagePrefix; + nsTArray<sandbox::WellKnownCapabilities> mWellKnownCapabilites; + nsTArray<const wchar_t*> mNamedCapabilites; +}; + +struct GenericUtilitySandboxProps : public UtilitySandboxProps {}; + +struct UtilityAudioDecodingWmfSandboxProps : public UtilitySandboxProps { + UtilityAudioDecodingWmfSandboxProps() { + mDelayedTokenLevel = sandbox::USER_LIMITED; + mDelayedMitigations = sandbox::MITIGATION_STRICT_HANDLE_CHECKS | + sandbox::MITIGATION_DLL_SEARCH_ORDER; +#ifdef MOZ_WMF + if (StaticPrefs::security_sandbox_utility_wmf_acg_enabled()) { + mDelayedMitigations |= DynamicCodeFlagForSystemMediaLibraries(); + } +#else + mDelayedMitigations |= sandbox::MITIGATION_DYNAMIC_CODE_DISABLE; +#endif // MOZ_WMF + } +}; + +#ifdef MOZ_WMF_MEDIA_ENGINE +struct UtilityMfMediaEngineCdmSandboxProps : public UtilitySandboxProps { + UtilityMfMediaEngineCdmSandboxProps() { + mJobLevel = sandbox::JOB_INTERACTIVE; + mInitialTokenLevel = sandbox::USER_UNPROTECTED; + mDelayedTokenLevel = sandbox::USER_UNPROTECTED; + mUseAlternateDesktop = false; + mUseAlternateWindowStation = false; + mLockdownDefaultDacl = false; + mAddRestrictingRandomSid = false; + + // When we have an LPAC we can't set an integrity level and the process will + // default to low integrity anyway. Without an LPAC using low integrity + // causes problems with the CDMs. + mInitialIntegrityLevel = sandbox::INTEGRITY_LEVEL_LAST; + mDelayedIntegrityLevel = sandbox::INTEGRITY_LEVEL_LAST; + + if (StaticPrefs::security_sandbox_utility_wmf_cdm_lpac_enabled()) { + mPackagePrefix = u"fx.sb.cdm"_ns; + mWellKnownCapabilites = { + sandbox::WellKnownCapabilities::kPrivateNetworkClientServer, + sandbox::WellKnownCapabilities::kInternetClient, + }; + mNamedCapabilites = { + L"lpacCom", + L"lpacIdentityServices", + L"lpacMedia", + L"lpacPnPNotifications", + L"lpacServicesManagement", + L"lpacSessionManagement", + L"lpacAppExperience", + L"lpacInstrumentation", + L"lpacCryptoServices", + L"lpacEnterprisePolicyChangeNotifications", + L"mediaFoundationCdmFiles", + L"lpacMediaFoundationCdmData", + L"registryRead", + kLpacFirefoxInstallFiles, + L"lpacDeviceAccess", + }; + } + mUseWin32kLockdown = false; + mInitialMitigations = + sandbox::MITIGATION_BOTTOM_UP_ASLR | + sandbox::MITIGATION_HEAP_TERMINATE | sandbox::MITIGATION_SEHOP | + sandbox::MITIGATION_EXTENSION_POINT_DISABLE | + sandbox::MITIGATION_DEP_NO_ATL_THUNK | sandbox::MITIGATION_DEP | + sandbox::MITIGATION_CET_COMPAT_MODE; + + mDelayedMitigations = sandbox::MITIGATION_DLL_SEARCH_ORDER; + } +}; +#endif + +struct WindowsUtilitySandboxProps : public UtilitySandboxProps { + WindowsUtilitySandboxProps() { + mJobLevel = sandbox::JOB_INTERACTIVE; + mDelayedTokenLevel = sandbox::USER_RESTRICTED_SAME_ACCESS; + mUseAlternateWindowStation = false; + mInitialIntegrityLevel = sandbox::INTEGRITY_LEVEL_MEDIUM; + mDelayedIntegrityLevel = sandbox::INTEGRITY_LEVEL_MEDIUM; + mUseWin32kLockdown = false; + mUseCig = false; + mDelayedMitigations = sandbox::MITIGATION_STRICT_HANDLE_CHECKS | + sandbox::MITIGATION_DLL_SEARCH_ORDER; + } +}; + +static const char* WellKnownCapabilityNames[] = { + "InternetClient", + "InternetClientServer", + "PrivateNetworkClientServer", + "PicturesLibrary", + "VideosLibrary", + "MusicLibrary", + "DocumentsLibrary", + "EnterpriseAuthentication", + "SharedUserCertificates", + "RemovableStorage", + "Appointments", + "Contacts", +}; + +void LogUtilitySandboxProps(const UtilitySandboxProps& us) { + if (!static_cast<LogModule*>(sSandboxBrokerLog)->ShouldLog(LogLevel::Debug)) { + return; + } + + nsAutoCString logMsg; + logMsg.AppendPrintf("Building sandbox for utility process:\n"); + logMsg.AppendPrintf("\tJob Level: %d\n", static_cast<int>(us.mJobLevel)); + logMsg.AppendPrintf("\tInitial Token Level: %d\n", + static_cast<int>(us.mInitialTokenLevel)); + logMsg.AppendPrintf("\tDelayed Token Level: %d\n", + static_cast<int>(us.mDelayedTokenLevel)); + logMsg.AppendPrintf("\tInitial Integrity Level: %d\n", + static_cast<int>(us.mInitialIntegrityLevel)); + logMsg.AppendPrintf("\tDelayed Integrity Level: %d\n", + static_cast<int>(us.mDelayedIntegrityLevel)); + logMsg.AppendPrintf("\tUse Alternate Window Station: %s\n", + us.mUseAlternateWindowStation ? "yes" : "no"); + logMsg.AppendPrintf("\tUse Alternate Desktop: %s\n", + us.mUseAlternateDesktop ? "yes" : "no"); + logMsg.AppendPrintf("\tLockdown Default Dacl: %s\n", + us.mLockdownDefaultDacl ? "yes" : "no"); + logMsg.AppendPrintf("\tAdd Random Restricting SID: %s\n", + us.mAddRestrictingRandomSid ? "yes" : "no"); + logMsg.AppendPrintf("\tUse Win32k Lockdown: %s\n", + us.mUseWin32kLockdown ? "yes" : "no"); + logMsg.AppendPrintf("\tUse CIG: %s\n", us.mUseCig ? "yes" : "no"); + logMsg.AppendPrintf("\tInitial mitigations: %016llx\n", + static_cast<uint64_t>(us.mInitialMitigations)); + logMsg.AppendPrintf("\tDelayed mitigations: %016llx\n", + static_cast<uint64_t>(us.mDelayedMitigations)); + if (us.mPackagePrefix.IsEmpty()) { + logMsg.AppendPrintf("\tNo Low Privileged Application Container\n"); + } else { + logMsg.AppendPrintf("\tLow Privileged Application Container Settings:\n"); + logMsg.AppendPrintf("\t\tPackage Name Prefix: %S\n", + static_cast<wchar_t*>(us.mPackagePrefix.get())); + logMsg.AppendPrintf("\t\tWell Known Capabilities:\n"); + for (auto wkCap : us.mWellKnownCapabilites) { + logMsg.AppendPrintf("\t\t\t%s\n", WellKnownCapabilityNames[wkCap]); + } + logMsg.AppendPrintf("\t\tNamed Capabilities:\n"); + for (auto namedCap : us.mNamedCapabilites) { + logMsg.AppendPrintf("\t\t\t%S\n", namedCap); + } + } + + LOG_D("%s", logMsg.get()); +} + +bool BuildUtilitySandbox(sandbox::TargetPolicy* policy, + const UtilitySandboxProps& us) { + LogUtilitySandboxProps(us); + + auto result = SetJobLevel(policy, us.mJobLevel, 0 /* ui_exceptions */); + SANDBOX_ENSURE_SUCCESS( + result, + "SetJobLevel should never fail with these arguments, what happened?"); + + result = policy->SetTokenLevel(us.mInitialTokenLevel, us.mDelayedTokenLevel); + SANDBOX_ENSURE_SUCCESS( + result, + "SetTokenLevel should never fail with these arguments, what happened?"); + + if (us.mInitialIntegrityLevel != sandbox::INTEGRITY_LEVEL_LAST) { + result = policy->SetIntegrityLevel(us.mInitialIntegrityLevel); + SANDBOX_ENSURE_SUCCESS(result, + "SetIntegrityLevel should never fail with these " + "arguments, what happened?"); + } + + if (us.mDelayedIntegrityLevel != sandbox::INTEGRITY_LEVEL_LAST) { + result = policy->SetDelayedIntegrityLevel(us.mDelayedIntegrityLevel); + SANDBOX_ENSURE_SUCCESS(result, + "SetIntegrityLevel should never fail with these " + "arguments, what happened?"); + } + + if (us.mUseAlternateDesktop) { + result = policy->SetAlternateDesktop(us.mUseAlternateWindowStation); + if (NS_WARN_IF(result != sandbox::SBOX_ALL_OK)) { + LOG_W("SetAlternateDesktop failed, result: %i, last error: %lx", result, + ::GetLastError()); + } + } + + if (us.mLockdownDefaultDacl) { + policy->SetLockdownDefaultDacl(); + } + if (us.mAddRestrictingRandomSid) { + policy->AddRestrictingRandomSid(); + } + + result = policy->SetProcessMitigations(us.mInitialMitigations); + SANDBOX_ENSURE_SUCCESS(result, "Invalid flags for SetProcessMitigations."); + + result = policy->SetDelayedProcessMitigations(us.mDelayedMitigations); + SANDBOX_ENSURE_SUCCESS(result, + "Invalid flags for SetDelayedProcessMitigations."); + + // Win32k lockdown might not work on earlier versions + // Bug 1719212, 1769992 + if (us.mUseWin32kLockdown && IsWin10FallCreatorsUpdateOrLater()) { + result = AddWin32kLockdownPolicy(policy, false); + SANDBOX_ENSURE_SUCCESS(result, "Failed to add the win32k lockdown policy"); + } + + if (us.mUseCig) { + bool alwaysProxyBinDirLoading = mozilla::HasPackageIdentity(); + result = AddCigToPolicy(policy, alwaysProxyBinDirLoading); + SANDBOX_ENSURE_SUCCESS(result, "Failed to initialize signed policy rules."); + } + + if (!us.mPackagePrefix.IsEmpty()) { + MOZ_ASSERT(us.mInitialIntegrityLevel == sandbox::INTEGRITY_LEVEL_LAST, + "Initial integrity level cannot be specified if using an LPAC."); + + result = AddAndConfigureAppContainerProfile(policy, us.mPackagePrefix, + us.mWellKnownCapabilites, + us.mNamedCapabilites); + SANDBOX_ENSURE_SUCCESS(result, "Failed to configure AppContainer profile."); + } + // Add the policy for the client side of a pipe. It is just a file + // in the \pipe\ namespace. We restrict it to pipes that start with + // "chrome." so the sandboxed process cannot connect to system services. + result = policy->AddRule(sandbox::TargetPolicy::SUBSYS_FILES, + sandbox::TargetPolicy::FILES_ALLOW_ANY, + L"\\??\\pipe\\chrome.*"); + SANDBOX_ENSURE_SUCCESS( + result, + "With these static arguments AddRule should never fail, what happened?"); + + // Add the policy for the client side of the crash server pipe. + result = policy->AddRule(sandbox::TargetPolicy::SUBSYS_FILES, + sandbox::TargetPolicy::FILES_ALLOW_ANY, + L"\\??\\pipe\\gecko-crash-server-pipe.*"); + SANDBOX_ENSURE_SUCCESS( + result, + "With these static arguments AddRule should never fail, what happened?"); + + return true; +} + +bool SandboxBroker::SetSecurityLevelForUtilityProcess( + mozilla::ipc::SandboxingKind aSandbox) { + if (!mPolicy) { + return false; + } + + switch (aSandbox) { + case mozilla::ipc::SandboxingKind::GENERIC_UTILITY: + return BuildUtilitySandbox(mPolicy, GenericUtilitySandboxProps()); + case mozilla::ipc::SandboxingKind::UTILITY_AUDIO_DECODING_WMF: + return BuildUtilitySandbox(mPolicy, + UtilityAudioDecodingWmfSandboxProps()); +#ifdef MOZ_WMF_MEDIA_ENGINE + case mozilla::ipc::SandboxingKind::MF_MEDIA_ENGINE_CDM: + return BuildUtilitySandbox(mPolicy, + UtilityMfMediaEngineCdmSandboxProps()); +#endif + case mozilla::ipc::SandboxingKind::WINDOWS_UTILS: + return BuildUtilitySandbox(mPolicy, WindowsUtilitySandboxProps()); + default: + MOZ_ASSERT_UNREACHABLE("Unknown sandboxing value"); + return false; + } +} + +bool SandboxBroker::SetSecurityLevelForGMPlugin(SandboxLevel aLevel, + bool aIsRemoteLaunch) { + if (!mPolicy) { + return false; + } + + auto result = + SetJobLevel(mPolicy, sandbox::JOB_LOCKDOWN, 0 /* ui_exceptions */); + SANDBOX_ENSURE_SUCCESS( + result, + "SetJobLevel should never fail with these arguments, what happened?"); + auto level = (aLevel == Restricted) ? sandbox::USER_RESTRICTED + : sandbox::USER_LOCKDOWN; + result = mPolicy->SetTokenLevel(sandbox::USER_RESTRICTED_SAME_ACCESS, level); + SANDBOX_ENSURE_SUCCESS( + result, + "SetTokenLevel should never fail with these arguments, what happened?"); + + result = mPolicy->SetAlternateDesktop(true); + if (NS_WARN_IF(result != sandbox::SBOX_ALL_OK)) { + LOG_W("SetAlternateDesktop failed, result: %i, last error: %lx", result, + ::GetLastError()); + } + + result = mPolicy->SetIntegrityLevel(sandbox::INTEGRITY_LEVEL_LOW); + MOZ_ASSERT(sandbox::SBOX_ALL_OK == result, + "SetIntegrityLevel should never fail with these arguments, what " + "happened?"); + + result = + mPolicy->SetDelayedIntegrityLevel(sandbox::INTEGRITY_LEVEL_UNTRUSTED); + SANDBOX_ENSURE_SUCCESS(result, + "SetIntegrityLevel should never fail with these " + "arguments, what happened?"); + + mPolicy->SetLockdownDefaultDacl(); + mPolicy->AddRestrictingRandomSid(); + + sandbox::MitigationFlags mitigations = + sandbox::MITIGATION_BOTTOM_UP_ASLR | sandbox::MITIGATION_HEAP_TERMINATE | + sandbox::MITIGATION_SEHOP | sandbox::MITIGATION_EXTENSION_POINT_DISABLE | + sandbox::MITIGATION_DEP_NO_ATL_THUNK | sandbox::MITIGATION_DEP; + + if (StaticPrefs::security_sandbox_gmp_shadow_stack_enabled()) { + mitigations |= sandbox::MITIGATION_CET_COMPAT_MODE; + } + + result = mPolicy->SetProcessMitigations(mitigations); + SANDBOX_ENSURE_SUCCESS(result, "Invalid flags for SetProcessMitigations."); + + // Chromium only implements win32k disable for PPAPI on Win10 or later, + // believed to be due to the interceptions required for OPM. + if (StaticPrefs::security_sandbox_gmp_win32k_disable() && IsWin10OrLater()) { + result = AddWin32kLockdownPolicy(mPolicy, true); + SANDBOX_ENSURE_SUCCESS(result, "Failed to add the win32k lockdown policy"); + } + + mitigations = sandbox::MITIGATION_STRICT_HANDLE_CHECKS | + sandbox::MITIGATION_DLL_SEARCH_ORDER; + + result = mPolicy->SetDelayedProcessMitigations(mitigations); + SANDBOX_ENSURE_SUCCESS(result, + "Invalid flags for SetDelayedProcessMitigations."); + + // Add the policy for the client side of a pipe. It is just a file + // in the \pipe\ namespace. We restrict it to pipes that start with + // "chrome." so the sandboxed process cannot connect to system services. + result = mPolicy->AddRule(sandbox::TargetPolicy::SUBSYS_FILES, + sandbox::TargetPolicy::FILES_ALLOW_ANY, + L"\\??\\pipe\\chrome.*"); + SANDBOX_ENSURE_SUCCESS( + result, + "With these static arguments AddRule should never fail, what happened?"); + + // Add the policy for the client side of the crash server pipe. + result = mPolicy->AddRule(sandbox::TargetPolicy::SUBSYS_FILES, + sandbox::TargetPolicy::FILES_ALLOW_ANY, + L"\\??\\pipe\\gecko-crash-server-pipe.*"); + SANDBOX_ENSURE_SUCCESS( + result, + "With these static arguments AddRule should never fail, what happened?"); + +#ifdef DEBUG + // The plugin process can't create named events, but we'll + // make an exception for the events used in logging. Removing + // this will break EME in debug builds. + result = mPolicy->AddRule(sandbox::TargetPolicy::SUBSYS_SYNC, + sandbox::TargetPolicy::EVENTS_ALLOW_ANY, + L"ChromeIPCLog.*"); + SANDBOX_ENSURE_SUCCESS( + result, + "With these static arguments AddRule should never fail, what happened?"); +#endif + + // The following rules were added because, during analysis of an EME + // plugin during development, these registry keys were accessed when + // loading the plugin. Commenting out these policy exceptions caused + // plugin loading to fail, so they are necessary for proper functioning + // of at least one EME plugin. + result = mPolicy->AddRule(sandbox::TargetPolicy::SUBSYS_REGISTRY, + sandbox::TargetPolicy::REG_ALLOW_READONLY, + L"HKEY_CURRENT_USER"); + SANDBOX_ENSURE_SUCCESS( + result, + "With these static arguments AddRule should never fail, what happened?"); + + result = mPolicy->AddRule(sandbox::TargetPolicy::SUBSYS_REGISTRY, + sandbox::TargetPolicy::REG_ALLOW_READONLY, + L"HKEY_CURRENT_USER\\Control Panel\\Desktop"); + SANDBOX_ENSURE_SUCCESS( + result, + "With these static arguments AddRule should never fail, what happened?"); + + result = mPolicy->AddRule( + sandbox::TargetPolicy::SUBSYS_REGISTRY, + sandbox::TargetPolicy::REG_ALLOW_READONLY, + L"HKEY_CURRENT_USER\\Control Panel\\Desktop\\LanguageConfiguration"); + SANDBOX_ENSURE_SUCCESS( + result, + "With these static arguments AddRule should never fail, what happened?"); + + result = mPolicy->AddRule( + sandbox::TargetPolicy::SUBSYS_REGISTRY, + sandbox::TargetPolicy::REG_ALLOW_READONLY, + L"HKEY_LOCAL_" + L"MACHINE\\SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\SideBySide"); + SANDBOX_ENSURE_SUCCESS( + result, + "With these static arguments AddRule should never fail, what happened?"); + + // The following rules were added because, during analysis of an EME + // plugin during development, these registry keys were accessed when + // loading the plugin. Commenting out these policy exceptions did not + // cause anything to break during initial testing, but might cause + // unforeseen issues down the road. + result = mPolicy->AddRule( + sandbox::TargetPolicy::SUBSYS_REGISTRY, + sandbox::TargetPolicy::REG_ALLOW_READONLY, + L"HKEY_LOCAL_MACHINE\\SOFTWARE\\Policies\\Microsoft\\MUI\\Settings"); + SANDBOX_ENSURE_SUCCESS( + result, + "With these static arguments AddRule should never fail, what happened?"); + + result = mPolicy->AddRule( + sandbox::TargetPolicy::SUBSYS_REGISTRY, + sandbox::TargetPolicy::REG_ALLOW_READONLY, + L"HKEY_CURRENT_USER\\Software\\Policies\\Microsoft\\Control " + L"Panel\\Desktop"); + SANDBOX_ENSURE_SUCCESS( + result, + "With these static arguments AddRule should never fail, what happened?"); + + result = mPolicy->AddRule( + sandbox::TargetPolicy::SUBSYS_REGISTRY, + sandbox::TargetPolicy::REG_ALLOW_READONLY, + L"HKEY_CURRENT_USER\\Control Panel\\Desktop\\PreferredUILanguages"); + SANDBOX_ENSURE_SUCCESS( + result, + "With these static arguments AddRule should never fail, what happened?"); + + result = mPolicy->AddRule(sandbox::TargetPolicy::SUBSYS_REGISTRY, + sandbox::TargetPolicy::REG_ALLOW_READONLY, + L"HKEY_LOCAL_" + L"MACHINE\\SOFTWARE\\Microsoft\\Windows\\CurrentVer" + L"sion\\SideBySide\\PreferExternalManifest"); + SANDBOX_ENSURE_SUCCESS( + result, + "With these static arguments AddRule should never fail, what happened?"); + + // The following rules were added to allow a GMP to be loaded when any + // AppLocker DLL rules are specified. If the rules specifically block the DLL + // then it will not load. + result = mPolicy->AddRule(sandbox::TargetPolicy::SUBSYS_FILES, + sandbox::TargetPolicy::FILES_ALLOW_READONLY, + L"\\Device\\SrpDevice"); + SANDBOX_ENSURE_SUCCESS( + result, + "With these static arguments AddRule should never fail, what happened?"); + result = mPolicy->AddRule( + sandbox::TargetPolicy::SUBSYS_REGISTRY, + sandbox::TargetPolicy::REG_ALLOW_READONLY, + L"HKEY_LOCAL_MACHINE\\System\\CurrentControlSet\\Control\\Srp\\GP\\"); + SANDBOX_ENSURE_SUCCESS( + result, + "With these static arguments AddRule should never fail, what happened?"); + // On certain Windows versions there is a double slash before GP in the path. + result = mPolicy->AddRule( + sandbox::TargetPolicy::SUBSYS_REGISTRY, + sandbox::TargetPolicy::REG_ALLOW_READONLY, + L"HKEY_LOCAL_MACHINE\\System\\CurrentControlSet\\Control\\Srp\\\\GP\\"); + SANDBOX_ENSURE_SUCCESS( + result, + "With these static arguments AddRule should never fail, what happened?"); + + // The GMP process needs to be able to share memory with the main process for + // crash reporting. On arm64 when we are launching remotely via an x86 broker, + // we need the rule to be HANDLES_DUP_ANY, because we still need to duplicate + // to the main process not the child's broker. + result = mPolicy->AddRule(sandbox::TargetPolicy::SUBSYS_HANDLES, + aIsRemoteLaunch + ? sandbox::TargetPolicy::HANDLES_DUP_ANY + : sandbox::TargetPolicy::HANDLES_DUP_BROKER, + L"Section"); + SANDBOX_ENSURE_SUCCESS( + result, + "With these static arguments AddRule should never fail, what happened?"); + + return true; +} +#undef SANDBOX_ENSURE_SUCCESS + +bool SandboxBroker::AllowReadFile(wchar_t const* file) { + if (!mPolicy) { + return false; + } + + auto result = + mPolicy->AddRule(sandbox::TargetPolicy::SUBSYS_FILES, + sandbox::TargetPolicy::FILES_ALLOW_READONLY, file); + if (sandbox::SBOX_ALL_OK != result) { + LOG_E("Failed (ResultCode %d) to add read access to: %S", result, file); + return false; + } + + return true; +} + +/* static */ +bool SandboxBroker::AddTargetPeer(HANDLE aPeerProcess) { + if (!sBrokerService) { + return false; + } + + sandbox::ResultCode result = sBrokerService->AddTargetPeer(aPeerProcess); + return (sandbox::SBOX_ALL_OK == result); +} + +void SandboxBroker::AddHandleToShare(HANDLE aHandle) { + mPolicy->AddHandleToShare(aHandle); +} + +bool SandboxBroker::IsWin32kLockedDown() { + return mPolicy->GetProcessMitigations() & sandbox::MITIGATION_WIN32K_DISABLE; +} + +void SandboxBroker::ApplyLoggingPolicy() { + MOZ_ASSERT(mPolicy); + + // Add dummy rules, so that we can log in the interception code. + // We already have a file interception set up for the client side of pipes. + // Also, passing just "dummy" for file system policy causes win_utils.cc + // IsReparsePoint() to loop. + mPolicy->AddRule(sandbox::TargetPolicy::SUBSYS_NAMED_PIPES, + sandbox::TargetPolicy::NAMEDPIPES_ALLOW_ANY, L"dummy"); + mPolicy->AddRule(sandbox::TargetPolicy::SUBSYS_PROCESS, + sandbox::TargetPolicy::PROCESS_MIN_EXEC, L"dummy"); + mPolicy->AddRule(sandbox::TargetPolicy::SUBSYS_REGISTRY, + sandbox::TargetPolicy::REG_ALLOW_READONLY, + L"HKEY_CURRENT_USER\\dummy"); + mPolicy->AddRule(sandbox::TargetPolicy::SUBSYS_SYNC, + sandbox::TargetPolicy::EVENTS_ALLOW_READONLY, L"dummy"); + mPolicy->AddRule(sandbox::TargetPolicy::SUBSYS_HANDLES, + sandbox::TargetPolicy::HANDLES_DUP_BROKER, L"dummy"); +} + +SandboxBroker::~SandboxBroker() { + if (mPolicy) { + mPolicy->Release(); + mPolicy = nullptr; + } +} + +} // namespace mozilla diff --git a/security/sandbox/win/src/sandboxbroker/sandboxBroker.h b/security/sandbox/win/src/sandboxbroker/sandboxBroker.h new file mode 100644 index 0000000000..752fbaa9aa --- /dev/null +++ b/security/sandbox/win/src/sandboxbroker/sandboxBroker.h @@ -0,0 +1,137 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=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/. */ + +#ifndef __SECURITY_SANDBOX_SANDBOXBROKER_H__ +#define __SECURITY_SANDBOX_SANDBOXBROKER_H__ + +#include <stdint.h> +#include <windows.h> + +#include "build/build_config.h" +#include "mozilla/ipc/EnvironmentMap.h" +#include "nsCOMPtr.h" +#include "nsXULAppAPI.h" +#include "nsISupportsImpl.h" + +#include "mozilla/ipc/UtilityProcessSandboxing.h" +#include "mozilla/ipc/LaunchError.h" +#include "mozilla/Result.h" + +namespace sandbox { +class BrokerServices; +class TargetPolicy; +} // namespace sandbox + +namespace mozilla { + +class AbstractSandboxBroker { + public: + NS_INLINE_DECL_THREADSAFE_REFCOUNTING(AbstractSandboxBroker) + + virtual void Shutdown() = 0; + virtual Result<Ok, mozilla::ipc::LaunchError> LaunchApp( + const wchar_t* aPath, const wchar_t* aArguments, + base::EnvironmentMap& aEnvironment, GeckoProcessType aProcessType, + const bool aEnableLogging, const IMAGE_THUNK_DATA* aCachedNtdllThunk, + void** aProcessHandle) = 0; + + // Security levels for different types of processes + virtual void SetSecurityLevelForContentProcess(int32_t aSandboxLevel, + bool aIsFileProcess) = 0; + + virtual void SetSecurityLevelForGPUProcess(int32_t aSandboxLevel) = 0; + virtual bool SetSecurityLevelForRDDProcess() = 0; + virtual bool SetSecurityLevelForSocketProcess() = 0; + virtual bool SetSecurityLevelForUtilityProcess( + mozilla::ipc::SandboxingKind aSandbox) = 0; + + enum SandboxLevel { LockDown, Restricted }; + virtual bool SetSecurityLevelForGMPlugin(SandboxLevel aLevel, + bool aIsRemoteLaunch = false) = 0; + + // File system permissions + virtual bool AllowReadFile(wchar_t const* file) = 0; + + /** + * Share a HANDLE with the child process. The HANDLE will be made available + * in the child process at the memory address + * |reinterpret_cast<uintptr_t>(aHandle)|. It is the caller's responsibility + * to communicate this address to the child. + */ + virtual void AddHandleToShare(HANDLE aHandle) = 0; + + /** + * @return true if policy has win32k locked down, otherwise false + */ + virtual bool IsWin32kLockedDown() = 0; + + protected: + virtual ~AbstractSandboxBroker() {} +}; + +class SandboxBroker : public AbstractSandboxBroker { + public: + SandboxBroker(); + + static void Initialize(sandbox::BrokerServices* aBrokerServices); + + void Shutdown() override {} + + /** + * Do initialization that depends on parts of the Gecko machinery having been + * created first. + */ + static void GeckoDependentInitialize(); + + Result<Ok, mozilla::ipc::LaunchError> LaunchApp( + const wchar_t* aPath, const wchar_t* aArguments, + base::EnvironmentMap& aEnvironment, GeckoProcessType aProcessType, + const bool aEnableLogging, const IMAGE_THUNK_DATA* aCachedNtdllThunk, + void** aProcessHandle) override; + virtual ~SandboxBroker(); + + // Security levels for different types of processes + void SetSecurityLevelForContentProcess(int32_t aSandboxLevel, + bool aIsFileProcess) override; + + void SetSecurityLevelForGPUProcess(int32_t aSandboxLevel) override; + bool SetSecurityLevelForRDDProcess() override; + bool SetSecurityLevelForSocketProcess() override; + bool SetSecurityLevelForGMPlugin(SandboxLevel aLevel, + bool aIsRemoteLaunch = false) override; + bool SetSecurityLevelForUtilityProcess( + mozilla::ipc::SandboxingKind aSandbox) override; + + // File system permissions + bool AllowReadFile(wchar_t const* file) override; + + /** + * Exposes AddTargetPeer from broker services, so that non-sandboxed + * processes can be added as handle duplication targets. + */ + static bool AddTargetPeer(HANDLE aPeerProcess); + + /** + * Share a HANDLE with the child process. The HANDLE will be made available + * in the child process at the memory address + * |reinterpret_cast<uintptr_t>(aHandle)|. It is the caller's responsibility + * to communicate this address to the child. + */ + void AddHandleToShare(HANDLE aHandle) override; + + bool IsWin32kLockedDown() final; + + // Set up dummy interceptions via the broker, so we can log calls. + void ApplyLoggingPolicy(); + + private: + static bool sRunningFromNetworkDrive; + sandbox::TargetPolicy* mPolicy; +}; + +} // namespace mozilla + +#endif diff --git a/security/sandbox/win/src/sandboxtarget/moz.build b/security/sandbox/win/src/sandboxtarget/moz.build new file mode 100644 index 0000000000..3c28eebba2 --- /dev/null +++ b/security/sandbox/win/src/sandboxtarget/moz.build @@ -0,0 +1,22 @@ +# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*- +# vim: set filetype=python: +# 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/. + +EXPORTS.mozilla += [ + "sandboxTarget.h", +] + +SOURCES += [ + "sandboxTarget.cpp", +] + +LOCAL_INCLUDES += [ + "/security/sandbox/chromium", + "/security/sandbox/chromium-shim", +] + +DEFINES["UNICODE"] = True + +FINAL_LIBRARY = "xul" diff --git a/security/sandbox/win/src/sandboxtarget/sandboxTarget.cpp b/security/sandbox/win/src/sandboxtarget/sandboxTarget.cpp new file mode 100644 index 0000000000..b0b0876bca --- /dev/null +++ b/security/sandbox/win/src/sandboxtarget/sandboxTarget.cpp @@ -0,0 +1,64 @@ +/* -*- 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 http://mozilla.org/MPL/2.0/. */ + +#include "sandboxTarget.h" + +#include "sandbox/win/src/sandbox.h" + +namespace mozilla { + +// We need to define this function out of line so that clang-cl doesn't inline +// it. +/* static */ +SandboxTarget* SandboxTarget::Instance() { + static SandboxTarget sb; + return &sb; +} + +void SandboxTarget::StartSandbox() { + if (mTargetServices) { + mTargetServices->LowerToken(); + NotifyStartObservers(); + } +} + +void SandboxTarget::NotifyStartObservers() { + for (auto&& obs : mStartObservers) { + if (!obs) { + continue; + } + + obs(); + } + + mStartObservers.clear(); +} + +bool SandboxTarget::BrokerDuplicateHandle(HANDLE aSourceHandle, + DWORD aTargetProcessId, + HANDLE* aTargetHandle, + DWORD aDesiredAccess, + DWORD aOptions) { + if (!mTargetServices) { + return false; + } + + sandbox::ResultCode result = mTargetServices->DuplicateHandle( + aSourceHandle, aTargetProcessId, aTargetHandle, aDesiredAccess, aOptions); + return (sandbox::SBOX_ALL_OK == result); +} +bool SandboxTarget::GetComplexLineBreaks(const WCHAR* text, uint32_t length, + uint8_t* break_before) { + if (!mTargetServices) { + return false; + } + + sandbox::ResultCode result = + mTargetServices->GetComplexLineBreaks(text, length, break_before); + return (sandbox::SBOX_ALL_OK == result); +} + +} // namespace mozilla diff --git a/security/sandbox/win/src/sandboxtarget/sandboxTarget.h b/security/sandbox/win/src/sandboxtarget/sandboxTarget.h new file mode 100644 index 0000000000..7867f0bb33 --- /dev/null +++ b/security/sandbox/win/src/sandboxtarget/sandboxTarget.h @@ -0,0 +1,80 @@ +/* -*- 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 http://mozilla.org/MPL/2.0/. */ + +#ifndef __SECURITY_SANDBOX_SANDBOXTARGET_H__ +#define __SECURITY_SANDBOX_SANDBOXTARGET_H__ + +#include <windows.h> + +#include <functional> +#include <list> +#include <utility> + +#include "mozilla/Assertions.h" + +namespace sandbox { +class TargetServices; +} + +namespace mozilla { + +class SandboxTarget { + public: + /** + * Obtains a pointer to the singleton instance + */ + static SandboxTarget* Instance(); + + /** + * Used by the application to pass in the target services that provide certain + * functions to the sandboxed code. + * The target services must already be initialized. + * + * @param aTargetServices The target services that will be used + */ + void SetTargetServices(sandbox::TargetServices* aTargetServices) { + MOZ_ASSERT(aTargetServices); + MOZ_ASSERT(!mTargetServices, + "Sandbox TargetServices must only be set once."); + + mTargetServices = aTargetServices; + } + + template <typename CallbackT> + void RegisterSandboxStartCallback(CallbackT&& aCallback) { + mStartObservers.push_back(std::forward<CallbackT>(aCallback)); + } + + /** + * Called by the library that wants to "start" the sandbox, i.e. change to the + * more secure delayed / lockdown policy. + */ + void StartSandbox(); + + /** + * Used to duplicate handles via the broker process. The permission for the + * handle type and target process has to have been set on the sandbox policy. + */ + bool BrokerDuplicateHandle(HANDLE aSourceHandle, DWORD aTargetProcessId, + HANDLE* aTargetHandle, DWORD aDesiredAccess, + DWORD aOptions); + + bool GetComplexLineBreaks(const WCHAR* text, uint32_t length, + uint8_t* break_before); + + protected: + SandboxTarget() : mTargetServices(nullptr) {} + + sandbox::TargetServices* mTargetServices; + + private: + void NotifyStartObservers(); + std::list<std::function<void()>> mStartObservers; +}; + +} // namespace mozilla + +#endif |