summaryrefslogtreecommitdiffstats
path: root/chromium/sandbox/linux/services
diff options
context:
space:
mode:
authorJocelyn Turcotte <jocelyn.turcotte@digia.com>2014-08-08 14:30:41 +0200
committerJocelyn Turcotte <jocelyn.turcotte@digia.com>2014-08-12 13:49:54 +0200
commitab0a50979b9eb4dfa3320eff7e187e41efedf7a9 (patch)
tree498dfb8a97ff3361a9f7486863a52bb4e26bb898 /chromium/sandbox/linux/services
parent4ce69f7403811819800e7c5ae1318b2647e778d1 (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')
-rw-r--r--chromium/sandbox/linux/services/android_futex.h28
-rw-r--r--chromium/sandbox/linux/services/android_ucontext.h2
-rw-r--r--chromium/sandbox/linux/services/android_x86_64_ucontext.h88
-rw-r--r--chromium/sandbox/linux/services/broker_process.cc59
-rw-r--r--chromium/sandbox/linux/services/broker_process.h12
-rw-r--r--chromium/sandbox/linux/services/broker_process_unittest.cc131
-rw-r--r--chromium/sandbox/linux/services/credentials.cc89
-rw-r--r--chromium/sandbox/linux/services/credentials.h15
-rw-r--r--chromium/sandbox/linux/services/credentials_unittest.cc43
-rw-r--r--chromium/sandbox/linux/services/init_process_reaper.h4
-rw-r--r--chromium/sandbox/linux/services/libc_urandom_override.cc12
-rw-r--r--chromium/sandbox/linux/services/scoped_process.cc119
-rw-r--r--chromium/sandbox/linux/services/scoped_process.h55
-rw-r--r--chromium/sandbox/linux/services/scoped_process_unittest.cc130
-rw-r--r--chromium/sandbox/linux/services/thread_helpers.cc20
-rw-r--r--chromium/sandbox/linux/services/thread_helpers.h11
-rw-r--r--chromium/sandbox/linux/services/thread_helpers_unittests.cc24
-rw-r--r--chromium/sandbox/linux/services/unix_domain_socket_unittest.cc267
-rw-r--r--chromium/sandbox/linux/services/yama.cc116
-rw-r--r--chromium/sandbox/linux/services/yama.h58
-rw-r--r--chromium/sandbox/linux/services/yama_unittests.cc152
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