Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux

hci_usb.h: fix hard-to-trigger race

If someone tries to _urb_unlink while _urb_queue_head is running, he'll see
_urb->queue == NULL and fail to do any locking. Prevent that from happening
by strategically placed barriers.

Signed-off-by: Pavel Machek <pavel@suse.cz>
Signed-off-by: Andrew Morton <akpm@linux-foundation.org>
Signed-off-by: David S. Miller <davem@davemloft.net>

authored by

Pavel Machek and committed by
David S. Miller
026672d0 84994e16

+13 -8
+13 -8
drivers/bluetooth/hci_usb.h
··· 70 70 { 71 71 unsigned long flags; 72 72 spin_lock_irqsave(&q->lock, flags); 73 - list_add(&_urb->list, &q->head); _urb->queue = q; 73 + /* _urb_unlink needs to know which spinlock to use, thus mb(). */ 74 + _urb->queue = q; mb(); list_add(&_urb->list, &q->head); 74 75 spin_unlock_irqrestore(&q->lock, flags); 75 76 } 76 77 ··· 79 78 { 80 79 unsigned long flags; 81 80 spin_lock_irqsave(&q->lock, flags); 82 - list_add_tail(&_urb->list, &q->head); _urb->queue = q; 81 + /* _urb_unlink needs to know which spinlock to use, thus mb(). */ 82 + _urb->queue = q; mb(); list_add_tail(&_urb->list, &q->head); 83 83 spin_unlock_irqrestore(&q->lock, flags); 84 84 } 85 85 86 86 static inline void _urb_unlink(struct _urb *_urb) 87 87 { 88 - struct _urb_queue *q = _urb->queue; 88 + struct _urb_queue *q; 89 89 unsigned long flags; 90 - if (q) { 91 - spin_lock_irqsave(&q->lock, flags); 92 - list_del(&_urb->list); _urb->queue = NULL; 93 - spin_unlock_irqrestore(&q->lock, flags); 94 - } 90 + 91 + mb(); 92 + q = _urb->queue; 93 + /* If q is NULL, it will die at easy-to-debug NULL pointer dereference. 94 + No need to BUG(). */ 95 + spin_lock_irqsave(&q->lock, flags); 96 + list_del(&_urb->list); _urb->queue = NULL; 97 + spin_unlock_irqrestore(&q->lock, flags); 95 98 } 96 99 97 100 struct hci_usb {