📄 ptracesandbox.c
字号:
/* * Part of Very Secure FTPd * Licence: GPL v2 * Author: Chris Evans * ptracesandbox.c * * Generic routines to setup and run a process under a restrictive ptrace() * based sandbox. * Note that the style in this file is to not go via the helper functions in * sysutil.c, but instead hit the system APIs directly. This is because I may * very well release just this file to the public domain, and do not want * dependencies on other parts of vsftpd. */#include "ptracesandbox.h"#if defined(__linux__) && defined(__i386__)#include <sys/mman.h>#include <sys/prctl.h>#include <sys/ptrace.h>/* For AF_MAX (NPROTO is defined to this) */#include <sys/socket.h>#include <sys/types.h>#include <sys/user.h>#include <sys/wait.h>#include <err.h>#include <errno.h>#include <fcntl.h>#include <signal.h>#include <stdlib.h>#include <string.h>#include <unistd.h>#include <syslog.h>#include <asm/unistd.h>/* For the socketcall() multiplex args. */#include <linux/net.h>#ifndef PTRACE_SETOPTIONS #define PTRACE_SETOPTIONS 0x4200#endif#ifndef PTRACE_O_TRACESYSGOOD #define PTRACE_O_TRACESYSGOOD 1#endif#ifndef PTRACE_O_TRACEFORK #define PTRACE_O_TRACEFORK 2#endif#ifndef PTRACE_O_TRACEVFORK #define PTRACE_O_TRACEVFORK 4#endif#ifndef PTRACE_O_TRACECLONE #define PTRACE_O_TRACECLONE 8#endif#ifndef O_DIRECT #define O_DIRECT 040000#endifstatic void sanitize_child();static int get_action(struct pt_sandbox* p_sandbox);static int validate_mmap2(struct pt_sandbox* p_sandbox, void* p_arg);static int validate_open_default(struct pt_sandbox* p_sandbox, void* p_arg);static int validate_open_readonly(struct pt_sandbox* p_sandbox, void* p_arg);static int validate_fcntl(struct pt_sandbox* p_sandbox, void* p_arg);static int validate_socketcall(struct pt_sandbox* p_sandbox, void* p_arg);static void install_socketcall(struct pt_sandbox* p_sandbox);#define MAX_SYSCALL 300struct pt_sandbox{ int read_event_fd; int write_event_fd; pid_t pid; int is_allowed[MAX_SYSCALL]; ptrace_sandbox_validator_t validator[MAX_SYSCALL]; void* validator_arg[MAX_SYSCALL]; int is_exit; struct user_regs_struct regs; int is_socketcall_allowed[NPROTO]; ptrace_sandbox_validator_t socketcall_validator[NPROTO]; void* socketcall_validator_arg[NPROTO];};static int s_sigchld_fd = -1;voidhandle_sigchld(int sig){ int ret; if (sig != SIGCHLD) { _exit(1); } if (s_sigchld_fd != -1) { do { static const char zero = '\0'; ret = write(s_sigchld_fd, &zero, sizeof(zero)); } while (ret == -1 && errno == EINTR); if (ret != 1) { _exit(2); } }}struct pt_sandbox*ptrace_sandbox_alloc(){ int i; struct sigaction sigact; struct pt_sandbox* ret = malloc(sizeof(struct pt_sandbox)); if (ret == NULL) { return NULL; } ret->pid = -1; ret->read_event_fd = -1; ret->write_event_fd = -1; ret->is_exit = 0; memset(&ret->regs, '\0', sizeof(ret->regs)); for (i = 0; i < MAX_SYSCALL; ++i) { ret->is_allowed[i] = 0; ret->validator[i] = 0; ret->validator_arg[i] = 0; } for (i = 0; i < NPROTO; ++i) { ret->is_socketcall_allowed[i] = 0; ret->socketcall_validator[i] = 0; ret->socketcall_validator_arg[i] = 0; } memset((void*) &sigact, '\0', sizeof(sigact)); sigact.sa_handler = handle_sigchld; if (sigaction(SIGCHLD, &sigact, NULL) != 0) { goto err_out; } return ret;err_out: ptrace_sandbox_free(ret); return NULL;}voidptrace_sandbox_free(struct pt_sandbox* p_sandbox){ if (p_sandbox->pid != -1) { warnx("bug: pid active in ptrace_sandbox_free"); /* We'll kill it for you so it doesn't escape the sandbox totally, but * we won't reap the zombie. * Killing it like this is a risk: if it's stopped in syscall entry, * that syscall will execute before the pending kill takes effect. * If that pending syscall were to be a fork(), there could be trouble. */ (void) kill(p_sandbox->pid, SIGKILL); } if (p_sandbox->read_event_fd != -1) { s_sigchld_fd = -1; close(p_sandbox->read_event_fd); close(p_sandbox->write_event_fd); } free(p_sandbox);}voidptrace_sandbox_attach_point(){ long pt_ret; int ret; pid_t pid = getpid(); if (pid <= 1) { warnx("weird pid"); _exit(1); } /* You don't have to use PTRACE_TRACEME, but if you don't, a rogue SIGCONT * might wake you up from the STOP below before the tracer has attached. */ pt_ret = ptrace(PTRACE_TRACEME, 0, 0, 0); if (pt_ret != 0) { warn("PTRACE_TRACEME failed"); _exit(2); } ret = kill(pid, SIGSTOP); if (ret != 0) { warn("kill SIGSTOP failed"); _exit(3); }}intptrace_sandbox_launch_process(struct pt_sandbox* p_sandbox, void (*p_func)(void*), void* p_arg){ long pt_ret; pid_t ret; int status; if (p_sandbox->pid != -1) { warnx("bug: process already active"); return -1; } ret = fork(); if (ret < 0) { return -1; } else if (ret == 0) { /* Child context. */ sanitize_child(); (*p_func)(p_arg); _exit(0); } /* Parent context */ p_sandbox->pid = ret; do { ret = waitpid(p_sandbox->pid, &status, 0); } while (ret == -1 && errno == EINTR); if (ret == -1) { warn("waitpid failed"); goto kill_out; } else if (ret != p_sandbox->pid) { warnx("unknown pid %d", ret); goto kill_out; } if (!WIFSTOPPED(status)) { warnx("not stopped status %d\n", status); goto kill_out; } if (WSTOPSIG(status) != SIGSTOP) { warnx("not SIGSTOP status %d\n", status); goto kill_out; } /* The fork, etc. tracing options are worth a bit of explanation. We don't * permit process launching syscalls at all as they are dangerous. But * there's a small race if the untrusted process attempts a denied fork() * and then takes a rouge SIGKILL before the supervisor gets a chance to * clear the orig_eax register. In this case the syscall will still execute. * (Policies may not include signal sending capabilities, thus mitigating this * direct attack, however a rogue SIGKILL may come from a non-malicious * source). Therefore, we'd rather any fork()ed process starts off traced, * just in case this tiny race condition triggers. */ pt_ret = ptrace(PTRACE_SETOPTIONS, p_sandbox->pid, 0, PTRACE_O_TRACESYSGOOD | PTRACE_O_TRACEFORK | PTRACE_O_TRACEVFORK | PTRACE_O_TRACECLONE); if (pt_ret != 0) { warn("PTRACE_SETOPTIONS failure"); goto kill_out; } return p_sandbox->pid;kill_out: (void) kill(p_sandbox->pid, SIGKILL); p_sandbox->pid = -1; return -1;}intptrace_sandbox_continue_process(struct pt_sandbox* p_sandbox, int sig){ long pt_ret = ptrace(PTRACE_SYSCALL, p_sandbox->pid, 0, sig); if (pt_ret != 0) { warn("PTRACE_SYSCALL failure"); if (errno == ESRCH) { return PTRACE_SANDBOX_ERR_DEAD; } return PTRACE_SANDBOX_ERR_PTRACE; } return 0;}intptrace_sandbox_get_event_fd(struct pt_sandbox* p_sandbox){ /* TODO: allocate pipe fds */ (void) p_sandbox; return -1;}intptrace_sandbox_get_event(struct pt_sandbox* p_sandbox, int* status, int block){ pid_t pid; int options = 0; if (!block) { options = WNOHANG; } do { pid = waitpid(p_sandbox->pid, status, options); } while (pid == -1 && errno == EINTR); if (pid == -1) { warn("waitpid failure"); if (errno == ECHILD) { return PTRACE_SANDBOX_ERR_DEAD; } return PTRACE_SANDBOX_ERR_WAITPID; } return pid;}intptrace_sandbox_handle_event(struct pt_sandbox* p_sandbox, int status){ int sig; int action; if (WIFEXITED(status) || WIFSIGNALED(status)) { p_sandbox->pid = -1; return 1; } if (!WIFSTOPPED(status)) { warnx("weird status: %d\n", status); return PTRACE_SANDBOX_ERR_WAIT_STATUS; } sig = WSTOPSIG(status); if (sig >= 0 && sig < 0x80) { /* It's a normal signal; deliver it right on. SIGSTOP / SIGCONT handling * are buggy in the kernel and I'm not sure it's safe to pass either on, * so the signal becomes a little more... robust :) */ if (sig == SIGSTOP || sig == SIGCONT) { sig = SIGKILL; } return ptrace_sandbox_continue_process(p_sandbox, sig); } if (!(sig & 0x80)) { warnx("weird status: %d\n", status); return PTRACE_SANDBOX_ERR_WAIT_STATUS; } /* Syscall trap. */ if (p_sandbox->is_exit) { p_sandbox->is_exit = 0; } else { p_sandbox->is_exit = 1; action = get_action(p_sandbox); if (action != 0) { return action; } } return ptrace_sandbox_continue_process(p_sandbox, 0);}intptrace_sandbox_run_processes(struct pt_sandbox* p_sandbox){ if (ptrace_sandbox_continue_process(p_sandbox, 0) != 0) { goto kill_out; } while (1) { int status; int ret = ptrace_sandbox_get_event(p_sandbox, &status, 1); if (ret <= 0) { goto kill_out; } ret = ptrace_sandbox_handle_event(p_sandbox, status); if (ret < 0) { warnx("couldn't handle sandbox event"); goto kill_out; } if (ret == 1) { return 0; } }kill_out: ptrace_sandbox_kill_processes(p_sandbox); return -1;}voidptrace_sandbox_kill_processes(struct pt_sandbox* p_sandbox){ long pt_ret; struct user_regs_struct regs; pid_t pid = p_sandbox->pid; if (pid == -1) { return; } p_sandbox->pid = -1; pt_ret = ptrace(PTRACE_GETREGS, pid, 0, ®s); if (pt_ret != 0) { warn("PTRACE_GETREGS failure"); /* This API is supposed to be called with the process stopped; but if it * is still running, we can at least help a bit. See security related * comment in ptrace_sandbox_free(), though. */ (void) kill(pid, SIGKILL); return; } /* Kind of nasty, but the only way of stopping a started syscall from * executing is to rewrite the registers to execute a different syscall. */ regs.orig_eax = __NR_exit_group; regs.eip = 0xffffffff; pt_ret = ptrace(PTRACE_SETREGS, pid, 0, ®s); if (pt_ret != 0) { warn("PTRACE_SETREGS failure"); /* Deliberate fall-thru. */ } pt_ret = ptrace(PTRACE_KILL, pid, 0, 0); if (pt_ret != 0) { warn("PTRACE_KILL failure"); /* Deliberate fall-thru. */ } /* Just to make ourselves clear. */ (void) kill(pid, SIGKILL); /* So the GETREGS succeeded, so the process definitely _was_ there. We can * safely wait for it to reap the zombie. */ (void) waitpid(pid, NULL, 0);}intptrace_sandbox_get_arg(struct pt_sandbox* p_sandbox, int arg, unsigned long* p_out){ long ret = 0; struct user_regs_struct* p_regs = &p_sandbox->regs; if (p_regs->orig_eax == 0) { return PTRACE_SANDBOX_ERR_API_ABUSE_STOPIT; } if (arg < 0 || arg > 5) { return PTRACE_SANDBOX_ERR_API_ABUSE_STOPIT; } switch (arg) { case 0: ret = p_regs->ebx; break; case 1: ret = p_regs->ecx; break; case 2: ret = p_regs->edx; break; case 3: ret = p_regs->esi; break; case 4: ret = p_regs->edi; break; case 5: ret = p_regs->ebp; break; } *p_out = ret; return 0;}intptrace_sandbox_get_socketcall_arg(struct pt_sandbox* p_sandbox, int arg, unsigned long* p_out){ unsigned long ptr; int ret; struct user_regs_struct* p_regs = &p_sandbox->regs; if (p_regs->orig_eax == 0) {
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -