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/.
124 lines
6 KiB
C
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();
|
|
}
|