โฌข DragonFlyBSD Kernel Audit
DF-1228 / mpr_smid_oob.c
โ† back to finding โ†“ download raw
/*
 * DF-1228 โ€” mpr_intr_locked() untrusted-SMID OOB-index proof-of-concept
 *
 * Bug (sys/dev/raid/mpr/mpr.c):
 *   Line 2336:  cm = &sc->commands[le16toh(desc->SCSIIOSuccess.SMID)];
 *   Line 2407:  cm = &sc->commands[le16toh(desc->AddressReply.SMID)];
 *
 * SMID is a U16 in the reply descriptor written by the MPT-Fusion 3 HBA
 * (sys/dev/raid/mpr/mpi/mpi2.h:416/442/...). sc->commands[] is allocated
 * as `sizeof(struct mpr_command) * sc->num_reqs` (mpr.c:1522), where
 * num_reqs is an int set from the controller's IOC facts โ€” typically a
 * few thousand. A reply SMID > num_reqs therefore reads an out-of-bounds
 * mpr_command struct whose cm_state / cm_reply / cm_complete fields are
 * attacker-influenced heap residue. mpr_complete_command() then derefs
 * cm->cm_complete as a function pointer (sys/dev/raid/mpr/mpr.c, via
 * `cm->cm_complete(sc, cm);` in mpr_complete_command) โ€” i.e. the OOB
 * read becomes a hijackable control-flow transfer.
 *
 * KASSERT(cm->cm_state == MPR_CM_STATE_INQUEUE, ...) at lines 2337/2409
 * only fires on INVARIANTS kernels AND only AFTER the OOB read has
 * happened, so it does not protect the index.
 *
 * Threat model: malicious/buggy PCIe HBA (firmware compromise, DMA attack,
 * buggy card, PCIe passthrough of a hostile device). The SMID is attacker
 * controlled in that model.
 *
 * THIS GUEST: no LSI/Avago MPT-Fusion controller in pciconf -l, so no
 * /dev/mprN exists; the interrupt handler is never entered. PoC prints
 * the reachability status. On a real host with an mpr HBA, a malicious
 * card replying with SMID > num_reqs triggers the OOB index.
 */

#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <errno.h>
#include <sys/ioctl.h>

int
main(void)
{
    int fd, i;
    const char *devs[] = {"/dev/mpr0", "/dev/mpr1", "/dev/mpr2", "/dev/mpr3"};

    printf("[DF-1228] mpr SMID OOB-index demonstrator\n");
    printf("[DF-1228] Bug: sc->commands[le16toh(SMID)] at mpr.c:2336 & :2407\n");
    printf("[DF-1228] sc->commands[] sized num_reqs (few thousand); SMID is u16 (0..65535)\n");
    printf("[DF-1228] KASSERT only fires AFTER OOB read; no real bounds check exists\n\n");

    for (i = 0; i < 4; i++) {
        fd = open(devs[i], O_RDWR);
        if (fd >= 0) {
            printf("[DF-1228] Opened %s -- controller present.\n", devs[i]);
            printf("[DF-1228] A malicious HBA replying with SMID >= num_reqs\n");
            printf("[DF-1228] OOB-indexes sc->commands[] -> OOB struct mpr_command\n");
            printf("[DF-1228] -> mpr_complete_command() calls cm->cm_complete(sc,cm)\n");
            printf("[DF-1228]   from OOB heap -> control-flow hijack on INVARIANTS-OFF kernels.\n");
            close(fd);
            return (0);
        }
    }
    printf("[DF-1228] No /dev/mprN found: %s\n", strerror(errno));
    printf("[DF-1228] No LSI MPT-Fusion HBA on guest -> interrupt handler never runs.\n");
    printf("[DF-1228] Source-level verification only (see VERDICT.md).\n");
    return (0);
}