โฌข DragonFlyBSD Kernel Audit
DF-0894 / harness.c
โ† back to finding โ†“ download raw
/*
 * harness.c - DF-0894 deterministic primitive characterizer.
 *
 * Transcribes the ffs_balloc indirect-block indexing math verbatim, with a
 * poisoned allocator that sizes the indirect-block buffer at exactly
 * `fs_bsize` bytes (the real kernel bread() size) and surrounds it with
 * sentinels. This proves, *without* depending on the kernel slab/page
 * layout, that a forged `fs_nindir` copied into `ump->um_nindir` at
 * ffs_vfsops.c:729 drives `bap[in_off]` indices that exceed the
 * `fs_bsize / sizeof(ufs_daddr_t)` entries actually present in the
 * bread()'d indirect-block buffer โ€” an out-of-bounds read (ffs_balloc.c:297)
 * and write (ffs_balloc.c:333/378/479).
 *
 * Source model (sys/vfs/ufs/ufs_bmap.c:281-324):
 *   MNINDIR(ump) = ump->um_nindir = fs->fs_nindir   [forged]
 *   for single-indirect: off = bn % MNINDIR(ump)
 *   indirs[1].in_off = off
 *
 * Sink model (sys/vfs/ufs/ffs_balloc.c:291-297):
 *   bread(vp, ..., (int)fs->fs_bsize, &bp);   // bp->b_data is fs_bsize bytes
 *   bap = (ufs_daddr_t *)bp->b_data;          // valid indices 0..fs_bsize/4-1
 *   nb = bap[indirs[i].in_off];               // OOB if in_off >= fs_bsize/4
 *
 * Build: cc -o harness harness.c
 * Run:   ./harness [<fs_bsize> <forged_nindir> <target_lbn>]
 *        defaults: fs_bsize=4096 forged_nindir=8192 target_lbn=2012
 */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdint.h>

#define UFS_NDADDR 12
#define UFS_NIADDR 3
#define SENTINEL 0xAB

static uint32_t MNINDIR;   /* forged */

/*
 * Verbatim transcription of ufs_getlbns (ufs_bmap.c:256-327) for the
 * single-indirect case. Returns the in_off used to index bap[].
 */
static int compute_in_off(int64_t lbn, int *numlevels){
    int64_t bn = lbn;
    long blockcnt;
    int64_t qblockcnt;
    int i, off, numlevels_local = 0;
    int64_t metalbn, realbn;
    int num = 0;

    realbn = bn;
    if (bn < 0) bn = -bn;
    if (bn < UFS_NDADDR) return -1;   /* direct block, no indirect */

    for (blockcnt = 1, i = UFS_NIADDR, bn -= UFS_NDADDR;; i--, bn -= blockcnt) {
        if (i == 0) return -2;        /* EFBIG */
        qblockcnt = (int64_t)blockcnt * MNINDIR;
        if (bn < qblockcnt) break;
        blockcnt = qblockcnt;
    }
    if (realbn >= 0) metalbn = -(realbn - bn + UFS_NIADDR - i);
    else             metalbn = -(-realbn - bn + UFS_NIADDR - i);

    off = UFS_NIADDR - i;   /* indirs[0].in_off โ€” index into i_ib[] */
    numlevels_local = 1;

    for (; i <= UFS_NIADDR; i++) {
        if (metalbn == realbn) break;
        off = (bn / blockcnt) % MNINDIR;    /* indirs[1].in_off โ€” index into bap[] */
        numlevels_local++;
        metalbn -= -1 + off * blockcnt;
        blockcnt /= MNINDIR;
    }
    num = numlevels_local;
    if (numlevels) *numlevels = num;
    return off;   /* this is indirs[num-1].in_off for the single-indirect case */
}

int main(int argc, char **argv){
    uint32_t fs_bsize      = (argc >= 2) ? (uint32_t)strtoul(argv[1],NULL,0) : 4096;
    uint32_t forged_nindir = (argc >= 3) ? (uint32_t)strtoul(argv[2],NULL,0) : 8192;
    int64_t  target_lbn    = (argc >= 4) ? (int64_t)strtoll(argv[3],NULL,0)  : 2012;

    MNINDIR = forged_nindir;
    uint32_t valid_entries = fs_bsize / 4;   /* sizeof(ufs_daddr_t) = 4 */

    printf("=== DF-0894 primitive characterization ===\n");
    printf("fs_bsize (indirect-block buffer size) = %u bytes\n", fs_bsize);
    printf("valid bap[] entries                   = %u  (indices 0..%u)\n",
           valid_entries, valid_entries - 1);
    printf("forged fs_nindir -> MNINDIR(ump)      = %u\n", forged_nindir);
    printf("target lbn                            = %lld\n",
           (long long)target_lbn);

    int nlev = 0;
    int in_off = compute_in_off(target_lbn, &nlev);
    if (in_off < 0) {
        printf("\ncompute_in_off returned %d (lbn not in indirect range)\n", in_off);
        return 1;
    }
    printf("\nufs_getlbns result:\n");
    printf("  numlevels          = %d\n", nlev);
    printf("  indirs[in_off]     = %d   <- indexes bap[] in ffs_balloc.c:297\n",
           in_off);

    /* model the bread()'d buffer with sentinels */
    uint32_t slab_size = fs_bsize + 8192;   /* generous pad */
    uint8_t *slab = (uint8_t*)malloc(slab_size);
    if (!slab) { perror("malloc"); return 2; }
    memset(slab, SENTINEL, slab_size);
    uint8_t *bp_data = slab;   /* the indirect-block buffer */
    memset(bp_data, 0x00, fs_bsize);   /* fresh zero-filled indirect block */

    uint32_t idx_byte_off = (uint32_t)in_off * 4;
    printf("\nbap[%d] = bp->b_data + %u\n", in_off, idx_byte_off);
    printf("buffer ends at bp->b_data + %u\n", fs_bsize);
    if (idx_byte_off >= fs_bsize) {
        uint32_t oob = idx_byte_off - fs_bsize;
        uint32_t oob_rounded = oob + 4;   /* the 4-byte access straddles */
        printf(">>> OOB READ at ffs_balloc.c:297: %u bytes past the %u-byte buffer "
               "(+4 for the ufs_daddr_t access)\n", oob, fs_bsize);
        printf(">>> OOB WRITE at ffs_balloc.c:333/378/479 (when nb==0): "
               "writes a disk block number %u bytes past the buffer\n", oob);

        /* check what the OOB read would return (sentinel region = 0xAB) */
        if (idx_byte_off + 4 <= slab_size) {
            uint8_t v0 = bp_data[idx_byte_off + 0];
            uint8_t v1 = bp_data[idx_byte_off + 1];
            uint8_t v2 = bp_data[idx_byte_off + 2];
            uint8_t v3 = bp_data[idx_byte_off + 3];
            uint32_t nb = (uint32_t)v0 | ((uint32_t)v1<<8) |
                          ((uint32_t)v2<<16) | ((uint32_t)v3<<24);
            printf("OOB read value nb = 0x%x (%s)\n", nb,
                   nb == 0 ? "zero -> allocation path -> OOB WRITE at :378" :
                   "non-zero -> treated as disk addr -> further corruption");
        }
        printf("\nRESULT: OOB index confirmed. bap[%d] reads/writes %u bytes "
               "past the %u-byte indirect-block buffer.\n",
               in_off, oob, fs_bsize);
        printf("        Heap/page corruption into adjacent kernel memory.\n");
        free(slab);
        return 0;
    } else {
        printf("\nRESULT: index in-bounds (no OOB). fs_nindir not forged enough "
               "or target lbn too low.\n");
        free(slab);
        return 1;
    }
}