lists.openwall.net   lists  /  announce  owl-users  owl-dev  john-users  john-dev  passwdqc-users  yescrypt  popa3d-users  /  oss-security  kernel-hardening  musl  sabotage  tlsify  passwords  /  crypt-dev  xvendor  /  Bugtraq  Full-Disclosure  linux-kernel  linux-netdev  linux-ext4  linux-hardening  linux-cve-announce  PHC 
Open Source and information security mailing list archives
 
Hash Suite: Windows password security audit tool. GUI, reports in PDF.
[<prev] [next>] [thread-next>] [day] [month] [year] [list]
Message-ID: <20250720210847.30998-1-mtormento80@gmail.com>
Date: Sun, 20 Jul 2025 23:08:47 +0200
From: Marco Tormento <mtormento80@...il.com>
To: gregkh@...uxfoundation.org
Cc: linux-usb@...r.kernel.org,
	linux-kernel@...r.kernel.org,
	Marco Tormento <mtormento80@...il.com>
Subject: [PATCH] USB: hub: Move typec deattach before children disconnections in usb_disconnect()

I have a Thinkpad T480s, it has 2 USB typec ports one of which is part of
the thunderbolt connector. On the thunderbolt typec port I plug a BenQ
EX3501R external monitor that has an integrated USB hub with a couple of
HID devices connected to it. On the other typec port I plug an Apple
Macbook USB-C power brick.

Steps to reproduce the issue this patch is supposed to fix:
- plug the power brick into typec port
- plug the monitor into the other typec port (thunderbolt)
- unplug the monitor
- unplug the power brick: get a warning stack trace saying "kernfs: can not
  remove 'typec', no directory" in typec_unregister_partner()
- plug the power brick again
- plug the monitor again
- unplug the monitor
- plug the monitor again: get a warning stack trace saying "sysfs: cannot
  create duplicate filename
  '/devices/platform/USBC000:00/typec/port0/port0-partner/3-1'" in
  typec_partner_link_device() called by typec_partner_attach()

>From that point on if you do the following steps a couple of times you get
a kernel oops with a general protection fault in
typec_unregister_partner(), system becomes unstable and a restart is
required:
- unplug the monitor
- unplug the power brick
- plug the power brick
- plug the monitor
- unplug the monitor
- unplug the power brick

If I plug the power brick to thunderbolt and the monitor to the other typec
port none of the above happens.

I tracked down the issue to the following logic:
- power brick is plugged in
- monitor is plugged in
- when I unplug the monitor in usb_disconnect() hub_disconnect_children()
  calls usb_disconnect() recursively, and this results in
  connector_unbind() invoked on all connectors, which resets
  port_dev->connector to NULL on the ports
- typec_deattach() is called for each device that has a parent, which in
  turn should fire typec_partner_deattach()
- port_dev->connector is NULL though, so typec_partner_deattach() is not
  called and port->usb2_dev is not set to NULL even though the hub device
  is actually gone

Now if I plug the monitor again:
- since the type_partner_deattach() was not invoked before, some links are
  still there and typec_partner_link_device() complains

If I unplug the power brick instead:
- ucsi_handle_connector_change() is invoked and typec_unregister_partner()
  is executed, port->usb2_dev is not NULL so we try to unlink the device
  from the partner with typec_partner_unlink_device(), but 'typec'
  directory doesn't exist anymore so it fails in the sysfs_remove_link()
  call

The reason why at some point kernel oops when I unplug the power brick a
final time is this: in typec_unregister_partner() when
typec_partner_unlink_device() is called port->usb2_dev->kobj ref count is
zero (the device is gone already), so when accessing usb_dev->kobj fields
it's just a matter of time and memory will be overwritten with something
else, which will cause access to protected memory and an oops.

In order to fix the above I tried moving the code that handles typec
deattachment from the parent before all the disconnections, this way
typec_partner_deattach() is invoked for the partner, port->usb2_dev is
cleared and typec_unregister_partner() is happy.

Note
It's still not clear to me why the hub device of the monitor is linked with
the typec port partner of the power brick in /sys/class/typec. The
thunderbolt controller surely has something to do with it, since it doesn't
happen if the monitor is plugged into the standard typec port instead, but
I haven't fully understood why it happens yet: maybe this is the actual
bug?
e.g.
Plug the power brick:
- /sys/class/typec/port0-partner appears
Plug the monitor:
- /sys/class/typec/port1-partner appears
- /sys/class/typec/port0-partner/3-1 appears, which is the monitor hub
If I plug the monitor first and then the power brick
/sys/class/typec/port0-partner/3-1 doesn't show up.

Disclaimer
I just started looking at kernel code for the first time a week ago, so all
of the above might be spectacularly wrong or a better way to fix the issue
might exist. Initially I did a quick fix that was just checking
port->usb2_dev->kobj ref count in typec_partner_unlink_device() and bailing
out if it was zero, but then I decided to dig deeper just for fun.
This patch has solved my problem, I'm running it on laptop since last week
without any issue, but it might have side-effects I'm not aware of.
I really hope I'm not wasting your time, thanks for all the great work you
do on the Linux kernel!

Signed-off-by: Marco Tormento <mtormento80@...il.com>
---
 drivers/usb/core/hub.c | 20 ++++++++++----------
 1 file changed, 10 insertions(+), 10 deletions(-)

diff --git a/drivers/usb/core/hub.c b/drivers/usb/core/hub.c
index 256fe8c86828..dfe4ba192faf 100644
--- a/drivers/usb/core/hub.c
+++ b/drivers/usb/core/hub.c
@@ -2334,16 +2334,6 @@ void usb_disconnect(struct usb_device **pdev)
 
 	usb_lock_device(udev);
 
-	hub_disconnect_children(udev);
-
-	/* deallocate hcd/hardware state ... nuking all pending urbs and
-	 * cleaning up all state associated with the current configuration
-	 * so that the hardware is now fully quiesced.
-	 */
-	dev_dbg(&udev->dev, "unregistering device\n");
-	usb_disable_device(udev, 0);
-	usb_hcd_synchronize_unlinks(udev);
-
 	if (udev->parent) {
 		port1 = udev->portnum;
 		hub = usb_hub_to_struct_hub(udev->parent);
@@ -2362,6 +2352,16 @@ void usb_disconnect(struct usb_device **pdev)
 		typec_deattach(port_dev->connector, &udev->dev);
 	}
 
+	hub_disconnect_children(udev);
+
+	/* deallocate hcd/hardware state ... nuking all pending urbs and
+	 * cleaning up all state associated with the current configuration
+	 * so that the hardware is now fully quiesced.
+	 */
+	dev_dbg(&udev->dev, "unregistering device\n");
+	usb_disable_device(udev, 0);
+	usb_hcd_synchronize_unlinks(udev);
+
 	usb_remove_ep_devs(&udev->ep0);
 	usb_unlock_device(udev);
 
-- 
2.50.1


Powered by blists - more mailing lists

Powered by Openwall GNU/*/Linux Powered by OpenVZ