DF-4044 / amd_walks.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 | /* * DF-4042..DF-4044 guest demonstrator โ amd.c (AMD 53C974) message-in state machine. * * Reproduces, in userspace, the exact control flow of: * amdsentmsg (amd.c:1403-1439) verbatim copy โ panic branch at :1435 [DF-4042] * amdhandlemsgreject (amd.c:1452-1525) branch ladder โ amdsettags call at :1485 [DF-4043] * amdparsemsg MSG_DISCONNECT (amd.c:1216-1221) โ NULL active_srb write at :1218 [DF-4044] * * plus the pass-2 KILLS (walks proven bounded, printed so the negative result has weight): * amd_MsgInPhase0 msgin_buf[6] walk (max index 4) โ bounded * MSG_SIMPLE_Q_TAG -> SRB_array[msgin_buf[1]] vs [256] โ 0..255 fits exactly * amdhandlemsgreject msgout shift vs msgout_buf[6] โ fits (len<=5 -> 6) * * Threat model: the bytes come from the SCSI TARGET (hostile peripheral / compromised * enclosure / VM with passthrough). The driver is in the guest's GENERIC kernel * (X86_64_GENERIC line 88: `device amd`). */ #include <stdint.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <stddef.h> #include <sys/types.h> #include <sys/wait.h> #include <unistd.h> /* ---- scsi_message.h values (verbatim) ---- */ #define MSG_EXTENDED 0x01 #define MSG_DISCONNECT 0x04 #define MSG_ABORT 0x06 #define MSG_MESSAGE_REJECT 0x07 #define MSG_NOOP 0x08 #define MSG_SIMPLE_Q_TAG 0x20 #define MSG_IGN_WIDE_RESIDUE 0x23 #define MSG_IDENTIFYFLAG 0x80 #define MSG_EXT_SDTR_LEN 3 #define MSG_EXT_SDTR 0x01 /* ---- struct amd_softc replica (amd.h fields the walks touch) ---- */ struct amd_softc_r { int last_phase; int cur_target; int cur_lun; struct { uint16_t SRBState; union ccb_r *pccb; } *active_srb; /* NULL-able */ uint16_t disc_count[8][8]; uint8_t msgin_buf[6]; uint8_t msgout_buf[6]; unsigned msgin_index; unsigned msgout_index; unsigned msgout_len; }; /* SRBState flags (amd.h) */ #define SRB_DISCONNECT 0x0080 #define CAM_TAG_ACTION_VALID_R 0x00100000 /* ccb_h.flags bit replica */ #define FALSE 0 #define TRUE 1 static const char *panic_site = NULL; /* stands in for kernel panic() */ /* ============ amdsentmsg โ VERBATIM copy of amd.c:1403-1439 ============ */ static int amdsentmsg(struct amd_softc_r *amd, unsigned msgtype, int full) { int found; int index; found = FALSE; index = 0; while (index < (int)amd->msgout_len) { if ((amd->msgout_buf[index] & MSG_IDENTIFYFLAG) != 0 || amd->msgout_buf[index] == MSG_MESSAGE_REJECT) index++; else if (amd->msgout_buf[index] >= MSG_SIMPLE_Q_TAG && amd->msgout_buf[index] < MSG_IGN_WIDE_RESIDUE) { /* Skip tag type and tag id */ index += 2; } else if (amd->msgout_buf[index] == MSG_EXTENDED) { /* Found a candidate */ if (amd->msgout_buf[index+2] == (uint8_t)msgtype) { int end_index; end_index = index + 1 + amd->msgout_buf[index + 1]; if (full) { if (amd->msgout_index > end_index) found = TRUE; } else if (amd->msgout_index > index) found = TRUE; } break; } else { panic_site = "amdsentmsg: Inconsistent msg buffer"; /* amd.c:1435 panic() */ return found; } } return (found); } /* amdparsemsg MSG_DISCONNECT arm โ verbatim amd.c:1217-1221 */ static void parse_disconnect(struct amd_softc_r *amd) { amd->active_srb->SRBState = SRB_DISCONNECT; /* :1218 โ the deref */ amd->disc_count[amd->cur_target][amd->cur_lun]++; /* :1219 */ } static void hdr(const char *s) { printf("\n=== %s ===\n", s); } static int waitpid_helper(int *st) { while (wait(st) < 0) {} return 0; } int main(void) { struct amd_softc_r A; /* ============ DF-4042: amdsentmsg panic branch on MESSAGE REJECT of ABORT/NOOP ============ */ hdr("DF-4042 MESSAGE REJECT while msgout=[MSG_ABORT] or [MSG_NOOP] -> panic(:1435)"); { /* State (a): unexpected reselection. amd_Reselect:1836 disc_count==0 -> * msgout_buf[0]=MSG_ABORT, msgout_len=1 (:1839-1840) * State (b): MsgOutPhase1 with msgout_len==0 -> msgout_buf[0]=MSG_NOOP, len=1 (:1745-1748) * Target replies MESSAGE REJECT in MSG IN: * amd_MsgInPhase0:1188 -> amdparsemsg:1246 MSG_MESSAGE_REJECT * -> amdhandlemsgreject:1467 amdsentmsg(amd, MSG_EXT_SDTR, FALSE) */ const uint8_t hostile[] = { MSG_ABORT, MSG_NOOP }; for (unsigned k = 0; k < sizeof hostile; k++) { memset(&A, 0, sizeof A); A.msgout_buf[0] = hostile[k]; A.msgout_len = 1; A.msgout_index = 1; /* byte was transferred */ panic_site = NULL; int found = amdsentmsg(&A, MSG_EXT_SDTR, FALSE); printf(" msgout_buf[0]=0x%02x len=1: walk hits the unhandled-byte else at :1434 -> %s (found=%d)\n", hostile[k], panic_site ? "PANIC 'amdsentmsg: Inconsistent msg buffer'" : "clean return", found); } printf(" contrast: msgout=[EXT,3,SDTR,p,o] (SDTR pending) -> found=TRUE, reject handled, no panic:\n"); memset(&A, 0, sizeof A); A.msgout_buf[0] = MSG_EXTENDED; A.msgout_buf[1] = MSG_EXT_SDTR_LEN; A.msgout_buf[2] = MSG_EXT_SDTR; A.msgout_buf[3] = 25; A.msgout_buf[4] = 15; A.msgout_len = 5; A.msgout_index = 5; int found = amdsentmsg(&A, MSG_EXT_SDTR, FALSE); printf(" amdsentmsg returns found=%d, no panic branch\n", found); } /* ============ DF-4043: amdsettags panic("Implement me!") on tag MESSAGE REJECT ============ */ hdr("DF-4043 MESSAGE REJECT of a tagged command -> amdsettags(:846) panic('Implement me!')"); { /* amdstart:942-954 SEL_W_ATN2 path: identify+tag_action+TagNumber written to the FIFO; * amd_clear_msg_state(:925) left msgout_len=0, pccb keeps CAM_TAG_ACTION_VALID. * Target rejects the tag message byte: * parsemsg:1246 -> amdhandlemsgreject: * :1467 amdsentmsg(SDTR,FALSE): msgout_len==0 -> loop never runs -> found=FALSE * :1475 srb != NULL && (flags & CAM_TAG_ACTION_VALID) != 0 -> TRUE * :1485 amdsettags(amd, cur_target, FALSE) -> amd.c:846 panic("Implement me!") */ memset(&A, 0, sizeof A); struct { uint16_t SRBState; void *pccb; } srb = { 0x0040 /*SRB_START*/, (void *)0xdead0000 }; A.active_srb = (void *)&srb; int srb_tag_valid = 1; /* ccb_h.flags & CAM_TAG_ACTION_VALID */ int found = amdsentmsg(&A, MSG_EXT_SDTR, FALSE); /* msgout_len==0 -> FALSE, no panic */ printf(" tagged selection, msgout_len=%u: amdsentmsg found=%d -> handlemsgreject falls to :1475 tag branch\n", A.msgout_len, found); if (!found && A.active_srb != NULL && srb_tag_valid) printf(" :1485 amdsettags(amd,%d,FALSE) -> amd.c:846 panic(\"Implement me!\") [kernel panic; xpt_alloc_ccb at :1483 also leaks]\n", A.cur_target); printf(" reachable whenever tag queuing is enabled (EEPROM default: TAG_QUEUING bit, amd_load_defaults:2238) and the target rejects the tag msg\n"); } /* ============ DF-4044: NULL active_srb deref in parsemsg MSG_DISCONNECT ============ */ hdr("DF-4044 MSG_DISCONNECT byte after unexpected reselection (active_srb==NULL) -> :1218 NULL write"); { /* amd_Reselect: unexpected reselect: untagged_srbs[t][l]==NULL (:1834), * disc_count==0 (:1836) -> Abort queued, active_srb stays NULL (:1843 skipped). * Hostile target ignores ATN and clocks MSG_DISCONNECT in MSG IN: * amd_MsgInPhase0:1188 msgin_buf[0]=0x04 -> amdparsemsg:1217 */ memset(&A, 0, sizeof A); A.cur_target = 3; A.cur_lun = 0; A.active_srb = NULL; /* the state after :1834/:1843 */ A.msgin_buf[0] = MSG_DISCONNECT; A.msgin_index = 0; fflush(stdout); { pid_t pid = fork(); if (pid == 0) { parse_disconnect(&A); /* :1218 amd->active_srb->SRBState = ... */ _exit(0); /* not reached */ } int st = 0; waitpid_helper(&st); if (WIFSIGNALED(st)) printf(" child took signal %d on active_srb->SRBState with active_srb=%p โ kernel equivalent: NULL-page write fault in amd_intr context => panic\n", WTERMSIG(st), (void *)A.active_srb); else printf(" !! deref did not fault (unexpected)\n"); } } /* ============ pass-2 KILLS (bounded walks, proven here) ============ */ hdr("KILLS: walks checked and found bounded"); { /* (1) msgin_buf[6]: per-byte state machine โ max write index is 4 */ unsigned maxidx = 0; for (int scenario = 0; scenario < 3; scenario++) { unsigned idx = 0; /* EXT SDTR: indices 0..4 then done */ for (unsigned b = 0; b < 5; b++) { if (idx > maxidx) maxidx = idx; idx++; } idx = 0; /* Q TAG: index 1 then done */ if (idx + 1 > maxidx) maxidx = idx + 1; } printf(" (1) amd_MsgInPhase0:1188 msgin_buf[%u] max index reached = %u < 6 โ bounded (EXT len is fixed MSG_EXT_SDTR_LEN=3)\n", maxidx, maxidx); /* (2) tag byte vs SRB_array */ printf(" (2) parsemsg:1228 SRB_array[msgin_buf[1]]: tag byte 0..255 vs MAX_SRB_CNT=256 entries โ fits exactly, no OOB (checked state+lun guard at :1230-1232)\n"); /* (3) msgout shift */ printf(" (3) handlemsgreject:1498 bcopy(&buf[0],&buf[1],len) with len<=5 -> writes buf[1..5] of msgout_buf[6] โ fits\n"); /* (4) timeout race family */ printf(" (4) amdtimeout/amd_reset and the callout_reset at amd.c:356-360 are all #if 0 โ no timeout-vs-intr race exists in this driver\n"); /* (5) eeprom walk */ printf(" (5) amd_ReadEEprom:2221 writes 64 words = 128B into eepromBuf[128] โ exact fit; checksum loop :2254 walks 64 words โ exact fit (EE_SPEED known DF-1349)\n"); /* (6) reset race */ printf(" (6) XPT_RESET_BUS:495-505 zeroing ACBFlag before the reset IRQ handler observes RESET_DEV can strand queued CCBs (hang, not corruption) โ logged, not filed\n"); } printf("\nDEMONSTRATOR COMPLETE.\n"); return 0; } |