summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorrickyz <rickyz@chromium.org>2015-03-31 18:33:52 -0700
committerCommit bot <commit-bot@chromium.org>2015-04-01 01:34:51 +0000
commitce8969517c091252c32c9af5b3dcb57eb17d431f (patch)
tree0baa038e2847d3526d27c9a8f324732736850961
parentc7dd693bae60b61f26b3c2886318d11f06ce5809 (diff)
downloadchromium_src-ce8969517c091252c32c9af5b3dcb57eb17d431f.zip
chromium_src-ce8969517c091252c32c9af5b3dcb57eb17d431f.tar.gz
chromium_src-ce8969517c091252c32c9af5b3dcb57eb17d431f.tar.bz2
Add utility functions for forking new PID namespaces.
Also cleans up Credentials::SetCapabilities. This change was split out of https://codereview.chromium.org/868233011/, which was reverted due to a kernel bug. BUG=460972 Review URL: https://codereview.chromium.org/1040193004 Cr-Commit-Position: refs/heads/master@{#323164}
-rw-r--r--sandbox/linux/services/credentials.cc51
-rw-r--r--sandbox/linux/services/credentials.h26
-rw-r--r--sandbox/linux/services/credentials_unittest.cc21
-rw-r--r--sandbox/linux/services/namespace_sandbox.cc76
-rw-r--r--sandbox/linux/services/namespace_sandbox.h36
-rw-r--r--sandbox/linux/services/namespace_sandbox_unittest.cc94
6 files changed, 267 insertions, 37 deletions
diff --git a/sandbox/linux/services/credentials.cc b/sandbox/linux/services/credentials.cc
index 210a955..23e87ea 100644
--- a/sandbox/linux/services/credentials.cc
+++ b/sandbox/linux/services/credentials.cc
@@ -110,23 +110,24 @@ void CheckCloneNewUserErrno(int error) {
error == ENOSYS);
}
-// Converts a LinuxCapability to the corresponding Linux CAP_XXX value.
-int LinuxCapabilityToKernelValue(LinuxCapability cap) {
+// Converts a Capability to the corresponding Linux CAP_XXX value.
+int CapabilityToKernelValue(Credentials::Capability cap) {
switch (cap) {
- case LinuxCapability::kCapSysChroot:
+ case Credentials::Capability::SYS_CHROOT:
return CAP_SYS_CHROOT;
- case LinuxCapability::kCapSysAdmin:
+ case Credentials::Capability::SYS_ADMIN:
return CAP_SYS_ADMIN;
}
- LOG(FATAL) << "Invalid LinuxCapability: " << static_cast<int>(cap);
+ LOG(FATAL) << "Invalid Capability: " << static_cast<int>(cap);
return 0;
}
} // namespace.
+// static
bool Credentials::DropAllCapabilities(int proc_fd) {
- if (!SetCapabilities(proc_fd, std::vector<LinuxCapability>())) {
+ if (!SetCapabilities(proc_fd, std::vector<Capability>())) {
return false;
}
@@ -134,30 +135,28 @@ bool Credentials::DropAllCapabilities(int proc_fd) {
return true;
}
+// static
bool Credentials::DropAllCapabilities() {
base::ScopedFD proc_fd(ProcUtil::OpenProc());
return Credentials::DropAllCapabilities(proc_fd.get());
}
// static
-bool Credentials::SetCapabilities(int proc_fd,
- const std::vector<LinuxCapability>& caps) {
- DCHECK_LE(0, proc_fd);
-
-#if !defined(THREAD_SANITIZER)
- // With TSAN, accept to break the security model as it is a testing
- // configuration.
- CHECK(ThreadHelpers::IsSingleThreaded(proc_fd));
-#endif
+bool Credentials::DropAllCapabilitiesOnCurrentThread() {
+ return SetCapabilitiesOnCurrentThread(std::vector<Capability>());
+}
+// static
+bool Credentials::SetCapabilitiesOnCurrentThread(
+ const std::vector<Capability>& caps) {
struct cap_hdr hdr = {};
hdr.version = _LINUX_CAPABILITY_VERSION_3;
struct cap_data data[_LINUX_CAPABILITY_U32S_3] = {{}};
// Initially, cap has no capability flags set. Enable the effective and
// permitted flags only for the requested capabilities.
- for (const LinuxCapability cap : caps) {
- const int cap_num = LinuxCapabilityToKernelValue(cap);
+ for (const Capability cap : caps) {
+ const int cap_num = CapabilityToKernelValue(cap);
const size_t index = CAP_TO_INDEX(cap_num);
const uint32_t mask = CAP_TO_MASK(cap_num);
data[index].effective |= mask;
@@ -167,6 +166,20 @@ bool Credentials::SetCapabilities(int proc_fd,
return sys_capset(&hdr, data) == 0;
}
+// static
+bool Credentials::SetCapabilities(int proc_fd,
+ const std::vector<Capability>& caps) {
+ DCHECK_LE(0, proc_fd);
+
+#if !defined(THREAD_SANITIZER)
+ // With TSAN, accept to break the security model as it is a testing
+ // configuration.
+ CHECK(ThreadHelpers::IsSingleThreaded(proc_fd));
+#endif
+
+ return SetCapabilitiesOnCurrentThread(caps);
+}
+
bool Credentials::HasAnyCapability() {
struct cap_hdr hdr = {};
hdr.version = _LINUX_CAPABILITY_VERSION_3;
@@ -183,14 +196,14 @@ bool Credentials::HasAnyCapability() {
return false;
}
-bool Credentials::HasCapability(LinuxCapability cap) {
+bool Credentials::HasCapability(Capability cap) {
struct cap_hdr hdr = {};
hdr.version = _LINUX_CAPABILITY_VERSION_3;
struct cap_data data[_LINUX_CAPABILITY_U32S_3] = {{}};
PCHECK(sys_capget(&hdr, data) == 0);
- const int cap_num = LinuxCapabilityToKernelValue(cap);
+ const int cap_num = CapabilityToKernelValue(cap);
const size_t index = CAP_TO_INDEX(cap_num);
const uint32_t mask = CAP_TO_MASK(cap_num);
diff --git a/sandbox/linux/services/credentials.h b/sandbox/linux/services/credentials.h
index 83f2c70..4f16230 100644
--- a/sandbox/linux/services/credentials.h
+++ b/sandbox/linux/services/credentials.h
@@ -22,18 +22,18 @@
namespace sandbox {
-// For brevity, we only expose enums for the subset of capabilities we use.
-// This can be expanded as the need arises.
-enum class LinuxCapability {
- kCapSysChroot,
- kCapSysAdmin,
-};
-
// This class should be used to manipulate the current process' credentials.
// It is currently a stub used to manipulate POSIX.1e capabilities as
// implemented by the Linux kernel.
class SANDBOX_EXPORT Credentials {
public:
+ // For brevity, we only expose enums for the subset of capabilities we use.
+ // This can be expanded as the need arises.
+ enum class Capability {
+ SYS_CHROOT,
+ SYS_ADMIN,
+ };
+
// Drop all capabilities in the effective, inheritable and permitted sets for
// the current thread. For security reasons, since capabilities are
// per-thread, the caller is responsible for ensuring it is single-threaded
@@ -46,12 +46,20 @@ class SANDBOX_EXPORT Credentials {
// Sets the effective and permitted capability sets for the current thread to
// the list of capabiltiies in |caps|. All other capability flags are cleared.
static bool SetCapabilities(int proc_fd,
- const std::vector<LinuxCapability>& caps)
+ const std::vector<Capability>& caps)
WARN_UNUSED_RESULT;
+ // Versions of the above functions which do not check that the process is
+ // single-threaded. After calling these functions, capabilities of other
+ // threads will not be changed. This is dangerous, do not use unless you nkow
+ // what you are doing.
+ static bool DropAllCapabilitiesOnCurrentThread() WARN_UNUSED_RESULT;
+ static bool SetCapabilitiesOnCurrentThread(
+ const std::vector<Capability>& caps) WARN_UNUSED_RESULT;
+
// Returns true if the current thread has either the effective, permitted, or
// inheritable flag set for the given capability.
- static bool HasCapability(LinuxCapability cap);
+ static bool HasCapability(Capability cap);
// Return true iff there is any capability in any of the capabilities sets
// of the current thread.
diff --git a/sandbox/linux/services/credentials_unittest.cc b/sandbox/linux/services/credentials_unittest.cc
index db19f6f..6b93c86 100644
--- a/sandbox/linux/services/credentials_unittest.cc
+++ b/sandbox/linux/services/credentials_unittest.cc
@@ -177,16 +177,17 @@ SANDBOX_TEST(Credentials, SetCapabilities) {
base::ScopedFD proc_fd(ProcUtil::OpenProc());
- CHECK(Credentials::HasCapability(LinuxCapability::kCapSysAdmin));
- CHECK(Credentials::HasCapability(LinuxCapability::kCapSysChroot));
+ CHECK(Credentials::HasCapability(Credentials::Capability::SYS_ADMIN));
+ CHECK(Credentials::HasCapability(Credentials::Capability::SYS_CHROOT));
- const std::vector<LinuxCapability> caps = {LinuxCapability::kCapSysChroot};
+ std::vector<Credentials::Capability> caps;
+ caps.push_back(Credentials::Capability::SYS_CHROOT);
CHECK(Credentials::SetCapabilities(proc_fd.get(), caps));
- CHECK(!Credentials::HasCapability(LinuxCapability::kCapSysAdmin));
- CHECK(Credentials::HasCapability(LinuxCapability::kCapSysChroot));
+ CHECK(!Credentials::HasCapability(Credentials::Capability::SYS_ADMIN));
+ CHECK(Credentials::HasCapability(Credentials::Capability::SYS_CHROOT));
- const std::vector<LinuxCapability> no_caps;
+ const std::vector<Credentials::Capability> no_caps;
CHECK(Credentials::SetCapabilities(proc_fd.get(), no_caps));
CHECK(!Credentials::HasAnyCapability());
}
@@ -198,10 +199,11 @@ SANDBOX_TEST(Credentials, SetCapabilitiesAndChroot) {
base::ScopedFD proc_fd(ProcUtil::OpenProc());
- CHECK(Credentials::HasCapability(LinuxCapability::kCapSysChroot));
+ CHECK(Credentials::HasCapability(Credentials::Capability::SYS_CHROOT));
PCHECK(chroot("/") == 0);
- const std::vector<LinuxCapability> caps = {LinuxCapability::kCapSysChroot};
+ std::vector<Credentials::Capability> caps;
+ caps.push_back(Credentials::Capability::SYS_CHROOT);
CHECK(Credentials::SetCapabilities(proc_fd.get(), caps));
PCHECK(chroot("/") == 0);
@@ -216,7 +218,8 @@ SANDBOX_TEST(Credentials, SetCapabilitiesMatchesLibCap2) {
base::ScopedFD proc_fd(ProcUtil::OpenProc());
- const std::vector<LinuxCapability> caps = {LinuxCapability::kCapSysChroot};
+ std::vector<Credentials::Capability> caps;
+ caps.push_back(Credentials::Capability::SYS_CHROOT);
CHECK(Credentials::SetCapabilities(proc_fd.get(), caps));
ScopedCap actual_cap(cap_get_proc());
diff --git a/sandbox/linux/services/namespace_sandbox.cc b/sandbox/linux/services/namespace_sandbox.cc
index 0f371eb..dfee8b2 100644
--- a/sandbox/linux/services/namespace_sandbox.cc
+++ b/sandbox/linux/services/namespace_sandbox.cc
@@ -5,6 +5,7 @@
#include "sandbox/linux/services/namespace_sandbox.h"
#include <sched.h>
+#include <signal.h>
#include <stdlib.h>
#include <sys/types.h>
#include <unistd.h>
@@ -17,9 +18,11 @@
#include "base/environment.h"
#include "base/files/scoped_file.h"
#include "base/logging.h"
+#include "base/macros.h"
#include "base/posix/eintr_wrapper.h"
#include "base/process/launch.h"
#include "base/process/process.h"
+#include "sandbox/linux/services/credentials.h"
#include "sandbox/linux/services/namespace_utils.h"
namespace sandbox {
@@ -62,6 +65,20 @@ const char kSandboxUSERNSEnvironmentVarName[] = "SBX_USER_NS";
const char kSandboxPIDNSEnvironmentVarName[] = "SBX_PID_NS";
const char kSandboxNETNSEnvironmentVarName[] = "SBX_NET_NS";
+// Linux supports up to 64 signals. This should be updated if that ever changes.
+int g_signal_exit_codes[64];
+
+void TerminationSignalHandler(int sig) {
+ // Return a special exit code so that the process is detected as terminated by
+ // a signal.
+ const size_t sig_idx = static_cast<size_t>(sig);
+ if (sig_idx < arraysize(g_signal_exit_codes)) {
+ _exit(g_signal_exit_codes[sig_idx]);
+ }
+
+ _exit(NamespaceSandbox::kDefaultExitCode);
+}
+
} // namespace
// static
@@ -111,6 +128,65 @@ base::Process NamespaceSandbox::LaunchProcess(
}
// static
+pid_t NamespaceSandbox::ForkInNewPidNamespace(bool drop_capabilities_in_child) {
+ const pid_t pid =
+ base::ForkWithFlags(CLONE_NEWPID | SIGCHLD, nullptr, nullptr);
+ if (pid < 0) {
+ return pid;
+ }
+
+ if (pid == 0) {
+ DCHECK_EQ(1, getpid());
+ if (drop_capabilities_in_child) {
+ // Since we just forked, we are single-threaded, so this should be safe.
+ CHECK(Credentials::DropAllCapabilitiesOnCurrentThread());
+ }
+ return 0;
+ }
+
+ return pid;
+}
+
+// static
+void NamespaceSandbox::InstallDefaultTerminationSignalHandlers() {
+ static const int kDefaultTermSignals[] = {
+ SIGHUP, SIGINT, SIGABRT, SIGQUIT, SIGPIPE, SIGTERM, SIGUSR1, SIGUSR2,
+ };
+
+ for (const int sig : kDefaultTermSignals) {
+ InstallTerminationSignalHandler(sig, kDefaultExitCode);
+ }
+}
+
+// static
+bool NamespaceSandbox::InstallTerminationSignalHandler(
+ int sig,
+ int exit_code) {
+ struct sigaction old_action;
+ PCHECK(sigaction(sig, nullptr, &old_action) == 0);
+
+ if (old_action.sa_flags & SA_SIGINFO &&
+ old_action.sa_sigaction != nullptr) {
+ return false;
+ } else if (old_action.sa_handler != SIG_DFL) {
+ return false;
+ }
+
+ const size_t sig_idx = static_cast<size_t>(sig);
+ CHECK_LT(sig_idx, arraysize(g_signal_exit_codes));
+
+ DCHECK_GE(exit_code, 0);
+ DCHECK_LT(exit_code, 256);
+
+ g_signal_exit_codes[sig_idx] = exit_code;
+
+ struct sigaction action = {};
+ action.sa_handler = &TerminationSignalHandler;
+ PCHECK(sigaction(sig, &action, nullptr) == 0);
+ return true;
+}
+
+// static
bool NamespaceSandbox::InNewUserNamespace() {
return getenv(kSandboxUSERNSEnvironmentVarName) != nullptr;
}
diff --git a/sandbox/linux/services/namespace_sandbox.h b/sandbox/linux/services/namespace_sandbox.h
index b92f581..b2ddbcf 100644
--- a/sandbox/linux/services/namespace_sandbox.h
+++ b/sandbox/linux/services/namespace_sandbox.h
@@ -5,6 +5,8 @@
#ifndef SANDBOX_LINUX_SERVICES_NAMESPACE_SANDBOX_H_
#define SANDBOX_LINUX_SERVICES_NAMESPACE_SANDBOX_H_
+#include <sys/types.h>
+
#include <string>
#include <vector>
@@ -35,6 +37,8 @@ namespace sandbox {
// Credentials::DropAllCapabilities().
class SANDBOX_EXPORT NamespaceSandbox {
public:
+ static const int kDefaultExitCode = 1;
+
// Launch a new process inside its own user/PID/network namespaces (depending
// on kernel support). Requires at a minimum that user namespaces are
// supported (use Credentials::CanCreateProcessInNewUserNS to check this).
@@ -47,6 +51,38 @@ class SANDBOX_EXPORT NamespaceSandbox {
static base::Process LaunchProcess(const std::vector<std::string>& argv,
const base::LaunchOptions& options);
+ // Forks a process in its own PID namespace. The child process is the init
+ // process inside of the PID namespace, so if the child needs to fork further,
+ // it should call CreateInitProcessReaper, which turns the init process into a
+ // reaper process.
+ //
+ // Otherwise, the child should setup handlers for signals which should
+ // terminate the process using InstallDefaultTerminationSignalHandlers or
+ // InstallTerminationSignalHandler. This works around the fact that init
+ // processes ignore such signals unless they have an explicit handler set.
+ //
+ // This function requries CAP_SYS_ADMIN. If |drop_capabilities_in_child| is
+ // true, then capabilities are dropped in the child.
+ static pid_t ForkInNewPidNamespace(bool drop_capabilities_in_child);
+
+ // Installs a signal handler for:
+ //
+ // SIGHUP, SIGINT, SIGABRT, SIGQUIT, SIGPIPE, SIGTERM, SIGUSR1, SIGUSR2
+ //
+ // that exits with kDefaultExitCode. These are signals whose default action is
+ // to terminate the program (apart from SIGILL, SIGFPE, and SIGSEGV, which
+ // will still terminate the process if e.g. an illegal instruction is
+ // encountered, etc.).
+ //
+ // If any of these already had a signal handler installed, this function will
+ // not override them.
+ static void InstallDefaultTerminationSignalHandlers();
+
+ // Installs a signal handler for |sig| which exits with |exit_code|. If a
+ // signal handler was already present for |sig|, does nothing and returns
+ // false.
+ static bool InstallTerminationSignalHandler(int sig, int exit_code);
+
// Returns whether the namespace sandbox created a new user, PID, and network
// namespace. In particular, InNewUserNamespace should return true iff the
// process was started via this class.
diff --git a/sandbox/linux/services/namespace_sandbox_unittest.cc b/sandbox/linux/services/namespace_sandbox_unittest.cc
index 8bb0bff..547ef67 100644
--- a/sandbox/linux/services/namespace_sandbox_unittest.cc
+++ b/sandbox/linux/services/namespace_sandbox_unittest.cc
@@ -4,6 +4,7 @@
#include "sandbox/linux/services/namespace_sandbox.h"
+#include <signal.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>
@@ -118,6 +119,99 @@ TEST_F(NamespaceSandboxTest, NestedNamespaceSandbox) {
TestProc("NestedNamespaceSandbox");
}
+const int kNormalExitCode = 0;
+const int kSignalTerminationExitCode = 255;
+
+// Ensure that CHECK(false) is distinguishable from _exit(kNormalExitCode).
+// Allowing noise since CHECK(false) will write a stack trace to stderr.
+SANDBOX_TEST_ALLOW_NOISE(ForkInNewPidNamespace, CheckDoesNotReturnZero) {
+ if (!Credentials::CanCreateProcessInNewUserNS()) {
+ return;
+ }
+
+ CHECK(sandbox::Credentials::MoveToNewUserNS());
+ const pid_t pid = NamespaceSandbox::ForkInNewPidNamespace(
+ /*drop_capabilities_in_child=*/true);
+ CHECK_GE(pid, 0);
+
+ if (pid == 0) {
+ CHECK(false);
+ _exit(kNormalExitCode);
+ }
+
+ int status;
+ PCHECK(waitpid(pid, &status, 0) == pid);
+ if (WIFEXITED(status)) {
+ CHECK_NE(kNormalExitCode, WEXITSTATUS(status));
+ }
+}
+
+SANDBOX_TEST(ForkInNewPidNamespace, BasicUsage) {
+ if (!Credentials::CanCreateProcessInNewUserNS()) {
+ return;
+ }
+
+ CHECK(sandbox::Credentials::MoveToNewUserNS());
+ const pid_t pid = NamespaceSandbox::ForkInNewPidNamespace(
+ /*drop_capabilities_in_child=*/true);
+ CHECK_GE(pid, 0);
+
+ if (pid == 0) {
+ CHECK_EQ(1, getpid());
+ CHECK(!Credentials::HasAnyCapability());
+ _exit(kNormalExitCode);
+ }
+
+ int status;
+ PCHECK(waitpid(pid, &status, 0) == pid);
+ CHECK(WIFEXITED(status));
+ CHECK_EQ(kNormalExitCode, WEXITSTATUS(status));
+}
+
+SANDBOX_TEST(ForkInNewPidNamespace, ExitWithSignal) {
+ if (!Credentials::CanCreateProcessInNewUserNS()) {
+ return;
+ }
+
+ CHECK(sandbox::Credentials::MoveToNewUserNS());
+ const pid_t pid = NamespaceSandbox::ForkInNewPidNamespace(
+ /*drop_capabilities_in_child=*/true);
+ CHECK_GE(pid, 0);
+
+ if (pid == 0) {
+ CHECK_EQ(1, getpid());
+ CHECK(!Credentials::HasAnyCapability());
+ CHECK(NamespaceSandbox::InstallTerminationSignalHandler(
+ SIGTERM, kSignalTerminationExitCode));
+ while (true) {
+ raise(SIGTERM);
+ }
+ }
+
+ int status;
+ PCHECK(waitpid(pid, &status, 0) == pid);
+ CHECK(WIFEXITED(status));
+ CHECK_EQ(kSignalTerminationExitCode, WEXITSTATUS(status));
+}
+
+volatile sig_atomic_t signal_handler_called;
+void ExitSuccessfully(int sig) {
+ signal_handler_called = 1;
+}
+
+SANDBOX_TEST(InstallTerminationSignalHandler, DoesNotOverrideExistingHandlers) {
+ struct sigaction action = {};
+ action.sa_handler = &ExitSuccessfully;
+ PCHECK(sigaction(SIGUSR1, &action, nullptr) == 0);
+
+ NamespaceSandbox::InstallDefaultTerminationSignalHandlers();
+ CHECK(!NamespaceSandbox::InstallTerminationSignalHandler(
+ SIGUSR1, kSignalTerminationExitCode));
+
+ raise(SIGUSR1);
+ CHECK_EQ(1, signal_handler_called);
+}
+
} // namespace
} // namespace sandbox