# DF-5344 — VERDICT

**Status: reproduced (OOB read executed by the live guest kernel).**
Impact: `leak`-class primitive (bounded by link layout); potential write
overflow of `cpu_model[128]` in worse layouts. No uid0 path — the corrupted
object is a fixed static string; control is limited to kernel .bss contents
that follow `cpu_brand` until the first NUL.

## What was proven, on what

1. **Hostile input constructible**: QEMU 7.2 `-cpu qemu64,model-id=<48 chars>`
   is accepted and copied into CPUID leaves 0x80000002..4 *without* a
   terminating NUL (proven by the kernel observing 48 non-NUL bytes).
2. **Live kernel OOB read**: booting the stock guest kernel
   (DragonFly 6.5-DEVELOPMENT #0 Thu Jul 2 2026, X86_64_GENERIC) with that
   brand yields `hw.model` == exactly 48 'A's (49 bytes with newline), read
   by an unprivileged uid=1001 user (`run.2.log`, `boot-brand48.log:32`).
   In-spec brand strings always contain a NUL within bytes 0..47, so a
   48-character result is only possible if `strcpy(cpu_model, brand)`
   (identcpu.c:164) read its terminating byte **past `cpu_brand[47]`** —
   an out-of-bounds read of kernel static memory, executed at boot.
3. **Fault-localized harness**: `brand_guard.c` (exact algorithm replica,
   buffer flush against a PROT_NONE page) faults precisely at `brand[48]`
   for both the `strcpy` path (mode 0) and the space-skip loop path
   (mode 1); the 47-char control survives (mode 2). Run on the guest with
   base cc 8.3 (`run.log`).
4. **Layout analysis** (`nm /boot/kernel/kernel`): `hw_clockrate` (zero at
   that instant — set only at identcpu.c:169, after the copy) immediately
   follows `cpu_brand`, so on this specific build the OOB walk stops after
   1 byte: no bytes are disclosed and the 50-byte write stays inside
   `cpu_model[128]`. The defect is nonetheless real and layout-dependent:
   any non-zero neighbor at boot time flows into world-readable
   `sysctl hw.model`; ≥80 contiguous non-NUL bytes overflow `cpu_model`.

## Why it matters

CPUID brand leaves are attacker-controlled in every VM (hypervisor/operator)
and firmware-controlled on bare metal. The kernel must not run unbounded
string operations over them. The guard-page harness shows both consumption
sites (:161 loop, :164 strcpy) read past the buffer.

## Fix validation

`fix.diff` forces `cpu_brand[47] = '\0'` after the CPUID fill loop.
Validated by rebuilding the guest kernel with only this change and re-running
the identical 48-char-brand boot: baseline kernel → `hw.model` = 48 bytes
(OOB); patched kernel → `hw.model` = 47 bytes (in-bounds truncation), guest
boots normally. See `fix_build.log`, `fix_run.log`.

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