From d883f52e1f6d2eca8378e3795f333c1396943873 Mon Sep 17 00:00:00 2001 From: Reilly Grant Date: Sun, 21 Feb 2016 18:38:01 -0300 Subject: [PATCH] usb: devio: Add ioctl to disallow detaching kernel USB drivers. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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 Acked-by: Alan Stern Signed-off-by: Emilio López Signed-off-by: Greg Kroah-Hartman --- Documentation/DocBook/usb.tmpl | 12 ++ Documentation/usb/usbdevfs-drop-permissions.c | 120 ++++++++++++++++++ drivers/usb/core/devio.c | 63 ++++++++- include/uapi/linux/usbdevice_fs.h | 2 + 4 files changed, 192 insertions(+), 5 deletions(-) create mode 100644 Documentation/usb/usbdevfs-drop-permissions.c diff --git a/Documentation/DocBook/usb.tmpl b/Documentation/DocBook/usb.tmpl index 4cd5b2cd0f3d..bc776be0f19c 100644 --- a/Documentation/DocBook/usb.tmpl +++ b/Documentation/DocBook/usb.tmpl @@ -732,6 +732,18 @@ usbdev_ioctl (int fd, int ifno, unsigned request, void *param) or SET_INTERFACE. + USBDEVFS_DROP_PRIVILEGES + This is used to relinquish the ability + to do certain operations which are considered to be + privileged on a usbfs file descriptor. + This includes claiming arbitrary interfaces, resetting + a device on which there are currently claimed interfaces + from other users, and issuing USBDEVFS_IOCTL calls. + The ioctl parameter is a 32 bit mask of interfaces + the user is allowed to claim on this file descriptor. + You may issue this ioctl more than one time to narrow + said mask. + diff --git a/Documentation/usb/usbdevfs-drop-permissions.c b/Documentation/usb/usbdevfs-drop-permissions.c new file mode 100644 index 000000000000..6b8da6ef0c9a --- /dev/null +++ b/Documentation/usb/usbdevfs-drop-permissions.c @@ -0,0 +1,120 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +/* For building without an updated set of headers */ +#ifndef USBDEVFS_DROP_PRIVILEGES +#define USBDEVFS_DROP_PRIVILEGES _IOW('U', 30, __u32) +#define USBDEVFS_CAP_DROP_PRIVILEGES 0x40 +#endif + +void drop_privileges(int fd, uint32_t mask) +{ + int res; + + res = ioctl(fd, USBDEVFS_DROP_PRIVILEGES, &mask); + if (res) + printf("ERROR: USBDEVFS_DROP_PRIVILEGES returned %d\n", res); + else + printf("OK: privileges dropped!\n"); +} + +void reset_device(int fd) +{ + int res; + + res = ioctl(fd, USBDEVFS_RESET); + if (!res) + printf("OK: USBDEVFS_RESET succeeded\n"); + else + printf("ERROR: reset failed! (%d - %s)\n", + -res, strerror(-res)); +} + +void claim_some_intf(int fd) +{ + int i, res; + + for (i = 0; i < 4; i++) { + res = ioctl(fd, USBDEVFS_CLAIMINTERFACE, &i); + if (!res) + printf("OK: claimed if %d\n", i); + else + printf("ERROR claiming if %d (%d - %s)\n", + i, -res, strerror(-res)); + } +} + +int main(int argc, char *argv[]) +{ + uint32_t mask, caps; + int c, fd; + + fd = open(argv[1], O_RDWR); + if (fd < 0) { + printf("Failed to open file\n"); + goto err_fd; + } + + /* + * check if dropping privileges is supported, + * bail on systems where the capability is not present + */ + ioctl(fd, USBDEVFS_GET_CAPABILITIES, &caps); + if (!(caps & USBDEVFS_CAP_DROP_PRIVILEGES)) { + printf("DROP_PRIVILEGES not supported\n"); + goto err; + } + + /* + * Drop privileges but keep the ability to claim all + * free interfaces (i.e., those not used by kernel drivers) + */ + drop_privileges(fd, -1U); + + printf("Available options:\n" + "[0] Exit now\n" + "[1] Reset device. Should fail if device is in use\n" + "[2] Claim 4 interfaces. Should succeed where not in use\n" + "[3] Narrow interface permission mask\n" + "Which option shall I run?: "); + + while (scanf("%d", &c) == 1) { + switch (c) { + case 0: + goto exit; + case 1: + reset_device(fd); + break; + case 2: + claim_some_intf(fd); + break; + case 3: + printf("Insert new mask: "); + scanf("%x", &mask); + drop_privileges(fd, mask); + break; + default: + printf("I don't recognize that\n"); + } + + printf("Which test shall I run next?: "); + } + +exit: + close(fd); + return 0; + +err: + close(fd); +err_fd: + return 1; +} 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: diff --git a/include/uapi/linux/usbdevice_fs.h b/include/uapi/linux/usbdevice_fs.h index ecbd17650e6c..a8653a6f40df 100644 --- a/include/uapi/linux/usbdevice_fs.h +++ b/include/uapi/linux/usbdevice_fs.h @@ -135,6 +135,7 @@ struct usbdevfs_hub_portinfo { #define USBDEVFS_CAP_BULK_SCATTER_GATHER 0x08 #define USBDEVFS_CAP_REAP_AFTER_DISCONNECT 0x10 #define USBDEVFS_CAP_MMAP 0x20 +#define USBDEVFS_CAP_DROP_PRIVILEGES 0x40 /* USBDEVFS_DISCONNECT_CLAIM flags & struct */ @@ -188,5 +189,6 @@ struct usbdevfs_streams { #define USBDEVFS_DISCONNECT_CLAIM _IOR('U', 27, struct usbdevfs_disconnect_claim) #define USBDEVFS_ALLOC_STREAMS _IOR('U', 28, struct usbdevfs_streams) #define USBDEVFS_FREE_STREAMS _IOR('U', 29, struct usbdevfs_streams) +#define USBDEVFS_DROP_PRIVILEGES _IOW('U', 30, __u32) #endif /* _UAPI_LINUX_USBDEVICE_FS_H */