Merge from Chromium at DEPS revision 257591
This commit was generated by merge_to_master.py.
Change-Id: I0010df2ec3fbb5d4947cd026de2feb150ce7a6b5
diff --git a/sandbox/linux/sandbox_linux.gypi b/sandbox/linux/sandbox_linux.gypi
index 21c0401..59c61ff 100644
--- a/sandbox/linux/sandbox_linux.gypi
+++ b/sandbox/linux/sandbox_linux.gypi
@@ -181,8 +181,12 @@
'services/broker_process.h',
'services/init_process_reaper.cc',
'services/init_process_reaper.h',
+ 'services/scoped_process.cc',
+ 'services/scoped_process.h',
'services/thread_helpers.cc',
'services/thread_helpers.h',
+ 'services/yama.h',
+ 'services/yama.cc',
],
'dependencies': [
'../base/base.gyp:base',
diff --git a/sandbox/linux/sandbox_linux_test_sources.gypi b/sandbox/linux/sandbox_linux_test_sources.gypi
index 37e48f8..a48017b 100644
--- a/sandbox/linux/sandbox_linux_test_sources.gypi
+++ b/sandbox/linux/sandbox_linux_test_sources.gypi
@@ -15,10 +15,14 @@
],
'sources': [
'tests/main.cc',
+ 'tests/test_utils.cc',
+ 'tests/test_utils.h',
'tests/unit_tests.cc',
'tests/unit_tests.h',
'services/broker_process_unittest.cc',
+ 'services/scoped_process_unittest.cc',
'services/thread_helpers_unittests.cc',
+ 'services/yama_unittests.cc',
],
'conditions': [
[ 'compile_suid_client==1', {
diff --git a/sandbox/linux/seccomp-bpf/sandbox_bpf_unittest.cc b/sandbox/linux/seccomp-bpf/sandbox_bpf_unittest.cc
index 988e295..d05878c 100644
--- a/sandbox/linux/seccomp-bpf/sandbox_bpf_unittest.cc
+++ b/sandbox/linux/seccomp-bpf/sandbox_bpf_unittest.cc
@@ -20,6 +20,7 @@
#include <ostream>
+#include "base/bind.h"
#include "base/memory/scoped_ptr.h"
#include "build/build_config.h"
#include "sandbox/linux/seccomp-bpf/bpf_tests.h"
@@ -670,6 +671,8 @@
BPF_ASSERT(errno == 0);
}
+bool NoOpCallback() { return true; }
+
// Test a trap handler that makes use of a broker process to open().
class InitializedOpenBroker {
@@ -682,7 +685,7 @@
broker_process_.reset(
new BrokerProcess(EPERM, allowed_files, std::vector<std::string>()));
BPF_ASSERT(broker_process() != NULL);
- BPF_ASSERT(broker_process_->Init(NULL));
+ BPF_ASSERT(broker_process_->Init(base::Bind(&NoOpCallback)));
initialized_ = true;
}
diff --git a/sandbox/linux/services/broker_process.cc b/sandbox/linux/services/broker_process.cc
index 438e972..2956cf9 100644
--- a/sandbox/linux/services/broker_process.cc
+++ b/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,6 +18,7 @@
#include <vector>
#include "base/basictypes.h"
+#include "base/callback.h"
#include "base/compiler_specific.h"
#include "base/logging.h"
#include "base/pickle.h"
@@ -132,11 +135,19 @@
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 +158,9 @@
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 +186,7 @@
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();
diff --git a/sandbox/linux/services/broker_process.h b/sandbox/linux/services/broker_process.h
index 6b13b33..84de396 100644
--- a/sandbox/linux/services/broker_process.h
+++ b/sandbox/linux/services/broker_process.h
@@ -9,6 +9,7 @@
#include <vector>
#include "base/basictypes.h"
+#include "base/callback_forward.h"
#include "base/pickle.h"
#include "base/process/process.h"
@@ -42,9 +43,9 @@
~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
diff --git a/sandbox/linux/services/broker_process_unittest.cc b/sandbox/linux/services/broker_process_unittest.cc
index f163ef9..7f1a685 100644
--- a/sandbox/linux/services/broker_process_unittest.cc
+++ b/sandbox/linux/services/broker_process_unittest.cc
@@ -15,15 +15,16 @@
#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 "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 {
namespace {
@@ -60,13 +61,9 @@
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 +71,18 @@
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 +113,7 @@
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 +264,11 @@
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 +280,7 @@
// 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 +289,7 @@
// 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 +302,9 @@
// 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 +329,7 @@
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 +366,19 @@
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 +393,7 @@
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;
diff --git a/sandbox/linux/services/credentials.cc b/sandbox/linux/services/credentials.cc
index 4f041dc..8077c68 100644
--- a/sandbox/linux/services/credentials.cc
+++ b/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 @@
}
};
-// 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 @@
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 {
@@ -231,6 +248,37 @@
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 +289,14 @@
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/sandbox/linux/services/credentials.h b/sandbox/linux/services/credentials.h
index c23db93..48cf259 100644
--- a/sandbox/linux/services/credentials.h
+++ b/sandbox/linux/services/credentials.h
@@ -51,6 +51,13 @@
// 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/sandbox/linux/services/credentials_unittest.cc b/sandbox/linux/services/credentials_unittest.cc
index 9160bf7..a54ed04 100644
--- a/sandbox/linux/services/credentials_unittest.cc
+++ b/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 {
@@ -65,7 +64,7 @@
{
// 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 +74,7 @@
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 +86,7 @@
{
// 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 +111,12 @@
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 +127,14 @@
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();
@@ -207,6 +215,7 @@
// The kernel should now prevent us from regaining capabilities because we
// are in a chroot.
+ CHECK(!Credentials::SupportsNewUserNS());
CHECK(!creds.MoveToNewUserNS());
}
diff --git a/sandbox/linux/services/scoped_process.cc b/sandbox/linux/services/scoped_process.cc
new file mode 100644
index 0000000..6a03a69
--- /dev/null
+++ b/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, 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 = 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/sandbox/linux/services/scoped_process.h b/sandbox/linux/services/scoped_process.h
new file mode 100644
index 0000000..d9f8b25
--- /dev/null
+++ b/sandbox/linux/services/scoped_process.h
@@ -0,0 +1,54 @@
+// 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"
+
+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 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/sandbox/linux/services/scoped_process_unittest.cc b/sandbox/linux/services/scoped_process_unittest.cc
new file mode 100644
index 0000000..2800bd7
--- /dev/null
+++ b/sandbox/linux/services/scoped_process_unittest.cc
@@ -0,0 +1,129 @@
+// 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)) {
+ 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/sandbox/linux/services/thread_helpers.cc b/sandbox/linux/services/thread_helpers.cc
index e0794f8..e820449 100644
--- a/sandbox/linux/services/thread_helpers.cc
+++ b/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 @@
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/sandbox/linux/services/thread_helpers.h b/sandbox/linux/services/thread_helpers.h
index 651e5d9..f1b9327 100644
--- a/sandbox/linux/services/thread_helpers.h
+++ b/sandbox/linux/services/thread_helpers.h
@@ -14,8 +14,10 @@
class 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 +30,6 @@
DISALLOW_IMPLICIT_CONSTRUCTORS(ThreadHelpers);
};
-} // namespace content
+} // namespace sandbox
#endif // SANDBOX_LINUX_SERVICES_THREAD_HELPERS_H_
diff --git a/sandbox/linux/services/thread_helpers_unittests.cc b/sandbox/linux/services/thread_helpers_unittests.cc
index 991e60e..a36fd29 100644
--- a/sandbox/linux/services/thread_helpers_unittests.cc
+++ b/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"
@@ -64,10 +65,12 @@
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));
}
diff --git a/sandbox/linux/services/yama.cc b/sandbox/linux/services/yama.cc
new file mode 100644
index 0000000..39ac079
--- /dev/null
+++ b/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(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 = 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/sandbox/linux/services/yama.h b/sandbox/linux/services/yama.h
new file mode 100644
index 0000000..236b74c
--- /dev/null
+++ b/sandbox/linux/services/yama.h
@@ -0,0 +1,57 @@
+// 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"
+
+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 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/sandbox/linux/services/yama_unittests.cc b/sandbox/linux/services/yama_unittests.cc
new file mode 100644
index 0000000..17ef4b4
--- /dev/null
+++ b/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
diff --git a/sandbox/linux/suid/client/setuid_sandbox_client.cc b/sandbox/linux/suid/client/setuid_sandbox_client.cc
index 740823a..224f754 100644
--- a/sandbox/linux/suid/client/setuid_sandbox_client.cc
+++ b/sandbox/linux/suid/client/setuid_sandbox_client.cc
@@ -129,7 +129,7 @@
// We need to reap the chroot helper process in any event.
pid_t helper_pid = GetHelperPID(env_);
// If helper_pid is -1 we wait for any child.
- if (waitpid(helper_pid, NULL, 0) < 0) {
+ if (HANDLE_EINTR(waitpid(helper_pid, NULL, 0)) < 0) {
PLOG(ERROR) << "Failed to wait for setuid helper to die";
return false;
}
@@ -182,4 +182,3 @@
}
} // namespace sandbox
-
diff --git a/sandbox/linux/tests/main.cc b/sandbox/linux/tests/main.cc
index 8fd85d9..b07718e 100644
--- a/sandbox/linux/tests/main.cc
+++ b/sandbox/linux/tests/main.cc
@@ -3,8 +3,25 @@
// found in the LICENSE file.
#include "base/at_exit.h"
+#include "base/logging.h"
+#include "sandbox/linux/tests/test_utils.h"
+#include "sandbox/linux/tests/unit_tests.h"
#include "testing/gtest/include/gtest/gtest.h"
+namespace sandbox {
+namespace {
+
+// Check for leaks in our tests.
+void RunPostTestsChecks() {
+ if (TestUtils::CurrentProcessHasChildren()) {
+ LOG(ERROR) << "One of the tests created a child that was not waited for. "
+ << "Please, clean-up after your tests!";
+ }
+}
+
+} // namespace
+} // namespace sandbox
+
int main(int argc, char* argv[]) {
// The use of Callbacks requires an AtExitManager.
base::AtExitManager exit_manager;
@@ -14,5 +31,8 @@
// additional side effect of getting rid of gtest warnings about fork()
// safety.
::testing::FLAGS_gtest_death_test_style = "threadsafe";
- return RUN_ALL_TESTS();
+ int tests_result = RUN_ALL_TESTS();
+
+ sandbox::RunPostTestsChecks();
+ return tests_result;
}
diff --git a/sandbox/linux/tests/test_utils.cc b/sandbox/linux/tests/test_utils.cc
new file mode 100644
index 0000000..398654b
--- /dev/null
+++ b/sandbox/linux/tests/test_utils.cc
@@ -0,0 +1,31 @@
+// 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/tests/test_utils.h"
+
+#include <errno.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <sys/wait.h>
+#include <unistd.h>
+
+#include "base/basictypes.h"
+#include "base/logging.h"
+#include "base/posix/eintr_wrapper.h"
+
+namespace sandbox {
+
+bool TestUtils::CurrentProcessHasChildren() {
+ siginfo_t process_info;
+ int ret = HANDLE_EINTR(
+ waitid(P_ALL, 0, &process_info, WEXITED | WNOHANG | WNOWAIT));
+ if (-1 == ret) {
+ PCHECK(ECHILD == errno);
+ return false;
+ } else {
+ return true;
+ }
+}
+
+} // namespace sandbox
diff --git a/sandbox/linux/tests/test_utils.h b/sandbox/linux/tests/test_utils.h
new file mode 100644
index 0000000..3269847
--- /dev/null
+++ b/sandbox/linux/tests/test_utils.h
@@ -0,0 +1,23 @@
+// 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_TESTS_TEST_UTILS_H_
+#define SANDBOX_LINUX_TESTS_TEST_UTILS_H_
+
+#include "base/basictypes.h"
+
+namespace sandbox {
+
+// This class provide small helpers to help writing tests.
+class TestUtils {
+ public:
+ static bool CurrentProcessHasChildren();
+
+ private:
+ DISALLOW_IMPLICIT_CONSTRUCTORS(TestUtils);
+};
+
+} // namespace sandbox
+
+#endif // SANDBOX_LINUX_TESTS_TEST_UTILS_H_
diff --git a/sandbox/linux/tests/unit_tests.cc b/sandbox/linux/tests/unit_tests.cc
index ad30d84..2e714aa 100644
--- a/sandbox/linux/tests/unit_tests.cc
+++ b/sandbox/linux/tests/unit_tests.cc
@@ -157,7 +157,7 @@
// Make sure read() will never block as we'll use poll() to
// block with a timeout instead.
const int fcntl_ret = fcntl(fds[0], F_SETFL, O_NONBLOCK);
- ASSERT_EQ(fcntl_ret, 0);
+ ASSERT_EQ(0, fcntl_ret);
struct pollfd poll_fd = {fds[0], POLLIN | POLLRDHUP, 0};
int poll_ret;
@@ -232,7 +232,7 @@
bool subprocess_terminated_normally = WIFEXITED(status);
ASSERT_TRUE(subprocess_terminated_normally) << details;
int subprocess_exit_status = WEXITSTATUS(status);
- ASSERT_EQ(subprocess_exit_status, expected_exit_code) << details;
+ ASSERT_EQ(expected_exit_code, subprocess_exit_status) << details;
}
void UnitTests::DeathBySignal(int status,
@@ -244,7 +244,7 @@
bool subprocess_terminated_by_signal = WIFSIGNALED(status);
ASSERT_TRUE(subprocess_terminated_by_signal) << details;
int subprocess_signal_number = WTERMSIG(status);
- ASSERT_EQ(subprocess_signal_number, expected_signo) << details;
+ ASSERT_EQ(expected_signo, subprocess_signal_number) << details;
}
void UnitTests::AssertionFailure(const char* expr, const char* file, int line) {
diff --git a/sandbox/linux/tests/unit_tests.h b/sandbox/linux/tests/unit_tests.h
index 5480b56..74aabe4 100644
--- a/sandbox/linux/tests/unit_tests.h
+++ b/sandbox/linux/tests/unit_tests.h
@@ -25,6 +25,12 @@
#define DISABLE_ON_TSAN(test_name) test_name
#endif // defined(THREAD_SANITIZER)
+#if defined(OS_ANDROID)
+#define DISABLE_ON_ANDROID(test_name) DISABLED_##test_name
+#else
+#define DISABLE_ON_ANDROID(test_name) test_name
+#endif
+
// While it is perfectly OK for a complex test to provide its own DeathCheck
// function. Most death tests have very simple requirements. These tests should
// use one of the predefined DEATH_XXX macros as an argument to