This commit resolves two minor bugs in the selection/discovery of more specific USB device drivers for devices that are currently bound to generic USB device drivers.
The first bug is related to the way a candidate USB device driver is compared against the generic USB device driver. The code in is_dev_usb_generic_driver() used to unconditionally use to_usb_device_driver() on each device driver, without verifying that the device driver in question is a USB device driver (as opposed to a USB interface driver).
The second bug is related to the logic that determines whether a device currently bound to a generic USB device driver should be re-probed by a more specific USB device driver or not. The code in __usb_bus_reprobe_drivers() used to have the following lines:
if (usb_device_match_id(udev, new_udriver->id_table) == NULL && (!new_udriver->match || new_udriver->match(udev) != 0)) return 0;
ret = device_reprobe(dev);
As the reader will notice, the code checks whether the USB device in consideration matches the identifier table (id_table) of a specific USB device_driver (new_udriver), followed by a similar check, but this time with the USB device driver's match function. However, the match function's return value is not checked correctly. When match() returns zero, it means that the specific USB device driver is *not* applicable to the USB device in question, but the code then goes on to reprobe the device with the new USB device driver under consideration. All this to say, the logic is inverted.
This commit resolves both of the bugs, which were found by code inspection and instrumentation after Andrey Konovalov's report indicating USB/IP subsystem's misbehaviour with the generic USB device driver matching code.
Reported-by: Andrey Konovalov andreyknvl@google.com Fixes: d5643d2249 ("USB: Fix device driver race") Link: https://lore.kernel.org/linux-usb/CAAeHK+zOrHnxjRFs=OE8T=O9208B9HP_oo8RZpyVO... Cc: stable@vger.kernel.org # 5.8 Cc: Greg Kroah-Hartman gregkh@linuxfoundation.org Cc: Alan Stern stern@rowland.harvard.edu Cc: Bastien Nocera hadess@hadess.net Cc: syzkaller@googlegroups.com Signed-off-by: M. Vefa Bicakci m.v.b@runbox.com --- drivers/usb/core/driver.c | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-)
diff --git a/drivers/usb/core/driver.c b/drivers/usb/core/driver.c index c976ea9f9582..509bb0d5fa4f 100644 --- a/drivers/usb/core/driver.c +++ b/drivers/usb/core/driver.c @@ -907,10 +907,18 @@ static int usb_uevent(struct device *dev, struct kobj_uevent_env *env)
static bool is_dev_usb_generic_driver(struct device *dev) { - struct usb_device_driver *udd = dev->driver ? - to_usb_device_driver(dev->driver) : NULL; + /* A non-existing driver can never be equal to &usb_generic_driver. */ + if (!dev->driver) + return 0; + + /* Check whether the driver is a USB interface driver, which is not + * a USB device driver, and hence cannot be &usb_generic_driver. + * (Plus, to_usb_device_driver is only valid for USB device drivers.) + */ + if (!is_usb_device_driver(dev->driver)) + return 0;
- return udd == &usb_generic_driver; + return to_usb_device_driver(dev->driver) == &usb_generic_driver; }
static int __usb_bus_reprobe_drivers(struct device *dev, void *data) @@ -924,7 +932,7 @@ static int __usb_bus_reprobe_drivers(struct device *dev, void *data)
udev = to_usb_device(dev); if (usb_device_match_id(udev, new_udriver->id_table) == NULL && - (!new_udriver->match || new_udriver->match(udev) != 0)) + (!new_udriver->match || new_udriver->match(udev) == 0)) return 0;
ret = device_reprobe(dev);
base-commit: 871e6496207c6aa94134448779c77631a11bfa2e