diff options
author | Jocelyn Turcotte <jocelyn.turcotte@digia.com> | 2014-08-08 14:30:41 +0200 |
---|---|---|
committer | Jocelyn Turcotte <jocelyn.turcotte@digia.com> | 2014-08-12 13:49:54 +0200 |
commit | ab0a50979b9eb4dfa3320eff7e187e41efedf7a9 (patch) | |
tree | 498dfb8a97ff3361a9f7486863a52bb4e26bb898 /chromium/sandbox/linux/services | |
parent | 4ce69f7403811819800e7c5ae1318b2647e778d1 (diff) |
Update Chromium to beta version 37.0.2062.68
Change-Id: I188e3b5aff1bec75566014291b654eb19f5bc8ca
Reviewed-by: Andras Becsi <andras.becsi@digia.com>
Diffstat (limited to 'chromium/sandbox/linux/services')
21 files changed, 1344 insertions, 91 deletions
diff --git a/chromium/sandbox/linux/services/android_futex.h b/chromium/sandbox/linux/services/android_futex.h new file mode 100644 index 00000000000..032b02481a1 --- /dev/null +++ b/chromium/sandbox/linux/services/android_futex.h @@ -0,0 +1,28 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef SANDBOX_LINUX_SERVICES_ANDROID_FUTEX_H_ +#define SANDBOX_LINUX_SERVICES_ANDROID_FUTEX_H_ + +#if !defined(FUTEX_PRIVATE_FLAG) +#define FUTEX_PRIVATE_FLAG 128 +#endif + +#if !defined(FUTEX_CLOCK_REALTIME) +#define FUTEX_CLOCK_REALTIME 256 +#endif + +#if !defined(FUTEX_CMP_REQUEUE_PI) +#define FUTEX_CMP_REQUEUE_PI 12 +#endif + +#if !defined(FUTEX_CMP_REQUEUE_PI_PRIVATE) +#define FUTEX_CMP_REQUEUE_PI_PRIVATE (FUTEX_CMP_REQUEUE_PI | FUTEX_PRIVATE_FLAG) +#endif + +#if !defined(FUTEX_CMD_MASK) +#define FUTEX_CMD_MASK ~(FUTEX_PRIVATE_FLAG | FUTEX_CLOCK_REALTIME) +#endif + +#endif // SANDBOX_LINUX_SERVICES_ANDROID_FUTEX_H_ diff --git a/chromium/sandbox/linux/services/android_ucontext.h b/chromium/sandbox/linux/services/android_ucontext.h index 437bbab7bab..caabaf5b1b3 100644 --- a/chromium/sandbox/linux/services/android_ucontext.h +++ b/chromium/sandbox/linux/services/android_ucontext.h @@ -11,6 +11,8 @@ #include "sandbox/linux/services/android_arm_ucontext.h" #elif defined(__i386__) #include "sandbox/linux/services/android_i386_ucontext.h" +#elif defined(__x86_64__) +#include "sandbox/linux/services/android_x86_64_ucontext.h" #else #error "No support for your architecture in Android header" #endif diff --git a/chromium/sandbox/linux/services/android_x86_64_ucontext.h b/chromium/sandbox/linux/services/android_x86_64_ucontext.h new file mode 100644 index 00000000000..ef328e55d6f --- /dev/null +++ b/chromium/sandbox/linux/services/android_x86_64_ucontext.h @@ -0,0 +1,88 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef SANDBOX_LINUX_SERVICES_ANDROID_X86_64_UCONTEXT_H_ +#define SANDBOX_LINUX_SERVICES_ANDROID_X86_64_UCONTEXT_H_ + +// We do something compatible with glibc. Hopefully, at some point Android will +// provide that for us, and __BIONIC_HAVE_UCONTEXT_T should be defined. +// Spec: +// http://refspecs.linuxfoundation.org/LSB_4.1.0/LSB-Core-AMD64/LSB-Core-AMD64/libc-ddefs.html#AEN5668 + +#if !defined(__BIONIC_HAVE_UCONTEXT_T) +#include <asm/sigcontext.h> + +struct _libc_fpxreg { + unsigned short significand[4]; + unsigned short exponent; + unsigned short padding[3]; +}; + +struct _libc_xmmreg { + uint32_t element[4]; +}; + +struct _libc_fpstate { + uint16_t cwd; + uint16_t swd; + uint16_t twd; + uint16_t fop; + uint64_t rip; + uint64_t rdp; + uint32_t mxcsr; + uint32_t mxcsr_mask; + struct _libc_fpxreg _st[8]; + struct _libc_xmmreg _xmm[16]; + uint32_t padding[24]; +}; + +typedef uint64_t greg_t; + +typedef struct { + greg_t gregs[23]; + struct _libc_fpstate* fpregs; + unsigned long __reserved1[8]; +} mcontext_t; + +enum { + REG_R8 = 0, + REG_R9, + REG_R10, + REG_R11, + REG_R12, + REG_R13, + REG_R14, + REG_R15, + REG_RDI, + REG_RSI, + REG_RBP, + REG_RBX, + REG_RDX, + REG_RAX, + REG_RCX, + REG_RSP, + REG_RIP, + REG_EFL, + REG_CSGSFS, + REG_ERR, + REG_TRAPNO, + REG_OLDMASK, + REG_CR2, + NGREG, +}; + +typedef struct ucontext { + unsigned long uc_flags; + struct ucontext* uc_link; + stack_t uc_stack; + mcontext_t uc_mcontext; + sigset_t uc_sigmask; + struct _libc_fpstate __fpregs_mem; +} ucontext_t; + +#else +#include <sys/ucontext.h> +#endif // __BIONIC_HAVE_UCONTEXT_T + +#endif // SANDBOX_LINUX_SERVICES_ANDROID_X86_64_UCONTEXT_H_ diff --git a/chromium/sandbox/linux/services/broker_process.cc b/chromium/sandbox/linux/services/broker_process.cc index 438e9726374..ef916f223a8 100644 --- a/chromium/sandbox/linux/services/broker_process.cc +++ b/chromium/sandbox/linux/services/broker_process.cc @@ -5,10 +5,12 @@ #include "sandbox/linux/services/broker_process.h" #include <fcntl.h> +#include <signal.h> #include <sys/socket.h> #include <sys/stat.h> #include <sys/syscall.h> #include <sys/types.h> +#include <sys/wait.h> #include <unistd.h> #include <algorithm> @@ -16,12 +18,16 @@ #include <vector> #include "base/basictypes.h" +#include "base/callback.h" #include "base/compiler_specific.h" +#include "base/files/scoped_file.h" #include "base/logging.h" +#include "base/memory/scoped_vector.h" #include "base/pickle.h" #include "base/posix/eintr_wrapper.h" #include "base/posix/unix_domain_socket_linux.h" #include "base/process/process_metrics.h" +#include "base/third_party/valgrind/valgrind.h" #include "build/build_config.h" #include "sandbox/linux/services/linux_syscalls.h" @@ -31,6 +37,22 @@ namespace { +bool IsRunningOnValgrind() { return RUNNING_ON_VALGRIND; } + +// A little open(2) wrapper to handle some oddities for us. In the general case +// make a direct system call since we want to keep in control of the broker +// process' system calls profile to be able to loosely sandbox it. +int sys_open(const char* pathname, int flags) { + // Always pass a defined |mode| in case flags mistakenly contains O_CREAT. + const int mode = 0; + if (IsRunningOnValgrind()) { + // Valgrind does not support AT_FDCWD, just use libc's open() in this case. + return open(pathname, flags, mode); + } else { + return syscall(__NR_openat, AT_FDCWD, pathname, flags, mode); + } +} + static const size_t kMaxMessageLength = 4096; // Some flags are local to the current process and cannot be sent over a Unix @@ -132,11 +154,19 @@ BrokerProcess::BrokerProcess(int denied_errno, BrokerProcess::~BrokerProcess() { if (initialized_ && ipc_socketpair_ != -1) { - close(ipc_socketpair_); + // Closing the socket should be enough to notify the child to die, + // unless it has been duplicated. + PCHECK(0 == IGNORE_EINTR(close(ipc_socketpair_))); + PCHECK(0 == kill(broker_pid_, SIGKILL)); + siginfo_t process_info; + // Reap the child. + int ret = HANDLE_EINTR(waitid(P_PID, broker_pid_, &process_info, WEXITED)); + PCHECK(0 == ret); } } -bool BrokerProcess::Init(bool (*sandbox_callback)(void)) { +bool BrokerProcess::Init( + const base::Callback<bool(void)>& broker_process_init_callback) { CHECK(!initialized_); int socket_pair[2]; // Use SOCK_SEQPACKET, because we need to preserve message boundaries @@ -147,7 +177,9 @@ bool BrokerProcess::Init(bool (*sandbox_callback)(void)) { return false; } +#if !defined(THREAD_SANITIZER) DCHECK_EQ(1, base::GetNumberOfThreads(base::GetCurrentProcessHandle())); +#endif int child_pid = fork(); if (child_pid == -1) { close(socket_pair[0]); @@ -173,10 +205,7 @@ bool BrokerProcess::Init(bool (*sandbox_callback)(void)) { shutdown(socket_pair[0], SHUT_WR); ipc_socketpair_ = socket_pair[0]; is_child_ = true; - // Enable the sandbox if provided. - if (sandbox_callback) { - CHECK(sandbox_callback()); - } + CHECK(broker_process_init_callback.Run()); initialized_ = true; for (;;) { HandleRequest(); @@ -292,8 +321,7 @@ int BrokerProcess::PathAndFlagsSyscall(enum IPCCommands syscall_type, // that we will then close. // A request should start with an int that will be used as the command type. bool BrokerProcess::HandleRequest() const { - - std::vector<int> fds; + ScopedVector<base::ScopedFD> fds; char buf[kMaxMessageLength]; errno = 0; const ssize_t msg_len = UnixDomainSocket::RecvMsg(ipc_socketpair_, buf, @@ -306,12 +334,13 @@ bool BrokerProcess::HandleRequest() const { // The parent should send exactly one file descriptor, on which we // will write the reply. - if (msg_len < 0 || fds.size() != 1 || fds.at(0) < 0) { + // TODO(mdempsky): ScopedVector doesn't have 'at()', only 'operator[]'. + if (msg_len < 0 || fds.size() != 1 || fds[0]->get() < 0) { PLOG(ERROR) << "Error reading message from the client"; return false; } - const int temporary_ipc = fds.at(0); + base::ScopedFD temporary_ipc(fds[0]->Pass()); Pickle pickle(buf, msg_len); PickleIterator iter(pickle); @@ -324,15 +353,13 @@ bool BrokerProcess::HandleRequest() const { case kCommandOpen: // We reply on the file descriptor sent to us via the IPC channel. r = HandleRemoteCommand(static_cast<IPCCommands>(command_type), - temporary_ipc, pickle, iter); + temporary_ipc.get(), pickle, iter); break; default: NOTREACHED(); r = false; break; } - int ret = IGNORE_EINTR(close(temporary_ipc)); - DCHECK(!ret) << "Could not close temporary IPC channel"; return r; } @@ -375,7 +402,7 @@ bool BrokerProcess::HandleRemoteCommand(IPCCommands command_type, int reply_ipc, // Close anything we have opened in this process. for (std::vector<int>::iterator it = opened_files.begin(); - it < opened_files.end(); ++it) { + it != opened_files.end(); ++it) { int ret = IGNORE_EINTR(close(*it)); DCHECK(!ret) << "Could not close file descriptor"; } @@ -423,9 +450,7 @@ void BrokerProcess::OpenFileForIPC(const std::string& requested_filename, if (safe_to_open_file) { CHECK(file_to_open); - // We're doing a 2-parameter open, so we don't support O_CREAT. It doesn't - // hurt to always pass a third argument though. - int opened_fd = syscall(__NR_open, file_to_open, flags, 0); + int opened_fd = sys_open(file_to_open, flags); if (opened_fd < 0) { write_pickle->WriteInt(-errno); } else { diff --git a/chromium/sandbox/linux/services/broker_process.h b/chromium/sandbox/linux/services/broker_process.h index 6b13b33046d..ddde42ebfc1 100644 --- a/chromium/sandbox/linux/services/broker_process.h +++ b/chromium/sandbox/linux/services/broker_process.h @@ -9,8 +9,10 @@ #include <vector> #include "base/basictypes.h" +#include "base/callback_forward.h" #include "base/pickle.h" #include "base/process/process.h" +#include "sandbox/sandbox_export.h" namespace sandbox { @@ -24,7 +26,7 @@ namespace sandbox { // 2. CHECK(open_broker.Init(NULL)); // 3. Enable sandbox. // 4. Use open_broker.Open() to open files. -class BrokerProcess { +class SANDBOX_EXPORT BrokerProcess { public: // |denied_errno| is the error code returned when methods such as Open() // or Access() are invoked on a file which is not in the whitelist. EACCESS @@ -42,9 +44,9 @@ class BrokerProcess { ~BrokerProcess(); // Will initialize the broker process. There should be no threads at this // point, since we need to fork(). - // sandbox_callback is a function that should be called to enable the - // sandbox in the broker. - bool Init(bool (*sandbox_callback)(void)); + // broker_process_init_callback will be called in the new broker process, + // after fork() returns. + bool Init(const base::Callback<bool(void)>& broker_process_init_callback); // Can be used in place of access(). Will be async signal safe. // X_OK will always return an error in practice since the broker process @@ -95,6 +97,8 @@ class BrokerProcess { const std::vector<std::string> allowed_w_files_; // Files allowed for write. int ipc_socketpair_; // Our communication channel to parent or child. DISALLOW_IMPLICIT_CONSTRUCTORS(BrokerProcess); + + friend class BrokerProcessTestHelper; }; } // namespace sandbox diff --git a/chromium/sandbox/linux/services/broker_process_unittest.cc b/chromium/sandbox/linux/services/broker_process_unittest.cc index f163ef99718..a1f38df3ae9 100644 --- a/chromium/sandbox/linux/services/broker_process_unittest.cc +++ b/chromium/sandbox/linux/services/broker_process_unittest.cc @@ -6,26 +6,37 @@ #include <errno.h> #include <fcntl.h> +#include <sys/resource.h> #include <sys/stat.h> #include <sys/types.h> #include <sys/wait.h> #include <unistd.h> +#include <algorithm> #include <string> #include <vector> #include "base/basictypes.h" +#include "base/bind.h" #include "base/file_util.h" +#include "base/files/scoped_file.h" #include "base/logging.h" #include "base/memory/scoped_ptr.h" #include "base/posix/eintr_wrapper.h" +#include "base/posix/unix_domain_socket_linux.h" +#include "sandbox/linux/tests/test_utils.h" #include "sandbox/linux/tests/unit_tests.h" #include "testing/gtest/include/gtest/gtest.h" -using file_util::ScopedFD; - namespace sandbox { +class BrokerProcessTestHelper { + public: + static int get_ipc_socketpair(const BrokerProcess* broker) { + return broker->ipc_socketpair_; + } +}; + namespace { // Creates and open a temporary file on creation and closes @@ -60,13 +71,9 @@ class ScopedTemporaryFile { DISALLOW_COPY_AND_ASSIGN(ScopedTemporaryFile); }; -} // namespace +bool NoOpCallback() { return true; } -#if defined(OS_ANDROID) - #define DISABLE_ON_ANDROID(function) DISABLED_##function -#else - #define DISABLE_ON_ANDROID(function) function -#endif +} // namespace TEST(BrokerProcess, CreateAndDestroy) { std::vector<std::string> read_whitelist; @@ -74,21 +81,18 @@ TEST(BrokerProcess, CreateAndDestroy) { scoped_ptr<BrokerProcess> open_broker( new BrokerProcess(EPERM, read_whitelist, std::vector<std::string>())); - ASSERT_TRUE(open_broker->Init(NULL)); - pid_t broker_pid = open_broker->broker_pid(); + ASSERT_TRUE(open_broker->Init(base::Bind(&NoOpCallback))); + ASSERT_TRUE(TestUtils::CurrentProcessHasChildren()); // Destroy the broker and check it has exited properly. open_broker.reset(); - int status = 0; - ASSERT_EQ(waitpid(broker_pid, &status, 0), broker_pid); - ASSERT_TRUE(WIFEXITED(status)); - ASSERT_EQ(WEXITSTATUS(status), 0); + ASSERT_FALSE(TestUtils::CurrentProcessHasChildren()); } TEST(BrokerProcess, TestOpenAccessNull) { const std::vector<std::string> empty; BrokerProcess open_broker(EPERM, empty, empty); - ASSERT_TRUE(open_broker.Init(NULL)); + ASSERT_TRUE(open_broker.Init(base::Bind(&NoOpCallback))); int fd = open_broker.Open(NULL, O_RDONLY); ASSERT_EQ(fd, -EFAULT); @@ -119,7 +123,7 @@ void TestOpenFilePerms(bool fast_check_in_client, int denied_errno) { read_whitelist, write_whitelist, fast_check_in_client); - ASSERT_TRUE(open_broker.Init(NULL)); + ASSERT_TRUE(open_broker.Init(base::Bind(&NoOpCallback))); int fd = -1; fd = open_broker.Open(kR_WhiteListed, O_RDONLY); @@ -270,12 +274,11 @@ void TestOpenCpuinfo(bool fast_check_in_client) { scoped_ptr<BrokerProcess> open_broker(new BrokerProcess( EPERM, read_whitelist, std::vector<std::string>(), fast_check_in_client)); - ASSERT_TRUE(open_broker->Init(NULL)); - pid_t broker_pid = open_broker->broker_pid(); + ASSERT_TRUE(open_broker->Init(base::Bind(&NoOpCallback))); int fd = -1; fd = open_broker->Open(kFileCpuInfo, O_RDWR); - ScopedFD fd_closer(&fd); + base::ScopedFD fd_closer(fd); ASSERT_EQ(fd, -EPERM); // Check we can read /proc/cpuinfo. @@ -287,7 +290,7 @@ void TestOpenCpuinfo(bool fast_check_in_client) { // Open cpuinfo via the broker. int cpuinfo_fd = open_broker->Open(kFileCpuInfo, O_RDONLY); - ScopedFD cpuinfo_fd_closer(&cpuinfo_fd); + base::ScopedFD cpuinfo_fd_closer(cpuinfo_fd); ASSERT_GE(cpuinfo_fd, 0); char buf[3]; memset(buf, 0, sizeof(buf)); @@ -296,7 +299,7 @@ void TestOpenCpuinfo(bool fast_check_in_client) { // Open cpuinfo directly. int cpuinfo_fd2 = open(kFileCpuInfo, O_RDONLY); - ScopedFD cpuinfo_fd2_closer(&cpuinfo_fd2); + base::ScopedFD cpuinfo_fd2_closer(cpuinfo_fd2); ASSERT_GE(cpuinfo_fd2, 0); char buf2[3]; memset(buf2, 1, sizeof(buf2)); @@ -309,13 +312,9 @@ void TestOpenCpuinfo(bool fast_check_in_client) { // ourselves. ASSERT_EQ(memcmp(buf, buf2, read_len1), 0); + ASSERT_TRUE(TestUtils::CurrentProcessHasChildren()); open_broker.reset(); - - // Now we check that the broker has exited properly. - int status = 0; - ASSERT_EQ(waitpid(broker_pid, &status, 0), broker_pid); - ASSERT_TRUE(WIFEXITED(status)); - ASSERT_EQ(WEXITSTATUS(status), 0); + ASSERT_FALSE(TestUtils::CurrentProcessHasChildren()); } // Run the same thing twice. The second time, we make sure that no security @@ -340,7 +339,7 @@ TEST(BrokerProcess, OpenFileRW) { whitelist.push_back(tempfile_name); BrokerProcess open_broker(EPERM, whitelist, whitelist); - ASSERT_TRUE(open_broker.Init(NULL)); + ASSERT_TRUE(open_broker.Init(base::Bind(&NoOpCallback))); // Check we can access that file with read or write. int can_access = open_broker.Access(tempfile_name, R_OK | W_OK); @@ -377,15 +376,19 @@ SANDBOX_TEST(BrokerProcess, BrokerDied) { std::vector<std::string>(), true /* fast_check_in_client */, true /* quiet_failures_for_tests */); - SANDBOX_ASSERT(open_broker.Init(NULL)); - pid_t broker_pid = open_broker.broker_pid(); + SANDBOX_ASSERT(open_broker.Init(base::Bind(&NoOpCallback))); + const pid_t broker_pid = open_broker.broker_pid(); SANDBOX_ASSERT(kill(broker_pid, SIGKILL) == 0); - // Now we check that the broker has exited properly. - int status = 0; - SANDBOX_ASSERT(waitpid(broker_pid, &status, 0) == broker_pid); - SANDBOX_ASSERT(WIFSIGNALED(status)); - SANDBOX_ASSERT(WTERMSIG(status) == SIGKILL); + // Now we check that the broker has been signaled, but do not reap it. + siginfo_t process_info; + SANDBOX_ASSERT(HANDLE_EINTR(waitid( + P_PID, broker_pid, &process_info, WEXITED | WNOWAIT)) == + 0); + SANDBOX_ASSERT(broker_pid == process_info.si_pid); + SANDBOX_ASSERT(CLD_KILLED == process_info.si_code); + SANDBOX_ASSERT(SIGKILL == process_info.si_status); + // Check that doing Open with a dead broker won't SIGPIPE us. SANDBOX_ASSERT(open_broker.Open("/proc/cpuinfo", O_RDONLY) == -ENOMEM); SANDBOX_ASSERT(open_broker.Access("/proc/cpuinfo", O_RDONLY) == -ENOMEM); @@ -400,7 +403,7 @@ void TestOpenComplexFlags(bool fast_check_in_client) { whitelist, whitelist, fast_check_in_client); - ASSERT_TRUE(open_broker.Init(NULL)); + ASSERT_TRUE(open_broker.Init(base::Bind(&NoOpCallback))); // Test that we do the right thing for O_CLOEXEC and O_NONBLOCK. int fd = -1; int ret = 0; @@ -441,4 +444,60 @@ TEST(BrokerProcess, OpenComplexFlagsNoClientCheck) { // expected. } +// We need to allow noise because the broker will log when it receives our +// bogus IPCs. +SANDBOX_TEST_ALLOW_NOISE(BrokerProcess, RecvMsgDescriptorLeak) { + // Find the four lowest available file descriptors. + int available_fds[4]; + SANDBOX_ASSERT(0 == pipe(available_fds)); + SANDBOX_ASSERT(0 == pipe(available_fds + 2)); + + // Save one FD to send to the broker later, and close the others. + base::ScopedFD message_fd(available_fds[0]); + for (size_t i = 1; i < arraysize(available_fds); i++) { + SANDBOX_ASSERT(0 == IGNORE_EINTR(close(available_fds[i]))); + } + + // Lower our file descriptor limit to just allow three more file descriptors + // to be allocated. (N.B., RLIMIT_NOFILE doesn't limit the number of file + // descriptors a process can have: it only limits the highest value that can + // be assigned to newly-created descriptors allocated by the process.) + const rlim_t fd_limit = + 1 + *std::max_element(available_fds, + available_fds + arraysize(available_fds)); + + // Valgrind doesn't allow changing the hard descriptor limit, so we only + // change the soft descriptor limit here. + struct rlimit rlim; + SANDBOX_ASSERT(0 == getrlimit(RLIMIT_NOFILE, &rlim)); + SANDBOX_ASSERT(fd_limit <= rlim.rlim_cur); + rlim.rlim_cur = fd_limit; + SANDBOX_ASSERT(0 == setrlimit(RLIMIT_NOFILE, &rlim)); + + static const char kCpuInfo[] = "/proc/cpuinfo"; + std::vector<std::string> read_whitelist; + read_whitelist.push_back(kCpuInfo); + + BrokerProcess open_broker(EPERM, read_whitelist, std::vector<std::string>()); + SANDBOX_ASSERT(open_broker.Init(base::Bind(&NoOpCallback))); + + const int ipc_fd = BrokerProcessTestHelper::get_ipc_socketpair(&open_broker); + SANDBOX_ASSERT(ipc_fd >= 0); + + static const char kBogus[] = "not a pickle"; + std::vector<int> fds; + fds.push_back(message_fd.get()); + + // The broker process should only have a couple spare file descriptors + // available, but for good measure we send it fd_limit bogus IPCs anyway. + for (rlim_t i = 0; i < fd_limit; ++i) { + SANDBOX_ASSERT( + UnixDomainSocket::SendMsg(ipc_fd, kBogus, sizeof(kBogus), fds)); + } + + const int fd = open_broker.Open(kCpuInfo, O_RDONLY); + SANDBOX_ASSERT(fd >= 0); + SANDBOX_ASSERT(0 == IGNORE_EINTR(close(fd))); +} + } // namespace sandbox diff --git a/chromium/sandbox/linux/services/credentials.cc b/chromium/sandbox/linux/services/credentials.cc index 4f041dc2887..96702b1700a 100644 --- a/chromium/sandbox/linux/services/credentials.cc +++ b/chromium/sandbox/linux/services/credentials.cc @@ -7,21 +7,28 @@ #include <dirent.h> #include <errno.h> #include <fcntl.h> +#include <signal.h> #include <stdio.h> #include <sys/capability.h> #include <sys/stat.h> +#include <sys/syscall.h> #include <sys/types.h> +#include <sys/wait.h> #include <unistd.h> #include "base/basictypes.h" #include "base/bind.h" #include "base/logging.h" +#include "base/posix/eintr_wrapper.h" #include "base/strings/string_number_conversions.h" #include "base/template_util.h" +#include "base/third_party/valgrind/valgrind.h" #include "base/threading/thread.h" namespace { +bool IsRunningOnValgrind() { return RUNNING_ON_VALGRIND; } + struct CapFreeDeleter { inline void operator()(cap_t cap) const { int ret = cap_free(cap); @@ -49,7 +56,7 @@ struct FILECloser { } }; -// Don't use ScopedFILE in base/file_util.h since it doesn't check fclose(). +// Don't use ScopedFILE in base since it doesn't check fclose(). // TODO(jln): fix base/. typedef scoped_ptr<FILE, FILECloser> ScopedFILE; @@ -146,6 +153,16 @@ bool ChrootToSafeEmptyDir() { return is_chrooted; } +// CHECK() that an attempt to move to a new user namespace raised an expected +// errno. +void CheckCloneNewUserErrno(int error) { + // EPERM can happen if already in a chroot. EUSERS if too many nested + // namespaces are used. EINVAL for kernels that don't support the feature. + // Valgrind will ENOSYS unshare(). + PCHECK(error == EPERM || error == EUSERS || error == EINVAL || + error == ENOSYS); +} + } // namespace. namespace sandbox { @@ -156,6 +173,35 @@ Credentials::Credentials() { Credentials::~Credentials() { } +int Credentials::CountOpenFds(int proc_fd) { + DCHECK_LE(0, proc_fd); + int proc_self_fd = openat(proc_fd, "self/fd", O_DIRECTORY | O_RDONLY); + PCHECK(0 <= proc_self_fd); + + // Ownership of proc_self_fd is transferred here, it must not be closed + // or modified afterwards except via dir. + ScopedDIR dir(fdopendir(proc_self_fd)); + CHECK(dir); + + int count = 0; + struct dirent e; + struct dirent* de; + while (!readdir_r(dir.get(), &e, &de) && de) { + if (strcmp(e.d_name, ".") == 0 || strcmp(e.d_name, "..") == 0) { + continue; + } + + int fd_num; + CHECK(base::StringToInt(e.d_name, &fd_num)); + if (fd_num == proc_fd || fd_num == proc_self_fd) { + continue; + } + + ++count; + } + return count; +} + bool Credentials::HasOpenDirectory(int proc_fd) { int proc_self_fd = -1; if (proc_fd >= 0) { @@ -175,7 +221,7 @@ bool Credentials::HasOpenDirectory(int proc_fd) { return false; } } - CHECK_GE(proc_self_fd, 0); + PCHECK(0 <= proc_self_fd); // Ownership of proc_self_fd is transferred here, it must not be closed // or modified afterwards except via dir. @@ -231,6 +277,37 @@ scoped_ptr<std::string> Credentials::GetCurrentCapString() const { return scoped_ptr<std::string> (new std::string(cap_text.get())); } +// static +bool Credentials::SupportsNewUserNS() { + // Valgrind will let clone(2) pass-through, but doesn't support unshare(), + // so always consider UserNS unsupported there. + if (IsRunningOnValgrind()) { + return false; + } + + // This is roughly a fork(). + const pid_t pid = syscall(__NR_clone, CLONE_NEWUSER | SIGCHLD, 0, 0, 0); + + if (pid == -1) { + CheckCloneNewUserErrno(errno); + return false; + } + + // The parent process could have had threads. In the child, these threads + // have disappeared. Make sure to not do anything in the child, as this is a + // fragile execution environment. + if (pid == 0) { + _exit(0); + } + + // Always reap the child. + siginfo_t infop; + PCHECK(0 == HANDLE_EINTR(waitid(P_PID, pid, &infop, WEXITED))); + + // clone(2) succeeded, we can use CLONE_NEWUSER. + return true; +} + bool Credentials::MoveToNewUserNS() { uid_t uid; gid_t gid; @@ -241,16 +318,14 @@ bool Credentials::MoveToNewUserNS() { return false; } int ret = unshare(CLONE_NEWUSER); - // EPERM can happen if already in a chroot. EUSERS if too many nested - // namespaces are used. EINVAL for kernels that don't support the feature. - // Valgrind will ENOSYS unshare(). - PCHECK(!ret || errno == EPERM || errno == EUSERS || errno == EINVAL || - errno == ENOSYS); if (ret) { + const int unshare_errno = errno; VLOG(1) << "Looks like unprivileged CLONE_NEWUSER may not be available " << "on this kernel."; + CheckCloneNewUserErrno(unshare_errno); return false; } + // The current {r,e,s}{u,g}id is now an overflow id (c.f. // /proc/sys/kernel/overflowuid). Setup the uid and gid maps. DCHECK(GetRESIds(NULL, NULL)); diff --git a/chromium/sandbox/linux/services/credentials.h b/chromium/sandbox/linux/services/credentials.h index c23db930df2..99f8f322dff 100644 --- a/chromium/sandbox/linux/services/credentials.h +++ b/chromium/sandbox/linux/services/credentials.h @@ -15,17 +15,23 @@ #include "base/basictypes.h" #include "base/memory/scoped_ptr.h" +#include "sandbox/sandbox_export.h" namespace sandbox { // 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 Credentials { +class SANDBOX_EXPORT Credentials { public: Credentials(); ~Credentials(); + // Returns the number of file descriptors in the current process's FD + // table, excluding |proc_fd|, which should be a file descriptor for + // /proc. + int CountOpenFds(int proc_fd); + // Checks whether the current process has any directory file descriptor open. // Directory file descriptors are "capabilities" that would let a process use // system calls such as openat() to bypass restrictions such as @@ -51,6 +57,13 @@ class Credentials { // debugging and tests. scoped_ptr<std::string> GetCurrentCapString() const; + // Returns whether the kernel supports CLONE_NEWUSER and whether it would be + // possible to immediately move to a new user namespace. There is no point + // in using this method right before calling MoveToNewUserNS(), simply call + // MoveToNewUserNS() immediately. This method is only useful to test kernel + // support ahead of time. + static bool SupportsNewUserNS(); + // Move the current process to a new "user namespace" as supported by Linux // 3.8+ (CLONE_NEWUSER). // The uid map will be set-up so that the perceived uid and gid will not diff --git a/chromium/sandbox/linux/services/credentials_unittest.cc b/chromium/sandbox/linux/services/credentials_unittest.cc index 9160bf7a1ca..12ef32006ab 100644 --- a/chromium/sandbox/linux/services/credentials_unittest.cc +++ b/chromium/sandbox/linux/services/credentials_unittest.cc @@ -12,13 +12,12 @@ #include <unistd.h> #include "base/file_util.h" +#include "base/files/scoped_file.h" #include "base/logging.h" #include "base/memory/scoped_ptr.h" #include "sandbox/linux/tests/unit_tests.h" #include "testing/gtest/include/gtest/gtest.h" -using file_util::ScopedFD; - namespace sandbox { namespace { @@ -58,6 +57,18 @@ TEST(Credentials, CreateAndDestroy) { scoped_ptr<Credentials> cred2(new Credentials); } +TEST(Credentials, CountOpenFds) { + base::ScopedFD proc_fd(open("/proc", O_RDONLY | O_DIRECTORY)); + ASSERT_TRUE(proc_fd.is_valid()); + Credentials creds; + int fd_count = creds.CountOpenFds(proc_fd.get()); + int fd = open("/dev/null", O_RDONLY); + ASSERT_LE(0, fd); + EXPECT_EQ(fd_count + 1, creds.CountOpenFds(proc_fd.get())); + ASSERT_EQ(0, IGNORE_EINTR(close(fd))); + EXPECT_EQ(fd_count, creds.CountOpenFds(proc_fd.get())); +} + TEST(Credentials, HasOpenDirectory) { Credentials creds; // No open directory should exist at startup. @@ -65,7 +76,7 @@ TEST(Credentials, HasOpenDirectory) { { // Have a "/dev" file descriptor around. int dev_fd = open("/dev", O_RDONLY | O_DIRECTORY); - ScopedFD dev_fd_closer(&dev_fd); + base::ScopedFD dev_fd_closer(dev_fd); EXPECT_TRUE(creds.HasOpenDirectory(-1)); } EXPECT_FALSE(creds.HasOpenDirectory(-1)); @@ -75,7 +86,7 @@ TEST(Credentials, HasOpenDirectoryWithFD) { Credentials creds; int proc_fd = open("/proc", O_RDONLY | O_DIRECTORY); - ScopedFD proc_fd_closer(&proc_fd); + base::ScopedFD proc_fd_closer(proc_fd); ASSERT_LE(0, proc_fd); // Don't pass |proc_fd|, an open directory (proc_fd) should @@ -87,7 +98,7 @@ TEST(Credentials, HasOpenDirectoryWithFD) { { // Have a "/dev" file descriptor around. int dev_fd = open("/dev", O_RDONLY | O_DIRECTORY); - ScopedFD dev_fd_closer(&dev_fd); + base::ScopedFD dev_fd_closer(dev_fd); EXPECT_TRUE(creds.HasOpenDirectory(proc_fd)); } @@ -112,11 +123,12 @@ SANDBOX_TEST(Credentials, GetCurrentCapString) { SANDBOX_TEST(Credentials, MoveToNewUserNS) { Credentials creds; creds.DropAllCapabilities(); - bool userns_supported = creds.MoveToNewUserNS(); - fprintf(stdout, "Unprivileged CLONE_NEWUSER supported: %s\n", - userns_supported ? "true." : "false."); + bool moved_to_new_ns = creds.MoveToNewUserNS(); + fprintf(stdout, + "Unprivileged CLONE_NEWUSER supported: %s\n", + moved_to_new_ns ? "true." : "false."); fflush(stdout); - if (!userns_supported) { + if (!moved_to_new_ns) { fprintf(stdout, "This kernel does not support unprivileged namespaces. " "USERNS tests will succeed without running.\n"); fflush(stdout); @@ -127,6 +139,14 @@ SANDBOX_TEST(Credentials, MoveToNewUserNS) { CHECK(!creds.HasAnyCapability()); } +SANDBOX_TEST(Credentials, SupportsUserNS) { + Credentials creds; + creds.DropAllCapabilities(); + bool user_ns_supported = Credentials::SupportsNewUserNS(); + bool moved_to_new_ns = creds.MoveToNewUserNS(); + CHECK_EQ(user_ns_supported, moved_to_new_ns); +} + SANDBOX_TEST(Credentials, UidIsPreserved) { Credentials creds; creds.DropAllCapabilities(); @@ -182,7 +202,7 @@ TEST(Credentials, CanDetectRoot) { ASSERT_TRUE(WorkingDirectoryIsRoot()); } -SANDBOX_TEST(Credentials, DropFileSystemAccessIsSafe) { +SANDBOX_TEST(Credentials, DISABLE_ON_LSAN(DropFileSystemAccessIsSafe)) { Credentials creds; CHECK(creds.DropAllCapabilities()); // Probably missing kernel support. @@ -197,7 +217,7 @@ SANDBOX_TEST(Credentials, DropFileSystemAccessIsSafe) { // Check that after dropping filesystem access and dropping privileges // it is not possible to regain capabilities. -SANDBOX_TEST(Credentials, CannotRegainPrivileges) { +SANDBOX_TEST(Credentials, DISABLE_ON_LSAN(CannotRegainPrivileges)) { Credentials creds; CHECK(creds.DropAllCapabilities()); // Probably missing kernel support. @@ -207,6 +227,7 @@ SANDBOX_TEST(Credentials, CannotRegainPrivileges) { // The kernel should now prevent us from regaining capabilities because we // are in a chroot. + CHECK(!Credentials::SupportsNewUserNS()); CHECK(!creds.MoveToNewUserNS()); } diff --git a/chromium/sandbox/linux/services/init_process_reaper.h b/chromium/sandbox/linux/services/init_process_reaper.h index 531d18cba11..840f6fcda7b 100644 --- a/chromium/sandbox/linux/services/init_process_reaper.h +++ b/chromium/sandbox/linux/services/init_process_reaper.h @@ -6,6 +6,7 @@ #define SANDBOX_LINUX_SERVICES_INIT_PROCESS_REAPER_H_ #include "base/callback_forward.h" +#include "sandbox/sandbox_export.h" namespace sandbox { @@ -16,7 +17,8 @@ namespace sandbox { // immediately after fork(). // Since this function calls fork(), it's very important that the caller has // only one thread running. -bool CreateInitProcessReaper(base::Closure* post_fork_parent_callback); +SANDBOX_EXPORT bool CreateInitProcessReaper( + base::Closure* post_fork_parent_callback); } // namespace sandbox. diff --git a/chromium/sandbox/linux/services/libc_urandom_override.cc b/chromium/sandbox/linux/services/libc_urandom_override.cc index c5c49bac8ca..33bb25d6b17 100644 --- a/chromium/sandbox/linux/services/libc_urandom_override.cc +++ b/chromium/sandbox/linux/services/libc_urandom_override.cc @@ -25,9 +25,9 @@ namespace sandbox { static bool g_override_urandom = false; // TODO(sergeyu): Currently InitLibcUrandomOverrides() doesn't work properly -// under ASAN - it crashes content_unittests. Make sure it works properly and -// enable it here. http://crbug.com/123263 -#if !defined(ADDRESS_SANITIZER) +// under ASan or MSan - it crashes content_unittests. Make sure it works +// properly and enable it here. http://crbug.com/123263 +#if !(defined(ADDRESS_SANITIZER) || defined(MEMORY_SANITIZER)) static void InitLibcFileIOFunctions(); static pthread_once_t g_libc_file_io_funcs_guard = PTHREAD_ONCE_INIT; #endif @@ -37,13 +37,13 @@ void InitLibcUrandomOverrides() { base::GetUrandomFD(); g_override_urandom = true; -#if !defined(ADDRESS_SANITIZER) +#if !(defined(ADDRESS_SANITIZER) || defined(MEMORY_SANITIZER)) CHECK_EQ(0, pthread_once(&g_libc_file_io_funcs_guard, InitLibcFileIOFunctions)); #endif } -#if !defined(ADDRESS_SANITIZER) +#if !(defined(ADDRESS_SANITIZER) || defined(MEMORY_SANITIZER)) static const char kUrandomDevPath[] = "/dev/urandom"; @@ -231,6 +231,6 @@ int stat64_override(const char *path, struct stat64 *buf) { #endif // HAVE_XSTAT -#endif // !defined(ADDRESS_SANITIZER) +#endif // !(defined(ADDRESS_SANITIZER) || defined(MEMORY_SANITIZER)) } // namespace content diff --git a/chromium/sandbox/linux/services/scoped_process.cc b/chromium/sandbox/linux/services/scoped_process.cc new file mode 100644 index 00000000000..fd42a2a6e17 --- /dev/null +++ b/chromium/sandbox/linux/services/scoped_process.cc @@ -0,0 +1,119 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "sandbox/linux/services/scoped_process.h" + +#include <fcntl.h> +#include <signal.h> +#include <sys/stat.h> +#include <sys/syscall.h> +#include <sys/types.h> +#include <sys/wait.h> +#include <unistd.h> + +#include "base/basictypes.h" +#include "base/callback.h" +#include "base/logging.h" +#include "base/posix/eintr_wrapper.h" +#include "build/build_config.h" +#include "sandbox/linux/services/thread_helpers.h" + +namespace sandbox { + +namespace { + +const char kSynchronisationChar[] = "D"; + +void WaitForever() { + while(true) { + pause(); + } +} + +} // namespace + +ScopedProcess::ScopedProcess(const base::Closure& child_callback) + : child_process_id_(-1), process_id_(getpid()) { + PCHECK(0 == pipe(pipe_fds_)); +#if !defined(THREAD_SANITIZER) + // Make sure that we can safely fork(). + CHECK(ThreadHelpers::IsSingleThreaded(-1)); +#endif + child_process_id_ = fork(); + PCHECK(0 <= child_process_id_); + + if (0 == child_process_id_) { + PCHECK(0 == IGNORE_EINTR(close(pipe_fds_[0]))); + pipe_fds_[0] = -1; + child_callback.Run(); + // Notify the parent that the closure has run. + CHECK_EQ(1, HANDLE_EINTR(write(pipe_fds_[1], kSynchronisationChar, 1))); + WaitForever(); + NOTREACHED(); + _exit(1); + } + + PCHECK(0 == IGNORE_EINTR(close(pipe_fds_[1]))); + pipe_fds_[1] = -1; +} + +ScopedProcess::~ScopedProcess() { + CHECK(IsOriginalProcess()); + if (child_process_id_ >= 0) { + PCHECK(0 == kill(child_process_id_, SIGKILL)); + siginfo_t process_info; + + PCHECK(0 == HANDLE_EINTR( + waitid(P_PID, child_process_id_, &process_info, WEXITED))); + } + if (pipe_fds_[0] >= 0) { + PCHECK(0 == IGNORE_EINTR(close(pipe_fds_[0]))); + } + if (pipe_fds_[1] >= 0) { + PCHECK(0 == IGNORE_EINTR(close(pipe_fds_[1]))); + } +} + +int ScopedProcess::WaitForExit(bool* got_signaled) { + DCHECK(got_signaled); + CHECK(IsOriginalProcess()); + siginfo_t process_info; + // WNOWAIT to make sure that the destructor can wait on the child. + int ret = HANDLE_EINTR( + waitid(P_PID, child_process_id_, &process_info, WEXITED | WNOWAIT)); + PCHECK(0 == ret) << "Did something else wait on the child?"; + + if (process_info.si_code == CLD_EXITED) { + *got_signaled = false; + } else if (process_info.si_code == CLD_KILLED || + process_info.si_code == CLD_DUMPED) { + *got_signaled = true; + } else { + CHECK(false) << "ScopedProcess needs to be extended for si_code " + << process_info.si_code; + } + return process_info.si_status; +} + +bool ScopedProcess::WaitForClosureToRun() { + char c = 0; + int ret = HANDLE_EINTR(read(pipe_fds_[0], &c, 1)); + PCHECK(ret >= 0); + if (0 == ret) + return false; + + CHECK_EQ(c, kSynchronisationChar[0]); + return true; +} + +// It would be problematic if after a fork(), another process would start using +// this object. +// This method allows to assert it is not happening. +bool ScopedProcess::IsOriginalProcess() { + // Make a direct syscall to bypass glibc caching of PIDs. + int pid = syscall(__NR_getpid); + return pid == process_id_; +} + +} // namespace sandbox diff --git a/chromium/sandbox/linux/services/scoped_process.h b/chromium/sandbox/linux/services/scoped_process.h new file mode 100644 index 00000000000..a28131ee67d --- /dev/null +++ b/chromium/sandbox/linux/services/scoped_process.h @@ -0,0 +1,55 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef SANDBOX_LINUX_SERVICES_SCOPED_PROCESS_H_ +#define SANDBOX_LINUX_SERVICES_SCOPED_PROCESS_H_ + +#include "base/basictypes.h" +#include "base/callback_forward.h" +#include "base/process/process_handle.h" +#include "sandbox/sandbox_export.h" + +namespace sandbox { + +// fork() a child process that will run a Closure. +// After the Closure has run, the child will pause forever. If this object +// is detroyed, the child will be destroyed, even if the closure did not +// finish running. It's ok to signal the child from outside of this class to +// destroy it. +// This class cannot be instanciated from a multi-threaded process, as it needs +// to fork(). +class SANDBOX_EXPORT ScopedProcess { + public: + // A new process will be created and |child_callback| will run in the child + // process. This callback is allowed to terminate the process or to simply + // return. If the callback returns, the process will wait forever. + explicit ScopedProcess(const base::Closure& child_callback); + ~ScopedProcess(); + + // Wait for the process to exit. + // |got_signaled| tells how to interpret the return value: either as an exit + // code, or as a signal number. + // When this returns, the process will still not have been reaped and will + // survive as a zombie for the lifetime of this object. This method can be + // called multiple times. + int WaitForExit(bool* got_signaled); + + // Wait for the |child_callback| passed at construction to run. Return false + // if |child_callback| did not finish running and we know it never will (for + // instance the child crashed or used _exit()). + bool WaitForClosureToRun(); + base::ProcessId GetPid() { return child_process_id_; } + + private: + bool IsOriginalProcess(); + + base::ProcessId child_process_id_; + base::ProcessId process_id_; + int pipe_fds_[2]; + DISALLOW_COPY_AND_ASSIGN(ScopedProcess); +}; + +} // namespace sandbox + +#endif // SANDBOX_LINUX_SERVICES_SCOPED_PROCESS_H_ diff --git a/chromium/sandbox/linux/services/scoped_process_unittest.cc b/chromium/sandbox/linux/services/scoped_process_unittest.cc new file mode 100644 index 00000000000..382c7fb2cb5 --- /dev/null +++ b/chromium/sandbox/linux/services/scoped_process_unittest.cc @@ -0,0 +1,130 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include <errno.h> +#include <fcntl.h> +#include <sys/stat.h> +#include <sys/types.h> +#include <sys/wait.h> +#include <unistd.h> + +#include "base/basictypes.h" +#include "base/bind.h" +#include "base/callback.h" +#include "base/file_util.h" +#include "base/files/scoped_file.h" +#include "base/logging.h" +#include "base/posix/eintr_wrapper.h" +#include "base/threading/platform_thread.h" +#include "base/time/time.h" +#include "sandbox/linux/services/scoped_process.h" +#include "sandbox/linux/tests/unit_tests.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace sandbox { + +namespace { + +void DoExit() { _exit(0); } + +void ExitWithCode(int exit_code) { _exit(exit_code); } + +void RaiseAndExit(int signal) { + PCHECK(0 == raise(signal)); + _exit(0); +} + +void DoNothing() {} + +TEST(ScopedProcess, ScopedProcessNormalExit) { + const int kCustomExitCode = 12; + ScopedProcess process(base::Bind(&ExitWithCode, kCustomExitCode)); + bool got_signaled = true; + int exit_code = process.WaitForExit(&got_signaled); + EXPECT_FALSE(got_signaled); + EXPECT_EQ(kCustomExitCode, exit_code); + + // Verify that WaitForExit() can be called multiple times on the same + // process. + bool got_signaled2 = true; + int exit_code2 = process.WaitForExit(&got_signaled2); + EXPECT_FALSE(got_signaled2); + EXPECT_EQ(kCustomExitCode, exit_code2); +} + +// Disable this test on Android, SIGABRT is funky there. +TEST(ScopedProcess, DISABLE_ON_ANDROID(ScopedProcessAbort)) { + PCHECK(SIG_ERR != signal(SIGABRT, SIG_DFL)); + ScopedProcess process(base::Bind(&RaiseAndExit, SIGABRT)); + bool got_signaled = false; + int exit_code = process.WaitForExit(&got_signaled); + EXPECT_TRUE(got_signaled); + EXPECT_EQ(SIGABRT, exit_code); +} + +TEST(ScopedProcess, ScopedProcessSignaled) { + ScopedProcess process(base::Bind(&DoNothing)); + bool got_signaled = false; + ASSERT_EQ(0, kill(process.GetPid(), SIGKILL)); + int exit_code = process.WaitForExit(&got_signaled); + EXPECT_TRUE(got_signaled); + EXPECT_EQ(SIGKILL, exit_code); +} + +TEST(ScopedProcess, DiesForReal) { + int pipe_fds[2]; + ASSERT_EQ(0, pipe(pipe_fds)); + base::ScopedFD read_end_closer(pipe_fds[0]); + base::ScopedFD write_end_closer(pipe_fds[1]); + + { ScopedProcess process(base::Bind(&DoExit)); } + + // Close writing end of the pipe. + write_end_closer.reset(); + pipe_fds[1] = -1; + + ASSERT_EQ(0, fcntl(pipe_fds[0], F_SETFL, O_NONBLOCK)); + char c; + // If the child process is dead for real, there will be no writing end + // for this pipe left and read will EOF instead of returning EWOULDBLOCK. + ASSERT_EQ(0, read(pipe_fds[0], &c, 1)); +} + +TEST(ScopedProcess, SynchronizationBasic) { + ScopedProcess process1(base::Bind(&DoNothing)); + EXPECT_TRUE(process1.WaitForClosureToRun()); + + ScopedProcess process2(base::Bind(&DoExit)); + // The closure didn't finish running normally. This case is simple enough + // that process.WaitForClosureToRun() should return false, even though the + // API does not guarantees that it will return at all. + EXPECT_FALSE(process2.WaitForClosureToRun()); +} + +void SleepInMsAndWriteOneByte(int time_to_sleep, int fd) { + base::PlatformThread::Sleep(base::TimeDelta::FromMilliseconds(time_to_sleep)); + CHECK(1 == write(fd, "1", 1)); +} + +TEST(ScopedProcess, SynchronizationWorks) { + int pipe_fds[2]; + ASSERT_EQ(0, pipe(pipe_fds)); + base::ScopedFD read_end_closer(pipe_fds[0]); + base::ScopedFD write_end_closer(pipe_fds[1]); + + // Start a process with a closure that takes a little bit to run. + ScopedProcess process( + base::Bind(&SleepInMsAndWriteOneByte, 100, pipe_fds[1])); + EXPECT_TRUE(process.WaitForClosureToRun()); + + // Verify that the closure did, indeed, run. + ASSERT_EQ(0, fcntl(pipe_fds[0], F_SETFL, O_NONBLOCK)); + char c = 0; + EXPECT_EQ(1, read(pipe_fds[0], &c, 1)); + EXPECT_EQ('1', c); +} + +} // namespace + +} // namespace sandbox diff --git a/chromium/sandbox/linux/services/thread_helpers.cc b/chromium/sandbox/linux/services/thread_helpers.cc index e0794f84a44..e820449cb70 100644 --- a/chromium/sandbox/linux/services/thread_helpers.cc +++ b/chromium/sandbox/linux/services/thread_helpers.cc @@ -5,6 +5,7 @@ #include "sandbox/linux/services/thread_helpers.h" #include <errno.h> +#include <fcntl.h> #include <signal.h> #include <sys/types.h> #include <sys/stat.h> @@ -21,7 +22,9 @@ namespace sandbox { -bool ThreadHelpers::IsSingleThreaded(int proc_self_task) { +namespace { + +bool IsSingleThreadedImpl(int proc_self_task) { CHECK_LE(0, proc_self_task); struct stat task_stat; int fstat_ret = fstat(proc_self_task, &task_stat); @@ -35,6 +38,21 @@ bool ThreadHelpers::IsSingleThreaded(int proc_self_task) { return task_stat.st_nlink == 3; } +} // namespace + +bool ThreadHelpers::IsSingleThreaded(int proc_self_task) { + DCHECK_LE(-1, proc_self_task); + if (-1 == proc_self_task) { + const int task_fd = open("/proc/self/task/", O_RDONLY | O_DIRECTORY); + PCHECK(0 <= task_fd); + const bool result = IsSingleThreadedImpl(task_fd); + PCHECK(0 == IGNORE_EINTR(close(task_fd))); + return result; + } else { + return IsSingleThreadedImpl(proc_self_task); + } +} + bool ThreadHelpers::StopThreadAndWatchProcFS(int proc_self_task, base::Thread* thread) { DCHECK_LE(0, proc_self_task); diff --git a/chromium/sandbox/linux/services/thread_helpers.h b/chromium/sandbox/linux/services/thread_helpers.h index 651e5d9c501..377742a539a 100644 --- a/chromium/sandbox/linux/services/thread_helpers.h +++ b/chromium/sandbox/linux/services/thread_helpers.h @@ -6,16 +6,19 @@ #define SANDBOX_LINUX_SERVICES_THREAD_HELPERS_H_ #include "base/basictypes.h" +#include "sandbox/sandbox_export.h" namespace base { class Thread; } namespace sandbox { -class ThreadHelpers { +class SANDBOX_EXPORT ThreadHelpers { public: // Check whether the current process is single threaded. |proc_self_tasks| - // should be a file descriptor to /proc/self/task/ and remains owned by the - // caller. + // can be a file descriptor to /proc/self/task/ and remains owned by the + // caller or -1. + // If |proc_self_tasks| is -1, this method will open /proc/self/task/ and + // crash if it cannot. static bool IsSingleThreaded(int proc_self_task); // Stop |thread| and ensure that it does not have an entry in @@ -28,6 +31,6 @@ class ThreadHelpers { DISALLOW_IMPLICIT_CONSTRUCTORS(ThreadHelpers); }; -} // namespace content +} // namespace sandbox #endif // SANDBOX_LINUX_SERVICES_THREAD_HELPERS_H_ diff --git a/chromium/sandbox/linux/services/thread_helpers_unittests.cc b/chromium/sandbox/linux/services/thread_helpers_unittests.cc index 725a62ef766..a36fd2975f8 100644 --- a/chromium/sandbox/linux/services/thread_helpers_unittests.cc +++ b/chromium/sandbox/linux/services/thread_helpers_unittests.cc @@ -17,6 +17,7 @@ #include "base/process/process_metrics.h" #include "base/threading/platform_thread.h" #include "base/threading/thread.h" +#include "build/build_config.h" #include "sandbox/linux/tests/unit_tests.h" #include "testing/gtest/include/gtest/gtest.h" @@ -50,16 +51,31 @@ class ScopedProcSelfTask { DISALLOW_COPY_AND_ASSIGN(ScopedProcSelfTask); }; -TEST(ThreadHelpers, IsSingleThreadedBasic) { +#if defined(THREAD_SANITIZER) +// These tests fail under ThreadSanitizer, see http://crbug.com/342305 +#define MAYBE_IsSingleThreadedBasic DISABLED_IsSingleThreadedBasic +#define MAYBE_IsSingleThreadedIterated DISABLED_IsSingleThreadedIterated +#define MAYBE_IsSingleThreadedStartAndStop DISABLED_IsSingleThreadedStartAndStop +#else +#define MAYBE_IsSingleThreadedBasic IsSingleThreadedBasic +#define MAYBE_IsSingleThreadedIterated IsSingleThreadedIterated +#define MAYBE_IsSingleThreadedStartAndStop IsSingleThreadedStartAndStop +#endif + +TEST(ThreadHelpers, MAYBE_IsSingleThreadedBasic) { ScopedProcSelfTask task; ASSERT_TRUE(ThreadHelpers::IsSingleThreaded(task.fd())); + ASSERT_TRUE(ThreadHelpers::IsSingleThreaded(-1)); base::Thread thread("sandbox_tests"); ASSERT_TRUE(thread.Start()); ASSERT_FALSE(ThreadHelpers::IsSingleThreaded(task.fd())); + ASSERT_FALSE(ThreadHelpers::IsSingleThreaded(-1)); + // Explicitly stop the thread here to not pollute the next test. + ASSERT_TRUE(ThreadHelpers::StopThreadAndWatchProcFS(task.fd(), &thread)); } -TEST(ThreadHelpers, IsSingleThreadedIterated) { +TEST(ThreadHelpers, MAYBE_IsSingleThreadedIterated) { ScopedProcSelfTask task; ASSERT_TRUE(ThreadHelpers::IsSingleThreaded(task.fd())); @@ -68,10 +84,12 @@ TEST(ThreadHelpers, IsSingleThreadedIterated) { base::Thread thread("sandbox_tests"); ASSERT_TRUE(thread.Start()); ASSERT_FALSE(ThreadHelpers::IsSingleThreaded(task.fd())); + // Explicitly stop the thread here to not pollute the next test. + ASSERT_TRUE(ThreadHelpers::StopThreadAndWatchProcFS(task.fd(), &thread)); } } -TEST(ThreadHelpers, IsSingleThreadedStartAndStop) { +TEST(ThreadHelpers, MAYBE_IsSingleThreadedStartAndStop) { ScopedProcSelfTask task; ASSERT_TRUE(ThreadHelpers::IsSingleThreaded(task.fd())); diff --git a/chromium/sandbox/linux/services/unix_domain_socket_unittest.cc b/chromium/sandbox/linux/services/unix_domain_socket_unittest.cc new file mode 100644 index 00000000000..17208a829dd --- /dev/null +++ b/chromium/sandbox/linux/services/unix_domain_socket_unittest.cc @@ -0,0 +1,267 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include <sched.h> +#include <stdio.h> +#include <string.h> +#include <sys/socket.h> +#include <sys/syscall.h> +#include <sys/wait.h> +#include <unistd.h> + +#include <vector> + +#include "base/files/scoped_file.h" +#include "base/logging.h" +#include "base/memory/scoped_vector.h" +#include "base/posix/eintr_wrapper.h" +#include "base/posix/unix_domain_socket_linux.h" +#include "base/process/process_handle.h" +#include "sandbox/linux/tests/unit_tests.h" + +// Additional tests for base's UnixDomainSocket to make sure it behaves +// correctly in the presence of sandboxing functionality (e.g., receiving +// PIDs across namespaces). + +namespace sandbox { + +namespace { + +const char kHello[] = "hello"; + +// If the calling process isn't root, then try using unshare(CLONE_NEWUSER) +// to fake it. +void FakeRoot() { + // If we're already root, then allow test to proceed. + if (geteuid() == 0) + return; + + // Otherwise hope the kernel supports unprivileged namespaces. + if (unshare(CLONE_NEWUSER) == 0) + return; + + printf("Permission to use CLONE_NEWPID missing; skipping test.\n"); + UnitTests::IgnoreThisTest(); +} + +void WaitForExit(pid_t pid) { + int status; + CHECK_EQ(pid, HANDLE_EINTR(waitpid(pid, &status, 0))); + CHECK(WIFEXITED(status)); + CHECK_EQ(0, WEXITSTATUS(status)); +} + +base::ProcessId GetParentProcessId(base::ProcessId pid) { + // base::GetParentProcessId() is defined as taking a ProcessHandle instead of + // a ProcessId, even though it's a POSIX-only function and IDs and Handles + // are both simply pid_t on POSIX... :/ + base::ProcessHandle handle; + CHECK(base::OpenProcessHandle(pid, &handle)); + base::ProcessId ret = base::GetParentProcessId(pid); + base::CloseProcessHandle(handle); + return ret; +} + +// SendHello sends a "hello" to socket fd, and then blocks until the recipient +// acknowledges it by calling RecvHello. +void SendHello(int fd) { + int pipe_fds[2]; + CHECK_EQ(0, pipe(pipe_fds)); + base::ScopedFD read_pipe(pipe_fds[0]); + base::ScopedFD write_pipe(pipe_fds[1]); + + std::vector<int> send_fds; + send_fds.push_back(write_pipe.get()); + CHECK(UnixDomainSocket::SendMsg(fd, kHello, sizeof(kHello), send_fds)); + + write_pipe.reset(); + + // Block until receiver closes their end of the pipe. + char ch; + CHECK_EQ(0, HANDLE_EINTR(read(read_pipe.get(), &ch, 1))); +} + +// RecvHello receives and acknowledges a "hello" on socket fd, and returns the +// process ID of the sender in sender_pid. Optionally, write_pipe can be used +// to return a file descriptor, and the acknowledgement will be delayed until +// the descriptor is closed. +// (Implementation details: SendHello allocates a new pipe, sends us the writing +// end alongside the "hello" message, and then blocks until we close the writing +// end of the pipe.) +void RecvHello(int fd, + base::ProcessId* sender_pid, + base::ScopedFD* write_pipe = NULL) { + // Extra receiving buffer space to make sure we really received only + // sizeof(kHello) bytes and it wasn't just truncated to fit the buffer. + char buf[sizeof(kHello) + 1]; + ScopedVector<base::ScopedFD> message_fds; + ssize_t n = UnixDomainSocket::RecvMsgWithPid( + fd, buf, sizeof(buf), &message_fds, sender_pid); + CHECK_EQ(sizeof(kHello), static_cast<size_t>(n)); + CHECK_EQ(0, memcmp(buf, kHello, sizeof(kHello))); + CHECK_EQ(1U, message_fds.size()); + if (write_pipe) + write_pipe->swap(*message_fds[0]); +} + +// Check that receiving PIDs works across a fork(). +SANDBOX_TEST(UnixDomainSocketTest, Fork) { + int fds[2]; + CHECK_EQ(0, socketpair(AF_UNIX, SOCK_SEQPACKET, 0, fds)); + base::ScopedFD recv_sock(fds[0]); + base::ScopedFD send_sock(fds[1]); + + CHECK(UnixDomainSocket::EnableReceiveProcessId(recv_sock.get())); + + const pid_t pid = fork(); + CHECK_NE(-1, pid); + if (pid == 0) { + // Child process. + recv_sock.reset(); + SendHello(send_sock.get()); + _exit(0); + } + + // Parent process. + send_sock.reset(); + + base::ProcessId sender_pid; + RecvHello(recv_sock.get(), &sender_pid); + CHECK_EQ(pid, sender_pid); + + WaitForExit(pid); +} + +// Similar to Fork above, but forking the child into a new pid namespace. +SANDBOX_TEST(UnixDomainSocketTest, Namespace) { + FakeRoot(); + + int fds[2]; + CHECK_EQ(0, socketpair(AF_UNIX, SOCK_SEQPACKET, 0, fds)); + base::ScopedFD recv_sock(fds[0]); + base::ScopedFD send_sock(fds[1]); + + CHECK(UnixDomainSocket::EnableReceiveProcessId(recv_sock.get())); + + const pid_t pid = syscall(__NR_clone, CLONE_NEWPID | SIGCHLD, 0, 0, 0); + CHECK_NE(-1, pid); + if (pid == 0) { + // Child process. + recv_sock.reset(); + + // Check that we think we're pid 1 in our new namespace. + CHECK_EQ(1, syscall(__NR_getpid)); + + SendHello(send_sock.get()); + _exit(0); + } + + // Parent process. + send_sock.reset(); + + base::ProcessId sender_pid; + RecvHello(recv_sock.get(), &sender_pid); + CHECK_EQ(pid, sender_pid); + + WaitForExit(pid); +} + +// Again similar to Fork, but now with nested PID namespaces. +SANDBOX_TEST(UnixDomainSocketTest, DoubleNamespace) { + FakeRoot(); + + int fds[2]; + CHECK_EQ(0, socketpair(AF_UNIX, SOCK_SEQPACKET, 0, fds)); + base::ScopedFD recv_sock(fds[0]); + base::ScopedFD send_sock(fds[1]); + + CHECK(UnixDomainSocket::EnableReceiveProcessId(recv_sock.get())); + + const pid_t pid = syscall(__NR_clone, CLONE_NEWPID | SIGCHLD, 0, 0, 0); + CHECK_NE(-1, pid); + if (pid == 0) { + // Child process. + recv_sock.reset(); + + const pid_t pid2 = syscall(__NR_clone, CLONE_NEWPID | SIGCHLD, 0, 0, 0); + CHECK_NE(-1, pid2); + + if (pid2 != 0) { + // Wait for grandchild to run to completion; see comments below. + WaitForExit(pid2); + + // Fallthrough once grandchild has sent its hello and exited. + } + + // Check that we think we're pid 1. + CHECK_EQ(1, syscall(__NR_getpid)); + + SendHello(send_sock.get()); + _exit(0); + } + + // Parent process. + send_sock.reset(); + + // We have two messages to receive: first from the grand-child, + // then from the child. + for (unsigned iteration = 0; iteration < 2; ++iteration) { + base::ProcessId sender_pid; + base::ScopedFD pipe_fd; + RecvHello(recv_sock.get(), &sender_pid, &pipe_fd); + + // We need our child and grandchild processes to both be alive for + // GetParentProcessId() to return a valid pid, hence the pipe trickery. + // (On the first iteration, grandchild is blocked reading from the pipe + // until we close it, and child is blocked waiting for grandchild to exit.) + switch (iteration) { + case 0: // Grandchild's message + // Check that sender_pid refers to our grandchild by checking that pid + // (our child) is its parent. + CHECK_EQ(pid, GetParentProcessId(sender_pid)); + break; + case 1: // Child's message + CHECK_EQ(pid, sender_pid); + break; + default: + NOTREACHED(); + } + } + + WaitForExit(pid); +} + +// Tests that GetPeerPid() returns 0 if the peer does not exist in caller's +// namespace. +SANDBOX_TEST(UnixDomainSocketTest, ImpossiblePid) { + FakeRoot(); + + int fds[2]; + CHECK_EQ(0, socketpair(AF_UNIX, SOCK_SEQPACKET, 0, fds)); + base::ScopedFD send_sock(fds[0]); + base::ScopedFD recv_sock(fds[1]); + + CHECK(UnixDomainSocket::EnableReceiveProcessId(recv_sock.get())); + + const pid_t pid = syscall(__NR_clone, CLONE_NEWPID | SIGCHLD, 0, 0, 0); + CHECK_NE(-1, pid); + if (pid == 0) { + // Child process. + send_sock.reset(); + + base::ProcessId sender_pid; + RecvHello(recv_sock.get(), &sender_pid); + CHECK_EQ(0, sender_pid); + _exit(0); + } + + // Parent process. + recv_sock.reset(); + SendHello(send_sock.get()); + WaitForExit(pid); +} + +} // namespace + +} // namespace sandbox diff --git a/chromium/sandbox/linux/services/yama.cc b/chromium/sandbox/linux/services/yama.cc new file mode 100644 index 00000000000..49e1b36aab3 --- /dev/null +++ b/chromium/sandbox/linux/services/yama.cc @@ -0,0 +1,116 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "sandbox/linux/services/yama.h" + +#include <fcntl.h> +#include <sys/prctl.h> +#include <sys/stat.h> +#include <sys/types.h> +#include <unistd.h> + +#include "base/basictypes.h" +#include "base/file_util.h" +#include "base/files/scoped_file.h" +#include "base/logging.h" +#include "base/posix/eintr_wrapper.h" + +#if !defined(PR_SET_PTRACER_ANY) +#define PR_SET_PTRACER_ANY ((unsigned long)-1) +#endif + +#if !defined(PR_SET_PTRACER) +#define PR_SET_PTRACER 0x59616d61 +#endif + +namespace sandbox { + +namespace { + +// Enable or disable the Yama ptracers restrictions. +// Return false if Yama is not present on this kernel. +bool SetYamaPtracersRestriction(bool enable_restrictions) { + unsigned long set_ptracer_arg; + if (enable_restrictions) { + set_ptracer_arg = 0; + } else { + set_ptracer_arg = PR_SET_PTRACER_ANY; + } + + const int ret = prctl(PR_SET_PTRACER, set_ptracer_arg); + const int prctl_errno = errno; + + if (0 == ret) { + return true; + } else { + // ENOSYS or EINVAL means Yama is not in the current kernel. + CHECK(ENOSYS == prctl_errno || EINVAL == prctl_errno); + return false; + } +} + +bool CanAccessProcFS() { + static const char kProcfsKernelSysPath[] = "/proc/sys/kernel/"; + int ret = access(kProcfsKernelSysPath, F_OK); + if (ret) { + return false; + } + return true; +} + +} // namespace + +// static +bool Yama::RestrictPtracersToAncestors() { + return SetYamaPtracersRestriction(true /* enable_restrictions */); +} + +// static +bool Yama::DisableYamaRestrictions() { + return SetYamaPtracersRestriction(false /* enable_restrictions */); +} + +// static +int Yama::GetStatus() { + if (!CanAccessProcFS()) { + return 0; + } + + static const char kPtraceScopePath[] = "/proc/sys/kernel/yama/ptrace_scope"; + + base::ScopedFD yama_scope(HANDLE_EINTR(open(kPtraceScopePath, O_RDONLY))); + + if (!yama_scope.is_valid()) { + const int open_errno = errno; + DCHECK(ENOENT == open_errno); + // The status is known, yama is not present. + return STATUS_KNOWN; + } + + char yama_scope_value = 0; + ssize_t num_read = HANDLE_EINTR(read(yama_scope.get(), &yama_scope_value, 1)); + PCHECK(1 == num_read); + + switch (yama_scope_value) { + case '0': + return STATUS_KNOWN | STATUS_PRESENT; + case '1': + return STATUS_KNOWN | STATUS_PRESENT | STATUS_ENFORCING; + case '2': + case '3': + return STATUS_KNOWN | STATUS_PRESENT | STATUS_ENFORCING | + STATUS_STRICT_ENFORCING; + default: + NOTREACHED(); + return 0; + } +} + +// static +bool Yama::IsPresent() { return GetStatus() & STATUS_PRESENT; } + +// static +bool Yama::IsEnforcing() { return GetStatus() & STATUS_ENFORCING; } + +} // namespace sandbox diff --git a/chromium/sandbox/linux/services/yama.h b/chromium/sandbox/linux/services/yama.h new file mode 100644 index 00000000000..20c28bae148 --- /dev/null +++ b/chromium/sandbox/linux/services/yama.h @@ -0,0 +1,58 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef SANDBOX_LINUX_SERVICES_YAMA_H_ +#define SANDBOX_LINUX_SERVICES_YAMA_H_ + +#include "base/basictypes.h" +#include "base/process/process_handle.h" +#include "sandbox/sandbox_export.h" + +namespace sandbox { + +// Yama is a LSM kernel module which can restrict ptrace(). +// This class provides ways to detect if Yama is present and enabled +// and to restrict which processes can ptrace the current process. +class SANDBOX_EXPORT Yama { + public: + // This enum should be used to set or check a bitmask. + // A value of 0 would indicate that the status is not known. + enum GlobalStatus { + STATUS_KNOWN = 1 << 0, + STATUS_PRESENT = 1 << 1, + STATUS_ENFORCING = 1 << 2, + // STATUS_STRICT_ENFORCING corresponds to either mode 2 or mode 3 of Yama. + // Ptrace could be entirely denied, or restricted to CAP_SYS_PTRACE + // and PTRACE_TRACEME. + STATUS_STRICT_ENFORCING = 1 << 3 + }; + + // Restrict who can ptrace() the current process to its ancestors. + // If this succeeds, then Yama is available on this kernel. + // However, Yama may not be enforcing at this time. + static bool RestrictPtracersToAncestors(); + + // Disable Yama restrictions for the current process. + // This will fail if Yama is not available on this kernel. + // This is meant for testing only. If you need this, implement + // a per-pid authorization instead. + static bool DisableYamaRestrictions(); + + // Checks if Yama is currently in enforcing mode for the machine (not the + // current process). This requires access to the filesystem and will use + // /proc/sys/kernel/yama/ptrace_scope. + static int GetStatus(); + + // Helper for checking for STATUS_PRESENT in GetStatus(). + static bool IsPresent(); + // Helper for checkking for STATUS_ENFORCING in GetStatus(). + static bool IsEnforcing(); + + private: + DISALLOW_IMPLICIT_CONSTRUCTORS(Yama); +}; + +} // namespace sandbox + +#endif // SANDBOX_LINUX_SERVICES_YAMA_H_ diff --git a/chromium/sandbox/linux/services/yama_unittests.cc b/chromium/sandbox/linux/services/yama_unittests.cc new file mode 100644 index 00000000000..17ef4b40de5 --- /dev/null +++ b/chromium/sandbox/linux/services/yama_unittests.cc @@ -0,0 +1,152 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include <errno.h> +#include <fcntl.h> +#include <sys/ptrace.h> +#include <sys/stat.h> +#include <sys/types.h> +#include <unistd.h> + +#include "base/bind.h" +#include "base/compiler_specific.h" +#include "base/posix/eintr_wrapper.h" +#include "sandbox/linux/services/scoped_process.h" +#include "sandbox/linux/services/yama.h" +#include "sandbox/linux/tests/unit_tests.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace sandbox { + +namespace { + +bool CanPtrace(pid_t pid) { + int ret; + ret = ptrace(PTRACE_ATTACH, pid, NULL, NULL); + if (ret == -1) { + CHECK_EQ(EPERM, errno); + return false; + } + // Wait for the process to be stopped so that it can be detached. + siginfo_t process_info; + int wait_ret = HANDLE_EINTR(waitid(P_PID, pid, &process_info, WSTOPPED)); + PCHECK(0 == wait_ret); + PCHECK(0 == ptrace(PTRACE_DETACH, pid, NULL, NULL)); + return true; +} + +// _exit(0) if pid can be ptraced by the current process. +// _exit(1) otherwise. +void ExitZeroIfCanPtrace(pid_t pid) { + if (CanPtrace(pid)) { + _exit(0); + } else { + _exit(1); + } +} + +bool CanSubProcessPtrace(pid_t pid) { + ScopedProcess process(base::Bind(&ExitZeroIfCanPtrace, pid)); + bool signaled; + int exit_code = process.WaitForExit(&signaled); + CHECK(!signaled); + return 0 == exit_code; +} + +// The tests below assume that the system-level configuration will not change +// while they run. + +TEST(Yama, GetStatus) { + int status1 = Yama::GetStatus(); + + // Check that the value is a possible bitmask. + ASSERT_LE(0, status1); + ASSERT_GE(Yama::STATUS_KNOWN | Yama::STATUS_PRESENT | Yama::STATUS_ENFORCING | + Yama::STATUS_STRICT_ENFORCING, + status1); + + // The status should not just be a random value. + int status2 = Yama::GetStatus(); + EXPECT_EQ(status1, status2); + + // This test is not running sandboxed, there is no reason to not know the + // status. + EXPECT_NE(0, Yama::STATUS_KNOWN & status1); + + if (status1 & Yama::STATUS_STRICT_ENFORCING) { + // If Yama is strictly enforcing, it is also enforcing. + EXPECT_TRUE(status1 & Yama::STATUS_ENFORCING); + } + + if (status1 & Yama::STATUS_ENFORCING) { + // If Yama is enforcing, Yama is present. + EXPECT_NE(0, status1 & Yama::STATUS_PRESENT); + } + + // Verify that the helper functions work as intended. + EXPECT_EQ(static_cast<bool>(status1 & Yama::STATUS_ENFORCING), + Yama::IsEnforcing()); + EXPECT_EQ(static_cast<bool>(status1 & Yama::STATUS_PRESENT), + Yama::IsPresent()); + + fprintf(stdout, + "Yama present: %s - enforcing: %s\n", + Yama::IsPresent() ? "Y" : "N", + Yama::IsEnforcing() ? "Y" : "N"); +} + +SANDBOX_TEST(Yama, RestrictPtraceSucceedsWhenYamaPresent) { + // This call will succeed iff Yama is present. + bool restricted = Yama::RestrictPtracersToAncestors(); + CHECK_EQ(restricted, Yama::IsPresent()); +} + +// Attempts to enable or disable Yama restrictions. +void SetYamaRestrictions(bool enable_restriction) { + if (enable_restriction) { + Yama::RestrictPtracersToAncestors(); + } else { + Yama::DisableYamaRestrictions(); + } +} + +TEST(Yama, RestrictPtraceWorks) { + ScopedProcess process1(base::Bind(&SetYamaRestrictions, true)); + ASSERT_TRUE(process1.WaitForClosureToRun()); + + if (Yama::IsEnforcing()) { + // A sibling process cannot ptrace process1. + ASSERT_FALSE(CanSubProcessPtrace(process1.GetPid())); + } + + if (!(Yama::GetStatus() & Yama::STATUS_STRICT_ENFORCING)) { + // However, parent can ptrace process1. + ASSERT_TRUE(CanPtrace(process1.GetPid())); + + // A sibling can ptrace process2 which disables any Yama protection. + ScopedProcess process2(base::Bind(&SetYamaRestrictions, false)); + ASSERT_TRUE(process2.WaitForClosureToRun()); + ASSERT_TRUE(CanSubProcessPtrace(process2.GetPid())); + } +} + +void DoNothing() {} + +SANDBOX_TEST(Yama, RestrictPtraceIsDefault) { + if (!Yama::IsPresent()) + return; + + CHECK(Yama::DisableYamaRestrictions()); + ScopedProcess process1(base::Bind(&DoNothing)); + + if (Yama::IsEnforcing()) { + // Check that process1 is protected by Yama, even though it has + // been created from a process that disabled Yama. + CHECK(!CanSubProcessPtrace(process1.GetPid())); + } +} + +} // namespace + +} // namespace sandbox |