summaryrefslogtreecommitdiffstats
path: root/chromium/sandbox/linux/seccomp-bpf-helpers/syscall_parameters_restrictions_unittests.cc
diff options
context:
space:
mode:
Diffstat (limited to 'chromium/sandbox/linux/seccomp-bpf-helpers/syscall_parameters_restrictions_unittests.cc')
-rw-r--r--chromium/sandbox/linux/seccomp-bpf-helpers/syscall_parameters_restrictions_unittests.cc231
1 files changed, 231 insertions, 0 deletions
diff --git a/chromium/sandbox/linux/seccomp-bpf-helpers/syscall_parameters_restrictions_unittests.cc b/chromium/sandbox/linux/seccomp-bpf-helpers/syscall_parameters_restrictions_unittests.cc
index 327da2bea41..d109c6186c9 100644
--- a/chromium/sandbox/linux/seccomp-bpf-helpers/syscall_parameters_restrictions_unittests.cc
+++ b/chromium/sandbox/linux/seccomp-bpf-helpers/syscall_parameters_restrictions_unittests.cc
@@ -5,14 +5,20 @@
#include "sandbox/linux/seccomp-bpf-helpers/syscall_parameters_restrictions.h"
#include <errno.h>
+#include <fcntl.h>
+#include <linux/elf.h>
#include <sched.h>
+#include <sys/prctl.h>
+#include <sys/ptrace.h>
#include <sys/resource.h>
#include <sys/syscall.h>
#include <sys/types.h>
+#include <sys/user.h>
#include <time.h>
#include <unistd.h>
#include "base/bind.h"
+#include "base/posix/eintr_wrapper.h"
#include "base/single_thread_task_runner.h"
#include "base/synchronization/waitable_event.h"
#include "base/system/sys_info.h"
@@ -242,6 +248,231 @@ BPF_DEATH_TEST_C(ParameterRestrictions,
getrusage(RUSAGE_CHILDREN, &usage);
}
+// ptace() Tests ///////////////////////////////////////////////////////////////
+
+// Tests for ptrace involve a slightly complex setup in order to properly test
+// ptrace and the variety of ways it is access-checked. The BPF_TEST macro,
+// the body of which already runs in its own process, spawns another process
+// called the "tracee". The "tracee" then spawns another process called the
+// "tracer". The child then traces the parent and performs the test operations.
+// The tracee must be careful to un-stop the tracer if the tracee expects to
+// die.
+
+class RestrictPtracePolicy : public bpf_dsl::Policy {
+ public:
+ RestrictPtracePolicy() = default;
+ ~RestrictPtracePolicy() override = default;
+
+ ResultExpr EvaluateSyscall(int sysno) const override {
+ switch (sysno) {
+ case __NR_ptrace:
+ return RestrictPtrace();
+ default:
+ return Allow();
+ }
+ }
+};
+
+constexpr char kExitPtraceChildClean = '!';
+
+class PtraceTestHarness {
+ public:
+ using PtraceChildTracerFunc = void (*)(pid_t tracee);
+
+ PtraceTestHarness(PtraceChildTracerFunc tracer_func, bool expect_death)
+ : tracer_func_(tracer_func), expect_death_(expect_death) {}
+ ~PtraceTestHarness() = default;
+
+ void Run() {
+ // Fork the tracee process that will be traced by its child.
+ pid_t pid = fork();
+ BPF_ASSERT_GE(pid, 0);
+
+ if (pid == 0) {
+ RunTracee();
+ } else {
+ // The tracee should always exit cleanly.
+ int status = 0;
+ int rv = waitpid(pid, &status, 0);
+ BPF_ASSERT_EQ(pid, rv);
+ BPF_ASSERT_EQ(0, WEXITSTATUS(status));
+ }
+ }
+
+ private:
+ void RunTracee() {
+ // Create a communications pipe between tracer and tracee.
+ int rv = pipe2(pipes_, O_NONBLOCK);
+ BPF_ASSERT_EQ(0, rv);
+
+ // Pipes for redirecting output.
+ int output_pipes[2];
+ BPF_ASSERT_EQ(0, pipe(output_pipes));
+
+ // Create the tracer process.
+ pid_t pid = fork();
+ BPF_ASSERT_GE(pid, 0);
+
+ if (pid == 0) {
+ // Close the pipe read ends and redirect output.
+ close(pipes_[0]);
+ close(output_pipes[0]);
+
+ close(STDOUT_FILENO);
+ dup2(output_pipes[1], STDOUT_FILENO);
+
+ close(STDERR_FILENO);
+ dup2(output_pipes[1], STDERR_FILENO);
+
+ RunTracer();
+
+ close(output_pipes[1]);
+ } else {
+ close(pipes_[1]);
+ close(output_pipes[1]);
+
+ // Ensure the tracer can trace the tracee. This may fail on systems
+ // without YAMA, so the result is not checked.
+ prctl(PR_SET_PTRACER, pid);
+
+ char c = 0;
+ while (c != kExitPtraceChildClean) {
+ // Read from the control channel in a non-blocking fashion.
+ // If no data are present, loop.
+ ignore_result(read(pipes_[0], &c, 1));
+
+ // Poll the exit status of the child.
+ int status = 0;
+ rv = waitpid(pid, &status, WNOHANG);
+ if (rv != 0) {
+ BPF_ASSERT_EQ(pid, rv);
+ CheckTracerStatus(status, output_pipes[0]);
+ _exit(0);
+ }
+ }
+
+ _exit(0);
+ }
+ }
+
+ void RunTracer() {
+ pid_t ppid = getppid();
+ BPF_ASSERT_NE(0, ppid);
+
+ // Attach to the tracee and then call out to the test function.
+ BPF_ASSERT_EQ(0, ptrace(PTRACE_ATTACH, ppid, nullptr, nullptr));
+
+ tracer_func_(ppid);
+
+ BPF_ASSERT_EQ(1, HANDLE_EINTR(write(pipes_[1], &kExitPtraceChildClean, 1)));
+ close(pipes_[1]);
+
+ _exit(0);
+ }
+
+ void CheckTracerStatus(int status, int output_pipe) {
+ // The child has exited. Test that it did so in the way we were
+ // expecting.
+ if (expect_death_) {
+ // This duplicates a bit of what //sandbox/linux/tests/unit_tests.cc does
+ // but that code is not shareable here.
+ std::string output;
+ const size_t kBufferSize = 1024;
+ size_t total_bytes_read = 0;
+ ssize_t read_this_pass = 0;
+ do {
+ output.resize(output.size() + kBufferSize);
+ read_this_pass = HANDLE_EINTR(
+ read(output_pipe, &output[total_bytes_read], kBufferSize));
+ if (read_this_pass >= 0) {
+ total_bytes_read += read_this_pass;
+ output.resize(total_bytes_read);
+ }
+ } while (read_this_pass > 0);
+
+#if !defined(SANDBOX_USES_BASE_TEST_SUITE)
+ const bool subprocess_got_sigsegv =
+ WIFSIGNALED(status) && (SIGSEGV == WTERMSIG(status));
+#else
+ // This hack is required when a signal handler is installed
+ // for SEGV that will _exit(1).
+ const bool subprocess_got_sigsegv =
+ WIFEXITED(status) && (1 == WEXITSTATUS(status));
+#endif
+ BPF_ASSERT(subprocess_got_sigsegv);
+ BPF_ASSERT_NE(output.find(GetPtraceErrorMessageContentForTests()),
+ std::string::npos);
+ } else {
+ BPF_ASSERT(WIFEXITED(status));
+ BPF_ASSERT_EQ(0, WEXITSTATUS(status));
+ }
+ }
+
+ PtraceChildTracerFunc tracer_func_;
+ bool expect_death_;
+ int pipes_[2];
+
+ DISALLOW_COPY_AND_ASSIGN(PtraceTestHarness);
+};
+
+BPF_TEST_C(ParameterRestrictions,
+ ptrace_getregs_allowed,
+ RestrictPtracePolicy) {
+ auto tracer = [](pid_t pid) {
+#if defined(__arm__)
+ user_regs regs;
+#else
+ user_regs_struct regs;
+#endif
+ iovec iov;
+ iov.iov_base = &regs;
+ iov.iov_len = sizeof(regs);
+ BPF_ASSERT_EQ(0, ptrace(PTRACE_GETREGSET, pid,
+ reinterpret_cast<void*>(NT_PRSTATUS), &iov));
+
+ BPF_ASSERT_EQ(0, ptrace(PTRACE_DETACH, pid, nullptr, nullptr));
+ };
+ PtraceTestHarness(tracer, false).Run();
+}
+
+BPF_TEST_C(ParameterRestrictions,
+ ptrace_syscall_blocked,
+ RestrictPtracePolicy) {
+ auto tracer = [](pid_t pid) {
+ // The tracer is about to die. Make sure the tracee is not stopped so it
+ // can reap it and inspect its death signal.
+ kill(pid, SIGCONT);
+
+ BPF_ASSERT_NE(0, ptrace(PTRACE_SYSCALL, 0, nullptr, nullptr));
+ };
+ PtraceTestHarness(tracer, true).Run();
+}
+
+BPF_TEST_C(ParameterRestrictions,
+ ptrace_setregs_blocked,
+ RestrictPtracePolicy) {
+ auto tracer = [](pid_t pid) {
+#if defined(__arm__)
+ user_regs regs;
+#else
+ user_regs_struct regs;
+#endif
+ iovec iov;
+ iov.iov_base = &regs;
+ iov.iov_len = sizeof(regs);
+ BPF_ASSERT_EQ(0, ptrace(PTRACE_GETREGSET, pid,
+ reinterpret_cast<void*>(NT_PRSTATUS), &iov));
+
+ // The tracer is about to die. Make sure the tracee is not stopped so it
+ // can reap it and inspect its death signal.
+ kill(pid, SIGCONT);
+
+ BPF_ASSERT_NE(0, ptrace(PTRACE_SETREGSET, pid,
+ reinterpret_cast<void*>(NT_PRSTATUS), &iov));
+ };
+ PtraceTestHarness(tracer, true).Run();
+}
+
} // namespace
} // namespace sandbox