Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit d883f52e authored by Reilly Grant's avatar Reilly Grant Committed by Greg Kroah-Hartman
Browse files

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: default avatarReilly Grant <reillyg@chromium.org>
Acked-by: default avatarAlan Stern <stern@rowland.harvard.edu>
Signed-off-by: default avatarEmilio López <emilio.lopez@collabora.co.uk>
Signed-off-by: default avatarGreg Kroah-Hartman <gregkh@linuxfoundation.org>
parent 3d0712de
Loading
Loading
Loading
Loading
+12 −0
Original line number Diff line number Diff line
@@ -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>
+120 −0
Original line number Diff line number Diff line
#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;
}
+58 −5
Original line number Diff line number Diff line
@@ -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:
+2 −0
Original line number Diff line number Diff line
@@ -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 */