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 <redacted>
---
drivers/usb/usbip/vhci_hcd.c | 47 ++++++++++++++++++++++++++----------
1 file changed, 34 insertions(+), 13 deletions(-)
@@ -945,7 +945,8 @@ static int vhci_urb_dequeue(struct usb_hcd *hcd, struct urb *urb, int status)return0;}-staticvoidvhci_device_unlink_cleanup(structvhci_device*vdev)+staticvoid__vhci_cleanup_unlink_list(structvhci_device*vdev,+structlist_head*unlink_list){structvhci_hcd*vhci_hcd=vdev_to_vhci_hcd(vdev);structusb_hcd*hcd=vhci_hcd_to_hcd(vhci_hcd);
@@ -953,23 +954,25 @@ static void vhci_device_unlink_cleanup(struct vhci_device *vdev)structvhci_unlink*unlink,*tmp;unsignedlongflags;+if(unlink_list!=&vdev->unlink_tx+&&unlink_list!=&vdev->unlink_rx){+pr_err("Invalid list passed to __vhci_cleanup_unlink_list\n");+BUG();+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){structurb*urb;-unlink=list_first_entry(&vdev->unlink_rx,structvhci_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 +1004,24 @@ static void vhci_device_unlink_cleanup(struct vhci_device *vdev)spin_unlock_irqrestore(&vhci->lock,flags);}+staticinlinevoidvhci_cleanup_unlink_tx(structvhci_device*vdev)+{+__vhci_cleanup_unlink_list(vdev,&vdev->unlink_tx);+}++staticinlinevoidvhci_cleanup_unlink_rx(structvhci_device*vdev)+{+__vhci_cleanup_unlink_list(vdev,&vdev->unlink_rx);+}++staticvoidvhci_device_unlink_cleanup(structvhci_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);+}+/**Theimportantthingisthatonlyonecontextbeginscleanup.*Thisiswhyerrorhandlingandcleanupbecomesimple.
On Fri, Aug 06, 2021 at 10:10:14PM +0530, Anirudh Rayabharam wrote:
quoted hunk
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 <redacted>
---
drivers/usb/usbip/vhci_hcd.c | 47 ++++++++++++++++++++++++++----------
1 file changed, 34 insertions(+), 13 deletions(-)
@@ -945,7 +945,8 @@ static int vhci_urb_dequeue(struct usb_hcd *hcd, struct urb *urb, int status)return0;}-staticvoidvhci_device_unlink_cleanup(structvhci_device*vdev)+staticvoid__vhci_cleanup_unlink_list(structvhci_device*vdev,+structlist_head*unlink_list){structvhci_hcd*vhci_hcd=vdev_to_vhci_hcd(vdev);structusb_hcd*hcd=vhci_hcd_to_hcd(vhci_hcd);
@@ -953,23 +954,25 @@ static void vhci_device_unlink_cleanup(struct vhci_device *vdev)structvhci_unlink*unlink,*tmp;unsignedlongflags;+if(unlink_list!=&vdev->unlink_tx+&&unlink_list!=&vdev->unlink_rx){+pr_err("Invalid list passed to __vhci_cleanup_unlink_list\n");+BUG();
Do not allow the system to crash, that is not ok.
+ return;
This call makes no sense as you just rebooted the machine :(
Handle errors properly and recover from them and move on. A single tiny
driver should not take down the whole system.
thanks,
greg k-h
On Fri, Aug 06, 2021 at 06:47:54PM +0200, Greg Kroah-Hartman wrote:
On Fri, Aug 06, 2021 at 10:10:14PM +0530, Anirudh Rayabharam wrote:
quoted
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 <redacted>
---
drivers/usb/usbip/vhci_hcd.c | 47 ++++++++++++++++++++++++++----------
1 file changed, 34 insertions(+), 13 deletions(-)
@@ -945,7 +945,8 @@ static int vhci_urb_dequeue(struct usb_hcd *hcd, struct urb *urb, int status)return0;}-staticvoidvhci_device_unlink_cleanup(structvhci_device*vdev)+staticvoid__vhci_cleanup_unlink_list(structvhci_device*vdev,+structlist_head*unlink_list){structvhci_hcd*vhci_hcd=vdev_to_vhci_hcd(vdev);structusb_hcd*hcd=vhci_hcd_to_hcd(vhci_hcd);
@@ -953,23 +954,25 @@ static void vhci_device_unlink_cleanup(struct vhci_device *vdev)structvhci_unlink*unlink,*tmp;unsignedlongflags;+if(unlink_list!=&vdev->unlink_tx+&&unlink_list!=&vdev->unlink_rx){+pr_err("Invalid list passed to __vhci_cleanup_unlink_list\n");+BUG();
Do not allow the system to crash, that is not ok.
quoted
+ return;
This call makes no sense as you just rebooted the machine :(
Handle errors properly and recover from them and move on. A single tiny
driver should not take down the whole system.
The execution can reach only if there is a developer error and they passed
some random list in `unlink_list`. So, BUG() here crashes the kernel and
draws attention to this fact. Is WARN() a better option here? There is
no way to recover from this and continue with the rest of the function.
Either we WARN()/BUG() or we return silently.
Thanks!
- Anirudh.
On Fri, Aug 06, 2021 at 10:46:17PM +0530, Anirudh Rayabharam wrote:
On Fri, Aug 06, 2021 at 06:47:54PM +0200, Greg Kroah-Hartman wrote:
quoted
On Fri, Aug 06, 2021 at 10:10:14PM +0530, Anirudh Rayabharam wrote:
quoted
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 <redacted>
---
drivers/usb/usbip/vhci_hcd.c | 47 ++++++++++++++++++++++++++----------
1 file changed, 34 insertions(+), 13 deletions(-)
@@ -945,7 +945,8 @@ static int vhci_urb_dequeue(struct usb_hcd *hcd, struct urb *urb, int status)return0;}-staticvoidvhci_device_unlink_cleanup(structvhci_device*vdev)+staticvoid__vhci_cleanup_unlink_list(structvhci_device*vdev,+structlist_head*unlink_list){structvhci_hcd*vhci_hcd=vdev_to_vhci_hcd(vdev);structusb_hcd*hcd=vhci_hcd_to_hcd(vhci_hcd);
@@ -953,23 +954,25 @@ static void vhci_device_unlink_cleanup(struct vhci_device *vdev)structvhci_unlink*unlink,*tmp;unsignedlongflags;+if(unlink_list!=&vdev->unlink_tx+&&unlink_list!=&vdev->unlink_rx){+pr_err("Invalid list passed to __vhci_cleanup_unlink_list\n");+BUG();
Do not allow the system to crash, that is not ok.
quoted
+ return;
This call makes no sense as you just rebooted the machine :(
Handle errors properly and recover from them and move on. A single tiny
driver should not take down the whole system.
The execution can reach only if there is a developer error and they passed
some random list in `unlink_list`.
Why would a developer do that? As that is not the case in the kernel
tree, no need to check for it.
thanks,
greg k-h