T-HUB/packages/media-sandbox/native/confine.c
glass beadian aa5e262f3a Tactical Media Broadcast Hub — initial release
Self-hosted multi-platform broadcast newsroom for activist collectives:
publishing pipeline with group approval and DST-proof scheduling, TweetDeck-style
column wall with hostile-content-safe rendering, live control room with Auto
Director, bridged chat governance with polls and quorum, distributed sandboxed
transcode workers, encrypted content-addressed archive, monthly analytics.

MIT licensed. See README.md and docs/.
2026-10-10 11:56:42 +00:00

124 lines
6 KiB
C

#define _GNU_SOURCE
#include <errno.h>
#include <fcntl.h>
#include <grp.h>
#include <linux/audit.h>
#include <linux/capability.h>
#include <linux/filter.h>
#include <linux/seccomp.h>
#include <stddef.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/mount.h>
#include <sys/prctl.h>
#include <sys/resource.h>
#include <sys/socket.h>
#include <sys/stat.h>
#include <sys/syscall.h>
#include <unistd.h>
/* 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();
}