summaryrefslogtreecommitdiffstats
path: root/security/sandbox/linux/SandboxChroot.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'security/sandbox/linux/SandboxChroot.cpp')
-rw-r--r--security/sandbox/linux/SandboxChroot.cpp212
1 files changed, 212 insertions, 0 deletions
diff --git a/security/sandbox/linux/SandboxChroot.cpp b/security/sandbox/linux/SandboxChroot.cpp
new file mode 100644
index 000000000..2091bfe05
--- /dev/null
+++ b/security/sandbox/linux/SandboxChroot.cpp
@@ -0,0 +1,212 @@
+/* -*- 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 "SandboxChroot.h"
+
+#include "SandboxLogging.h"
+#include "LinuxCapabilities.h"
+
+#include <errno.h>
+#include <fcntl.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include "base/posix/eintr_wrapper.h"
+#include "mozilla/Assertions.h"
+#include "mozilla/DebugOnly.h"
+#include "mozilla/NullPtr.h"
+
+#define MOZ_ALWAYS_ZERO(e) MOZ_ALWAYS_TRUE((e) == 0)
+
+namespace mozilla {
+
+SandboxChroot::SandboxChroot()
+{
+ pthread_mutexattr_t attr;
+ MOZ_ALWAYS_ZERO(pthread_mutexattr_init(&attr));
+ MOZ_ALWAYS_ZERO(pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_ERRORCHECK));
+ MOZ_ALWAYS_ZERO(pthread_mutex_init(&mMutex, &attr));
+ MOZ_ALWAYS_ZERO(pthread_cond_init(&mWakeup, nullptr));
+ mCommand = NO_THREAD;
+}
+
+SandboxChroot::~SandboxChroot()
+{
+ SendCommand(JUST_EXIT);
+ MOZ_ALWAYS_ZERO(pthread_mutex_destroy(&mMutex));
+ MOZ_ALWAYS_ZERO(pthread_cond_destroy(&mWakeup));
+}
+
+bool
+SandboxChroot::SendCommand(Command aComm)
+{
+ MOZ_ALWAYS_ZERO(pthread_mutex_lock(&mMutex));
+ if (mCommand == NO_THREAD) {
+ MOZ_RELEASE_ASSERT(aComm == JUST_EXIT);
+ MOZ_ALWAYS_ZERO(pthread_mutex_unlock(&mMutex));
+ return false;
+ } else {
+ MOZ_ASSERT(mCommand == NO_COMMAND);
+ mCommand = aComm;
+ MOZ_ALWAYS_ZERO(pthread_mutex_unlock(&mMutex));
+ MOZ_ALWAYS_ZERO(pthread_cond_signal(&mWakeup));
+ void *retval;
+ if (pthread_join(mThread, &retval) != 0 || retval != nullptr) {
+ MOZ_CRASH("Failed to stop privileged chroot thread");
+ }
+ MOZ_ASSERT(mCommand == NO_THREAD);
+ }
+ return true;
+}
+
+static void
+AlwaysClose(int fd)
+{
+ if (IGNORE_EINTR(close(fd)) != 0) {
+ SANDBOX_LOG_ERROR("close: %s", strerror(errno));
+ MOZ_CRASH("failed to close()");
+ }
+}
+
+static int
+OpenDeletedDirectory()
+{
+ // We don't need this directory to persist between invocations of
+ // the program (nor need it to be cleaned up if something goes wrong
+ // here, because mkdtemp will choose a fresh name), so /tmp as
+ // specified by FHS is adequate.
+ //
+ // However, this needs a filesystem where a deleted directory can
+ // still be used, and /tmp is sometimes not that; e.g., aufs(5),
+ // often used for containers, will cause the chroot() to fail with
+ // ESTALE (bug 1162965). So this uses /dev/shm if possible instead.
+ char tmpPath[] = "/tmp/mozsandbox.XXXXXX";
+ char shmPath[] = "/dev/shm/mozsandbox.XXXXXX";
+ char* path;
+ if (mkdtemp(shmPath)) {
+ path = shmPath;
+ } else if (mkdtemp(tmpPath)) {
+ path = tmpPath;
+ } else {
+ SANDBOX_LOG_ERROR("mkdtemp: %s", strerror(errno));
+ return -1;
+ }
+ int fd = HANDLE_EINTR(open(path, O_RDONLY | O_DIRECTORY));
+ if (fd < 0) {
+ SANDBOX_LOG_ERROR("open %s: %s", path, strerror(errno));
+ // Try to clean up. Shouldn't fail, but livable if it does.
+ DebugOnly<bool> ok = HANDLE_EINTR(rmdir(path)) == 0;
+ MOZ_ASSERT(ok);
+ return -1;
+ }
+ if (HANDLE_EINTR(rmdir(path)) != 0) {
+ SANDBOX_LOG_ERROR("rmdir %s: %s", path, strerror(errno));
+ AlwaysClose(fd);
+ return -1;
+ }
+ return fd;
+}
+
+bool
+SandboxChroot::Prepare()
+{
+ LinuxCapabilities caps;
+ if (!caps.GetCurrent() || !caps.Effective(CAP_SYS_CHROOT)) {
+ SANDBOX_LOG_ERROR("don't have permission to chroot");
+ return false;
+ }
+ mFd = OpenDeletedDirectory();
+ if (mFd < 0) {
+ SANDBOX_LOG_ERROR("failed to create empty directory for chroot");
+ return false;
+ }
+ MOZ_ALWAYS_ZERO(pthread_mutex_lock(&mMutex));
+ MOZ_ASSERT(mCommand == NO_THREAD);
+ if (pthread_create(&mThread, nullptr, StaticThreadMain, this) != 0) {
+ MOZ_ALWAYS_ZERO(pthread_mutex_unlock(&mMutex));
+ SANDBOX_LOG_ERROR("pthread_create: %s", strerror(errno));
+ return false;
+ }
+ while (mCommand != NO_COMMAND) {
+ MOZ_ASSERT(mCommand == NO_THREAD);
+ MOZ_ALWAYS_ZERO(pthread_cond_wait(&mWakeup, &mMutex));
+ }
+ MOZ_ALWAYS_ZERO(pthread_mutex_unlock(&mMutex));
+ return true;
+}
+
+void
+SandboxChroot::Invoke()
+{
+ MOZ_ALWAYS_TRUE(SendCommand(DO_CHROOT));
+}
+
+static bool
+ChrootToFileDesc(int fd)
+{
+ if (fchdir(fd) != 0) {
+ SANDBOX_LOG_ERROR("fchdir: %s", strerror(errno));
+ return false;
+ }
+ if (chroot(".") != 0) {
+ SANDBOX_LOG_ERROR("chroot: %s", strerror(errno));
+ return false;
+ }
+ return true;
+}
+
+/* static */ void*
+SandboxChroot::StaticThreadMain(void* aVoidPtr)
+{
+ static_cast<SandboxChroot*>(aVoidPtr)->ThreadMain();
+ return nullptr;
+}
+
+void
+SandboxChroot::ThreadMain()
+{
+ // First, drop everything that isn't CAP_SYS_CHROOT. (This code
+ // assumes that this thread already has effective CAP_SYS_CHROOT,
+ // because Prepare() checked for it before creating this thread.)
+ LinuxCapabilities caps;
+ caps.Effective(CAP_SYS_CHROOT) = true;
+ if (!caps.SetCurrent()) {
+ SANDBOX_LOG_ERROR("capset: %s", strerror(errno));
+ MOZ_CRASH("Can't limit chroot thread's capabilities");
+ }
+
+ MOZ_ALWAYS_ZERO(pthread_mutex_lock(&mMutex));
+ MOZ_ASSERT(mCommand == NO_THREAD);
+ mCommand = NO_COMMAND;
+ MOZ_ALWAYS_ZERO(pthread_cond_signal(&mWakeup));
+ while (mCommand == NO_COMMAND) {
+ MOZ_ALWAYS_ZERO(pthread_cond_wait(&mWakeup, &mMutex));
+ }
+ if (mCommand == DO_CHROOT) {
+ MOZ_ASSERT(mFd >= 0);
+ if (!ChrootToFileDesc(mFd)) {
+ MOZ_CRASH("Failed to chroot");
+ }
+ } else {
+ MOZ_ASSERT(mCommand == JUST_EXIT);
+ }
+ if (mFd >= 0) {
+ AlwaysClose(mFd);
+ mFd = -1;
+ }
+ mCommand = NO_THREAD;
+ MOZ_ALWAYS_ZERO(pthread_mutex_unlock(&mMutex));
+ // Drop the remaining capabilities; see note in SandboxChroot.h
+ // about the potential unreliability of pthread_join.
+ if (!LinuxCapabilities().SetCurrent()) {
+ MOZ_CRASH("can't drop capabilities");
+ }
+}
+
+} // namespace mozilla
+
+#undef MOZ_ALWAYS_ZERO