forked from Minki/linux
USB: core: Fix races in character device registration and deregistraion
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>
This commit is contained in:
parent
0cf25bc5d0
commit
303911cfc5
@ -193,9 +193,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);
|
||||
@ -203,12 +204,11 @@ int usb_register_dev(struct usb_interface *intf,
|
||||
MKDEV(USB_MAJOR, minor), class_driver,
|
||||
"%s", kbasename(name));
|
||||
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);
|
||||
@ -234,12 +234,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();
|
||||
|
Loading…
Reference in New Issue
Block a user