/*
 * DF-0806 — dirfs_readlink off-by-one heap overflow + OOB read
 * Deterministic userspace harness (faithful transcription of the kernel code).
 *
 * BUG LOCATION: sys/vfs/dirfs/dirfs_vnops.c:1328-1334 (dirfs_readlink)
 *
 *   1328:  buf = kmalloc(uio->uio_resid, M_DIRFS_MISC, M_WAITOK | M_ZERO);
 *   1329:  nlen = readlinkat(pathnp->dn_fd, dnp->dn_name, buf, uio->uio_resid);
 *   1330:  if (nlen == -1 ) {
 *   1331:      error = errno;
 *   1332:  } else {
 *   1333:      error = uiomove(buf, nlen + 1, uio);   // copies nlen+1 bytes
 *   1334:          buf[nlen] = '\0';                   // writes at index nlen
 *   1335:      ...
 *
 * uio->uio_resid flows UNCLAMPED from the user's raw readlink() count via
 * kern_readlink (sys/kern/vfs_syscalls.c:3211  auio.uio_resid = count).
 *
 * When the symlink target length >= uio_resid (== N), POSIX readlinkat returns
 * nlen == N (exactly bufsiz). Then:
 *   - line 1334: buf[N] = '\0'  -> writes 1 byte past the N-byte allocation
 *                                    (CWE-787 off-by-one heap overflow / OOB write)
 *   - line 1333: uiomove(buf, N+1, uio) -> reads buf[0..N] = N+1 bytes from an
 *                                    N-byte buffer (CWE-125 1-byte OOB read)
 *
 * WHY A HARNESS: dirfs is vkernel64-only — listed in
 * sys/platform/vkernel64/conf/files (optional dirfs) but NOT in sys/conf/files,
 * so it is absent from the running X86_64_GENERIC kernel, and there is no
 * dirfs.ko in /boot/kernel.  It cannot be mounted or triggered on this guest.
 * The finding explicitly authorizes a deterministic harness fallback.  This
 * program transcribes the exact buggy operations with a guard-page allocator
 * (mmap + mprotect) so a 1-byte overflow is detected deterministically
 * (ASan/libasan is not shipped on this guest either).
 *
 * The guard-page allocator places the N-byte buffer flush against a PROT_NONE
 * page, so any access to buf[N] lands in the guard page and faults (SIGSEGV).
 * The "fixed" path allocates N+1 bytes so buf[N] is the legitimately-writable
 * last byte and no fault occurs — proving the fix closes the OOB.
 *
 * Build:  cc -O2 -Wall -o harness harness.c
 * Run:    ./harness            (runs vulnerable + fixed for several N)
 *         ./harness <N>        (single size)
 */

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <signal.h>
#include <setjmp.h>
#include <unistd.h>
#include <sys/mman.h>
#include <sys/wait.h>

static sigjmp_buf oob_jmp;
static volatile sig_atomic_t oob_caught;

static void oob_handler(int sig, siginfo_t *si, void *ctx)
{
    (void)sig; (void)si; (void)ctx;
    oob_caught = 1;
    siglongjmp(oob_jmp, 1);
}

/* Install a SEGV/BUS catcher that jumps back to the last sigsetjmp. */
static void install_catcher(void)
{
    struct sigaction sa;
    memset(&sa, 0, sizeof(sa));
    sa.sa_sigaction = oob_handler;
    sa.sa_flags = SA_SIGINFO;
    sigemptyset(&sa.sa_mask);
    sigaction(SIGSEGV, &sa, NULL);
    sigaction(SIGBUS,  &sa, NULL);
}

/*
 * Guard-page allocator: returns a pointer to `alloc` writable bytes whose last
 * byte is flush against a PROT_NONE page.  buf[alloc] therefore lands in the
 * guard page and faults.  This emulates kmalloc(alloc) with byte-exact bounds.
 */
static char *guard_alloc(size_t alloc)
{
    long pgsz = sysconf(_SC_PAGESIZE);
    if (pgsz <= 0) pgsz = 4096;
    size_t need = (size_t)pgsz * 2;
    void *base = mmap(NULL, need, PROT_READ | PROT_WRITE,
                      MAP_PRIVATE | MAP_ANON, -1, 0);
    if (base == MAP_FAILED) {
        perror("mmap");
        exit(2);
    }
    /* Second page is the guard. */
    if (mprotect((char *)base + pgsz, (size_t)pgsz, PROT_NONE) != 0) {
        perror("mprotect");
        exit(2);
    }
    /* buf occupies the LAST `alloc` bytes of the first page. */
    return (char *)base + (pgsz - alloc);
}

/* Simulated uiomove: copy `n` bytes from src to a user sink (UIO_READ). */
static void sim_uiomove(const char *src, size_t n, char *sink)
{
    /* Reads src[0..n-1] — if n > allocation, src[n-1] is an OOB read. */
    memcpy(sink, src, n);
}

/*
 * Vulnerable transcription of dirfs_readlink lines 1328-1334 for a given N
 * (uio_resid) and target length >= N.  `mode` selects which op to probe:
 *   'w' = buf[nlen]='\0'  (line 1334, OOB write)
 *   'r' = uiomove(buf, nlen+1)  (line 1333, OOB read)
 * Returns 0 if the op completed without fault, 1 if it faulted (OOB detected).
 */
static int vulnerable(size_t N, char mode)
{
    size_t nlen = N; /* readlinkat returns N when target length >= bufsiz */

    /* line 1328: kmalloc(uio_resid) == exactly N bytes */
    char *buf = guard_alloc(N);
    memset(buf, 'A', N);     /* simulate readlinkat filling buf */

    char sink[N + 2];
    memset(sink, 0, sizeof(sink));

    install_catcher();
    oob_caught = 0;
    if (sigsetjmp(oob_jmp, 1) == 0) {
        if (mode == 'r') {
            /* line 1333: uiomove(buf, nlen + 1, uio)  -> reads N+1 bytes */
            sim_uiomove(buf, nlen + 1, sink);
        } else {
            /* line 1334: buf[nlen] = '\0'  -> writes at index N */
            buf[nlen] = '\0';
        }
        return 0; /* no fault — op was in-bounds */
    }
    return 1; /* faulted — OOB access confirmed */
}

/*
 * Fixed transcription: kmalloc(uio_resid + 1) and uiomove(buf, nlen).
 * Same modes; should NEVER fault.
 */
static int fixed(size_t N, char mode)
{
    size_t nlen = N;

    /* FIX: kmalloc(uio_resid + 1) == N+1 bytes */
    char *buf = guard_alloc(N + 1);
    memset(buf, 'A', N);     /* readlinkat fills first N bytes */

    char sink[N + 2];
    memset(sink, 0, sizeof(sink));

    install_catcher();
    oob_caught = 0;
    if (sigsetjmp(oob_jmp, 1) == 0) {
        if (mode == 'r') {
            /* FIX: uiomove(buf, nlen, uio)  -> reads exactly N bytes */
            sim_uiomove(buf, nlen, sink);
        } else {
            /* buf[nlen] = '\0' now writes the (N+1)th byte — in-bounds */
            buf[nlen] = '\0';
        }
        return 0;
    }
    return 1;
}

static int run_case(const char *label, size_t N)
{
    int w_vuln = vulnerable(N, 'w');
    int r_vuln = vulnerable(N, 'r');
    int w_fix  = fixed(N, 'w');
    int r_fix  = fixed(N, 'r');

    printf("[%s] N=%zu\n", label, N);
    printf("  VULNERABLE dirfs_readlink transcription (kmalloc(N), nlen=N):\n");
    printf("    line 1334 buf[nlen]='\\0'  : %s\n",
           w_vuln ? "FAULT (1-byte heap overflow / OOB WRITE confirmed)" : "no fault");
    printf("    line 1333 uiomove(buf,N+1) : %s\n",
           r_vuln ? "FAULT (1-byte OOB READ confirmed)" : "no fault");
    printf("  FIXED transcription (kmalloc(N+1), uiomove(buf,nlen)):\n");
    printf("    buf[nlen]='\\0'            : %s\n",
           w_fix ? "FAULT (unexpected!)" : "no fault (in-bounds)");
    printf("    uiomove(buf,N)             : %s\n",
           r_fix ? "FAULT (unexpected!)" : "no fault (in-bounds)");

    int vuln_hit = (w_vuln || r_vuln);
    int fix_ok   = (!w_fix && !r_fix);
    printf("  => BUG %s; FIX %s\n\n",
           vuln_hit ? "PRESENT (OOB detected)" : "absent",
           fix_ok   ? "VALID (no OOB)" : "INVALID (still OOB)");
    return vuln_hit && fix_ok;
}

int main(int argc, char **argv)
{
    printf("=== DF-0806 dirfs_readlink off-by-one harness ===\n");
    printf("Transcription of sys/vfs/dirfs/dirfs_vnops.c:1328-1334\n");
    printf("Guard-page allocator detects any access to buf[N].\n\n");

    int all_ok = 1;

    if (argc > 1) {
        size_t N = (size_t)strtoul(argv[1], NULL, 0);
        if (N == 0 || N >= 4000) {
            fprintf(stderr, "N must be in 1..4095\n");
            return 2;
        }
        all_ok &= run_case("user-supplied", N);
    } else {
        /* Test several kmalloc bucket-relevant sizes. */
        size_t sizes[] = { 16, 32, 64, 128, 256 };
        size_t i;
        for (i = 0; i < sizeof(sizes)/sizeof(sizes[0]); i++) {
            char label[32];
            snprintf(label, sizeof(label), "kmalloc-%zu bucket", sizes[i]);
            all_ok &= run_case(label, sizes[i]);
        }
    }

    printf("=== SUMMARY ===\n");
    printf("Vulnerable code: 1-byte OOB WRITE at buf[N] (CWE-787) AND\n");
    printf("                  1-byte OOB READ via uiomove(buf,N+1) (CWE-125)\n");
    printf("Fixed code:       no OOB (kmalloc(N+1) + uiomove(buf,nlen))\n");
    printf("Overall: %s\n", all_ok ? "BUG CONFIRMED + FIX VALIDATED" : "ANOMALY");
    return all_ok ? 0 : 1;
}
