/*
 * DF-0743 — greip / mobip_h overlay assumes ip_hl == 5
 * =====================================================
 *
 * Deterministic userspace harness that replicates the EXACT struct-overlay
 * dereferences performed by the DragonFlyBSD kernel in:
 *
 *   sys/netinet/ip_gre.c : gre_input2()       (IPPROTO_GRE)
 *   sys/netinet/ip_gre.c : gre_mobile_input() (IPPROTO_MOBILE)
 *
 * Both handlers overlay a fixed-layout struct (struct greip / struct mobip_h)
 * at the very start of the mbuf (mtod(m)) and dereference the GRE / mobile
 * fields at a FIXED byte offset that corresponds to a 20-byte (ip_hl==5) IP
 * header:
 *
 *     struct greip  { struct ip gi_i; struct gre_h gi_g; }
 *       gi_flags  == gi_g.flags  -> byte offset 20   (sizeof(struct ip))
 *       gi_ptype  == gi_g.ptype  -> byte offset 22
 *
 *     struct mobip_h { struct ip mi; struct mobile_h mh; }
 *       mh.proto  -> byte offset 20
 *       mh.odst   -> byte offset 24
 *       mh.osrc   -> byte offset 28
 *
 * When the OUTER encapsulating IP header carries IP options (ip_hl > 5), the
 * real GRE / mobile header begins at byte offset ip_hl*4 > 20, but the overlay
 * still dereferences offset 20 / 22 / 24 / 28 — i.e. it reads the IP-OPTION
 * bytes, not the GRE / mobile header.  This is a deterministic misparse.
 *
 * (The pointer-arithmetic strip that follows — m->m_data += hlen where
 *  hlen = ip_hl*4 + sizeof(gre_h), computed from the *offp argument passed by
 *  ip_input — DOES correctly account for IP options.  Only the struct-overlay
 *  field reads are wrong.  So the bytes that drive the protocol-decision logic
 *  [flags -> optional-field sizing; ptype -> inner-protocol classification;
 *   S-bit -> mobile-header size; osrc/odst -> rewritten inner src/dst;
 *   gre_in_cksum range -> checksum validation] all come from the wrong place.)
 *
 * This harness constructs an mbuf-shaped buffer that mimics exactly what the
 * kernel sees when ip_input delivers a GRE/mobile packet with ip_hl==7
 * (28-byte outer IP header = 20-byte fixed part + 8 bytes of options) to the
 * gre handlers, then performs the kernel's overlay derefs byte-for-byte and
 * compares them with the CORRECT (ip_hl-aware) reads.  On the vulnerable
 * logic the two differ for EVERY field => the misparse is proven.
 *
 * Build:  cc -O2 -Wall -o harness harness.c
 * Run:    ./harness            (no privilege required; pure C replication)
 *
 * Expected on vulnerable logic (this finding): the "OVERLAY" reads return the
 * IP-option bytes (0xAAAA.. / 0xBBBB.. / 0xCCCC.. / 0xDDDD..) and DIFFER from
 * the "CORRECT" reads (the real GRE/mobile header bytes) for every field =>
 * harness prints "MISPARSE PROVEN (vulnerable)".
 *
 * After the fix (overlay pointer computed from ip_hl*4, or fields read via an
 * explicit offset), the kernel reads would equal the "CORRECT" reads =>
 * equivalent harness logic would print "no misparse".
 */

#include <sys/types.h>
#include <stdio.h>
#include <stdint.h>
#include <string.h>
#include <stdlib.h>
#include <arpa/inet.h>

/* ---- Mirror the kernel's struct layouts (sys/net/gre/if_gre.h) ---- */

struct ip_hdr {                       /* 20-byte fixed part */
    uint8_t  ip_v_hl;                 /* version<<4 | header_len>>2 */
    uint8_t  ip_tos;
    uint16_t ip_len;
    uint16_t ip_id;
    uint16_t ip_off;
    uint8_t  ip_ttl;
    uint8_t  ip_p;
    uint16_t ip_sum;
    uint32_t ip_src;
    uint32_t ip_dst;
} __attribute__((__packed__));

struct gre_h {                        /* sys/net/gre/if_gre.h:65 */
    uint16_t flags;
    uint16_t ptype;
} __attribute__((__packed__));

struct greip {                        /* sys/net/gre/if_gre.h:90 */
    struct ip_hdr gi_i;
    struct gre_h  gi_g;
} __attribute__((__packed__));
#define gi_ptype gi_g.ptype
#define gi_flags gi_g.flags

struct mobile_h {                     /* sys/net/gre/if_gre.h:134 */
    uint16_t proto;
    uint16_t hcrc;
    uint32_t odst;
    uint32_t osrc;
} __attribute__((__packed__));

struct mobip_h {                      /* sys/net/gre/if_gre.h:141 */
    struct ip_hdr  mi;
    struct mobile_h mh;
} __attribute__((__packed__));

#define MOB_H_SBIT 0x0080

/* IP header length (bytes) encoded in ip_v_hl as the kernel reads it. */
#define IP_HL(ip) (((ip)->ip_v_hl) & 0x0f)

static uint16_t
cksum16(const void *p, size_t len)
{
    uint32_t s = 0;
    const uint16_t *w = p;
    int n = len >> 1;
    while (n-- > 0) s += *w++;
    if (len & 1) s += htons(((const uint8_t *)p)[len - 1]);
    s = (s >> 16) + (s & 0xffff);
    s += (s >> 16);
    return (uint16_t)(~s);
}

static int fail = 0;

static void
check_field(const char *name, uint16_t overlay, uint16_t correct)
{
    int bad = (overlay != correct);
    if (bad) fail = 1;
    printf("    %-22s overlay=0x%04x  correct=0x%04x  %s\n",
           name, overlay, correct, bad ? "*** MISMATCH (misparse) ***" : "ok");
}

static void
check_field32(const char *name, uint32_t overlay, uint32_t correct)
{
    int bad = (overlay != correct);
    if (bad) fail = 1;
    printf("    %-22s overlay=0x%08x  correct=0x%08x  %s\n",
           name, overlay, correct, bad ? "*** MISMATCH (misparse) ***" : "ok");
}

/* ====================== GRE path (gre_input2) ====================== */
static void
test_gre(void)
{
    /* Build an mbuf-shaped buffer: 28-byte IP header (ip_hl=7, 8 opt bytes)
     * followed by the REAL gre header at offset 28, then inner payload. */
    uint8_t buf[64];
    memset(buf, 0, sizeof(buf));

    /* Outer IP header, ip_hl = 7 (28 bytes). */
    struct ip_hdr *ip = (struct ip_hdr *)buf;
    ip->ip_v_hl = (4 << 4) | 7;          /* version 4, ip_hl 7 */
    ip->ip_len  = htons(sizeof(buf));
    ip->ip_ttl  = 64;
    ip->ip_p    = 47;                     /* IPPROTO_GRE */
    ip->ip_src  = htonl(0x7f000001);      /* 127.0.0.1 */
    ip->ip_dst  = htonl(0x7f000001);
    /* IP checksum over the full 28-byte header (options included). */
    ip->ip_sum  = 0;
    ip->ip_sum  = cksum16(buf, 28);

    /* 8 bytes of IP options at offset 20..27 — DISTINCT marker bytes so a
     * misparse is unambiguous. */
    buf[20] = 0xAA; buf[21] = 0xAA;   /* would-be gi_flags  */
    buf[22] = 0xBB; buf[23] = 0xBB;   /* would-be gi_ptype  */
    buf[24] = 0x0c; buf[25] = 0x04;   /* would-be mh.odst hi */
    buf[26] = 0x00; buf[27] = 0x00;
    /* (checksum already computed over these; recompute to stay consistent) */
    ip->ip_sum = 0;
    ip->ip_sum = cksum16(buf, 28);

    /* REAL gre header at offset 28 (ip_hl*4). */
    struct gre_h *real = (struct gre_h *)(buf + 28);
    real->flags = htons(0x0000);
    real->ptype = htons(0x0800);          /* ETHERTYPE_IP */

    printf("=== GRE path (gre_input2)  outer ip_hl=%d (IP options present) ===\n",
           IP_HL(ip));
    printf("    IP-option bytes @20..27: "
           "%02x %02x %02x %02x %02x %02x %02x %02x\n",
           buf[20], buf[21], buf[22], buf[23],
           buf[24], buf[25], buf[26], buf[27]);
    printf("    real GRE header @28..31: "
           "flags=0x%04x ptype=0x%04x\n",
           ntohs(real->flags), ntohs(real->ptype));

    /* ---- Replicate the kernel's overlay read (gre_input2, ip_gre.c:133) ----
     *     struct greip *gip = mtod(m, struct greip *);
     *     flags = ntohs(gip->gi_flags);          // ip_gre.c:151
     *     switch (ntohs(gip->gi_ptype)) {        // ip_gre.c:164
     */
    struct greip *gip = (struct greip *)buf;     /* mtod(m) equivalent */
    uint16_t k_flags = ntohs(gip->gi_flags);     /* fixed offset 20 */
    uint16_t k_ptype = ntohs(gip->gi_ptype);     /* fixed offset 22 */

    /* ---- Correct (ip_hl-aware) read: gre header starts at ip_hl*4 ---- */
    struct gre_h *gh = (struct gre_h *)(buf + (IP_HL(ip) << 2));
    uint16_t c_flags = ntohs(gh->flags);
    uint16_t c_ptype = ntohs(gh->ptype);

    check_field("gi_flags", k_flags, c_flags);
    check_field("gi_ptype", k_ptype, c_ptype);

    /* Demonstrate the protocol-decision divergence: kernel's overlay ptype
     * (0xBBBB) is NOT ETHERTYPE_IP -> switch default -> return(0) -> packet
     * silently dropped.  Correct ptype (0x0800) -> accepted -> decapsulated.
     */
    int k_accept = (k_ptype == 0x0800);
    int c_accept = (c_ptype == 0x0800);
    printf("    kernel overlay decision: ptype 0x%04x %s ETHERTYPE_IP -> %s\n",
           k_ptype, k_accept ? "==" : "!=", k_accept ? "ACCEPT/decapsulate" : "DROP");
    printf("    correct      decision: ptype 0x%04x %s ETHERTYPE_IP -> %s\n",
           c_ptype, c_accept ? "==" : "!=", c_accept ? "ACCEPT/decapsulate" : "DROP");
    if (k_accept != c_accept) {
        fail = 1;
        printf("    *** DECISION DIVERGES: misparse changes accept/drop ***\n");
    }
    printf("\n");
}

/* ================== MOBILE path (gre_mobile_input) ================= */
static void
test_mobile(void)
{
    uint8_t buf[64];
    memset(buf, 0, sizeof(buf));

    struct ip_hdr *ip = (struct ip_hdr *)buf;
    ip->ip_v_hl = (4 << 4) | 7;          /* ip_hl = 7 */
    ip->ip_len  = htons(sizeof(buf));
    ip->ip_ttl  = 64;
    ip->ip_p    = 55;                     /* IPPROTO_MOBILE */
    ip->ip_src  = htonl(0x7f000001);
    ip->ip_dst  = htonl(0x7f000001);

    /* 8 distinct option-marker bytes at 20..27 */
    buf[20] = 0xCC; buf[21] = 0xCC;       /* would-be mh.proto */
    buf[22] = 0x00; buf[23] = 0x00;       /* would-be mh.hcrc  */
    buf[24] = 0xDD; buf[25] = 0xDD;       /* would-be mh.odst  */
    buf[26] = 0xDD; buf[27] = 0xDD;
    ip->ip_sum = 0;
    ip->ip_sum = cksum16(buf, 28);

    /* REAL mobile header at offset 28 (ip_hl*4), S-bit set, valid checksum. */
    struct mobile_h *mh = (struct mobile_h *)(buf + 28);
    mh->proto = htons(MOB_H_SBIT);        /* S-bit set -> msiz = 12 */
    mh->hcrc  = 0;
    mh->odst  = htonl(0x0a000002);        /* 10.0.0.2 */
    mh->osrc  = htonl(0x0a000003);        /* 10.0.0.3 */
    mh->hcrc  = cksum16(mh, sizeof(*mh)); /* valid gre_in_cksum */

    printf("=== MOBILE path (gre_mobile_input)  outer ip_hl=%d ===\n",
           IP_HL(ip));
    printf("    IP-option bytes @20..27: "
           "%02x %02x %02x %02x %02x %02x %02x %02x\n",
           buf[20], buf[21], buf[22], buf[23],
           buf[24], buf[25], buf[26], buf[27]);
    printf("    real mobile hdr  @28..39: proto=0x%04x hcrc=0x%04x "
           "odst=0x%08x osrc=0x%08x\n",
           ntohs(mh->proto), ntohs(mh->hcrc), ntohl(mh->odst), ntohl(mh->osrc));

    /* ---- Kernel overlay read (gre_mobile_input, ip_gre.c:210) ----
     *     struct mobip_h *mip = mtod(m, struct mobip_h *);
     *     if (ntohs(mip->mh.proto) & MOB_H_SBIT) {           // :223
     *         msiz = MOB_H_SIZ_L;
     *         mip->mi.ip_src.s_addr = mip->mh.osrc;          // :225
     *     } else msiz = MOB_H_SIZ_S;
     *     mip->mi.ip_dst.s_addr = mip->mh.odst;              // :229
     *     mip->mi.ip_p = (ntohs(mip->mh.proto) >> 8);        // :230
     */
    struct mobip_h *mip = (struct mobip_h *)buf;     /* mtod(m) equiv */
    uint16_t k_proto = ntohs(mip->mh.proto);         /* fixed offset 20 */
    uint32_t k_odst  = mip->mh.odst;                 /* fixed offset 24 */
    uint32_t k_osrc  = mip->mh.osrc;                 /* fixed offset 28 */

    /* ---- Correct (ip_hl-aware) read ---- */
    struct mobile_h *mh_c = (struct mobile_h *)(buf + (IP_HL(ip) << 2));
    uint16_t c_proto = ntohs(mh_c->proto);
    uint32_t c_odst  = mh_c->odst;
    uint32_t c_osrc  = mh_c->osrc;

    check_field("mh.proto", k_proto, c_proto);
    check_field32("mh.odst",  k_odst, c_odst);
    check_field32("mh.osrc",  k_osrc, c_osrc);

    int k_sbit = (k_proto & MOB_H_SBIT) != 0;
    int c_sbit = (c_proto & MOB_H_SBIT) != 0;
    printf("    kernel overlay S-bit: proto 0x%04x -> S=%d -> msiz=%d\n",
           k_proto, k_sbit, k_sbit ? 12 : 8);
    printf("    correct      S-bit: proto 0x%04x -> S=%d -> msiz=%d\n",
           c_proto, c_sbit, c_sbit ? 12 : 8);
    if (k_sbit != c_sbit) {
        fail = 1;
        printf("    *** S-BIT DECISION DIVERGES: misparse changes msiz ***\n");
    }

    /* gre_in_cksum is computed over &mip->mh (offset 20) for msiz bytes — on
     * the overlay that is the OPTION bytes, so the checksum fails and the
     * packet is dropped at ip_gre.c:232-234 even though the real mobile
     * header has a VALID checksum. */
    uint16_t k_cksum = cksum16(&mip->mh, k_sbit ? 12 : 8);
    uint16_t c_cksum = cksum16(mh_c,     c_sbit ? 12 : 8);
    printf("    kernel overlay gre_in_cksum(&mip->mh) = 0x%04x -> %s\n",
           k_cksum, k_cksum == 0 ? "PASS" : "FAIL -> m_freem DROP");
    printf("    correct      gre_in_cksum(mh_real)    = 0x%04x -> %s\n",
           c_cksum, c_cksum == 0 ? "PASS" : "FAIL -> m_freem DROP");
    if ((k_cksum == 0) != (c_cksum == 0)) {
        fail = 1;
        printf("    *** CHECKSUM DECISION DIVERGES ***\n");
    }
    printf("\n");
}

int
main(void)
{
    printf("DF-0743 harness: greip / mobip_h overlay vs ip_hl-aware read\n");
    printf("Replicates the kernel struct-overlay derefs at:\n");
    printf("  sys/netinet/ip_gre.c:133 (gip = mtod(m, struct greip *))\n");
    printf("  sys/netinet/ip_gre.c:151 (flags = ntohs(gip->gi_flags))\n");
    printf("  sys/netinet/ip_gre.c:164 (ntohs(gip->gi_ptype))\n");
    printf("  sys/netinet/ip_gre.c:210 (mip = mtod(m, struct mobip_h *))\n");
    printf("  sys/netinet/ip_gre.c:223,225,229,230,232\n\n");

    test_gre();
    test_mobile();

    if (fail) {
        printf("VERDICT: MISPARSE PROVEN (vulnerable) — overlay reads IP-option "
               "bytes\n         instead of the real GRE/mobile header when "
               "ip_hl > 5.\n");
        return 0;                 /* bug demonstrated */
    }
    printf("VERDICT: no misparse (overlay reads match ip_hl-aware reads)\n");
    return 1;
}
