ipmi(4): IPMICTL_SEND_COMMAND flood parks an unbounded in-flight request backlog β kernel memory exhaustion, panic "ipmi: malloc limit exceeded" (operator-group local kernel DoS)
Summary
SEND_COMMAND kmallocs up to ~66.6KB per ioctl and enqueues asynchronously; the only drain is the single serial kcs_loop kthread pushing every byte over KCS one port-IO at a time. dev->ipmi_requests is never bounded, so an operator-group user (0660 /dev/ipmi0) pins unbounded kernel memory until DragonFly panics in kmalloc. REPRODUCED 3x from fresh boots on a QEMU ipmi-bmc-sim+isa-ipmi-kcs guest: 5,000 ioctls pinned 329MB, panic with ipmi_ioctl trace; FIX VALIDATED on the rebuilt kernel: EBUSY at 128 in-flight, ~8.1MB peak, full recovery, survives 5 sustained floods. Availability-only. Per-handle cap + enqueue-error rollback fix.
Discussion (0)
PoC verification
Evidence pack
findings/poc/DF-4505 Β· 13 files| File | Type | Description | Size | |
|---|---|---|---|---|
| flood.c | β | 2.5 KB | view raw | |
| build.sh | β | 89 B | view raw | |
| run.sh | β | 240 B | view raw | |
| run.log | β | 1.6 KB | view raw | |
| run1.partial.txt | β | 654 B | view raw | |
| panic.txt | β | 556 B | view raw | |
| panic2.txt | β | 563 B | view raw | |
| panic.baseline-withsrc.txt | β | 563 B | view raw | |
| fix.diff | β | 1.3 KB | view raw | |
| fix_build.log | β | 1.5 KB | view raw | |
| fix_run.log | β | 677 B | view raw | |
| env.txt | β | 449 B | view raw | |
| VERDICT.md | β | 4.4 KB | β raw |
DF-4505 β VERDICT
REPRODUCED β kernel panic (local DoS) from an operator-group user, and the one-file fix is validated live on the same kernel.
Root cause
ipmi_ioctl()IPMICTL_SEND_COMMAND (ipmi.c:401-416): every ioctl allocatessizeof(struct ipmi_request) + req->msg.data_len + IPMI_MAX_RXbytes (ipmi_alloc_request, ipmi.c:528-549;msg.data_lenisunsigned short, max 65535 β ~66.6 KB per request) and enqueues it asynchronously.- The only drain is a single kthread (
kcs_loop, ipmi_kcs.c:449-469) that processes pending requests serially; each request is pushed over the KCS interface one byte per port-IO (ipmi_kcs.c:347-365), so service time per 64 KB request is five orders of magnitude larger than ioctl enqueue time. dev->ipmi_requestsis a plain counter β nothing in the driver bounds it.- When the M_IPMI kmem accounting limit is crossed, DragonFly's
kmallocpanics:panic: ipmi: malloc limit exceeded.
Attack class: CWE-400 (uncontrolled resource consumption). Access class:
/dev/ipmi0 is created 0660 root:operator (ipmi.c:831-832) β any local user
in group operator (the designated semi-privileged class for this node; also
the class DF-1661 was rated against). No hardware fault, misconfig, or root
is needed: ~20,000 plain ioctls.
How it was reproduced (3x baseline, from fresh boots)
- Guest booted with a simulated BMC so the surface is real:
QEMU_EXTRA="-device ipmi-bmc-sim,id=bmc0 -device isa-ipmi-kcs,bmc=bmc0", thenkldload ipmiβipmi0: KCS mode found at io 0xca2 on acpi,/dev/ipmi0=crw-rw---- root operator. maxx(uid 1001, groups: maxx + operator only) runs./flood 60000 65535.- Observed:
- 5,000 sends β v_free_count 847,167 β 762,987 (329 MB pinned β exactly
5000 Γ 66.6 KB of in-flight backlog; run1.partial.txt),
- guest stops answering ssh;
vm.sh statusβ down, - serial console:panic: ipmi: malloc limit exceededwith trace_kmalloc β ipmi_alloc_request β ipmi_ioctl β dev_dioctl β devfs_fo_ioctl(panic.txt, panic2.txt, panic.baseline-withsrc.txt β the third on the with-src snapshot kernel used for fix validation).
Honest scope: this is a kernel panic / availability bug, not a memory-
corruption primitive β kmalloc fails cleanly before any write occurs, so
there is no escalation path from this finding itself.
Fix validation (mandatory-quality evidence)
fix.diff (single file, applies clean with git apply):
- adds
IPMI_MAX_REQUESTS 128and, in SEND_COMMAND's final locked section, returnsEBUSYwhendev->ipmi_requests >= IPMI_MAX_REQUESTS(frees the just-allocated request); - fixes the latent error path found during this audit: if
ipmi_enqueue_request()ever fails (all three backends currently return 0 unconditionally β ipmi_kcs.c:487, ipmi_smic.c:393, ipmi_ssif.c:371), the counter is decremented and the request freed, instead of leaking both and hangingipmi_dtor()'s drain loop forever (ipmi.c:226-230).
Baseline (same with-src snapshot kernel): panic, guest dead
(panic.baseline-withsrc.txt).
Patched: make nativekernel KERNCONF=X86_64_GENERIC && make installkernel,
reboot, exact same PoC:
send 128 failed: Device busy after flood (fd open) v_free_count = 845510 pages (3302 MB) # 128Γ66.6KB β 8.1MB after close+10s v_free_count = 847545 pages (3310 MB) # fully recovered
Guest stayed up; 5 additional sustained floods (5Γ20,000 sends) showed no
panic and no cumulative leak (run.log). Residual exposure: the cap is
per-open-handle (128 Γ 66.6 KB β 8.5 MB per fd); a user may hold many fds.
A per-ipmi_softc or per-cred cap would be a stricter follow-up.
Files (evidence)
flood.c,build.sh,run.shβ exact PoC + commandsrun.logβ patched-kernel decisive run, full outputrun1.partial.txtβ first baseline run output (mid-flood 329 MB sample)panic.txt,panic2.txt,panic.baseline-withsrc.txtβ three panic capturesfix.diff,fix_build.log,fix_run.logβ validated fixenv.txtβ guest environment
Fix status (batch validation)
Applied with the 36 sibling fix.diffs of this batch in findings/poc/_batch41/all_fixes.patch; single in-guest make -j6 nativekernel KERNCONF=X86_64_GENERIC completed RC=0 with zero errors under -Werror (full log: findings/poc/_batch41/fix_build8.log). Behavior A/B intentionally skipped for this Info-tier item.
Fix verification
fixedBaseline on the with-src snapshot kernel panics (panic.baseline-withsrc.txt). After applying fix.diff, make nativekernel + installkernel + reboot, the exact same PoC returns EBUSY at 128 in-flight requests, pins only ~8.1MB, recovers fully on close, and survives 5 sustained 20k-request floods with no panic and no leak.
fix.diff; fix_build.log; fix_run.log; run.log
Confirmed kernel references
Detail
Evidence (decisive lines)
panic.txt / panic2.txt / panic.baseline-withsrc.txt (three serial-console panic captures with the ipmi_ioctl trace); run1.partial.txt (329MB pinned at 5k sends); run.log + fix_run.log (patched kernel: 'send 128 failed: Device busy', recovery, sustained-flood survival); fix_build.log (nativekernel build of fix.diff)
PoC changes
authored fresh (no seed): flood.c sends IPMI_APP_REQUEST/GET_DEVICE_ID with data_len 65535 per ioctl, samples vm.stats.vm.v_free_count mid-flood, and reports close/dtor drain behaviour
Verified recommended fix
Cap outstanding requests per open handle (IPMI_MAX_REQUESTS 128) in the SEND_COMMAND locked section, returning EBUSY; roll back the counter and free the request if enqueue fails
Verdict
IPMICTL_SEND_COMMAND (ipmi.c:401-416) kmallocs ~66.6KB per ioctl and enqueues asynchronously with no per-handle cap on outstanding requests; the single serial kcs_loop kthread cannot drain the backlog, so an operator-group user (the 0660 /dev/ipmi0 class, ipmi.c:831) pins unbounded kernel memory until DragonFly panics with 'ipmi: malloc limit exceeded'. Reproduced 3x from fresh boots on the QEMU ipmi-bmc-sim-backed guest as uid-1001 operator-group user maxx (329MB pinned at 5k sends; identical _kmalloc<-ipmi_alloc_request<-ipmi_ioctl panic traces in panic*.txt). Availability-only bug: kmalloc fails before any corruption, no escalation path from this finding itself. fix.diff (cap 128 outstanding + enqueue-error rollback) validated live on the same snapshot kernel: baseline panics, patched kernel returns EBUSY at 128 requests (~8.1MB peak), full memory recovery, 5 sustained floods with no panic or leak.
No comments yet.