]> git.karo-electronics.de Git - karo-tx-linux.git/commitdiff
usb: devio: Add ioctl to disallow detaching kernel USB drivers.
authorReilly Grant <reillyg@chromium.org>
Sun, 21 Feb 2016 21:38:01 +0000 (18:38 -0300)
committerGreg Kroah-Hartman <gregkh@linuxfoundation.org>
Sat, 5 Mar 2016 20:05:01 +0000 (12:05 -0800)
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>
Documentation/DocBook/usb.tmpl
Documentation/usb/usbdevfs-drop-permissions.c [new file with mode: 0644]
drivers/usb/core/devio.c
include/uapi/linux/usbdevice_fs.h

index 4cd5b2cd0f3d47e11e0d3cf8c9b76d3f39b8ec83..bc776be0f19c15bba21577dc97c1bfb37c9ced2b 100644 (file)
@@ -732,6 +732,18 @@ usbdev_ioctl (int fd, int ifno, unsigned request, void *param)
                    or SET_INTERFACE.
                    </para></warning></listitem></varlistentry>
 
+               <varlistentry><term>USBDEVFS_DROP_PRIVILEGES</term>
+                   <listitem><para>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.
+                   </para></listitem></varlistentry>
                </variablelist>
 
                </sect2>
diff --git a/Documentation/usb/usbdevfs-drop-permissions.c b/Documentation/usb/usbdevfs-drop-permissions.c
new file mode 100644 (file)
index 0000000..6b8da6e
--- /dev/null
@@ -0,0 +1,120 @@
+#include <sys/ioctl.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <stdio.h>
+#include <errno.h>
+#include <string.h>
+#include <inttypes.h>
+#include <unistd.h>
+
+#include <linux/usbdevice_fs.h>
+
+/* 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;
+}
index 39da1662b76fb1c6ff60b199d2136c599794625e..52c4461dfccddecff1bf1708c677bb605e9bc5a9 100644 (file)
@@ -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:
index ecbd17650e6caf478ef6d59978db4e0f513da8e8..a8653a6f40df3b93e1b06510ca97ddffbc6596b4 100644 (file)
@@ -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 */