aboutsummaryrefslogtreecommitdiffstats
path: root/drivers
diff options
context:
space:
mode:
authorDmitry Torokhov <dmitry.torokhov@gmail.com>2012-07-30 01:48:31 -0400
committerDmitry Torokhov <dmitry.torokhov@gmail.com>2012-08-22 01:29:53 -0400
commit22ae19c6e3c22b390952e90f452f26adad9b8687 (patch)
tree2de61c0c509f60e38306fad5b523e2b11420d134 /drivers
parentf40033acc2d14acecd1b27a79dc8a0ad437e619a (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')
-rw-r--r--drivers/input/misc/uinput.c74
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
473static ssize_t uinput_read(struct file *file, char __user *buffer, size_t count, loff_t *ppos) 476static 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); 497static 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}