/*
 * DF-1795 source-confirmation harness (PLIP SIOCSIFMTU race).
 *
 * The buggy kernel code is HW-gated (needs PLIP parallel-port hardware)
 * and cannot run on this audit guest. This harness reproduces the LOGIC
 * of the race it documents: an interrupt handler reads a length bound
 * (if_mtu) and a buffer pointer (sc_ifbuf) that are updated in three
 * SEPARATE unlocked steps by SIOCSIFMTU. We model the two thread roles
 * and show that the ithread can observe the OLD mtu with the NEW
 * (smaller) buffer, producing a heap overflow of (OLD_MTU - NEW_MTU)
 * bytes. The model uses the exact field-update sequence from
 * sys/dev/netif/plip/if_plip.c:348-354 and the length check at :470.
 *
 * Build:  cc -O2 -o harness harness.c -lpthread
 * Run:    ./harness
 */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <pthread.h>
#include <unistd.h>

/* Model of the lp_data softc — only the fields involved in the race. */
struct lp_model {
    unsigned char *sc_ifbuf;   /* lp_data.sc_ifbuf                  */
    size_t         if_mtu;     /* lp_data.sc_if.if_mtu              */
    size_t         buf_cap;    /* actual allocation (for harness)   */
};

static struct lp_model state;
static volatile int    stop;

#define MLPIPHDRLEN 14        /* from if_plip.c:#define MLPIPHDRLEN */
#define CLPIPHDRLEN 14
#define LPIPHDRLEN  14

/* SIOCSIFMTU handler: lines 348-354 of if_plip.c.  THREE unlocked steps:
 *   ptr = sc->sc_ifbuf;
 *   sc->sc_ifbuf = kmalloc(ifr_mtu + MLPIPHDRLEN, ...);   // step 1: new buf
 *   if (ptr) kfree(ptr);                                  // step 2: old freed
 *   sc->sc_if.if_mtu = ifr_mtu;                           // step 3: new mtu
 *
 * Critically: step 1 publishes a NEW (smaller) buffer BEFORE step 3
 * publishes the matching NEW mtu.  An interrupt sampling in between
 * sees (old mtu, new small buf) -> overflow.
 */
static void
siocsifmtu(struct lp_model *s, size_t new_mtu)
{
    unsigned char *ptr = s->sc_ifbuf;
    size_t cap = new_mtu + MLPIPHDRLEN;
    s->sc_ifbuf = calloc(1, cap);      /* step 1 */
    s->buf_cap  = cap;
    if (ptr) free(ptr);                /* step 2 (UAF window in kernel) */
    s->if_mtu  = new_mtu;              /* step 3 */
}

/* lp_intr: lines 444-503.  It reads sc->sc_if.if_mtu as the length bound
 * for writing into sc->sc_ifbuf.  In the kernel this runs in an ithread
 * with ONLY crit_enter() (line 454) — NOT the ifnet serializer that
 * wraps lpioctl (if.c:2276).  crit_enter does not block the ioctl thread
 * on another CPU. */
static volatile int overflow_observed;
static volatile size_t overflow_bytes;

static void *
lp_intr_thread(void *arg)
{
    struct lp_model *s = arg;
    while (!stop) {
        /* intr samples mtu as the bound, writes into sc_ifbuf */
        size_t len = s->if_mtu + MLPIPHDRLEN;   /* line 470 */
        if (s->sc_ifbuf && len > s->buf_cap) {
            /* In the kernel: *bp++ = j; for len iterations -> heap overflow.
             * The overflow depth is exactly len - buf_cap bytes. */
            overflow_observed++;
            overflow_bytes = len - s->buf_cap;
            stop = 1;
            break;
        }
    }
    return NULL;
}

static void *
siocsifmtu_thread(void *arg)
{
    struct lp_model *s = arg;
    size_t mtu = 1500;
    while (!stop) {
        /* Toggle MTU between large (1500) and tiny (64) to widen the window */
        siocsifmtu(s, mtu);
        mtu = (mtu == 1500) ? 64 : 1500;
        usleep(1);
    }
    return NULL;
}

int main(void) {
    state.sc_ifbuf = calloc(1, 1500 + MLPIPHDRLEN);
    state.buf_cap  = 1500 + MLPIPHDRLEN;
    state.if_mtu   = 1500;

    pthread_t intr, ioctl_t;
    pthread_create(&intr,   NULL, lp_intr_thread,     &state);
    pthread_create(&ioctl_t,NULL, siocsifmtu_thread,  &state);
    pthread_join(intr, NULL);
    stop = 1;
    pthread_join(ioctl_t, NULL);

    if (overflow_observed) {
        printf("DF-1795: race confirmed — lp_intr observed mtu+14=%zu-byte bound "
               "with %zu-byte buffer -> %zu-byte heap overflow\n",
               state.if_mtu + MLPIPHDRLEN, state.buf_cap, overflow_bytes);
        printf("This matches sys/dev/netif/plip/if_plip.c:470 (len bound) vs :350 "
               "(new smaller buf) vs :353 (new mtu published LAST).\n");
        return 0;
    }
    printf("DF-1795: race not observed in this short run (kernel has a wider window).\n");
    return 0;
}
