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

Missing Buffer.Length check in acpi_hp_exec_wmi_command (heap OOB read of result[1]/result[2])

Summary

acpi_hp_exec_wmi_command validates ACPI reply is ACPI_TYPE_BUFFER but never validates obj->Buffer.Length before indexing as UINT32 array reading result[1] (offset 4) and result[2] (offset 8). Short/zero-length buffer from BIOS causes OOB heap read up to 12 bytes. ACPI_ALLOCATE_BUFFER lets ACPICA size buffer to whatever BIOS returned. Triggered indirectly via every read of dev.acpi_hp.0.* sysctls wrapping WMI command and via acpi_hp_evaluate_auto_on_off on every ACPI wireless event. Firmware-gated (conformant HP BIOS returns 12 bytes) primarily robustness/defense-in-depth. [VERIFIED pass2: reproduced through the real sysctl path - declared Length 4 surfaces the 0x41414141 canary at declared+4..+8 to UNPRIVILEGED userland via hw.acpi.hp sysctls, tight 4-byte reply surfaces uninitialized tail, Length 0 with NULL pointer panics at acpi_hp_exec_wmi_command+0xad; firmware-gated 8-byte leak plus NULL-deref DoS; length-check fix-validated, conformant replies bit-identical]

Discussion (0)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-2384 Β· 10 files
FileTypeDescriptionSize
README.md readme reply-mode matrix + build/run instructions 1.6 KB ↓ raw
VERDICT.md verdict line-accurate analysis, mode table, reachability ceiling, fix A/B 4.8 KB ↓ raw
hp_sim.c source interposition harness (shared with DF-2383; evaluate_call reply modes) 33.8 KB view raw
build.sh build-script sha256-gated push + nativekernel build/install (baseline | fixed) 120 B view raw
run.sh run-script create + mode 0..3 sysctl reads (incl. unprivileged) 174 B view raw
run.log run-log baseline + fixed mode matrix outputs 2.7 KB view raw
panic.txt panic full serial: Fatal trap 12 fault 0x4 at acpi_hp_exec_wmi_command+0xad <- acpi_hp_sysctl+0x44 6.0 KB view raw
fix.diff suggested-fix Buffer.Length >= 12 check (git-apply clean) 822 B view raw
env.txt environment unames, config, sha256 gate 1.7 KB view raw
verdict.json verdict-json machine verdict (persist_poc.py schema) 3.7 KB view raw
README.md readme reply-mode matrix + build/run instructions
↓ download raw

DF-2384 β€” Missing Buffer.Length check in acpi_hp_exec_wmi_command

File: sys/dev/acpica/acpi_hp/acpi_hp.c:838-860 Verdict: REPRODUCED β€” see VERDICT.md + verdict.json

Harness

Same verbatim-interposition kernel harness as DF-2383 (shared hp_sim.c, shared HPHARNESS kernel): the REAL acpi_hp.c compiled verbatim, attached under the real hw.acpi tree; the interposed evaluate_call returns crafted BIOS reply objects selected by hw.hpsim.reply_mode:

mode reply object baseline result
0 Buffer, Length=12 (conformant) display: 683 (control)
1 Length=4; canaries 0x41414141@+4, 0x42424242@+8 display: 1094795585 β€” OOB word surfaced to userland (root and unprivileged)
2 Length=4, tight allocation (debug.use_malloc_pattern=1) display: -1 β€” uninitialized allocation tail read
3 Length=0, Buffer.Pointer=NULL kernel panic at acpi_hp_exec_wmi_command+0xad (fault 0x4) from the sysctl read

The read path is the REAL one: userland sysctl hw.acpi.hp.* β†’ real acpi_hp_sysctl β†’ verbatim acpi_hp_exec_wmi_command.

Build

sh build.sh          # baseline kernel
sh build.sh fixed    # applies the DF-2384 length-check hunk

Run

sh run.sh

Baseline success criteria: mode 1 prints 1094795585 (also as maxx), mode 2 prints -1, mode 3 panics (full block in panic.txt). Fixed: modes 1-3 β†’ -22, mode 0 bit-identical, no panic.

Fix

fix.diff (git-apply-able, A/B validated): require obj->Buffer.Length >= 3 * sizeof(UINT32) before indexing result[].

VERDICT.md verdict line-accurate analysis, mode table, reachability ceiling, fix A/B
↓ download raw

DF-2384 β€” PoC verdict

File: sys/dev/acpica/acpi_hp/acpi_hp.c:838-860

Verdict: REPRODUCED β€” the unchecked result[] indexing reads past the ACPI buffer the BIOS declared, surfaces the out-of-bounds bytes to unprivileged userland through the hw.acpi.hp.* sysctls, and a zero-length BIOS reply with a NULL Buffer.Pointer panics the kernel from the same missing check. Fix A/B validated.

Line-accurate confirmation

acpi_hp_exec_wmi_command (acpi_hp.c:828-860):

obj = out.Pointer;
if (!obj || obj->Type != ACPI_TYPE_BUFFER) {   /* :848 β€” type check only */
        acpi_hp_free_buffer(&out);
        return (-EINVAL);
}
result = (UINT32*) obj->Buffer.Pointer;        /* :852 */
retval = result[2];                            /* :853 β€” reads +8..+12 */
if (result[1] > 0) {                           /* :854 β€” reads +4..+8 */
        retval = result[1];
}

Buffer.Length is never consulted: with a conformant HP BIOS the reply is 12 bytes (0x55434553, status, value), but nothing requires the firmware to honor that. A reply of 4 bytes makes both indexed reads OOB (8 bytes past the object); a zero-length buffer object can carry Buffer.Pointer == NULL, making result[1]/result[2] a NULL-relative read.

Callers reachable without privilege: acpi_hp_sysctl_get for every hw.acpi.hp.* value sysctl (world-readable by default β€” wlan_enabled, display, hdd_temperature, is_docked, ...) and the WMI hotkey/wireless event path (acpi_hp_evaluate_auto_on_off).

Mechanical verification (verbatim-interposition harness)

Same HPHARNESS kernel as DF-2383: the verbatim acpi_hp.c (sha256-gated) attaches under the real hw.acpi tree; the interposed acpi_wmi_evaluate_call returns crafted BIOS reply objects:

mode reply object baseline result meaning
0 Buffer, Length=12 display: 683 (as reply_val) conformant control
1 Length=4, canaries 0x41414141@+4, 0x42424242@+8 display: 1094795585 (=0x41414141) the canary past the declared length was read and returned to userland β€” read by root AND by unprivileged maxx
2 Length=4, tight allocation (debug.use_malloc_pattern=1) display: -1 (0xffffffff) reads the uninitialized allocation tail at +4..+12 β€” heap bytes the object does not own
3 Length=0, Buffer.Pointer = NULL kernel panic Fatal trap 12, fault va=0x4 at acpi_hp_exec_wmi_command+0xad ← acpi_hp_sysctl+0x44; serial console prints Fatal user address access from kernel mode from sysctl

Attribution for mode 1 is byte-exact: the value surfaced equals the 32-bit word at Buffer.Pointer+4 (the result[1] slot), i.e. the read at declared-length+4..+8; result[2] (+8..+12) is also loaded unconditionally first (retval = result[2]). Mode 2 shows real uninitialized heap tail data (deterministic 0xff... with the INVARIANTS malloc pattern knob). Mode 3 is the same missing check degrading to a NULL-deref DoS (panic.txt has the full serial block).

Reachability / impact ceiling (honest)

  • The trigger is a sysctl read β€” unprivileged on a default system β€” but the malicious input is the BIOS reply itself: only HP WMI firmware (or AML it delegates to) controls the reply object. This is firmware-gated: a buggy or hostile HP BIOS returns short replies; the kernel must not trust them. Practical impact ceiling on real hardware:
  • info leak of up to 8 adjacent heap bytes per read (mode 2 shows uninitialized-tail bytes; canary mode proves the exact offsets) β€” Low-severity kernel memory disclosure,
  • NULL-deref panic (reliable DoS) from a zero-length reply.
  • No attacker-controlled write; no privesc path β€” flag as Low, not closeable as privesc. (On the QEMU guest there is no HP WMI hardware at all β€” the harness supplies the BIOS side, which is exactly the trusted-but-untrusted boundary being tested.)

Fix validation (A/B)

fix.diff (git-apply-able): require obj->Buffer.Length >= 3 * sizeof(UINT32) before indexing, else -EINVAL.

mode baseline fixed
0 683 683 (bit-identical)
1 1094795585 -22
2 -1 -22
3 kernel panic -22 (repeat: -22; guest stays up)

Conformant 12-byte replies behave identically before/after the fix.

Classification

  • status: reproduced
  • reproduced: 1
  • impact: leak (8 bytes of out-of-bounds heap read surfaced to unprivileged userland; plus the panic variant from the same missing check β€” firmware-gated)
  • confidence: certain
  • fix_status: fixed (A/B validated)

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
↓ fix.diffper-fix-DF-2384

Confirmed kernel references

Detail

Exploit chain

unprivileged local user on HP WMI hardware with a short-reply BIOS: sysctl hw.acpi.hp. -> acpi_hp_sysctl -> acpi_hp_exec_wmi_command indexes result[2] (+8..+12) and result[1] (+4..+8) of a reply buffer declared 4 bytes (or 0 bytes) -> 8 bytes past the ACPI object surface to userland per read; zero-length+NULL reply instead panics the kernel (DoS). Ceiling: kernel heap info leak + firmware-gated DoS; no write primitive.

Evidence (decisive lines)

run.log (mode0=683 control; mode1=1094795585 root AND unprivileged maxx; mode2=-1 pattern tail; mode3 panic; fixed: -22 x3, mode0 identical, guest up); panic.txt (full serial: Fatal trap 12 fault 0x4 at acpi_hp_exec_wmi_command+0xad <- acpi_hp_sysctl+0x44, Fatal user address access from kernel mode from sysctl); hp_sim.c (interposed evaluate_call reply modes); fix.diff (length check); env.txt

PoC changes

No seed PoC existed; reused the verbatim-interposition kernel harness built for DF-2383 (same kernel, same hp_sim.c): the interposed acpi_wmi_evaluate_call returns ACPI_TYPE_BUFFER objects with controllable Buffer.Length and Buffer.Pointer plus canary tails; the read path is the REAL sysctl (hw.acpi.hp.* created by the verbatim attach under the real hw.acpi tree), exercised from both root and an unprivileged user.

Verified recommended fix

In acpi_hp_exec_wmi_command require obj->Buffer.Length >= 3 * sizeof(UINT32) before indexing result[], returning -EINVAL otherwise

Verdict

Missing Buffer.Length check confirmed line-accurate (acpi_hp.c:848-856 checks only the object type before indexing result[1]/result[2]) and mechanically with the verbatim acpi_hp.c running under the interposed WMI harness: a BIOS reply declared Length=4 made the driver return the 0x41414141 canary planted at declared+4..+8 to USERLAND through the world-readable hw.acpi.hp.* sysctls (read as unprivileged maxx, value 1094795585), a tight 4-byte reply surfaced the uninitialized allocation tail (0xffffffff with debug.use_malloc_pattern=1), and a zero-length reply with NULL Buffer.Pointer panicked the kernel at acpi_hp_exec_wmi_command+0xad from a sysctl read (Fatal trap 12, fault 0x4). Impact is firmware-gated (the BIOS controls the reply object): up to 8 OOB heap bytes leaked per unprivileged sysctl read plus a reliable NULL-deref DoS; no attacker-controlled write, no privesc. Fix (require >= 12 bytes, else -EINVAL) A/B validated: modes 1-3 return -22 with no panic, conformant 12-byte replies bit-identical.