DragonFlyBSD Kernel Audit
DF-1258 / mfi_ioctl_overflow.c
← back to finding ↓ download raw
/*
 * DF-1258 trigger PoC — mfi MFI_CMD / MFI_LINUX_CMD_2 SGL heap overflow.
 *
 * Bug (sys/dev/raid/mfi/mfi.c):
 *   MFI_CMD path (mfi_ioctl, case MFI_CMD):
 *     cm->cm_len = cm->cm_frame->header.data_len;            (uint32, user)
 *     data = kmalloc(cm->cm_len, M_MFIBUF, ...)              (~:2998)
 *     for (i=0; i<ioc->mfi_sge_count; i++){                  (~:3015)
 *         len = ioc->mfi_sgl[i].iov_len;
 *         copyin(addr, temp, len);
 *         temp = &temp[len];                                 // NO check vs cm->cm_len
 *     }
 *   DATAOUT: sum(iov_len) > cm->cm_len  -> heap overflow past `data`.
 *   DATAIN : copyout reads past `data`  -> heap info leak.
 *   MFI_LINUX_CMD_2 (~:3253/3295): same, only the SGE *count* is capped
 *   (MAX_LINUX_IOCTL_SGE=16); the accumulated-length overflow is unguarded.
 *
 * Reachability on the audit guest:
 *   mfi IS compiled into X86_64_GENERIC (`device mfi`, 172 mfi_ symbols in
 *   /boot/kernel/kernel), but the /dev/mfi%d cdev is created only when mfi
 *   attaches to an LSI MegaRAID SAS controller (mfi.c:721 make_dev).  The
 *   QEMU/KVM guest has NO LSI MegaRAID PCI device (pciconf shows only a
 *   virtio-class vgapci), so /dev/mfi0 does not exist and mfi_ioctl is
 *   unreachable here.  This program attempts the trigger and reports the
 *   device-absence so the result is unambiguous: the bug is a real, latent
 *   driver defect reachable on MegaRAID-equipped hosts (by root / operator
 *   group; the node is mode 0640 root:operator), but not on this guest.
 *
 * Build:  cc -o mfi_ioctl_overflow mfi_ioctl_overflow.c
 * Run:    ./mfi_ioctl_overflow
 */
#include <sys/types.h>
#include <sys/ioctl.h>
#include <fcntl.h>
#include <stdio.h>
#include <errno.h>
#include <string.h>
#include <unistd.h>

/* MFI_CMD magic from sys/dev/raid/mfi/mfi_ioctl.h (matches struct mfi_ioc_packet) */
#define MFI_CMD_IOCTL  _IOWR('M', 1, char[512])

int main(void) {
    int fd = open("/dev/mfi0", O_RDWR);
    if (fd < 0) {
        printf("[DF-1258] open(\"/dev/mfi0\") failed: %s (errno=%d)\n",
               strerror(errno), errno);
        if (errno == ENOENT || errno == ENXIO)
            printf("[DF-1258] RESULT: /dev/mfi0 absent -> mfi driver not attached.\n"
                   "[DF-1258] The LSI MegaRAID hardware required to create the\n"
                   "[DF-1258] mfi cdev is NOT present on this QEMU guest. The\n"
                   "[DF-1258] MFI_CMD ioctl path (and the overflow it contains)\n"
                   "[DF-1258] is therefore NOT reachable here. Bug confirmed in\n"
                   "[DF-1258] source; latent on hardware-equipped hosts.\n");
        return 0;
    }
    /* If a controller were attached, we would craft an mfi_ioc_packet with
     * data_len < sum(mfi_sgl[].iov_len) and MFI_FRAME_DATAOUT set, then ioctl.
     * The driver would copyin past the kmalloc'd data buffer. */
    char buf[512] = {0};
    int rc = ioctl(fd, MFI_CMD_IOCTL, buf);
    printf("[DF-1258] ioctl rc=%d errno=%d (%s)\n", rc, errno, strerror(errno));
    close(fd);
    return 0;
}