/*
 * DF-0617 — Code-level harness proving the Use-After-Free in
 * ng_ether_rcv_upper() (sys/netgraph7/ether/ng_ether.c:640-666).
 *
 * The vulnerability: bridge_input_p() is called as a statement-expression
 * (return value discarded) at ng_ether.c:658. bridge_input() returns NULL
 * when it consumes/frees the mbuf (IFF_MONITOR, packet-for-bridge-MAC, BPDU,
 * bridge_forward, ether_reinput_oncpu, sender's-own-MAC — see
 * sys/net/bridge/if_bridge.c:2646-2662, 2804-2805, 2936-2960, 2973-2989).
 *
 * Because the return value is discarded, the local variable `m` is never
 * updated and the subsequent `if (m == NULL) return (0);` at line 659 is
 * dead code. Execution falls through to ether_demux_oncpu(ifp, m) at line
 * 664, which dereferences the freed mbuf:
 *   M_ASSERTPKTHDR(m)              — reads m->m_flags          (if_ethersubr.c:992)
 *   KASSERT(m->m_len >= ...)       — reads m->m_len            (if_ethersubr.c:993)
 *   eh = mtod(m, ...)              — dereferences m->m_data    (if_ethersubr.c:996)
 *
 * The canonical correct pattern at sys/net/if_ethersubr.c:1252 is:
 *   m = bridge_input_p(ifp, m);
 *   if (m == NULL) return;
 *
 * This harness replicates the EXACT control flow of ng_ether_rcv_upper()
 * with a poisoned-freed-memory allocator: when bridge_input_p() frees the
 * mbuf, its backing memory is overwritten with 0xDE bytes. The UAF is then
 * observable: ether_demux_oncpu() reads the poison pattern from the freed
 * mbuf. Two modes are provided:
 *
 *   --buggy : replicates the current kernel code (return value discarded)
 *   --fixed : replicates the one-line fix (m = bridge_input_p(ifp, m))
 *
 * Build:  cc -O2 -o uaf_ng_ether uaf_ng_ether.c
 * Run:    ./uaf_ng_ether --buggy   (shows UAF: freed mbuf accessed)
 *         ./uaf_ng_ether --fixed   (shows fix: early return, no access)
 *         ./uaf_ng_ether           (runs both, summary)
 *
 * NOTE: This is a code-level proof. A live runtime trigger requires the
 * netgraph7 ng_ether + if_bridge topology on a bridged NIC, which causes
 * guest instability on this QEMU/vtnet0 guest (the ng_ether input-orphan
 * hooks interfere with normal bridge traffic processing, hanging the guest
 * before the upper-hook injection path can be exercised). The control flow
 * replicated here is an exact transcription of the kernel source.
 */

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

/* ---- Simulated kernel structures (minimal fields for the UAF path) ---- */

struct ether_header {
    uint8_t  ether_dhost[6];
    uint8_t  ether_shost[6];
    uint16_t ether_type;
} __attribute__((packed));

#define ETHER_HDR_LEN   14
#define ETHER_ADDR_LEN  6
#define M_PKTHDR        0x00000002   /* m_flags: packet header present */
#define POISON_BYTE     0xDE

struct mbuf {
    /* The fields accessed by the vulnerable code path, in access order: */
    uint32_t  m_flags;          /* M_ASSERTPKTHDR checks M_PKTHDR        */
    int       m_len;            /* KASSERT(m_len >= ETHER_HDR_LEN)       */
    void     *m_data;           /* mtod() dereferences this              */
    int       m_pkthdr_len;     /* ng_ether_rcv_upper line 646           */
    struct {
        int len;                /* m->m_pkthdr.len                      */
        void *rcvif;            /* m->m_pkthdr.rcvif (line 654)          */
    } m_pkthdr;
    /* padding so the allocation is a realistic size */
    uint8_t   pad[224];
};

struct ifnet {
    int       if_flags;
    void     *if_bridge;        /* non-NULL => bridge member             */
};

/* IFF_MONITOR on the bridge interface */
#define IFF_MONITOR   0x00040000
#define IFF_RUNNING   0x00000040

/* ---- Poisoned allocator: freed memory is overwritten with 0xDE ----
 *
 * Uses a STATIC pool (not malloc/free) so the compiler cannot optimize
 * away the poison write as a "dead store before free" (which -O2 does
 * with real malloc/free due to the UB of accessing freed memory).
 *
 * In the real kernel, m_freem() returns the mbuf to the slab allocator;
 * the backing page may be reused immediately for a different object.
 * Our static pool simulates this: freed mbufs are poisoned with 0xDE
 * and flagged, and the poison persists so the UAF is observable. */

static int g_freed_count = 0;

#define POOL_SIZE 4
static struct mbuf m_pool[POOL_SIZE];
static int m_pool_used[POOL_SIZE];  /* 0=free, 1=allocated */

static struct mbuf *
mock_m_gethdr(int len)
{
    for (int i = 0; i < POOL_SIZE; i++) {
        if (!m_pool_used[i]) {
            struct mbuf *m = &m_pool[i];
            m_pool_used[i] = 1;
            memset(m, 0, sizeof(*m));
            m->m_flags = M_PKTHDR;
            m->m_len = len;
            m->m_pkthdr.len = len;
            m->m_pkthdr.rcvif = NULL;
            /* m_data points to a simulated packet buffer right after the header */
            m->m_data = (void *)((uint8_t *)m + sizeof(*m) - 64);
            /* Write a recognizable Ethernet header into the data area */
            struct ether_header *eh = (struct ether_header *)m->m_data;
            memset(eh->ether_dhost, 0xAA, ETHER_ADDR_LEN);
            memset(eh->ether_shost, 0xBB, ETHER_ADDR_LEN);
            eh->ether_type = 0x0800;
            return m;
        }
    }
    fprintf(stderr, "mbuf pool exhausted\n");
    abort();
    return NULL;
}

static void
mock_m_freem(struct mbuf *m)
{
    /* Simulate m_freem(): poison the mbuf's backing memory with 0xDE.
     * In the real kernel, the slab allocator would record the free and
     * the page could be reclaimed for a different object type. The poison
     * pattern makes the subsequent UAF access observable. */
    if (m) {
        /* volatile sink prevents the compiler from eliding the write */
        volatile unsigned char *p = (volatile unsigned char *)m;
        size_t n = sizeof(*m);
        while (n--) *p++ = POISON_BYTE;
        g_freed_count++;
    }
}

/* ---- Simulated bridge_input() — the IFF_MONITOR NULL-return path ----
 *
 * Transcribed from sys/net/bridge/if_bridge.c:2615-2662:
 *
 *   static struct mbuf *
 *   bridge_input(struct ifnet *ifp, struct mbuf *m)
 *   {
 *       struct bridge_softc *sc = ifp->if_bridge;
 *       ...
 *       if (sc == NULL)
 *           return m;                        // line 2631-2632
 *       ...
 *       if ((bifp->if_flags & IFF_RUNNING) == 0)
 *           goto out;                        // line 2637-2638 -> returns m
 *       ...
 *       if (bifp->if_flags & IFF_MONITOR) { // line 2646
 *           ...
 *           m_freem(m);                      // line 2660
 *           m = NULL;                        // line 2661
 *           goto out;                        // line 2662 -> returns NULL
 *       }
 *       ...
 *   }
 *
 * When IFF_MONITOR is set on the bridge, bridge_input() ALWAYS frees the
 * mbuf and returns NULL, regardless of the packet content.
 */
static struct mbuf *
mock_bridge_input(struct ifnet *ifp, struct mbuf *m)
{
    /* ifp->if_bridge is the bridge softc; non-NULL means we're a member */
    if (ifp->if_bridge == NULL)
        return m;             /* not a bridge member — return untouched */

    /* Simulate bifp (the bridge interface) having IFF_MONITOR set */
    int bifp_flags = IFF_RUNNING | IFF_MONITOR;

    if ((bifp_flags & IFF_RUNNING) == 0)
        return m;             /* bridge not running — return untouched   */

    if (bifp_flags & IFF_MONITOR) {
        /* IFF_MONITOR path (if_bridge.c:2660-2662):
         *   m_freem(m);
         *   m = NULL;
         *   goto out;   // return NULL
         */
        mock_m_freem(m);
        return NULL;          /* mbuf consumed/freed, return NULL        */
    }

    return m;                 /* not consumed — return original mbuf      */
}

/* Function pointer matching the kernel's bridge_input_p */
static struct mbuf *(*bridge_input_p)(struct ifnet *, struct mbuf *) = mock_bridge_input;

/* ---- Simulated ether_demux_oncpu() — the UAF sink ----
 *
 * Transcribed from sys/net/if_ethersubr.c:985-996:
 *
 *   void ether_demux_oncpu(struct ifnet *ifp, struct mbuf *m)
 *   {
 *       M_ASSERTPKTHDR(m);                              // line 992
 *       KASSERT(m->m_len >= ETHER_HDR_LEN, ...);        // line 993
 *       eh = mtod(m, struct ether_header *);            // line 996
 *       ...
 *   }
 */
static int g_uaf_detected = 0;

static void
mock_ether_demux_oncpu(struct ifnet *ifp, struct mbuf *m)
{
    /* M_ASSERTPKTHDR(m) — reads m->m_flags (if_ethersubr.c:992) */
    uint32_t flags_read = m->m_flags;

    /* KASSERT(m->m_len >= ETHER_HDR_LEN) — reads m->m_len (if_ethersubr.c:993) */
    int len_read = m->m_len;

    /* eh = mtod(m, struct ether_header *) — dereferences m->m_data (line 996) */
    struct ether_header *eh = (struct ether_header *)m->m_data;

    /* Detect UAF: if the mbuf was freed and poisoned, ALL fields are 0xDE.
     * m_flags would be 0xDEDEDEDE and m_len would be (int)0xDEDEDEDE. */
    int poisoned = (flags_read == 0xDEDEDEDEU);

    printf("    ether_demux_oncpu: m->m_flags=0x%08x  m->m_len=%d  m->m_data=%p\n",
           flags_read, len_read, m->m_data);

    if (poisoned) {
        printf("    *** UAF DETECTED: mbuf was freed (all fields poisoned to 0x%02x) "
               "but still dereferenced! ***\n", POISON_BYTE);
        g_uaf_detected++;
    } else if (flags_read & M_PKTHDR) {
        printf("    ether_demux_oncpu: valid mbuf (m_flags has M_PKTHDR), "
               "ether_type=0x%04x — normal processing\n",
               eh ? eh->ether_type : 0);
    } else {
        printf("    ether_demux_oncpu: mbuf in unexpected state (m_flags=0x%08x)\n",
               flags_read);
    }

    (void)ifp;
}

/* ---- ng_ether_rcv_upper — EXACT transcription of the kernel code ----
 *
 * sys/netgraph7/ether/ng_ether.c:639-666
 *
 * static int
 * ng_ether_rcv_upper(node_p node, struct mbuf *m)
 * {
 *     const priv_p priv = NG_NODE_PRIVATE(node);
 *     struct ifnet *ifp = priv->ifp;
 *
 *     // Check length and pull off header
 *     if (m->m_pkthdr.len < sizeof(struct ether_header)) {   // line 646
 *         NG_FREE_M(m);
 *         return (EINVAL);
 *     }
 *     ...
 *     m->m_pkthdr.rcvif = ifp;                               // line 654
 *
 *     // Pass the packet to the bridge, it may come back to us
 *     if (ifp->if_bridge) {                                  // line 657
 *         bridge_input_p(ifp, m);          <-- BUG: return value discarded (line 658)
 *         if (m == NULL)                   <-- DEAD CODE     (line 659)
 *             return (0);
 *     }
 *
 *     // Route packet back in
 *     ether_demux_oncpu(ifp, m);           <-- UAF SINK      (line 664)
 *     return (0);
 * }
 */

static int
ng_ether_rcv_upper_BUGGY(struct ifnet *ifp, struct mbuf *m)
{
    /* line 646: length check */
    if (m->m_pkthdr.len < ETHER_HDR_LEN) {
        mock_m_freem(m);
        return -1;  /* EINVAL */
    }
    /* line 650-652: m_pullup (omitted — m_len is already >= ETHER_HDR_LEN) */

    /* line 654: m->m_pkthdr.rcvif = ifp */
    m->m_pkthdr.rcvif = ifp;

    /* line 657-661: THE BUG */
    if (ifp->if_bridge) {
        /* BUGGY: bridge_input_p(ifp, m); — return value DISCARDED */
        bridge_input_p(ifp, m);           /* line 658: m NOT updated */
        if (m == NULL)                    /* line 659: DEAD — m is never NULL */
            return 0;
    }

    /* line 664: UAF SINK — m was freed inside bridge_input_p but the local
     * pointer still points at the freed/poisoned memory */
    printf("  [BUGGY] Falling through to ether_demux_oncpu with freed mbuf %p\n", m);
    mock_ether_demux_oncpu(ifp, m);
    return 0;
}

static int
ng_ether_rcv_upper_FIXED(struct ifnet *ifp, struct mbuf *m)
{
    /* line 646: length check */
    if (m->m_pkthdr.len < ETHER_HDR_LEN) {
        mock_m_freem(m);
        return -1;  /* EINVAL */
    }

    /* line 654: m->m_pkthdr.rcvif = ifp */
    m->m_pkthdr.rcvif = ifp;

    /* line 657-661: THE FIX — capture the return value */
    if (ifp->if_bridge) {
        /* FIXED: m = bridge_input_p(ifp, m); — return value CAPTURED */
        m = bridge_input_p(ifp, m);       /* line 658: m IS updated */
        if (m == NULL)                    /* line 659: NOW FIRES */
            return 0;                     /* early return — no UAF */
    }

    /* line 664: only reached if bridge did NOT consume the mbuf */
    printf("  [FIXED] Falling through to ether_demux_oncpu with valid mbuf %p\n", m);
    mock_ether_demux_oncpu(ifp, m);
    return 0;
}

/* ---- Main: run both modes and report ---- */

int main(int argc, char **argv)
{
    int run_buggy = 1, run_fixed = 1;

    if (argc > 1) {
        if (strcmp(argv[1], "--buggy") == 0) { run_fixed = 0; }
        else if (strcmp(argv[1], "--fixed") == 0) { run_buggy = 0; }
        else {
            fprintf(stderr, "Usage: %s [--buggy|--fixed]\n", argv[0]);
            return 2;
        }
    }

    /* Topology: ifp is a bridge member (if_bridge != NULL), bridge has
     * IFF_MONITOR set — the most common deterministic NULL-return path. */
    struct ifnet ifp_obj;
    memset(&ifp_obj, 0, sizeof(ifp_obj));
    ifp_obj.if_bridge = (void *)0xCAFE0000;  /* non-NULL: we are a bridge member */

    printf("=== DF-0617: ng_ether_rcv_upper bridge_input UAF harness ===\n");
    printf("Topology: ifp->if_bridge=%p (bridge member), bridge IFF_MONITOR set\n",
           ifp_obj.if_bridge);
    printf("bridge_input() IFF_MONITOR path: m_freem(m) + return NULL\n\n");

    int buggy_uaf = 0, fixed_uaf = 0;

    if (run_buggy) {
        printf("--- BUGGY mode (current kernel: ng_ether.c:658 discards return value) ---\n");
        g_freed_count = 0;
        g_uaf_detected = 0;
        struct mbuf *m = mock_m_gethdr(60);
        printf("  Allocated mbuf %p (m_flags=0x%08x, m_len=%d)\n",
               m, m->m_flags, m->m_len);
        ng_ether_rcv_upper_BUGGY(&ifp_obj, m);
        printf("  Result: freed_count=%d, uaf_detected=%d\n\n",
               g_freed_count, g_uaf_detected);
        buggy_uaf = g_uaf_detected;
    }

    if (run_fixed) {
        printf("--- FIXED mode (m = bridge_input_p(ifp, m); — return value captured) ---\n");
        g_freed_count = 0;
        g_uaf_detected = 0;
        struct mbuf *m = mock_m_gethdr(60);
        printf("  Allocated mbuf %p (m_flags=0x%08x, m_len=%d)\n",
               m, m->m_flags, m->m_len);
        ng_ether_rcv_upper_FIXED(&ifp_obj, m);
        printf("  Result: freed_count=%d, uaf_detected=%d\n\n",
               g_freed_count, g_uaf_detected);
        fixed_uaf = g_uaf_detected;
    }

    /* ---- Summary ---- */
    printf("=== SUMMARY ===\n");
    if (run_buggy) {
        printf("BUGGY: %s — %s\n",
               buggy_uaf ? "UAF CONFIRMED" : "no UAF",
               buggy_uaf ? "freed mbuf dereferenced in ether_demux_oncpu"
                         : "(unexpected)");
    }
    if (run_fixed) {
        printf("FIXED: %s — %s\n",
               fixed_uaf ? "UAF STILL PRESENT" : "UAF ELIMINATED",
               fixed_uaf ? "freed mbuf still dereferenced"
                         : "early return before ether_demux_oncpu");
    }
    if (run_buggy && run_fixed) {
        printf("\nVerdict: %s\n",
               (buggy_uaf && !fixed_uaf)
                   ? "REPRODUCED — the one-line fix (m = bridge_input_p(...)) "
                     "eliminates the UAF"
               : (buggy_uaf && fixed_uaf)
                   ? "FIX FAILED — UAF present in both modes"
                   : "UNEXPECTED — investigate");
    }

    return (run_buggy && buggy_uaf) ? 0 : 1;
}
