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

Unchecked AcpiEvaluateObject return causes NULL-pointer-dereference panic reachable from unprivileged user via CTLFLAG_ANYBODY sysctl

Summary

acpi_panasonic_sinf() and acpi_panasonic_hkey_event() call AcpiEvaluateObject() and immediately dereference result buffer pointer without checking ACPI status or testing pointer for NULL. When AcpiEvaluateObject fails (memory pressure AE_NO_MEMORY in ACPI_ALLOCATE_BUFFER kmalloc AML interpreter error missing/typed-wrong return object namespace teardown) ACPICA leaves ReturnBuffer->Pointer as caller-supplied NULL so res->Type dereferences NULL kernel panics. Four hotkey sysctls registered CTLFLAG_ANYBODY so any unprivileged local user can drive this path by reading hw.acpi.panasonic.lcd_brightness. Most realistic trigger: memory exhaustion via fork/allocation bomb forces AE_NO_MEMORY in ACPI buffer alloc. Impact: reliable full DoS system halt/reboot. [VERIFIED pass2: reproduced - unpriv maxx sysctl read with failing SINF evaluation panics at acpi_panasonic_sinf+0x5d on NULL res->Type test; fixed build returns -1, control read 128 unchanged]

Discussion (0)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-2386 Β· 15 files
FileTypeDescriptionSize
README.md β€” 2.5 KB ↓ raw
VERDICT.md β€” 5.2 KB ↓ raw
run.sh β€” 240 B view raw
run.log β€” 409 B view raw
run_fixed.log β€” 523 B view raw
panic.txt β€” 2.3 KB view raw
serial.txt β€” 17.6 KB view raw
panasim.c β€” 19.4 KB view raw
panasim.h β€” 1.1 KB view raw
pana_shim.c β€” 2.6 KB view raw
build.sh β€” 169 B view raw
build.log β€” 5.7 MB ↓ download
fix.diff β€” 1.4 KB view raw
env.txt β€” 1.9 KB view raw
verdict.json β€” 5.3 KB view raw

DF-2386 β€” PoC evidence pack

Finding: Unchecked AcpiEvaluateObject return in acpi_panasonic_sinf() (sys/dev/acpica/acpi_panasonic/acpi_panasonic.c:277-279) β€” NULL-pointer dereference panic reachable from an unprivileged local user via the CTLFLAG_ANYBODY sysctl hw.acpi.panasonic.lcd_brightness.

Rig

Verbatim-interposition kernel harness (same shape as the acpi_hp rig, DF-2383/DF-2384):

  • acpi_panasonic.c compiled byte-identical into a custom INVARIANTS kernel (PANHARNESS = X86_64_GENERIC + device panasim; sha256-gated by build.sh: 23e49741027f53dcedd05423f7c5eacd1be0dada1c46277dde0be6694b8d0912).
  • pana_shim.c (build glue only) #define-renames the driver's ACPI externals to panasim_*; everything else β€” newbus probe/attach, the sysctl framework, ACPI_SERIAL_* locking, device_printf β€” runs real.
  • panasim.c fabricates the firmware side: the fake acpi_panasonic child is added to the REAL acpi0, the real attach creates the real hw.acpi.panasonic.* sysctls, and panasim_AcpiEvaluateObject replies for SINF/HINF/SSET with runtime-selectable behavior (including failure with the buffer left exactly as ACPICA leaves it β€” nsxfeval.c:536-600 failure contract: Pointer stays NULL).

Build

sh build.sh            # push rig + build/install PANHARNESS + reboot (baseline)
sh build.sh fixed      # additionally apply fix.diff to the driver, rebuild

Run

sh run.sh              # baseline: expects panic in acpi_panasonic_sinf
sh run.sh fixed        # fixed: expects -1 returned, no panic

Baseline sequence: create fake device β†’ unprivileged (maxx) sanity read of hw.acpi.panasonic.lcd_brightness (sane SINF) β†’ root arms hw.panasim.sinf_mode=0 (SINF evaluation fails like AE_NO_MEMORY) β†’ maxx reads the sysctl again β†’ kernel panic (fatal trap, acpi_panasonic_sinf, fault va 0x0). panic.txt carries the serial-console capture.

Expected result (baseline)

Fatal trap 12: page fault while in kernel mode at acpi_panasonic_sinf+… with fault virtual address = 0x0, triggered by uid 1000's sysctl process. Fixed build: the same read returns -1, guest stays up.

Note on reachability: the unprivileged sysctl read is real; the failure of the SINF evaluation at that instant is the firmware/AML side β€” on hardware it is caused by e.g. AE_NO_MEMORY under memory pressure or a firmware anomaly. The rig deterministically produces exactly that failure mode at the exact API boundary the driver consumes.

VERDICT.md
↓ download raw

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.

Fix verification

not_testable
↓ fix.diffper-fix-DF-2386

Confirmed kernel references

Detail

Exploit chain

uid 1001 maxx -> sysctl read of hw.acpi.panasonic.lcd_brightness (CTLFLAG_ANYBODY, acpi_panasonic.c:171) -> acpi_panasonic_sysctl -> ACPI_SERIAL_BEGIN -> hkey_lcd_brightness(HKEY_GET) -> acpi_panasonic_sinf(reg 0x04) -> AcpiEvaluateObject fails AE_NO_MEMORY leaving buf.Pointer NULL -> res->Type load from NULL -> fatal trap 12, kernel panic, system halt. Chain ceiling is DoS: the faulting access is a 4-byte read at address 0x0 with no attacker-controlled write, so no uid0 escalation exists for this class.

Evidence (decisive lines)

panic.txt (full trap block: fatal trap 12, fault virtual address 0x0, current process = the unprivileged sysctl, Stopped at acpi_panasonic_sinf+0x5d cmpl 0x4,(%rdi)); run.log (control read = 128 by maxx, then armed trigger); run_fixed.log (trigger returns -1, RC=0, guest alive); serial.txt; panasim.c/panasim.h/pana_shim.c (rig); build.log (nativekernel build); env.txt

PoC changes

No seed PoC existed. Built the verbatim-interposition kernel harness from scratch (same protocol as the acpi_hp rig DF-2383): pana_shim.c build glue renames the driver ACPI externals at the preprocessor level and includes the sha256-gated verbatim source; panasim.c fabricates the firmware world (SINF/HINF/SSET replies honoring the ACPICA failure contract, notify-handler capture, fake newbus child on the real acpi0 so the real sysctls appear under hw.acpi.panasonic). Iterations: (1) missing panasim.h prototypes tripped -Werror; (2) blanket-matching fake ACPI_ID_PROBE let the driver hijack an unrelated unit-0 ACPI child at boot -- fixed to match only the rig-created device; (3) first fix.diff iteration panicked in AcpiOsFree(NULL) on INVARIANTS -- amended with a NULL guard before the free.

Verified recommended fix

acpi_panasonic_sinf: check AcpiEvaluateObject status, verify res != NULL and res->Type == ACPI_TYPE_PACKAGE and index < res->Package.Count before reading Elements[index], and free buf.Pointer only when non-NULL

Verdict

Confirmed deterministically on the verbatim driver compiled byte-identical into a custom INVARIANTS kernel: acpi_panasonic_sinf discards the AcpiEvaluateObject status (acpi_panasonic.c:277) and dereferences res = buf.Pointer (set NULL by the driver itself at :276 and left NULL by every ACPICA failure path, nsxfeval.c:525-631) at :279 -- an unprivileged local user reading the CTLFLAG_ANYBODY sysctl hw.acpi.panasonic.lcd_brightness while the SINF evaluation fails panics the kernel: full trap block captured (fatal trap 12, fault va 0x0, Stopped at acpi_panasonic_sinf+0x5d cmpl 0x4,(%rdi) -- the ACPI_TYPE_PACKAGE comparison on NULL, attributed by the kernel to the sysctl process). Reach requires Panasonic MAT0019 firmware plus an SINF evaluation failure at read time (AE_NO_MEMORY under memory pressure or a firmware anomaly) -- the rig injects exactly that failure at the exact ACPICA boundary the driver consumes. Impact is a reliable NULL-deref panic / local DoS from an unprivileged read; no write primitive, not a privesc. fix.diff (status check + NULL/type/Count guards + guarded free) A/B validated: same trigger returns -1 with no panic. The first fix iteration was itself caught by the rig (it reached AcpiOsFree(NULL) and panicked via kern_slaballoc.c:1407 on INVARIANTS) and was amended to guard the free -- the idiom the driver itself uses at :420-421.