#define _GNU_SOURCE #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include /* All errors are fixed text: never echo argv, paths, environment or media bytes. */ static void fail(void) { fputs("media sandbox confinement failed\n", stderr); exit(125); } static void check(int result) { if (result < 0) fail(); } static unsigned long number(const char *s) { char *end; errno = 0; unsigned long n = strtoul(s, &end, 10); if (errno || !*s || *end) fail(); return n; } static void limit(int resource, unsigned long value) { struct rlimit r = { value, value }; check(setrlimit(resource, &r)); } static void target(char *out, size_t size, const char *root, const char *name) { if (snprintf(out, size, "%s%s", root, name) >= (int)size) fail(); } static void filter(void) { #if defined(__x86_64__) #define ARCH AUDIT_ARCH_X86_64 #elif defined(__aarch64__) #define ARCH AUDIT_ARCH_AARCH64 #else #error Unsupported seccomp architecture #endif #define DENY(n) BPF_JUMP(BPF_JMP|BPF_JEQ|BPF_K, __NR_##n, 0, 1), BPF_STMT(BPF_RET|BPF_K, SECCOMP_RET_ERRNO|EPERM) struct sock_filter rules[] = { BPF_STMT(BPF_LD|BPF_W|BPF_ABS, offsetof(struct seccomp_data, arch)), BPF_JUMP(BPF_JMP|BPF_JEQ|BPF_K, ARCH, 1, 0), BPF_STMT(BPF_RET|BPF_K, SECCOMP_RET_KILL_PROCESS), BPF_STMT(BPF_LD|BPF_W|BPF_ABS, offsetof(struct seccomp_data, nr)), /* Reject the x32 ABI as well as unknown syscall-number namespaces. */ BPF_JUMP(BPF_JMP|BPF_JGE|BPF_K, 0x40000000, 0, 1), BPF_STMT(BPF_RET|BPF_K, SECCOMP_RET_KILL_PROCESS), DENY(socket), DENY(socketpair), DENY(connect), DENY(bind), DENY(listen), DENY(accept), DENY(accept4), DENY(sendto), DENY(sendmsg), DENY(sendmmsg), DENY(mount), DENY(umount2), DENY(pivot_root), DENY(chroot), DENY(unshare), DENY(setns), DENY(ptrace), DENY(process_vm_readv), DENY(process_vm_writev), DENY(bpf), DENY(perf_event_open), DENY(keyctl), DENY(add_key), DENY(request_key), DENY(reboot), DENY(kexec_load), DENY(init_module), DENY(finit_module), DENY(delete_module), DENY(open_by_handle_at), DENY(name_to_handle_at), DENY(userfaultfd), DENY(io_uring_setup), DENY(io_uring_enter), DENY(io_uring_register), /* FFmpeg needs threads, never new namespaces. clone3 falls back to clone. */ BPF_JUMP(BPF_JMP|BPF_JEQ|BPF_K, __NR_clone3, 0, 1), BPF_STMT(BPF_RET|BPF_K, SECCOMP_RET_ERRNO|ENOSYS), BPF_JUMP(BPF_JMP|BPF_JEQ|BPF_K, __NR_clone, 0, 3), BPF_STMT(BPF_LD|BPF_W|BPF_ABS, offsetof(struct seccomp_data, args[0])), BPF_JUMP(BPF_JMP|BPF_JSET|BPF_K, 0x7e020000, 0, 1), BPF_STMT(BPF_RET|BPF_K, SECCOMP_RET_ERRNO|EPERM), BPF_STMT(BPF_RET|BPF_K, SECCOMP_RET_ALLOW) }; struct sock_fprog p = { sizeof(rules)/sizeof(rules[0]), rules }; check(prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0)); check(prctl(PR_SET_SECCOMP, SECCOMP_MODE_FILTER, &p)); } int main(int argc, char **argv) { if (argc == 2 && !strcmp(argv[1], "--seccomp-check")) { filter(); if (socket(AF_INET, SOCK_STREAM, 0) != -1 || errno != EPERM) fail(); if (syscall(SYS_unshare, 0) != -1 || errno != EPERM) fail(); if (syscall(SYS_clone3, NULL, 0) != -1 || errno != ENOSYS) fail(); if (prctl(PR_GET_NO_NEW_PRIVS, 0, 0, 0, 0) != 1 || getpid() <= 0) fail(); return 0; } /* Join before unshare forks: every descendant inherits the bounded cgroup. */ if (argc > 4 && !strcmp(argv[1], "--join")) { int fd = open(argv[2], O_WRONLY|O_CLOEXEC|O_NOFOLLOW); check(fd); char pid[32]; int n = snprintf(pid, sizeof(pid), "%ld", (long)getpid()); if (write(fd, pid, n) != n) fail(); check(close(fd)); execv(argv[3], &argv[3]); fail(); } /* root staging, trusted rootfs, upload staging, scratch, uid, size, CPU seconds, executable, argv */ if (argc < 10 || strcmp(argv[1], "--confine")) fail(); const char *root = argv[2]; unsigned long uid = number(argv[6]); if (uid < 1000 || uid > 60000) fail(); check(mount(NULL, "/", NULL, MS_REC|MS_PRIVATE, NULL)); check(mount(argv[3], root, NULL, MS_BIND|MS_REC, NULL)); check(mount(NULL, root, NULL, MS_BIND|MS_REMOUNT|MS_RDONLY|MS_NOSUID|MS_NODEV, NULL)); char path[4096]; target(path, sizeof(path), root, "/upload"); check(mount(argv[4], path, NULL, MS_BIND, NULL)); check(mount(NULL, path, NULL, MS_BIND|MS_REMOUNT|MS_RDONLY|MS_NOSUID|MS_NODEV|MS_NOEXEC, NULL)); target(path, sizeof(path), root, "/scratch"); check(mount(argv[5], path, NULL, MS_BIND, NULL)); check(mount(NULL, path, NULL, MS_BIND|MS_REMOUNT|MS_NOSUID|MS_NODEV|MS_NOEXEC, NULL)); target(path, sizeof(path), root, "/tmp"); check(mount("tmpfs", path, "tmpfs", MS_NOSUID|MS_NODEV|MS_NOEXEC, "size=16m,mode=1777")); target(path, sizeof(path), root, "/proc"); check(mount("proc", path, "proc", MS_NOSUID|MS_NODEV|MS_NOEXEC|MS_RDONLY, NULL)); target(path, sizeof(path), root, "/dev/null"); check(mount("/dev/null", path, NULL, MS_BIND, NULL)); check(mount(NULL, path, NULL, MS_BIND|MS_REMOUNT|MS_NOSUID|MS_NOEXEC, NULL)); check(chdir(root)); check(chroot(".")); check(chdir("/scratch")); limit(RLIMIT_CORE, 0); limit(RLIMIT_FSIZE, number(argv[7])); limit(RLIMIT_CPU, number(argv[8])); limit(RLIMIT_NOFILE, 1024); /* Subordinate GID mapping keeps setgroups allowed; remove all inherited groups. */ int groups = getgroups(0, NULL); check(groups); if (groups > 0) check(setgroups(0, NULL)); for (int c = 0; c <= CAP_LAST_CAP; c++) check(prctl(PR_CAPBSET_DROP, c, 0, 0, 0)); check(setresgid(uid, uid, uid)); check(setresuid(uid, uid, uid)); struct __user_cap_header_struct h = { _LINUX_CAPABILITY_VERSION_3, 0 }; struct __user_cap_data_struct d[2] = {{0}, {0}}; check(syscall(SYS_capset, &h, d)); check(prctl(PR_SET_DUMPABLE, 0)); filter(); if (!strcmp(argv[9], "--check")) return 0; execv(argv[9], &argv[9]); fail(); }