Re: [PATCH] HID: hidraw - add HIDIOCREVOKE ioctl

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



On Thu, Apr 21, 2022 at 9:41 AM Benjamin Tissoires
<benjamin.tissoires@xxxxxxxxxx> wrote:
>
>
>
> On Thu, Apr 21, 2022 at 8:57 AM Peter Hutterer <peter.hutterer@xxxxxxxxx> wrote:
> >
> > 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 c7dc65737c9a607d3e6f8478659876074ad129b8 for full details.
> >
> > A draft MR for systemd-logind has been filed here:
> > https://github.com/systemd/systemd/pull/23140
> >
> > Signed-off-by: Peter Hutterer <peter.hutterer@xxxxxxxxx>
> > ---
> > Maybe noteworthy: even with logind support this is only the first step of
> > many. logind only hands the fd to whoever controls the session and the fd will
> > then have to be passed forward through portals to the application.
> >
> >  drivers/hid/hidraw.c        | 34 ++++++++++++++++++++++++++++++----
> >  include/linux/hidraw.h      |  1 +
> >  include/uapi/linux/hidraw.h |  1 +
> >  3 files changed, 32 insertions(+), 4 deletions(-)
> >
> > diff --git a/drivers/hid/hidraw.c b/drivers/hid/hidraw.c
> > index 681614a8302a..3449fe856090 100644
> > --- a/drivers/hid/hidraw.c
> > +++ b/drivers/hid/hidraw.c
> > @@ -42,6 +42,9 @@ static ssize_t hidraw_read(struct file *file, char __user *buffer, size_t count,
> >         int ret = 0, len;
> >         DECLARE_WAITQUEUE(wait, current);
> >
> > +       if (list->revoked)
> > +               return -ENODEV;
> > +
> >         mutex_lock(&list->read_mutex);
> >
> >         while (ret == 0) {
> > @@ -159,9 +162,13 @@ static ssize_t hidraw_send_report(struct file *file, const char __user *buffer,
> >
> >  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 (list->revoked)
> > +               ret = -ENODEV;
> > +       else
> > +               ret = hidraw_send_report(file, buffer, count, HID_OUTPUT_REPORT);
> >         up_read(&minors_rwsem);
> >         return ret;
> >  }
> > @@ -254,7 +261,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 || list->revoked)
> >                 mask |= EPOLLERR | EPOLLHUP;
> >         return mask;
> >  }
> > @@ -313,6 +320,9 @@ static int hidraw_fasync(int fd, struct file *file, int on)
> >  {
> >         struct hidraw_list *list = file->private_data;
> >
> > +       if (list->revoked)
> > +               return -ENODEV;
> > +
> >         return fasync_helper(fd, file, on, &list->fasync);
> >  }
> >
> > @@ -360,6 +370,13 @@ static int hidraw_release(struct inode * inode, struct file * file)
> >         return 0;
> >  }
> >
> > +static int hidraw_revoke(struct hidraw_list *list, struct file *file)
>
> There is no use of *file here, we can drop the argument.
>
> > +{
> > +       list->revoked = true;
> > +
> > +       return 0;
> > +}
> > +
> >  static long hidraw_ioctl(struct file *file, unsigned int cmd,
> >                                                         unsigned long arg)
> >  {
> > @@ -367,11 +384,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 || list->revoked) {
> >                 ret = -ENODEV;
> >                 goto out;
> >         }
> > @@ -409,6 +427,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, file);
> > +                               break;
> > +                       }
> >                 default:
> >                         {
> >                                 struct hid_device *hid = dev->hid;
> > @@ -515,7 +541,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 (list->revoked || new_head == list->tail)
>
> We had quite some discussions offline about that, and I wonder if you
> should not squash the following patch into this one:
>
> ---
> diff --git a/drivers/hid/hidraw.c b/drivers/hid/hidraw.c
> index 3449fe856090..ee5e6fe33a4d 100644
> --- a/drivers/hid/hidraw.c
> +++ b/drivers/hid/hidraw.c
> @@ -36,13 +36,19 @@ static struct class *hidraw_class;
>   static struct hidraw *hidraw_table[HIDRAW_MAX_DEVICES];
>   static DECLARE_RWSEM(minors_rwsem);
>
> +__weak noinline bool hidraw_is_revoked(struct hidraw_list *list)
> +{
> +       return list->revoked;
> +}
> +ALLOW_ERROR_INJECTION(hidraw_is_revoked, TRUE);
> +
>   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 (list->revoked)
> +       if (hidraw_is_revoked(list))
>                 return -ENODEV;
>
>         mutex_lock(&list->read_mutex);
> @@ -165,7 +171,7 @@ static ssize_t hidraw_write(struct file *file, const char __user *buffer, size_t
>         struct hidraw_list *list = file->private_data;
>         ssize_t ret;
>         down_read(&minors_rwsem);
> -       if (list->revoked)
> +       if (hidraw_is_revoked(list))
>                 ret = -ENODEV;
>         else
>                 ret = hidraw_send_report(file, buffer, count, HID_OUTPUT_REPORT);
> @@ -261,7 +267,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 || list->revoked)
> +       if (!list->hidraw->exist || hidraw_is_revoked(list))
>                 mask |= EPOLLERR | EPOLLHUP;
>         return mask;
>   }
> @@ -320,7 +326,7 @@ static int hidraw_fasync(int fd, struct file *file, int on)
>   {
>         struct hidraw_list *list = file->private_data;
>
> -       if (list->revoked)
> +       if (hidraw_is_revoked(list))
>                 return -ENODEV;
>
>         return fasync_helper(fd, file, on, &list->fasync);
> @@ -389,7 +395,7 @@ static long hidraw_ioctl(struct file *file, unsigned int cmd,
>
>         down_read(&minors_rwsem);
>         dev = hidraw_table[minor];
> -       if (!dev || !dev->exist || list->revoked) {
> +       if (!dev || !dev->exist || hidraw_is_revoked(list)) {
>                 ret = -ENODEV;
>                 goto out;
>         }
> @@ -541,7 +547,8 @@ 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 (list->revoked || 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))) {
> ---
>
> The reasons are:
> - we get one common helper for revoked
> - we can then emulate with BPF the ioctl even if logind is not the owner
> of the fd. This way, we can have the functionality without having to
> change a single line in the client applications.
>
> For an example such BPF program, see https://gitlab.freedesktop.org/bentiss/logind-hidraw

Another quick thought: maybe we want stable to be added to this patch.
This code hasn't changed in a while and could easily be backported in
older kernel releases. Not sure if it matches stable criterias though
(but it seems we are more relaxed with those criterias).

Cheers,
Benjamin

>
> Cheers,
> Benjamin
>
> >                         continue;
> >
> >                 if (!(list->buffer[list->head].value = kmemdup(data, len, GFP_ATOMIC))) {
> > diff --git a/include/linux/hidraw.h b/include/linux/hidraw.h
> > index cd67f4ca5599..18fd30a288de 100644
> > --- a/include/linux/hidraw.h
> > +++ b/include/linux/hidraw.h
> > @@ -32,6 +32,7 @@ struct hidraw_list {
> >         struct hidraw *hidraw;
> >         struct list_head node;
> >         struct mutex read_mutex;
> > +       bool revoked;
> >  };
> >
> >  #ifdef CONFIG_HIDRAW
> > diff --git a/include/uapi/linux/hidraw.h b/include/uapi/linux/hidraw.h
> > index 33ebad81720a..d0563f251da5 100644
> > --- a/include/uapi/linux/hidraw.h
> > +++ b/include/uapi/linux/hidraw.h
> > @@ -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
> > --
> > 2.36.0
> >




[Index of Archives]     [Linux Media Devel]     [Linux USB Devel]     [Video for Linux]     [Linux Audio Users]     [Yosemite News]     [Linux Kernel]     [Linux SCSI]     [Linux Wireless Networking]     [Linux Omap]

  Powered by Linux