diff options
author | rickyz <rickyz@chromium.org> | 2015-03-27 15:31:15 -0700 |
---|---|---|
committer | Commit bot <commit-bot@chromium.org> | 2015-03-27 22:32:14 +0000 |
commit | 4557699fbb30704f76c68b906d2656d2e322572c (patch) | |
tree | eff092838beee026cd01744ab663b647d9a16ebb | |
parent | d5620b8ff8e4d3e7ba75d1c798072f7eb4f123ce (diff) | |
download | chromium_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.cc | 7 | ||||
-rw-r--r-- | content/common/sandbox_linux/sandbox_linux.h | 8 | ||||
-rw-r--r-- | content/zygote/zygote_linux.cc | 44 | ||||
-rw-r--r-- | content/zygote/zygote_main_linux.cc | 16 | ||||
-rw-r--r-- | sandbox/linux/services/credentials.cc | 51 | ||||
-rw-r--r-- | sandbox/linux/services/credentials.h | 26 | ||||
-rw-r--r-- | sandbox/linux/services/credentials_unittest.cc | 21 | ||||
-rw-r--r-- | sandbox/linux/services/namespace_sandbox.cc | 76 | ||||
-rw-r--r-- | sandbox/linux/services/namespace_sandbox.h | 36 | ||||
-rw-r--r-- | sandbox/linux/services/namespace_sandbox_unittest.cc | 94 |
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 |