Unbounded busy-wait polling in SMIC allows permanent kernel thread hang / CPU DoS
- File:
sys/dev/misc/ipmi/ipmi_smic.c - Lines: 53β81 (
smic_wait_for_tx_okay/smic_wait_for_rx_okay/smic_wait_for_not_busy); reachable fromsmic_polled_requestviasmic_loopkthread - Severity: Medium
- CVSS 3.1:
CVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:U:C:N/I:N/A:H - CWE: CWE-835 Loop with Unreachable Exit Condition (Infinite Loop)
- Confidence: certain
- Status: new
Summary
The three SMIC status-register polling functions
(smic_wait_for_tx_okay, smic_wait_for_rx_okay, smic_wait_for_not_busy)
are unbounded do/while busy-spin loops with:
- no timeout,
- no
DELAY()between polls, - no
signal_pending/detach check.
When a BMC is slow, hung, faulty, or compromised (an explicit threat model per
AGENT.md), the smic_loop kthread spins forever at 100% CPU, cannot be killed,
blocks module unload (ipmi_detach sleeps indefinitely on the kthread at
ipmi.c:895), and prevents clean system shutdown.
The sibling KCS transport correctly bounds all equivalent loops with
MAX_TIMEOUT and DELAY(100); SMIC is the only IPMI backend with unbounded
polling.
Root cause
smic_wait_for_tx_okay (ipmi_smic.c:53-61),
smic_wait_for_rx_okay (ipmi_smic.c:63-71), and
smic_wait_for_not_busy (ipmi_smic.c:73-81) each implement an unbounded
polling loop of the form:
do { flags = INB(sc, SMIC_FLAGS); } while (!(flags & FLAG));
- No tick-based timeout β
MAX_TIMEOUTis defined as6*hzinipmivars.h:219and used by KCS atipmi_kcs.c:60,66,83,89β but never referenced inipmi_smic.c. - No
DELAY(100)between iterations β KCS usesDELAY(100)on every loop; SMIC has zeroDELAYcalls in the entire 407-line file. - No
signal_pending()orsc->ipmi_detachingcheck.
The smic_loop kthread (ipmi_smic.c:354-376) releases the IPMI lock at line
363, then calls smic_polled_request (line 366) which calls these wait
functions.
If the BMC never asserts the expected flag (TX_RDY, RX_RDY, or clears
BUSY), the kthread is trapped in the INB spin loop with no escape.
ipmi_detach (ipmi.c:891-897) then calls lksleep(sc->ipmi_kthread, ..., 0)
with timeout=0 (infinite) at ipmi.c:895, blocking forever because the
kthread can never reach kthread_exit() at ipmi_smic.c:375.
Threat model
Attacker positions:
(a) A local user in the operator group (device is mode 0660 GID operator per
ipmi.c:832) opens /dev/ipmi0 and submits an IPMI request via
IPMICTL_SEND_COMMAND. If the BMC is unresponsive (firmware hang, hardware
fault, overload from request flooding, or compromise), the smic_loop kthread
spins forever, permanently consuming one CPU core and making the IPMI subsystem
and module unload non-functional.
(b) No user action is needed at all β the kernel's own boot-time
GET_DEVICE_ID request (ipmi.c:732-735) or watchdog timer
(ipmi.c:688-705) can trigger the same hang if the BMC is faulty during boot.
(c) A compromised BMC (explicit threat model) can intentionally withhold the expected status flag to hang the host OS at will.
Impact:
- permanent 100% CPU consumption on one core,
- inability to unload the
ipmimodule, - inability to cleanly shut down the system,
- all subsequent IPMI requests (including watchdog resets) are blocked β potentially leading to watchdog-triggered system reset with no graceful path.
Proof of concept
Preconditions
A system with an SMIC-type IPMI interface (ACPI _IFT=0x02, or PCI progif
PCIP_SERIALBUS_IPMI_SMIC, or SMBIOS iface_type=SMIC_MODE) and a BMC that
enters a state where it does not assert SMIC_STATUS_TX_RDY /
SMIC_STATUS_RX_RDY or clear SMIC_STATUS_BUSY.
PoC approach (operator-group user trigger)
/* ipmi_hang.c -- Build: cc -o ipmi_hang ipmi_hang.c
*
* A trivial C program that opens /dev/ipmi0 and sends an IPMI request.
*/
#include <sys/ioctl.h>
#include <sys/ipmi.h>
#include <fcntl.h>
#include <string.h>
#include <unistd.h>
#include <stdio.h>
int main(void) {
int fd = open("/dev/ipmi0", O_RDWR);
if (fd < 0) { perror("open"); return 1; }
struct ipmi_system_interface_addr addr = {
.addr_type = IPMI_SYSTEM_INTERFACE_ADDR_TYPE,
.channel = IPMI_BMC_CHANNEL,
.lun = 0,
};
unsigned char data[1] = {0};
struct ipmi_req req = {
.addr = (void *)&addr,
.addr_len = sizeof(addr),
.msgid = 1,
.msg = {
.netfn = IPMI_APP_REQUEST,
.cmd = IPMI_GET_DEVICE_ID,
.data_len = 0,
.data = data,
},
};
ioctl(fd, IPMICTL_SEND_COMMAND, &req);
/* If BMC is hung/unresponsive, smic_loop kthread is now spinning forever */
pause();
return 0;
}
Run as a user in the operator group: ./ipmi_hang
Success indicator
top -P or ps aux shows the ipmi0: smic kernel thread at 100% CPU on one
core. kldunload ipmi hangs indefinitely. System shutdown hangs. The thread
cannot be killed with kill -9 (it's a kernel thread). The only recovery is a
hard reset.
Amplifying reliability
Flood the BMC with requests in a tight loop (while(1) ioctl(...)) to
overwhelm its firmware, or target hardware with known-slow BMCs. On some Dell
PE2650-class hardware (explicitly listed in ipmi_pci.c:57 as a SMIC device),
BMC firmware bugs are well-documented.
Note: The PoC does not require the attacker to control the BMC β any BMC malfunction during the request window triggers the permanent hang. The bug is the kernel's lack of any timeout, not the BMC's behavior.
Recommended fix
Add MAX_TIMEOUT-bounded loops with DELAY(100) to all three wait functions
(matching the KCS transport pattern at ipmi_kcs.c:52-96), and propagate
timeout failures to callers so smic_polled_request can abort and the kthread
can proceed to the next request or exit on detach.
--- a/sys/dev/misc/ipmi/ipmi_smic.c
+++ b/sys/dev/misc/ipmi/ipmi_smic.c
@@ -45,10 +45,10 @@
#include <dev/misc/ipmi/ipmivars.h>
#endif
-static void smic_wait_for_tx_okay(struct ipmi_softc *);
-static void smic_wait_for_rx_okay(struct ipmi_softc *);
-static void smic_wait_for_not_busy(struct ipmi_softc *);
-static void smic_set_busy(struct ipmi_softc *);
+static int smic_wait_for_tx_okay(struct ipmi_softc *);
+static int smic_wait_for_rx_okay(struct ipmi_softc *);
+static int smic_wait_for_not_busy(struct ipmi_softc *);
+static void smic_set_busy(struct ipmi_softc *);
static int
smic_wait_for_tx_okay(struct ipmi_softc *sc)
{
int flags;
- do {
- flags = INB(sc, SMIC_FLAGS);
- } while (!(flags & SMIC_STATUS_TX_RDY));
+ int start = ticks;
+
+ do {
+ flags = INB(sc, SMIC_FLAGS);
+ if (flags & SMIC_STATUS_TX_RDY)
+ return (1);
+ DELAY(100);
+ } while (ticks - start < MAX_TIMEOUT);
+ device_printf(sc->ipmi_dev, "SMIC: TX_RDY timeout\n");
+ return (0);
}
-static void
+static int
smic_wait_for_rx_okay(struct ipmi_softc *sc)
{
int flags;
- do {
- flags = INB(sc, SMIC_FLAGS);
- } while (!(flags & SMIC_STATUS_RX_RDY));
+ int start = ticks;
+
+ do {
+ flags = INB(sc, SMIC_FLAGS);
+ if (flags & SMIC_STATUS_RX_RDY)
+ return (1);
+ DELAY(100);
+ } while (ticks - start < MAX_TIMEOUT);
+ device_printf(sc->ipmi_dev, "SMIC: RX_RDY timeout\n");
+ return (0);
}
-static void
+static int
smic_wait_for_not_busy(struct ipmi_softc *sc)
{
int flags;
- do {
- flags = INB(sc, SMIC_FLAGS);
- } while (flags & SMIC_STATUS_BUSY);
+ int start = ticks;
+
+ do {
+ flags = INB(sc, SMIC_FLAGS);
+ if (!(flags & SMIC_STATUS_BUSY))
+ return (1);
+ DELAY(100);
+ } while (ticks - start < MAX_TIMEOUT);
+ device_printf(sc->ipmi_dev, "SMIC: not-busy timeout\n");
+ return (0);
}
Plus caller-side propagation: each smic_start_write/smic_write_next/etc.
caller checks the new return value and propagates failure up to
smic_polled_request, which returns 0, causing smic_loop to set
ir_error = EIO and call ipmi_complete_request. The kthread then loops back
to ipmi_dequeue_request, where it checks sc->ipmi_detaching and can exit
cleanly via kthread_exit().
References
sys/dev/misc/ipmi/ipmi_smic.c:53-61βsmic_wait_for_tx_okayunbounded loopsys/dev/misc/ipmi/ipmi_smic.c:63-71βsmic_wait_for_rx_okayunbounded loopsys/dev/misc/ipmi/ipmi_smic.c:73-81βsmic_wait_for_not_busyunbounded loopsys/dev/misc/ipmi/ipmivars.h:219βMAX_TIMEOUT = 6*hz(defined but not used by SMIC)sys/dev/misc/ipmi/ipmi_kcs.c:52-96β sibling KCS transport with correctMAX_TIMEOUT+DELAY(100)patternsys/dev/misc/ipmi/ipmi.c:895βipmi_detachinfinitelksleep(...,0)on kthreadsys/dev/misc/ipmi/ipmi_smic.c:354-376βsmic_loopkthread callingsmic_polled_requestsys/dev/misc/ipmi/ipmi.c:832β/dev/ipmiNmode0660 GID operator
Discussion (0)
PoC verification
Evidence pack
findings/poc/DF-2003 Β· 9 files| File | Type | Description | Size | |
|---|---|---|---|---|
| df2003_confirm.c | trigger-source | static structural check confirming the three unbounded SMIC loops + KCS contrast | 2.6 KB | view raw |
| build.sh | build-script | cc -O2 -Wall -o df2003_confirm df2003_confirm.c | 154 B | view raw |
| run.sh | run-script | ./df2003_confirm | 131 B | view raw |
| build.log | build-log | BUILD_EXIT=0 | 13 B | view raw |
| run.log | run-log | ALL_PROPERTIES=CONFIRMED | 1.6 KB | view raw |
| VERDICT.md | verdict | full source-trace narrative + DoS mechanism + fix rationale | 4.1 KB | β raw |
| fix.diff | suggested-fix | bound the three SMIC wait loops with MAX_TIMEOUT + DELAY(100), matching KCS (git-apply-able) | 1.7 KB | view raw |
| env.txt | environment | guest uname + /dev/ipmi0 absence (no HW) | 251 B | view raw |
| fix_build.log | build-log | Phase 8 combined kernel build rc=0 -Werror (3 fixes); patched .o + .ko confirmed | 883 B | view raw |
DF-2003 β VERDICT
Verdict: CONFIRMED (source-trace), HW-gated β inconclusive at runtime
The unbounded busy-wait DoS is real and confirmed by a complete source trace.
It cannot be exercised on this audit guest because the ipmi0 device requires
SMIC-type IPMI hardware + a BMC, which is not present (no /dev/ipmi0, no SMIC
interface in dmesg). Per the standard HW-gated pattern, runtime reproduction
is inconclusive / reproduced=0 / impact=none, with the bug proven by code
inspection.
Mechanism (confirmed path:line)
- Three unbounded polling loops β
sys/dev/misc/ipmi/ipmi_smic.c:53-81: -smic_wait_for_tx_okay(53-61):do { flags = INB(sc, SMIC_FLAGS); } while (!(flags & SMIC_STATUS_TX_RDY));-smic_wait_for_rx_okay(63-71):do { ... } while (!(flags & SMIC_STATUS_RX_RDY));-smic_wait_for_not_busy(73-81):do { ... } while (flags & SMIC_STATUS_BUSY);
Each is a tight INB spin with: no tick-based timeout, no DELAY()
between polls, and no signal_pending() / sc->ipmi_detaching check.
-
MAX_TIMEOUTexists but is unused here βsys/dev/misc/ipmi/ipmivars.h:219defines#define MAX_TIMEOUT 6 * hz. The sibling KCS transport uses it atipmi_kcs.c:60,66,83,89together withDELAY(100). The entire 407-lineipmi_smic.chas zero references toMAX_TIMEOUTand zeroDELAYcalls β SMIC is the only IPMI backend without bounded polling. -
Reachable from the
smic_loopkthread βipmi_smic.c:355-376: the kthread loops onipmi_dequeue_requestβsmic_polled_request(366) β the wait functions. If the BMC never asserts the expected flag, the kthread is trapped in theINBspin forever. -
Detach/shutdown wedge β
sys/dev/misc/ipmi/ipmi.c:895:ipmi_detachdoeslksleep(sc->ipmi_kthread, ..., 0)withtimeout=0(infinite). A stuck kthread can never reachkthread_exit()(ipmi_smic.c:375), sokldunload ipmiand clean shutdown hang indefinitely.
Impact
- Permanent 100% CPU consumption on one core (unkillable kernel thread).
- Inability to
kldunload ipmi. - Inability to cleanly shut down.
- All subsequent IPMI requests (including watchdog resets) blocked β may lead to a watchdog-triggered hard reset with no graceful path.
Threat positions (realistic): a local operator-group user submitting an
IPMI request against an unresponsive BMC; the kernel's own boot-time
GET_DEVICE_ID (ipmi.c:732-735) or watchdog (ipmi.c:688-705) hitting a
faulty BMC; or a compromised BMC (explicit threat model) intentionally
withholding the flag. The PoC needs no attacker control of the BMC β any BMC
malfunction in the request window triggers the permanent hang.
Exploit chain
Not applicable β this is a pure DoS (CWE-835 infinite loop), not a memory- corruption primitive. The impact ceiling is permanent kernel-thread hang + CPU DoS + shutdown/module-unload wedge, fully characterized above.
PoC changes
- Added
df2003_confirm.cβ a static structural check documenting the three unbounded loops, the unusedMAX_TIMEOUT, the KCS contrast, and the detach-wedge path. - Added
build.sh/run.sh. - Authored
fix.diffβ bounds all three wait functions withMAX_TIMEOUT+DELAY(100)(matching the KCS pattern), changes their return type toint(1=ok, 0=timeout) with a diagnosticdevice_printfon timeout. Matches the finding markdown's proposal. On timeout the function returns 0; the caller's subsequentINBreads a still-busy/wrong status, the existingif (status != expected)check fires, and the error propagates up tosmic_polled_requestβsmic_loop, which setsir_errorand callsipmi_complete_request, letting the kthread loop back toipmi_dequeue_requestwhere it can checkipmi_detachingand exit cleanly.
Fix
fix.diff replaces the three unbounded do/while spins with bounded
do { INB; if (flag) return 1; DELAY(100); } while (ticks - start < MAX_TIMEOUT)
loops, matching the KCS transport's proven pattern. git apply --check passes.
Validated by a clean kernel build in Phase 8.
Fix verification
not_testableVALIDATED build. Patch applies, ipmi.ko rebuilds rc=0.
NK_DONE rc=0; ipmi_smic.o with 3 MAX_TIMEOUT bounds.
Confirmed kernel references
- s
- y
- s
- /
- d
- e
- v
- /
- m
- i
- s
- c
- /
- i
- p
- m
- i
- /
- i
- p
- m
- i
- _
- s
- m
- i
- c
- .
- c
- :
- 5
- 3
- s
- y
- s
- /
- d
- e
- v
- /
- m
- i
- s
- c
- /
- i
- p
- m
- i
- /
- i
- p
- m
- i
- _
- s
- m
- i
- c
- .
- c
- :
- 6
- 3
- s
- y
- s
- /
- d
- e
- v
- /
- m
- i
- s
- c
- /
- i
- p
- m
- i
- /
- i
- p
- m
- i
- _
- s
- m
- i
- c
- .
- c
- :
- 7
- 3
- s
- y
- s
- /
- d
- e
- v
- /
- m
- i
- s
- c
- /
- i
- p
- m
- i
- /
- i
- p
- m
- i
- _
- k
- c
- s
- .
- c
- :
- 6
- 0
Detail
Exploit chain
none (CWE-835 infinite loop DoS, not memory corruption).
Evidence (decisive lines)
Structural check: 3 unbounded loops, 0 MAX_TIMEOUT refs in SMIC vs KCS correct.
Verified recommended fix
Bound all 3 SMIC waits with MAX_TIMEOUT+DELAY(100), matching KCS pattern.
Verdict
HW-GATED (no IPMI BMC). Bug CONFIRMED source-trace. ipmi_smic.c:53-81 three do/while INB spins with NO timeout/DELAY/signal. KCS sibling correctly bounded with MAX_TIMEOUT+DELAY. SMIC is the lone outlier. Hung BMC traps kthread forever; ipmi_detach wedges.
No comments yet.