DF-4027 / sili_model.c
/* * DF-4027 — sili deferred-timeout ccb ownership leak (illustrative model). * * NOT a runnable guest PoC: the trigger requires a SiLion Image 3124/3132 * HBA (sili(4)) with a device/port-multiplier target that lets a POLLED * command time out while another command is still active on the same * port. The audit guest has no sili controller, so this finding is * verified by kernel-source trace (see VERDICT.md). * * The program below models the exact kernel state-machine interleaving * that sili.c permits, using the same state names, so the lifetimes can * be inspected without hardware. Run it to print the double-lifecycle: * * cc -O2 -o sili_model sili_model.c && ./sili_model */ #include <stdio.h> enum { ATA_S_PUT, ATA_S_SETUP, ATA_S_PENDING, ATA_S_ONCHIP, ATA_S_COMPLETE, ATA_S_ERROR, ATA_S_TIMEOUT, }; static const char *st[] = { "PUT", "SETUP", "PENDING", "ONCHIP", "COMPLETE", "ERROR", "TIMEOUT" }; /* mirrors sili_poll() + sili_core_timeout() (sili.c:1311-1360, 2451-2520) */ static const char * sili_poll_model(int other_cmd_active, int *out_state) { /* ccb on chip; device stalls past the poll timeout */ *out_state = ATA_S_ONCHIP; /* * sili_core_timeout(ccb, 0): * ap_expired |= 1<<slot; sili.c:2505 * if (ap_active != ap_expired) return 1; sili.c:2507-2511 */ if (other_cmd_active) return ("DEFERRED (returned 1; state stays ONCHIP)"); *out_state = ATA_S_TIMEOUT; return ("reinit path"); } int main(void) { int state; printf("sili_ata_cmd(POLL) -> sili_poll():\n"); printf(" probed ccb state: %s\n", st[ATA_S_ONCHIP]); printf(" poll timeout -> sili_ata_cmd_timeout -> sili_core_timeout:\n"); printf(" %s\n", sili_poll_model(1, &state)); printf(" sili_poll returns xa->state == %s to the caller\n", st[state]); printf(" sili_cam.c:315 'if (sili_ata_cmd(xa) != ATA_S_COMPLETE)'\n"); printf(" -> sili_ata_put_xfer(xa): sili_put_ccb() puts the ccb\n"); printf(" on ap_ccb_free and bumps serial (sili.c:2020-2030)\n"); printf(" PORT STILL OWNS IT: ap_active bit set, ap_expired bit set\n\n"); printf("next command reuses the freed ccb:\n"); printf(" sili_get_ccb -> state==PUT check passes (sili.c:2010)\n"); printf(" sili_ata_cmd -> sili_load_prb loads a NEW dmamap\n"); printf(" sili_issue_pending_commands: ap_active |= 1<<slot AGAIN\n"); printf(" -> ap_active_cnt inflated; two logical commands on one slot\n\n"); printf("device finally completes / port drains:\n"); printf(" sili_port_reinit expired loop (sili.c:403-413):\n"); printf(" ccb->ccb_xa.state = ATA_S_TIMEOUT;\n"); printf(" ccb->ccb_done(ccb); <- sili_ata_cmd_done:\n"); printf(" sili_unload_prb() <- unloads the NEW command's\n"); printf(" ACTIVE dmamap (DMA-writeback\n"); printf(" onto reused memory)\n"); printf(" ccb->ccb_xa.complete(...) <- for a CAM command this is\n"); printf(" sili_ata_complete_disk_rw on the REISSUED ccb: xpt_done\n"); printf(" on a CAM ccb that is already owned by the new request\n"); printf(" -> double CAM completion / UAF in the CAM layer\n"); printf(" KKASSERT(ap->ap_active_cnt == 0) in sili_port_hardstop can\n"); printf(" also fire (underflow) on INVARIANTS kernels.\n"); return (0); } |