USB: core: Fix races in character device registration and deregistraion [Linux 4.4.190]

This Linux kernel change "USB: core: Fix races in character device registration and deregistraion" is included in the Linux 4.4.190 release. This change is authored by Alan Stern <stern [at] rowland.harvard.edu> on Mon Aug 12 16:11:07 2019 -0400. The commit for this change in Linux stable tree is 8f67bc7 (patch) which is from upstream commit 303911c. The same Linux upstream change may have been applied to various maintained Linux releases and you can find all Linux releases containing changes from upstream 303911c.

USB: core: Fix races in character device registration and deregistraion

commit 303911cfc5b95d33687d9046133ff184cf5043ff upstream.

The syzbot fuzzer has found two (!) races in the USB character device
registration and deregistration routines.  This patch fixes the races.

The first race results from the fact that usb_deregister_dev() sets
usb_minors[intf->minor] to NULL before calling device_destroy() on the
class device.  This leaves a window during which another thread can
allocate the same minor number but will encounter a duplicate name
error when it tries to register its own class device.  A typical error
message in the system log would look like:

    sysfs: cannot create duplicate filename '/class/usbmisc/ldusb0'

The patch fixes this race by destroying the class device first.

The second race is in usb_register_dev().  When that routine runs, it
first allocates a minor number, then drops minor_rwsem, and then
creates the class device.  If the device creation fails, the minor
number is deallocated and the whole routine returns an error.  But
during the time while minor_rwsem was dropped, there is a window in
which the minor number is allocated and so another thread can
successfully open the device file.  Typically this results in
use-after-free errors or invalid accesses when the other thread closes
its open file reference, because the kernel then tries to release
resources that were already deallocated when usb_register_dev()
failed.  The patch fixes this race by keeping minor_rwsem locked
throughout the entire routine.

Reported-and-tested-by: syzbot+30cf45ebfe0b0c4847a1@syzkaller.appspotmail.com
Signed-off-by: Alan Stern <stern@rowland.harvard.edu>
CC: <stable@vger.kernel.org>
Link: https://lore.kernel.org/r/Pine.LNX.4.44L0.1908121607590.1659-100000@iolanthe.rowland.org
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>

There are 10 lines of Linux source code added/deleted in this change. Code changes to Linux kernel are as follows.

 drivers/usb/core/file.c | 10 +++++-----
 1 file changed, 5 insertions(+), 5 deletions(-)

diff --git a/drivers/usb/core/file.c b/drivers/usb/core/file.c
index b3de806..097977c 100644
--- a/drivers/usb/core/file.c
+++ b/drivers/usb/core/file.c
@@ -191,9 +191,10 @@ int usb_register_dev(struct usb_interface *intf,
        intf->minor = minor;
        break;
    }
-   up_write(&minor_rwsem);
-   if (intf->minor < 0)
+   if (intf->minor < 0) {
+       up_write(&minor_rwsem);
        return -EXFULL;
+   }

    /* create a usb class device for this usb interface */
    snprintf(name, sizeof(name), class_driver->name, minor - minor_base);
@@ -206,12 +207,11 @@ int usb_register_dev(struct usb_interface *intf,
                      MKDEV(USB_MAJOR, minor), class_driver,
                      "%s", temp);
    if (IS_ERR(intf->usb_dev)) {
-       down_write(&minor_rwsem);
        usb_minors[minor] = NULL;
        intf->minor = -1;
-       up_write(&minor_rwsem);
        retval = PTR_ERR(intf->usb_dev);
    }
+   up_write(&minor_rwsem);
    return retval;
 }
 EXPORT_SYMBOL_GPL(usb_register_dev);
@@ -237,12 +237,12 @@ void usb_deregister_dev(struct usb_interface *intf,
        return;

    dev_dbg(&intf->dev, "removing %d minor\n", intf->minor);
+   device_destroy(usb_class->class, MKDEV(USB_MAJOR, intf->minor));

    down_write(&minor_rwsem);
    usb_minors[intf->minor] = NULL;
    up_write(&minor_rwsem);

-   device_destroy(usb_class->class, MKDEV(USB_MAJOR, intf->minor));
    intf->usb_dev = NULL;
    intf->minor = -1;
    destroy_usb_class();

Leave a Reply

Your email address will not be published. Required fields are marked *