diff options
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.cc | 231 |
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 = ®s; + 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 = ®s; + 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 |