diff --git a/debuggerd/Android.bp b/debuggerd/Android.bp index 7fec47dae..fb8a20577 100644 --- a/debuggerd/Android.bp +++ b/debuggerd/Android.bp @@ -195,6 +195,7 @@ cc_test { "libcutils", "libdebuggerd_client", "liblog", + "libminijail", "libnativehelper" ], diff --git a/debuggerd/Android.mk b/debuggerd/Android.mk new file mode 100644 index 000000000..3bc174250 --- /dev/null +++ b/debuggerd/Android.mk @@ -0,0 +1,23 @@ +LOCAL_PATH := $(call my-dir) + +include $(CLEAR_VARS) +LOCAL_MODULE := crash_dump.policy +LOCAL_MODULE_CLASS := ETC +LOCAL_MULTILIB := both + +ifeq ($(TARGET_ARCH), $(filter $(TARGET_ARCH), arm arm64)) +LOCAL_MODULE_STEM_32 := crash_dump.arm.policy +LOCAL_MODULE_STEM_64 := crash_dump.arm64.policy +endif + +ifeq ($(TARGET_ARCH), $(filter $(TARGET_ARCH), x86 x86_64)) +LOCAL_MODULE_STEM_32 := crash_dump.x86.policy +LOCAL_MODULE_STEM_64 := crash_dump.x86_64.policy +endif + +LOCAL_MODULE_PATH := $(TARGET_OUT)/etc/seccomp_policy +LOCAL_SRC_FILES_arm := seccomp_policy/crash_dump.arm.policy +LOCAL_SRC_FILES_arm64 := seccomp_policy/crash_dump.arm64.policy +LOCAL_SRC_FILES_x86 := seccomp_policy/crash_dump.x86.policy +LOCAL_SRC_FILES_x86_64 := seccomp_policy/crash_dump.x86_64.policy +include $(BUILD_PREBUILT) diff --git a/debuggerd/debuggerd_test.cpp b/debuggerd/debuggerd_test.cpp index 939f4d257..f8b4bad6e 100644 --- a/debuggerd/debuggerd_test.cpp +++ b/debuggerd/debuggerd_test.cpp @@ -41,6 +41,9 @@ #include #include +#include +#include + #include "debuggerd/handler.h" #include "protocol.h" #include "tombstoned/tombstoned.h" @@ -76,9 +79,8 @@ constexpr char kWaitForGdbKey[] = "debug.debuggerd.wait_for_gdb"; return value; \ }() -#define ASSERT_BACKTRACE_FRAME(result, frame_name) \ - ASSERT_MATCH(result, R"(#\d\d pc [0-9a-f]+\s+ /system/lib)" ARCH_SUFFIX \ - R"(/libc.so \()" frame_name R"(\+)") +#define ASSERT_BACKTRACE_FRAME(result, frame_name) \ + ASSERT_MATCH(result, R"(#\d\d pc [0-9a-f]+\s+ \S+ \()" frame_name R"(\+)"); static void tombstoned_intercept(pid_t target_pid, unique_fd* intercept_fd, unique_fd* output_fd, InterceptStatus* status, DebuggerdDumpType intercept_type) { @@ -565,6 +567,141 @@ TEST_F(CrasherTest, fake_pid) { ASSERT_BACKTRACE_FRAME(result, "tgkill"); } +static const char* const kDebuggerdSeccompPolicy = + "/system/etc/seccomp_policy/crash_dump." ABI_STRING ".policy"; + +pid_t seccomp_fork() { + unique_fd policy_fd(open(kDebuggerdSeccompPolicy, O_RDONLY | O_CLOEXEC)); + if (policy_fd == -1) { + LOG(FATAL) << "failed to open policy " << kDebuggerdSeccompPolicy; + } + + ScopedMinijail jail{minijail_new()}; + if (!jail) { + LOG(FATAL) << "failed to create minijail"; + } + + minijail_no_new_privs(jail.get()); + minijail_log_seccomp_filter_failures(jail.get()); + minijail_use_seccomp_filter(jail.get()); + minijail_parse_seccomp_filters_from_fd(jail.get(), policy_fd.release()); + + pid_t result = fork(); + if (result == -1) { + return result; + } else if (result != 0) { + return result; + } + + // Spawn and detach a thread that spins forever. + std::atomic thread_ready(false); + std::thread thread([&jail, &thread_ready]() { + minijail_enter(jail.get()); + thread_ready = true; + for (;;) + ; + }); + thread.detach(); + + while (!thread_ready) { + continue; + } + + minijail_enter(jail.get()); + return result; +} + +TEST_F(CrasherTest, seccomp_crash) { + int intercept_result; + unique_fd output_fd; + + StartProcess([]() { abort(); }, &seccomp_fork); + + StartIntercept(&output_fd); + FinishCrasher(); + AssertDeath(SIGABRT); + FinishIntercept(&intercept_result); + ASSERT_EQ(1, intercept_result) << "tombstoned reported failure"; + + std::string result; + ConsumeFd(std::move(output_fd), &result); + ASSERT_BACKTRACE_FRAME(result, "abort"); +} + +__attribute__((noinline)) extern "C" bool raise_debugger_signal(DebuggerdDumpType dump_type) { + siginfo_t siginfo; + siginfo.si_code = SI_QUEUE; + siginfo.si_pid = getpid(); + siginfo.si_uid = getuid(); + + if (dump_type != kDebuggerdNativeBacktrace && dump_type != kDebuggerdTombstone) { + PLOG(FATAL) << "invalid dump type"; + } + + siginfo.si_value.sival_int = dump_type == kDebuggerdNativeBacktrace; + + if (syscall(__NR_rt_tgsigqueueinfo, getpid(), gettid(), DEBUGGER_SIGNAL, &siginfo) != 0) { + PLOG(ERROR) << "libdebuggerd_client: failed to send signal to self"; + return false; + } + + return true; +} + +TEST_F(CrasherTest, seccomp_tombstone) { + int intercept_result; + unique_fd output_fd; + + static const auto dump_type = kDebuggerdTombstone; + StartProcess( + []() { + raise_debugger_signal(dump_type); + _exit(0); + }, + &seccomp_fork); + + StartIntercept(&output_fd, dump_type); + FinishCrasher(); + AssertDeath(0); + FinishIntercept(&intercept_result); + ASSERT_EQ(1, intercept_result) << "tombstoned reported failure"; + + std::string result; + ConsumeFd(std::move(output_fd), &result); + ASSERT_BACKTRACE_FRAME(result, "raise_debugger_signal"); +} + +TEST_F(CrasherTest, seccomp_backtrace) { + int intercept_result; + unique_fd output_fd; + + static const auto dump_type = kDebuggerdNativeBacktrace; + StartProcess( + []() { + raise_debugger_signal(dump_type); + _exit(0); + }, + &seccomp_fork); + + StartIntercept(&output_fd, dump_type); + FinishCrasher(); + AssertDeath(0); + FinishIntercept(&intercept_result); + ASSERT_EQ(1, intercept_result) << "tombstoned reported failure"; + + std::string result; + ConsumeFd(std::move(output_fd), &result); + ASSERT_BACKTRACE_FRAME(result, "raise_debugger_signal"); +} + +TEST_F(CrasherTest, seccomp_crash_logcat) { + StartProcess([]() { abort(); }, &seccomp_fork); + FinishCrasher(); + + // Make sure we don't get SIGSYS when trying to dump a crash to logcat. + AssertDeath(SIGABRT); +} + TEST_F(CrasherTest, competing_tracer) { int intercept_result; unique_fd output_fd; diff --git a/debuggerd/seccomp_policy/crash_dump.arm.policy b/debuggerd/seccomp_policy/crash_dump.arm.policy new file mode 100644 index 000000000..c64e28824 --- /dev/null +++ b/debuggerd/seccomp_policy/crash_dump.arm.policy @@ -0,0 +1,37 @@ +read: 1 +write: 1 +exit: 1 +rt_sigreturn: 1 +sigreturn: 1 +exit_group: 1 +clock_gettime: 1 +gettimeofday: 1 +futex: 1 +getrandom: 1 +getpid: 1 +gettid: 1 +ppoll: 1 +pipe2: 1 +openat: 1 +dup: 1 +close: 1 +lseek: 1 +getdents64: 1 +faccessat: 1 +recvmsg: 1 +process_vm_readv: 1 +tgkill: 1 +rt_sigprocmask: 1 +rt_tgsigqueueinfo: 1 +prctl: arg0 == PR_GET_NO_NEW_PRIVS || arg0 == 0x53564d41 +madvise: 1 +mprotect: arg2 in PROT_READ|PROT_WRITE +munmap: 1 +getuid32: 1 +fstat64: 1 +mmap2: arg2 in PROT_READ|PROT_WRITE +sigaction: 1 +geteuid32: 1 +getgid32: 1 +getegid32: 1 +getgroups32: 1 diff --git a/debuggerd/seccomp_policy/crash_dump.arm64.policy b/debuggerd/seccomp_policy/crash_dump.arm64.policy new file mode 100644 index 000000000..0c689bbd6 --- /dev/null +++ b/debuggerd/seccomp_policy/crash_dump.arm64.policy @@ -0,0 +1,36 @@ +read: 1 +write: 1 +exit: 1 +rt_sigreturn: 1 +exit_group: 1 +clock_gettime: 1 +gettimeofday: 1 +futex: 1 +getrandom: 1 +getpid: 1 +gettid: 1 +ppoll: 1 +pipe2: 1 +openat: 1 +dup: 1 +close: 1 +lseek: 1 +getdents64: 1 +faccessat: 1 +recvmsg: 1 +process_vm_readv: 1 +tgkill: 1 +rt_sigprocmask: 1 +rt_tgsigqueueinfo: 1 +prctl: arg0 == PR_GET_NO_NEW_PRIVS || arg0 == 0x53564d41 +madvise: 1 +mprotect: arg2 in PROT_READ|PROT_WRITE +munmap: 1 +getuid: 1 +fstat: 1 +mmap: arg2 in PROT_READ|PROT_WRITE +rt_sigaction: 1 +geteuid: 1 +getgid: 1 +getegid: 1 +getgroups: 1 diff --git a/debuggerd/seccomp_policy/crash_dump.policy.def b/debuggerd/seccomp_policy/crash_dump.policy.def new file mode 100644 index 000000000..dadffac8d --- /dev/null +++ b/debuggerd/seccomp_policy/crash_dump.policy.def @@ -0,0 +1,64 @@ +// SECCOMP_MODE_STRICT +read: 1 +write: 1 +exit: 1 +rt_sigreturn: 1 +#if !defined(__LP64__) +sigreturn: 1 +#endif + +exit_group: 1 +clock_gettime: 1 +gettimeofday: 1 +futex: 1 +getrandom: 1 +getpid: 1 +gettid: 1 + +ppoll: 1 +pipe2: 1 +openat: 1 +dup: 1 +close: 1 +lseek: 1 +getdents64: 1 +faccessat: 1 +recvmsg: 1 + +process_vm_readv: 1 + +tgkill: 1 +rt_sigprocmask: 1 +rt_tgsigqueueinfo: 1 + +#define PR_SET_VMA 0x53564d41 +prctl: arg0 == PR_GET_NO_NEW_PRIVS || arg0 == PR_SET_VMA + +madvise: 1 +mprotect: arg2 in PROT_READ|PROT_WRITE +munmap: 1 + +#if defined(__LP64__) +getuid: 1 +fstat: 1 +mmap: arg2 in PROT_READ|PROT_WRITE +rt_sigaction: 1 +#else +getuid32: 1 +fstat64: 1 +mmap2: arg2 in PROT_READ|PROT_WRITE +sigaction: 1 +#endif + +// Needed for logging. +#if defined(__LP64__) +geteuid: 1 +getgid: 1 +getegid: 1 +getgroups: 1 +#else +geteuid32: 1 +getgid32: 1 +getegid32: 1 +getgroups32: 1 +#endif diff --git a/debuggerd/seccomp_policy/crash_dump.x86.policy b/debuggerd/seccomp_policy/crash_dump.x86.policy new file mode 100644 index 000000000..c64e28824 --- /dev/null +++ b/debuggerd/seccomp_policy/crash_dump.x86.policy @@ -0,0 +1,37 @@ +read: 1 +write: 1 +exit: 1 +rt_sigreturn: 1 +sigreturn: 1 +exit_group: 1 +clock_gettime: 1 +gettimeofday: 1 +futex: 1 +getrandom: 1 +getpid: 1 +gettid: 1 +ppoll: 1 +pipe2: 1 +openat: 1 +dup: 1 +close: 1 +lseek: 1 +getdents64: 1 +faccessat: 1 +recvmsg: 1 +process_vm_readv: 1 +tgkill: 1 +rt_sigprocmask: 1 +rt_tgsigqueueinfo: 1 +prctl: arg0 == PR_GET_NO_NEW_PRIVS || arg0 == 0x53564d41 +madvise: 1 +mprotect: arg2 in PROT_READ|PROT_WRITE +munmap: 1 +getuid32: 1 +fstat64: 1 +mmap2: arg2 in PROT_READ|PROT_WRITE +sigaction: 1 +geteuid32: 1 +getgid32: 1 +getegid32: 1 +getgroups32: 1 diff --git a/debuggerd/seccomp_policy/crash_dump.x86_64.policy b/debuggerd/seccomp_policy/crash_dump.x86_64.policy new file mode 100644 index 000000000..0c689bbd6 --- /dev/null +++ b/debuggerd/seccomp_policy/crash_dump.x86_64.policy @@ -0,0 +1,36 @@ +read: 1 +write: 1 +exit: 1 +rt_sigreturn: 1 +exit_group: 1 +clock_gettime: 1 +gettimeofday: 1 +futex: 1 +getrandom: 1 +getpid: 1 +gettid: 1 +ppoll: 1 +pipe2: 1 +openat: 1 +dup: 1 +close: 1 +lseek: 1 +getdents64: 1 +faccessat: 1 +recvmsg: 1 +process_vm_readv: 1 +tgkill: 1 +rt_sigprocmask: 1 +rt_tgsigqueueinfo: 1 +prctl: arg0 == PR_GET_NO_NEW_PRIVS || arg0 == 0x53564d41 +madvise: 1 +mprotect: arg2 in PROT_READ|PROT_WRITE +munmap: 1 +getuid: 1 +fstat: 1 +mmap: arg2 in PROT_READ|PROT_WRITE +rt_sigaction: 1 +geteuid: 1 +getgid: 1 +getegid: 1 +getgroups: 1 diff --git a/debuggerd/seccomp_policy/generate.sh b/debuggerd/seccomp_policy/generate.sh new file mode 100755 index 000000000..8c58b052f --- /dev/null +++ b/debuggerd/seccomp_policy/generate.sh @@ -0,0 +1,10 @@ +#!/bin/bash + +set -ex + +cd "$(dirname "$0")" +CPP='cpp -undef -E -P crash_dump.policy.def' +$CPP -D__arm__ -o crash_dump.arm.policy +$CPP -D__aarch64__ -D__LP64__ -o crash_dump.arm64.policy +$CPP -D__i386__ -o crash_dump.x86.policy +$CPP -D__x86_64__ -D__LP64__ -o crash_dump.x86_64.policy