Re: [PATCH 4/4] USB: move dynamic ids out of usb driver structures

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



On Fri, Jun 14, 2024 at 02:11:52PM +0200, Greg Kroah-Hartman wrote:
> 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.

> diff --git a/drivers/usb/core/driver.c b/drivers/usb/core/driver.c
> index 3f69b32222f3..8bba102de39f 100644
> --- a/drivers/usb/core/driver.c
> +++ b/drivers/usb/core/driver.c
> @@ -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...

> +
> +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;
> +};
> +
> +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.

> +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.

> +	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;
> +
> +	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.)

> -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;
>  	struct 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.

> @@ -160,8 +216,12 @@ static ssize_t remove_id_store(struct device_driver *driver, const char *buf,
>  	if (fields < 2)
>  		return -EINVAL;
>  
> +	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.

> @@ -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




[Index of Archives]     [Linux Media]     [Linux Input]     [Linux Audio Users]     [Yosemite News]     [Linux Kernel]     [Linux SCSI]     [Old Linux USB Devel Archive]

  Powered by Linux