⬢ DragonFlyBSD Kernel Audit
DF-4415 / psm_reach.c
← back to finding ↓ download raw
/*
 * DF-4415 probe: unprivileged reachability of the atkbdc polling loops.
 *
 * sys/dev/misc/kbd/atkbdc.c: on DragonFly /dev/psm0 is created 0666
 * (crw-rw-rw-), so ANY local user can issue mouse ioctls that funnel into
 * the atkbdc controller-layer busy-wait pollers (wait_while_controller_busy
 * :417, wait_for_aux_ack :545, empty_aux_buffer :894 ...). Each poller burns
 * DELAY()-busy CPU for up to 70-200ms x KBD_MAXRETRY(3) whenever the
 * controller is slow; and the *flood* loops below have NO overall timeout:
 *
 *   empty_kbd_buffer       :865-882   `t = wait;` reset on EVERY byte
 *   empty_aux_buffer       :905-922   `t = wait;` reset on EVERY byte
 *   read_kbd_data_no_wait  :799-804   while AUX-full: keep reading
 *   read_aux_data_no_wait  :838-843   while KBD-full: keep reading
 *
 * empty_both_buffers (:934-978) got an explicit `waited` cap for exactly
 * this scenario ("Intel/IBM blades ... will hang in this procedure"), the
 * four siblings above did not -> an i8042/EC (or VM host device model) that
 * keeps streaming data wedges the kernel thread forever.
 *
 * This probe demonstrates the unprivileged entry path; the unbounded loop
 * itself needs a flooding controller, which guest userspace cannot inject
 * (no /dev/io access for non-root).
 */
#include <errno.h>
#include <fcntl.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <time.h>
#include <unistd.h>
#include <sys/ioctl.h>
#include <sys/mouse.h>

static double now_s(void)
{
	struct timespec ts;

	clock_gettime(CLOCK_MONOTONIC, &ts);
	return ts.tv_sec + ts.tv_nsec / 1e9;
}

int main(void)
{
	mousemode_t mode, omode;
	mousehw_t info;
	int fd, i, ok_get = 0, ok_set = 0;
	double t0, dt, worst = 0;

	printf("uid=%d euid=%d\n", getuid(), geteuid());

	fd = open("/dev/psm0", O_RDWR | O_NONBLOCK);
	if (fd < 0) {
		printf("open(/dev/psm0): %s\n", strerror(errno));
		return 2;
	}
	printf("open(/dev/psm0): OK (world-writable by default on DFly)\n");

	if (ioctl(fd, MOUSE_GETHWINFO, &info) == 0) {
		ok_get = 1;
		printf("MOUSE_GETINFO: OK (buttons=%d iftype=%d type=%d "
		    "hwid=%d) -- reached the atkbdc layer as uid %d\n",
		    info.buttons, info.iftype, info.type, info.hwid,
		    getuid());
	} else {
		printf("MOUSE_GETINFO: %s\n", strerror(errno));
	}

	if (ioctl(fd, MOUSE_GETMODE, &omode) == 0) {
		/* re-set the SAME mode: drives psm -> send_aux_command() ->
		 * write_aux_command -> wait_while_controller_busy pollers */
		mode = omode;
		for (i = 0; i < 20; i++) {
			t0 = now_s();
			if (ioctl(fd, MOUSE_SETMODE, &mode) == 0)
				ok_set++;
			dt = now_s() - t0;
			if (dt > worst)
				worst = dt;
		}
		printf("MOUSE_SETMODE x20: %d ok, worst latency %.6fs "
		    "(each call runs the atkbdc busy-wait poll chain)\n",
		    ok_set, worst);
	} else {
		printf("MOUSE_GETMODE: %s\n", strerror(errno));
	}

	printf("unbounded-flood loops (empty_kbd_buffer/empty_aux_buffer/"
	    "read_*_no_wait) NOT exercisable from guest userspace: they need "
	    "an i8042/EC data flood (hardware / VM-host injection)\n");

	close(fd);
	return (ok_get || ok_set) ? 0 : 1;
}