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)
PoC verification
Evidence pack
findings/poc/DF-2384 Β· 10 files| File | Type | Description | Size | |
|---|---|---|---|---|
| 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 |
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[].
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: reproducedreproduced: 1impact: 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: certainfix_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_testableConfirmed kernel references
Detail
Exploit chain
unprivileged local user on HP WMI hardware with a short-reply BIOS: sysctl hw.acpi.hp.
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.
No comments yet.