* [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