Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux

HID: hidraw: add HIDIOCREVOKE ioctl

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>

authored by

Peter Hutterer and committed by
Benjamin Tissoires
b31c9d9d 6e443653

+37 -4
+35 -4
drivers/hid/hidraw.c
··· 38 38 static struct hidraw *hidraw_table[HIDRAW_MAX_DEVICES]; 39 39 static DECLARE_RWSEM(minors_rwsem); 40 40 41 + static inline bool hidraw_is_revoked(struct hidraw_list *list) 42 + { 43 + return list->revoked; 44 + } 45 + 41 46 static ssize_t hidraw_read(struct file *file, char __user *buffer, size_t count, loff_t *ppos) 42 47 { 43 48 struct hidraw_list *list = file->private_data; 44 49 int ret = 0, len; 45 50 DECLARE_WAITQUEUE(wait, current); 51 + 52 + if (hidraw_is_revoked(list)) 53 + return -ENODEV; 46 54 47 55 mutex_lock(&list->read_mutex); 48 56 ··· 169 161 170 162 static ssize_t hidraw_write(struct file *file, const char __user *buffer, size_t count, loff_t *ppos) 171 163 { 164 + struct hidraw_list *list = file->private_data; 172 165 ssize_t ret; 173 166 down_read(&minors_rwsem); 174 - ret = hidraw_send_report(file, buffer, count, HID_OUTPUT_REPORT); 167 + if (hidraw_is_revoked(list)) 168 + ret = -ENODEV; 169 + else 170 + ret = hidraw_send_report(file, buffer, count, HID_OUTPUT_REPORT); 175 171 up_read(&minors_rwsem); 176 172 return ret; 177 173 } ··· 268 256 poll_wait(file, &list->hidraw->wait, wait); 269 257 if (list->head != list->tail) 270 258 mask |= EPOLLIN | EPOLLRDNORM; 271 - if (!list->hidraw->exist) 259 + if (!list->hidraw->exist || hidraw_is_revoked(list)) 272 260 mask |= EPOLLERR | EPOLLHUP; 273 261 return mask; 274 262 } ··· 332 320 { 333 321 struct hidraw_list *list = file->private_data; 334 322 323 + if (hidraw_is_revoked(list)) 324 + return -ENODEV; 325 + 335 326 return fasync_helper(fd, file, on, &list->fasync); 336 327 } 337 328 ··· 387 372 return 0; 388 373 } 389 374 375 + static int hidraw_revoke(struct hidraw_list *list) 376 + { 377 + list->revoked = true; 378 + 379 + return 0; 380 + } 381 + 390 382 static long hidraw_ioctl(struct file *file, unsigned int cmd, 391 383 unsigned long arg) 392 384 { ··· 401 379 unsigned int minor = iminor(inode); 402 380 long ret = 0; 403 381 struct hidraw *dev; 382 + struct hidraw_list *list = file->private_data; 404 383 void __user *user_arg = (void __user*) arg; 405 384 406 385 down_read(&minors_rwsem); 407 386 dev = hidraw_table[minor]; 408 - if (!dev || !dev->exist) { 387 + if (!dev || !dev->exist || hidraw_is_revoked(list)) { 409 388 ret = -ENODEV; 410 389 goto out; 411 390 } ··· 442 419 dinfo.product = dev->hid->product; 443 420 if (copy_to_user(user_arg, &dinfo, sizeof(dinfo))) 444 421 ret = -EFAULT; 422 + break; 423 + } 424 + case HIDIOCREVOKE: 425 + { 426 + if (user_arg) 427 + ret = -EINVAL; 428 + else 429 + ret = hidraw_revoke(list); 445 430 break; 446 431 } 447 432 default: ··· 558 527 list_for_each_entry(list, &dev->list, node) { 559 528 int new_head = (list->head + 1) & (HIDRAW_BUFFER_SIZE - 1); 560 529 561 - if (new_head == list->tail) 530 + if (hidraw_is_revoked(list) || new_head == list->tail) 562 531 continue; 563 532 564 533 if (!(list->buffer[list->head].value = kmemdup(data, len, GFP_ATOMIC))) {
+1
include/linux/hidraw.h
··· 32 32 struct hidraw *hidraw; 33 33 struct list_head node; 34 34 struct mutex read_mutex; 35 + bool revoked; 35 36 }; 36 37 37 38 #ifdef CONFIG_HIDRAW
+1
include/uapi/linux/hidraw.h
··· 46 46 /* The first byte of SOUTPUT and GOUTPUT is the report number */ 47 47 #define HIDIOCSOUTPUT(len) _IOC(_IOC_WRITE|_IOC_READ, 'H', 0x0B, len) 48 48 #define HIDIOCGOUTPUT(len) _IOC(_IOC_WRITE|_IOC_READ, 'H', 0x0C, len) 49 + #define HIDIOCREVOKE _IOW('H', 0x0D, int) /* Revoke device access */ 49 50 50 51 #define HIDRAW_FIRST_MINOR 0 51 52 #define HIDRAW_MAX_DEVICES 64