HID: hidraw: add HIDIOCREVOKE ioctl
authorPeter Hutterer <peter.hutterer@who-t.net>
Tue, 27 Aug 2024 08:19:29 +0000 (17:19 +0900)
committerBenjamin Tissoires <bentiss@kernel.org>
Thu, 29 Aug 2024 08:39:37 +0000 (10:39 +0200)
There is a need for userspace applications to open HID devices directly.
Use-cases include configuration of gaming mice or direct access to
joystick devices. The latter is currently handled by the uaccess tag in
systemd, other devices include more custom/local configurations or just
sudo.

A better approach is what we already have for evdev devices: give the
application a file descriptor and revoke it when it may no longer access
that device.

This patch is the hidraw equivalent to the EVIOCREVOKE ioctl, see
commit c7dc65737c9a ("Input: evdev - add EVIOCREVOKE ioctl") for full
details.

An MR for systemd-logind has been filed here:
https://github.com/systemd/systemd/pull/33970

Signed-off-by: Peter Hutterer <peter.hutterer@who-t.net>
Link: https://patch.msgid.link/20240827-hidraw-revoke-v5-1-d004a7451aea@kernel.org
Signed-off-by: Benjamin Tissoires <bentiss@kernel.org>
drivers/hid/hidraw.c
include/linux/hidraw.h
include/uapi/linux/hidraw.h

index 716294e..c887f48 100644 (file)
@@ -38,12 +38,20 @@ static const struct class hidraw_class = {
 static struct hidraw *hidraw_table[HIDRAW_MAX_DEVICES];
 static DECLARE_RWSEM(minors_rwsem);
 
+static inline bool hidraw_is_revoked(struct hidraw_list *list)
+{
+       return list->revoked;
+}
+
 static ssize_t hidraw_read(struct file *file, char __user *buffer, size_t count, loff_t *ppos)
 {
        struct hidraw_list *list = file->private_data;
        int ret = 0, len;
        DECLARE_WAITQUEUE(wait, current);
 
+       if (hidraw_is_revoked(list))
+               return -ENODEV;
+
        mutex_lock(&list->read_mutex);
 
        while (ret == 0) {
@@ -161,9 +169,13 @@ out:
 
 static ssize_t hidraw_write(struct file *file, const char __user *buffer, size_t count, loff_t *ppos)
 {
+       struct hidraw_list *list = file->private_data;
        ssize_t ret;
        down_read(&minors_rwsem);
-       ret = hidraw_send_report(file, buffer, count, HID_OUTPUT_REPORT);
+       if (hidraw_is_revoked(list))
+               ret = -ENODEV;
+       else
+               ret = hidraw_send_report(file, buffer, count, HID_OUTPUT_REPORT);
        up_read(&minors_rwsem);
        return ret;
 }
@@ -256,7 +268,7 @@ static __poll_t hidraw_poll(struct file *file, poll_table *wait)
        poll_wait(file, &list->hidraw->wait, wait);
        if (list->head != list->tail)
                mask |= EPOLLIN | EPOLLRDNORM;
-       if (!list->hidraw->exist)
+       if (!list->hidraw->exist || hidraw_is_revoked(list))
                mask |= EPOLLERR | EPOLLHUP;
        return mask;
 }
@@ -320,6 +332,9 @@ static int hidraw_fasync(int fd, struct file *file, int on)
 {
        struct hidraw_list *list = file->private_data;
 
+       if (hidraw_is_revoked(list))
+               return -ENODEV;
+
        return fasync_helper(fd, file, on, &list->fasync);
 }
 
@@ -372,6 +387,13 @@ static int hidraw_release(struct inode * inode, struct file * file)
        return 0;
 }
 
+static int hidraw_revoke(struct hidraw_list *list)
+{
+       list->revoked = true;
+
+       return 0;
+}
+
 static long hidraw_ioctl(struct file *file, unsigned int cmd,
                                                        unsigned long arg)
 {
@@ -379,11 +401,12 @@ static long hidraw_ioctl(struct file *file, unsigned int cmd,
        unsigned int minor = iminor(inode);
        long ret = 0;
        struct hidraw *dev;
+       struct hidraw_list *list = file->private_data;
        void __user *user_arg = (void __user*) arg;
 
        down_read(&minors_rwsem);
        dev = hidraw_table[minor];
-       if (!dev || !dev->exist) {
+       if (!dev || !dev->exist || hidraw_is_revoked(list)) {
                ret = -ENODEV;
                goto out;
        }
@@ -421,6 +444,14 @@ static long hidraw_ioctl(struct file *file, unsigned int cmd,
                                        ret = -EFAULT;
                                break;
                        }
+               case HIDIOCREVOKE:
+                       {
+                               if (user_arg)
+                                       ret = -EINVAL;
+                               else
+                                       ret = hidraw_revoke(list);
+                               break;
+                       }
                default:
                        {
                                struct hid_device *hid = dev->hid;
@@ -527,7 +558,7 @@ int hidraw_report_event(struct hid_device *hid, u8 *data, int len)
        list_for_each_entry(list, &dev->list, node) {
                int new_head = (list->head + 1) & (HIDRAW_BUFFER_SIZE - 1);
 
-               if (new_head == list->tail)
+               if (hidraw_is_revoked(list) || new_head == list->tail)
                        continue;
 
                if (!(list->buffer[list->head].value = kmemdup(data, len, GFP_ATOMIC))) {
index cd67f4c..18fd30a 100644 (file)
@@ -32,6 +32,7 @@ struct hidraw_list {
        struct hidraw *hidraw;
        struct list_head node;
        struct mutex read_mutex;
+       bool revoked;
 };
 
 #ifdef CONFIG_HIDRAW
index 33ebad8..d5ee269 100644 (file)
@@ -46,6 +46,7 @@ struct hidraw_devinfo {
 /* The first byte of SOUTPUT and GOUTPUT is the report number */
 #define HIDIOCSOUTPUT(len)    _IOC(_IOC_WRITE|_IOC_READ, 'H', 0x0B, len)
 #define HIDIOCGOUTPUT(len)    _IOC(_IOC_WRITE|_IOC_READ, 'H', 0x0C, len)
+#define HIDIOCREVOKE         _IOW('H', 0x0D, int) /* Revoke device access */
 
 #define HIDRAW_FIRST_MINOR 0
 #define HIDRAW_MAX_DEVICES 64