public inbox for linux-usb@vger.kernel.org
 help / color / mirror / Atom feed
* [PATCH] usb: core: hub: avoid NULL deref in usb_disconnect()
@ 2026-01-08 22:04 Kery Qi
  2026-01-08 22:23 ` Alan Stern
  0 siblings, 1 reply; 2+ messages in thread
From: Kery Qi @ 2026-01-08 22:04 UTC (permalink / raw)
  To: gregkh; +Cc: linux-usb, Kery Qi

usb_disconnect() assumes that usb_hub_to_struct_hub(udev->parent)
always returns a valid struct usb_hub when udev->parent is set.
However, usb_hub_to_struct_hub() can return NULL if the parent hub
is already unconfigured/disconnected (e.g. actconfig == NULL or
maxchild == 0).

In that case, usb_disconnect() would dereference hub->ports[...]
and hub->child_usage_bits, causing a NULL pointer dereference and
kernel crash during device disconnect.

Guard the hub-specific port handling with a NULL check and only
touch port_dev links and the child_usage_bits/pm runtime reference
when a valid hub is present.
Fixes: 7027df36e418 ("usb: resume child device when port is powered on")

Signed-off-by: Kery Qi <qikeyu2017@gmail.com>
---
 drivers/usb/core/hub.c | 27 +++++++++++++++------------
 1 file changed, 15 insertions(+), 12 deletions(-)

diff --git a/drivers/usb/core/hub.c b/drivers/usb/core/hub.c
index be50d03034a9..444e04ea433e 100644
--- a/drivers/usb/core/hub.c
+++ b/drivers/usb/core/hub.c
@@ -2348,19 +2348,21 @@ void usb_disconnect(struct usb_device **pdev)
 	if (udev->parent) {
 		port1 = udev->portnum;
 		hub = usb_hub_to_struct_hub(udev->parent);
-		port_dev = hub->ports[port1 - 1];
+		if (hub) {
+			port_dev = hub->ports[port1 - 1];
 
-		sysfs_remove_link(&udev->dev.kobj, "port");
-		sysfs_remove_link(&port_dev->dev.kobj, "device");
+			sysfs_remove_link(&udev->dev.kobj, "port");
+			sysfs_remove_link(&port_dev->dev.kobj, "device");
 
-		/*
-		 * As usb_port_runtime_resume() de-references udev, make
-		 * sure no resumes occur during removal
-		 */
-		if (!test_and_set_bit(port1, hub->child_usage_bits))
-			pm_runtime_get_sync(&port_dev->dev);
+			/*
+			 * As usb_port_runtime_resume() de-references udev, make
+			 * sure no resumes occur during removal
+			 */
+			if (!test_and_set_bit(port1, hub->child_usage_bits))
+				pm_runtime_get_sync(&port_dev->dev);
 
-		typec_deattach(port_dev->connector, &udev->dev);
+			typec_deattach(port_dev->connector, &udev->dev);
+		}
 	}
 
 	usb_remove_ep_devs(&udev->ep0);
@@ -2385,8 +2387,9 @@ void usb_disconnect(struct usb_device **pdev)
 	*pdev = NULL;
 	spin_unlock_irq(&device_state_lock);
 
-	if (port_dev && test_and_clear_bit(port1, hub->child_usage_bits))
-		pm_runtime_put(&port_dev->dev);
+	if (hub)
+		if (port_dev && test_and_clear_bit(port1, hub->child_usage_bits))
+			pm_runtime_put(&port_dev->dev);
 
 	hub_free_dev(udev);
 
-- 
2.34.1


^ permalink raw reply related	[flat|nested] 2+ messages in thread

* Re: [PATCH] usb: core: hub: avoid NULL deref in usb_disconnect()
  2026-01-08 22:04 [PATCH] usb: core: hub: avoid NULL deref in usb_disconnect() Kery Qi
@ 2026-01-08 22:23 ` Alan Stern
  0 siblings, 0 replies; 2+ messages in thread
From: Alan Stern @ 2026-01-08 22:23 UTC (permalink / raw)
  To: Kery Qi; +Cc: gregkh, linux-usb

On Fri, Jan 09, 2026 at 06:04:37AM +0800, Kery Qi wrote:
> usb_disconnect() assumes that usb_hub_to_struct_hub(udev->parent)
> always returns a valid struct usb_hub when udev->parent is set.
> However, usb_hub_to_struct_hub() can return NULL if the parent hub
> is already unconfigured/disconnected (e.g. actconfig == NULL or
> maxchild == 0).
> 
> In that case, usb_disconnect() would dereference hub->ports[...]
> and hub->child_usage_bits, causing a NULL pointer dereference and
> kernel crash during device disconnect.

Have you ever observed this happening?  Are you certain that it is 
really possible?  (Hint: Does hub_disconnect() call hub_quiesce() after 
setting maxchild to 0?)

How did you discover this problem?

Alan Stern

> Guard the hub-specific port handling with a NULL check and only
> touch port_dev links and the child_usage_bits/pm runtime reference
> when a valid hub is present.
> Fixes: 7027df36e418 ("usb: resume child device when port is powered on")
> 
> Signed-off-by: Kery Qi <qikeyu2017@gmail.com>
> ---
>  drivers/usb/core/hub.c | 27 +++++++++++++++------------
>  1 file changed, 15 insertions(+), 12 deletions(-)
> 
> diff --git a/drivers/usb/core/hub.c b/drivers/usb/core/hub.c
> index be50d03034a9..444e04ea433e 100644
> --- a/drivers/usb/core/hub.c
> +++ b/drivers/usb/core/hub.c
> @@ -2348,19 +2348,21 @@ void usb_disconnect(struct usb_device **pdev)
>  	if (udev->parent) {
>  		port1 = udev->portnum;
>  		hub = usb_hub_to_struct_hub(udev->parent);
> -		port_dev = hub->ports[port1 - 1];
> +		if (hub) {
> +			port_dev = hub->ports[port1 - 1];
>  
> -		sysfs_remove_link(&udev->dev.kobj, "port");
> -		sysfs_remove_link(&port_dev->dev.kobj, "device");
> +			sysfs_remove_link(&udev->dev.kobj, "port");
> +			sysfs_remove_link(&port_dev->dev.kobj, "device");
>  
> -		/*
> -		 * As usb_port_runtime_resume() de-references udev, make
> -		 * sure no resumes occur during removal
> -		 */
> -		if (!test_and_set_bit(port1, hub->child_usage_bits))
> -			pm_runtime_get_sync(&port_dev->dev);
> +			/*
> +			 * As usb_port_runtime_resume() de-references udev, make
> +			 * sure no resumes occur during removal
> +			 */
> +			if (!test_and_set_bit(port1, hub->child_usage_bits))
> +				pm_runtime_get_sync(&port_dev->dev);
>  
> -		typec_deattach(port_dev->connector, &udev->dev);
> +			typec_deattach(port_dev->connector, &udev->dev);
> +		}
>  	}
>  
>  	usb_remove_ep_devs(&udev->ep0);
> @@ -2385,8 +2387,9 @@ void usb_disconnect(struct usb_device **pdev)
>  	*pdev = NULL;
>  	spin_unlock_irq(&device_state_lock);
>  
> -	if (port_dev && test_and_clear_bit(port1, hub->child_usage_bits))
> -		pm_runtime_put(&port_dev->dev);
> +	if (hub)
> +		if (port_dev && test_and_clear_bit(port1, hub->child_usage_bits))
> +			pm_runtime_put(&port_dev->dev);
>  
>  	hub_free_dev(udev);
>  
> -- 
> 2.34.1
> 
> 

^ permalink raw reply	[flat|nested] 2+ messages in thread

end of thread, other threads:[~2026-01-08 22:24 UTC | newest]

Thread overview: 2+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-01-08 22:04 [PATCH] usb: core: hub: avoid NULL deref in usb_disconnect() Kery Qi
2026-01-08 22:23 ` Alan Stern

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox