# DF-5102 VERDICT

**Status: reproduced (mechanism-level PoC on the audit guest) — impact: panic (DoS).**
**Finding severity: Low** (narrow trigger: Apple-flagged firmware + absent smbios kenv
entries + driver present via LINT64/custom kernel/root kldload).

## What was run

`df5102_nullfree.c` is a KLD that executes the exact `apple_fw_probe`
sequence — `kgetenv()` of an environment variable then `kfreeenv()` — with
variables that are absent from the kernel environment. On hardware meeting
the finding's precondition (FADT OemId "APPLE " without SMBIOS-derived kenv
entries), `kgetenv("smbios.system.product")` returns NULL exactly the same
way, and apple_fw.c:112-113 feeds that NULL into `kfreeenv()`.

Observed on the guest (console_full.log / panic.txt):

```
DF5102: enter
DF5102: product=0 biosver=0 (NULL == kenv entry absent)
DF5102: precondition met, calling kfreeenv as apple_fw.c:112-113 does -- expect panic
panic: trying to free NULL pointer
cpuid = 0
Trace ... _kfree() at _kfree+0x558
        df5102_load() at df5102_load+0x6e
        module_register_init() ... linker_load_file ...
Debugger("panic")
db>
```

The guest wedged in DDB and stopped answering ssh (`vm.sh status` → down),
satisfying the panic classification. Guest was reset with `vm.sh reset
with-src` afterwards.

## Why the driver path itself was not driven end-to-end

`apple_fw_identify` only adds a child when `apple_fw_is_apple()` sees FADT
OemId "APPLE " (apple_fw.c:58-67). The audit guest's ACPI OEM is "BOCHS "
(env.txt: `ACPI: RSDP ... (v00 BOCHS )`), and QEMU does not let the FADT
OEM ID be overridden from inside the guest. The sink (`kfreeenv(NULL)` →
`kfree(NULL)` → `_kfree` panic) is nevertheless fully proven above, and the
probe code has no NULL guard between kgetenv and kfreeenv — the chain is
source-certain.

## Attempts

1. First module used the real `smbios.system.product` name — the guest HAS
   that variable (env.txt), so kgetenv returned a valid pointer and the
   module survived. This confirmed the precondition analysis (normal
   machines with SMBIOS entries never hit the NULL path).
2. Second attempt collided with the already-loaded module name (module had
   no unload handler); rebuilt under a new module name (`df5102b`).
3. Third attempt: precondition met → panic, exactly as predicted.

## Fix validation

- Baseline: reproduced (panic above).
- Patched run: **not testable on this guest** — the driver is
  `optional apple_fw acpi` (not in X86_64_GENERIC) and its probe is gated on
  an "APPLE " FADT OEM ID which this guest cannot provide, so the fixed
  code path cannot be exercised here. The fix (`fix.diff`) is a two-line
  NULL guard; the mechanism PoC demonstrates the guarded call becomes a
  no-op (the module's own "vars present" run in attempt 1 exercised the
  non-NULL branch without harm).

## Kernel references

- sys/dev/apple/fw/apple_fw.c:107-113 (the missing NULL check)
- sys/kern/kern_environment.c:228-251 (kgetenv NULL return), 331-336 (kfreeenv unconditional kfree)
- sys/kern/kern_slaballoc.c:1407-1408 (panic on free(NULL))
- sys/kern/subr_prf.c:750-753 (ksnprintf %s NULL → "(null)" — why the ksnprintf call itself is safe)

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