/*
 * DF-1487 harness — wb_encap OOB write into wb_frag[16]
 *
 * sys/dev/netif/wb/if_wb.c:1221-1238  fill loop: stops at frag==WB_MAXFRAGS(16)
 *      :1248 if (m != NULL) coalesce branch — SKIPPED if we consumed exactly
 *           16 mbufs and m went NULL via m->m_next
 *      :1274 if (total_len < WB_MIN_FRAMELEN(60)) padding branch
 *      :1275-1280 f = &c->wb_ptr->wb_frag[frag=16]   <- one past end of
 *               struct wb_txdesc.wb_frag[16] (if_wbreg.h:291)
 *               writes f->wb_ctl/wb_data/wb_status = 12 bytes OOB
 *      :1284 c->wb_lastdesc = frag-1 = 16
 *      :1285 WB_TXCTL(c) |= LASTFRAG  -> wb_frag[16].wb_ctl |= ... RMW (OOB)
 *
 * Trigger: an outgoing mbuf chain with EXACTLY 16 non-empty mbufs whose
 * total m_len sum is < 60 bytes — e.g. sendmsg with 16-byte iov using
 * AF_INET SOCK_RAW IP_HDRINCL with a tiny IP header in the first mbuf.
 *
 * Guest has no Winbond W89C840F NIC, so this is a harness proof against
 * the genuine wb_encap fragment-fill + padding arithmetic.
 */

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

#define WB_MAXFRAGS    16
#define WB_MIN_FRAMELEN 60

struct wb_desc { uint32_t wb_status; uint32_t wb_ctl; uint32_t wb_data; uint32_t wb_next; };

struct wb_txdesc { struct wb_desc wb_frag[WB_MAXFRAGS]; };

/* Track which indices get written.  Returns: 0 = in-bounds, 1 = OOB write. */
static int wb_encap_harness(int nmibs, int m_len_each, int *oob_idx)
{
    struct wb_txdesc tx;
    memset(&tx, 0, sizeof(tx));
    /* "canary" past the end — anything written here is OOB */
    uint32_t canary[8] = { 0xDEADBEEF, 0xDEADBEEF, 0xDEADBEEF, 0xDEADBEEF,
                           0xDEADBEEF, 0xDEADBEEF, 0xDEADBEEF, 0xDEADBEEF };
    struct wb_desc *frag_base = tx.wb_frag;

    int total_len = 0;
    int frag = 0;
    int m;
    for (m = 0; m < nmibs; m++) {
        if (m_len_each != 0) {
            if (frag == WB_MAXFRAGS) break;
            total_len += m_len_each;
            frag_base[frag].wb_ctl   = 0x1000 | m_len_each;
            frag_base[frag].wb_data  = 0xCAFEBABE + frag;
            frag_base[frag].wb_status= 0;
            frag++;
        }
    }
    int m_is_null = (m >= nmibs);  /* simulating m==NULL after the loop */

    /* line 1248: coalesce branch fires only if m != NULL */
    if (!m_is_null) {
        /* would copy to a cluster; we model that as: frag=1, total_len=big */
        frag = 1; total_len = WB_MIN_FRAMELEN + 100;
    }

    /* line 1274: padding branch */
    if (total_len < WB_MIN_FRAMELEN) {
        struct wb_desc *f = &frag_base[frag];     /* line 1275 */
        f->wb_ctl   = WB_MIN_FRAMELEN - total_len; /* line 1276 */
        f->wb_data  = 0xCAFEBABE;                  /* line 1277 vtophys(&wb_pad) */
        f->wb_ctl  |= 0x200;                       /* line 1278 TXCTL_TLINK */
        f->wb_status = 0x8000;                     /* line 1279 TXSTAT_OWN */
        frag++;                                    /* line 1280 */
    }

    int lastdesc = frag - 1;                       /* line 1284 */
    /* line 1285: WB_TXCTL(c) |= LASTFRAG */
    frag_base[lastdesc].wb_ctl |= 0x40000000;

    *oob_idx = -1;
    if (lastdesc >= WB_MAXFRAGS) {
        *oob_idx = lastdesc;
        return 1; /* OOB */
    }
    /* check canary: did frag_base[16..] get hit? */
    for (int i = WB_MAXFRAGS; i < WB_MAXFRAGS + 4; i++) {
        /* the harness allocation is exactly WB_MAXFRAGS long; the canary
         * simulation only makes sense if lastdesc==16, which we caught above. */
    }
    (void)canary;
    return 0;
}

int main(void)
{
    int confs[][2] = {
        /* {n_mibs, m_len_each} */
        { 16, 1 },   /* 16 mbufs of 1 byte each: total=16 < 60 -> BUG */
        { 16, 2 },   /* 32 < 60 -> BUG */
        { 16, 3 },   /* 48 < 60 -> BUG */
        { 16, 4 },   /* 64 >= 60 -> no padding branch (in-bounds) */
        { 15, 1 },   /* 15 < 60 but frag==15 in-bounds */
        { 17, 1 },   /* coalesce branch fires (m!=NULL) */
    };
    int n = sizeof(confs)/(2*sizeof(int));
    int bugs = 0;
    printf("%-22s %10s %14s %10s\n","config","total_len","coalesce?","result");
    for (int i=0;i<n;i++) {
        int mb = confs[i][0], len = confs[i][1];
        int total = mb*len;
        int oob_idx = -1;
        /* For the 17-mbuf case, the loop breaks at frag==16 with m!=NULL */
        int rc = wb_encap_harness(mb, len, &oob_idx);
        const char *coalesced = (mb > 16) ? "yes" : "no";
        printf("%-22s %10d %14s %10s\n",
               (i==0?"16x1":i==1?"16x2":i==2?"16x3":i==3?"16x4":i==4?"15x1":"17x1"),
               total, coalesced, rc? "OOB!" : "in-bounds");
        if (rc) bugs++;
    }
    printf("\nBuggy configs: %d/%d write past wb_frag[15]\n", bugs, n);
    if (bugs >= 1) {
        printf("CONFIRMED: wb_encap writes 12+ bytes (wb_ctl/wb_data/wb_status) "
               "past the end of struct wb_txdesc (wb_frag[16..]) when the chain "
               "has exactly 16 non-empty mbufs summing to < 60 bytes, plus a "
               "4-byte RMW via WB_TXCTL(c). On real hardware this corrupts "
               "adjacent kernel heap (or the next contiguous wb_txdesc when "
               "wb_ptr==wb_tx_list[127]).\n");
        return 0;
    }
    fprintf(stderr,"NOT CONFIRMED\n");
    return 1;
}
