diff options
author | jln@chromium.org <jln@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2014-03-07 00:22:24 +0000 |
---|---|---|
committer | jln@chromium.org <jln@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2014-03-07 00:22:24 +0000 |
commit | 682af83f7e17c896150ec2cbefe2a2000ab9135f (patch) | |
tree | fd8df15d13e60131f9e15f4cb735aef5520cc9da | |
parent | 4181c28ea5f37ef2acb87f850fac10791fbf9967 (diff) | |
download | chromium_src-682af83f7e17c896150ec2cbefe2a2000ab9135f.zip chromium_src-682af83f7e17c896150ec2cbefe2a2000ab9135f.tar.gz chromium_src-682af83f7e17c896150ec2cbefe2a2000ab9135f.tar.bz2 |
Linux sandbox: add basic Yama support
This CL adds basic detection of whether or not the Yama LSM module is
available, and allow to opt-in and opt-out of its protections.
BUG=349673
R=jorgelo@chromium.org, keescook@chromium.org
Review URL: https://codereview.chromium.org/188193002
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@255475 0039d316-1c4b-4281-b951-d872f2087c98
-rw-r--r-- | sandbox/linux/sandbox_linux.gypi | 4 | ||||
-rw-r--r-- | sandbox/linux/sandbox_linux_test_sources.gypi | 2 | ||||
-rw-r--r-- | sandbox/linux/services/broker_process.cc | 2 | ||||
-rw-r--r-- | sandbox/linux/services/broker_process_unittest.cc | 6 | ||||
-rw-r--r-- | sandbox/linux/services/scoped_process.cc | 119 | ||||
-rw-r--r-- | sandbox/linux/services/scoped_process.h | 54 | ||||
-rw-r--r-- | sandbox/linux/services/scoped_process_unittest.cc | 128 | ||||
-rw-r--r-- | sandbox/linux/services/thread_helpers.cc | 20 | ||||
-rw-r--r-- | sandbox/linux/services/thread_helpers.h | 8 | ||||
-rw-r--r-- | sandbox/linux/services/thread_helpers_unittests.cc | 3 | ||||
-rw-r--r-- | sandbox/linux/services/yama.cc | 116 | ||||
-rw-r--r-- | sandbox/linux/services/yama.h | 57 | ||||
-rw-r--r-- | sandbox/linux/services/yama_unittests.cc | 152 | ||||
-rw-r--r-- | sandbox/linux/tests/unit_tests.h | 6 |
14 files changed, 667 insertions, 10 deletions
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..d6d4f8e 100644 --- a/sandbox/linux/sandbox_linux_test_sources.gypi +++ b/sandbox/linux/sandbox_linux_test_sources.gypi @@ -18,7 +18,9 @@ '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/services/broker_process.cc b/sandbox/linux/services/broker_process.cc index 39e40f4..ac4b3c4 100644 --- a/sandbox/linux/services/broker_process.cc +++ b/sandbox/linux/services/broker_process.cc @@ -149,7 +149,9 @@ bool BrokerProcess::Init( 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]); diff --git a/sandbox/linux/services/broker_process_unittest.cc b/sandbox/linux/services/broker_process_unittest.cc index cd50a6b..f3effa10 100644 --- a/sandbox/linux/services/broker_process_unittest.cc +++ b/sandbox/linux/services/broker_process_unittest.cc @@ -65,12 +65,6 @@ bool NoOpCallback() { return true; } } // namespace -#if defined(OS_ANDROID) - #define DISABLE_ON_ANDROID(function) DISABLED_##function -#else - #define DISABLE_ON_ANDROID(function) function -#endif - TEST(BrokerProcess, CreateAndDestroy) { std::vector<std::string> read_whitelist; read_whitelist.push_back("/proc/cpuinfo"); 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..7ae82bf8 --- /dev/null +++ b/sandbox/linux/services/scoped_process_unittest.cc @@ -0,0 +1,128 @@ +// 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/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)); + file_util::ScopedFDCloser read_end_closer(pipe_fds); + file_util::ScopedFDCloser write_end_closer(pipe_fds + 1); + + { ScopedProcess process(base::Bind(&DoExit)); } + + // Close writing end of the pipe. + ASSERT_EQ(0, IGNORE_EINTR(close(pipe_fds[1]))); + 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)); + file_util::ScopedFDCloser read_end_closer(pipe_fds); + file_util::ScopedFDCloser 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 @@ 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/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 @@ namespace sandbox { 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 @@ class ThreadHelpers { 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 @@ class ScopedProcSelfTask { 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..efb261c --- /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/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"; + + int yama_scope = open(kPtraceScopePath, O_RDONLY); + + if (yama_scope < 0) { + const int open_errno = errno; + DCHECK(ENOENT == open_errno); + // The status is known, yama is not present. + return STATUS_KNOWN; + } + + file_util::ScopedFDCloser yama_scope_closer(&yama_scope); + char yama_scope_value = 0; + ssize_t num_read = read(yama_scope, &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..17ef4b40 --- /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/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 @@ bool IsRunningOnValgrind(); #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 |