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

ipmi_acpi SSIF stub attaches a dead softc (return 0 with nothing initialized); any later detach executes NULL sc->ipmi_enqueue_request β†’ fatal kernel trap

Summary

The SSIF arm prints 'not supported' then returns 0 - attach success on a softc with NULL ipmi_enqueue_request and no locks; DFly's ipmi_detach then calls ipmi_set_watchdog -> submit_driver_request -> call through NULL. REPRODUCED on guest with a hand-assembled IPI0001/_IFT=4 SSDT: Fatal trap 12 rip=0x0 on kldunload, DDB, guest dead; refuse-ENXIO + defensive-guard fix validated (attach fails cleanly, unload returns 0). Firmware/VM-config-gated (SSDT injection).

Discussion (0)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-5145 Β· 12 files
FileTypeDescriptionSize
README.md β€” 2.3 KB ↓ raw
VERDICT.md β€” 5.8 KB ↓ raw
ssdt_gen.py β€” 4.0 KB view raw
build.sh β€” 229 B view raw
run.sh β€” 264 B view raw
build.log β€” 960 B view raw
run.log β€” 1.7 KB view raw
panic.txt β€” 1.4 KB view raw
env.txt β€” 635 B view raw
fix.diff β€” 1.0 KB view raw
manifest.json β€” 1.4 KB view raw
verdict.json β€” 4.1 KB view raw

DF-5145 β€” ipmi_acpi SSIF dead-softc attach β†’ NULL ipmi_enqueue_request call on detach

What this proves

Firmware that describes its BMC with an ACPI IPI0001 device whose _IFT is 4 (SSIF_MODE, ipmivars.h:132) makes ipmi_acpi_attach() return success while leaving the softc completely uninitialized β€” it never runs ipmi_kcs_attach/ipmi_smic_attach (so sc->ipmi_enqueue_request is NULL), never calls ipmi_attach() (so lockinit in ipmi_startup() never happens, no cdev, ipmi_attached stays 0).

Every later detach of that device β€” the SYSUNINIT(ipmi_unload) at every shutdown/reboot (ipmi.c:940), or module teardown β€” runs DFly's ipmi_detach() β†’ ipmi_set_watchdog(sc, 0) (ipmi.c:887) β†’ ipmi_submit_driver_request() β†’ sc->ipmi_enqueue_request(sc, req) β€” an indirect call through a NULL function pointer β†’ fatal kernel trap.

Files

  • ssdt_gen.py β€” writes ssdt-ipmi-ssif.aml (hand-assembled AML, no iasl)
  • build.sh β€” build the audit-tree ipmi.ko + kernel inside the guest
  • run.sh β€” the full reproduction sequence (baseline)
  • fix.diff β€” the verified fix (ipmi_acpi.c: refuse SSIF instead of no-op-attaching) + defensive guard in ipmi.c
  • panic.txt β€” serial-console capture of the fatal trap (baseline run)
  • verdict.json β€” machine verdict

Reproduce

  1. python3 ssdt_gen.py ssdt-ipmi-ssif.aml
  2. Boot the guest with the table attached: cd dfbsd-qemu && QEMU_EXTRA="-acpitable file=$PWD/ssdt-ipmi-ssif.aml" ./vm.sh up (kernel must be built from the audit tree β€” the stock prebuilt guest kernel is newer and ignores RSDT-SSDT-added devices; see VERDICT.md)
  3. vm.sh run_root 'kldload /root/ipmi-audit.ko' β†’ dmesg: ipmi0: SSIF interface not supported on ACPI (attach "succeeded", device attached, softc dead)
  4. vm.sh run_root 'reboot' β†’ serial console: Fatal trap 12: page fault while in kernel mode at fault virtual address = 0x0 inside ipmi_submit_driver_request

Expected result

Baseline: kernel panic on every reboot of a machine whose ACPI describes an SSIF BMC (deterministic NULL function-pointer call; no attacker control of the target β€” CWE-476).

Patched (fix.diff): ipmi_acpi_attach returns ENXIO for _IFT==4, the ACPI node is left unattached, reboot is clean.

VERDICT.md
↓ download raw

DF-5145 VERDICT β€” ipmi_acpi SSIF dead-softc attach β†’ NULL ipmi_enqueue_request call on detach

Verdict: REPRODUCED (panic, firmware/config-gated) β€” fix validated

The chain (all lines against the pristine audit tree)

  1. Trigger β€” firmware (DSDT/SSDT) describes an IPI0001 ACPI device with _IFT = 4 (SSIF_MODE, sys/dev/misc/ipmi/ipmivars.h:132). Reproduced with a hand-assembled SSDT (ssdt_gen.py; _HID EISAID 0x01000926 = byte-swapped "IPI0001", verified against QEMU's own isa-ipmi-kcs AML) injected via QEMU -acpitable.
  2. Probe β€” ipmi_acpi_probe (ipmi_acpi.c:58-72): ipmi_attached guard passes (0), ACPI_ID_PROBE matches IPI0001 β†’ attach runs. The singleton guard itself is present and correct in this frontend (unlike DF-5076's ipmi2_pci).
  3. The bug β€” ipmi_acpi_attach (ipmi_acpi.c:104-109): the SSIF_MODE case reads _ADR, prints "SSIF interface not supported on ACPI", and return (0) β€” attach SUCCESS on a completely uninitialized softc: - sc->ipmi_enqueue_request is NULL (only ipmi_kcs_attach (ipmi_kcs.c:487) / ipmi_smic_attach (ipmi_smic.c:393) set it), - lockinit/cv_init/TAILQ_INIT never ran (they live in ipmi_startup (ipmi.c:719-721), reached only via the config_intrhook that ipmi_attach() (never called) establishes), - no cdev, no kthread, and ipmi_attached stays 0.
  4. The crash β€” any detach of that device. Proven on the kldunload path: devclass_delete_driver β†’ device_detach β†’ ipmi_detach (ipmi.c:869) β†’ ipmi_set_watchdog(sc, 0) (ipmi.c:887, DragonFly-specific β€” FreeBSD's ipmi_detach does not do this) β†’ ipmi_submit_driver_request (ipmi.c:584) β†’ IPMI_LOCK on the zeroed lock (survives by accident) β†’ sc->ipmi_enqueue_request(sc, req) β€” indirect call through NULL.

Observed (guest, kernel built from the audit tree, QEMU -acpitable)

Baseline (pristine module ipmi-audit.ko built from /usr/src):

ipmi0: <IPMI System Interface> port 0xca2-0xca3 on acpi0
ipmi0: SSIF interface not supported on ACPI
# kldunload ipmi-audit
Fatal user address access from kernel mode from kldunload at 0000000000000000
Fatal trap 12: page fault while in kernel mode
fault virtual address = 0x0
instruction pointer   = 0x8:0x0
current process = 808 (kldunload)
Stopped at 0:
db>

(panic.txt β€” full serial capture; guest wedged in DDB, ssh dead.)

Note on the reboot path: a plain reboot of the kldloaded configuration shut down cleanly (the module's SYSUNINIT(ipmi_unload, ipmi.c:940) did not tear the device down on that path in this test). The deterministic trigger is module unload; a built-in kernel's shutdown/unload paths remain exposed for the same reason (any ipmi_detach on the dead softc).

Fix validation (same kernel, same SSDT, fix.diff applied β†’ ipmi-fixed.ko)

ipmi0: <IPMI System Interface> port 0xca2-0xca3 on acpi0
ipmi0: SSIF interface not supported on ACPI
device_probe_and_attach: ipmi0 attach returned 6     <- refused, no dead softc
# kldunload ipmi-fixed
UNLOAD_RC_0_CLEAN                                     <- no panic, guest alive

fix.diff = (a) ipmi_acpi.c: SSIF case returns ENXIO instead of claiming the device with a dead softc (also drops the now-unused info/flags locals); (b) ipmi.c ipmi_submit_driver_request: refuse the NULL enqueue (defense in depth for any other dead-softc path).

Why the guest needed an audit-tree kernel

The stock prebuilt guest kernel is built from NEWER source (its ipmi.ko contains the post-audit acpi_id_probe_t framework; the audit tree's ACPI_ID_PROBE comes from the generated acpi_if.h) and its ACPI enumeration ignores devices added via RSDT SSDTs (verified: neither my IPI0001 nor a PNP0C0C control device got children, while QEMU's own DSDT-appended IPI0001 KCS device attached fine). A kernel built from the audited /usr/src (make nativekernel KERNCONF=X86_64_GENERIC) enumerates the SSDT device and exhibits the audited code's behavior.

PoC engineering notes (what had to be right)

  • EISAID for Name(_HID, ...) is stored byte-swapped: "IPI0001" β†’ 0x01000926 (ACPICA byte-swaps before decoding; confirmed against QEMU's generated DSDT bytes 0c 26 09 00 01).
  • ACPICA's 2-byte PkgLength form is (byte1 << 4) | (byte0 & 0x0F) (psargs.c AcpiPsGetNextPackageLength), not the bits[5:0]+<<6 form β€” a wrong length silently truncates the Device body after N names (this cost several debug iterations: _HID/_UID/_IFT existed, _SRV/_ADR/_STA/_CRS didn't).
  • Buffer(N){...} needs the BufferSize TermArg (0a N) before the byte list.
  • The small I/O-port descriptor's _LEN is one byte.
  • _SRV must exist (ipmi_acpi.c:89 reads it before the interface switch).
  • Failed-attach newbus children are not re-probed when another driver registers β€” each module test needs a fresh boot.

Impact

  • Firmware/config-gated local kernel panic (CWE-476): any machine whose ACPI tables describe an SSIF BMC via IPI0001 (or any operator of a VM/cloud image with injected ACPI tables β€” trivially done with -acpitable) gets a deterministic NULL function-pointer call in kernel mode on ipmi module unload/teardown. No control over the call target (address 0), no data corruption, no info leak: availability only. Severity Low per the project rubric (requires specific firmware/configuration); a maintainer-fixable robustness defect inherited from FreeBSD's ipmi_acpi.c SSIF stub plus DragonFly's detach-time watchdog-off call.

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

fix.diff applied to guest /usr/src (module-only rebuild, same kernel, same SSDT): attach now refuses ('ipmi0: SSIF interface not supported on ACPI' + 'device_probe_and_attach: ipmi0 attach returned 6'), no device attached, kldunload returns 0, guest stays alive. Baseline with pristine module in the identical setup panics (trap 12, rip=0). Bad behavior GONE with fix.

fix.diff; VERDICT.md 'Fix validation' section; guest transcript: LOADED_FIXED / attach returned 6 / UNLOAD_RC_0_CLEAN / NO_IPMI_LOADED / STILL_ALIVE
↓ fix.diffper-fix-DF-5145

Confirmed kernel references

Detail

Exploit chain

firmware/VM config (IPI0001 + _IFT=4 SSDT) -> ipmi_acpi probe match -> dead-softc attach (return 0) -> module unload/teardown -> NULL indirect call -> fatal trap 12 at address 0 -> DDB (system down). No escalation: target address is fixed NULL, no memory corruption.

Evidence (decisive lines)

panic.txt (decisive serial capture: 'Fatal user address access from kernel mode from kldunload at 0000000000000000' / rip=0x0); run.log (baseline + fix sequence); VERDICT.md (full narrative incl. fix validation transcript); ssdt_gen.py (trigger generator); env.txt (guest uname)

PoC changes

Seed concept replaced entirely: trigger is a hand-assembled SSDT (ssdt_gen.py), not a C program. Required: byte-swapped EISAID (0x01000926), ACPICA 2-byte PkgLength form (byte1<<4 | byte0&0xF), Buffer(N) size TermArg, 1-byte small-IO _LEN, _SRV present; audit-tree kernel build (stock prebuilt guest kernel ignores RSDT-SSDT devices); trigger via kldunload (plain reboot does not tear the module down).

Verified recommended fix

ipmi_acpi.c SSIF case: return ENXIO instead of return(0) (never attach a dead softc); plus defensive NULL guard on ipmi_enqueue_request in ipmi_submit_driver_request (see fix.diff).

Verdict

REPRODUCED on the audit-tree kernel+module: an ACPI IPI0001 device with _IFT=4 (SSIF) makes ipmi_acpi_attach() return 0 on a completely uninitialized softc (ipmi_acpi.c:104-109 β€” no ipmi_enqueue_request, no lockinit, no cdev). kldunload then runs ipmi_detach -> ipmi_set_watchdog(sc,0) (ipmi.c:887, DragonFly-specific) -> ipmi_submit_driver_request -> sc->ipmi_enqueue_request(sc,req) == NULL call: 'Fatal user address access from kernel mode from kldunload at 0000000000000000', trap 12, rip=0, DDB, guest dead (panic.txt). Fix validated in the same setup: with fix.diff the attach refuses with ENXIO ('attach returned 6') and kldunload completes cleanly (UNLOAD_RC_0_CLEAN). Firmware/config-gated availability impact only (CWE-476); no attacker control of the call target.