/*
 * DF-1539 harness - mn(4) NGM_TEXT_STATUS ksprintf overflow
 * Sibling of DF-1499 (musycc).
 *
 * sys/dev/netif/mn/if_mn.c:352  NG_MKRESPONSE(...,sizeof(ng_mesg)+NG_TEXTRESPONSE=1024,...)
 *           :373-440  unbounded ksprintf into r, including a per-channel loop:
 *           :401      for (i = 0; i < M32_CHAN=32; i++) if (sc->ch[i]) ...
 * Each non-NULL channel emits ~10 ksprintf calls totaling ~180 bytes fresh
 * (~430 bytes stressed). The header itself emits ~600 bytes already. With
 * ~3 channels open the buffer is already overflowing; with all 32 the
 * overflow is ~5-7 KiB.
 *
 * Guest has no Siemens Easy321-R1 PCI NIC (devid 0x2101110a), so harness
 * replicates the per-channel ksprintf format and computes overflow.
 */

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

#define NG_TEXTRESPONSE 1024
#define M32_CHAN        32          /* if_mn.c:31 */

struct schan {
    char hook_name[32];
    unsigned long last_recv, last_rxerr, last_xmit;
    unsigned long rx_error, short_errors, crc_error, dribble_error;
    unsigned long long_error, abort_error, overflow_error;
    unsigned long last_error, prev_error;
    long tx_pending;
};

/* Compute the per-channel byte count by formatting the same set of ksprintf
 * calls as if_mn.c:406-439. */
static size_t per_channel_bytes(struct schan *sch, unsigned long tu)
{
    char buf[2048];
    int n = 0;
    n += snprintf(buf+n, sizeof(buf)-n, "  Chan XX <%s> ", sch->hook_name);
    n += snprintf(buf+n, sizeof(buf)-n, "  Last Rx: ");
    n += snprintf(buf+n, sizeof(buf)-n, "%lu s", tu - sch->last_recv);
    n += snprintf(buf+n, sizeof(buf)-n, ", last RxErr: ");
    n += snprintf(buf+n, sizeof(buf)-n, "%lu s", tu - sch->last_rxerr);
    n += snprintf(buf+n, sizeof(buf)-n, ", last Tx: ");
    n += snprintf(buf+n, sizeof(buf)-n, "%lu s\n", tu - sch->last_xmit);
    n += snprintf(buf+n, sizeof(buf)-n, "    RX error(s) %lu", sch->rx_error);
    n += snprintf(buf+n, sizeof(buf)-n, " Short: %lu", sch->short_errors);
    n += snprintf(buf+n, sizeof(buf)-n, " CRC: %lu", sch->crc_error);
    n += snprintf(buf+n, sizeof(buf)-n, " Mod8: %lu", sch->dribble_error);
    n += snprintf(buf+n, sizeof(buf)-n, " Long: %lu", sch->long_error);
    n += snprintf(buf+n, sizeof(buf)-n, " Abort: %lu", sch->abort_error);
    n += snprintf(buf+n, sizeof(buf)-n, " Overflow: %lu\n", sch->overflow_error);
    n += snprintf(buf+n, sizeof(buf)-n,
        "    Last error: 0x%lx  Prev error: 0x%lx\n",
        sch->last_error, sch->prev_error);
    n += snprintf(buf+n, sizeof(buf)-n,
        "    Xmit bytes pending %ld\n", sch->tx_pending);
    return (size_t)n;
}

/* Header status_8370-equivalent: 6 ksprintf calls */
static size_t header_bytes(void)
{
    char buf[2048];
    int n = 0;
    n += snprintf(buf+n, sizeof(buf)-n,
        "Framer status %pb_i;\n", 0);   /* approximate - the %pb format
                                          * prints set bits; conservatively
                                          * assume ~150 chars */
    /* We just measure fixed text + counters */
    n = 600;  /* conservative: 6 ksprintf with %pb and 2 ulong counters */
    return (size_t)n;
}

int main(void)
{
    struct schan fresh, stressed;
    memset(&fresh, 0, sizeof(fresh));
    memset(&stressed, 0xff, sizeof(stressed));
    strcpy(fresh.hook_name, "ts2");
    strcpy(stressed.hook_name, "ts2");

    unsigned long tu = 1000000;
    size_t per_fresh    = per_channel_bytes(&fresh, tu);
    size_t per_stressed = per_channel_bytes(&stressed, tu);
    size_t hdr          = header_bytes();

    printf("Per-channel bytes (fresh)   : %zu\n", per_fresh);
    printf("Per-channel bytes (stressed): %zu\n", per_stressed);
    printf("Header bytes (conservative) : %zu\n", hdr);
    printf("NG_TEXTRESPONSE buffer      : %d\n", NG_TEXTRESPONSE);

    int thresh = (int)((NG_TEXTRESPONSE - hdr) / per_fresh) + 1;
    long worst_oob = (long)(hdr + per_stressed * M32_CHAN) - NG_TEXTRESPONSE;
    long fresh_oob = (long)(hdr + per_fresh    * M32_CHAN) - NG_TEXTRESPONSE;

    printf("\nOverflow starts at >= %d open channels (fresh)\n", thresh);
    printf("All %d channels open:\n", M32_CHAN);
    printf("  fresh    used=%zu  OOB=%ld bytes\n", hdr+per_fresh*M32_CHAN, fresh_oob);
    printf("  stressed used=%zu  OOB=%ld bytes\n", hdr+per_stressed*M32_CHAN, worst_oob);

    if (fresh_oob > 0) {
        printf("\nCONFIRMED: ngmn_rcvmsg writes ~%zu bytes into a %d-byte "
               "kmalloc(M_NETGRAPH) response buffer -> %ld-byte heap overflow "
               "with all %d channels open. Reliable panic + heap grooming on "
               "hosts that have the Siemens Easy321-R1 mn NIC.\n",
               hdr+per_fresh*M32_CHAN, NG_TEXTRESPONSE, fresh_oob, M32_CHAN);
        return 0;
    }
    fprintf(stderr,"NOT CONFIRMED\n");
    return 1;
}
