summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorrickyz <rickyz@chromium.org>2015-03-27 15:31:15 -0700
committerCommit bot <commit-bot@chromium.org>2015-03-27 22:32:14 +0000
commit4557699fbb30704f76c68b906d2656d2e322572c (patch)
treeeff092838beee026cd01744ab663b647d9a16ebb
parentd5620b8ff8e4d3e7ba75d1c798072f7eb4f123ce (diff)
downloadchromium_src-4557699fbb30704f76c68b906d2656d2e322572c.zip
chromium_src-4557699fbb30704f76c68b906d2656d2e322572c.tar.gz
chromium_src-4557699fbb30704f76c68b906d2656d2e322572c.tar.bz2
Start all children in their own PID namespace.
BUG=460972 Review URL: https://codereview.chromium.org/868233011 Cr-Commit-Position: refs/heads/master@{#322660}
-rw-r--r--content/common/sandbox_linux/sandbox_linux.cc7
-rw-r--r--content/common/sandbox_linux/sandbox_linux.h8
-rw-r--r--content/zygote/zygote_linux.cc44
-rw-r--r--content/zygote/zygote_main_linux.cc16
-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
10 files changed, 336 insertions, 43 deletions
diff --git a/content/common/sandbox_linux/sandbox_linux.cc b/content/common/sandbox_linux/sandbox_linux.cc
index 5eee4e1..b2a7b3e 100644
--- a/content/common/sandbox_linux/sandbox_linux.cc
+++ b/content/common/sandbox_linux/sandbox_linux.cc
@@ -186,7 +186,12 @@ void LinuxSandbox::EngageNamespaceSandbox() {
// Note: this requires SealSandbox() to be called later in this process to be
// safe, as this class is keeping a file descriptor to /proc/.
CHECK(sandbox::Credentials::DropFileSystemAccess(proc_fd_));
- CHECK(sandbox::Credentials::DropAllCapabilities(proc_fd_));
+
+ // We do not drop CAP_SYS_ADMIN because we need it to place each child process
+ // in its own PID namespace later on.
+ std::vector<sandbox::Credentials::Capability> caps;
+ caps.push_back(sandbox::Credentials::Capability::SYS_ADMIN);
+ CHECK(sandbox::Credentials::SetCapabilities(proc_fd_, caps));
// This needs to happen after moving to a new user NS, since doing so involves
// writing the UID/GID map.
diff --git a/content/common/sandbox_linux/sandbox_linux.h b/content/common/sandbox_linux/sandbox_linux.h
index 12aabcf..01a0bd9a 100644
--- a/content/common/sandbox_linux/sandbox_linux.h
+++ b/content/common/sandbox_linux/sandbox_linux.h
@@ -9,6 +9,7 @@
#include <vector>
#include "base/basictypes.h"
+#include "base/logging.h"
#include "base/memory/scoped_ptr.h"
#include "content/public/common/sandbox_linux.h"
@@ -118,6 +119,13 @@ class LinuxSandbox {
// to make some vulnerabilities harder to exploit.
bool LimitAddressSpace(const std::string& process_type);
+ // Returns a file descriptor to proc. The file descriptor is no longer valid
+ // after the sandbox has been sealed.
+ int proc_fd() const {
+ DCHECK_NE(-1, proc_fd_);
+ return proc_fd_;
+ }
+
#if defined(ANY_OF_AMTLU_SANITIZER)
__sanitizer_sandbox_arguments* sanitizer_args() const {
return sanitizer_args_.get();
diff --git a/content/zygote/zygote_linux.cc b/content/zygote/zygote_linux.cc
index 5944f87..5a84dec 100644
--- a/content/zygote/zygote_linux.cc
+++ b/content/zygote/zygote_linux.cc
@@ -36,6 +36,8 @@
#include "content/public/common/zygote_fork_delegate_linux.h"
#include "ipc/ipc_channel.h"
#include "ipc/ipc_switches.h"
+#include "sandbox/linux/services/credentials.h"
+#include "sandbox/linux/services/namespace_sandbox.h"
// See http://code.google.com/p/chromium/wiki/LinuxZygote
@@ -47,6 +49,14 @@ namespace {
void SIGCHLDHandler(int signal) {
}
+// On Linux, when a process is the init process of a PID namespace, it cannot be
+// terminated by signals like SIGTERM or SIGINT, since they are ignored unless
+// we register a handler for them. In the handlers, we exit with this special
+// exit code that GetTerminationStatus understands to mean that we were
+// terminated by an external signal.
+const int kKilledExitCode = 0x80;
+const int kUnexpectedExitCode = 0x81;
+
int LookUpFd(const base::GlobalDescriptors::Mapping& fd_mapping, uint32_t key) {
for (size_t index = 0; index < fd_mapping.size(); ++index) {
if (fd_mapping[index].key == key)
@@ -104,7 +114,7 @@ bool Zygote::ProcessRequests() {
struct sigaction action;
memset(&action, 0, sizeof(action));
action.sa_handler = &SIGCHLDHandler;
- CHECK(sigaction(SIGCHLD, &action, NULL) == 0);
+ PCHECK(sigaction(SIGCHLD, &action, NULL) == 0);
if (UsingSUIDSandbox() || UsingNSSandbox()) {
// Let the ZygoteHost know we are ready to go.
@@ -305,6 +315,11 @@ bool Zygote::GetTerminationStatus(base::ProcessHandle real_pid,
// Time to forget about this process.
process_info_map_.erase(real_pid);
}
+
+ if (WIFEXITED(*exit_code) && WEXITSTATUS(*exit_code) == kKilledExitCode) {
+ *status = base::TERMINATION_STATUS_PROCESS_WAS_KILLED;
+ }
+
return true;
}
@@ -375,12 +390,33 @@ int Zygote::ForkWithRealPid(const std::string& process_type,
CHECK_NE(pid, 0);
} else {
CreatePipe(&read_pipe, &write_pipe);
- // This is roughly equivalent to a fork(). We are using ForkWithFlags mainly
- // to give it some more diverse test coverage.
- pid = base::ForkWithFlags(SIGCHLD, nullptr, nullptr);
+ if (sandbox_flags_ & kSandboxLinuxPIDNS &&
+ sandbox_flags_ & kSandboxLinuxUserNS) {
+ pid = sandbox::NamespaceSandbox::ForkInNewPidNamespace(
+ /*drop_capabilities_in_child=*/true);
+ } else {
+ pid = fork();
+ }
}
if (pid == 0) {
+ // If the process is the init process inside a PID namespace, it must have
+ // explicit signal handlers.
+ if (getpid() == 1) {
+ for (const int sig : {SIGINT, SIGTERM}) {
+ sandbox::NamespaceSandbox::InstallTerminationSignalHandler(
+ sig, kKilledExitCode);
+ }
+
+ static const int kUnexpectedSignals[] = {
+ SIGHUP, SIGQUIT, SIGABRT, SIGPIPE, SIGUSR1, SIGUSR2,
+ };
+ for (const int sig : kUnexpectedSignals) {
+ sandbox::NamespaceSandbox::InstallTerminationSignalHandler(
+ sig, kUnexpectedExitCode);
+ }
+ }
+
// In the child process.
write_pipe.reset();
diff --git a/content/zygote/zygote_main_linux.cc b/content/zygote/zygote_main_linux.cc
index 96fac21..b9dfdca 100644
--- a/content/zygote/zygote_main_linux.cc
+++ b/content/zygote/zygote_main_linux.cc
@@ -40,6 +40,7 @@
#include "content/public/common/zygote_fork_delegate_linux.h"
#include "content/zygote/zygote_linux.h"
#include "crypto/nss_util.h"
+#include "sandbox/linux/services/credentials.h"
#include "sandbox/linux/services/init_process_reaper.h"
#include "sandbox/linux/services/libc_urandom_override.h"
#include "sandbox/linux/services/namespace_sandbox.h"
@@ -80,6 +81,11 @@ void CloseFds(const std::vector<int>& fds) {
}
}
+void RunTwoClosures(const base::Closure* first, const base::Closure* second) {
+ first->Run();
+ second->Run();
+}
+
} // namespace
// See http://code.google.com/p/chromium/wiki/LinuxZygote
@@ -407,12 +413,20 @@ static bool EnterSuidSandbox(sandbox::SetuidSandboxClient* setuid_sandbox,
return true;
}
+static void DropAllCapabilities(int proc_fd) {
+ CHECK(sandbox::Credentials::DropAllCapabilities(proc_fd));
+}
+
static void EnterNamespaceSandbox(LinuxSandbox* linux_sandbox,
base::Closure* post_fork_parent_callback) {
linux_sandbox->EngageNamespaceSandbox();
if (getpid() == 1) {
- CHECK(CreateInitProcessReaper(post_fork_parent_callback));
+ base::Closure drop_all_caps_callback =
+ base::Bind(&DropAllCapabilities, linux_sandbox->proc_fd());
+ base::Closure callback = base::Bind(
+ &RunTwoClosures, &drop_all_caps_callback, post_fork_parent_callback);
+ CHECK(CreateInitProcessReaper(&callback));
}
}
diff --git a/sandbox/linux/services/credentials.cc b/sandbox/linux/services/credentials.cc
index 2e66d97..0dea5aa 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