Return-Path: X-Spam-Checker-Version: SpamAssassin 3.4.0 (2014-02-07) on aws-us-west-2-korg-lkml-1.web.codeaurora.org Received: from vger.kernel.org (vger.kernel.org [23.128.96.18]) by smtp.lore.kernel.org (Postfix) with ESMTP id E1983C433EF for ; Mon, 20 Dec 2021 15:18:43 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S237055AbhLTPSl (ORCPT ); Mon, 20 Dec 2021 10:18:41 -0500 Received: from lindbergh.monkeyblade.net ([23.128.96.19]:40088 "EHLO lindbergh.monkeyblade.net" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S238501AbhLTPR1 (ORCPT ); Mon, 20 Dec 2021 10:17:27 -0500 Received: from dfw.source.kernel.org (dfw.source.kernel.org [IPv6:2604:1380:4641:c500::1]) by lindbergh.monkeyblade.net (Postfix) with ESMTPS id C5FDEC08EC90; Mon, 20 Dec 2021 06:58:41 -0800 (PST) Received: from smtp.kernel.org (relay.kernel.org [52.25.139.140]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by dfw.source.kernel.org (Postfix) with ESMTPS id 645A0611AA; Mon, 20 Dec 2021 14:58:41 +0000 (UTC) Received: by smtp.kernel.org (Postfix) with ESMTPSA id 480FBC36AE8; Mon, 20 Dec 2021 14:58:40 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=linuxfoundation.org; s=korg; t=1640012320; bh=UqCWcFfa/BQABkZRB+kpl+Wp5gkCxwe/jLHj3rF9DDY=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=vsKY14aAzs4rgkc4j7YnVk73l8O6ukz1WSYjw5H6XHNJxsz4RM/TeG0GoznfXIwve Su3YmHveprJqL4rK9f7fXgu8Id46VbcTWzLtw9Pm7PAx+p1GfjLljsFOyqwRgLBIWO gNPHTEq+eISb/LJSdWFcrGtlVCYnUJ/x1AaiJo0w= From: Greg Kroah-Hartman To: linux-kernel@vger.kernel.org Cc: Greg Kroah-Hartman , stable@vger.kernel.org, Oliver Neukum , Alan Stern , syzbot+ada0f7d3d9fd2016d927@syzkaller.appspotmail.com Subject: [PATCH 5.15 159/177] USB: core: Make do_proc_control() and do_proc_bulk() killable Date: Mon, 20 Dec 2021 15:35:09 +0100 Message-Id: <20211220143045.435716834@linuxfoundation.org> X-Mailer: git-send-email 2.34.1 In-Reply-To: <20211220143040.058287525@linuxfoundation.org> References: <20211220143040.058287525@linuxfoundation.org> User-Agent: quilt/0.66 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Precedence: bulk List-ID: X-Mailing-List: linux-kernel@vger.kernel.org From: Alan Stern commit ae8709b296d80c7f45aa1f35c0e7659ad69edce1 upstream. The USBDEVFS_CONTROL and USBDEVFS_BULK ioctls invoke usb_start_wait_urb(), which contains an uninterruptible wait with a user-specified timeout value. If timeout value is very large and the device being accessed does not respond in a reasonable amount of time, the kernel will complain about "Task X blocked for more than N seconds", as found in testing by syzbot: INFO: task syz-executor.0:8700 blocked for more than 143 seconds. Not tainted 5.14.0-rc7-syzkaller #0 "echo 0 > /proc/sys/kernel/hung_task_timeout_secs" disables this message. task:syz-executor.0 state:D stack:23192 pid: 8700 ppid: 8455 flags:0x00004004 Call Trace: context_switch kernel/sched/core.c:4681 [inline] __schedule+0xc07/0x11f0 kernel/sched/core.c:5938 schedule+0x14b/0x210 kernel/sched/core.c:6017 schedule_timeout+0x98/0x2f0 kernel/time/timer.c:1857 do_wait_for_common+0x2da/0x480 kernel/sched/completion.c:85 __wait_for_common kernel/sched/completion.c:106 [inline] wait_for_common kernel/sched/completion.c:117 [inline] wait_for_completion_timeout+0x46/0x60 kernel/sched/completion.c:157 usb_start_wait_urb+0x167/0x550 drivers/usb/core/message.c:63 do_proc_bulk+0x978/0x1080 drivers/usb/core/devio.c:1236 proc_bulk drivers/usb/core/devio.c:1273 [inline] usbdev_do_ioctl drivers/usb/core/devio.c:2547 [inline] usbdev_ioctl+0x3441/0x6b10 drivers/usb/core/devio.c:2713 ... To fix this problem, this patch replaces usbfs's calls to usb_control_msg() and usb_bulk_msg() with special-purpose code that does essentially the same thing (as recommended in the comment for usb_start_wait_urb()), except that it always uses a killable wait and it uses GFP_KERNEL rather than GFP_NOIO. Reported-and-tested-by: syzbot+ada0f7d3d9fd2016d927@syzkaller.appspotmail.com Suggested-by: Oliver Neukum Signed-off-by: Alan Stern Link: https://lore.kernel.org/r/20210903175312.GA468440@rowland.harvard.edu Signed-off-by: Greg Kroah-Hartman --- drivers/usb/core/devio.c | 144 ++++++++++++++++++++++++++++++++++++----------- 1 file changed, 111 insertions(+), 33 deletions(-) --- a/drivers/usb/core/devio.c +++ b/drivers/usb/core/devio.c @@ -32,6 +32,7 @@ #include #include #include /* for usbcore internals */ +#include #include #include #include @@ -1102,14 +1103,55 @@ static int usbdev_release(struct inode * return 0; } +static void usbfs_blocking_completion(struct urb *urb) +{ + complete((struct completion *) urb->context); +} + +/* + * Much like usb_start_wait_urb, but returns status separately from + * actual_length and uses a killable wait. + */ +static int usbfs_start_wait_urb(struct urb *urb, int timeout, + unsigned int *actlen) +{ + DECLARE_COMPLETION_ONSTACK(ctx); + unsigned long expire; + int rc; + + urb->context = &ctx; + urb->complete = usbfs_blocking_completion; + *actlen = 0; + rc = usb_submit_urb(urb, GFP_KERNEL); + if (unlikely(rc)) + return rc; + + expire = (timeout ? msecs_to_jiffies(timeout) : MAX_SCHEDULE_TIMEOUT); + rc = wait_for_completion_killable_timeout(&ctx, expire); + if (rc <= 0) { + usb_kill_urb(urb); + *actlen = urb->actual_length; + if (urb->status != -ENOENT) + ; /* Completed before it was killed */ + else if (rc < 0) + return -EINTR; + else + return -ETIMEDOUT; + } + *actlen = urb->actual_length; + return urb->status; +} + static int do_proc_control(struct usb_dev_state *ps, struct usbdevfs_ctrltransfer *ctrl) { struct usb_device *dev = ps->dev; unsigned int tmo; unsigned char *tbuf; - unsigned wLength; + unsigned int wLength, actlen; int i, pipe, ret; + struct urb *urb = NULL; + struct usb_ctrlrequest *dr = NULL; ret = check_ctrlrecip(ps, ctrl->bRequestType, ctrl->bRequest, ctrl->wIndex); @@ -1122,51 +1164,63 @@ static int do_proc_control(struct usb_de sizeof(struct usb_ctrlrequest)); if (ret) return ret; + + ret = -ENOMEM; tbuf = (unsigned char *)__get_free_page(GFP_KERNEL); - if (!tbuf) { - ret = -ENOMEM; + if (!tbuf) goto done; - } + urb = usb_alloc_urb(0, GFP_NOIO); + if (!urb) + goto done; + dr = kmalloc(sizeof(struct usb_ctrlrequest), GFP_NOIO); + if (!dr) + goto done; + + dr->bRequestType = ctrl->bRequestType; + dr->bRequest = ctrl->bRequest; + dr->wValue = cpu_to_le16(ctrl->wValue); + dr->wIndex = cpu_to_le16(ctrl->wIndex); + dr->wLength = cpu_to_le16(ctrl->wLength); + tmo = ctrl->timeout; snoop(&dev->dev, "control urb: bRequestType=%02x " "bRequest=%02x wValue=%04x " "wIndex=%04x wLength=%04x\n", ctrl->bRequestType, ctrl->bRequest, ctrl->wValue, ctrl->wIndex, ctrl->wLength); - if ((ctrl->bRequestType & USB_DIR_IN) && ctrl->wLength) { + + if ((ctrl->bRequestType & USB_DIR_IN) && wLength) { pipe = usb_rcvctrlpipe(dev, 0); - snoop_urb(dev, NULL, pipe, ctrl->wLength, tmo, SUBMIT, NULL, 0); + usb_fill_control_urb(urb, dev, pipe, (unsigned char *) dr, tbuf, + wLength, NULL, NULL); + snoop_urb(dev, NULL, pipe, wLength, tmo, SUBMIT, NULL, 0); usb_unlock_device(dev); - i = usb_control_msg(dev, pipe, ctrl->bRequest, - ctrl->bRequestType, ctrl->wValue, ctrl->wIndex, - tbuf, ctrl->wLength, tmo); + i = usbfs_start_wait_urb(urb, tmo, &actlen); usb_lock_device(dev); - snoop_urb(dev, NULL, pipe, max(i, 0), min(i, 0), COMPLETE, - tbuf, max(i, 0)); - if ((i > 0) && ctrl->wLength) { - if (copy_to_user(ctrl->data, tbuf, i)) { + snoop_urb(dev, NULL, pipe, actlen, i, COMPLETE, tbuf, actlen); + if (!i && actlen) { + if (copy_to_user(ctrl->data, tbuf, actlen)) { ret = -EFAULT; - goto done; + goto recv_fault; } } } else { - if (ctrl->wLength) { - if (copy_from_user(tbuf, ctrl->data, ctrl->wLength)) { + if (wLength) { + if (copy_from_user(tbuf, ctrl->data, wLength)) { ret = -EFAULT; goto done; } } pipe = usb_sndctrlpipe(dev, 0); - snoop_urb(dev, NULL, pipe, ctrl->wLength, tmo, SUBMIT, - tbuf, ctrl->wLength); + usb_fill_control_urb(urb, dev, pipe, (unsigned char *) dr, tbuf, + wLength, NULL, NULL); + snoop_urb(dev, NULL, pipe, wLength, tmo, SUBMIT, tbuf, wLength); usb_unlock_device(dev); - i = usb_control_msg(dev, pipe, ctrl->bRequest, - ctrl->bRequestType, ctrl->wValue, ctrl->wIndex, - tbuf, ctrl->wLength, tmo); + i = usbfs_start_wait_urb(urb, tmo, &actlen); usb_lock_device(dev); - snoop_urb(dev, NULL, pipe, max(i, 0), min(i, 0), COMPLETE, NULL, 0); + snoop_urb(dev, NULL, pipe, actlen, i, COMPLETE, NULL, 0); } if (i < 0 && i != -EPIPE) { dev_printk(KERN_DEBUG, &dev->dev, "usbfs: USBDEVFS_CONTROL " @@ -1174,8 +1228,15 @@ static int do_proc_control(struct usb_de current->comm, ctrl->bRequestType, ctrl->bRequest, ctrl->wLength, i); } - ret = i; + ret = (i < 0 ? i : actlen); + + recv_fault: + /* Linger a bit, prior to the next control message. */ + if (dev->quirks & USB_QUIRK_DELAY_CTRL_MSG) + msleep(200); done: + kfree(dr); + usb_free_urb(urb); free_page((unsigned long) tbuf); usbfs_decrease_memory_usage(PAGE_SIZE + sizeof(struct urb) + sizeof(struct usb_ctrlrequest)); @@ -1195,10 +1256,11 @@ static int do_proc_bulk(struct usb_dev_s struct usbdevfs_bulktransfer *bulk) { struct usb_device *dev = ps->dev; - unsigned int tmo, len1, pipe; - int len2; + unsigned int tmo, len1, len2, pipe; unsigned char *tbuf; int i, ret; + struct urb *urb = NULL; + struct usb_host_endpoint *ep; ret = findintfep(ps->dev, bulk->ep); if (ret < 0) @@ -1206,14 +1268,17 @@ static int do_proc_bulk(struct usb_dev_s ret = checkintf(ps, ret); if (ret) return ret; + + len1 = bulk->len; + if (len1 < 0 || len1 >= (INT_MAX - sizeof(struct urb))) + return -EINVAL; + if (bulk->ep & USB_DIR_IN) pipe = usb_rcvbulkpipe(dev, bulk->ep & 0x7f); else pipe = usb_sndbulkpipe(dev, bulk->ep & 0x7f); - if (!usb_maxpacket(dev, pipe, !(bulk->ep & USB_DIR_IN))) - return -EINVAL; - len1 = bulk->len; - if (len1 >= (INT_MAX - sizeof(struct urb))) + ep = usb_pipe_endpoint(dev, pipe); + if (!ep || !usb_endpoint_maxp(&ep->desc)) return -EINVAL; ret = usbfs_increase_memory_usage(len1 + sizeof(struct urb)); if (ret) @@ -1223,17 +1288,29 @@ static int do_proc_bulk(struct usb_dev_s * len1 can be almost arbitrarily large. Don't WARN if it's * too big, just fail the request. */ + ret = -ENOMEM; tbuf = kmalloc(len1, GFP_KERNEL | __GFP_NOWARN); - if (!tbuf) { - ret = -ENOMEM; + if (!tbuf) goto done; + urb = usb_alloc_urb(0, GFP_KERNEL); + if (!urb) + goto done; + + if ((ep->desc.bmAttributes & USB_ENDPOINT_XFERTYPE_MASK) == + USB_ENDPOINT_XFER_INT) { + pipe = (pipe & ~(3 << 30)) | (PIPE_INTERRUPT << 30); + usb_fill_int_urb(urb, dev, pipe, tbuf, len1, + NULL, NULL, ep->desc.bInterval); + } else { + usb_fill_bulk_urb(urb, dev, pipe, tbuf, len1, NULL, NULL); } + tmo = bulk->timeout; if (bulk->ep & 0x80) { snoop_urb(dev, NULL, pipe, len1, tmo, SUBMIT, NULL, 0); usb_unlock_device(dev); - i = usb_bulk_msg(dev, pipe, tbuf, len1, &len2, tmo); + i = usbfs_start_wait_urb(urb, tmo, &len2); usb_lock_device(dev); snoop_urb(dev, NULL, pipe, len2, i, COMPLETE, tbuf, len2); @@ -1253,12 +1330,13 @@ static int do_proc_bulk(struct usb_dev_s snoop_urb(dev, NULL, pipe, len1, tmo, SUBMIT, tbuf, len1); usb_unlock_device(dev); - i = usb_bulk_msg(dev, pipe, tbuf, len1, &len2, tmo); + i = usbfs_start_wait_urb(urb, tmo, &len2); usb_lock_device(dev); snoop_urb(dev, NULL, pipe, len2, i, COMPLETE, NULL, 0); } ret = (i < 0 ? i : len2); done: + usb_free_urb(urb); kfree(tbuf); usbfs_decrease_memory_usage(len1 + sizeof(struct urb)); return ret;