[PATCH 3/3] hwmon: (powerz) Fix potential use-after-free on USB disconnect

From: Pradhan, Sanman

Date: Thu Apr 02 2026 - 20:01:37 EST


From: Sanman Pradhan <psanman@xxxxxxxxxxx>

powerz_disconnect() frees the URB but does not clear the interface
data pointer. The hwmon device registered via
devm_hwmon_device_register_with_info() is unregistered later during
devm cleanup, which runs after disconnect returns.

Between usb_free_urb() in disconnect and devm teardown, the hwmon sysfs
files remain accessible. A concurrent read through powerz_read() can
reach powerz_read_data(), which calls usb_fill_bulk_urb() with the
freed URB pointer. The existing NULL check on the return value of
usb_get_intfdata() does not trigger because the interface data was
never cleared.

Address this by:
1. Clearing usb_set_intfdata() in disconnect before taking the mutex,
so concurrent powerz_read() callers observe a NULL pointer and
return early.
2. Setting priv->urb to NULL after freeing it under the mutex, so any
reader that obtained priv before the interface data was cleared
sees the NULL URB pointer and returns -ENODEV.
3. Adding a NULL check on priv->urb in powerz_read_data().
4. Moving usb_set_intfdata() before the hwmon registration in probe()
so that interface data is set before sysfs files become visible.

Fixes: 4381a36abdf1c ("hwmon: add POWER-Z driver")
Cc: stable@xxxxxxxxxxxxxxx
Signed-off-by: Sanman Pradhan <psanman@xxxxxxxxxxx>
---
drivers/hwmon/powerz.c | 11 +++++++++--
1 file changed, 9 insertions(+), 2 deletions(-)

diff --git a/drivers/hwmon/powerz.c b/drivers/hwmon/powerz.c
index 4e663d5b4e330..add20b354f862 100644
--- a/drivers/hwmon/powerz.c
+++ b/drivers/hwmon/powerz.c
@@ -108,6 +108,9 @@ static int powerz_read_data(struct usb_device *udev, struct powerz_priv *priv)
{
int ret;

+ if (!priv->urb)
+ return -ENODEV;
+
priv->status = -ETIMEDOUT;
reinit_completion(&priv->completion);

@@ -224,16 +227,17 @@ static int powerz_probe(struct usb_interface *intf,
mutex_init(&priv->mutex);
init_completion(&priv->completion);

+ usb_set_intfdata(intf, priv);
+
hwmon_dev =
devm_hwmon_device_register_with_info(parent, DRIVER_NAME, priv,
&powerz_chip_info, NULL);
if (IS_ERR(hwmon_dev)) {
usb_free_urb(priv->urb);
+ usb_set_intfdata(intf, NULL);
return PTR_ERR(hwmon_dev);
}

- usb_set_intfdata(intf, priv);
-
return 0;
}

@@ -241,9 +245,12 @@ static void powerz_disconnect(struct usb_interface *intf)
{
struct powerz_priv *priv = usb_get_intfdata(intf);

+ usb_set_intfdata(intf, NULL);
+
mutex_lock(&priv->mutex);
usb_kill_urb(priv->urb);
usb_free_urb(priv->urb);
+ priv->urb = NULL;
mutex_unlock(&priv->mutex);
}

--
2.34.1