| /* sigsegv.c |
| * Copyright (c) 2012 The Chromium OS Authors. All rights reserved. |
| * Use of this source code is governed by a BSD-style license that can be |
| * found in the LICENSE file. |
| * |
| * Forces a denied system call to trigger a SIGSEGV at the instruction after |
| * the call using a SIGSYS handler.. This can be useful when debugging |
| * frameworks have trouble tracing through the SIGSYS handler. |
| * Proof of concept using amd64 registers and 'syscall'. |
| */ |
| |
| #include <asm/siginfo.h> |
| #define __have_siginfo_t 1 |
| #define __have_sigval_t 1 |
| #define __have_sigevent_t 1 |
| |
| #include <linux/filter.h> |
| #include <linux/prctl.h> |
| #include <linux/seccomp.h> |
| #include <limits.h> |
| #include <stddef.h> |
| #include <stdbool.h> |
| #include <string.h> |
| #include <syscall.h> |
| #define __USE_GNU 1 |
| #include <sys/ucontext.h> |
| #include <sys/mman.h> |
| |
| #include "test_harness.h" |
| |
| #ifndef PR_SET_NO_NEW_PRIVS |
| #define PR_SET_NO_NEW_PRIVS 38 |
| #define PR_GET_NO_NEW_PRIVS 39 |
| #endif |
| |
| #if defined(__i386__) |
| #define REG_IP REG_EIP |
| #define REG_SP REG_ESP |
| #define REG_RESULT REG_EAX |
| #define REG_SYSCALL REG_EAX |
| #define REG_ARG0 REG_EBX |
| #define REG_ARG1 REG_ECX |
| #define REG_ARG2 REG_EDX |
| #define REG_ARG3 REG_ESI |
| #define REG_ARG4 REG_EDI |
| #define REG_ARG5 REG_EBP |
| #elif defined(__x86_64__) |
| #define REG_IP REG_RIP |
| #define REG_SP REG_RSP |
| #define REG_RESULT REG_RAX |
| #define REG_SYSCALL REG_RAX |
| #define REG_ARG0 REG_RDI |
| #define REG_ARG1 REG_RSI |
| #define REG_ARG2 REG_RDX |
| #define REG_ARG3 REG_R10 |
| #define REG_ARG4 REG_R8 |
| #define REG_ARG5 REG_R9 |
| #endif |
| |
| FIXTURE_DATA(TRAP) { |
| struct sock_fprog prog; |
| }; |
| |
| FIXTURE_SETUP(TRAP) { |
| /* instruction after the syscall. Will be arch specific, of course. */ |
| { |
| struct sock_filter filter[] = { |
| BPF_STMT(BPF_LD+BPF_W+BPF_ABS, |
| offsetof(struct seccomp_data, nr)), |
| /* Whitelist anything you might need in the sigaction */ |
| #ifdef __NR_sigreturn |
| BPF_JUMP(BPF_JMP+BPF_JEQ+BPF_K, __NR_sigreturn, 4, 0), |
| #endif |
| /* TODO: only allow PROT_NONE */ |
| BPF_JUMP(BPF_JMP+BPF_JEQ+BPF_K, __NR_mprotect, 3, 0), |
| BPF_JUMP(BPF_JMP+BPF_JEQ+BPF_K, __NR_exit, 2, 0), |
| BPF_JUMP(BPF_JMP+BPF_JEQ+BPF_K, __NR_rt_sigreturn, 1, 0), |
| /* Allow __NR_write so easy logging. */ |
| BPF_JUMP(BPF_JMP+BPF_JEQ+BPF_K, __NR_write, 0, 1), |
| BPF_STMT(BPF_RET+BPF_K, SECCOMP_RET_ALLOW), |
| BPF_STMT(BPF_RET+BPF_K, SECCOMP_RET_TRAP), |
| }; |
| memset(&self->prog, 0, sizeof(self->prog)); |
| self->prog.filter = malloc(sizeof(filter)); |
| ASSERT_NE(NULL, self->prog.filter); |
| memcpy(self->prog.filter, filter, sizeof(filter)); |
| self->prog.len = (unsigned short)(sizeof(filter)/sizeof(filter[0])); |
| } |
| } |
| |
| FIXTURE_TEARDOWN(TRAP) { |
| if (self->prog.filter) |
| free(self->prog.filter); |
| }; |
| |
| struct arch_sigsys { |
| void *_call_addr; /* calling user insn */ |
| int _syscall; /* triggering system call number */ |
| unsigned int _arch; /* AUDIT_ARCH_* of syscall */ |
| }; |
| |
| #define _ALIGN(x,sz) (((x + ((sz)-1)) & ~((sz)-1)) - (sz)) |
| #define ALIGN(x,sz) ((typeof(x))_ALIGN((unsigned long)(x),(unsigned long)(sz))) |
| static long local_mprotect(void *target, unsigned long sz) |
| { |
| register unsigned long res asm ("rax") = __NR_mprotect; |
| register void *addr asm ("rdi") = ALIGN(target, sz); |
| register long len asm ("rsi") = sz; |
| register long num asm ("rdx") = PROT_NONE; |
| __asm__("syscall\n"); |
| return res; |
| } |
| |
| static void TRAP_action(int nr, siginfo_t *info, void *void_context) |
| { |
| ucontext_t *ctx = (ucontext_t *)void_context; |
| char buf[256]; |
| int len; |
| int do_ret = 1; |
| struct arch_sigsys *sys = (struct arch_sigsys *) |
| #ifdef si_syscall |
| &(info->si_call_addr); |
| #else |
| &(info->si_pid); |
| #endif |
| |
| if (info->si_code != SYS_SECCOMP) |
| return; |
| if (!ctx) |
| return; |
| len = snprintf(buf, sizeof(buf), |
| "@0x%lX:%X:%d:0x%lX:0x%lX:0x%lX:0x%lX:0x%lX:0x%lX [0x%lX]\n", |
| (unsigned long)sys->_call_addr, |
| sys->_arch, |
| sys->_syscall, |
| ctx->uc_mcontext.gregs[REG_ARG0], |
| ctx->uc_mcontext.gregs[REG_ARG1], |
| ctx->uc_mcontext.gregs[REG_ARG2], |
| ctx->uc_mcontext.gregs[REG_ARG3], |
| ctx->uc_mcontext.gregs[REG_ARG4], |
| ctx->uc_mcontext.gregs[REG_ARG5], |
| ALIGN(ctx->uc_mcontext.gregs[REG_IP], 4096)); |
| /* Emit some useful logs or whatever. */ |
| syscall(__NR_write, STDOUT_FILENO, buf, len); |
| /* Make the calling page non-exec */ |
| /* Careful on how it is called since it may make the syscall() instructions non-exec. */ |
| local_mprotect((void *)ctx->uc_mcontext.gregs[REG_IP], sysconf(_SC_PAGE_SIZE)); |
| } |
| |
| TEST_F_SIGNAL(TRAP, sigsegv, SIGSEGV) { |
| int ret; |
| struct sigaction act; |
| pid_t pid; |
| sigset_t mask; |
| memset(&act, 0, sizeof(act)); |
| sigemptyset(&mask); |
| sigaddset(&mask, SIGSYS); |
| |
| act.sa_sigaction = &TRAP_action; |
| act.sa_flags = SA_SIGINFO; |
| ret = sigaction(SIGSYS, &act, NULL); |
| ASSERT_EQ(0, ret) { |
| TH_LOG("sigaction failed"); |
| } |
| ret = sigprocmask(SIG_UNBLOCK, &mask, NULL); |
| ASSERT_EQ(0, ret) { |
| TH_LOG("sigprocmask failed"); |
| } |
| |
| /* Get the pid to compare against. */ |
| pid = getpid(); |
| |
| ret = prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0); |
| ASSERT_EQ(0, ret); |
| ret = prctl(PR_SET_SECCOMP, SECCOMP_MODE_FILTER, &self->prog); |
| ASSERT_EQ(0, ret); |
| |
| /* Call anything! */ |
| ret = syscall(__NR_getpid); |
| } |
| |
| TEST_HARNESS_MAIN |