DragonFlyBSD Kernel Audit
DF-0785 / harness.c
← back to finding ↓ download raw
/*
 * DF-0785 deterministic harness.
 *
 * Transcribes the EXACT allocation + copy sequence of ntfs_ntlookupfile
 * (sys/vfs/ntfs/ntfs_subr.c:867-891) into userspace, backed by a poisoned
 * allocator that places the "object" at the end of a guard-paged page so the
 * overflow is detected byte-exactly (no slab luck required).
 *
 *   blsize = vap->va_a_iroot->ir_size;                 // :867  -> kmalloc size
 *   rdsize = vap->va_datalen;                          // :868  -> copy size
 *   rdbuf  = kmalloc(blsize, M_TEMP, M_WAITOK);        // :888
 *   ntfs_readattr(... rdsize ... rdbuf);               // :890-891  -> memcpy
 *
 * No check rdsize<=blsize exists.  Compare ntfs_ntreaddir:1105 which sizes
 * with max(va_datalen, f_dirblsz).  With a poisoned allocator we prove the
 * overflow extent = rdsize - blsize attacker-controlled bytes.
 *
 * Build (Linux host):  cc -O2 -o harness harness.c
 * Run:                  ./harness [blsize] [rdsize]   (defaults 16 200)
 */

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdint.h>
#include <sys/mman.h>
#include <unistd.h>

static long PAGE = 0;

/* Poisoned allocation: return a pointer to the LAST `blsize` bytes of a
 * fresh page, with the next page PROT_NONE.  Any write past blsize faults
 * into the guard page -> deterministic SIGSEGV at the exact overflow byte. */
static unsigned char *poison_alloc(uint32_t blsize) {
    long pg = PAGE;
    /* two pages: lower usable, upper guard (PROT_NONE). Place object at the
     * very end of the lower page so overflow immediately hits the guard. */
    char *base = mmap(NULL, pg * 2, PROT_READ | PROT_WRITE,
                      MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
    if (base == MAP_FAILED) { perror("mmap"); exit(2); }
    if (mprotect(base + pg, pg, PROT_NONE) != 0) { perror("mprotect"); exit(2); }
    return (unsigned char *)(base + pg - blsize);
}

int main(int argc, char **argv) {
    PAGE = sysconf(_SC_PAGESIZE);

    uint32_t blsize = (argc > 1) ? (uint32_t)strtoul(argv[1], 0, 0) : 16; /* ir_size */
    uint32_t rdsize = (argc > 2) ? (uint32_t)strtoul(argv[2], 0, 0) : 200;/* va_datalen */

    printf("[harness] DF-0785 ntfs_ntlookupfile overflow transcription\n");
    printf("[harness] blsize (ir_size,  kmalloc)  = %u\n", blsize);
    printf("[harness] rdsize (va_datalen, copy)   = %u\n", rdsize);

    if (rdsize <= blsize) {
        printf("[harness] rdsize <= blsize: no overflow (well-formed case). OK.\n");
        return 0;
    }
    printf("[harness] OVERFLOW = %u attacker-controlled bytes past %u-byte object\n",
           rdsize - blsize, blsize);

    /* attacker-controlled resident $INDEX_ROOT data (what ntfs_readattr copies) */
    unsigned char *attacker = malloc(rdsize);
    memset(attacker, 0x44, rdsize);              /* "D" marker */

    /* the buggy allocation */
    unsigned char *rdbuf = poison_alloc(blsize);
    printf("[harness] rdbuf  = %p  (last %u bytes of a page; next page PROT_NONE)\n",
           (void *)rdbuf, blsize);

    /* the buggy copy: ntfs_readattr(... rdsize ... rdbuf) -> memcpy(rdbuf, src, rdsize) */
    printf("[harness] performing ntfs_readattr-equivalent memcpy of %u bytes...\n", rdsize);
    fflush(stdout);
    memcpy(rdbuf, attacker, rdsize);   /* <-- BUG: writes rdsize into blsize buffer */

    /* If we reach here the allocator did not guard the tail (should not happen). */
    printf("[harness] ERROR: overflow was NOT caught -- allocator guard missing\n");
    return 3;
}