# DF-3793 — `acpi_GetInteger()` reads an uninitialized stack `ACPI_OBJECT`

* File: `sys/dev/acpica/acpi.c` (pass 2)
* Lines: 2161–2204 (read of `param.Type` / `param.Integer.Value` at 2179–2180)
* Class: CWE-457 (use of uninitialized memory)
* Severity: Low · Confidence: certain (code + runtime proven on guest)

## What

`acpi_GetInteger()` hands ACPICA a caller-supplied (stack) buffer:

```c
ACPI_OBJECT param;
...
buf.Pointer = &param;
buf.Length = sizeof(param);
status = AcpiEvaluateObject(handle, path, NULL, &buf);
if (ACPI_SUCCESS(status)) {
    if (param.Type == ACPI_TYPE_INTEGER)     /* <-- uninitialized if the
                                                object returned no value */
        *number = param.Integer.Value;
    else
        status = AE_TYPE;
}
```

When the named AML object exists, evaluates without error, but **produces no
return object**, ACPICA (`nsxfeval.c`, `AcpiEvaluateObject`) sets
`ReturnBuffer->Length = 0`, never writes the caller's buffer, and returns
`AE_OK`. `acpi_GetInteger` then inspects `param.Type` — stale stack data — and,
if the garbage happens to equal `ACPI_TYPE_INTEGER` (1), propagates
`param.Integer.Value` (stale stack garbage) into `*number`.

On DragonFly's default configuration the interpreter-slack "implicit return"
(`AcpiGbl_EnableInterpreterSlack = 1`, set at acpi.c:538 from
`debug.acpi.interpreter_slack`, default 1) makes most value-less methods
return their last operator result, which masks the bug for typical BIOS AML.
With slack disabled — a supported boot tunable — *every* method without an
explicit `Return()` hits the uninitialized read. Slack-independent triggers
exist too: empty method bodies, bare `Return()`, or bodies whose operators
produce no result (only `If`/`Notify`/`Sleep`).

## Reachability / threat

* The DSDT/SSDT AML is firmware-provided; a defective or malicious BIOS (or a
  VM owner injecting tables) controls whether probed methods return nothing.
* In-kernel consumers of `acpi_GetInteger` include `_STA`
  (`acpi_DeviceIsPresent` acpi.c:2014, `acpi_BatteryIsPresent` acpi.c:2039),
  `_LID` (acpi_lid.c:161), power-resource `_STA` (acpi_powerres.c:668/712),
  EC / thermal / video paths. Consequences: device-present decisions, lid
  state, thermal thresholds and battery values derived from stale kernel
  stack data — including values that are user-readable through sysctls
  (limited stack-content disclosure) and state decisions (availability).
* No unprivileged runtime trigger found on a stock system (DO_MCALL does not
  use this helper), hence Low severity.

## How it was proven (guest, see VERDICT.md)

Kernel module `df3793.c` calls `AcpiEvaluateObject()` with a pre-poisoned
caller buffer on a no-`Return` method of the live DSDT
(`\_SB.PCI0.PRES._INI`, body `CSEL = Zero`):

```
DF3793: path=\_SB_.PCI0.PRES._INI status=AE_OK buf.Length_after=0
DF3793:   param.Type=1 param.Value=0xdeadbeefcafebabe  <== BUFFER UNTOUCHED, AE_OK
DF3793:   acpi_GetInteger -> AE_TYPE number=0x00000000  (frame layout differs; see Part A)
```

with `debug.acpi.interpreter_slack=0`. AE_OK, buffer untouched ⇒ inside
`acpi_GetInteger` the same call reads uninitialized stack. The helper's
outcome (`AE_TYPE` here, `AE_OK`+garbage number in other runs — observed
varying across runs before/after the tunable) is decided by stale stack
contents.

## Reproduce

1. `debug.acpi.interpreter_slack=0` in `/boot/loader.conf.local`, reboot.
2. Build: `make` in this directory (Makefile below; needs
   `/usr/obj/usr/src/sys/X86_64_GENERIC/opt_acpi.h` + `opt_global.h`
   copied in).
3. `kldload ./df3793.ko && dmesg | grep DF3793`.

Expected: `status=AE_OK buf.Length_after=0` with the poison value intact
(`<== BUFFER UNTOUCHED, AE_OK`), and `acpi_GetInteger` returning a
stack-history-dependent result instead of a defined "no value" status.

## Fix

See `fix.diff`: zero `param` and convert the "AE_OK but no object" case into
`AE_AML_NO_RETURN_VALUE`.
