Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux

usb: devio: Add ioctl to disallow detaching kernel USB drivers.

The new USBDEVFS_DROP_PRIVILEGES ioctl allows a process to voluntarily
relinquish the ability to issue other ioctls that may interfere with
other processes and drivers that have claimed an interface on the
device.

This commit also includes a simple utility to be able to test the
ioctl, located at Documentation/usb/usbdevfs-drop-permissions.c

Example (with qemu-kvm's input device):

$ lsusb
...
Bus 001 Device 002: ID 0627:0001 Adomax Technology Co., Ltd

$ usb-devices
...
C: #Ifs= 1 Cfg#= 1 Atr=a0 MxPwr=100mA
I: If#= 0 Alt= 0 #EPs= 1 Cls=03(HID ) Sub=00 Prot=02 Driver=usbhid

$ sudo ./usbdevfs-drop-permissions /dev/bus/usb/001/002
OK: privileges dropped!
Available options:
[0] Exit now
[1] Reset device. Should fail if device is in use
[2] Claim 4 interfaces. Should succeed where not in use
[3] Narrow interface permission mask
Which option shall I run?: 1
ERROR: USBDEVFS_RESET failed! (1 - Operation not permitted)
Which test shall I run next?: 2
ERROR claiming if 0 (1 - Operation not permitted)
ERROR claiming if 1 (1 - Operation not permitted)
ERROR claiming if 2 (1 - Operation not permitted)
ERROR claiming if 3 (1 - Operation not permitted)
Which test shall I run next?: 0

After unbinding usbhid:

$ usb-devices
...
I: If#= 0 Alt= 0 #EPs= 1 Cls=03(HID ) Sub=00 Prot=02 Driver=(none)

$ sudo ./usbdevfs-drop-permissions /dev/bus/usb/001/002
...
Which option shall I run?: 2
OK: claimed if 0
ERROR claiming if 1 (1 - Operation not permitted)
ERROR claiming if 2 (1 - Operation not permitted)
ERROR claiming if 3 (1 - Operation not permitted)
Which test shall I run next?: 1
OK: USBDEVFS_RESET succeeded
Which test shall I run next?: 0

After unbinding usbhid and restricting the mask:

$ sudo ./usbdevfs-drop-permissions /dev/bus/usb/001/002
...
Which option shall I run?: 3
Insert new mask: 0
OK: privileges dropped!
Which test shall I run next?: 2
ERROR claiming if 0 (1 - Operation not permitted)
ERROR claiming if 1 (1 - Operation not permitted)
ERROR claiming if 2 (1 - Operation not permitted)
ERROR claiming if 3 (1 - Operation not permitted)

Signed-off-by: Reilly Grant <reillyg@chromium.org>
Acked-by: Alan Stern <stern@rowland.harvard.edu>
Signed-off-by: Emilio López <emilio.lopez@collabora.co.uk>
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>

authored by

Reilly Grant and committed by
Greg Kroah-Hartman
d883f52e 3d0712de

+192 -5
+12
Documentation/DocBook/usb.tmpl
··· 732 732 or SET_INTERFACE. 733 733 </para></warning></listitem></varlistentry> 734 734 735 + <varlistentry><term>USBDEVFS_DROP_PRIVILEGES</term> 736 + <listitem><para>This is used to relinquish the ability 737 + to do certain operations which are considered to be 738 + privileged on a usbfs file descriptor. 739 + This includes claiming arbitrary interfaces, resetting 740 + a device on which there are currently claimed interfaces 741 + from other users, and issuing USBDEVFS_IOCTL calls. 742 + The ioctl parameter is a 32 bit mask of interfaces 743 + the user is allowed to claim on this file descriptor. 744 + You may issue this ioctl more than one time to narrow 745 + said mask. 746 + </para></listitem></varlistentry> 735 747 </variablelist> 736 748 737 749 </sect2>
+120
Documentation/usb/usbdevfs-drop-permissions.c
··· 1 + #include <sys/ioctl.h> 2 + #include <sys/types.h> 3 + #include <sys/stat.h> 4 + #include <fcntl.h> 5 + #include <stdio.h> 6 + #include <errno.h> 7 + #include <string.h> 8 + #include <inttypes.h> 9 + #include <unistd.h> 10 + 11 + #include <linux/usbdevice_fs.h> 12 + 13 + /* For building without an updated set of headers */ 14 + #ifndef USBDEVFS_DROP_PRIVILEGES 15 + #define USBDEVFS_DROP_PRIVILEGES _IOW('U', 30, __u32) 16 + #define USBDEVFS_CAP_DROP_PRIVILEGES 0x40 17 + #endif 18 + 19 + void drop_privileges(int fd, uint32_t mask) 20 + { 21 + int res; 22 + 23 + res = ioctl(fd, USBDEVFS_DROP_PRIVILEGES, &mask); 24 + if (res) 25 + printf("ERROR: USBDEVFS_DROP_PRIVILEGES returned %d\n", res); 26 + else 27 + printf("OK: privileges dropped!\n"); 28 + } 29 + 30 + void reset_device(int fd) 31 + { 32 + int res; 33 + 34 + res = ioctl(fd, USBDEVFS_RESET); 35 + if (!res) 36 + printf("OK: USBDEVFS_RESET succeeded\n"); 37 + else 38 + printf("ERROR: reset failed! (%d - %s)\n", 39 + -res, strerror(-res)); 40 + } 41 + 42 + void claim_some_intf(int fd) 43 + { 44 + int i, res; 45 + 46 + for (i = 0; i < 4; i++) { 47 + res = ioctl(fd, USBDEVFS_CLAIMINTERFACE, &i); 48 + if (!res) 49 + printf("OK: claimed if %d\n", i); 50 + else 51 + printf("ERROR claiming if %d (%d - %s)\n", 52 + i, -res, strerror(-res)); 53 + } 54 + } 55 + 56 + int main(int argc, char *argv[]) 57 + { 58 + uint32_t mask, caps; 59 + int c, fd; 60 + 61 + fd = open(argv[1], O_RDWR); 62 + if (fd < 0) { 63 + printf("Failed to open file\n"); 64 + goto err_fd; 65 + } 66 + 67 + /* 68 + * check if dropping privileges is supported, 69 + * bail on systems where the capability is not present 70 + */ 71 + ioctl(fd, USBDEVFS_GET_CAPABILITIES, &caps); 72 + if (!(caps & USBDEVFS_CAP_DROP_PRIVILEGES)) { 73 + printf("DROP_PRIVILEGES not supported\n"); 74 + goto err; 75 + } 76 + 77 + /* 78 + * Drop privileges but keep the ability to claim all 79 + * free interfaces (i.e., those not used by kernel drivers) 80 + */ 81 + drop_privileges(fd, -1U); 82 + 83 + printf("Available options:\n" 84 + "[0] Exit now\n" 85 + "[1] Reset device. Should fail if device is in use\n" 86 + "[2] Claim 4 interfaces. Should succeed where not in use\n" 87 + "[3] Narrow interface permission mask\n" 88 + "Which option shall I run?: "); 89 + 90 + while (scanf("%d", &c) == 1) { 91 + switch (c) { 92 + case 0: 93 + goto exit; 94 + case 1: 95 + reset_device(fd); 96 + break; 97 + case 2: 98 + claim_some_intf(fd); 99 + break; 100 + case 3: 101 + printf("Insert new mask: "); 102 + scanf("%x", &mask); 103 + drop_privileges(fd, mask); 104 + break; 105 + default: 106 + printf("I don't recognize that\n"); 107 + } 108 + 109 + printf("Which test shall I run next?: "); 110 + } 111 + 112 + exit: 113 + close(fd); 114 + return 0; 115 + 116 + err: 117 + close(fd); 118 + err_fd: 119 + return 1; 120 + }
+58 -5
drivers/usb/core/devio.c
··· 79 79 unsigned long ifclaimed; 80 80 u32 secid; 81 81 u32 disabled_bulk_eps; 82 + bool privileges_dropped; 83 + unsigned long interface_allowed_mask; 82 84 }; 83 85 84 86 struct usb_memory { ··· 750 748 if (test_bit(ifnum, &ps->ifclaimed)) 751 749 return 0; 752 750 751 + if (ps->privileges_dropped && 752 + !test_bit(ifnum, &ps->interface_allowed_mask)) 753 + return -EACCES; 754 + 753 755 intf = usb_ifnum_to_if(dev, ifnum); 754 756 if (!intf) 755 757 err = -ENOENT; ··· 991 985 int ret; 992 986 993 987 ret = -ENOMEM; 994 - ps = kmalloc(sizeof(struct usb_dev_state), GFP_KERNEL); 988 + ps = kzalloc(sizeof(struct usb_dev_state), GFP_KERNEL); 995 989 if (!ps) 996 990 goto out_free_ps; 997 991 ··· 1019 1013 1020 1014 ps->dev = dev; 1021 1015 ps->file = file; 1016 + ps->interface_allowed_mask = 0xFFFFFFFF; /* 32 bits */ 1022 1017 spin_lock_init(&ps->lock); 1023 1018 INIT_LIST_HEAD(&ps->list); 1024 1019 INIT_LIST_HEAD(&ps->async_pending); 1025 1020 INIT_LIST_HEAD(&ps->async_completed); 1026 1021 INIT_LIST_HEAD(&ps->memory_list); 1027 1022 init_waitqueue_head(&ps->wait); 1028 - ps->discsignr = 0; 1029 1023 ps->disc_pid = get_pid(task_pid(current)); 1030 1024 ps->cred = get_current_cred(); 1031 - ps->disccontext = NULL; 1032 - ps->ifclaimed = 0; 1033 1025 security_task_getsecid(current, &ps->secid); 1034 1026 smp_wmb(); 1035 1027 list_add_tail(&ps->list, &dev->filelist); ··· 1328 1324 1329 1325 static int proc_resetdevice(struct usb_dev_state *ps) 1330 1326 { 1327 + struct usb_host_config *actconfig = ps->dev->actconfig; 1328 + struct usb_interface *interface; 1329 + int i, number; 1330 + 1331 + /* Don't allow a device reset if the process has dropped the 1332 + * privilege to do such things and any of the interfaces are 1333 + * currently claimed. 1334 + */ 1335 + if (ps->privileges_dropped && actconfig) { 1336 + for (i = 0; i < actconfig->desc.bNumInterfaces; ++i) { 1337 + interface = actconfig->interface[i]; 1338 + number = interface->cur_altsetting->desc.bInterfaceNumber; 1339 + if (usb_interface_claimed(interface) && 1340 + !test_bit(number, &ps->ifclaimed)) { 1341 + dev_warn(&ps->dev->dev, 1342 + "usbfs: interface %d claimed by %s while '%s' resets device\n", 1343 + number, interface->dev.driver->name, current->comm); 1344 + return -EACCES; 1345 + } 1346 + } 1347 + } 1348 + 1331 1349 return usb_reset_device(ps->dev); 1332 1350 } 1333 1351 ··· 2116 2090 struct usb_interface *intf = NULL; 2117 2091 struct usb_driver *driver = NULL; 2118 2092 2093 + if (ps->privileges_dropped) 2094 + return -EACCES; 2095 + 2119 2096 /* alloc buffer */ 2120 2097 size = _IOC_SIZE(ctl->ioctl_code); 2121 2098 if (size > 0) { ··· 2244 2215 __u32 caps; 2245 2216 2246 2217 caps = USBDEVFS_CAP_ZERO_PACKET | USBDEVFS_CAP_NO_PACKET_SIZE_LIM | 2247 - USBDEVFS_CAP_REAP_AFTER_DISCONNECT | USBDEVFS_CAP_MMAP; 2218 + USBDEVFS_CAP_REAP_AFTER_DISCONNECT | USBDEVFS_CAP_MMAP | 2219 + USBDEVFS_CAP_DROP_PRIVILEGES; 2248 2220 if (!ps->dev->bus->no_stop_on_short) 2249 2221 caps |= USBDEVFS_CAP_BULK_CONTINUATION; 2250 2222 if (ps->dev->bus->sg_tablesize) ··· 2271 2241 2272 2242 if (intf->dev.driver) { 2273 2243 struct usb_driver *driver = to_usb_driver(intf->dev.driver); 2244 + 2245 + if (ps->privileges_dropped) 2246 + return -EACCES; 2274 2247 2275 2248 if ((dc.flags & USBDEVFS_DISCONNECT_CLAIM_IF_DRIVER) && 2276 2249 strncmp(dc.driver, intf->dev.driver->name, ··· 2329 2296 r = usb_free_streams(intf, eps, num_eps, GFP_KERNEL); 2330 2297 kfree(eps); 2331 2298 return r; 2299 + } 2300 + 2301 + static int proc_drop_privileges(struct usb_dev_state *ps, void __user *arg) 2302 + { 2303 + u32 data; 2304 + 2305 + if (copy_from_user(&data, arg, sizeof(data))) 2306 + return -EFAULT; 2307 + 2308 + /* This is an one way operation. Once privileges are 2309 + * dropped, you cannot regain them. You may however reissue 2310 + * this ioctl to shrink the allowed interfaces mask. 2311 + */ 2312 + ps->interface_allowed_mask &= data; 2313 + ps->privileges_dropped = true; 2314 + 2315 + return 0; 2332 2316 } 2333 2317 2334 2318 /* ··· 2535 2485 break; 2536 2486 case USBDEVFS_FREE_STREAMS: 2537 2487 ret = proc_free_streams(ps, p); 2488 + break; 2489 + case USBDEVFS_DROP_PRIVILEGES: 2490 + ret = proc_drop_privileges(ps, p); 2538 2491 break; 2539 2492 } 2540 2493
+2
include/uapi/linux/usbdevice_fs.h
··· 135 135 #define USBDEVFS_CAP_BULK_SCATTER_GATHER 0x08 136 136 #define USBDEVFS_CAP_REAP_AFTER_DISCONNECT 0x10 137 137 #define USBDEVFS_CAP_MMAP 0x20 138 + #define USBDEVFS_CAP_DROP_PRIVILEGES 0x40 138 139 139 140 /* USBDEVFS_DISCONNECT_CLAIM flags & struct */ 140 141 ··· 189 188 #define USBDEVFS_DISCONNECT_CLAIM _IOR('U', 27, struct usbdevfs_disconnect_claim) 190 189 #define USBDEVFS_ALLOC_STREAMS _IOR('U', 28, struct usbdevfs_streams) 191 190 #define USBDEVFS_FREE_STREAMS _IOR('U', 29, struct usbdevfs_streams) 191 + #define USBDEVFS_DROP_PRIVILEGES _IOW('U', 30, __u32) 192 192 193 193 #endif /* _UAPI_LINUX_USBDEVICE_FS_H */