# DF-2216 — VERDICT

**REPRODUCED — verbatim-code, kernel-compiled rig; leak quantified with the
kernel's own malloc accounting; fix A/B-validated in the same kernel.**
Impact: unbounded kernel-heap (M_KENV) exhaustion — availability only.
No privesc route (see below).

## Rig class and why

Runtime `kldload` of drm GPFs on this audit guest, so the established
kernel-compile rig class was used (same as the DF-2212 ttm harness kernel):
one custom `KERNCONF DFV22X` (= stock `X86_64_GENERIC` + `device dfverify`)
compiles `dev/dfverify/df_22x_rig.c`, which `#include`s the production
`sys/dev/drm/linux_dmi.c` **verbatim** (`build.sh` aborts unless the guest
tree file sha256 equals the audited-tree hash `c0f7f7a5...`). The function
under test in the kernel is byte-for-byte the production `dmi_match()`.

## Line-accurate confirmation

`sys/dev/drm/linux_dmi.c:35-67`: each of the four switch arms
(`DMI_SYS_VENDOR` :44, `DMI_BOARD_VENDOR` :49, `DMI_PRODUCT_NAME` :54,
`DMI_BOARD_NAME` :59) does `s = kgetenv(...)` and `return true` or falls
through — **no `kfreeenv()` anywhere**. `kgetenv()` (kern_environment.c:229)
`kmalloc(len, M_KENV, M_WAITOK)`s a fresh copy of the value (:242) whenever the
dynamic kernel environment is active (production default; active on the guest —
`kenv smbios.system.maker` = QEMU). Every in-tree consumer pairs the two
(`kgetenv_string()` kern_environment.c:369 does `kfreeenv(tmp)`); `dmi_match()`
is the sole defector. Sibling DMI helper `dmi_found()` (drm_drv.c:1329-1336)
frees all four fields — the idiom this function forgot.

## Mechanical reproduction (decisive run, run.log)

The rig fabricates the kenv boundary (100-char values for all four smbios.*
keys via `ksetenv`), then measures `Σ malloc_type.ks_use[cpu].memuse` for
M_KENV — the exact aggregation the kernel itself uses (kern_slaballoc.c:451):

| phase | result |
|---|---|
| CONTROL: 16384 paired `kgetenv`+`kfreeenv` | **delta = 0** (calls +16384) |
| BASELINE: 16384 real `dmi_match()` (match/mismatch arms alternated) | **delta = 1,703,936 B = 104 B/call**, both return paths leak |
| single-shot per arm (4 arms) | **+104 B each** |
| FIXED (`fix.diff` applied, renamed def) 16384 calls | **delta = 0** |

104 = slab-chunk-quantized 101-byte request (`strlen(value)+1`). The finding is
confirmed line-accurately: per-call, per-arm, both return paths, unbounded.

### Meter engineering note (recorded for reproducibility)

The first iteration double-counted `ks_loosememuse` — an **up-only** fold
counter that `kfree()` never decrements (kern_slaballoc.c:1136-1140). The
telltale artifact (`init:+131104` = one 131072-byte fold + 32) identified it;
the corrected meter sums only `ks_use[].memuse` and the control/fixed loops
went to exactly 0. First-iteration log kept as `run.first_meterbug.log`.

## Fix validation (A/B, same kernel)

`fix.diff` frees `s` with `kfreeenv()` on every path of all four arms (and
widens `s` to `char *`). The fixed function was extracted from a
`fix.diff`-applied scratch copy of the same tree bytes (sha chain in
`build.sh`/env.txt) and compiled into the same kernel as `dmi_match_fixed`.
Result: identical call counts, identical return values (`matchret=1
mismatchret=0` in both), **memuse delta 0** → leak eliminated, behavior
otherwise unchanged. `fix_status: fixed`.

## Reachability (who can actually trigger this)

Sole in-tree caller: `radeon_atom_pick_dig_encoder()`
(atombios_encoders.c:2251), gated by `ASIC_IS_DCE32(rdev)` — radeon DCE3.2
(HD4000-class) hardware. Invoked during encoder assignment (display init,
DP/HDMI hotplug, mode sets) — reachable by a user holding the DRI device node
(`/dev/dri/cardN`). On such a system each event leaks ~104 B of M_KENV with no
bound. On the audit guest: no GPU → no runtime trigger; the defect is proven
through the verbatim code path (dynamic kenv real, values fabricated).
Quantified ceiling: `104 B × events` — kenv-heap exhaustion after
~10^7 events (GBs) — a sustained-abuse DoS, matching Low severity.

## Privesc assessment (standing directive)

None. The bug is a pure allocation leak: no overread, no overwrite, no
use-after-free, no double-free, nothing copied to userspace. The leaked memory
is never re-referenced. There is no primitive from which to build an
escalation chain; impact is availability only.

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