Re: [PATCH v3] Input: evdev - Flush queues during EVIOCGKEY-like ioctls

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

 



Hi Dmitry

Any update on this?

Regards
David

On Thu, Mar 28, 2013 at 12:59 PM, David Herrmann <dh.herrmann@xxxxxxxxx> wrote:
> If userspace requests current KEY-state, they very likely assume that no
> such events are pending in the output queue of the evdev device.
> Otherwise, they will parse events which they already handled via
> EVIOCGKEY(). For XKB applications this can cause irreversible keyboard
> states if a modifier is locked multiple times because a CTRL-DOWN event is
> handled once via EVIOCGKEY() and once from the queue via read(), even
> though it should handle it only once.
>
> Therefore, lets do the only logical thing and flush the evdev queue
> atomically during this ioctl. We only flush events that are affected by
> the given ioctl.
>
> This only affects boolean events like KEY, SND, SW and LED. ABS, REL and
> others are not affected as duplicate events can be handled gracefully by
> user-space.
>
> Note: This actually breaks semantics of the evdev ABI. However,
> investigations showed that userspace already expects the new semantics and
> we end up fixing at least all XKB applications.
> All applications that are aware of this race-condition mirror the KEY
> state for each open-file and detect/drop duplicate events. Hence, they do
> not care whether duplicates are posted or not and work fine with this fix.
>
> Also note that we need proper locking to guarantee atomicity and avoid
> dead-locks. event_lock must be locked before queue_lock (see input-core).
> However, we can safely release event_lock while flushing the queue. This
> allows the input-core to proceed with pending events and only stop if it
> needs our queue_lock to post new events.
> This should guarantee that we don't block event-dispatching for too long
> while flushing a single event queue.
>
> Signed-off-by: David Herrmann <dh.herrmann@xxxxxxxxx>
> ---
> Hi Dmitry
>
> I hope this time everything is right. I tested this on my local machine with
> uinput. You can find the selftests here:
>   https://gist.github.com/dvdhrm/5262589/raw/a3802ebfea0c319e42842664b0d7d1e7580197f1/inputtest.c
> With syntax-highlighting:
>   https://gist.github.com/dvdhrm/5262589
>
> I'm currently not entirely sure how the kernel selftests in
> tools/testing/selftests/ work so I haven't added it. However, feel free to add
> this. It's public domain.
>
> The script tests normal input-queue operation, SYN_DROPPED, SYN_REPORT,
> EVIOCGKEY+FLUSH, EVIOCKGEY+FLUSH+SYN_DROPPED.
> Everything worked as expected and the EVIOCGKEY tests fail without this patch
> applied (as expected).
>
> Thanks
> David
>
>  drivers/input/evdev.c | 133 ++++++++++++++++++++++++++++++++++++++++++++++++--
>  1 file changed, 129 insertions(+), 4 deletions(-)
>
> diff --git a/drivers/input/evdev.c b/drivers/input/evdev.c
> index f0f8928..e5ceebc 100644
> --- a/drivers/input/evdev.c
> +++ b/drivers/input/evdev.c
> @@ -52,6 +52,82 @@ struct evdev_client {
>         struct input_event buffer[];
>  };
>
> +/* flush queued events of type @type, caller must hold client->buffer_lock */
> +static void __flush_queue(struct evdev_client *client, unsigned int type)
> +{
> +       unsigned int i, head, num;
> +       unsigned int mask = client->bufsize - 1;
> +       bool is_report;
> +       struct input_event *ev;
> +
> +       BUG_ON(type == EV_SYN);
> +
> +       head = client->tail;
> +       client->packet_head = client->tail;
> +
> +       /* init to 1 so a leading SYN_REPORT will not be dropped */
> +       num = 1;
> +
> +       for (i = client->tail; i != client->head; i = (i + 1) & mask) {
> +               ev = &client->buffer[i];
> +               is_report = ev->type == EV_SYN && ev->code == SYN_REPORT;
> +
> +               if (ev->type == type) {
> +                       /* drop matched entry */
> +                       continue;
> +               } else if (is_report && !num) {
> +                       /* drop empty SYN_REPORT groups */
> +                       continue;
> +               } else if (head != i) {
> +                       /* move entry to fill the gap */
> +                       client->buffer[head].time = ev->time;
> +                       client->buffer[head].type = ev->type;
> +                       client->buffer[head].code = ev->code;
> +                       client->buffer[head].value = ev->value;
> +               }
> +
> +               num++;
> +               head = (head + 1) & mask;
> +
> +               if (is_report) {
> +                       num = 0;
> +                       client->packet_head = head;
> +               }
> +       }
> +
> +       client->head = head;
> +}
> +
> +/* queue SYN_DROPPED event */
> +static void queue_syn_dropped(struct evdev_client *client)
> +{
> +       unsigned long flags;
> +       struct input_event ev;
> +       ktime_t time;
> +
> +       time = ktime_get();
> +       if (client->clkid != CLOCK_MONOTONIC)
> +               time = ktime_sub(time, ktime_get_monotonic_offset());
> +
> +       ev.time = ktime_to_timeval(time);
> +       ev.type = EV_SYN;
> +       ev.code = SYN_DROPPED;
> +       ev.value = 0;
> +
> +       spin_lock_irqsave(&client->buffer_lock, flags);
> +
> +       client->buffer[client->head++] = ev;
> +       client->head &= client->bufsize - 1;
> +
> +       if (unlikely(client->head == client->tail)) {
> +               /* drop queue but keep our SYN_DROPPED event */
> +               client->tail = (client->head - 1) & (client->bufsize - 1);
> +               client->packet_head = client->tail;
> +       }
> +
> +       spin_unlock_irqrestore(&client->buffer_lock, flags);
> +}
> +
>  static void __pass_event(struct evdev_client *client,
>                          const struct input_event *event)
>  {
> @@ -650,6 +726,51 @@ static int evdev_handle_set_keycode_v2(struct input_dev *dev, void __user *p)
>         return input_set_keycode(dev, &ke);
>  }
>
> +/*
> + * If we transfer state to the user, we should flush all pending events
> + * from the client's queue. Otherwise, they might end up with duplicate
> + * events, which can screw up client's state tracking.
> + * If bits_to_user fails after flushing the queue, we queue a SYN_DROPPED event
> + * so user-space will notice missing events.
> + *
> + * LOCKING:
> + * We need to take event_lock before buffer_lock to avoid dead-locks. But we
> + * need the even_lock only to guarantee consistent state. We can safely release
> + * it while flushing the queue. This allows input-core to handle filters while
> + * we flush the queue.
> + */
> +static int evdev_handle_get_val(struct evdev_client *client,
> +                               struct input_dev *dev, unsigned int type,
> +                               unsigned long *bits, unsigned int max,
> +                               unsigned int size, void __user *p, int compat)
> +{
> +       int ret;
> +       unsigned long *mem;
> +
> +       mem = kmalloc(sizeof(unsigned long) * max, GFP_KERNEL);
> +       if (!mem)
> +               return -ENOMEM;
> +
> +       spin_lock_irq(&dev->event_lock);
> +       spin_lock(&client->buffer_lock);
> +
> +       memcpy(mem, bits, sizeof(unsigned long) * max);
> +
> +       spin_unlock(&dev->event_lock);
> +
> +       __flush_queue(client, type);
> +
> +       spin_unlock_irq(&client->buffer_lock);
> +
> +       ret = bits_to_user(mem, max, size, p, compat);
> +       if (ret < 0)
> +               queue_syn_dropped(client);
> +
> +       kfree(mem);
> +
> +       return ret;
> +}
> +
>  static int evdev_handle_mt_request(struct input_dev *dev,
>                                    unsigned int size,
>                                    int __user *ip)
> @@ -771,16 +892,20 @@ static long evdev_do_ioctl(struct file *file, unsigned int cmd,
>                 return evdev_handle_mt_request(dev, size, ip);
>
>         case EVIOCGKEY(0):
> -               return bits_to_user(dev->key, KEY_MAX, size, p, compat_mode);
> +               return evdev_handle_get_val(client, dev, EV_KEY, dev->key,
> +                                           KEY_MAX, size, p, compat_mode);
>
>         case EVIOCGLED(0):
> -               return bits_to_user(dev->led, LED_MAX, size, p, compat_mode);
> +               return evdev_handle_get_val(client, dev, EV_LED, dev->led,
> +                                           LED_MAX, size, p, compat_mode);
>
>         case EVIOCGSND(0):
> -               return bits_to_user(dev->snd, SND_MAX, size, p, compat_mode);
> +               return evdev_handle_get_val(client, dev, EV_SND, dev->snd,
> +                                           SND_MAX, size, p, compat_mode);
>
>         case EVIOCGSW(0):
> -               return bits_to_user(dev->sw, SW_MAX, size, p, compat_mode);
> +               return evdev_handle_get_val(client, dev, EV_SW, dev->sw,
> +                                           SW_MAX, size, p, compat_mode);
>
>         case EVIOCGNAME(0):
>                 return str_to_user(dev->name, size, p);
> --
> 1.8.2
>
--
To unsubscribe from this list: send the line "unsubscribe linux-input" in
the body of a message to majordomo@xxxxxxxxxxxxxxx
More majordomo info at  http://vger.kernel.org/majordomo-info.html




[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