linux-kernel.vger.kernel.org archive mirror
 help / color / mirror / Atom feed
* [PATCH 0/2] USB: ldusb: fix ring-buffer bugs
@ 2019-10-18 14:17 Johan Hovold
  2019-10-18 14:17 ` [PATCH 1/2] USB: ldusb: fix read info leaks Johan Hovold
  2019-10-18 14:17 ` [PATCH RFC 2/2] USB: ldusb: fix ring-buffer locking Johan Hovold
  0 siblings, 2 replies; 4+ messages in thread
From: Johan Hovold @ 2019-10-18 14:17 UTC (permalink / raw)
  To: Greg Kroah-Hartman
  Cc: Alan Stern, Oliver Neukum, Paul E . McKenney, linux-usb,
	linux-kernel, Johan Hovold

Syzbot has been reporting a slab-out-of-bound/bad user copy in ldusb for
some time now.

This turned out to due to a bug in the read() implementation, which
would have read() access the uninitialised ring buffer and leak huge
amounts of slab data on URB completion errors (e.g. disconnect).

The first patch plugs the info leaks.

The second patch fixes a couple of issues in the custom ring-buffer
implementation, which before the first patch also could have led to
info leaks.

In an attempt to avoid copying the ring-buffer entry to a temporary
buffer while holding the spinlock, I added an smp_rmb() before
copy_to_user() which I think will suffice, but I'd appreciate if you
could help me verify that. Hence the RFC on that one.

The first commit could go to Linus meanwhile.

Johan


Johan Hovold (2):
  USB: ldusb: fix read info leaks
  USB: ldusb: fix ring-buffer locking

 drivers/usb/misc/ldusb.c | 31 ++++++++++++++++++++++---------
 1 file changed, 22 insertions(+), 9 deletions(-)

-- 
2.23.0


^ permalink raw reply	[flat|nested] 4+ messages in thread

* [PATCH 1/2] USB: ldusb: fix read info leaks
  2019-10-18 14:17 [PATCH 0/2] USB: ldusb: fix ring-buffer bugs Johan Hovold
@ 2019-10-18 14:17 ` Johan Hovold
  2019-10-18 15:03   ` Johan Hovold
  2019-10-18 14:17 ` [PATCH RFC 2/2] USB: ldusb: fix ring-buffer locking Johan Hovold
  1 sibling, 1 reply; 4+ messages in thread
From: Johan Hovold @ 2019-10-18 14:17 UTC (permalink / raw)
  To: Greg Kroah-Hartman
  Cc: Alan Stern, Oliver Neukum, Paul E . McKenney, linux-usb,
	linux-kernel, Johan Hovold, stable, syzbot+6fe95b826644f7f12b0b

Fix broken read implementation, which could be used to trigger slab info
leaks.

The driver failed to check if the custom ring buffer was still empty
when waking up after having waited for more data. This would happen on
every interrupt-in completion, even if no data had been added to the
ring buffer (e.g. on disconnect events).

Due to missing sanity checks and uninitialised (kmalloced) ring-buffer
entries, this meant that huge slab info leaks could easily be triggered.

Note that the empty-buffer check after wakeup is enough to fix the info
leak on disconnect, but let's clear the buffer on allocation and add a
sanity check to read() to prevent further leaks.

Fixes: 2824bd250f0b ("[PATCH] USB: add ldusb driver")
Cc: stable <stable@vger.kernel.org>     # 2.6.13
Reported-by: syzbot+6fe95b826644f7f12b0b@syzkaller.appspotmail.com
Signed-off-by: Johan Hovold <johan@kernel.org>
---
 drivers/usb/misc/ldusb.c | 18 +++++++++++-------
 1 file changed, 11 insertions(+), 7 deletions(-)

diff --git a/drivers/usb/misc/ldusb.c b/drivers/usb/misc/ldusb.c
index 147c90c2a4e5..94780e14e95d 100644
--- a/drivers/usb/misc/ldusb.c
+++ b/drivers/usb/misc/ldusb.c
@@ -464,7 +464,7 @@ static ssize_t ld_usb_read(struct file *file, char __user *buffer, size_t count,
 
 	/* wait for data */
 	spin_lock_irq(&dev->rbsl);
-	if (dev->ring_head == dev->ring_tail) {
+	while (dev->ring_head == dev->ring_tail) {
 		dev->interrupt_in_done = 0;
 		spin_unlock_irq(&dev->rbsl);
 		if (file->f_flags & O_NONBLOCK) {
@@ -474,12 +474,17 @@ static ssize_t ld_usb_read(struct file *file, char __user *buffer, size_t count,
 		retval = wait_event_interruptible(dev->read_wait, dev->interrupt_in_done);
 		if (retval < 0)
 			goto unlock_exit;
-	} else {
-		spin_unlock_irq(&dev->rbsl);
+
+		spin_lock_irq(&dev->rbsl);
 	}
+	spin_unlock_irq(&dev->rbsl);
 
 	/* actual_buffer contains actual_length + interrupt_in_buffer */
 	actual_buffer = (size_t *)(dev->ring_buffer + dev->ring_tail * (sizeof(size_t)+dev->interrupt_in_endpoint_size));
+	if (*actual_buffer > sizeof(size_t) + dev->interrupt_in_endpoint_size) {
+		retval = -EIO;
+		goto unlock_exit;
+	}
 	bytes_to_read = min(count, *actual_buffer);
 	if (bytes_to_read < *actual_buffer)
 		dev_warn(&dev->intf->dev, "Read buffer overflow, %zd bytes dropped\n",
@@ -690,10 +695,9 @@ static int ld_usb_probe(struct usb_interface *intf, const struct usb_device_id *
 		dev_warn(&intf->dev, "Interrupt out endpoint not found (using control endpoint instead)\n");
 
 	dev->interrupt_in_endpoint_size = usb_endpoint_maxp(dev->interrupt_in_endpoint);
-	dev->ring_buffer =
-		kmalloc_array(ring_buffer_size,
-			      sizeof(size_t) + dev->interrupt_in_endpoint_size,
-			      GFP_KERNEL);
+	dev->ring_buffer = kcalloc(ring_buffer_size,
+			sizeof(size_t) + dev->interrupt_in_endpoint_size,
+			GFP_KERNEL);
 	if (!dev->ring_buffer)
 		goto error;
 	dev->interrupt_in_buffer = kmalloc(dev->interrupt_in_endpoint_size, GFP_KERNEL);
-- 
2.23.0


^ permalink raw reply related	[flat|nested] 4+ messages in thread

* [PATCH RFC 2/2] USB: ldusb: fix ring-buffer locking
  2019-10-18 14:17 [PATCH 0/2] USB: ldusb: fix ring-buffer bugs Johan Hovold
  2019-10-18 14:17 ` [PATCH 1/2] USB: ldusb: fix read info leaks Johan Hovold
@ 2019-10-18 14:17 ` Johan Hovold
  1 sibling, 0 replies; 4+ messages in thread
From: Johan Hovold @ 2019-10-18 14:17 UTC (permalink / raw)
  To: Greg Kroah-Hartman
  Cc: Alan Stern, Oliver Neukum, Paul E . McKenney, linux-usb,
	linux-kernel, Johan Hovold, stable

The custom ring-buffer implementation was merged without any locking
whatsoever, but a spinlock was later added by commit 9d33efd9a791
("USB: ldusb bugfix").

The lock did not cover the loads from the ring-buffer-entry after
determining the buffer was non-empty, nor the update of the tail index
once the entry had been processed. The former could lead to stale data
being returned, while the latter could lead to memory corruption on
sufficiently weakly ordered architectures.

Fixes: 2824bd250f0b ("[PATCH] USB: add ldusb driver")
Fixes: 9d33efd9a791 ("USB: ldusb bugfix")
Cc: stable <stable@vger.kernel.org>     # 2.6.13
Signed-off-by: Johan Hovold <johan@kernel.org>
---
 drivers/usb/misc/ldusb.c | 15 ++++++++++++---
 1 file changed, 12 insertions(+), 3 deletions(-)

diff --git a/drivers/usb/misc/ldusb.c b/drivers/usb/misc/ldusb.c
index 94780e14e95d..c6cf2fa6cf4c 100644
--- a/drivers/usb/misc/ldusb.c
+++ b/drivers/usb/misc/ldusb.c
@@ -477,11 +477,11 @@ static ssize_t ld_usb_read(struct file *file, char __user *buffer, size_t count,
 
 		spin_lock_irq(&dev->rbsl);
 	}
-	spin_unlock_irq(&dev->rbsl);
 
 	/* actual_buffer contains actual_length + interrupt_in_buffer */
 	actual_buffer = (size_t *)(dev->ring_buffer + dev->ring_tail * (sizeof(size_t)+dev->interrupt_in_endpoint_size));
 	if (*actual_buffer > sizeof(size_t) + dev->interrupt_in_endpoint_size) {
+		spin_unlock_irq(&dev->rbsl);
 		retval = -EIO;
 		goto unlock_exit;
 	}
@@ -489,17 +489,26 @@ static ssize_t ld_usb_read(struct file *file, char __user *buffer, size_t count,
 	if (bytes_to_read < *actual_buffer)
 		dev_warn(&dev->intf->dev, "Read buffer overflow, %zd bytes dropped\n",
 			 *actual_buffer-bytes_to_read);
+	spin_unlock_irq(&dev->rbsl);
+
+	/*
+	 * Pairs with spin_unlock_irqrestore() in
+	 * ld_usb_interrupt_in_callback() and makes sure the ring-buffer entry
+	 * has been updated before copy_to_user().
+	 */
+	smp_rmb();
 
 	/* copy one interrupt_in_buffer from ring_buffer into userspace */
 	if (copy_to_user(buffer, actual_buffer+1, bytes_to_read)) {
 		retval = -EFAULT;
 		goto unlock_exit;
 	}
-	dev->ring_tail = (dev->ring_tail+1) % ring_buffer_size;
-
 	retval = bytes_to_read;
 
 	spin_lock_irq(&dev->rbsl);
+
+	dev->ring_tail = (dev->ring_tail + 1) % ring_buffer_size;
+
 	if (dev->buffer_overflow) {
 		dev->buffer_overflow = 0;
 		spin_unlock_irq(&dev->rbsl);
-- 
2.23.0


^ permalink raw reply related	[flat|nested] 4+ messages in thread

* Re: [PATCH 1/2] USB: ldusb: fix read info leaks
  2019-10-18 14:17 ` [PATCH 1/2] USB: ldusb: fix read info leaks Johan Hovold
@ 2019-10-18 15:03   ` Johan Hovold
  0 siblings, 0 replies; 4+ messages in thread
From: Johan Hovold @ 2019-10-18 15:03 UTC (permalink / raw)
  To: Greg Kroah-Hartman
  Cc: Alan Stern, Oliver Neukum, Paul E . McKenney, linux-usb,
	linux-kernel, Johan Hovold, stable, syzbot+6fe95b826644f7f12b0b

On Fri, Oct 18, 2019 at 04:17:49PM +0200, Johan Hovold wrote:
> Fix broken read implementation, which could be used to trigger slab info
> leaks.
> 
> The driver failed to check if the custom ring buffer was still empty
> when waking up after having waited for more data. This would happen on
> every interrupt-in completion, even if no data had been added to the
> ring buffer (e.g. on disconnect events).
> 
> Due to missing sanity checks and uninitialised (kmalloced) ring-buffer
> entries, this meant that huge slab info leaks could easily be triggered.
> 
> Note that the empty-buffer check after wakeup is enough to fix the info
> leak on disconnect, but let's clear the buffer on allocation and add a
> sanity check to read() to prevent further leaks.
> 
> Fixes: 2824bd250f0b ("[PATCH] USB: add ldusb driver")
> Cc: stable <stable@vger.kernel.org>     # 2.6.13
> Reported-by: syzbot+6fe95b826644f7f12b0b@syzkaller.appspotmail.com
> Signed-off-by: Johan Hovold <johan@kernel.org>
> ---
>  drivers/usb/misc/ldusb.c | 18 +++++++++++-------
>  1 file changed, 11 insertions(+), 7 deletions(-)
> 
> diff --git a/drivers/usb/misc/ldusb.c b/drivers/usb/misc/ldusb.c
> index 147c90c2a4e5..94780e14e95d 100644
> --- a/drivers/usb/misc/ldusb.c
> +++ b/drivers/usb/misc/ldusb.c
> @@ -464,7 +464,7 @@ static ssize_t ld_usb_read(struct file *file, char __user *buffer, size_t count,
>  
>  	/* wait for data */
>  	spin_lock_irq(&dev->rbsl);
> -	if (dev->ring_head == dev->ring_tail) {
> +	while (dev->ring_head == dev->ring_tail) {
>  		dev->interrupt_in_done = 0;
>  		spin_unlock_irq(&dev->rbsl);
>  		if (file->f_flags & O_NONBLOCK) {
> @@ -474,12 +474,17 @@ static ssize_t ld_usb_read(struct file *file, char __user *buffer, size_t count,
>  		retval = wait_event_interruptible(dev->read_wait, dev->interrupt_in_done);
>  		if (retval < 0)
>  			goto unlock_exit;
> -	} else {
> -		spin_unlock_irq(&dev->rbsl);
> +
> +		spin_lock_irq(&dev->rbsl);
>  	}
> +	spin_unlock_irq(&dev->rbsl);
>  
>  	/* actual_buffer contains actual_length + interrupt_in_buffer */
>  	actual_buffer = (size_t *)(dev->ring_buffer + dev->ring_tail * (sizeof(size_t)+dev->interrupt_in_endpoint_size));
> +	if (*actual_buffer > sizeof(size_t) + dev->interrupt_in_endpoint_size) {

Bah, this should have been just

	if (*actual_buffer > dev->interrupt_in_endpoint_size)

will send a v2.
	
> +		retval = -EIO;
> +		goto unlock_exit;
> +	}
>  	bytes_to_read = min(count, *actual_buffer);
>  	if (bytes_to_read < *actual_buffer)
>  		dev_warn(&dev->intf->dev, "Read buffer overflow, %zd bytes dropped\n",

Johan

^ permalink raw reply	[flat|nested] 4+ messages in thread

end of thread, other threads:[~2019-10-18 15:03 UTC | newest]

Thread overview: 4+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2019-10-18 14:17 [PATCH 0/2] USB: ldusb: fix ring-buffer bugs Johan Hovold
2019-10-18 14:17 ` [PATCH 1/2] USB: ldusb: fix read info leaks Johan Hovold
2019-10-18 15:03   ` Johan Hovold
2019-10-18 14:17 ` [PATCH RFC 2/2] USB: ldusb: fix ring-buffer locking Johan Hovold

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).