From: Gary King <gary.king@oculus.com>
To: Jiri Kosina <jikos@kernel.org>
Cc: Gary King <gary.king@oculus.com>,
benjamin.tissoires@redhat.com, linux-input@vger.kernel.org
Subject: Re: [PATCH] hidraw: fix list->buffer race condition
Date: Fri, 7 Oct 2016 10:30:19 -0700 [thread overview]
Message-ID: <alpine.OSX.1.10.1610071027351.23556@cromulentg-mba.local> (raw)
In-Reply-To: <alpine.LNX.2.00.1610071038340.31629@cbobk.fhfr.pm>
Hi Jiri,
> > hidraw input events are stored for each file descriptor in a lockless
> > circular queue.
>
> Well, there is a per-hidraw device list_lock; but you're right that it's
> usage throughout hidraw.c is probably incomplete and that should be fixed.
The complication with list_lock is that it is a spin-lock (and IRQ-safe
at that), and I did not want to introduce multiple IRQ-safe spinlock
acquires in hidraw_read (either via list_lock or by addings a per-fd
spinlock) without a substantially better test configuration than my
environment is able to accommodate.
> > ---
> > drivers/hid/hidraw.c | 7 ++++++-
> > 1 file changed, 6 insertions(+), 1 deletion(-)
> >
> > diff --git a/drivers/hid/hidraw.c b/drivers/hid/hidraw.c
> > index f0e2757..dc3465f 100644
> > --- a/drivers/hid/hidraw.c
> > +++ b/drivers/hid/hidraw.c
> > @@ -53,6 +53,7 @@ static ssize_t hidraw_read(struct file *file, char __user *buffer, size_t count,
> > mutex_lock(&list->read_mutex);
> >
> > while (ret == 0) {
> > + smp_rmb();
>
> Which _wmb() does this one pair with?
The _rmb() operations don't really pair with the _wmb(), and aren't
needed for correctness (I should have cleaned this up before providing
the patch, sorry). Using _rmb() here (and below) reduces rare chances
that the buffer is incorrectly detected as full / empty due to reads
being reordered across loop iterations when a second core enters
_read() / _report_event().
> > if (list->head == list->tail) {
> > add_wait_queue(&list->hidraw->wait, &wait);
> > set_current_state(TASK_INTERRUPTIBLE);
> > @@ -98,7 +99,9 @@ static ssize_t hidraw_read(struct file *file, char __user *buffer, size_t count,
> >
> > kfree(list->buffer[list->tail].value);
> > list->buffer[list->tail].value = NULL;
> > + smp_wmb();
> > list->tail = (list->tail + 1) & (HIDRAW_BUFFER_SIZE - 1);
> > + smp_wmb();
>
> This is completely confusing; it seems like you imply that those two
> _wmb() should pair together, but that makes no sense. Please always, when
> using SMP barriers, document which _rmb() pairs to which _wmb(), otherwise
> the rules become quickly totally unclear.
Upon further review, I believe the second _wmb is unnecessary, and
redundant with memory barrier operations performed either by subsequent
loop iterations or by the mutex_unlock operation (I originally included it
to reduce the window in which a free entry in the circular queue was
unobservable to other cores, forgetting that the barrier would also be
provided by subsequent loop iterations or the mutex_unlock below).
The fundamental problem we encountered was that changes to the list->tail
and list->head values were visible on other processors before the
corresponding changes to list->buffer and list->buffer[N].
> > }
> > out:
> > mutex_unlock(&list->read_mutex);
> > @@ -487,7 +490,7 @@ int hidraw_report_event(struct hid_device *hid, u8 *data, int len)
> > spin_lock_irqsave(&dev->list_lock, flags);
> > list_for_each_entry(list, &dev->list, node) {
> > int new_head = (list->head + 1) & (HIDRAW_BUFFER_SIZE - 1);
> > -
> > + smp_rmb();
> > if (new_head == list->tail)
>
> Again, which _wmb() does this one pair with please?
>
> > continue;
> >
> > @@ -496,7 +499,9 @@ int hidraw_report_event(struct hid_device *hid, u8 *data, int len)
> > break;
> > }
> > list->buffer[list->head].len = len;
> > + smp_wmb();
> > list->head = new_head;
> > + smp_wmb();
>
> Same as in hidraw_read(), I completely fail to see the point of having two
> here. Which read side does each of those pair with?
>
> I think this is just papering the real problem (missing list_lock) over in
> an odd way; could you please look into whether locking list_lock properly
> would resolve the crashes you're able to trigger on your ARM system?
list_lock does not appear to be a good solution for this, since it is
currently an IRQ-safe spinlock on the device. Using it would cause
_read() to have unnecessary lock contention if the device is opened
and read simultaneously from multiple fds, and every loop iteration would
need to acquire the spinlock / disable interrupts at least once.
(Separately, the current locking design seems dangerous to me, since the
amount of time spent with interrupts disabled in the current
implementation may be arbitrarily long (the device may be opened
multiple times, and the hidraw event buffer that is kmemdup'd for each
open fd may be large), but I wanted to supply a patch that fixed the
crash before embarking on any more substantial changes to the stack.)
The patch I supplied should be functionally correct when reduced to just
two calls to smp_wmb(): one preceding the update to list->tail in
_read(), one preceding the update to list-head in _report_event(). I'm
happy to make and test this change.
- Gary
next prev parent reply other threads:[~2016-10-07 17:31 UTC|newest]
Thread overview: 8+ messages / expand[flat|nested] mbox.gz Atom feed top
2016-10-05 0:44 [PATCH] hidraw: fix list->buffer race condition Gary King
2016-10-05 7:53 ` Benjamin Tissoires
2016-10-07 8:43 ` Jiri Kosina
2016-10-07 8:43 ` Jiri Kosina
2016-10-07 17:30 ` Gary King [this message]
2016-10-10 8:36 ` Jiri Kosina
2016-10-11 3:12 ` Dmitry Torokhov
2016-10-14 1:29 ` Gary King
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=alpine.OSX.1.10.1610071027351.23556@cromulentg-mba.local \
--to=gary.king@oculus.com \
--cc=benjamin.tissoires@redhat.com \
--cc=jikos@kernel.org \
--cc=linux-input@vger.kernel.org \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox