/*
 * DF-1116 userspace harness — integer divide-by-zero primitive.
 *
 * The kernel bug:
 *   sys/bus/firewire/fwdma.c:159  fwdma_malloc_multiseg()
 *       ssize = rounddown(PAGE_SIZE, esize);   // expands to (PAGE_SIZE/esize)*esize
 *   with esize == 0 (passed unvalidated from FW_SSTBUF ioctl via fwdev.c:504-509
 *   bcopy -> fwdev_allocbuf -> fwdma_malloc_multiseg) this is integer divide by
 *   zero -> kernel #DE trap -> panic at trap.c:1101.
 *
 * This harness reproduces the arithmetic primitive in userspace (SIGFPE on x86
 * is the user-mode analogue of the kernel #DE panic). It proves the bug is real
 * and deterministic; it cannot fire on this audit guest because no FireWire
 * controller is present (the driver never attaches -> /dev/fwN.M never created).
 *
 * Build:  cc -O2 -o harness harness.c
 * Run:    ./harness
 * Expected: "SIGFPE: integer divide by zero" (demonstrates the primitive).
 */
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <signal.h>
#include <stdint.h>

#define PAGE_SIZE        4096u
#define rounddown(x, y)  (((x) / (y)) * (y))   /* sys/sys/param.h:400 */
#define roundup2(x, y)   (((x) + ((y) - 1)) & (~((y) - 1)))

static void fpe_hdl(int s, siginfo_t *si, void *uc) {
    (void)s; (void)si; (void)uc;
    /* x86 #DE does not advance RIP, so returning would re-trap forever.
     * Report and exit in-handler.  Use stderr (unbuffered) since _exit
     * does not flush stdout. */
    fflush(stdout);
    fputs("\nSIGFPE: integer divide by zero -- primitive reproduced.\n", stderr);
    fputs("In kernel context this is a #DE trap -> panic "
          "(sys/platform/pc64/x86_64/trap.c:1101).\n", stderr);
    _exit(0);
}

int main(void) {
    struct sigaction sa = {0};
    sa.sa_sigaction = fpe_hdl;
    sa.sa_flags = SA_SIGINFO;
    sigaction(SIGFPE, &sa, NULL);

    /* Simulate the FW_SSTBUF ioctl with b->psize == 0 (no validation).
     * Volatile prevents the compiler from constant-folding the div-by-zero
     * (which is UB and would not trap at -O2). We want the real #DE. */
    volatile unsigned int user_psize_v = 0;
    unsigned int user_psize = user_psize_v;

    /* fwdev.c:107  b->psize = roundup2(b->psize, sizeof(u_int32_t)) */
    unsigned int psize_after_roundup = roundup2(user_psize, sizeof(uint32_t));
    printf("user psize            = %u\n", user_psize);
    printf("after roundup2(_, 4)  = %u   (0 stays 0 -> the bug seed)\n",
           psize_after_roundup);

    /* fwdma.c:145-159  fwdma_malloc_multiseg(fc, _, esize=psize, _, _) */
    int esize_v = (int)psize_after_roundup;
    volatile int vesize = esize_v;  /* prevent constant-fold of /0 */
    int esize = vesize;
    printf("esize passed to fwdma_malloc_multiseg = %d\n", esize);

    if (esize > (int)PAGE_SIZE) {
        printf("(not taken: esize==0)\n");
    } else {
        /* ssize = rounddown(PAGE_SIZE, esize)  ->  (4096 / 0) * 0  -> #DE -> SIGFPE */
        unsigned int ssize = rounddown(PAGE_SIZE, esize);
        printf("rounddown(PAGE_SIZE, esize) = %u  (UNREACHED)\n", ssize);
    }
    /* should not reach here -- handler _exit()s */
    printf("\nERROR: expected SIGFPE but did not get one.\n");
    return 1;
}
