USB: make to_usb_driver() use container_of_const()
Subsystems (2)
Greg Kroah-Hartman
maintainerPATCH 1/4 USB: make to_usb_driver() use container_of_const()
Turns out that we have some const pointers being passed to to_usb_driver() but were not catching this. Change the macro to properly propagate the const-ness of the pointer so that we will notice when we try to write to memory that we shouldn't be writing to.
This requires fixing up the usb_match_dynamic_id() function as well, because it can handle a const * to struct usb_driver.
--- drivers/usb/core/driver.c | 4 ++-- include/linux/usb.h | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-)
drivers/usb/core/driver.c +2 -2
@@ -229,7 +229,7 @@ static void usb_free_dynids(struct usb_driver *usb_drv) }static const struct usb_device_id *usb_match_dynamic_id (struct usb_interface *intf, -struct usb_driver *drv) +const struct usb_driver *drv) {struct usb_dynid *dynid;
@@ -880,7 +880,7 @@ static int usb_device_match(struct device *dev, const struct device_driver *drv) }else if (is_usb_interface (dev)) {struct usb_interface *intf; -struct usb_driver *usb_drv; +const struct usb_driver *usb_drv;const struct usb_device_id *id;/* device drivers never match interfaces */
include/linux/usb.h +1 -1
@@ -1232,7 +1232,7 @@ struct usb_driver { unsigned int disable_hub_initiated_lpm:1; unsigned int soft_unbind:1; }; -#define to_usb_driver (d) container_of(d, struct usb_driver, driver) +#define to_usb_driver (d) container_of_const(d, struct usb_driver, driver)/** * struct usb_device_driver - identifies USB device driver to usbcore
Signature · 1 line
2.45.2
Greg Kroah-Hartman
maintainerPATCH 2/4 USB: make to_usb_device_driver() use container_of_const()
Turns out that we have some const pointers being passed to to_usb_device_driver() but were not catching this. Change the macro to properly propagate the const-ness of the pointer so that we will notice when we try to write to memory that we shouldn't be writing to.
This requires fixing up the usb_driver_applicable() function as well, because it can handle a const * to struct usb_driver.
--- drivers/usb/core/driver.c | 4 ++-- drivers/usb/core/usb.h | 2 +- include/linux/usb.h | 3 +-- 3 files changed, 4 insertions(+), 5 deletions(-)
drivers/usb/core/driver.c +2 -2
@@ -840,7 +840,7 @@ const struct usb_device_id *usb_device_match_id(struct usb_device *udev,EXPORT_SYMBOL_GPL (usb_device_match_id);bool usb_driver_applicable (struct usb_device *udev, -struct usb_device_driver *udrv) +const struct usb_device_driver *udrv) {if (udrv->id_table && udrv->match) return usb_device_match_id(udev, udrv->id_table) != NULL &&
@@ -860,7 +860,7 @@ static int usb_device_match(struct device *dev, const struct device_driver *drv)/* devices and interfaces are handled separately */ if (is_usb_device (dev)) {struct usb_device *udev; -struct usb_device_driver *udrv; +const struct usb_device_driver *udrv;/* interface drivers never match devices */ if (!is_usb_device_driver (drv))
drivers/usb/core/usb.h +1 -1
@@ -75,7 +75,7 @@ extern int usb_match_device(struct usb_device *dev,extern const struct usb_device_id *usb_device_match_id (struct usb_device *udev,const struct usb_device_id *id);extern bool usb_driver_applicable (struct usb_device *udev, -struct usb_device_driver *udrv); +const struct usb_device_driver *udrv);extern void usb_forced_unbind_intf (struct usb_interface *intf);extern void usb_unbind_and_rebind_marked_interfaces (struct usb_device *udev);
include/linux/usb.h +1 -2
@@ -1283,8 +1283,7 @@ struct usb_device_driver { unsigned int supports_autosuspend:1; unsigned int generic_subclass:1; }; -#define to_usb_device_driver (d) container_of(d, struct usb_device_driver, \ - driver) +#define to_usb_device_driver (d) container_of_const(d, struct usb_device_driver, driver)/** * struct usb_class_driver - identifies a USB driver that wants to use the USB major number
Signature · 1 line
2.45.2
Greg Kroah-Hartman
maintainerPATCH 3/4 USB: make single lock for all usb dynamic id lists
In reply to Greg Kroah-HartmanThere are a number of places where we accidentally pass in a constant structure to later cast it off to a dynamic one, and then attempt to grab a lock on it, which is not a good idea. To help resolve this, move the dynamic id lock out of the dynamic id structure for the driver and into one single lock for all USB dynamic ids. As this lock should never have any real contention (it's only every accessed when a device is added or removed, which is always serialized) there should not be any difference except for some memory savings.
--- drivers/usb/common/common.c | 4 ++++ drivers/usb/core/driver.c | 15 +++++---------- drivers/usb/serial/bus.c | 4 +--- drivers/usb/serial/usb-serial.c | 4 +--- include/linux/usb.h | 2 +- 5 files changed, 12 insertions(+), 17 deletions(-)
drivers/usb/common/common.c +4 -0
@@ -417,9 +417,13 @@ EXPORT_SYMBOL_GPL(usb_of_get_companion_dev);struct dentry *usb_debug_root;EXPORT_SYMBOL_GPL (usb_debug_root); +spinlock_t usb_dynids_lock; +EXPORT_SYMBOL_GPL (usb_dynids_lock); +static int __init usb_common_init (void ) { usb_debug_root =debugfs_create_dir ("usb" ,NULL ); +spin_lock_init (&usb_dynids_lock);ledtrig_usb_init ();return 0 ; }
drivers/usb/core/driver.c +5 -10
@@ -95,9 +95,9 @@ ssize_t usb_store_new_id(struct usb_dynids *dynids, } } -spin_lock (&dynids->lock); +spin_lock (&usb_dynids_lock);list_add_tail (&dynid->node, &dynids->list); -spin_unlock (&dynids->lock); +spin_unlock (&usb_dynids_lock); retval =driver_attach (driver);
@@ -160,7 +160,7 @@ static ssize_t remove_id_store(struct device_driver *driver, const char *buf,if (fields <2 )return -EINVAL ; -spin_lock (&usb_driver->dynids.lock); +guard (spinlock)(&usb_dynids_lock);list_for_each_entry_safe (dynid, n, &usb_driver->dynids.list, node) {struct usb_device_id *id = &dynid->id;
@@ -171,7 +171,6 @@ static ssize_t remove_id_store(struct device_driver *driver, const char *buf,break ; } } -spin_unlock (&usb_driver->dynids.lock);return count; }
@@ -220,12 +219,11 @@ static void usb_free_dynids(struct usb_driver *usb_drv) {struct usb_dynid *dynid, *n; -spin_lock (&usb_drv->dynids.lock); +guard (spinlock)(&usb_dynids_lock);list_for_each_entry_safe (dynid, n, &usb_drv->dynids.list, node) {list_del (&dynid->node);kfree (dynid); } -spin_unlock (&usb_drv->dynids.lock); } static const struct usb_device_id *usb_match_dynamic_id(struct usb_interface *intf,
@@ -233,14 +231,12 @@ static const struct usb_device_id *usb_match_dynamic_id(struct usb_interface *in {struct usb_dynid *dynid; -spin_lock (&drv->dynids.lock); +guard (spinlock)(&usb_dynids_lock);list_for_each_entry (dynid, &drv->dynids.list, node) {if (usb_match_one_id (intf, &dynid->id)) { -spin_unlock (&drv->dynids.lock);return &dynid->id; } } -spin_unlock (&drv->dynids.lock);return NULL ; }
@@ -1062,7 +1058,6 @@ int usb_register_driver(struct usb_driver *new_driver, struct module *owner, new_driver->driver.owner = owner; new_driver->driver.mod_name = mod_name; new_driver->driver.dev_groups = new_driver->dev_groups; -spin_lock_init (&new_driver->dynids.lock);INIT_LIST_HEAD (&new_driver->dynids.list); retval =driver_register (&new_driver->driver);
drivers/usb/serial/bus.c +1 -3
@@ -136,12 +136,11 @@ static void free_dynids(struct usb_serial_driver *drv) {struct usb_dynid *dynid, *n; -spin_lock (&drv->dynids.lock); +guard (spinlock)(&usb_dynids_lock);list_for_each_entry_safe (dynid, n, &drv->dynids.list, node) {list_del (&dynid->node);kfree (dynid); } -spin_unlock (&drv->dynids.lock); }const struct bus_type usb_serial_bus_type = {
@@ -157,7 +156,6 @@ int usb_serial_bus_register(struct usb_serial_driver *driver)int retval; driver->driver.bus = &usb_serial_bus_type; -spin_lock_init (&driver->dynids.lock);INIT_LIST_HEAD (&driver->dynids.list); retval =driver_register (&driver->driver);
drivers/usb/serial/usb-serial.c +1 -3
@@ -706,14 +706,12 @@ static const struct usb_device_id *match_dynamic_id(struct usb_interface *intf, {struct usb_dynid *dynid; -spin_lock (&drv->dynids.lock); +guard (spinlock)(&usb_dynids_lock);list_for_each_entry (dynid, &drv->dynids.list, node) {if (usb_match_one_id (intf, &dynid->id)) { -spin_unlock (&drv->dynids.lock);return &dynid->id; } } -spin_unlock (&drv->dynids.lock);return NULL ; }
include/linux/usb.h +1 -1
@@ -1121,8 +1121,8 @@ static inline int usb_make_path(struct usb_device *dev, char *buf, size_t size)/* ----------------------------------------------------------------------- */ /* Stuff for dynamic usb ids */ +extern spinlock_t usb_dynids_lock;struct usb_dynids { -spinlock_t lock;struct list_head list; };
Signature · 1 line
2.45.2
Greg Kroah-Hartman
maintainerPATCH 4/4 USB: move dynamic ids out of usb driver structures
In reply to Greg Kroah-HartmanThe usb driver structures contain a dynamic id structure that is written to, preventing them from being able to be constant structures. To help resolve this, move the dynamic id structure out of the driver and into a separate local list, indexed off of the driver * so that all USB subsystems can use it (i.e. usb-serial).
Overall it moves some duplicated code out of the usb-serial core as it's already in the usb core, and now the usb dynamic id structures can be private entirely to the usb core itself.
--- drivers/usb/common/common.c | 4 - drivers/usb/core/driver.c | 141 ++++++++++++++++++++++++-------- drivers/usb/core/usb.c | 1 + drivers/usb/core/usb.h | 1 + drivers/usb/serial/bus.c | 24 +----- drivers/usb/serial/usb-serial.c | 16 +--- include/linux/usb.h | 24 ++---- include/linux/usb/serial.h | 1 - 8 files changed, 121 insertions(+), 91 deletions(-)
drivers/usb/common/common.c +0 -4
@@ -417,13 +417,9 @@ EXPORT_SYMBOL_GPL(usb_of_get_companion_dev);struct dentry *usb_debug_root;EXPORT_SYMBOL_GPL (usb_debug_root); -spinlock_t usb_dynids_lock; -EXPORT_SYMBOL_GPL (usb_dynids_lock); -static int __init usb_common_init (void ) { usb_debug_root =debugfs_create_dir ("usb" ,NULL ); -spin_lock_init (&usb_dynids_lock);ledtrig_usb_init ();return 0 ; }
drivers/usb/core/driver.c +107 -34
269 lines not on this page · raw message
drivers/usb/core/usb.c +1 -0
@@ -1078,6 +1078,7 @@ static int __init usb_init(void)usb_init_pool_max ();usb_debugfs_init (); +usb_dynids_init ();usb_acpi_register (); retval =bus_register (&usb_bus_type);
drivers/usb/core/usb.h +1 -0
@@ -89,6 +89,7 @@ extern int usb_major_init(void);extern void usb_major_cleanup (void );extern int usb_device_supports_lpm (struct usb_device *udev);extern int usb_port_disable (struct usb_device *udev); +void usb_dynids_init (void );#ifdef CONFIG_PM
drivers/usb/serial/bus.c +4 -20
@@ -107,22 +107,18 @@ static ssize_t new_id_store(struct device_driver *driver, const char *buf, size_t count) {struct usb_serial_driver *usb_drv =to_usb_serial_driver (driver); -ssize_t retval =usb_store_new_id (&usb_drv->dynids, usb_drv->id_table, - driver, buf, count); +ssize_t retval =usb_store_new_id (driver, usb_drv->id_table, buf, count);if (retval >=0 && usb_drv->usb_driver !=NULL ) - retval =usb_store_new_id (&usb_drv->usb_driver->dynids, + retval =usb_store_new_id (&usb_drv->usb_driver->driver, usb_drv->usb_driver->id_table, - &usb_drv->usb_driver->driver, buf, count);return retval; }static ssize_t new_id_show (struct device_driver *driver,char *buf) { -struct usb_serial_driver *usb_drv =to_usb_serial_driver (driver); - -return usb_show_dynids (&usb_drv->dynids, buf); +return usb_show_dynids (driver, buf); }static DRIVER_ATTR_RW (new_id);
@@ -132,17 +128,6 @@ static struct attribute *usb_serial_drv_attrs[] = { };ATTRIBUTE_GROUPS (usb_serial_drv); -static void free_dynids (struct usb_serial_driver *drv) -{ -struct usb_dynid *dynid, *n; - -guard (spinlock)(&usb_dynids_lock); -list_for_each_entry_safe (dynid, n, &drv->dynids.list, node) { -list_del (&dynid->node); -kfree (dynid); - } -} -const struct bus_type usb_serial_bus_type = { .name ="usb-serial" , .match = usb_serial_device_match,
@@ -156,7 +141,6 @@ int usb_serial_bus_register(struct usb_serial_driver *driver)int retval; driver->driver.bus = &usb_serial_bus_type; -INIT_LIST_HEAD (&driver->dynids.list); retval =driver_register (&driver->driver);
@@ -165,7 +149,7 @@ int usb_serial_bus_register(struct usb_serial_driver *driver)void usb_serial_bus_deregister (struct usb_serial_driver *driver) { -free_dynids (driver); +usb_free_dynids (&driver->driver);driver_unregister (&driver->driver); }
drivers/usb/serial/usb-serial.c +1 -15
@@ -701,20 +701,6 @@ static struct usb_serial *create_serial(struct usb_device *dev,return serial; } -static const struct usb_device_id *match_dynamic_id (struct usb_interface *intf, -struct usb_serial_driver *drv) -{ -struct usb_dynid *dynid; - -guard (spinlock)(&usb_dynids_lock); -list_for_each_entry (dynid, &drv->dynids.list, node) { -if (usb_match_one_id (intf, &dynid->id)) { -return &dynid->id; - } - } -return NULL ; -} -static const struct usb_device_id *get_iface_id (struct usb_serial_driver *drv,struct usb_interface *intf) {
@@ -725,7 +711,7 @@ static const struct usb_device_id *get_iface_id(struct usb_serial_driver *drv,dev_dbg (&intf->dev,"static descriptor matches\n" ); goto exit; } - id =match_dynamic_id (intf, drv); + id =usb_match_dynamic_id (intf, &drv->driver);if (id)dev_dbg (&intf->dev,"dynamic descriptor matches\n" ); exit:
include/linux/usb.h +7 -17
@@ -1121,22 +1121,13 @@ static inline int usb_make_path(struct usb_device *dev, char *buf, size_t size)/* ----------------------------------------------------------------------- */ /* Stuff for dynamic usb ids */ -extern spinlock_t usb_dynids_lock; -struct usb_dynids { -struct list_head list; -}; - -struct usb_dynid { -struct list_head node; -struct usb_device_id id; -}; - -extern ssize_t usb_store_new_id (struct usb_dynids *dynids, -const struct usb_device_id *id_table, -struct device_driver *driver, -const char *buf,size_t count); - -extern ssize_t usb_show_dynids (struct usb_dynids *dynids,char *buf); +ssize_t usb_store_new_id (struct device_driver *driver, +const struct usb_device_id *id_table, +const char *buf,size_t count); +ssize_t usb_show_dynids (const struct device_driver *driver,char *buf); +const struct usb_device_id *usb_match_dynamic_id (struct usb_interface *intf, +const struct device_driver *driver); +void usb_free_dynids (const struct device_driver *driver);/** * struct usb_driver - identifies USB interface driver to usbcore
@@ -1225,7 +1216,6 @@ struct usb_driver {const struct usb_device_id *id_table;const struct attribute_group **dev_groups; -struct usb_dynids dynids;struct device_driver driver; unsigned int no_dynamic_id:1; unsigned int supports_autosuspend:1;
include/linux/usb/serial.h +0 -1
@@ -237,7 +237,6 @@ struct usb_serial_driver {struct list_head driver_list;struct device_driver driver;struct usb_driver *usb_driver; -struct usb_dynids dynids;unsigned char num_ports;
Signature · 1 line
2.45.2
Re: [PATCH 4/4] USB: move dynamic ids out of usb driver structures
Quoted 9 lines · Greg Kroah-Hartman
The usb driver structures contain a dynamic id structure that is written to, preventing them from being able to be constant structures. To help resolve this, move the dynamic id structure out of the driver and into a separate local list, indexed off of the driver * so that all USB subsystems can use it (i.e. usb-serial).
Overall it moves some duplicated code out of the usb-serial core as it's already in the usb core, and now the usb dynamic id structures can be private entirely to the usb core itself.
I'm concerned about the locking of the usb_dynid and usb_dynids structures. There doesn't seem to be anything to prevent these things from being deallocated while another thread is reading them.
drivers/usb/core/driver.c line 38
Quoted 10 lines
drivers/usb/core/driver.c +2
-0@@ -34,17 +34,76 @@#include "usb.h" +static struct list_head usb_dynids; +static spinlock_t usb_dynids_lock;
Is there any particular reason you decided to make this a spinlock instead of a mutex?
Why not use static initialization for these things...
Quoted 7 lines
drivers/usb/core/driver.c +7
-0+ +struct usb_dynids { +struct list_head node; +const struct device_driver *driver; +struct list_head list; +}; +
drivers/usb/core/driver.c line 55
Quoted 10 lines
drivers/usb/core/driver.c +10
-0+struct usb_dynid { +struct list_head node; +struct usb_device_id id; +}; + +void usb_dynids_init (void ) +{ +spin_lock_init (&usb_dynids_lock); +INIT_LIST_HEAD (&usb_dynids); +}
... instead of this dynamic initialization? Not that it's a big deal.
drivers/usb/core/driver.c line 66
Quoted 10 lines
drivers/usb/core/driver.c +10
-0+static struct usb_dynids *usb_find_dynids (const struct device_driver *driver) +{ +struct usb_dynids *u; + +/* Loop through the list to find if this driver has an id list already */ +guard (spinlock)(&usb_dynids_lock); +list_for_each_entry (u, &usb_dynids, node) { +if (u->driver == driver) +return u; + }
So here, for instance. usb_dynids_lock is held while this routine iterates through the usb_dynids list. But when an entry is found, the lock is released. What's to prevent another thread from deallocating right now the structure that u points to?
For instance, do we know that this code will always run under the protection of some mutex associated with the driver? Not as far as I can see.
Quoted 18 lines
drivers/usb/core/driver.c +18
-0+return NULL ; +} + +static int store_id (const struct device_driver *driver,const struct usb_device_id *id) +{ +struct usb_dynids *u; +struct usb_dynid *usb_dynid; + + u =usb_find_dynids (driver); +if (!u) { +/* This driver has not stored any ids yet, so make a new entry for it */ + u =kmalloc (sizeof (*u),GFP_KERNEL ); +if (!u) +return -ENOMEM ; + u->driver = driver; +INIT_LIST_HEAD (&u->list); +guard (spinlock)(&usb_dynids_lock); +list_add_tail (&u->node, &usb_dynids);
drivers/usb/core/driver.c line 94
Quoted 10 lines
drivers/usb/core/driver.c +10
-0+ } + +/* Allocate a new entry and add it to the list of driver ids for this driver */ + usb_dynid =kmalloc (sizeof (*usb_dynid),GFP_KERNEL ); +if (!usb_dynid) +return -ENOMEM ; + +INIT_LIST_HEAD (&usb_dynid->node); +memcpy (&usb_dynid->id, id,sizeof (*id)); +list_add_tail (&usb_dynid->node, &u->list);
Here we see that the spinlock is _not_ held while a new usb_dynid entry is added to the driver's list. (Yes, the existing code already has the same problem; it's not something you added.)
Quoted 4 lines
drivers/usb/core/driver.c +2
-1-ssize_t usb_show_dynids (struct usb_dynids *dynids,char *buf) +ssize_t usb_show_dynids (const struct device_driver *driver,char *buf) { +struct usb_dynids *dynids;
drivers/usb/core/driver.c ssize_t usb_store_new_id(struct usb_dynids *dynids, · line 179
Quoted 10 lines
drivers/usb/core/driver.c +4
-0struct usb_dynid *dynid;size_t count =0 ; + dynids =usb_find_dynids (driver); +if (!dynids) +return 0 ; +list_for_each_entry (dynid, &dynids->list, node)if (dynid->id.bInterfaceClass !=0 ) count += scnprintf(&buf[count], PAGE_SIZE - count, "%04x %04x %02x\n",
And here nothing holds the spinlock while we iterate through the list.
Quoted 4 lines
drivers/usb/core/driver.c +0
-0@@ -160,8 +216,12 @@ static ssize_t remove_id_store(struct device_driver *driver, const char *buf,if (fields <2 )return -EINVAL ;
drivers/usb/core/driver.c static ssize_t remove_id_store(struct device_driver *driver, const char *buf, · line 227
Quoted 10 lines
drivers/usb/core/driver.c +5
-1+ dynids =usb_find_dynids (driver); +if (!dynids) +return count; +guard (spinlock)(&usb_dynids_lock); -list_for_each_entry_safe (dynid, n, &usb_driver->dynids.list, node) { +list_for_each_entry_safe (dynid, n, &dynids->list, node) {struct usb_device_id *id = &dynid->id; if ((id->idVendor == idVendor) &&
Although here the spinlock is held while an entry is removed. But that doesn't do any good if readers don't also acquire the spinlock.
Overall, I think it would be better to hold the spinlock throughout the entire time that the dynamic ids are being accessed: Grab it before starting the outer search and don't release it until the desired entry has been found, added, or removed.
drivers/usb/core/driver.c void usb_deregister(struct usb_driver *driver) · line 1104
Quoted 7 lines
drivers/usb/core/driver.c +1
-1@@ -1100,8 +1173,8 @@ void usb_deregister(struct usb_driver *driver) usbcore_name, driver->name);usb_remove_newid_files (driver); +usb_free_dynids (&driver->driver);driver_unregister (&driver->driver); -usb_free_dynids (driver);
Here's another potential problem. You moved the usb_free_dynids() call from after driver_unregister() to before it. This means that the driver is still visible when usb_free_dynids() runs, so another thread might be iterating through the dynid list while the list is removed. In fact, that other thread might go ahead and add a new usb_dynids structure right after the function call here removes the old one!
Alan Stern
Greg Kroah-Hartman
maintainerRe: [PATCH 4/4] USB: move dynamic ids out of usb driver structures
Quoted 7 lines · Alan Stern
Quoted 6 lines · Greg Kroah-Hartman
The usb driver structures contain a dynamic id structure that is written to, preventing them from being able to be constant structures. To help resolve this, move the dynamic id structure out of the driver and into a separate local list, indexed off of the driver * so that all USB subsystems can use it (i.e. usb-serial).
Quoted 7 lines
Quoted 3 lines
Overall it moves some duplicated code out of the usb-serial core as it's already in the usb core, and now the usb dynamic id structures can be private entirely to the usb core itself.
I'm concerned about the locking of the
usb_dynidandusb_dynidsstructures. There doesn't seem to be anything to prevent these things from being deallocated while another thread is reading them.
You are correct, but I think I am just preserving the broken behaviour that we have today :)
Quoted 6 lines
Quoted 6 lines
drivers/usb/core/driver.c +0
-0@@ -34,17 +34,76 @@
Quoted 7 lines
drivers/usb/core/driver.c line 38
Quoted 4 lines
drivers/usb/core/driver.c +2
-0#include "usb.h" +static struct list_head usb_dynids; +static spinlock_t usb_dynids_lock;Is there any particular reason you decided to make this a spinlock instead of a mutex?
I was just keeping the original lock type.
Quoted 1 line
Why not use static initialization for these things...
Good point, I missed that for some reason, will fix.
Quoted 11 lines
Quoted 11 lines
drivers/usb/core/driver.c +11
-0+ +struct usb_dynids { +struct list_head node; +const struct device_driver *driver; +struct list_head list; +}; + +struct usb_dynid { +struct list_head node; +struct usb_device_id id; +};
Quoted 8 lines
drivers/usb/core/driver.c line 55
Quoted 6 lines
drivers/usb/core/driver.c +6
-0+ +void usb_dynids_init (void ) +{ +spin_lock_init (&usb_dynids_lock); +INIT_LIST_HEAD (&usb_dynids); +}... instead of this dynamic initialization? Not that it's a big deal.
It saves a function call, so yes, you are right, will fix that up.
Quoted 16 lines
drivers/usb/core/driver.c line 66
Quoted 10 lines
drivers/usb/core/driver.c +10
-0+static struct usb_dynids *usb_find_dynids (const struct device_driver *driver) +{ +struct usb_dynids *u; + +/* Loop through the list to find if this driver has an id list already */ +guard (spinlock)(&usb_dynids_lock); +list_for_each_entry (u, &usb_dynids, node) { +if (u->driver == driver) +return u; + }So here, for instance.
usb_dynids_lockis held while this routine iterates through theusb_dynidslist. But when an entry is found, the lock is released. What's to prevent another thread from deallocating right now the structure that u points to?
Quoted 3 lines
For instance, do we know that this code will always run under the protection of some mutex associated with the driver? Not as far as I can see.
Nope, looks wrong.
Quoted 25 lines
Quoted 25 lines
drivers/usb/core/driver.c +24
-0+return NULL ; +} + +static int store_id (const struct device_driver *driver,const struct usb_device_id *id) +{ +struct usb_dynids *u; +struct usb_dynid *usb_dynid; + + u =usb_find_dynids (driver); +if (!u) { +/* This driver has not stored any ids yet, so make a new entry for it */ + u =kmalloc (sizeof (*u),GFP_KERNEL ); +if (!u) +return -ENOMEM ; + u->driver = driver; +INIT_LIST_HEAD (&u->list); +guard (spinlock)(&usb_dynids_lock); +list_add_tail (&u->node, &usb_dynids); + } + +/* Allocate a new entry and add it to the list of driver ids for this driver */ + usb_dynid =kmalloc (sizeof (*usb_dynid),GFP_KERNEL ); +if (!usb_dynid) +return -ENOMEM ;
Quoted 8 lines
drivers/usb/core/driver.c line 94
Quoted 4 lines
drivers/usb/core/driver.c +4
-0+ +INIT_LIST_HEAD (&usb_dynid->node); +memcpy (&usb_dynid->id, id,sizeof (*id)); +list_add_tail (&usb_dynid->node, &u->list);Here we see that the spinlock is _not_ held while a new
usb_dynidentry is added to the driver's list. (Yes, the existing code already has the same problem; it's not something you added.)
That's the main problem here, I kept the existing locking "scheme" which was only half-there.
I'll make this a mutex and add locking everywhere to properly protect the lists.
Quoted 43 lines
Quoted 4 lines
drivers/usb/core/driver.c +2
-1-ssize_t usb_show_dynids (struct usb_dynids *dynids,char *buf) +ssize_t usb_show_dynids (const struct device_driver *driver,char *buf) { +struct usb_dynids *dynids;drivers/usb/core/driver.c ssize_t usb_store_new_id(struct usb_dynids *dynids, · line 179
Quoted 10 lines
drivers/usb/core/driver.c +4
-0struct usb_dynid *dynid;size_t count =0 ; + dynids =usb_find_dynids (driver); +if (!dynids) +return 0 ; +list_for_each_entry (dynid, &dynids->list, node)if (dynid->id.bInterfaceClass !=0 ) count += scnprintf(&buf[count], PAGE_SIZE - count, "%04x %04x %02x\n",And here nothing holds the spinlock while we iterate through the list.
Quoted 4 lines
drivers/usb/core/driver.c +0
-0@@ -160,8 +216,12 @@ static ssize_t remove_id_store(struct device_driver *driver, const char *buf,if (fields <2 )return -EINVAL ;drivers/usb/core/driver.c static ssize_t remove_id_store(struct device_driver *driver, const char *buf, · line 227
Quoted 10 lines
drivers/usb/core/driver.c +5
-1+ dynids =usb_find_dynids (driver); +if (!dynids) +return count; +guard (spinlock)(&usb_dynids_lock); -list_for_each_entry_safe (dynid, n, &usb_driver->dynids.list, node) { +list_for_each_entry_safe (dynid, n, &dynids->list, node) {struct usb_device_id *id = &dynid->id; if ((id->idVendor == idVendor) &&Although here the spinlock is held while an entry is removed. But that doesn't do any good if readers don't also acquire the spinlock.
Overall, I think it would be better to hold the spinlock throughout the entire time that the dynamic ids are being accessed: Grab it before starting the outer search and don't release it until the desired entry has been found, added, or removed.
Quoted 3 lines
drivers/usb/core/driver.c +0
-0@@ -1100,8 +1173,8 @@ void usb_deregister(struct usb_driver *driver) usbcore_name, driver->name);
Quoted 11 lines
drivers/usb/core/driver.c void usb_deregister(struct usb_driver *driver) · line 1104
Quoted 4 lines
drivers/usb/core/driver.c +1
-1usb_remove_newid_files (driver); +usb_free_dynids (&driver->driver);driver_unregister (&driver->driver); -usb_free_dynids (driver);Here's another potential problem. You moved the
usb_free_dynids()call from afterdriver_unregister()to before it. This means that the driver is still visible whenusb_free_dynids()runs, so another thread might be iterating through the dynid list while the list is removed. In fact, that other thread might go ahead and add a newusb_dynidsstructure right after the function call here removes the old one!
For some reason I thought that we needed to free the memory before it could be used, but given the locking issues, this could break, you are correct.
I'll go back and redo the locks here to properly be held everywhere, and just be a mutex to make things simpler, thanks so much for the review!
greg k-h
Greg Kroah-Hartman
maintainerQuoted 26 lines · Greg Kroah-Hartman
Turns out that we have some const pointers being passed to
to_usb_driver()but were not catching this. Change the macro to properly propagate the const-ness of the pointer so that we will notice when we try to write to memory that we shouldn't be writing to.This requires fixing up the
usb_match_dynamic_id()function as well, because it can handle a const * to structusb_driver.Cc Johan Hovold <[email protected]>Cc Alan Stern <[email protected]>Cc Grant Grundler <[email protected]>Cc Yajun Deng <[email protected]>Cc Oliver Neukum <[email protected]>Cc Douglas Anderson <[email protected]>Signed-off-by Greg Kroah-Hartman <[email protected]>--- drivers/usb/core/driver.c | 4 ++-- include/linux/usb.h | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-)drivers/usb/core/driver.c +0
-0
drivers/usb/core/driver.c static void usb_free_dynids(struct usb_driver *usb_drv) · line 232
Quoted 5 lines
drivers/usb/core/driver.c +1
-1@@ -229,7 +229,7 @@ static void usb_free_dynids(struct usb_driver *usb_drv) }static const struct usb_device_id *usb_match_dynamic_id (struct usb_interface *intf, -struct usb_driver *drv) +const struct usb_driver *drv)
Oops, this requires a patch that I have not sent out yet that changes the signature of all match() callbacks for busses, that is still winding it's way through the build systems. So I'll hold off on applying any of this until that is accepted, but at least it's good to get this out for review now to see if anyone objects to this series.
Also, I've tested this locally and all seems to work properly, but finding good "unit tests" for adding new device ids is hard...
thanks,
greg k-h