# DF-5180 — Verification Verdict

**Status:** untested (source-confirmed; ACPI-firmware-gated — no unprivileged
trigger on the audit guest)
**Impact:** dos (heap OOB read; usually silent, worst-case kernel panic at an
allocation/page boundary; no information disclosure)
**Confidence:** certain
**Attempts:** 0 (no runtime attempt — see below)

## Why the code path is proven without a guest run

1. `acpi_PkgStr()` accepts `ACPI_TYPE_BUFFER` and copies with `strlcpy()`
   (`sys/dev/acpica/acpi_package.c:86-98`) using `siz = Buffer.Length + 1`.
2. ACPICA externalizes Buffers as a plain `memcpy` of exactly
   `Buffer.Length` bytes — **no NUL terminator appended** — while Strings
   get `Length + 1` bytes including the terminator
   (`sys/contrib/dev/acpica/source/components/utilities/utcopy.c:276-290`).
3. DragonFly `strlcpy()` ends with an unbounded source scan when no NUL was
   found within `siz - 1` bytes (`sys/libkern/strlcpy.c:56-61`:
   `while (*s++) ;`). For a zero-free buffer, the first scanned byte
   (`ptr[Buffer.Length]`) is already one past the ACPI buffer object.
4. Size accounting shows the worst case is real: a package's element data
   regions are packed with `ACPI_ROUND_UP_TO_NATIVE_WORD` rounding, and a
   Buffer contributes exactly `Buffer.Length`
   (`sys/contrib/dev/acpica/source/components/utilities/utobject.c:512-514`,
   `utcopy.c:285-286`). For a trailing buffer whose length is 8-aligned, the
   content ends exactly at the end of the zeroed result allocation
   (`utalloc.c:527`), so the scan reads past the kmalloc'd block.
5. Reachability: `acpi_cmbat.c:374-380` extracts `_BIF` model/serial/type/
   oeminfo via `acpi_PkgStr()`; the helper deliberately handles Buffer-typed
   elements, so crafted ACPI tables or hostile EC/battery AML reach the scan.

Containment (why Low): the result blob is zeroed on allocation
(`utalloc.c:527`), so when `Buffer.Length % 8 != 0` the 1–7 padding bytes
after the content are zero and the scan stops inside the same allocation.
Even in the worst case the scan stops at the first zero byte in adjacent
heap; the `strlcpy()` return value is discarded and the write to `dst` is
bounded by the pre-checked `size`, so **no OOB data is disclosed** — the
impact ceiling is a kernel panic at an unlucky slab/page boundary.

Distinct from the known trio: DF-2177 is the zero-length `ptr == NULL`
dereference; DF-2385 is the unbounded `Elements[idx]`; DF-2178 the dead
NULL checks. This finding is the *unbounded post-buffer scan* on a
well-formed, non-empty buffer with valid `ptr` and in-bounds `idx`.

## Why no guest run

The object must originate from ACPI firmware. The audit guest's QEMU DSDT is
benign; delivering a crafted table requires rebuilding the guest's firmware
configuration (root), and there is no post-boot entry point (no ioctl/sysctl
reaches `acpi_PkgStr`). A KLD that fabricates an `ACPI_OBJECT` would only
demonstrate a read that is usually contained within the same allocation —
not an observable effect. Marked `untested` (static proof), matching the
family precedent (DF-2177/DF-2178/DF-2385 were source-confirmed).

## Fix validation

`fix.diff` (in this directory) bounds the Buffer path: scan for the first
NUL strictly inside `Buffer.Length`, then `memcpy()` the bounded prefix
(`dst` is already zeroed at acpi_package.c:79). Verified to apply cleanly
(`patch -p1 --dry-run` OK against a pristine copy of the file). Kernel
build/boot validation not performed (Low severity, firmware-gated trigger):
`fix_status: not_testable`.

## Source File

`sys/dev/acpica/acpi_package.c`

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