summaryrefslogtreecommitdiffstats
path: root/security/sandbox/linux/launch
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-19 00:47:55 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-19 00:47:55 +0000
commit26a029d407be480d791972afb5975cf62c9360a6 (patch)
treef435a8308119effd964b339f76abb83a57c29483 /security/sandbox/linux/launch
parentInitial commit. (diff)
downloadfirefox-26a029d407be480d791972afb5975cf62c9360a6.tar.xz
firefox-26a029d407be480d791972afb5975cf62c9360a6.zip
Adding upstream version 124.0.1.upstream/124.0.1
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'security/sandbox/linux/launch')
-rw-r--r--security/sandbox/linux/launch/LinuxCapabilities.cpp26
-rw-r--r--security/sandbox/linux/launch/LinuxCapabilities.h122
-rw-r--r--security/sandbox/linux/launch/SandboxLaunch.cpp675
-rw-r--r--security/sandbox/linux/launch/SandboxLaunch.h71
-rw-r--r--security/sandbox/linux/launch/moz.build33
5 files changed, 927 insertions, 0 deletions
diff --git a/security/sandbox/linux/launch/LinuxCapabilities.cpp b/security/sandbox/linux/launch/LinuxCapabilities.cpp
new file mode 100644
index 0000000000..6a1c73ed37
--- /dev/null
+++ b/security/sandbox/linux/launch/LinuxCapabilities.cpp
@@ -0,0 +1,26 @@
+/* -*- 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 "LinuxCapabilities.h"
+
+#include <unistd.h>
+#include <sys/syscall.h>
+
+namespace mozilla {
+
+bool LinuxCapabilities::GetCurrent() {
+ __user_cap_header_struct header = {_LINUX_CAPABILITY_VERSION_3, 0};
+ return syscall(__NR_capget, &header, &mBits) == 0 &&
+ header.version == _LINUX_CAPABILITY_VERSION_3;
+}
+
+bool LinuxCapabilities::SetCurrentRaw() const {
+ __user_cap_header_struct header = {_LINUX_CAPABILITY_VERSION_3, 0};
+ return syscall(__NR_capset, &header, &mBits) == 0 &&
+ header.version == _LINUX_CAPABILITY_VERSION_3;
+}
+
+} // namespace mozilla
diff --git a/security/sandbox/linux/launch/LinuxCapabilities.h b/security/sandbox/linux/launch/LinuxCapabilities.h
new file mode 100644
index 0000000000..c8db719811
--- /dev/null
+++ b/security/sandbox/linux/launch/LinuxCapabilities.h
@@ -0,0 +1,122 @@
+/* -*- 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 mozilla_LinuxCapabilities_h
+#define mozilla_LinuxCapabilities_h
+
+#include <linux/capability.h>
+#include <stdint.h>
+
+#include "mozilla/Assertions.h"
+#include "mozilla/Attributes.h"
+#include "mozilla/PodOperations.h"
+
+// This class is a relatively simple interface to manipulating the
+// capabilities of a Linux process/thread; see the capabilities(7) man
+// page for background information.
+
+// Unfortunately, Android's kernel headers omit some definitions
+// needed for the low-level capability interface. They're part of the
+// stable syscall ABI, so it's safe to include them here.
+#ifndef _LINUX_CAPABILITY_VERSION_3
+# define _LINUX_CAPABILITY_VERSION_3 0x20080522
+# define _LINUX_CAPABILITY_U32S_3 2
+#endif
+#ifndef CAP_TO_INDEX
+# define CAP_TO_INDEX(x) ((x) >> 5)
+# define CAP_TO_MASK(x) (1 << ((x) & 31))
+#endif
+
+namespace mozilla {
+
+class LinuxCapabilities final {
+ public:
+ // A class to represent a bit within the capability sets as an lvalue.
+ class BitRef {
+ __u32& mWord;
+ __u32 mMask;
+ friend class LinuxCapabilities;
+ BitRef(__u32& aWord, uint32_t aMask) : mWord(aWord), mMask(aMask) {}
+ BitRef(const BitRef& aBit) = default;
+
+ public:
+ MOZ_IMPLICIT operator bool() const { return mWord & mMask; }
+ BitRef& operator=(bool aSetTo) {
+ if (aSetTo) {
+ mWord |= mMask;
+ } else {
+ mWord &= mMask;
+ }
+ return *this;
+ }
+ };
+
+ // The default value is the empty set.
+ LinuxCapabilities() { PodArrayZero(mBits); }
+
+ // Get the current thread's capability sets and assign them to this
+ // object. Returns whether it succeeded and sets errno on failure.
+ // Shouldn't fail unless the kernel is very old.
+ bool GetCurrent();
+
+ // Try to set the current thread's capability sets to those
+ // specified in this object. Returns whether it succeeded and sets
+ // errno on failure.
+ bool SetCurrentRaw() const;
+
+ // The capability model requires that the permitted set always be a
+ // superset of the effective and inheritable sets. This method
+ // expands the permitted set as needed and then sets the current
+ // thread's capabilities, as described above.
+ bool SetCurrent() {
+ Normalize();
+ return SetCurrentRaw();
+ }
+
+ void Normalize() {
+ for (size_t i = 0; i < _LINUX_CAPABILITY_U32S_3; ++i) {
+ mBits[i].permitted |= mBits[i].effective | mBits[i].inheritable;
+ }
+ }
+
+ bool AnyEffective() const {
+ for (size_t i = 0; i < _LINUX_CAPABILITY_U32S_3; ++i) {
+ if (mBits[i].effective != 0) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ // These three methods expose individual bits in the three
+ // capability sets as objects that can be used as bool lvalues.
+ // The argument is the capability number, as defined in
+ // the <linux/capability.h> header.
+ BitRef Effective(unsigned aCap) {
+ return GenericBitRef(&__user_cap_data_struct::effective, aCap);
+ }
+
+ BitRef Permitted(unsigned aCap) {
+ return GenericBitRef(&__user_cap_data_struct::permitted, aCap);
+ }
+
+ BitRef Inheritable(unsigned aCap) {
+ return GenericBitRef(&__user_cap_data_struct::inheritable, aCap);
+ }
+
+ private:
+ __user_cap_data_struct mBits[_LINUX_CAPABILITY_U32S_3];
+
+ BitRef GenericBitRef(__u32 __user_cap_data_struct::*aField, unsigned aCap) {
+ // Please don't pass untrusted data as the capability number.
+ MOZ_ASSERT(CAP_TO_INDEX(aCap) < _LINUX_CAPABILITY_U32S_3);
+ return BitRef(mBits[CAP_TO_INDEX(aCap)].*aField, CAP_TO_MASK(aCap));
+ }
+};
+
+} // namespace mozilla
+
+#endif // mozilla_LinuxCapabilities_h
diff --git a/security/sandbox/linux/launch/SandboxLaunch.cpp b/security/sandbox/linux/launch/SandboxLaunch.cpp
new file mode 100644
index 0000000000..bec94f3c4c
--- /dev/null
+++ b/security/sandbox/linux/launch/SandboxLaunch.cpp
@@ -0,0 +1,675 @@
+/* -*- 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 "SandboxLaunch.h"
+
+#include <fcntl.h>
+#include <sched.h>
+#include <setjmp.h>
+#include <signal.h>
+#include <sys/prctl.h>
+#include <sys/socket.h>
+#include <sys/syscall.h>
+#include <unistd.h>
+
+#include <utility>
+
+#include "LinuxCapabilities.h"
+#include "LinuxSched.h"
+#include "SandboxChrootProto.h"
+#include "SandboxInfo.h"
+#include "SandboxLogging.h"
+#include "base/eintr_wrapper.h"
+#include "base/strings/safe_sprintf.h"
+#include "mozilla/Array.h"
+#include "mozilla/ArrayUtils.h"
+#include "mozilla/Assertions.h"
+#include "mozilla/Attributes.h"
+#include "mozilla/Preferences.h"
+#include "mozilla/SandboxReporter.h"
+#include "mozilla/SandboxSettings.h"
+#include "mozilla/Components.h"
+#include "mozilla/StaticPrefs_media.h"
+#include "mozilla/StaticPrefs_security.h"
+#include "mozilla/Unused.h"
+#include "mozilla/ipc/UtilityProcessSandboxing.h"
+#include "nsCOMPtr.h"
+#include "nsDebug.h"
+#include "nsIGfxInfo.h"
+#include "nsString.h"
+#include "nsThreadUtils.h"
+#include "prenv.h"
+#include "sandbox/linux/system_headers/linux_syscalls.h"
+
+#ifdef MOZ_X11
+# ifndef MOZ_WIDGET_GTK
+# error "Unknown toolkit"
+# endif
+# include "mozilla/WidgetUtilsGtk.h"
+# include <gdk/gdk.h>
+# include <gdk/gdkx.h>
+# include "X11UndefineNone.h"
+# include "gfxPlatform.h"
+#endif
+
+namespace mozilla {
+
+// Returns true if graphics will work from a content process
+// started in a new network namespace. Specifically, named
+// Unix-domain sockets will work, but TCP/IP will not, even if it's a
+// connection to localhost: the child process has its own private
+// loopback interface.
+//
+// (Longer-term we intend to either proxy or remove X11 access from
+// content processes, at which point this will stop being an issue.)
+static bool IsGraphicsOkWithoutNetwork() {
+ // For X11, check whether the parent's connection is a Unix-domain
+ // socket. This is done instead of trying to parse the display name
+ // because an empty hostname (e.g., ":0") will fall back to TCP in
+ // case of failure to connect using Unix-domain sockets.
+#ifdef MOZ_X11
+ // First, ensure that the parent process's graphics are initialized.
+ DebugOnly<gfxPlatform*> gfxPlatform = gfxPlatform::GetPlatform();
+
+ const auto display = gdk_display_get_default();
+ if (!display) {
+ // In this case, the browser is headless, but WebGL could still
+ // try to use X11. However, WebGL isn't supported with remote
+ // X11, and in any case these connections are made after sandbox
+ // startup (lazily when WebGL is used), so they aren't being done
+ // directly by the process anyway. (For local X11, they're
+ // brokered.)
+ MOZ_ASSERT(gfxPlatform->IsHeadless());
+ return true;
+ }
+ if (mozilla::widget::GdkIsX11Display(display)) {
+ const int xSocketFd = ConnectionNumber(GDK_DISPLAY_XDISPLAY(display));
+ if (NS_WARN_IF(xSocketFd < 0)) {
+ return false;
+ }
+
+ int domain;
+ socklen_t optlen = static_cast<socklen_t>(sizeof(domain));
+ int rv = getsockopt(xSocketFd, SOL_SOCKET, SO_DOMAIN, &domain, &optlen);
+ if (NS_WARN_IF(rv != 0)) {
+ return false;
+ }
+ MOZ_RELEASE_ASSERT(static_cast<size_t>(optlen) == sizeof(domain));
+ if (domain != AF_LOCAL) {
+ return false;
+ }
+ // There's one more complication: Xorg listens on named sockets
+ // (actual filesystem nodes) as well as abstract addresses (opaque
+ // octet strings scoped to the network namespace; this is a Linux
+ // extension).
+ //
+ // Inside a container environment (e.g., when running as a Snap
+ // package), it's possible that only the abstract addresses are
+ // accessible. In that case, the display must be considered
+ // remote. See also bug 1450740.
+ //
+ // Unfortunately, the Xorg client libraries prefer the abstract
+ // addresses, so this isn't directly detectable by inspecting the
+ // parent process's socket. Instead, parse the DISPLAY env var
+ // (which was updated if necessary in nsAppRunner.cpp) to get the
+ // display number and construct the socket path, falling back to
+ // testing the directory in case that doesn't work. (See bug
+ // 1565972 and bug 1559368 for cases where we need to test the
+ // specific socket.)
+ const char* const displayStr = PR_GetEnv("DISPLAY");
+ nsAutoCString socketPath("/tmp/.X11-unix");
+ int accessFlags = X_OK;
+ int displayNum;
+ // sscanf ignores trailing text, so display names with a screen
+ // number (e.g., ":0.2") will parse correctly.
+ if (displayStr && (sscanf(displayStr, ":%d", &displayNum) == 1 ||
+ sscanf(displayStr, "unix:%d", &displayNum) == 1)) {
+ socketPath.AppendPrintf("/X%d", displayNum);
+ accessFlags = R_OK | W_OK;
+ }
+ if (access(socketPath.get(), accessFlags) != 0) {
+ SANDBOX_LOG_ERRNO(
+ "%s is inaccessible; can't isolate network namespace in"
+ " content processes",
+ socketPath.get());
+ return false;
+ }
+ }
+#endif
+
+ // Assume that other backends (e.g., Wayland) will not use the
+ // network namespace.
+ return true;
+}
+
+bool HasAtiDrivers() {
+ nsCOMPtr<nsIGfxInfo> gfxInfo = components::GfxInfo::Service();
+ nsAutoString vendorID;
+ static const Array<nsresult (nsIGfxInfo::*)(nsAString&), 2> kMethods = {
+ &nsIGfxInfo::GetAdapterVendorID,
+ &nsIGfxInfo::GetAdapterVendorID2,
+ };
+ for (const auto method : kMethods) {
+ if (NS_SUCCEEDED((gfxInfo->*method)(vendorID))) {
+ // This test is based on telemetry data. The proprietary ATI
+ // drivers seem to use this vendor string, including for some
+ // newer devices that have AMD branding in the device name, such
+ // as those using AMDGPU-PRO drivers.
+ // The open-source drivers integrated into Mesa appear to use
+ // the vendor ID "X.Org" instead.
+ if (vendorID.EqualsLiteral("ATI Technologies Inc.")) {
+ return true;
+ }
+ }
+ }
+
+ return false;
+}
+
+// Content processes may need direct access to SysV IPC in certain
+// uncommon use cases.
+static bool ContentNeedsSysVIPC() {
+ // The ALSA dmix plugin uses SysV semaphores and shared memory to
+ // coordinate software mixing.
+#ifdef MOZ_ALSA
+ if (!StaticPrefs::media_cubeb_sandbox()) {
+ return true;
+ }
+#endif
+
+ if (!StaticPrefs::security_sandbox_content_headless_AtStartup()) {
+ // Bug 1438391: VirtualGL uses SysV shm for images and configuration.
+ if (PR_GetEnv("VGL_ISACTIVE") != nullptr) {
+ return true;
+ }
+
+ // The fglrx (ATI Catalyst) GPU drivers use SysV IPC.
+ if (HasAtiDrivers()) {
+ return true;
+ }
+ }
+
+ return false;
+}
+
+static void PreloadSandboxLib(base::environment_map* aEnv) {
+ // Preload libmozsandbox.so so that sandbox-related interpositions
+ // can be defined there instead of in the executable.
+ // (This could be made conditional on intent to use sandboxing, but
+ // it's harmless for non-sandboxed processes.)
+ nsAutoCString preload;
+ // Prepend this, because people can and do preload libpthread.
+ // (See bug 1222500.)
+ preload.AssignLiteral("libmozsandbox.so");
+ if (const char* oldPreload = PR_GetEnv("LD_PRELOAD")) {
+ // Doesn't matter if oldPreload is ""; extra separators are ignored.
+ preload.Append(' ');
+ preload.Append(oldPreload);
+ (*aEnv)["MOZ_ORIG_LD_PRELOAD"] = oldPreload;
+ }
+ MOZ_ASSERT(aEnv->count("LD_PRELOAD") == 0);
+ (*aEnv)["LD_PRELOAD"] = preload.get();
+}
+
+static void AttachSandboxReporter(base::file_handle_mapping_vector* aFdMap) {
+ int srcFd, dstFd;
+ SandboxReporter::Singleton()->GetClientFileDescriptorMapping(&srcFd, &dstFd);
+ aFdMap->push_back({srcFd, dstFd});
+}
+
+static int GetEffectiveSandboxLevel(GeckoProcessType aType,
+ ipc::SandboxingKind aKind) {
+ auto info = SandboxInfo::Get();
+ switch (aType) {
+#ifdef MOZ_ENABLE_FORKSERVER
+ // With this env MOZ_SANDBOXED will be set, and mozsandbox will
+ // be preloaded for the fork server. Sandboxed child processes
+ // rely on wrappers defined by mozsandbox to work properly.
+ case GeckoProcessType_ForkServer:
+ return 1;
+ break;
+#endif
+ case GeckoProcessType_Content:
+ // GetEffectiveContentSandboxLevel is main-thread-only due to prefs.
+ MOZ_ASSERT(NS_IsMainThread());
+ if (info.Test(SandboxInfo::kEnabledForContent)) {
+ return GetEffectiveContentSandboxLevel();
+ }
+ return 0;
+ case GeckoProcessType_GMPlugin:
+ if (info.Test(SandboxInfo::kEnabledForMedia)) {
+ return 1;
+ }
+ return 0;
+ case GeckoProcessType_RDD:
+ return PR_GetEnv("MOZ_DISABLE_RDD_SANDBOX") == nullptr ? 1 : 0;
+ case GeckoProcessType_Socket:
+ // GetEffectiveSocketProcessSandboxLevel is main-thread-only due to prefs.
+ MOZ_ASSERT(NS_IsMainThread());
+ return GetEffectiveSocketProcessSandboxLevel();
+ case GeckoProcessType_Utility:
+ return IsUtilitySandboxEnabled(aKind);
+ default:
+ return 0;
+ }
+}
+
+// static
+void SandboxLaunch::Configure(GeckoProcessType aType, SandboxingKind aKind,
+ LaunchOptions* aOptions) {
+ MOZ_ASSERT(aOptions->fork_flags == 0 && !aOptions->sandbox_chroot);
+ auto info = SandboxInfo::Get();
+
+ // We won't try any kind of sandboxing without seccomp-bpf.
+ if (!info.Test(SandboxInfo::kHasSeccompBPF)) {
+ return;
+ }
+
+ // Check prefs (and env vars) controlling sandbox use.
+ int level = GetEffectiveSandboxLevel(aType, aKind);
+ if (level == 0) {
+ return;
+ }
+
+ // At this point, we know we'll be using sandboxing; generic
+ // sandboxing support goes here. The MOZ_SANDBOXED env var tells
+ // the child process whether this is the case.
+ aOptions->env_map["MOZ_SANDBOXED"] = "1";
+ PreloadSandboxLib(&aOptions->env_map);
+ AttachSandboxReporter(&aOptions->fds_to_remap);
+
+ bool canChroot = false;
+ int flags = 0;
+
+ if (aType == GeckoProcessType_Content && level >= 1) {
+ static const bool needSysV = ContentNeedsSysVIPC();
+ if (needSysV) {
+ // Tell the child process so it can adjust its seccomp-bpf
+ // policy.
+ aOptions->env_map["MOZ_SANDBOX_ALLOW_SYSV"] = "1";
+ } else {
+ flags |= CLONE_NEWIPC;
+ }
+
+ if (StaticPrefs::security_sandbox_content_headless_AtStartup()) {
+ aOptions->env_map["MOZ_HEADLESS"] = "1";
+ }
+ }
+
+ // Anything below this requires unprivileged user namespaces.
+ if (!info.Test(SandboxInfo::kHasUserNamespaces)) {
+ return;
+ }
+
+ switch (aType) {
+ case GeckoProcessType_Socket:
+ if (level >= 1) {
+ canChroot = true;
+ flags |= CLONE_NEWIPC;
+ }
+ break;
+ case GeckoProcessType_GMPlugin:
+ case GeckoProcessType_RDD:
+ if (level >= 1) {
+ canChroot = true;
+ // Can't use CLONE_NEWIPC because of intel-media-driver.
+ flags |= CLONE_NEWNET;
+ }
+ break;
+ case GeckoProcessType_Content:
+ if (level >= 4) {
+ canChroot = true;
+
+ // Unshare network namespace if allowed by graphics; see
+ // function definition above for details. (The display
+ // local-ness is cached because it won't change.)
+ static const bool canCloneNet =
+ StaticPrefs::security_sandbox_content_headless_AtStartup() ||
+ (IsGraphicsOkWithoutNetwork() &&
+ !PR_GetEnv("RENDERDOC_CAPTUREOPTS"));
+
+ if (canCloneNet) {
+ flags |= CLONE_NEWNET;
+ }
+ }
+ // Hidden pref to allow testing user namespaces separately, even
+ // if there's nothing that would require them.
+ if (Preferences::GetBool("security.sandbox.content.force-namespace",
+ false)) {
+ flags |= CLONE_NEWUSER;
+ }
+ break;
+ default:
+ // Nothing yet.
+ break;
+ }
+
+ if (canChroot || flags != 0) {
+ flags |= CLONE_NEWUSER;
+ }
+
+ aOptions->env_map[kSandboxChrootEnvFlag] = std::to_string(canChroot ? 1 : 0);
+
+ aOptions->sandbox_chroot = canChroot;
+ aOptions->fork_flags = flags;
+}
+
+SandboxLaunch::SandboxLaunch()
+ : mFlags(0), mChrootServer(-1), mChrootClient(-1) {}
+
+SandboxLaunch::~SandboxLaunch() {
+ if (mChrootClient >= 0) {
+ close(mChrootClient);
+ }
+ if (mChrootServer >= 0) {
+ close(mChrootServer);
+ }
+}
+
+bool SandboxLaunch::Prepare(LaunchOptions* aOptions) {
+ MOZ_ASSERT(mChrootClient < 0 && mChrootServer < 0);
+
+ mFlags = aOptions->fork_flags;
+
+ // Create the socket for communication between the child process and
+ // the chroot helper process. The client end is passed to the child
+ // via `fds_to_remap` and the server end is inherited and used in
+ // `StartChrootServer`.
+ if (aOptions->sandbox_chroot) {
+ int fds[2];
+ int rv = socketpair(AF_UNIX, SOCK_STREAM | SOCK_CLOEXEC, 0, fds);
+ if (rv != 0) {
+ SANDBOX_LOG_ERRNO("socketpair");
+ return false;
+ }
+ mChrootClient = fds[0];
+ mChrootServer = fds[1];
+
+ aOptions->fds_to_remap.push_back({mChrootClient, kSandboxChrootClientFd});
+ }
+
+ return true;
+}
+
+static void BlockAllSignals(sigset_t* aOldSigs) {
+ sigset_t allSigs;
+ int rv = sigfillset(&allSigs);
+ MOZ_RELEASE_ASSERT(rv == 0);
+ rv = pthread_sigmask(SIG_BLOCK, &allSigs, aOldSigs);
+ if (rv != 0) {
+ SANDBOX_LOG_WITH_ERROR(rv, "pthread_sigmask (block all)");
+ MOZ_CRASH("pthread_sigmask");
+ }
+}
+
+static void RestoreSignals(const sigset_t* aOldSigs) {
+ // Assuming that pthread_sigmask is a thin layer over rt_sigprocmask
+ // and doesn't try to touch TLS, which may be in an "interesting"
+ // state right now:
+ int rv = pthread_sigmask(SIG_SETMASK, aOldSigs, nullptr);
+ if (rv != 0) {
+ SANDBOX_LOG_WITH_ERROR(rv, "pthread_sigmask (restore)");
+ MOZ_CRASH("pthread_sigmask");
+ }
+}
+
+static bool IsSignalIgnored(int aSig) {
+ struct sigaction sa {};
+
+ if (sigaction(aSig, nullptr, &sa) != 0) {
+ if (errno != EINVAL) {
+ SANDBOX_LOG_ERRNO("sigaction(%d)", aSig);
+ }
+ return false;
+ }
+ return sa.sa_handler == SIG_IGN;
+}
+
+static void ResetSignalHandlers() {
+ for (int signum = 1; signum <= SIGRTMAX; ++signum) {
+ if (IsSignalIgnored(signum)) {
+ continue;
+ }
+ if (signal(signum, SIG_DFL) == SIG_ERR) {
+ MOZ_DIAGNOSTIC_ASSERT(errno == EINVAL);
+ }
+ }
+}
+
+namespace {
+
+// The libc clone() routine insists on calling a provided function on
+// a new stack, even if the address space isn't shared and it would be
+// safe to expose the underlying system call's fork()-like behavior.
+// So, we work around this by longjmp()ing back onto the original stack;
+// this technique is also used by Chromium.
+//
+// In theory, the clone syscall could be used directly if we ensure
+// that functions like raise() are never used in the child, including
+// by inherited signal handlers, but the longjmp approach isn't much
+// extra code and avoids a class of potential bugs.
+static int CloneCallee(void* aPtr) {
+ auto ctxPtr = reinterpret_cast<jmp_buf*>(aPtr);
+ longjmp(*ctxPtr, 1);
+ MOZ_CRASH("unreachable");
+ return 1;
+}
+
+// According to the Chromium developers, builds with FORTIFY_SOURCE
+// require that longjump move the stack pointer towards the root
+// function of the call stack. Therefore, we must ensure that the
+// clone callee stack is leafward of the stack pointer captured in
+// setjmp() below by using this no-inline helper function.
+//
+// ASan apparently also causes problems, by the combination of
+// allocating the large stack-allocated buffer outside of the actual
+// stack and then assuming that longjmp is used only to unwind a
+// stack, not switch stacks.
+//
+// Valgrind would disapprove of using clone() without CLONE_VM;
+// Chromium uses the raw syscall as a workaround in that case, but
+// we don't currently support sandboxing under valgrind.
+MOZ_NEVER_INLINE MOZ_ASAN_IGNORE static pid_t DoClone(int aFlags,
+ jmp_buf* aCtx) {
+ static constexpr size_t kStackAlignment = 16;
+ uint8_t miniStack[4096] __attribute__((aligned(kStackAlignment)));
+#ifdef __hppa__
+ void* stackPtr = miniStack;
+#else
+ void* stackPtr = ArrayEnd(miniStack);
+#endif
+ return clone(CloneCallee, stackPtr, aFlags, aCtx);
+}
+
+} // namespace
+
+// Similar to fork(), but allows passing flags to clone() and does not
+// run pthread_atfork hooks.
+static pid_t ForkWithFlags(int aFlags) {
+ // Don't allow flags that would share the address space, or
+ // require clone() arguments we're not passing:
+ static const int kBadFlags = CLONE_VM | CLONE_VFORK | CLONE_SETTLS |
+ CLONE_PARENT_SETTID | CLONE_CHILD_SETTID |
+ CLONE_CHILD_CLEARTID;
+ MOZ_RELEASE_ASSERT((aFlags & kBadFlags) == 0);
+
+ // Block signals due to small stack in DoClone.
+ sigset_t oldSigs;
+ BlockAllSignals(&oldSigs);
+
+ int ret = 0;
+ jmp_buf ctx;
+ if (setjmp(ctx) == 0) {
+ // In the parent and just called setjmp:
+ ret = DoClone(aFlags | SIGCHLD, &ctx);
+ }
+ RestoreSignals(&oldSigs);
+ // In the child and have longjmp'ed:
+ return ret;
+}
+
+static bool WriteStringToFile(const char* aPath, const char* aStr,
+ const size_t aLen) {
+ int fd = open(aPath, O_WRONLY);
+ if (fd < 0) {
+ return false;
+ }
+ ssize_t written = write(fd, aStr, aLen);
+ if (close(fd) != 0 || written != ssize_t(aLen)) {
+ return false;
+ }
+ return true;
+}
+
+// This function sets up uid/gid mappings that preserve the
+// process's previous ids. Mapping the uid/gid to something is
+// necessary in order to nest user namespaces (not currently being
+// used, but could be useful), and leaving the ids unchanged is
+// likely to minimize unexpected side-effects.
+static void ConfigureUserNamespace(uid_t uid, gid_t gid) {
+ using base::strings::SafeSPrintf;
+ char buf[sizeof("18446744073709551615 18446744073709551615 1")];
+ size_t len;
+
+ len = static_cast<size_t>(SafeSPrintf(buf, "%d %d 1", uid, uid));
+ MOZ_RELEASE_ASSERT(len < sizeof(buf));
+ if (!WriteStringToFile("/proc/self/uid_map", buf, len)) {
+ MOZ_CRASH("Failed to write /proc/self/uid_map");
+ }
+
+ // In recent kernels (3.19, 3.18.2, 3.17.8), for security reasons,
+ // establishing gid mappings will fail unless the process first
+ // revokes its ability to call setgroups() by using a /proc node
+ // added in the same set of patches.
+ Unused << WriteStringToFile("/proc/self/setgroups", "deny", 4);
+
+ len = static_cast<size_t>(SafeSPrintf(buf, "%d %d 1", gid, gid));
+ MOZ_RELEASE_ASSERT(len < sizeof(buf));
+ if (!WriteStringToFile("/proc/self/gid_map", buf, len)) {
+ MOZ_CRASH("Failed to write /proc/self/gid_map");
+ }
+}
+
+static void DropAllCaps() {
+ if (!LinuxCapabilities().SetCurrent()) {
+ SANDBOX_LOG_ERRNO("capset (drop all)");
+ }
+}
+
+pid_t SandboxLaunch::Fork() {
+ if (mFlags == 0) {
+ MOZ_ASSERT(mChrootServer < 0);
+ return fork();
+ }
+
+ uid_t uid = getuid();
+ gid_t gid = getgid();
+
+ // Block signals so that the handlers can be safely reset in the
+ // child process without races, and so that repeated SIGPROF from
+ // the profiler won't prevent clone() from making progress. (The
+ // profiler uses pthread_atfork to do that, but ForkWithFlags
+ // can't run atfork hooks.)
+ sigset_t oldSigs;
+ BlockAllSignals(&oldSigs);
+ pid_t pid = ForkWithFlags(mFlags);
+ if (pid != 0) {
+ RestoreSignals(&oldSigs);
+ return pid;
+ }
+
+ // WARNING: all code from this point on (and in StartChrootServer)
+ // must be async signal safe. In particular, it cannot do anything
+ // that could allocate heap memory or use mutexes.
+ prctl(PR_SET_NAME, "Sandbox Forked");
+
+ // Clear signal handlers in the child, under the assumption that any
+ // actions they would take (running the crash reporter, manipulating
+ // the Gecko profile, etc.) wouldn't work correctly in the child.
+ ResetSignalHandlers();
+ RestoreSignals(&oldSigs);
+ ConfigureUserNamespace(uid, gid);
+
+ if (mChrootServer >= 0) {
+ StartChrootServer();
+ // Don't close the client fd when this object is destroyed. At
+ // this point we're in the child process proper, so it's "owned"
+ // by the FileDescriptorShuffle / CloseSuperfluous code (i.e.,
+ // that's what will consume it and close it).
+ mChrootClient = -1;
+ }
+
+ // execve() will drop capabilities, but the fork server case doesn't
+ // exec so we need to do this directly. (Also, it's a good idea to
+ // follow the principle of least privilege even when not strictly
+ // necessary.)
+ //
+ // Note that, while capabilities within an unprivileged user
+ // namespace are constrained in theory, in practice they expose a
+ // lot of attack surface and there have been exploitable kernel bugs
+ // related to that in the past, so we really want to drop them
+ // before doing anything that needs sandboxing.
+ DropAllCaps();
+ return 0;
+}
+
+void SandboxLaunch::StartChrootServer() {
+ // Run the rest of this function in a separate process that can
+ // chroot() on behalf of this process after it's sandboxed.
+ pid_t pid = ForkWithFlags(CLONE_FS);
+ if (pid < 0) {
+ MOZ_CRASH("failed to clone chroot helper process");
+ }
+ if (pid > 0) {
+ return;
+ }
+ prctl(PR_SET_NAME, "Chroot Helper");
+
+ LinuxCapabilities caps;
+ caps.Effective(CAP_SYS_CHROOT) = true;
+ if (!caps.SetCurrent()) {
+ SANDBOX_LOG_ERRNO("capset (chroot helper)");
+ MOZ_DIAGNOSTIC_ASSERT(false);
+ }
+
+ base::CloseSuperfluousFds(this, [](void* aCtx, int aFd) {
+ return aFd == static_cast<decltype(this)>(aCtx)->mChrootServer;
+ });
+
+ char msg;
+ ssize_t msgLen = HANDLE_EINTR(read(mChrootServer, &msg, 1));
+ if (msgLen == 0) {
+ // Process exited before chrooting (or chose not to chroot?).
+ _exit(0);
+ }
+ MOZ_RELEASE_ASSERT(msgLen == 1);
+ MOZ_RELEASE_ASSERT(msg == kSandboxChrootRequest);
+
+ // This chroots both processes to this process's procfs fdinfo
+ // directory, which becomes empty and unlinked when this process
+ // exits at the end of this function, and which is always
+ // unwriteable.
+ int rv = chroot("/proc/self/fdinfo");
+ MOZ_RELEASE_ASSERT(rv == 0);
+
+ // Drop CAP_SYS_CHROOT ASAP. This must happen before responding;
+ // the main child won't be able to waitpid(), so it could start
+ // handling hostile content before this process finishes exiting.
+ DropAllCaps();
+
+ // The working directory still grant access to the real filesystem;
+ // remove that. (Note: if the process can obtain directory fds, for
+ // example via SandboxBroker, it must be blocked from using fchdir.)
+ rv = chdir("/");
+ MOZ_RELEASE_ASSERT(rv == 0);
+
+ msg = kSandboxChrootResponse;
+ msgLen = HANDLE_EINTR(write(mChrootServer, &msg, 1));
+ MOZ_RELEASE_ASSERT(msgLen == 1);
+ _exit(0);
+}
+
+} // namespace mozilla
diff --git a/security/sandbox/linux/launch/SandboxLaunch.h b/security/sandbox/linux/launch/SandboxLaunch.h
new file mode 100644
index 0000000000..988709dcdb
--- /dev/null
+++ b/security/sandbox/linux/launch/SandboxLaunch.h
@@ -0,0 +1,71 @@
+/* -*- 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 mozilla_SandboxLaunch_h
+#define mozilla_SandboxLaunch_h
+
+#include "base/process_util.h"
+#include "mozilla/ipc/UtilityProcessSandboxing.h"
+#include "nsXULAppAPI.h"
+#include <vector>
+
+namespace mozilla {
+
+class SandboxLaunch final {
+ public:
+ SandboxLaunch();
+ ~SandboxLaunch();
+
+ SandboxLaunch(const SandboxLaunch&) = delete;
+ SandboxLaunch& operator=(const SandboxLaunch&) = delete;
+
+ using LaunchOptions = base::LaunchOptions;
+ using SandboxingKind = ipc::SandboxingKind;
+
+ // Decide what sandboxing features will be used for a process, and
+ // modify `*aOptions` accordingly. This does not allocate fds or
+ // other OS resources (other than memory for strings).
+ //
+ // This is meant to be called in the parent process (even if the
+ // fork server will be used), and if `aType` is Content then it must
+ // be called on the main thread in order to access prefs.
+ static void Configure(GeckoProcessType aType, SandboxingKind aKind,
+ LaunchOptions* aOptions);
+
+ // Finish setting up for process launch, based on the information
+ // from `Configure(...)`. Called in the process that will do the
+ // launch (fork server if applicable, otherwise parent), and before
+ // calling `FileDescriptorShuffle::Init`.
+ //
+ // This can allocate fds (owned by `*this`) and modify
+ // `aOptions->fds_to_remap`, but does not access the
+ // environment-related fields of `*aOptions`.
+ bool Prepare(LaunchOptions* aOptions);
+
+ // Launch the child process, similarly to `::fork()`; called after
+ // `Configure` and `Prepare`.
+ //
+ // If launch-time sandboxing features are used, `pthread_atfork`
+ // hooks are not currently supported in that case, and signal
+ // handlers are reset in the child process. If sandboxing is not
+ // used, this is equivalent to `::fork()`.
+ pid_t Fork();
+
+ private:
+ int mFlags;
+ int mChrootServer;
+ int mChrootClient;
+
+ void StartChrootServer();
+};
+
+// This doesn't really belong in this header but it's used in both
+// SandboxLaunch and SandboxBrokerPolicyFactory.
+bool HasAtiDrivers();
+
+} // namespace mozilla
+
+#endif // mozilla_SandboxLaunch_h
diff --git a/security/sandbox/linux/launch/moz.build b/security/sandbox/linux/launch/moz.build
new file mode 100644
index 0000000000..09f483f519
--- /dev/null
+++ b/security/sandbox/linux/launch/moz.build
@@ -0,0 +1,33 @@
+# -*- Mode: python; python-indent: 4; 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 += [
+ "SandboxLaunch.h",
+]
+
+UNIFIED_SOURCES += [
+ "LinuxCapabilities.cpp",
+ "SandboxLaunch.cpp",
+]
+
+include("/ipc/chromium/chromium-config.mozbuild")
+
+LOCAL_INCLUDES += [
+ # Need this for safe_sprintf.h used by SandboxLogging.h,
+ # but it has to be after ipc/chromium/src.
+ "/security/sandbox/chromium",
+ "/security/sandbox/linux",
+]
+
+USE_LIBS += [
+ "mozsandbox",
+]
+
+# For the X11 socket domain inspection in SandboxLaunch:
+if CONFIG["MOZ_WIDGET_TOOLKIT"] == "gtk":
+ CXXFLAGS += CONFIG["MOZ_GTK3_CFLAGS"]
+
+FINAL_LIBRARY = "xul"