# DF-2809 VERDICT — reproduced (wild kernel read panic; nbytes never enforced)

**Status: reproduced.** Stock guest kernel
`DragonFly 6.5-DEVELOPMENT #0 Thu Jul 2 06:02:54 UTC 2026 X86_64_GENERIC`,
fresh `vm.sh reset with-src` before the run.

## Evidence

### phdr-wild (deterministic panic)
```
login: kldload: Unreadable program headers
Fatal user address access from kernel mode from kldload at ffffffff80627802
Fatal trap 12: page fault while in kernel mode
cpuid = 4
fault virtual address  = 0x2801170e3000
fault code             = supervisor read data, page not present
Stopped at      link_elf_load_file+0x242:       movl    (%rax),%edx
```
(run.log, panic.txt). The 64-byte module sets `e_phoff = 0x300000000000`.
The message ordering on the serial console is the smoking gun: the loader
*printed* "Unreadable program headers" (the check condition is TRUE:
`0x300000000000 + 1*56 > PAGE_SIZE`) and then walked straight into
`phdr = firstpage + 0x300000000000` and dereferenced it. firstpage
(0xfffffe1170e3000) + 0x300000000000 wrapped to the user-range address
0x2801170e3000 → "Fatal user address access from kernel mode".

`link_elf_load_file+0x242` is `switch (phdr->p_type)` at
sys/kern/link_elf.c:505.

### phdr-short (uninitialized heap parsed as phdrs)
64-byte file with `e_phoff=0x40, e_phnum=2`: the check fires again
(`0x40 + 2*56 = 0xb0 > nbytes = 0x40`) and loading continues, parsing both
phdr entries out of the kmalloc'd-but-never-written tail of `firstpage`
(allocated WITHOUT M_ZERO at sys/kern/link_elf.c:449). Observed outcome:
`kldload: Object is not dynamically-linked` — a decision derived purely
from stale heap contents (nondeterministic across heap states; on a groomed
heap the stale bytes can equally produce PT_LOAD/PT_DYNAMIC entries feeding
the segs[] machinery). `nbytes` (sys/kern/link_elf.c:453) is computed and
used in the dead check only — it never bounds any parsing.

## Root cause
sys/kern/link_elf.c:489-492 — the if body is only `link_elf_error(...)`.
FreeBSD's link_elf.c has `error = ENOEXEC; goto out;` in this block;
DragonFly is missing them, disabling BOTH protections the condition
expresses (in-page bounds and short-file/bytes-read bounds).

## Impact
Root-gated (kldload) crafted-module kernel read at an attacker-chosen 64-bit
offset → panic (DoS). The uninit-heap parse additionally makes module
acceptance depend on stale kernel memory (non-deterministic loads). Low
severity per the project's root-supplied-input rubric (cf. DF-0060 Low).

## Fix validation
fix.diff adds the missing `{ error = ENOEXEC; goto out; }`. Patched kernel:
`kldload /tmp/phdr-wild.ko` → "kldload: Unreadable program headers",
RC=1, guest stays up. phdr-short likewise rejected. See run_fix.log.
