β¬’ DragonFlyBSD Kernel Audit
← triage Β· dashboard
DF-4505

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)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-4505 Β· 13 files
FileTypeDescriptionSize
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
VERDICT.md
↓ download 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

sys/dev/misc/ipmi/ipmi.c:

  • ipmi_ioctl() IPMICTL_SEND_COMMAND (ipmi.c:401-416): every ioctl allocates sizeof(struct ipmi_request) + req->msg.data_len + IPMI_MAX_RX bytes (ipmi_alloc_request, ipmi.c:528-549; msg.data_len is unsigned 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_requests is a plain counter β€” nothing in the driver bounds it.
  • When the M_IPMI kmem accounting limit is crossed, DragonFly's kmalloc panics: 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)

  1. 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", then kldload ipmi β†’ ipmi0: KCS mode found at io 0xca2 on acpi, /dev/ipmi0 = crw-rw---- root operator.
  2. maxx (uid 1001, groups: maxx + operator only) runs ./flood 60000 65535.
  3. 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 exceeded with 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 128 and, in SEND_COMMAND's final locked section, returns EBUSY when dev->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 hanging ipmi_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 + commands
  • run.log β€” patched-kernel decisive run, full output
  • run1.partial.txt β€” first baseline run output (mid-flood 329 MB sample)
  • panic.txt, panic2.txt, panic.baseline-withsrc.txt β€” three panic captures
  • fix.diff, fix_build.log, fix_run.log β€” validated fix
  • env.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

fixed
baseline reproduced→ patch + rebuild →patched clean

Baseline 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
↓ fix.diffDragonFly dfbsd 6.5-DEVELOPMENT #1: Sun Sep 20 22:09:55 UTC 2026 root@dfbsd:/usr/obj/usr/src/sys/X86_64_GENERIC x86_64

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.