โฌข DragonFlyBSD Kernel Audit
DF-4043 / amd_walks.c
โ† back to finding โ†“ download raw
/*
 * 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;
}