diff mbox series

USB: add usbfs ioctl to retrieve the connection parameters

Message ID 20190610223658.GA162167@dtor-ws (mailing list archive)
State Mainlined
Commit 6d101f24f1dd41ef6eff3d7f175417ce27a3055a
Headers show
Series USB: add usbfs ioctl to retrieve the connection parameters | expand

Commit Message

Dmitry Torokhov June 10, 2019, 10:36 p.m. UTC
Recently usfbs gained availability to retrieve device speed, but there
is sill no way to determine the bus number or list of ports the device
is connected to when using usbfs. While this information can be obtained
from sysfs, not all environments allow sysfs access. In a jailed
environment a program might be simply given an opened file descriptor to
usbfs device, and it is really important that all data can be gathered
from said file descriptor.

This patch introduces a new ioctl, USBDEVFS_CONNINFO_EX, which return
extended connection information for the device, including the bus
number, address, port list and speed. The API allows kernel to extend
amount of data returned by the ioctl and userspace has an option of
adjusting the amount of data it is willing to consume. A new capability,
USBDEVFS_CAP_CONNINFO_EX, is introduced to help userspace in determining
whether the kernel supports this new ioctl.

Signed-off-by: Dmitry Torokhov <dmitry.torokhov@gmail.com>
---
 drivers/usb/core/devio.c          | 42 ++++++++++++++++++++++++++++++-
 include/uapi/linux/usbdevice_fs.h | 26 +++++++++++++++++++
 2 files changed, 67 insertions(+), 1 deletion(-)

Comments

Alan Stern June 11, 2019, 1:50 p.m. UTC | #1
On Mon, 10 Jun 2019, Dmitry Torokhov wrote:

> Recently usfbs gained availability to retrieve device speed, but there
> is sill no way to determine the bus number or list of ports the device
> is connected to when using usbfs. While this information can be obtained
> from sysfs, not all environments allow sysfs access. In a jailed
> environment a program might be simply given an opened file descriptor to
> usbfs device, and it is really important that all data can be gathered
> from said file descriptor.
> 
> This patch introduces a new ioctl, USBDEVFS_CONNINFO_EX, which return
> extended connection information for the device, including the bus
> number, address, port list and speed. The API allows kernel to extend
> amount of data returned by the ioctl and userspace has an option of
> adjusting the amount of data it is willing to consume. A new capability,
> USBDEVFS_CAP_CONNINFO_EX, is introduced to help userspace in determining
> whether the kernel supports this new ioctl.
> 
> Signed-off-by: Dmitry Torokhov <dmitry.torokhov@gmail.com>

Acked-by: Alan Stern <stern@rowland.harvard.edu>
diff mbox series

Patch

diff --git a/drivers/usb/core/devio.c b/drivers/usb/core/devio.c
index fa783531ee88..fb6a074e4f1d 100644
--- a/drivers/usb/core/devio.c
+++ b/drivers/usb/core/devio.c
@@ -1308,6 +1308,39 @@  static int proc_connectinfo(struct usb_dev_state *ps, void __user *arg)
 	return 0;
 }
 
+static int proc_conninfo_ex(struct usb_dev_state *ps,
+			    void __user *arg, size_t size)
+{
+	struct usbdevfs_conninfo_ex ci;
+	struct usb_device *udev = ps->dev;
+
+	if (size < sizeof(ci.size))
+		return -EINVAL;
+
+	memset(&ci, 0, sizeof(ci));
+	ci.size = sizeof(ci);
+	ci.busnum = udev->bus->busnum;
+	ci.devnum = udev->devnum;
+	ci.speed = udev->speed;
+
+	while (udev && udev->portnum != 0) {
+		if (++ci.num_ports <= ARRAY_SIZE(ci.ports))
+			ci.ports[ARRAY_SIZE(ci.ports) - ci.num_ports] =
+					udev->portnum;
+		udev = udev->parent;
+	}
+
+	if (ci.num_ports < ARRAY_SIZE(ci.ports))
+		memmove(&ci.ports[0],
+			&ci.ports[ARRAY_SIZE(ci.ports) - ci.num_ports],
+			ci.num_ports);
+
+	if (copy_to_user(arg, &ci, min(sizeof(ci), size)))
+		return -EFAULT;
+
+	return 0;
+}
+
 static int proc_resetdevice(struct usb_dev_state *ps)
 {
 	struct usb_host_config *actconfig = ps->dev->actconfig;
@@ -2252,7 +2285,7 @@  static int proc_get_capabilities(struct usb_dev_state *ps, void __user *arg)
 
 	caps = USBDEVFS_CAP_ZERO_PACKET | USBDEVFS_CAP_NO_PACKET_SIZE_LIM |
 			USBDEVFS_CAP_REAP_AFTER_DISCONNECT | USBDEVFS_CAP_MMAP |
-			USBDEVFS_CAP_DROP_PRIVILEGES;
+			USBDEVFS_CAP_DROP_PRIVILEGES | USBDEVFS_CAP_CONNINFO_EX;
 	if (!ps->dev->bus->no_stop_on_short)
 		caps |= USBDEVFS_CAP_BULK_CONTINUATION;
 	if (ps->dev->bus->sg_tablesize)
@@ -2551,6 +2584,13 @@  static long usbdev_do_ioctl(struct file *file, unsigned int cmd,
 		break;
 	}
 
+	/* Handle variable-length commands */
+	switch (cmd & ~IOCSIZE_MASK) {
+	case USBDEVFS_CONNINFO_EX(0):
+		ret = proc_conninfo_ex(ps, p, _IOC_SIZE(cmd));
+		break;
+	}
+
  done:
 	usb_unlock_device(dev);
 	if (ret >= 0)
diff --git a/include/uapi/linux/usbdevice_fs.h b/include/uapi/linux/usbdevice_fs.h
index 964e87217be4..393a2de914e8 100644
--- a/include/uapi/linux/usbdevice_fs.h
+++ b/include/uapi/linux/usbdevice_fs.h
@@ -76,6 +76,26 @@  struct usbdevfs_connectinfo {
 	unsigned char slow;
 };
 
+struct usbdevfs_conninfo_ex {
+	__u32 size;		/* Size of the structure from the kernel's */
+				/* point of view. Can be used by userspace */
+				/* to determine how much data can be       */
+				/* used/trusted.                           */
+	__u32 busnum;           /* USB bus number, as enumerated by the    */
+				/* kernel, the device is connected to.     */
+	__u32 devnum;           /* Device address on the bus.              */
+	__u32 speed;		/* USB_SPEED_* constants from ch9.h        */
+	u8 num_ports;		/* Number of ports the device is connected */
+				/* to on the way to the root hub. It may   */
+				/* be bigger than size of 'ports' array so */
+				/* userspace can detect overflows.         */
+	u8 ports[7];		/* List of ports on the way from the root  */
+				/* hub to the device. Current limit in     */
+				/* USB specification is 7 tiers (root hub, */
+				/* 5 intermediate hubs, device), which     */
+				/* gives at most 6 port entries.           */
+};
+
 #define USBDEVFS_URB_SHORT_NOT_OK	0x01
 #define USBDEVFS_URB_ISO_ASAP		0x02
 #define USBDEVFS_URB_BULK_CONTINUATION	0x04
@@ -137,6 +157,7 @@  struct usbdevfs_hub_portinfo {
 #define USBDEVFS_CAP_REAP_AFTER_DISCONNECT	0x10
 #define USBDEVFS_CAP_MMAP			0x20
 #define USBDEVFS_CAP_DROP_PRIVILEGES		0x40
+#define USBDEVFS_CAP_CONNINFO_EX		0x80
 
 /* USBDEVFS_DISCONNECT_CLAIM flags & struct */
 
@@ -197,5 +218,10 @@  struct usbdevfs_streams {
 #define USBDEVFS_FREE_STREAMS      _IOR('U', 29, struct usbdevfs_streams)
 #define USBDEVFS_DROP_PRIVILEGES   _IOW('U', 30, __u32)
 #define USBDEVFS_GET_SPEED         _IO('U', 31)
+/*
+ * Returns struct usbdevfs_conninfo_ex; length is variable to allow
+ * extending size of the data returned.
+ */
+#define USBDEVFS_CONNINFO_EX(len)  _IOC(_IOC_READ, 'U', 32, len)
 
 #endif /* _UAPI_LINUX_USBDEVICE_FS_H */