diff options
author | Dmitry Torokhov <dmitry.torokhov@gmail.com> | 2012-07-30 01:48:31 -0400 |
---|---|---|
committer | Dmitry Torokhov <dmitry.torokhov@gmail.com> | 2012-08-22 01:29:53 -0400 |
commit | 22ae19c6e3c22b390952e90f452f26adad9b8687 (patch) | |
tree | 2de61c0c509f60e38306fad5b523e2b11420d134 /drivers/input | |
parent | f40033acc2d14acecd1b27a79dc8a0ad437e619a (diff) |
Input: uinput - fix race that can block nonblocking read
Consider two threads calling read() on the same uinput-fd, both
non-blocking. Assume there is data-available so both will simultaneously
pass:
udev->head == udev->tail
Then the first thread goes to sleep and the second one pops the message
from the queue. Now assume udev->head == udev->tail. If the first thread
wakes up it will call wait_event_*() and sleep in the waitq. This
effectively turns the non-blocking FD into a blocking one.
We fix this by attempting to fetch events from the queue first and only
if we fail to retrieve any events we either return -EAGAIN (in case of
non-blocing read) or wait until there are more events.
This also fixes incorrect return code (we were returning 0 instead of
-EAGAIN for non-blocking reads) when an event is "stolen" by another
thread. Blocking reads will now continue to wait instead of returning 0
in this scenario.
Count of 0 continues to be a special case, as per spec: we will check for
device existence and whether there are events in the queue, but no events
will be actually retrieved.
Reported-by: David Herrmann <dh.herrmann@googlemail.com>
Signed-off-by: Dmitry Torokhov <dtor@mail.ru>
Diffstat (limited to 'drivers/input')
-rw-r--r-- | drivers/input/misc/uinput.c | 74 |
1 files changed, 44 insertions, 30 deletions
diff --git a/drivers/input/misc/uinput.c b/drivers/input/misc/uinput.c index e74ed9cc6371..1719554fe194 100644 --- a/drivers/input/misc/uinput.c +++ b/drivers/input/misc/uinput.c | |||
@@ -439,6 +439,9 @@ static ssize_t uinput_write(struct file *file, const char __user *buffer, size_t | |||
439 | struct uinput_device *udev = file->private_data; | 439 | struct uinput_device *udev = file->private_data; |
440 | int retval; | 440 | int retval; |
441 | 441 | ||
442 | if (count == 0) | ||
443 | return 0; | ||
444 | |||
442 | retval = mutex_lock_interruptible(&udev->mutex); | 445 | retval = mutex_lock_interruptible(&udev->mutex); |
443 | if (retval) | 446 | if (retval) |
444 | return retval; | 447 | return retval; |
@@ -470,48 +473,59 @@ static bool uinput_fetch_next_event(struct uinput_device *udev, | |||
470 | return have_event; | 473 | return have_event; |
471 | } | 474 | } |
472 | 475 | ||
473 | static ssize_t uinput_read(struct file *file, char __user *buffer, size_t count, loff_t *ppos) | 476 | static ssize_t uinput_events_to_user(struct uinput_device *udev, |
477 | char __user *buffer, size_t count) | ||
474 | { | 478 | { |
475 | struct uinput_device *udev = file->private_data; | ||
476 | struct input_event event; | 479 | struct input_event event; |
477 | int retval = 0; | 480 | size_t read = 0; |
481 | int error = 0; | ||
478 | 482 | ||
479 | if (count != 0 && count < input_event_size()) | 483 | while (read + input_event_size() <= count && |
480 | return -EINVAL; | 484 | uinput_fetch_next_event(udev, &event)) { |
481 | 485 | ||
482 | if (udev->state != UIST_CREATED) | 486 | if (input_event_to_user(buffer + read, &event)) { |
483 | return -ENODEV; | 487 | error = -EFAULT; |
488 | break; | ||
489 | } | ||
484 | 490 | ||
485 | if (udev->head == udev->tail && (file->f_flags & O_NONBLOCK)) | 491 | read += input_event_size(); |
486 | return -EAGAIN; | 492 | } |
487 | 493 | ||
488 | retval = wait_event_interruptible(udev->waitq, | 494 | return read ?: error; |
489 | udev->head != udev->tail || udev->state != UIST_CREATED); | 495 | } |
490 | if (retval) | ||
491 | return retval; | ||
492 | 496 | ||
493 | retval = mutex_lock_interruptible(&udev->mutex); | 497 | static ssize_t uinput_read(struct file *file, char __user *buffer, |
494 | if (retval) | 498 | size_t count, loff_t *ppos) |
495 | return retval; | 499 | { |
500 | struct uinput_device *udev = file->private_data; | ||
501 | ssize_t retval; | ||
496 | 502 | ||
497 | if (udev->state != UIST_CREATED) { | 503 | if (count != 0 && count < input_event_size()) |
498 | retval = -ENODEV; | 504 | return -EINVAL; |
499 | goto out; | ||
500 | } | ||
501 | 505 | ||
502 | while (retval + input_event_size() <= count && | 506 | do { |
503 | uinput_fetch_next_event(udev, &event)) { | 507 | retval = mutex_lock_interruptible(&udev->mutex); |
508 | if (retval) | ||
509 | return retval; | ||
504 | 510 | ||
505 | if (input_event_to_user(buffer + retval, &event)) { | 511 | if (udev->state != UIST_CREATED) |
506 | retval = -EFAULT; | 512 | retval = -ENODEV; |
507 | goto out; | 513 | else if (udev->head == udev->tail && |
508 | } | 514 | (file->f_flags & O_NONBLOCK)) |
515 | retval = -EAGAIN; | ||
516 | else | ||
517 | retval = uinput_events_to_user(udev, buffer, count); | ||
509 | 518 | ||
510 | retval += input_event_size(); | 519 | mutex_unlock(&udev->mutex); |
511 | } | ||
512 | 520 | ||
513 | out: | 521 | if (retval || count == 0) |
514 | mutex_unlock(&udev->mutex); | 522 | break; |
523 | |||
524 | if (!(file->f_flags & O_NONBLOCK)) | ||
525 | retval = wait_event_interruptible(udev->waitq, | ||
526 | udev->head != udev->tail || | ||
527 | udev->state != UIST_CREATED); | ||
528 | } while (retval == 0); | ||
515 | 529 | ||
516 | return retval; | 530 | return retval; |
517 | } | 531 | } |