/*
 * DF-0754 — Deterministic proof of the TWO manifestations of the
 * mpls_output() by-value bug, as seen by the mpls_output_process() and
 * ip_output() callers (manifestations 1 & 2 of the finding).
 *
 * (DF-0753 already covered manifestation 3 — the mpls_forward() caller in
 *  mpls_input.c.  This harness covers the OTHER two callers, which are the
 *  ones cited in the DF-0754 finding.)
 *
 * This harness transcribes the EXACT control flow of:
 *   - ip_output() MPLS dispatch    sys/netinet/ip_output.c:694-700, 738-744
 *   - mpls_output_process()        sys/netproto/mpls/mpls_output.c:134-150
 *   - mpls_output()                sys/netproto/mpls/mpls_output.c:49-129
 *   - mpls_push()                  sys/netproto/mpls/mpls_output.c:152-169
 *   - mpls_swap()                  sys/netproto/mpls/mpls_output.c:171-194
 *   - mpls_pop()                   sys/netproto/mpls/mpls_output.c:196-212
 *   - m_prepend()                  sys/kern/uipc_mbuf.c:1500-1520
 *   - m_pullup()                   sys/kern/uipc_mbuf.c:2103-2158
 *   - M_PREPEND / M_MOVE_PKTHDR    sys/sys/mbuf.h
 *
 * with faithful userspace stand-ins for struct mbuf / m_freem / m_gethdr.
 *
 * The bug: mpls_output() takes `struct mbuf *m` BY VALUE (mpls_output.c:50).
 * When mpls_push/mpls_swap/mpls_pop reallocate the head mbuf (via M_PREPEND
 * or m_pullup), only the LOCAL copy of m inside mpls_output is rebound.
 * mpls_output_process() (which called mpls_output(m,...) by value at :143)
 * and ip_output() (which called mpls_output_process(m,...) by value at :695)
 * NEVER see the new head.  Two consequences:
 *
 *   MANIFESTATION 1 (double-free / leak, mpls_output.c:143-146):
 *     If mpls_output() returns an error after a realloc, mpls_output_process()
 *     does m_freem(m) on the stale pointer.  Two sub-cases:
 *       M1a: m_prepend OOM  -> m already freed inside mpls_push   -> DOUBLE-FREE
 *       M1b: m_pullup OOM    -> m already freed inside mpls_swap   -> DOUBLE-FREE
 *       M1c: PUSH ok then later op fails -> stale head freed, NEW head LEAKED
 *
 *   MANIFESTATION 2 (stale-mbuf-to-driver, ip_output.c:698 / :742):
 *     On the SUCCESS path, mpls_output_process() returns TRUE and ip_output()
 *     hands the stale `m` to ifp->if_output().  Two sub-cases:
 *       M2a: PUSH realloc succeeded -> m is the OLD (demoted) head, missing
 *            the freshly-pushed MPLS label; the NEW head is LEAKED.  Driver
 *            transmits garbage / wrong packet.
 *       M2b: SWAP/POP m_pullup realloc succeeded -> m points to FREED memory
 *            -> UAF when if_output dereferences it.
 *
 * The harness uses a POISONED ALLOCATOR (freed memory marked 0xdeadc0de,
 * matching DragonFly's INVARIANTS WEIRD_ADDR) and detects double-free / UAF.
 * An injection knob (fail_after) forces m_prepend/m_pullup OOM deterministically
 * to drive M1a/M1b.
 *
 * Build:  cc -O2 -Wall -o harness harness.c
 * Run:    ./harness
 */

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdint.h>
#include <assert.h>

/* Userspace stand-ins for kernel-only defines used by the transcribed code. */
#define ENOBUFS    105   /* mpls_output.c return values */
#define ETIMEDOUT  110
#define ENOTSUP    91
typedef unsigned char boolean_t;
#ifndef TRUE
#define TRUE 1
#endif
#ifndef FALSE
#define FALSE 0
#endif

/* ------------------------------------------------------------------ */
/*  Minimal mbuf model (faithful to the fields the bug touches)        */
/*  Identical to the proven DF-0753 harness.                           */
/* ------------------------------------------------------------------ */

#define MHLEN        84
#define MLEN         100
#define M_PKTHDR     0x02
#define M_EXT        0x04
#define M_MPLSLABELED 0x4000
#define ETHER_HDR_LEN 14
#define MPLS_SHIM_LEN 4

struct mbuf {
    uint32_t magic;
    uint32_t state;           /* 0=free(poisoned), 1=live */
    struct mbuf *m_next;
    int  m_flags;
    int  m_len;
    char *m_data;
    int  pkthdr_len;
    char m_pktdat[MLEN];
    int  refcnt;
    int  label;               /* tagged by tests to identify which mbuf */
};

#define LIVE_MAGIC  0x4d42464d   /* "MBFM" */
#define DEAD_MAGIC  0xdeadc0de

static int alloc_count = 0;
static int free_count  = 0;
static int double_free_detected = 0;
static int uaf_detected = 0;
static int live_allocs = 0;      /* outstanding (not freed) mbufs = leak meter */

/* OOM injection: when > 0, the next fail_after successful mbuf_alloc()s
 * in m_prepend/m_pullup return NULL (simulating M_NOWAIT exhaustion). */
static int fail_after = -1;      /* -1 = never fail */
static int alloc_budget = 0;     /* counts down inside prepend/pullup paths */

static struct mbuf *mbuf_alloc(int flags)
{
    struct mbuf *m = calloc(1, sizeof(struct mbuf));
    assert(m);
    m->magic = LIVE_MAGIC;
    m->state = 1;
    m->m_flags = flags;
    m->m_data = m->m_pktdat;
    m->m_next = NULL;
    m->refcnt = 1;
    alloc_count++;
    live_allocs++;
    return m;
}

static struct mbuf *m_gethdr(int how, int type)
{
    (void)how; (void)type;
    return mbuf_alloc(M_PKTHDR);
}

static struct mbuf *check_live(struct mbuf *m, const char *where)
{
    if (m == NULL) return NULL;
    if (m->state == 0 || m->magic == DEAD_MAGIC) {
        printf("  [UAF] deref of freed mbuf %p (label=%d) at %s (magic=0x%x)\n",
               (void*)m, m->label, where, m->magic);
        uaf_detected++;
        return NULL;
    }
    return m;
}

static void m_free_one(struct mbuf *m)
{
    if (m == NULL) return;
    if (m->state == 0 || m->magic == DEAD_MAGIC) {
        printf("  [DOUBLE-FREE] m_freem on already-freed mbuf %p (label=%d) "
               "(magic=0x%x)\n", (void*)m, m->label, m->magic);
        double_free_detected++;
        return;
    }
    m->state = 0;
    m->magic = DEAD_MAGIC;
    free_count++;
    live_allocs--;
}

static void m_freem(struct mbuf *m)
{
    while (m) {
        struct mbuf *next = m->m_next;
        m_free_one(m);
        m = next;
    }
}

/* ------------------------------------------------------------------ */
/*  Faithful transcription of M_MOVE_PKTHDR (uipc_mbuf.c:2551)         */
/*  NOTE: does NOT clear M_PKTHDR from `from`.                         */
/* ------------------------------------------------------------------ */
static void M_MOVE_PKTHDR(struct mbuf *to, struct mbuf *from)
{
    assert(to->m_flags & M_PKTHDR);
    assert(from->m_flags & M_PKTHDR);
    to->m_flags |= (from->m_flags & (M_PKTHDR|M_MPLSLABELED));
    to->pkthdr_len = from->pkthdr_len;
}

static int M_LEADINGSPACE(struct mbuf *m)
{
    return (int)(m->m_data - m->m_pktdat);
}

/* ------------------------------------------------------------------ */
/*  M_PREPEND (mbuf.h:469)                                            */
/* ------------------------------------------------------------------ */
#define M_PREPEND(mp, plen) do {                          \
    if (M_LEADINGSPACE(*(mp)) >= (plen)) {                \
        (*(mp))->m_data -= (plen);                        \
        (*(mp))->m_len   += (plen);                        \
        (*(mp))->pkthdr_len += (plen);                     \
    } else {                                              \
        *(mp) = m_prepend_harness(*(mp), (plen));         \
        if (*(mp)) (*(mp))->pkthdr_len += (plen);         \
    }                                                     \
} while(0)

/* m_prepend (uipc_mbuf.c:1500) — allocates new head, chains old.
 * BUG-CRITICAL OOM path: if alloc fails, m_freem(m) and return NULL. */
static struct mbuf *m_prepend_harness(struct mbuf *m, int len)
{
    struct mbuf *mn;
    if (fail_after >= 0 && alloc_budget >= fail_after) {
        printf("  [inject] m_prepend: alloc OOM -> m_freem(m=%p label=%d) + return NULL\n",
               (void*)m, m->label);
        m_freem(m);          /* *** OLD m is FREED here (uipc_mbuf.c:1510) *** */
        return NULL;
    }
    alloc_budget++;
    if (m->m_flags & M_PKTHDR)
        mn = m_gethdr(0, 0);
    else
        mn = mbuf_alloc(0);
    if (mn == NULL) { m_freem(m); return NULL; }
    if (m->m_flags & M_PKTHDR)
        M_MOVE_PKTHDR(mn, m);
    mn->m_next = m;               /* OLD m is chained, NOT freed */
    mn->m_len  = len;
    mn->m_data = mn->m_pktdat;
    return mn;                    /* returns NEW head */
}

/* ------------------------------------------------------------------ */
/*  m_pullup (uipc_mbuf.c:2103) — slow path frees consumed source mbufs.
 *  BUG-CRITICAL OOM path: if alloc fails, m_freem(n) and return NULL. */
/* ------------------------------------------------------------------ */
static struct mbuf *m_pullup_harness(struct mbuf *n, int len)
{
    if (n->m_len >= len)
        return n;
    if (fail_after >= 0 && alloc_budget >= fail_after) {
        printf("  [inject] m_pullup: alloc OOM -> m_freem(n=%p label=%d) + return NULL\n",
               (void*)n, n->label);
        m_freem(n);          /* *** OLD n is FREED here *** */
        return NULL;
    }
    alloc_budget++;
    {
        struct mbuf *m;
        if (n->m_flags & M_PKTHDR)
            m = m_gethdr(0, 0);
        else
            m = mbuf_alloc(0);
        if (m == NULL) { m_freem(n); return NULL; }
        m->m_len = 0;
        if (n->m_flags & M_PKTHDR)
            M_MOVE_PKTHDR(m, n);
        {
            int want = len;
            struct mbuf *src = n;
            while (want > 0 && src) {
                struct mbuf *next = src->m_next;
                int cnt = src->m_len < want ? src->m_len : want;
                memcpy(m->m_pktdat + m->m_len, src->m_data, cnt);
                m->m_len += cnt;
                want     -= cnt;
                m_free_one(src);      /* consumed source mbuf FREED */
                src = next;
            }
        }
        m->m_data = m->m_pktdat;
        m->m_next = NULL;
        return m;                     /* NEW head, old n is FREED */
    }
}

/* ------------------------------------------------------------------ */
/*  MPLS label encode/decode (faithful to sys/netproto/mpls/mpls.h)    */
/* ------------------------------------------------------------------ */
struct mpls { uint32_t mpls_shim; };
#define MPLS_LABEL(s)   ((s >> 12) & 0xfffff)
#define MPLS_STACK(s)   ((s >> 8) & 1)
#define MPLS_TTL(s)     (s & 0xff)
#define MPLS_SET_LABEL(b,l) (b |= ((l & 0xfffff) << 12))
#define MPLS_SET_STACK(b,s) (b |= ((s & 1) << 8))
#define MPLS_SET_TTL(b,t)   (b |= (t & 0xff))

struct sockaddr_mpls { int smpls_op; uint32_t smpls_label; };
#define MPLSLOP_PUSH 1
#define MPLSLOP_SWAP 2
#define MPLSLOP_POP  3

struct rtentry {
    struct sockaddr_mpls shim[3];
    int nshim;
    int from_mpls;
    int rt_flags;
};
#define RTF_MPLSOPS 0x100
#define AF_MPLS 35
#define AF_INET  2

static int label_counter = 100;

/* ------------------------------------------------------------------ */
/*  THE BUGGY FUNCTIONS — transcribed verbatim from the kernel.        */
/*  *** mpls_output / mpls_swap / mpls_pop take m BY VALUE ***         */
/* ------------------------------------------------------------------ */

/* mpls_push (mpls_output.c:152) — takes struct mbuf** so it DOES update
 * mpls_output's local m.  The bug is one level up. */
static int mpls_push(struct mbuf **m, uint32_t label, int s, int ttl)
{
    uint32_t buf = 0;
    M_PREPEND(m, MPLS_SHIM_LEN);
    if (*m == NULL) return (ENOBUFS);   /* mpls_output.c:158-159 */
    MPLS_SET_LABEL(buf, label);
    MPLS_SET_STACK(buf, s);
    MPLS_SET_TTL(buf, ttl);
    {
        struct mpls *p = (struct mpls*)(*m)->m_data;
        p->mpls_shim = buf;
    }
    (*m)->m_flags |= M_MPLSLABELED;
    (*m)->label = ++label_counter;
    printf("  mpls_push: new head m=%p label=%d\n", (void*)(*m), (*m)->label);
    return 0;
}

/* mpls_swap (mpls_output.c:171) — takes m BY VALUE. */
static int mpls_swap(struct mbuf *m, uint32_t label)
{
    if (m->m_len < MPLS_SHIM_LEN) {
        m = m_pullup_harness(m, MPLS_SHIM_LEN);   /* local rebind ONLY */
        if (m == NULL) return (ENOBUFS);
    }
    {
        struct mpls *p = (struct mpls*)m->m_data;
        uint32_t buf = p->mpls_shim;
        int ttl = MPLS_TTL(buf);
        if (--ttl <= 0) return (ETIMEDOUT);
        buf = 0;
        MPLS_SET_LABEL(buf, label);
        MPLS_SET_TTL(buf, ttl);
        p->mpls_shim = buf;
    }
    return 0;
}

/* mpls_pop (mpls_output.c:196) — takes m BY VALUE. */
static int mpls_pop(struct mbuf *m, int *sbit)
{
    if (m->m_len < MPLS_SHIM_LEN) {
        m = m_pullup_harness(m, MPLS_SHIM_LEN);   /* local rebind ONLY */
        if (m == NULL) return (ENOBUFS);
    }
    {
        struct mpls *p = (struct mpls*)m->m_data;
        uint32_t buf = p->mpls_shim;
        *sbit = MPLS_STACK(buf);
    }
    m->m_data += MPLS_SHIM_LEN;
    m->m_len  -= MPLS_SHIM_LEN;
    return 0;
}

/* mpls_output (mpls_output.c:49) — takes m BY VALUE.  HEART OF THE BUG. */
static int mpls_output(struct mbuf *m, struct rtentry *rt)
{
    int i, error = 0;
    int stackempty;
    int ttl = 255;
    stackempty = rt->from_mpls ? 0 : 1;

    for (i = 0; i < rt->nshim; i++) {
        struct sockaddr_mpls *s = &rt->shim[i];
        switch (s->smpls_op) {
        case MPLSLOP_PUSH:
            error = mpls_push(&m, s->smpls_label,
                              (stackempty && i == 0) ? 1 : 0, ttl);
            if (error) return (error);            /* :88 — local m lost */
            stackempty = 0;
            m->m_flags |= M_MPLSLABELED;
            break;
        case MPLSLOP_SWAP:
            if (stackempty) return (ENOTSUP);
            error = mpls_swap(m, s->smpls_label);  /* m by value! */
            if (error) return (error);             /* :102 */
            break;
        case MPLSLOP_POP:
            if (stackempty) return (ENOTSUP);
            { int sb; error = mpls_pop(m, &sb); }  /* m by value! */
            if (error) return (error);             /* :114 */
            break;
        default:
            return (ENOTSUP);
        }
    }
    return (error);     /* *** caller's m is UNCHANGED — stale *** */
}

/* ------------------------------------------------------------------ */
/*  mpls_output_process (mpls_output.c:134) — BY VALUE caller.         */
/*  MANIFESTATION 1 lives here: error-path m_freem(m) at :145.         */
/* ------------------------------------------------------------------ */
static boolean_t mpls_output_process(struct mbuf *m, struct rtentry *rt)
{
    int error;
    if (!(rt->rt_flags & RTF_MPLSOPS))   /* :140 */
        return TRUE;
    error = mpls_output(m, rt);          /* :143 — m by VALUE */
    if (error) {
        printf("  mpls_output_process: error=%d -> m_freem(stale m=%p label=%d)\n",
               error, (void*)m, m ? m->label : -1);
        m_freem(m);                       /* :145 — DOUBLE-FREE / UAF */
        return FALSE;
    }
    return TRUE;                          /* success: caller uses stale m */
}

/* ------------------------------------------------------------------ */
/*  ip_output MPLS dispatch (ip_output.c:694-700) — the upstream caller.
 *  MANIFESTATION 2 lives here: success-path ifp->if_output(stale m) at :698. */
/* ------------------------------------------------------------------ */
static int if_output_deref_count = 0;
static int if_output_received_label = -1;

static int fake_if_output(struct mbuf *m)
{
    if_output_deref_count++;
    printf("  if_output: received m=%p label=%d\n", (void*)m, m ? m->label : -1);
    /* The driver dereferences m (reads m_len, m_data, m_flags, pkthdr.len).
     * If m is freed memory -> UAF.  If m is a demoted head -> wrong packet. */
    if (check_live(m, "if_output(m) deref")) {
        if_output_received_label = m->label;
        printf("  if_output: m LIVE, m_len=%d pkthdr.len=%d M_PKTHDR=%d "
               "M_MPLSLABELED=%d\n",
               m->m_len, m->pkthdr_len,
               (m->m_flags & M_PKTHDR)?1:0,
               (m->m_flags & M_MPLSLABELED)?1:0);
        /* faithfully: if_output/ifq_dispatch consumes (frees) the mbuf */
        m_freem(m);
    }
    return 0;
}

/* Simulate ip_output()'s MPLS dispatch + if_output call (ip_output.c:694-700). */
static void ip_output_mpls_dispatch(struct mbuf *m, struct rtentry *rt)
{
    boolean_t cont;
    printf("  ip_output: m=%p label=%d (head)\n", (void*)m, m->label);
    cont = mpls_output_process(m, rt);            /* ip_output.c:695 — by value */
    if (!cont) {
        printf("  ip_output: mpls_output_process=FALSE -> goto done (m already freed at :145)\n");
        return;                                    /* goto done */
    }
    printf("  ip_output: mpls_output_process=TRUE -> ifp->if_output(stale m=%p)\n",
           (void*)m);
    (void)fake_if_output(m);                       /* ip_output.c:698 — STALE m */
}

/* ------------------------------------------------------------------ */
/*  Test helpers                                                       */
/* ------------------------------------------------------------------ */
static struct mbuf *make_ip_pkt(int leading_space)
{
    struct mbuf *m = m_gethdr(0, 0);
    m->m_data = m->m_pktdat + leading_space;
    memset(m->m_pktdat, 0xAA, sizeof(m->m_pktdat));
    /* dummy IP header (first 4 bytes = version/IHL + ttl byte at offset 8) */
    m->m_data[0] = 0x45; m->m_data[8] = 64;       /* ip_ttl = 64 */
    m->m_len = 20;
    m->pkthdr_len = 20;
    m->label = ++label_counter;
    return m;
}

static struct mbuf *make_mpls_frag_chain(void)
{
    /* fragmented MPLS chain: first mbuf m_len=2 (< 4) -> triggers m_pullup.
     * Write a valid MPLS shim (TTL=64 in the low byte) across the split so
     * that after m_pullup reassembles it, mpls_swap sees a live TTL and
     * SUCCEEDS (returns 0) — exercising the success-path stale-mbuf-to-driver
     * (manifestation 2b) rather than the error path. */
    struct mbuf *m = m_gethdr(0, 0);
    struct mbuf *m2 = mbuf_alloc(0);
    uint32_t shim = 0;
    MPLS_SET_LABEL(shim, 100);
    MPLS_SET_STACK(shim, 1);
    MPLS_SET_TTL(shim, 64);          /* TTL=64 so SWAP succeeds (--ttl=63) */
    m->m_flags |= M_MPLSLABELED;
    m->m_len = 2;
    /* scatter the 4-byte shim: first 2 bytes in m, next 2 in m2 */
    memcpy(m->m_data, &shim, 2);
    memcpy(m2->m_pktdat, ((char*)&shim) + 2, 2);
    m2->m_len = 32;
    memset(m2->m_pktdat + 2, 0xBB, 30);
    m2->m_data = m2->m_pktdat;
    m2->label = ++label_counter;
    m->m_next = m2;
    m->pkthdr_len = 34;
    m->label = ++label_counter;
    return m;
}

static void reset_accounting(void)
{
    alloc_count = free_count = 0;
    double_free_detected = uaf_detected = 0;
    live_allocs = 0;
    if_output_deref_count = 0;
    if_output_received_label = -1;
    fail_after = -1;
    alloc_budget = 0;
}

static void report(const char *label, const char *expect)
{
    printf("\n  --- %s ---\n", label);
    printf("  allocs=%d frees=%d  outstanding(live)=%d  double_free=%d  uaf=%d\n",
           alloc_count, free_count, live_allocs, double_free_detected, uaf_detected);
    printf("  if_output derefs=%d  last_received_label=%d\n",
           if_output_deref_count, if_output_received_label);
    printf("  EXPECT: %s\n", expect);
    if (double_free_detected)
        printf("  *** MANIFESTATION 1 (DOUBLE-FREE) CONFIRMED ***\n");
    if (uaf_detected)
        printf("  *** UAF CONFIRMED ***\n");
    if (live_allocs > 0 && !double_free_detected)
        printf("  *** MBUF LEAK: %d outstanding alloc(s) not freed ***\n", live_allocs);
}

/* ================================================================== */
/*  MAIN — run all manifestation scenarios                             */
/* ================================================================== */
int main(void)
{
    struct rtentry rt_push, rt_swap, rt_push_then_swap;

    printf("================================================================\n");
    printf("DF-0754 harness — mpls_output() by-value bug\n");
    printf("Focus: mpls_output_process() [M1] + ip_output() [M2] callers\n");
    printf("(DF-0753 already covered the mpls_forward()/mpls_input.c caller)\n");
    printf("================================================================\n\n");

    /* ---- MANIFESTATION 1a: PUSH + m_prepend OOM -> DOUBLE-FREE ----
     * mpls_push -> M_PREPEND -> m_prepend OOM -> m_prepend does
     * m_freem(m) and returns NULL.  mpls_output returns ENOBUFS.
     * mpls_output_process:145 does m_freem(m) on the SAME (freed) m. */
    printf("----------------------------------------------------------------\n");
    printf("M1a: PUSH + m_prepend OOM -> mpls_output_process:145 DOUBLE-FREE\n");
    printf("     (mpls_output.c:143 by-value, :145 m_freem on already-freed m)\n");
    printf("----------------------------------------------------------------\n");
    reset_accounting();
    memset(&rt_push, 0, sizeof(rt_push));
    rt_push.from_mpls = 1;
    rt_push.rt_flags = RTF_MPLSOPS;
    rt_push.nshim = 1;
    rt_push.shim[0].smpls_op = MPLSLOP_PUSH;
    rt_push.shim[0].smpls_label = 999;
    {
        struct mbuf *m = make_ip_pkt(/*leading_space=*/0);  /* force m_prepend */
        fail_after = 0;  /* first m_prepend alloc -> OOM */
        ip_output_mpls_dispatch(m, &rt_push);
    }
    report("M1a result", "double_free >= 1 (mpls_output_process:145 frees freed m)");

    /* ---- MANIFESTATION 1b: SWAP + m_pullup OOM -> DOUBLE-FREE ----
     * mpls_swap -> m_pullup OOM -> m_pullup does m_freem(n) and returns NULL.
     * mpls_swap returns ENOBUFS, mpls_output returns ENOBUFS,
     * mpls_output_process:145 m_freem(m) on freed m. */
    printf("\n----------------------------------------------------------------\n");
    printf("M1b: SWAP + m_pullup OOM -> mpls_output_process:145 DOUBLE-FREE\n");
    printf("     (mpls_swap takes m by value; m_pullup frees old m on OOM)\n");
    printf("----------------------------------------------------------------\n");
    reset_accounting();
    memset(&rt_swap, 0, sizeof(rt_swap));
    rt_swap.from_mpls = 1;
    rt_swap.rt_flags = RTF_MPLSOPS;
    rt_swap.nshim = 1;
    rt_swap.shim[0].smpls_op = MPLSLOP_SWAP;
    rt_swap.shim[0].smpls_label = 200;
    {
        struct mbuf *m = make_mpls_frag_chain();  /* m_len=2 -> m_pullup */
        fail_after = 0;  /* m_pullup alloc -> OOM */
        ip_output_mpls_dispatch(m, &rt_swap);
    }
    report("M1b result", "double_free >= 1 (stale m freed by m_pullup, re-freed at :145)");

    /* ---- MANIFESTATION 1c: PUSH ok, then later op errors -> LEAK ----
     * PUSH reallocs (new head mn, old m chained under it).  Then a subsequent
     * op errors (here: unknown op -> ENOTSUP at mpls_output.c:124, which is
     * the cleanest "push-then-error" since it does no alloc/free itself).
     * mpls_output returns error.  mpls_output_process:145 m_freem(stale m)
     * frees only the OLD chain reachable from the stale pointer; the NEW head
     * mn is NOT reachable from stale m (mn->m_next=m, not m->m_next=mn) and
     * is therefore LEAKED.  This is the "new-head leak (push-then-error)"
     * variant named in the finding. */
    printf("\n----------------------------------------------------------------\n");
    printf("M1c: PUSH ok then later op ENOTSUP -> stale head freed, NEW head LEAKED\n");
    printf("     (mpls_output_process:145 frees stale m; new pushed head unreachable)\n");
    printf("----------------------------------------------------------------\n");
    reset_accounting();
    memset(&rt_push_then_swap, 0, sizeof(rt_push_then_swap));
    rt_push_then_swap.from_mpls = 1;
    rt_push_then_swap.rt_flags = RTF_MPLSOPS;
    rt_push_then_swap.nshim = 2;
    rt_push_then_swap.shim[0].smpls_op = MPLSLOP_PUSH;
    rt_push_then_swap.shim[0].smpls_label = 999;
    rt_push_then_swap.shim[1].smpls_op = 99;   /* unknown -> default: ENOTSUP */
    rt_push_then_swap.shim[1].smpls_label = 200;
    {
        struct mbuf *m = make_ip_pkt(/*leading_space=*/0);  /* force m_prepend */
        fail_after = -1;  /* PUSH succeeds, second op errors without alloc */
        ip_output_mpls_dispatch(m, &rt_push_then_swap);
    }
    report("M1c result", "double_free=0 (old chain freed once), "
                       "live_allocs >= 1 (new pushed head LEAKED)");

    /* ---- MANIFESTATION 2a: PUSH realloc, SUCCESS -> stale m to driver ----
     * PUSH succeeds (new head mn created, old m chained under it).
     * mpls_output returns 0, mpls_output_process returns TRUE.
     * ip_output:698 if_output(stale m) — m is the OLD head, missing the
     * freshly-pushed MPLS label.  The driver transmits the WRONG packet.
     * New head mn is LEAKED. */
    printf("\n----------------------------------------------------------------\n");
    printf("M2a: PUSH realloc SUCCESS -> ip_output:698 if_output(STALE m)\n");
    printf("     (driver gets demoted old head; new pushed head leaked)\n");
    printf("----------------------------------------------------------------\n");
    reset_accounting();
    {
        struct mbuf *m = make_ip_pkt(/*leading_space=*/0);  /* force m_prepend */
        fail_after = -1;
        ip_output_mpls_dispatch(m, &rt_push);
    }
    report("M2a result", "if_output_received_label == original IP mbuf label (STALE), "
                       "live_allocs >= 1 (new head leaked)");

    /* ---- MANIFESTATION 2b: SWAP m_pullup realloc, SUCCESS -> UAF ----
     * mpls_swap -> m_pullup (m_len<4) -> realloc, frees old m, returns new.
     * mpls_swap's local m is rebound; mpls_output's & caller's m are NOT.
     * mpls_output returns 0, mpls_output_process returns TRUE.
     * ip_output:698 if_output(stale m) — m is FREED memory -> UAF in driver. */
    printf("\n----------------------------------------------------------------\n");
    printf("M2b: SWAP m_pullup realloc SUCCESS -> ip_output:698 if_output(FREED m)\n");
    printf("     (stale m is freed memory; driver deref = UAF)\n");
    printf("----------------------------------------------------------------\n");
    reset_accounting();
    {
        struct mbuf *m = make_mpls_frag_chain();  /* m_len=2 -> m_pullup realloc */
        fail_after = -1;
        ip_output_mpls_dispatch(m, &rt_swap);
    }
    report("M2b result", "uaf >= 1 (driver deref of freed m at if_output)");

    /* ---- CONTROL: PUSH with adequate leading space -> NO bug ----
     * 14 bytes headroom >= 4 -> M_PREPEND fast path (m_data -= 4), no realloc.
     * Caller's m stays valid.  Proves the bug is realloc-dependent. */
    printf("\n----------------------------------------------------------------\n");
    printf("CONTROL: PUSH with leading_space=14 -> fast path, NO realloc, NO bug\n");
    printf("----------------------------------------------------------------\n");
    reset_accounting();
    {
        struct mbuf *m = make_ip_pkt(/*leading_space=*/ETHER_HDR_LEN);
        fail_after = -1;
        ip_output_mpls_dispatch(m, &rt_push);
    }
    report("CONTROL result", "double_free=0, uaf=0, live_allocs=0 (clean)");

    printf("\n================================================================\n");
    printf("DF-0754 harness complete.\n");
    printf("Root cause: mpls_output() takes `struct mbuf *m` BY VALUE\n");
    printf("  (mpls_output.c:50).  Callers mpls_output_process() (:143) and\n");
    printf("  ip_output() (:695) never see the new head after a realloc.\n");
    printf("  M1: error path m_freem(stale) at :145 = DOUBLE-FREE / leak.\n");
    printf("  M2: success path if_output(stale) at ip_output.c:698 = stale/UAF.\n");
    printf("Fix: mpls_output(struct mbuf **mp) propagates head through *mp\n");
    printf("  (same fix as DF-0753 — closes BOTH findings).\n");
    return 0;
}
