DF-2423 / dfx2423.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 | /* * DF-2423 - verification harness (verbatim-interposition, kernel-compiled). * * Target: sys/dev/disk/ahci/ahci_pm.c ahci_pm_read (:976-978) and * ahci_pm_write (:1013-1015) call ahci_ata_get_xfer() and deref the * result (xa->fis at offsetof(struct ata_xfer, fis) == 0) without a * NULL check. ahci_ata_get_xfer (ahci.c:3670-3689) returns NULL when * the per-port CCB free list (ap->ap_ccb_free) is empty, i.e. when all * sc_ncmds slots are in flight (NCQ I/O). * * Rig: this file is compiled INTO the kernel. It #includes the * PRISTINE ahci_pm.c translation unit (sha256-gated by build.sh) and * drives the REAL ahci_pm_read/ahci_pm_write with a fabricated * ahci_port / ata_port / ahci_ccb pool state. Because the stock * X86_64_GENERIC config builds the ahci driver into the kernel * (config line 65: "device ahci"), every symbol ahci_pm.c *defines* * is renamed with a dfx_ prefix via the preprocessor - the function * BODIES are compiled byte-verbatim, only the exported names change. * Only the three pool-path functions are interposed: * * ahci_ata_get_xfer -> dfx_ahci_ata_get_xfer (fabricated pool: * returns NULL when dfx_pool_empty==1, faithfully modeling * ahci_get_ccb()==NULL at ahci.c:3674-3678; otherwise performs * the same bzero/at/fis->type preset as ahci.c:3684-3688) * ahci_ata_cmd -> dfx_ahci_ata_cmd (fabricated completing * device: fills rfis, sets ATA_S_COMPLETE, invokes the REAL * static ahci_pm_dummy_done hook assigned by ahci_pm_read) * ahci_ata_put_xfer -> dfx_ahci_ata_put_xfer (records the put, * sets state=ATA_S_PUT as ahci_put_ccb does, ahci.c:3317-3330) * * Every other external the TU references (ahci_pread, ahci_poll, ...) * links against the REAL in-kernel ahci driver; none of them are on * the exercised paths. * * Trigger (root): sysctl hw.dfx2423_phase=N * 1 = control: pool has a free CCB; real pm_read + pm_write run * to completion against the fabricated device. * 2 = exhausted pool, real ahci_pm_read -> expect Fatal trap 12, * fault VA 0x0 (load of xa->fis, offset 0), at * ahci_pm.c:978 (compiled as dfx_pm_read). * 3 = exhausted pool, real ahci_pm_write -> expect Fatal trap 12, * fault VA 0x0, at ahci_pm.c:1015 (dfx_pm_write). */ #include <sys/param.h> #include <sys/kernel.h> #include <sys/systm.h> #include <sys/sysctl.h> /* ---- interpose the CCB pool path (unit-under-test boundary) ---- */ #define ahci_ata_get_xfer dfx_ahci_ata_get_xfer #define ahci_ata_put_xfer dfx_ahci_ata_put_xfer #define ahci_ata_cmd dfx_ahci_ata_cmd /* ---- rename TU exports; bodies byte-verbatim ---- */ #define ahci_pm_port_init dfx_pm_port_init #define ahci_pm_port_probe dfx_pm_port_probe #define ahci_pm_identify dfx_pm_identify #define ahci_pm_hardreset dfx_pm_hardreset #define ahci_pm_softreset dfx_pm_softreset #define ahci_pm_phy_status dfx_pm_phy_status #define ahci_pm_check_good dfx_pm_check_good #define ahci_pm_read dfx_pm_read /* ahci_pm.c:970 */ #define ahci_pm_write dfx_pm_write /* ahci_pm.c:1007 */ #include "../../disk/ahci/ahci_pm.c" /* PRISTINE TU, sha256-gated */ /* ================= fabrications (structs now defined) ================= */ static struct ata_fis_h2d dfx_h2d; /* cmd FIS backing memory */ static struct ahci_ccb dfx_ccb; /* the single fabricated CCB */ static struct ata_port dfx_at; /* fake PM control target 15 */ static struct ahci_port dfx_ap; /* fake ahci_port */ static int dfx_pool_empty; /* 1 = free list empty, as if all * sc_ncmds slots are in flight */ static int dfx_nget, dfx_nput, dfx_ncmd, dfx_ndone; /* * Fabricated ahci_ata_get_xfer - mirrors ahci.c:3669-3689 exactly. */ struct ata_xfer * dfx_ahci_ata_get_xfer(struct ahci_port *ap, struct ata_port *at) { ++dfx_nget; if (dfx_pool_empty) { /* ahci.c:3674-3678: ahci_get_ccb() found the free list * empty and returned NULL -> get_xfer returns NULL. */ kprintf("DFX2423: get_xfer: CCB pool EXHAUSTED -> NULL " "(models ahci.c:3675-3678)\n"); return (NULL); } /* ahci.c:3684-3688 */ bzero(dfx_ccb.ccb_xa.fis, sizeof(*dfx_ccb.ccb_xa.fis)); dfx_ccb.ccb_xa.at = at; dfx_ccb.ccb_xa.fis->type = ATA_FIS_TYPE_H2D; dfx_ccb.ccb_xa.state = ATA_S_SETUP; /* as ahci_get_ccb ahci.c:3304 */ return (&dfx_ccb.ccb_xa); } /* * Fabricated completing device - stands in for ahci_ata_cmd+chip. */ int dfx_ahci_ata_cmd(struct ata_xfer *xa) { ++dfx_ncmd; /* fabricate the rfis the real code composes datap from */ xa->rfis.sector_count = 0xAA; xa->rfis.lba_low = 0xBB; xa->rfis.lba_mid = 0xCC; xa->rfis.lba_high = 0xDD; xa->state = ATA_S_COMPLETE; if (xa->complete) { ++dfx_ndone; /* the REAL static ahci_pm_dummy_done hook, * assigned by ahci_pm_read (:985) / * ahci_pm_write (:1026) */ xa->complete(xa); } return (ATA_S_COMPLETE); } /* * Fabricated ahci_ata_put_xfer - as ahci_put_ccb (ahci.c:3317-3330). */ void dfx_ahci_ata_put_xfer(struct ata_xfer *xa) { ++dfx_nput; if (xa->state != ATA_S_PUT) xa->state = ATA_S_PUT; ++xa->serial; } static void dfx_report_fis(const char *tag) { kprintf("DFX2423: %s fis: type=%02x flags=%02x cmd=%02x feat=%02x " "dev=%02x scnt=%02x lba=%02x,%02x,%02x ctl=%02x\n", tag, dfx_h2d.type, dfx_h2d.flags, dfx_h2d.command, dfx_h2d.features, dfx_h2d.device, dfx_h2d.sector_count, dfx_h2d.lba_low, dfx_h2d.lba_mid, dfx_h2d.lba_high, dfx_h2d.control); } /* * Phase 1 (control): a free CCB exists. The real pm_read must: * - build the H2D FIS in dfx_h2d (read PM reg, target 15): * type=27 flags=9f cmd=e4(ATA_C_READ_PM) feat=20(EINFO) * dev=4f(15|ATA_H2D_DEVICE_LBA) ctl=08 * - get ATA_S_COMPLETE from the fabricated device and compose * datap = 0xAA | 0xBB<<8 | 0xCC<<16 | 0xDD<<24 = 0xddccbbaa * and the real pm_write (target 3, SCTL, 0xa1b2c3d4): * cmd=e8 feat=02 dev=43 scnt=d4 lba=c3,b2,a1 ctl=08 */ static void dfx_phase_control(void) { u_int32_t data; int rv; dfx_pool_empty = 0; dfx_nget = dfx_nput = dfx_ncmd = dfx_ndone = 0; kprintf("DFX2423: phase1 CONTROL: free CCB available\n"); rv = dfx_pm_read(&dfx_ap, 15, SATA_PMREG_EINFO, &data); kprintf("DFX2423: pm_read(15, EINFO) rv=%d data=%08x " "(expect rv=0 data=ddccbbaa)\n", rv, data); dfx_report_fis("read "); rv = dfx_pm_write(&dfx_ap, 3, SATA_PMREG_SCTL, 0xA1B2C3D4); kprintf("DFX2423: pm_write(3, SCTL, a1b2c3d4) rv=%d (expect 0)\n", rv); dfx_report_fis("write"); kprintf("DFX2423: counters get=%d cmd=%d done=%d put=%d " "(expect 2/2/2/2)\n", dfx_nget, dfx_ncmd, dfx_ndone, dfx_nput); } static int dfx2423_sysctl(SYSCTL_HANDLER_ARGS) { int error, phase, rv; u_int32_t data; phase = 0; if (req->newptr == NULL) return (0); error = SYSCTL_IN(req, &phase, sizeof(phase)); if (error) return (error); switch (phase) { case 1: dfx_phase_control(); break; case 2: kprintf("DFX2423: phase2: CCB pool EXHAUSTED, calling REAL " "ahci_pm_read (ahci_pm.c:976) - expect NULL deref of " "xa->fis at offset 0\n"); dfx_pool_empty = 1; rv = dfx_pm_read(&dfx_ap, 15, SATA_PMREG_EINFO, &data); dfx_pool_empty = 0; kprintf("DFX2423: pm_read returned %d - NO CRASH\n", rv); break; case 3: kprintf("DFX2423: phase3: CCB pool EXHAUSTED, calling REAL " "ahci_pm_write (ahci_pm.c:1013) - expect NULL deref of " "xa->fis at offset 0\n"); dfx_pool_empty = 1; rv = dfx_pm_write(&dfx_ap, 3, SATA_PMREG_SCTL, 0xA1B2C3D4); dfx_pool_empty = 0; kprintf("DFX2423: pm_write returned %d - NO CRASH\n", rv); break; default: return (EINVAL); } return (0); } SYSCTL_PROC(_hw, OID_AUTO, dfx2423_phase, CTLTYPE_INT | CTLFLAG_RW, 0, 0, dfx2423_sysctl, "I", "DF-2423: 1=control 2=exhausted-pool-read 3=exhausted-pool-write"); static void dfx2423_init(void *arg __unused) { strlcpy(dfx_ap.ap_name, "fakep0", sizeof(dfx_ap.ap_name)); dfx_ap.ap_ata[15] = &dfx_at; /* PM control target */ dfx_ap.ap_pmcount = 4; strlcpy(dfx_at.at_name, "fakep0.15", sizeof(dfx_at.at_name)); dfx_at.at_target = 15; dfx_at.at_probe = ATA_PROBE_GOOD; dfx_at.at_type = ATA_PORT_T_PM; dfx_ccb.ccb_xa.fis = &dfx_h2d; dfx_ccb.ccb_slot = 1; dfx_ccb.ccb_port = &dfx_ap; dfx_ccb.ccb_xa.state = ATA_S_PUT; kprintf("DFX2423: harness initialized (verbatim ahci_pm.c TU; " "trigger: sysctl hw.dfx2423_phase=1|2|3)\n"); } SYSINIT(dfx2423, SI_SUB_DRIVERS, SI_ORDER_ANY, dfx2423_init, NULL); |