diff options
author | Reilly Grant <reillyg@chromium.org> | 2016-02-21 18:38:01 -0300 |
---|---|---|
committer | Greg Kroah-Hartman <gregkh@linuxfoundation.org> | 2016-03-05 12:05:01 -0800 |
commit | d883f52e1f6d2eca8378e3795f333c1396943873 (patch) | |
tree | 849a84ea953f7adf6869229156f1b6bd97a77f4b /drivers/usb | |
parent | 3d0712deb0a416021e55febc7ec7f6c24f460e06 (diff) | |
download | linux-d883f52e1f6d2eca8378e3795f333c1396943873.tar.bz2 |
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>
Diffstat (limited to 'drivers/usb')
-rw-r--r-- | drivers/usb/core/devio.c | 63 |
1 files changed, 58 insertions, 5 deletions
diff --git a/drivers/usb/core/devio.c b/drivers/usb/core/devio.c index 39da1662b76f..52c4461dfccd 100644 --- a/drivers/usb/core/devio.c +++ b/drivers/usb/core/devio.c @@ -79,6 +79,8 @@ struct usb_dev_state { unsigned long ifclaimed; u32 secid; u32 disabled_bulk_eps; + bool privileges_dropped; + unsigned long interface_allowed_mask; }; struct usb_memory { @@ -748,6 +750,10 @@ static int claimintf(struct usb_dev_state *ps, unsigned int ifnum) if (test_bit(ifnum, &ps->ifclaimed)) return 0; + if (ps->privileges_dropped && + !test_bit(ifnum, &ps->interface_allowed_mask)) + return -EACCES; + intf = usb_ifnum_to_if(dev, ifnum); if (!intf) err = -ENOENT; @@ -985,7 +991,7 @@ static int usbdev_open(struct inode *inode, struct file *file) int ret; ret = -ENOMEM; - ps = kmalloc(sizeof(struct usb_dev_state), GFP_KERNEL); + ps = kzalloc(sizeof(struct usb_dev_state), GFP_KERNEL); if (!ps) goto out_free_ps; @@ -1013,17 +1019,15 @@ static int usbdev_open(struct inode *inode, struct file *file) ps->dev = dev; ps->file = file; + ps->interface_allowed_mask = 0xFFFFFFFF; /* 32 bits */ spin_lock_init(&ps->lock); INIT_LIST_HEAD(&ps->list); INIT_LIST_HEAD(&ps->async_pending); INIT_LIST_HEAD(&ps->async_completed); INIT_LIST_HEAD(&ps->memory_list); init_waitqueue_head(&ps->wait); - ps->discsignr = 0; ps->disc_pid = get_pid(task_pid(current)); ps->cred = get_current_cred(); - ps->disccontext = NULL; - ps->ifclaimed = 0; security_task_getsecid(current, &ps->secid); smp_wmb(); list_add_tail(&ps->list, &dev->filelist); @@ -1324,6 +1328,28 @@ static int proc_connectinfo(struct usb_dev_state *ps, void __user *arg) static int proc_resetdevice(struct usb_dev_state *ps) { + struct usb_host_config *actconfig = ps->dev->actconfig; + struct usb_interface *interface; + int i, number; + + /* Don't allow a device reset if the process has dropped the + * privilege to do such things and any of the interfaces are + * currently claimed. + */ + if (ps->privileges_dropped && actconfig) { + for (i = 0; i < actconfig->desc.bNumInterfaces; ++i) { + interface = actconfig->interface[i]; + number = interface->cur_altsetting->desc.bInterfaceNumber; + if (usb_interface_claimed(interface) && + !test_bit(number, &ps->ifclaimed)) { + dev_warn(&ps->dev->dev, + "usbfs: interface %d claimed by %s while '%s' resets device\n", + number, interface->dev.driver->name, current->comm); + return -EACCES; + } + } + } + return usb_reset_device(ps->dev); } @@ -2090,6 +2116,9 @@ static int proc_ioctl(struct usb_dev_state *ps, struct usbdevfs_ioctl *ctl) struct usb_interface *intf = NULL; struct usb_driver *driver = NULL; + if (ps->privileges_dropped) + return -EACCES; + /* alloc buffer */ size = _IOC_SIZE(ctl->ioctl_code); if (size > 0) { @@ -2215,7 +2244,8 @@ static int proc_get_capabilities(struct usb_dev_state *ps, void __user *arg) __u32 caps; caps = USBDEVFS_CAP_ZERO_PACKET | USBDEVFS_CAP_NO_PACKET_SIZE_LIM | - USBDEVFS_CAP_REAP_AFTER_DISCONNECT | USBDEVFS_CAP_MMAP; + USBDEVFS_CAP_REAP_AFTER_DISCONNECT | USBDEVFS_CAP_MMAP | + USBDEVFS_CAP_DROP_PRIVILEGES; if (!ps->dev->bus->no_stop_on_short) caps |= USBDEVFS_CAP_BULK_CONTINUATION; if (ps->dev->bus->sg_tablesize) @@ -2242,6 +2272,9 @@ static int proc_disconnect_claim(struct usb_dev_state *ps, void __user *arg) if (intf->dev.driver) { struct usb_driver *driver = to_usb_driver(intf->dev.driver); + if (ps->privileges_dropped) + return -EACCES; + if ((dc.flags & USBDEVFS_DISCONNECT_CLAIM_IF_DRIVER) && strncmp(dc.driver, intf->dev.driver->name, sizeof(dc.driver)) != 0) @@ -2298,6 +2331,23 @@ static int proc_free_streams(struct usb_dev_state *ps, void __user *arg) return r; } +static int proc_drop_privileges(struct usb_dev_state *ps, void __user *arg) +{ + u32 data; + + if (copy_from_user(&data, arg, sizeof(data))) + return -EFAULT; + + /* This is an one way operation. Once privileges are + * dropped, you cannot regain them. You may however reissue + * this ioctl to shrink the allowed interfaces mask. + */ + ps->interface_allowed_mask &= data; + ps->privileges_dropped = true; + + return 0; +} + /* * NOTE: All requests here that have interface numbers as parameters * are assuming that somehow the configuration has been prevented from @@ -2486,6 +2536,9 @@ static long usbdev_do_ioctl(struct file *file, unsigned int cmd, case USBDEVFS_FREE_STREAMS: ret = proc_free_streams(ps, p); break; + case USBDEVFS_DROP_PRIVILEGES: + ret = proc_drop_privileges(ps, p); + break; } done: |