# 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):

```c
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.
