CVE-2026-46073
Description
In the Linux kernel, the following vulnerability has been resolved:
hwmon: (powerz) Fix missing usb_kill_urb() on signal interrupt
wait_for_completion_interruptible_timeout() returns -ERESTARTSYS when interrupted. This needs to abort the URB and return an error. No data has been received from the device so any reads from the transfer buffer are invalid.
The original code tests !ret, which only catches the timeout case (0). On signal delivery (-ERESTARTSYS), !ret is false so the function skips usb_kill_urb() and falls through to read from the unfilled transfer buffer.
Fix by capturing the return value into a long (matching the function return type) and handling signal (negative) and timeout (zero) cases with separate checks that both call usb_kill_urb() before returning.
AI Insight
LLM-synthesized narrative grounded in this CVE's description and references.
A missing usb_kill_urb() call in the powerz hwmon driver when a signal interrupts wait_for_completion_interruptible_timeout() can lead to a read from an unfilled transfer buffer.
Vulnerability
In the Linux kernel's powerz hwmon driver, a bug exists in the handling of wait_for_completion_interruptible_timeout(). This function returns -ERESTARTSYS when a signal interrupts the wait, 0 on timeout, or a positive value on success. The original code only tested !ret, which catches timeout (0) but not signal interruption. On signal delivery, !ret is false, so the code skips usb_kill_urb() and falls through to read from the unfilled transfer buffer. This affects all versions of the kernel containing this driver before the fix commit 8b51277eec43[1].
Exploitation
An attacker would need to be able to send a signal to the process or thread that is performing a USB read via the powerz driver, such that wait_for_completion_interruptible_timeout() returns -ERESTARTSYS. This requires local access to send signals (e.g., via kill or from a user-space process in the same session). The attacker's signal must be delivered at the precise moment the driver is waiting for a USB transfer to complete, interrupting the wait. After the signal, the code does not cancel the URB, but continues reading the transfer buffer which may contain uninitialized or stale data.
Impact
On successful exploitation, the driver reads from a buffer that has not been properly filled by the USB device, potentially leaking stale kernel memory data to user space via the hwmon interface. This can lead to information disclosure of sensitive kernel data. The attacker gains no code execution, but can exfiltrate kernel memory contents, which may include cryptographic keys or other secrets.
Mitigation
The fix is included in Linux kernel stable commit 8b51277eec433d4e724b273a5a5c64e8acfbe405[1]. The fix modifies the error handling by capturing the return value and adding separate checks for signal (negative) and timeout (zero) cases, ensuring usb_kill_urb() is called in both error paths. Users should apply the latest stable kernel updates that include this commit. If patching is not immediately possible, the workaround is to ensure that no signals are sent to processes using the powerz hwmon driver, or to avoid using this driver version.
AI Insight generated on May 27, 2026. Synthesized from this CVE's description and the cited reference URLs; citations are validated against the source bundle.
Affected products
3Patches
8b6cb07f02253hwmon: (powerz) Fix missing usb_kill_urb() on signal interrupt
2 files changed · +18 −6
drivers/hwmon/powerz.c+9 −3 modifieddiff --git a/drivers/hwmon/powerz.c b/drivers/hwmon/powerz.c index a75b941bd6e2fc..96438f5f05d489 100644 --- a/drivers/hwmon/powerz.c +++ b/drivers/hwmon/powerz.c @@ -106,6 +106,7 @@ static void powerz_usb_cmd_complete(struct urb *urb) static int powerz_read_data(struct usb_device *udev, struct powerz_priv *priv) { + long rc; int ret; if (!priv->urb) @@ -127,8 +128,14 @@ static int powerz_read_data(struct usb_device *udev, struct powerz_priv *priv) if (ret) return ret; - if (!wait_for_completion_interruptible_timeout - (&priv->completion, msecs_to_jiffies(5))) { + rc = wait_for_completion_interruptible_timeout(&priv->completion, + msecs_to_jiffies(5)); + if (rc < 0) { + usb_kill_urb(priv->urb); + return rc; + } + + if (rc == 0) { usb_kill_urb(priv->urb); return -EIO; } -- cgit 1.3-korg
drivers/hwmon/powerz.c+9 −3 modifieddiff --git a/drivers/hwmon/powerz.c b/drivers/hwmon/powerz.c index a75b941bd6e2fc..96438f5f05d489 100644 --- a/drivers/hwmon/powerz.c +++ b/drivers/hwmon/powerz.c @@ -106,6 +106,7 @@ static void powerz_usb_cmd_complete(struct urb *urb) static int powerz_read_data(struct usb_device *udev, struct powerz_priv *priv) { + long rc; int ret; if (!priv->urb) @@ -127,8 +128,14 @@ static int powerz_read_data(struct usb_device *udev, struct powerz_priv *priv) if (ret) return ret; - if (!wait_for_completion_interruptible_timeout - (&priv->completion, msecs_to_jiffies(5))) { + rc = wait_for_completion_interruptible_timeout(&priv->completion, + msecs_to_jiffies(5)); + if (rc < 0) { + usb_kill_urb(priv->urb); + return rc; + } + + if (rc == 0) { usb_kill_urb(priv->urb); return -EIO; } -- cgit 1.3-korg
d64458784036hwmon: (powerz) Fix missing usb_kill_urb() on signal interrupt
2 files changed · +18 −6
drivers/hwmon/powerz.c+9 −3 modifieddiff --git a/drivers/hwmon/powerz.c b/drivers/hwmon/powerz.c index a75b941bd6e2fc..96438f5f05d489 100644 --- a/drivers/hwmon/powerz.c +++ b/drivers/hwmon/powerz.c @@ -106,6 +106,7 @@ static void powerz_usb_cmd_complete(struct urb *urb) static int powerz_read_data(struct usb_device *udev, struct powerz_priv *priv) { + long rc; int ret; if (!priv->urb) @@ -127,8 +128,14 @@ static int powerz_read_data(struct usb_device *udev, struct powerz_priv *priv) if (ret) return ret; - if (!wait_for_completion_interruptible_timeout - (&priv->completion, msecs_to_jiffies(5))) { + rc = wait_for_completion_interruptible_timeout(&priv->completion, + msecs_to_jiffies(5)); + if (rc < 0) { + usb_kill_urb(priv->urb); + return rc; + } + + if (rc == 0) { usb_kill_urb(priv->urb); return -EIO; } -- cgit 1.3-korg
drivers/hwmon/powerz.c+9 −3 modifieddiff --git a/drivers/hwmon/powerz.c b/drivers/hwmon/powerz.c index a75b941bd6e2fc..96438f5f05d489 100644 --- a/drivers/hwmon/powerz.c +++ b/drivers/hwmon/powerz.c @@ -106,6 +106,7 @@ static void powerz_usb_cmd_complete(struct urb *urb) static int powerz_read_data(struct usb_device *udev, struct powerz_priv *priv) { + long rc; int ret; if (!priv->urb) @@ -127,8 +128,14 @@ static int powerz_read_data(struct usb_device *udev, struct powerz_priv *priv) if (ret) return ret; - if (!wait_for_completion_interruptible_timeout - (&priv->completion, msecs_to_jiffies(5))) { + rc = wait_for_completion_interruptible_timeout(&priv->completion, + msecs_to_jiffies(5)); + if (rc < 0) { + usb_kill_urb(priv->urb); + return rc; + } + + if (rc == 0) { usb_kill_urb(priv->urb); return -EIO; } -- cgit 1.3-korg
b66437cb20a2hwmon: (powerz) Fix missing usb_kill_urb() on signal interrupt
2 files changed · +18 −6
drivers/hwmon/powerz.c+9 −3 modifieddiff --git a/drivers/hwmon/powerz.c b/drivers/hwmon/powerz.c index a75b941bd6e2fc..96438f5f05d489 100644 --- a/drivers/hwmon/powerz.c +++ b/drivers/hwmon/powerz.c @@ -106,6 +106,7 @@ static void powerz_usb_cmd_complete(struct urb *urb) static int powerz_read_data(struct usb_device *udev, struct powerz_priv *priv) { + long rc; int ret; if (!priv->urb) @@ -127,8 +128,14 @@ static int powerz_read_data(struct usb_device *udev, struct powerz_priv *priv) if (ret) return ret; - if (!wait_for_completion_interruptible_timeout - (&priv->completion, msecs_to_jiffies(5))) { + rc = wait_for_completion_interruptible_timeout(&priv->completion, + msecs_to_jiffies(5)); + if (rc < 0) { + usb_kill_urb(priv->urb); + return rc; + } + + if (rc == 0) { usb_kill_urb(priv->urb); return -EIO; } -- cgit 1.3-korg
drivers/hwmon/powerz.c+9 −3 modifieddiff --git a/drivers/hwmon/powerz.c b/drivers/hwmon/powerz.c index a75b941bd6e2fc..96438f5f05d489 100644 --- a/drivers/hwmon/powerz.c +++ b/drivers/hwmon/powerz.c @@ -106,6 +106,7 @@ static void powerz_usb_cmd_complete(struct urb *urb) static int powerz_read_data(struct usb_device *udev, struct powerz_priv *priv) { + long rc; int ret; if (!priv->urb) @@ -127,8 +128,14 @@ static int powerz_read_data(struct usb_device *udev, struct powerz_priv *priv) if (ret) return ret; - if (!wait_for_completion_interruptible_timeout - (&priv->completion, msecs_to_jiffies(5))) { + rc = wait_for_completion_interruptible_timeout(&priv->completion, + msecs_to_jiffies(5)); + if (rc < 0) { + usb_kill_urb(priv->urb); + return rc; + } + + if (rc == 0) { usb_kill_urb(priv->urb); return -EIO; } -- cgit 1.3-korg
8b51277eec43hwmon: (powerz) Fix missing usb_kill_urb() on signal interrupt
2 files changed · +18 −6
drivers/hwmon/powerz.c+9 −3 modifieddiff --git a/drivers/hwmon/powerz.c b/drivers/hwmon/powerz.c index 9e1dfe59aa561e..da6dd48ac67c5a 100644 --- a/drivers/hwmon/powerz.c +++ b/drivers/hwmon/powerz.c @@ -112,6 +112,7 @@ static void powerz_usb_cmd_complete(struct urb *urb) static int powerz_read_data(struct usb_device *udev, struct powerz_priv *priv) { + long rc; int ret; if (!priv->urb) @@ -133,8 +134,14 @@ static int powerz_read_data(struct usb_device *udev, struct powerz_priv *priv) if (ret) return ret; - if (!wait_for_completion_interruptible_timeout - (&priv->completion, msecs_to_jiffies(5))) { + rc = wait_for_completion_interruptible_timeout(&priv->completion, + msecs_to_jiffies(5)); + if (rc < 0) { + usb_kill_urb(priv->urb); + return rc; + } + + if (rc == 0) { usb_kill_urb(priv->urb); return -EIO; } -- cgit 1.3-korg
drivers/hwmon/powerz.c+9 −3 modifieddiff --git a/drivers/hwmon/powerz.c b/drivers/hwmon/powerz.c index 9e1dfe59aa561e..da6dd48ac67c5a 100644 --- a/drivers/hwmon/powerz.c +++ b/drivers/hwmon/powerz.c @@ -112,6 +112,7 @@ static void powerz_usb_cmd_complete(struct urb *urb) static int powerz_read_data(struct usb_device *udev, struct powerz_priv *priv) { + long rc; int ret; if (!priv->urb) @@ -133,8 +134,14 @@ static int powerz_read_data(struct usb_device *udev, struct powerz_priv *priv) if (ret) return ret; - if (!wait_for_completion_interruptible_timeout - (&priv->completion, msecs_to_jiffies(5))) { + rc = wait_for_completion_interruptible_timeout(&priv->completion, + msecs_to_jiffies(5)); + if (rc < 0) { + usb_kill_urb(priv->urb); + return rc; + } + + if (rc == 0) { usb_kill_urb(priv->urb); return -EIO; } -- cgit 1.3-korg
b66437cb20a2hwmon: (powerz) Fix missing usb_kill_urb() on signal interrupt
2 files changed · +18 −6
drivers/hwmon/powerz.c+9 −3 modifieddiff --git a/drivers/hwmon/powerz.c b/drivers/hwmon/powerz.c index a75b941bd6e2fc..96438f5f05d489 100644 --- a/drivers/hwmon/powerz.c +++ b/drivers/hwmon/powerz.c @@ -106,6 +106,7 @@ static void powerz_usb_cmd_complete(struct urb *urb) static int powerz_read_data(struct usb_device *udev, struct powerz_priv *priv) { + long rc; int ret; if (!priv->urb) @@ -127,8 +128,14 @@ static int powerz_read_data(struct usb_device *udev, struct powerz_priv *priv) if (ret) return ret; - if (!wait_for_completion_interruptible_timeout - (&priv->completion, msecs_to_jiffies(5))) { + rc = wait_for_completion_interruptible_timeout(&priv->completion, + msecs_to_jiffies(5)); + if (rc < 0) { + usb_kill_urb(priv->urb); + return rc; + } + + if (rc == 0) { usb_kill_urb(priv->urb); return -EIO; } -- cgit 1.3-korg
drivers/hwmon/powerz.c+9 −3 modifieddiff --git a/drivers/hwmon/powerz.c b/drivers/hwmon/powerz.c index a75b941bd6e2fc..96438f5f05d489 100644 --- a/drivers/hwmon/powerz.c +++ b/drivers/hwmon/powerz.c @@ -106,6 +106,7 @@ static void powerz_usb_cmd_complete(struct urb *urb) static int powerz_read_data(struct usb_device *udev, struct powerz_priv *priv) { + long rc; int ret; if (!priv->urb) @@ -127,8 +128,14 @@ static int powerz_read_data(struct usb_device *udev, struct powerz_priv *priv) if (ret) return ret; - if (!wait_for_completion_interruptible_timeout - (&priv->completion, msecs_to_jiffies(5))) { + rc = wait_for_completion_interruptible_timeout(&priv->completion, + msecs_to_jiffies(5)); + if (rc < 0) { + usb_kill_urb(priv->urb); + return rc; + } + + if (rc == 0) { usb_kill_urb(priv->urb); return -EIO; } -- cgit 1.3-korg
b6cb07f02253hwmon: (powerz) Fix missing usb_kill_urb() on signal interrupt
2 files changed · +18 −6
drivers/hwmon/powerz.c+9 −3 modifieddiff --git a/drivers/hwmon/powerz.c b/drivers/hwmon/powerz.c index a75b941bd6e2fc..96438f5f05d489 100644 --- a/drivers/hwmon/powerz.c +++ b/drivers/hwmon/powerz.c @@ -106,6 +106,7 @@ static void powerz_usb_cmd_complete(struct urb *urb) static int powerz_read_data(struct usb_device *udev, struct powerz_priv *priv) { + long rc; int ret; if (!priv->urb) @@ -127,8 +128,14 @@ static int powerz_read_data(struct usb_device *udev, struct powerz_priv *priv) if (ret) return ret; - if (!wait_for_completion_interruptible_timeout - (&priv->completion, msecs_to_jiffies(5))) { + rc = wait_for_completion_interruptible_timeout(&priv->completion, + msecs_to_jiffies(5)); + if (rc < 0) { + usb_kill_urb(priv->urb); + return rc; + } + + if (rc == 0) { usb_kill_urb(priv->urb); return -EIO; } -- cgit 1.3-korg
drivers/hwmon/powerz.c+9 −3 modifieddiff --git a/drivers/hwmon/powerz.c b/drivers/hwmon/powerz.c index a75b941bd6e2fc..96438f5f05d489 100644 --- a/drivers/hwmon/powerz.c +++ b/drivers/hwmon/powerz.c @@ -106,6 +106,7 @@ static void powerz_usb_cmd_complete(struct urb *urb) static int powerz_read_data(struct usb_device *udev, struct powerz_priv *priv) { + long rc; int ret; if (!priv->urb) @@ -127,8 +128,14 @@ static int powerz_read_data(struct usb_device *udev, struct powerz_priv *priv) if (ret) return ret; - if (!wait_for_completion_interruptible_timeout - (&priv->completion, msecs_to_jiffies(5))) { + rc = wait_for_completion_interruptible_timeout(&priv->completion, + msecs_to_jiffies(5)); + if (rc < 0) { + usb_kill_urb(priv->urb); + return rc; + } + + if (rc == 0) { usb_kill_urb(priv->urb); return -EIO; } -- cgit 1.3-korg
8b51277eec43hwmon: (powerz) Fix missing usb_kill_urb() on signal interrupt
2 files changed · +18 −6
drivers/hwmon/powerz.c+9 −3 modifieddiff --git a/drivers/hwmon/powerz.c b/drivers/hwmon/powerz.c index 9e1dfe59aa561e..da6dd48ac67c5a 100644 --- a/drivers/hwmon/powerz.c +++ b/drivers/hwmon/powerz.c @@ -112,6 +112,7 @@ static void powerz_usb_cmd_complete(struct urb *urb) static int powerz_read_data(struct usb_device *udev, struct powerz_priv *priv) { + long rc; int ret; if (!priv->urb) @@ -133,8 +134,14 @@ static int powerz_read_data(struct usb_device *udev, struct powerz_priv *priv) if (ret) return ret; - if (!wait_for_completion_interruptible_timeout - (&priv->completion, msecs_to_jiffies(5))) { + rc = wait_for_completion_interruptible_timeout(&priv->completion, + msecs_to_jiffies(5)); + if (rc < 0) { + usb_kill_urb(priv->urb); + return rc; + } + + if (rc == 0) { usb_kill_urb(priv->urb); return -EIO; } -- cgit 1.3-korg
drivers/hwmon/powerz.c+9 −3 modifieddiff --git a/drivers/hwmon/powerz.c b/drivers/hwmon/powerz.c index 9e1dfe59aa561e..da6dd48ac67c5a 100644 --- a/drivers/hwmon/powerz.c +++ b/drivers/hwmon/powerz.c @@ -112,6 +112,7 @@ static void powerz_usb_cmd_complete(struct urb *urb) static int powerz_read_data(struct usb_device *udev, struct powerz_priv *priv) { + long rc; int ret; if (!priv->urb) @@ -133,8 +134,14 @@ static int powerz_read_data(struct usb_device *udev, struct powerz_priv *priv) if (ret) return ret; - if (!wait_for_completion_interruptible_timeout - (&priv->completion, msecs_to_jiffies(5))) { + rc = wait_for_completion_interruptible_timeout(&priv->completion, + msecs_to_jiffies(5)); + if (rc < 0) { + usb_kill_urb(priv->urb); + return rc; + } + + if (rc == 0) { usb_kill_urb(priv->urb); return -EIO; } -- cgit 1.3-korg
d64458784036hwmon: (powerz) Fix missing usb_kill_urb() on signal interrupt
2 files changed · +18 −6
drivers/hwmon/powerz.c+9 −3 modifieddiff --git a/drivers/hwmon/powerz.c b/drivers/hwmon/powerz.c index a75b941bd6e2fc..96438f5f05d489 100644 --- a/drivers/hwmon/powerz.c +++ b/drivers/hwmon/powerz.c @@ -106,6 +106,7 @@ static void powerz_usb_cmd_complete(struct urb *urb) static int powerz_read_data(struct usb_device *udev, struct powerz_priv *priv) { + long rc; int ret; if (!priv->urb) @@ -127,8 +128,14 @@ static int powerz_read_data(struct usb_device *udev, struct powerz_priv *priv) if (ret) return ret; - if (!wait_for_completion_interruptible_timeout - (&priv->completion, msecs_to_jiffies(5))) { + rc = wait_for_completion_interruptible_timeout(&priv->completion, + msecs_to_jiffies(5)); + if (rc < 0) { + usb_kill_urb(priv->urb); + return rc; + } + + if (rc == 0) { usb_kill_urb(priv->urb); return -EIO; } -- cgit 1.3-korg
drivers/hwmon/powerz.c+9 −3 modifieddiff --git a/drivers/hwmon/powerz.c b/drivers/hwmon/powerz.c index a75b941bd6e2fc..96438f5f05d489 100644 --- a/drivers/hwmon/powerz.c +++ b/drivers/hwmon/powerz.c @@ -106,6 +106,7 @@ static void powerz_usb_cmd_complete(struct urb *urb) static int powerz_read_data(struct usb_device *udev, struct powerz_priv *priv) { + long rc; int ret; if (!priv->urb) @@ -127,8 +128,14 @@ static int powerz_read_data(struct usb_device *udev, struct powerz_priv *priv) if (ret) return ret; - if (!wait_for_completion_interruptible_timeout - (&priv->completion, msecs_to_jiffies(5))) { + rc = wait_for_completion_interruptible_timeout(&priv->completion, + msecs_to_jiffies(5)); + if (rc < 0) { + usb_kill_urb(priv->urb); + return rc; + } + + if (rc == 0) { usb_kill_urb(priv->urb); return -EIO; } -- cgit 1.3-korg
Vulnerability mechanics
Root cause
"Missing error handling for signal interrupt in powerz_read_data() — when wait_for_completion_interruptible_timeout() returns -ERESTARTSYS, the original code does not call usb_kill_urb() and proceeds to read from an unfilled transfer buffer."
Attack vector
An attacker with local access can send a signal (e.g. SIGINT) to a process that is reading hwmon data from a POWER-Z USB device via the powerz driver. The signal causes wait_for_completion_interruptible_timeout() in powerz_read_data() to return -ERESTARTSYS. Because the original code only checked for timeout (return value 0) via the !ret test, the negative return value is not caught, usb_kill_urb() is skipped, and the function falls through to read stale or uninitialized data from the transfer buffer [patch_id=2659933].
Affected code
The vulnerable function is powerz_read_data() in drivers/hwmon/powerz.c. The original code at the wait_for_completion_interruptible_timeout() call site used a single !ret check that only handled the timeout case (0), missing the signal-interrupt case (-ERESTARTSYS) [patch_id=2659933].
What the fix does
The patch captures the return value of wait_for_completion_interruptible_timeout() into a long variable 'rc' and adds two explicit checks: if rc < 0 (signal interrupt), call usb_kill_urb() and return rc; if rc == 0 (timeout), call usb_kill_urb() and return -EIO [patch_id=2659933]. This ensures that on signal delivery the URB is properly killed and the function returns an error instead of reading from the unfilled transfer buffer.
Preconditions
- configThe POWER-Z USB device must be connected and the powerz hwmon driver must be loaded.
- inputA process must be performing a read from the hwmon sysfs interface (triggering powerz_read_data()).
- authThe attacker must be able to deliver a signal (e.g. SIGINT) to that process.
Generated on May 27, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.
References
4News mentions
0No linked articles in our index yet.