diff mbox series

[v2] usbip: give back URBs for unsent unlink requests during cleanup

Message ID 20210806181335.2078-1-mail@anirudhrb.com
State Superseded
Headers show
Series [v2] usbip: give back URBs for unsent unlink requests during cleanup | expand

Commit Message

Anirudh Rayabharam Aug. 6, 2021, 6:13 p.m. UTC
In vhci_device_unlink_cleanup(), the URBs for unsent unlink requests are
not given back. This sometimes causes usb_kill_urb to wait indefinitely
for that urb to be given back. syzbot has reported a hung task issue [1]
for this.

To fix this, give back the urbs corresponding to unsent unlink requests
(unlink_tx list) similar to how urbs corresponding to unanswered unlink
requests (unlink_rx list) are given back. Since the code is almost the
same, extract it into a new function and call it for both unlink_rx and
unlink_tx lists.

[1]: https://syzkaller.appspot.com/bug?id=08f12df95ae7da69814e64eb5515d5a85ed06b76

Reported-by: syzbot+74d6ef051d3d2eacf428@syzkaller.appspotmail.com
Tested-by: syzbot+74d6ef051d3d2eacf428@syzkaller.appspotmail.com
Signed-off-by: Anirudh Rayabharam <mail@anirudhrb.com>
---

Changes in v2:
Use WARN_ON() instead of BUG() when unlink_list is neither unlink_tx nor
unlink_rx.

v1: https://lore.kernel.org/lkml/20210806164015.25263-1-mail@anirudhrb.com/

---
 drivers/usb/usbip/vhci_hcd.c | 45 +++++++++++++++++++++++++-----------
 1 file changed, 32 insertions(+), 13 deletions(-)

Comments

Shuah Khan Aug. 10, 2021, 11:25 p.m. UTC | #1
On 8/6/21 12:13 PM, Anirudh Rayabharam wrote:
> In vhci_device_unlink_cleanup(), the URBs for unsent unlink requests are

> not given back. This sometimes causes usb_kill_urb to wait indefinitely

> for that urb to be given back. syzbot has reported a hung task issue [1]

> for this.

> 

> To fix this, give back the urbs corresponding to unsent unlink requests

> (unlink_tx list) similar to how urbs corresponding to unanswered unlink

> requests (unlink_rx list) are given back. Since the code is almost the

> same, extract it into a new function and call it for both unlink_rx and

> unlink_tx lists.

> 


Let's not do the refactor - let's first fix the problem and then the refactor.

> [1]: https://syzkaller.appspot.com/bug?id=08f12df95ae7da69814e64eb5515d5a85ed06b76

> 

> Reported-by: syzbot+74d6ef051d3d2eacf428@syzkaller.appspotmail.com

> Tested-by: syzbot+74d6ef051d3d2eacf428@syzkaller.appspotmail.com

> Signed-off-by: Anirudh Rayabharam <mail@anirudhrb.com>

> ---

> 

> Changes in v2:

> Use WARN_ON() instead of BUG() when unlink_list is neither unlink_tx nor

> unlink_rx.

> 

> v1: https://lore.kernel.org/lkml/20210806164015.25263-1-mail@anirudhrb.com/

> 

> ---

>   drivers/usb/usbip/vhci_hcd.c | 45 +++++++++++++++++++++++++-----------

>   1 file changed, 32 insertions(+), 13 deletions(-)

> 

> diff --git a/drivers/usb/usbip/vhci_hcd.c b/drivers/usb/usbip/vhci_hcd.c

> index 4ba6bcdaa8e9..67e638f4c455 100644

> --- a/drivers/usb/usbip/vhci_hcd.c

> +++ b/drivers/usb/usbip/vhci_hcd.c

> @@ -945,7 +945,8 @@ static int vhci_urb_dequeue(struct usb_hcd *hcd, struct urb *urb, int status)

>   	return 0;

>   }

>   

> -static void vhci_device_unlink_cleanup(struct vhci_device *vdev)

> +static void __vhci_cleanup_unlink_list(struct vhci_device *vdev,

> +		struct list_head *unlink_list)

>   {

>   	struct vhci_hcd *vhci_hcd = vdev_to_vhci_hcd(vdev);

>   	struct usb_hcd *hcd = vhci_hcd_to_hcd(vhci_hcd);

> @@ -953,23 +954,23 @@ static void vhci_device_unlink_cleanup(struct vhci_device *vdev)

>   	struct vhci_unlink *unlink, *tmp;

>   	unsigned long flags;

>   

> +	if (WARN(unlink_list != &vdev->unlink_tx

> +				&& unlink_list != &vdev->unlink_rx,

> +			"Invalid list passed to __vhci_cleanup_unlink_list\n"))

> +		return;

> +


With this change, this will be only place unlink_rx is used without
vdev->priv_lock hold? Please explain why this is safe.

>   	spin_lock_irqsave(&vhci->lock, flags);

>   	spin_lock(&vdev->priv_lock);

>   

> -	list_for_each_entry_safe(unlink, tmp, &vdev->unlink_tx, list) {

> -		pr_info("unlink cleanup tx %lu\n", unlink->unlink_seqnum);

> -		list_del(&unlink->list);

> -		kfree(unlink);

> -	}

> -

> -	while (!list_empty(&vdev->unlink_rx)) {

> +	list_for_each_entry_safe(unlink, tmp, unlink_list, list) {

>   		struct urb *urb;

>   

> -		unlink = list_first_entry(&vdev->unlink_rx, struct vhci_unlink,

> -			list);

> -

> -		/* give back URB of unanswered unlink request */

> -		pr_info("unlink cleanup rx %lu\n", unlink->unlink_seqnum);

> +		if (unlink_list == &vdev->unlink_tx)

> +			pr_info("unlink cleanup tx %lu\n",

> +					unlink->unlink_seqnum);

> +		else

> +			pr_info("unlink cleanup rx %lu\n",

> +					unlink->unlink_seqnum);

>   

>   		urb = pickup_urb_and_free_priv(vdev, unlink->unlink_seqnum);

>   		if (!urb) {

> @@ -1001,6 +1002,24 @@ static void vhci_device_unlink_cleanup(struct vhci_device *vdev)

>   	spin_unlock_irqrestore(&vhci->lock, flags);

>   }

>   

> +static inline void vhci_cleanup_unlink_tx(struct vhci_device *vdev)

> +{

> +	__vhci_cleanup_unlink_list(vdev, &vdev->unlink_tx);


With this change, this will be only place unlink_rx is used without
vdev->priv_lock hold? Please explain why this is safe.

> +}

> +


Is there a need for this layer?

> +static inline void vhci_cleanup_unlink_rx(struct vhci_device *vdev)

> +{

> +	__vhci_cleanup_unlink_list(vdev, &vdev->unlink_rx);


With this change, this will be only place unlink_rx is used without
vdev->priv_lock hold? Please explain why this is safe.

> +}

> +

Is there a need for this layer?

> +static void vhci_device_unlink_cleanup(struct vhci_device *vdev)

> +{

> +	/* give back URBs of unsent unlink requests */

> +	vhci_cleanup_unlink_tx(vdev);

> +	/* give back URBs of unanswered unlink requests */

> +	vhci_cleanup_unlink_rx(vdev);

> +}

> +

>   /*

>    * The important thing is that only one context begins cleanup.

>    * This is why error handling and cleanup become simple.

> 


thanks,
-- Shuah
Anirudh Rayabharam Aug. 11, 2021, 1:58 p.m. UTC | #2
On Tue, Aug 10, 2021 at 05:25:51PM -0600, Shuah Khan wrote:
> On 8/6/21 12:13 PM, Anirudh Rayabharam wrote:

> > In vhci_device_unlink_cleanup(), the URBs for unsent unlink requests are

> > not given back. This sometimes causes usb_kill_urb to wait indefinitely

> > for that urb to be given back. syzbot has reported a hung task issue [1]

> > for this.

> > 

> > To fix this, give back the urbs corresponding to unsent unlink requests

> > (unlink_tx list) similar to how urbs corresponding to unanswered unlink

> > requests (unlink_rx list) are given back. Since the code is almost the

> > same, extract it into a new function and call it for both unlink_rx and

> > unlink_tx lists.

> > 

> 

> Let's not do the refactor - let's first fix the problem and then the refactor.


Sure, I will make it a two patch series where the first one fixes the
problem and the second one does the refactor.

> 

> > [1]: https://syzkaller.appspot.com/bug?id=08f12df95ae7da69814e64eb5515d5a85ed06b76

> > 

> > Reported-by: syzbot+74d6ef051d3d2eacf428@syzkaller.appspotmail.com

> > Tested-by: syzbot+74d6ef051d3d2eacf428@syzkaller.appspotmail.com

> > Signed-off-by: Anirudh Rayabharam <mail@anirudhrb.com>

> > ---

> > 

> > Changes in v2:

> > Use WARN_ON() instead of BUG() when unlink_list is neither unlink_tx nor

> > unlink_rx.

> > 

> > v1: https://lore.kernel.org/lkml/20210806164015.25263-1-mail@anirudhrb.com/

> > 

> > ---

> >   drivers/usb/usbip/vhci_hcd.c | 45 +++++++++++++++++++++++++-----------

> >   1 file changed, 32 insertions(+), 13 deletions(-)

> > 

> > diff --git a/drivers/usb/usbip/vhci_hcd.c b/drivers/usb/usbip/vhci_hcd.c

> > index 4ba6bcdaa8e9..67e638f4c455 100644

> > --- a/drivers/usb/usbip/vhci_hcd.c

> > +++ b/drivers/usb/usbip/vhci_hcd.c

> > @@ -945,7 +945,8 @@ static int vhci_urb_dequeue(struct usb_hcd *hcd, struct urb *urb, int status)

> >   	return 0;

> >   }

> > -static void vhci_device_unlink_cleanup(struct vhci_device *vdev)

> > +static void __vhci_cleanup_unlink_list(struct vhci_device *vdev,

> > +		struct list_head *unlink_list)

> >   {

> >   	struct vhci_hcd *vhci_hcd = vdev_to_vhci_hcd(vdev);

> >   	struct usb_hcd *hcd = vhci_hcd_to_hcd(vhci_hcd);

> > @@ -953,23 +954,23 @@ static void vhci_device_unlink_cleanup(struct vhci_device *vdev)

> >   	struct vhci_unlink *unlink, *tmp;

> >   	unsigned long flags;

> > +	if (WARN(unlink_list != &vdev->unlink_tx

> > +				&& unlink_list != &vdev->unlink_rx,

> > +			"Invalid list passed to __vhci_cleanup_unlink_list\n"))

> > +		return;

> > +

> 

> With this change, this will be only place unlink_rx is used without

> vdev->priv_lock hold? Please explain why this is safe.


Well, this doesn't read or modify the contents of unlink_rx and unlink_tx.
So, it looks safe to me. Let me know if I'm missing something here.

> 

> >   	spin_lock_irqsave(&vhci->lock, flags);

> >   	spin_lock(&vdev->priv_lock);

> > -	list_for_each_entry_safe(unlink, tmp, &vdev->unlink_tx, list) {

> > -		pr_info("unlink cleanup tx %lu\n", unlink->unlink_seqnum);

> > -		list_del(&unlink->list);

> > -		kfree(unlink);

> > -	}

> > -

> > -	while (!list_empty(&vdev->unlink_rx)) {

> > +	list_for_each_entry_safe(unlink, tmp, unlink_list, list) {

> >   		struct urb *urb;

> > -		unlink = list_first_entry(&vdev->unlink_rx, struct vhci_unlink,

> > -			list);

> > -

> > -		/* give back URB of unanswered unlink request */

> > -		pr_info("unlink cleanup rx %lu\n", unlink->unlink_seqnum);

> > +		if (unlink_list == &vdev->unlink_tx)

> > +			pr_info("unlink cleanup tx %lu\n",

> > +					unlink->unlink_seqnum);

> > +		else

> > +			pr_info("unlink cleanup rx %lu\n",

> > +					unlink->unlink_seqnum);

> >   		urb = pickup_urb_and_free_priv(vdev, unlink->unlink_seqnum);

> >   		if (!urb) {

> > @@ -1001,6 +1002,24 @@ static void vhci_device_unlink_cleanup(struct vhci_device *vdev)

> >   	spin_unlock_irqrestore(&vhci->lock, flags);

> >   }

> > +static inline void vhci_cleanup_unlink_tx(struct vhci_device *vdev)

> > +{

> > +	__vhci_cleanup_unlink_list(vdev, &vdev->unlink_tx);

> 

> With this change, this will be only place unlink_rx is used without

> vdev->priv_lock hold? Please explain why this is safe.

> 

> > +}

> > +

> 

> Is there a need for this layer?

> 

> > +static inline void vhci_cleanup_unlink_rx(struct vhci_device *vdev)

> > +{

> > +	__vhci_cleanup_unlink_list(vdev, &vdev->unlink_rx);

> 

> With this change, this will be only place unlink_rx is used without

> vdev->priv_lock hold? Please explain why this is safe.

> 

> > +}

> > +

> Is there a need for this layer?


I added these wrappers purely for convenience. There is no other purpose.
Would you prefer this patch without the wrappers?

Thanks for the review!

	- Anirudh.
Shuah Khan Aug. 11, 2021, 9:51 p.m. UTC | #3
On 8/11/21 7:58 AM, Anirudh Rayabharam wrote:
> On Tue, Aug 10, 2021 at 05:25:51PM -0600, Shuah Khan wrote:

>> On 8/6/21 12:13 PM, Anirudh Rayabharam wrote:

>>> In vhci_device_unlink_cleanup(), the URBs for unsent unlink requests are

>>> not given back. This sometimes causes usb_kill_urb to wait indefinitely

>>> for that urb to be given back. syzbot has reported a hung task issue [1]

>>> for this.

>>>

>>> To fix this, give back the urbs corresponding to unsent unlink requests

>>> (unlink_tx list) similar to how urbs corresponding to unanswered unlink

>>> requests (unlink_rx list) are given back. Since the code is almost the

>>> same, extract it into a new function and call it for both unlink_rx and

>>> unlink_tx lists.

>>>

>>

>> Let's not do the refactor - let's first fix the problem and then the refactor.

> 

> Sure, I will make it a two patch series where the first one fixes the

> problem and the second one does the refactor.

> 

>>

>>> [1]: https://syzkaller.appspot.com/bug?id=08f12df95ae7da69814e64eb5515d5a85ed06b76

>>>

>>> Reported-by: syzbot+74d6ef051d3d2eacf428@syzkaller.appspotmail.com

>>> Tested-by: syzbot+74d6ef051d3d2eacf428@syzkaller.appspotmail.com

>>> Signed-off-by: Anirudh Rayabharam <mail@anirudhrb.com>

>>> ---

>>>

>>> Changes in v2:

>>> Use WARN_ON() instead of BUG() when unlink_list is neither unlink_tx nor

>>> unlink_rx.

>>>

>>> v1: https://lore.kernel.org/lkml/20210806164015.25263-1-mail@anirudhrb.com/

>>>

>>> ---

>>>    drivers/usb/usbip/vhci_hcd.c | 45 +++++++++++++++++++++++++-----------

>>>    1 file changed, 32 insertions(+), 13 deletions(-)

>>>

>>> diff --git a/drivers/usb/usbip/vhci_hcd.c b/drivers/usb/usbip/vhci_hcd.c

>>> index 4ba6bcdaa8e9..67e638f4c455 100644

>>> --- a/drivers/usb/usbip/vhci_hcd.c

>>> +++ b/drivers/usb/usbip/vhci_hcd.c

>>> @@ -945,7 +945,8 @@ static int vhci_urb_dequeue(struct usb_hcd *hcd, struct urb *urb, int status)

>>>    	return 0;

>>>    }

>>> -static void vhci_device_unlink_cleanup(struct vhci_device *vdev)

>>> +static void __vhci_cleanup_unlink_list(struct vhci_device *vdev,

>>> +		struct list_head *unlink_list)

>>>    {

>>>    	struct vhci_hcd *vhci_hcd = vdev_to_vhci_hcd(vdev);

>>>    	struct usb_hcd *hcd = vhci_hcd_to_hcd(vhci_hcd);

>>> @@ -953,23 +954,23 @@ static void vhci_device_unlink_cleanup(struct vhci_device *vdev)

>>>    	struct vhci_unlink *unlink, *tmp;

>>>    	unsigned long flags;

>>> +	if (WARN(unlink_list != &vdev->unlink_tx

>>> +				&& unlink_list != &vdev->unlink_rx,

>>> +			"Invalid list passed to __vhci_cleanup_unlink_list\n"))

>>> +		return;

>>> +

>>

>> With this change, this will be only place unlink_rx is used without

>> vdev->priv_lock hold? Please explain why this is safe.

> 

> Well, this doesn't read or modify the contents of unlink_rx and unlink_tx.

> So, it looks safe to me. Let me know if I'm missing something here.

> 

>>

>>>    	spin_lock_irqsave(&vhci->lock, flags);

>>>    	spin_lock(&vdev->priv_lock);

>>> -	list_for_each_entry_safe(unlink, tmp, &vdev->unlink_tx, list) {

>>> -		pr_info("unlink cleanup tx %lu\n", unlink->unlink_seqnum);

>>> -		list_del(&unlink->list);

>>> -		kfree(unlink);

>>> -	}

>>> -

>>> -	while (!list_empty(&vdev->unlink_rx)) {

>>> +	list_for_each_entry_safe(unlink, tmp, unlink_list, list) {

>>>    		struct urb *urb;

>>> -		unlink = list_first_entry(&vdev->unlink_rx, struct vhci_unlink,

>>> -			list);

>>> -

>>> -		/* give back URB of unanswered unlink request */

>>> -		pr_info("unlink cleanup rx %lu\n", unlink->unlink_seqnum);

>>> +		if (unlink_list == &vdev->unlink_tx)

>>> +			pr_info("unlink cleanup tx %lu\n",

>>> +					unlink->unlink_seqnum);

>>> +		else

>>> +			pr_info("unlink cleanup rx %lu\n",

>>> +					unlink->unlink_seqnum);

>>>    		urb = pickup_urb_and_free_priv(vdev, unlink->unlink_seqnum);

>>>    		if (!urb) {

>>> @@ -1001,6 +1002,24 @@ static void vhci_device_unlink_cleanup(struct vhci_device *vdev)

>>>    	spin_unlock_irqrestore(&vhci->lock, flags);

>>>    }

>>> +static inline void vhci_cleanup_unlink_tx(struct vhci_device *vdev)

>>> +{

>>> +	__vhci_cleanup_unlink_list(vdev, &vdev->unlink_tx);

>>

>> With this change, this will be only place unlink_rx is used without

>> vdev->priv_lock hold? Please explain why this is safe.

>>

>>> +}

>>> +

>>

>> Is there a need for this layer?

>>

>>> +static inline void vhci_cleanup_unlink_rx(struct vhci_device *vdev)

>>> +{

>>> +	__vhci_cleanup_unlink_list(vdev, &vdev->unlink_rx);

>>

>> With this change, this will be only place unlink_rx is used without

>> vdev->priv_lock hold? Please explain why this is safe.

>>

>>> +}

>>> +

>> Is there a need for this layer?

> 

> I added these wrappers purely for convenience. There is no other purpose.

> Would you prefer this patch without the wrappers?

> 


Yes. Prefer it without the wrappers. When you take the wrappers
out, I think the unlink_rx could be within spinlock hold easily.

thanks,
-- Shuah
diff mbox series

Patch

diff --git a/drivers/usb/usbip/vhci_hcd.c b/drivers/usb/usbip/vhci_hcd.c
index 4ba6bcdaa8e9..67e638f4c455 100644
--- a/drivers/usb/usbip/vhci_hcd.c
+++ b/drivers/usb/usbip/vhci_hcd.c
@@ -945,7 +945,8 @@  static int vhci_urb_dequeue(struct usb_hcd *hcd, struct urb *urb, int status)
 	return 0;
 }
 
-static void vhci_device_unlink_cleanup(struct vhci_device *vdev)
+static void __vhci_cleanup_unlink_list(struct vhci_device *vdev,
+		struct list_head *unlink_list)
 {
 	struct vhci_hcd *vhci_hcd = vdev_to_vhci_hcd(vdev);
 	struct usb_hcd *hcd = vhci_hcd_to_hcd(vhci_hcd);
@@ -953,23 +954,23 @@  static void vhci_device_unlink_cleanup(struct vhci_device *vdev)
 	struct vhci_unlink *unlink, *tmp;
 	unsigned long flags;
 
+	if (WARN(unlink_list != &vdev->unlink_tx
+				&& unlink_list != &vdev->unlink_rx,
+			"Invalid list passed to __vhci_cleanup_unlink_list\n"))
+		return;
+
 	spin_lock_irqsave(&vhci->lock, flags);
 	spin_lock(&vdev->priv_lock);
 
-	list_for_each_entry_safe(unlink, tmp, &vdev->unlink_tx, list) {
-		pr_info("unlink cleanup tx %lu\n", unlink->unlink_seqnum);
-		list_del(&unlink->list);
-		kfree(unlink);
-	}
-
-	while (!list_empty(&vdev->unlink_rx)) {
+	list_for_each_entry_safe(unlink, tmp, unlink_list, list) {
 		struct urb *urb;
 
-		unlink = list_first_entry(&vdev->unlink_rx, struct vhci_unlink,
-			list);
-
-		/* give back URB of unanswered unlink request */
-		pr_info("unlink cleanup rx %lu\n", unlink->unlink_seqnum);
+		if (unlink_list == &vdev->unlink_tx)
+			pr_info("unlink cleanup tx %lu\n",
+					unlink->unlink_seqnum);
+		else
+			pr_info("unlink cleanup rx %lu\n",
+					unlink->unlink_seqnum);
 
 		urb = pickup_urb_and_free_priv(vdev, unlink->unlink_seqnum);
 		if (!urb) {
@@ -1001,6 +1002,24 @@  static void vhci_device_unlink_cleanup(struct vhci_device *vdev)
 	spin_unlock_irqrestore(&vhci->lock, flags);
 }
 
+static inline void vhci_cleanup_unlink_tx(struct vhci_device *vdev)
+{
+	__vhci_cleanup_unlink_list(vdev, &vdev->unlink_tx);
+}
+
+static inline void vhci_cleanup_unlink_rx(struct vhci_device *vdev)
+{
+	__vhci_cleanup_unlink_list(vdev, &vdev->unlink_rx);
+}
+
+static void vhci_device_unlink_cleanup(struct vhci_device *vdev)
+{
+	/* give back URBs of unsent unlink requests */
+	vhci_cleanup_unlink_tx(vdev);
+	/* give back URBs of unanswered unlink requests */
+	vhci_cleanup_unlink_rx(vdev);
+}
+
 /*
  * The important thing is that only one context begins cleanup.
  * This is why error handling and cleanup become simple.