/*
 * DF-0753 — Deterministic proof of the stale-mbuf-pointer / double-free
 * in mpls_output() called from mpls_forward().
 *
 * This harness transcribes the EXACT control flow of:
 *   - mpls_forward()         sys/netproto/mpls/mpls_input.c:173-218
 *   - 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 harness proves:
 *   (A) PUSH via m_prepend (when leading space < sizeof(struct mpls)):
 *       mpls_output's local m is rebound to the new head; the CALLER
 *       (mpls_forward) still holds the OLD pointer.  The old mbuf is still
 *       alive (m_prepend chains it, does not free it), but it is no longer
 *       the chain head — the freshly-pushed label lives only in the new
 *       head that the caller never sees.  if_output receives the WRONG mbuf
 *       (stale head), and on if_output-error m_freem(m) at mpls_input.c:218
 *       DOUBLE-FREES the mbuf that if_output already consumed.
 *
 *   (B) SWAP/POP via m_pullup (when m_len < sizeof(struct mpls)):
 *       m_pullup CAN free the original mbuf and return a new one.  Because
 *       mpls_swap/mpls_pop take m BY VALUE, the rebind never reaches
 *       mpls_output (let alone mpls_forward).  The caller's m is a genuine
 *       DANGLING pointer to freed memory — true UAF.
 *
 * The harness uses a POISONED ALLOCATOR: freed memory is marked with
 * 0xdeadc0de (matching DragonFly's INVARIANTS WEIRD_ADDR semantics) and
 * double-free / use-after-free are detected and reported.
 *
 * Build:  cc -O2 -o mpls_stale_harness mpls_stale_harness.c
 * Run:    ./mpls_stale_harness
 */

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

/* ------------------------------------------------------------------ */
/*  Minimal mbuf model (faithful to the fields the bug touches)        */
/* ------------------------------------------------------------------ */

#define MHLEN        84           /* internal data area for pkthdr mbuf */
#define MLEN         100          /* internal data area for normal mbuf */
#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;           /* allocation sentinel */
    uint32_t state;           /* 0=free(poisoned), 1=live */
    struct mbuf *m_next;
    int  m_flags;
    int  m_len;
    char *m_data;             /* points into m_pktdat or ext_buf */
    int  pkthdr_len;          /* m_pkthdr.len stand-in */
    char m_pktdat[MLEN];      /* internal data area */
    int  refcnt;
} ;

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

/* allocator accounting for leak / double-free detection */
static int alloc_count = 0;
static int free_count  = 0;
static int double_free_detected = 0;
static int uaf_detected = 0;

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++;
    return m;
}

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

/* Check a pointer for liveliness; returns the mbuf or flags UAF */
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 at %s (magic=0x%x)\n",
               (void*)m, 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 "
               "(magic=0x%x)\n", (void*)m, m->magic);
        double_free_detected++;
        return;
    }
    m->state = 0;
    m->magic = DEAD_MAGIC;
    free_count++;
    /* NOTE: we do NOT actually free() the memory so we can detect UAF
     * by reading the poisoned magic.  A real allocator would release it. */
}

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)         */
/*    to->m_flags |= from->m_flags & M_COPYFLAGS;                     */
/*    to->m_pkthdr = from->m_pkthdr;                                   */
/*    SLIST_INIT(&from->m_pkthdr.tags);  (we skip tags here)          */
/*  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;
    /* from->m_flags M_PKTHDR is NOT cleared — faithful to kernel */
}

/* M_LEADINGSPACE (mbuf.h:446) — pkthdr path only here */
static int M_LEADINGSPACE(struct mbuf *m)
{
    return (int)(m->m_data - m->m_pktdat);
}

/* ------------------------------------------------------------------ */
/*  M_PREPEND (mbuf.h:469) — verbatim semantics                       */
/*    if leading space enough: adjust m_data/m_len                    */
/*    else: m = m_prepend(m, len, how)                                */
/* ------------------------------------------------------------------ */
#define M_PREPEND(mp, plen) do {                          \
    if (M_LEADINGSPACE(*(mp)) >= (plen)) {                \
        (*(mp))->m_data -= (plen);                        \
        (*(mp))->m_len   += (plen);                        \
    } else {                                              \
        *(mp) = m_prepend_harness(*(mp), (plen));         \
    }                                                     \
    if (*(mp) && (*(mp))->m_flags & M_PKTHDR)             \
        (*(mp))->pkthdr_len += (plen);                    \
} while(0)

/* m_prepend (uipc_mbuf.c:1500) — allocates new head, chains old */
static struct mbuf *m_prepend_harness(struct mbuf *m, int len)
{
    struct mbuf *mn;
    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);     /* moves pkthdr to new head */
    mn->m_next = m;               /* OLD m is chained, NOT freed */
    mn->m_len  = len;
    mn->m_data = mn->m_pktdat;    /* new data at start */
    return mn;                    /* returns NEW head */
}

/* ------------------------------------------------------------------ */
/*  m_pullup (uipc_mbuf.c:2103) — simplified: when we need to pull    */
/*  the first `len` bytes into a contiguous leading mbuf.             */
/*  The bug-critical branch: if the first mbuf has a cluster (M_EXT)  */
/*  or no room, we allocate a NEW mbuf, M_MOVE_PKTHDR, and the OLD    */
/*  first mbuf is m_free'd after copying.  Returns new head (may be   */
/*  the SAME pointer if no realloc was needed).                       */
/* ------------------------------------------------------------------ */
static struct mbuf *m_pullup_harness(struct mbuf *n, int len)
{
    /* Fast path: first mbuf already has enough contiguous data */
    if (n->m_len >= len)
        return n;
    /* Slow path: need new leading mbuf */
    {
        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);
        /* copy `len` bytes from the chain into m, freeing consumed mbufs */
        {
            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;
                /* free the source mbuf we just consumed */
                m_free_one(src);
                src = next;
            }
        }
        m->m_data = m->m_pktdat;
        m->m_next = NULL; /* chain truncated for the demo */
        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;   /* rt_key family == AF_MPLS */
};
#define AF_MPLS 35
#define AF_INET  2

/* ------------------------------------------------------------------ */
/*  THE BUGGY FUNCTIONS — transcribed verbatim from the kernel         */
/* ------------------------------------------------------------------ */

/* mpls_push (mpls_output.c:152) — takes struct mbuf** so it DOES update
 * mpls_output's local m.  The bug is one level up: mpls_output itself
 * takes m by value from ITS caller. */
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 -1;
    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;   /* host order for the demo */
    }
    (*m)->m_flags |= M_MPLSLABELED;
    return 0;
}

/* mpls_swap (mpls_output.c:171) — takes m BY VALUE.  m_pullup rebinds
 * only the LOCAL m.  Caller (mpls_output) never sees the new head. */
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 */
        if (m == NULL) return -1;
    }
    {
        struct mpls *p = (struct mpls*)m->m_data;
        uint32_t buf = p->mpls_shim;
        int ttl = MPLS_TTL(buf);
        if (--ttl <= 0) return -2;
        buf = 0;
        MPLS_SET_LABEL(buf, label);
        MPLS_SET_TTL(buf, ttl);
        p->mpls_shim = buf;
    }
    return 0;
}

/* mpls_pop (mpls_output.c:196) — same by-value issue as mpls_swap */
static int mpls_pop(struct mbuf *m, int *sbit)
{
    if (m->m_len < MPLS_SHIM_LEN) {
        m = m_pullup_harness(m, MPLS_SHIM_LEN);
        if (m == NULL) return -1;
    }
    {
        struct mpls *p = (struct mpls*)m->m_data;
        uint32_t buf = p->mpls_shim;
        *sbit = MPLS_STACK(buf);
    }
    /* m_adj(m, sizeof(struct mpls)) — advance data past the popped label */
    m->m_data += MPLS_SHIM_LEN;
    m->m_len  -= MPLS_SHIM_LEN;
    return 0;
}

/* mpls_output (mpls_output.c:49) — takes m BY VALUE.  This is the
 * heart of DF-0753/0754: the rebound local never escapes. */
static int mpls_output(struct mbuf *m, struct rtentry *rt)
{
    int i, stackempty;
    int ttl = 255;
    stackempty = rt->from_mpls ? 0 : 1;

    for (i = 0; i < rt->nshim; i++) {
        struct sockaddr_mpls *s = &rt->shim[i];
        int err;
        switch (s->smpls_op) {
        case MPLSLOP_PUSH:
            err = mpls_push(&m, s->smpls_label,
                            (stackempty && i == 0) ? 1 : 0, ttl);
            if (err) return err;
            stackempty = 0;
            break;
        case MPLSLOP_SWAP:
            if (stackempty) return -3;
            err = mpls_swap(m, s->smpls_label);   /* m by value! */
            if (err) return err;
            break;
        case MPLSLOP_POP:
            if (stackempty) return -3;
            { int sb; err = mpls_pop(m, &sb); }    /* m by value! */
            if (err) return err;
            if (0) stackempty = 1; /* demo: we don't track pop result */
            break;
        }
    }
    return 0;
}

/* mpls_forward (mpls_input.c:173) — the VICTIM caller.  Holds its own
 * local m which goes stale when mpls_output reallocs. */
static int if_output_calls = 0;
static int if_output_consume_free = 1;  /* if_output consumes mbuf (DragonFly) */

static int fake_if_output(struct mbuf *m)
{
    if_output_calls++;
    /* Check: is this mbuf actually live? */
    if (check_live(m, "if_output(m)")) {
        printf("  if_output: received LIVE mbuf %p (m_len=%d pkthdr.len=%d)\n",
               (void*)m, m->m_len, m->pkthdr_len);
        if (m->m_flags & M_MPLSLABELED)
            printf("         (has M_MPLSLABELED — but is it the RIGHT head?)\n");
        else
            printf("         *** mbuf is NOT the pushed-label head (STALE) ***\n");
    }
    if (if_output_consume_free) {
        /* DragonFly if_output/ifq_dispatch consumes the mbuf */
        m_freem(m);
    }
    return 0;  /* success path: no error, so no double-free here */
}

static int fake_if_output_error(struct mbuf *m)
{
    if_output_calls++;
    check_live(m, "if_output_error(m)");
    if (if_output_consume_free)
        m_freem(m);   /* consumed AND freed */
    return -1;        /* return error → caller does m_freem → DOUBLE-FREE */
}

static void mpls_forward(struct mbuf *m, struct rtentry *rt,
                         int (*ifp_if_output)(struct mbuf *))
{
    int error;
    printf("  mpls_forward: m=%p (head, m_flags=0x%x)\n",
           (void*)m, m->m_flags);
    error = mpls_output(m, rt);              /* m passed BY VALUE */
    if (error) { printf("  mpls_output returned %d\n", error); goto bad; }
    error = ifp_if_output(m);                /* STALE m if realloc happened */
    if (error) { printf("  if_output returned %d\n", error); goto bad; }
    printf("  mpls_forward: forwarded OK (no double-free path)\n");
    return;
bad:
    printf("  mpls_forward bad: m_freem(%p) — ", (void*)m);
    check_live(m, "m_freem at bad:");
    m_freem(m);   /* *** DOUBLE-FREE if if_output already freed m *** */
}

/* ------------------------------------------------------------------ */
/*  Test scenarios                                                     */
/* ------------------------------------------------------------------ */

/* Build a received MPLS frame: m_data at offset ETHER_HDR_LEN so there
 * is exactly ETHER_HDR_LEN bytes of leading space (faithful to ether_input
 * stripping the ethernet header). */
static struct mbuf *make_rx_frame(int payload_len, int leading_space)
{
    struct mbuf *m = m_gethdr(0, 0);
    /* position m_data to leave exactly `leading_space` bytes of headroom */
    m->m_data = m->m_pktdat + leading_space;
    memset(m->m_pktdat, 0xAA, sizeof(m->m_pktdat));
    /* write a dummy MPLS label at m_data */
    {
        struct mpls *p = (struct mpls*)m->m_data;
        MPLS_SET_LABEL(p->mpls_shim, 100);
        MPLS_SET_STACK(p->mpls_shim, 1);
        MPLS_SET_TTL(p->mpls_shim, 64);
    }
    m->m_len = MPLS_SHIM_LEN + payload_len;
    m->pkthdr_len = m->m_len;
    return m;
}

static void reset_accounting(void)
{
    alloc_count = free_count = 0;
    double_free_detected = uaf_detected = 0;
    if_output_calls = 0;
}

static void report(const char *label)
{
    printf("\n=== %s ===\n", label);
    printf("  allocs=%d frees=%d  double_free=%d  uaf=%d\n",
           alloc_count, free_count, double_free_detected, uaf_detected);
    if (double_free_detected)
        printf("  *** DOUBLE-FREE CONFIRMED (DF-0753 mpls_input.c:218) ***\n");
    if (uaf_detected)
        printf("  *** USE-AFTER-FREE CONFIRMED (stale m in mpls_forward) ***\n");
}

int main(void)
{
    struct rtentry rt_push, rt_swap_frag, rt_push_noheadroom;
    int i;

    printf("DF-0753 stale-mbuf-pointer / double-free harness\n");
    printf("Transcribes mpls_output/mpls_push/mpls_swap/mpls_pop/mpls_forward\n");
    printf("and m_prepend/m_pullup verbatim from sys/netproto/mpls/ & sys/kern/uipc_mbuf.c\n\n");

    /* ---- Scenario A: PUSH with insufficient leading space ----
     * This is the DF-0753 headline case.  ether_input normally leaves
     * 14 bytes of headroom (>= 3 PUSHes * 4 = 12), so this requires an
     * unusual frame layout — but the CODE PATH is real and fires the
     * instant leading space < 4 on a PUSH. */
    printf("--------------------------------------------------------\n");
    printf("Scenario A: PUSH, leading_space=2 (< sizeof(struct mpls)=4)\n");
    printf("           -> m_prepend allocates new head, caller goes stale\n");
    printf("--------------------------------------------------------\n");
    reset_accounting();
    memset(&rt_push_noheadroom, 0, sizeof(rt_push_noheadroom));
    rt_push_noheadroom.from_mpls = 1;
    rt_push_noheadroom.nshim = 1;
    rt_push_noheadroom.shim[0].smpls_op = MPLSLOP_PUSH;
    rt_push_noheadroom.shim[0].smpls_label = 999;
    {
        struct mbuf *m = make_rx_frame(20, /*leading_space=*/2);
        /* simulate if_output that SUCCEEDS (consumes m) */
        mpls_forward(m, &rt_push_noheadroom, fake_if_output);
    }
    report("A: PUSH no-headroom, if_output success");

    /* ---- Scenario A2: same but if_output returns error ----
     * This is the DOUBLE-FREE: if_output consumed+freed the stale m,
     * then mpls_forward:218 m_freem(m) frees it again. */
    printf("\n--------------------------------------------------------\n");
    printf("Scenario A2: PUSH no-headroom, if_output RETURNS ERROR\n");
    printf("            -> if_output freed m, then m_freem(m) = DOUBLE-FREE\n");
    printf("--------------------------------------------------------\n");
    reset_accounting();
    {
        struct mbuf *m = make_rx_frame(20, /*leading_space=*/2);
        mpls_forward(m, &rt_push_noheadroom, fake_if_output_error);
    }
    report("A2: PUSH no-headroom, if_output error -> DOUBLE-FREE");

    /* ---- Scenario B: SWAP on a fragmented chain (m_len < 4) ----
     * mpls_swap takes m BY VALUE; m_pullup frees the old m and returns
     * a new one, but mpls_output never sees it.  The caller's m is a
     * genuine dangling pointer. */
    printf("\n--------------------------------------------------------\n");
    printf("Scenario B: SWAP, first mbuf has m_len=2 (< 4) -> m_pullup\n");
    printf("           frees old m, returns new; caller's m DANGLING\n");
    printf("--------------------------------------------------------\n");
    reset_accounting();
    memset(&rt_swap_frag, 0, sizeof(rt_swap_frag));
    rt_swap_frag.from_mpls = 1;
    rt_swap_frag.nshim = 1;
    rt_swap_frag.shim[0].smpls_op = MPLSLOP_SWAP;
    rt_swap_frag.shim[0].smpls_label = 200;
    {
        /* build a fragmented chain: first mbuf has only 2 bytes of the
         * MPLS label; the rest is in m_next */
        struct mbuf *m = m_gethdr(0, 0);
        struct mbuf *m2 = mbuf_alloc(0);
        m->m_flags |= M_MPLSLABELED;
        m->m_len = 2;   /* < sizeof(struct mpls) -> triggers m_pullup */
        memcpy(m->m_pktdat, "\x00\x00", 2);
        m->m_data = m->m_pktdat;
        m2->m_len = 32;
        memset(m2->m_pktdat, 0xBB, 32);
        m2->m_data = m2->m_pktdat;
        m->m_next = m2;
        m->pkthdr_len = 34;
        mpls_forward(m, &rt_swap_frag, fake_if_output);
    }
    report("B: SWAP fragmented -> m_pullup UAF + double-free");

    /* ---- Scenario C: control — leading_space=14 (normal ether_input) ----
     * 14 bytes headroom >= 4, so PUSH uses the fast path (m_data -= 4),
     * NO realloc, NO stale pointer, NO bug.  This proves the bug is
     * headroom-dependent. */
    printf("\n--------------------------------------------------------\n");
    printf("Scenario C: CONTROL — leading_space=14 (normal ether_input)\n");
    printf("           PUSH fast-path, NO realloc, NO bug\n");
    printf("--------------------------------------------------------\n");
    reset_accounting();
    {
        struct mbuf *m = make_rx_frame(20, /*leading_space=*/ETHER_HDR_LEN);
        mpls_forward(m, &rt_push_noheadroom, fake_if_output);
    }
    report("C: control — no bug (explains why live trigger is hard)");

    /* ---- Scenario D: 3 PUSHes (rt_shim full) with 14 bytes headroom ----
     * 3 * 4 = 12 bytes consumed, 14 - 12 = 2 bytes left.  No 4th PUSH
     * possible (rt_shim[3] / MPLS_MAXLOPS=3).  Bug does NOT fire on
     * standard ethernet frames. */
    printf("\n--------------------------------------------------------\n");
    printf("Scenario D: 3 PUSHes (max rt_shim), leading_space=14\n");
    printf("           3*4=12 <= 14 headroom, fast-path, NO realloc\n");
    printf("--------------------------------------------------------\n");
    reset_accounting();
    memset(&rt_push, 0, sizeof(rt_push));
    rt_push.from_mpls = 1;
    rt_push.nshim = 3;
    for (i = 0; i < 3; i++) {
        rt_push.shim[i].smpls_op = MPLSLOP_PUSH;
        rt_push.shim[i].smpls_label = 100 + i;
    }
    {
        struct mbuf *m = make_rx_frame(20, /*leading_space=*/ETHER_HDR_LEN);
        mpls_forward(m, &rt_push, fake_if_output);
    }
    report("D: 3 PUSHes normal headroom — no bug");

    printf("\n=========================================================\n");
    printf("DF-0753 harness complete.\n");
    printf("Root cause: mpls_output() takes `struct mbuf *m` BY VALUE.\n");
    printf("  PUSH via m_prepend, and SWAP/POP via m_pullup, rebind the\n");
    printf("  LOCAL m in mpls_output — the caller mpls_forward never sees\n");
    printf("  the new head.  Forwarding then uses a STALE pointer.\n");
    printf("  On if_output error: m_freem(stale) = DOUBLE-FREE.\n");
    printf("  On SWAP/POP m_pullup: stale = freed memory = UAF.\n");
    printf("Fix: change mpls_output to take `struct mbuf **mp` and write\n");
    printf("  the new head through *mp at every rebind.\n");
    return 0;
}
