# DF-2386 — PoC verdict

**File:** `sys/dev/acpica/acpi_panasonic/acpi_panasonic.c` (:277-279)

**Verdict:** **REPRODUCED** — deterministic kernel NULL-pointer-dereference
panic inside the verbatim `acpi_panasonic_sinf()`, triggered by an
**unprivileged** local user reading `hw.acpi.panasonic.lcd_brightness`,
when the SINF evaluation fails. The verified fix makes the panic
disappear and returns `-1` instead (A/B validated).

## Line-accurate confirmation

1. `acpi_panasonic.c:275-276` — `buf.Length = ACPI_ALLOCATE_BUFFER;
   buf.Pointer = NULL;` — the caller initializes the pointer to NULL.
2. `acpi_panasonic.c:277` — `AcpiEvaluateObject(h, "SINF", NULL, &buf);`
   — return status discarded.
3. ACPICA contract, `sys/contrib/dev/acpica/source/components/namespace/
   nsxfeval.c:525-631`: on EVERY failure path the `ReturnBuffer` is left
   as the caller initialized it — evaluation error → `goto
   CleanupReturnObject` with `ReturnBuffer` untouched (:530-566);
   no return object → only `ReturnBuffer->Length = 0` is written,
   `Pointer` stays NULL (:541-545); buffer allocation failure inside
   `AcpiUtInitializeBuffer` (:580) also leaves `Pointer` NULL. So
   `buf.Pointer == NULL` after any failed SINF evaluation.
4. `acpi_panasonic.c:278-279` — `res = (ACPI_OBJECT *)buf.Pointer;
   if (res->Type == ACPI_TYPE_PACKAGE)` — NULL deref.
5. Reach: the four hotkey sysctls are registered `CTLFLAG_ANYBODY`
   (`acpi_panasonic.c:171`) under the real `hw.acpi` tree — any local
   user reaches `acpi_panasonic_sysctl` → `ACPI_SERIAL_BEGIN` →
   `hkey_lcd_brightness` → `acpi_panasonic_sinf` by a plain
   `sysctl hw.acpi.panasonic.lcd_brightness` read.

## Mechanical verification (verbatim-interposition rig)

Kernel `PANHARNESS` = stock INVARIANTS `X86_64_GENERIC` + `device
panasim`; the driver is compiled **byte-identical**
(sha256 `23e49741...d0912`, gated by build.sh) through `pana_shim.c`
build glue that renames only its ACPI externals to `panasim_*`
fakes; everything else (newbus, sysctl framework, ACPI_SERIAL locking)
runs real. The rig adds a real newbus child `acpi_panasonic0` to the
real `acpi0`; the verbatim probe/attach run and create the real
`hw.acpi.panasonic.*` sysctls. `panasim_AcpiEvaluateObject` reproduces
the documented ACPICA failure contract exactly: returns `AE_NO_MEMORY`
leaving `buf.Pointer` NULL.

Baseline run (`run.log`, `panic.txt`):

- control: sane 9-element SINF → maxx (uid 1001) reads
  `hw.acpi.panasonic.lcd_brightness` = `128`, RC=0 — unprivileged read
  works;
- armed: `hw.panasim.sinf_mode=0` (SINF evaluation fails);
- **trigger: maxx reads the sysctl again → kernel panic**, full trap
  block captured:

```
Fatal user address access from kernel mode from sysctl at ffffffff80a8013d
Fatal trap 12: page fault while in kernel mode
fault virtual address = 0x0
fault code = supervisor read data, page not present
current process = 1027
kernel: type 12 trap, code=0
Stopped at acpi_panasonic_sinf+0x5d: cmpl $0x4,(%rdi)
```

`cmpl $0x4,(%rdi)` is literally `res->Type == ACPI_TYPE_PACKAGE`
(`ACPI_TYPE_PACKAGE == 4`) with `%rdi = res = NULL`; the kernel itself
attributes the fault to the unprivileged `sysctl` process.

## Reachability / privesc assessment (honest)

- Hardware precondition: the driver only attaches on Panasonic
  `MAT0019` firmware (Let's note laptops); the unprivileged sysctl read
  is real but the *fault* additionally requires the SINF evaluation to
  fail at that moment — realistically `AE_NO_MEMORY` under kernel
  memory pressure (the finding's fork/allocation-bomb scenario) or a
  firmware anomaly. The rig injects exactly that failure mode at the
  exact API boundary; the driver-side unchecked deref is proven
  1/1 deterministic.
- Impact: NULL-deref **panic / reliable local DoS**. No write
  primitive, no controllable corruption — nothing to pivot to uid=0;
  not a privesc. Severity Medium upheld (C:N/I:N/A:H from an
  unprivileged read surface on matching hardware).

## Fix validation (A/B)

`fix.diff` (status check + `res != NULL` + `index < Count` guard +
guarded free + HINF/shutdown hunks shared with DF-2387/2590/2591):

- first iteration of the fix was itself caught by the rig: on the
  failure path it reached `AcpiOsFree(NULL)` → INVARIANTS
  `panic: trying to free NULL pointer` (`sys/kern/kern_slaballoc.c:
  1407` — even the real `AcpiOsFree(NULL)` would panic on INVARIANTS
  DragonFly). Amended with `if (buf.Pointer != NULL)` (the same idiom
  the driver itself uses at :420-421) — this is precisely why fix
  validation matters.
- fixed kernel (#6): identical trigger sequence → maxx read returns
  `-1` (`UINT64 -1` truncation), RC=0, guest stays up
  (`run_fixed.log`), repeated 2×.

## Artifacts

`run.log`/`run_fixed.log` (script outputs), `panic.txt` (full trap
block + DDB stop), `serial.txt` (fixed-run serial), `panasim.c` /
`panasim.h` / `pana_shim.c` (rig), `build.sh` / `run.sh`,
`build.log` (nativekernel, full), `fix.diff`, `env.txt`.

## 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.
