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

UINT8 loop-variable wraparound in CMI enumeration causes infinite loop while holding the global hp lock (local DoS; also amplifies cmi_order[] overflow)

Summary

The CMI enumeration uses UINT8 instance with the error-break idiom 'instance = maxInstance'. With max_instance==254 and the SHOW_MAX_INSTANCE detail bit, maxInstance==255; on get_cmi_block failure instance=255 then ++instance wraps to 0, restarting the loop forever inside dev_read while holding the global ACPI_SERIAL hp lockmgr. Failure at 0 = pure unkillable hang wedging all hp sysctls and /dev/hpcmi; failure at k>=1 = infinite re-append driving cmi_order_size unboundedly past cmi_order[128] (wraparound-restart mechanism, distinct from DF-2382's >128-instances). Unpriv /dev/hpcmi read (0644); data precondition BIOS-controlled. Loop logic LIVE-harness-verified compiled on the guest. stop-flag fix.

Discussion (0)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-4426 Β· 9 files
FileTypeDescriptionSize
wrap_harness.c β€” 3.9 KB view raw
build.sh β€” 98 B view raw
run.sh β€” 141 B view raw
build.log β€” 86 B view raw
run.log β€” 874 B view raw
env.txt β€” 488 B view raw
fix.diff β€” 943 B view raw
VERDICT.md β€” 3.6 KB ↓ raw
verdict.json β€” 3.5 KB view raw
VERDICT.md
↓ download raw

DF-4426 VERDICT β€” UINT8 instance wraparound β†’ infinite loop under hp lock

Reproduced?

On-guest kernel reproduction: no β€” the audit guest (QEMU/SeaBIOS, no HP BIOS) has no PNP0C14 ACPI node, so acpi_wmi never attaches (env.txt: no /dev/wmistat*, no hw.acpi.wmi sysctl), and acpi_hp consequently never creates /dev/hpcmi (env.txt: no /dev/hpcmi, no hw.acpi.hp sysctl oid). The defect requires a physical HP WMI BIOS whose CMI GUID advertises max_instance == 254.

Logic-level reproduction: YES, on the DragonFly guest itself β€” the exact loop control flow from acpi_hp.c:1164-1204 was extracted verbatim into wrap_harness.c (only the WMI layer stubbed; UINT8 = unsigned char identical on DFly x86-64), compiled with DragonFly cc 8.3 on the guest, and run (run.log):

  • error at instance 0 β†’ 300,000 body executions, 0 inserts β€” a pure infinite loop with zero memory writes. In-kernel this pins the calling thread inside dev_read forever while it holds lockmgr(&hp_serial, LK_EXCLUSIVE|LK_RETRY) (acpi_hp.c:1157, acpivar.h:170) β€” every later hw.acpi.hp.* sysctl op and every /dev/hpcmi open blocks forever. Unprivileged local DoS (device is 0644, acpi_hp.c:523-524).
  • error at instance 5 β†’ unbounded restart: cmi_order_size reached 250,000 in the capped demo β€” in-kernel this drives cmi_order[128] OOB writes far beyond what DF-2382 describes (which covers >128 instances; here a single broken instance at k causes infinite re-appending of k entries per wrap).
  • no error β†’ normal 255-iteration termination (control run).

Root cause (path:line)

  • sys/dev/acpica/acpi_hp/acpi_hp.c:1172 β€” for (instance = 0; instance < maxInstance; ++instance) with UINT8 instance (declared :1145).
  • sys/dev/acpica/acpi_hp/acpi_hp.c:1178 β€” instance = maxInstance; as the error-break idiom; with maxInstance == 255, ++instance wraps to 0 and the loop restarts (UINT8 arithmetic).
  • maxInstance == 255 requires sc->has_cmi == 255 (WMI GUID max_instance == 254, returned as max_instance+1 by acpi_wmi.c:344) and ACPI_HP_CMI_DETAIL_SHOW_MAX_INSTANCE set in hw.acpi.hp.cmi_detail (sysctl root-writable only β€” kern_sysctl.c:1446 SYSCAP_NOSYSCTL_WR check), which skips the maxInstance-- at acpi_hp.c:1168.

Why the guest can't show the kernel manifestation

No WMI firmware. Injecting a crafted PNP0C14 SSDT into the QEMU firmware is out of scope for a Low-severity finding; the loop logic itself is fully characterized and the lock-held-forever consequence is structural (the whole enumeration runs between ACPI_SERIAL_BEGIN(hp)/:1157 and ACPI_SERIAL_END(hp)/:1229).

Exploit chain

(none β€” denial of service only: stuck kernel thread + permanently held global hp sx/lockmgr lock; repeatable by any user once the sysctl precondition is set, e.g. sysctl hw.acpi.hp.cmi_detail=8 then cat /dev/hpcmi on a matching BIOS.)

Fix

fix.diff (dry-run verified): replace the instance = maxInstance idiom with a stop flag + break. One-line semantic fix; keeps the loop variable wrap-proof.

Fix validation

not_testable β€” no HP WMI hardware on the guest; the fix is a control-flow change whose logic effect is directly visible in the harness (with the fix applied to the harness loop, all three runs terminate).

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

not_testable
baseline no→ patch + rebuild →patched clean

Fix validation not possible: kernel path needs HP WMI firmware absent from the guest. fix.diff is control-flow-only and its effect is directly visible when applied to the harness loop (all runs terminate).

fix.diff (patch -p1 dry-run verified against the read-only sys/ tree copy)
↓ fix.diffper-fix-DF-4426

Confirmed kernel references

Detail

Evidence (decisive lines)

run.log: 'RUN err@0' 300000 executions/0 inserts and 'RUN err@5' 250000 inserts with VERDICT infinite-loop lines; VERDICT.md root-cause walk (acpi_hp.c:1145,1157,1165,1168,1172,1178; acpi_wmi.c:344); env.txt guest hardware absence; fix.diff dry-run applyable stop-flag fix

PoC changes

No PoC seed existed (new pass-2 finding). Wrote wrap_harness.c replicating the loop verbatim with stubbed WMI layer; compiled and executed on the DragonFly guest itself (cc 8.3 [DragonFly]) rather than the Linux host (no host compiler).

Verified recommended fix

Replace the 'instance = maxInstance' error-break idiom with a stop flag tested at loop top (fix.diff), or use an int loop variable and break.

Verdict

UINT8 loop-variable wraparound in acpi_hp_hpcmi_read's CMI enumeration (acpi_hp.c:1172,1178): when the WMI CMI GUID reports max_instance==254 and the SHOW_MAX_INSTANCE detail bit is set, maxInstance stays 255 and the error-break idiom 'instance = maxInstance' wraps 255->0 on ++instance, restarting the loop forever while the global hp lockmgr lock (acpi_hp.c:1157) is held - unkillable kernel thread plus permanent wedge of hw.acpi.hp.* sysctls and /dev/hpcmi. Kernel-level reproduction is impossible on the audit guest (no PNP0C14/WMI firmware: no /dev/hpcmi, no hw.acpi.wmi sysctl - see env.txt), but the exact loop logic was extracted verbatim, compiled with DragonFly cc 8.3 ON the guest, and demonstrated: error-at-0 gives a pure 0-write infinite loop; error-at-5 re-appends cmi_order entries unboundedly (cmi_order_size hit 250000 in a capped run), which in-kernel also amplifies the DF-2382 cmi_order[128] overflow far past the >128-instances scenario. Distinct defect from DF-2382/DF-1606: different mechanism (wraparound restart vs missing bound), different minimal manifestation (no-write pure hang).