Re: [PATCH] hidraw: fix list->buffer race condition

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

 



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
--
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