# DF-2809 — "Unreadable program headers" check is warning-only → unbounded OOB read via e_phoff/e_phnum + first-page parsing never bounded by bytes actually read

## Build
```
python3 gen_exec_ko.py phdr-wild.ko  phdr-wild    # 64-byte file, e_phoff = 0x300000000000
python3 gen_exec_ko.py phdr-short.ko phdr-short    # 64-byte file, e_phoff=0x40, e_phnum=2 (phdrs live past EOF)
```

## Run
```
scp -F dfbsd-qemu/config phdr-wild.ko phdr-short.ko dfbsd:/tmp/
dfbsd-qemu/vm.sh run_root 'kldload /tmp/phdr-wild.ko; echo RC=$?'
dfbsd-qemu/vm.sh log 45
```

## Expected (stock kernel, verified 2026-09-01)
```
kldload: Unreadable program headers        <-- the check FIRES...
Fatal user address access from kernel mode from kldload at ffffffff80627802
Fatal trap 12: page fault while in kernel mode
fault virtual address  = 0x2801170e3000
Stopped at      link_elf_load_file+0x242:  movl (%rax),%edx   <-- ...and is ignored
```
phdr-short: `kldload: Unreadable program headers` then a decision made from
UNINITIALIZED heap ("Object is not dynamically-linked" on the observed run —
the phdr entries past the 64 file bytes came from stale M_LINKER heap).

## Expected (patched kernel)
```
kldload: Unreadable program headers, RC=1 (ENOEXEC), guest stays up
```

## Why (root cause, sys/kern/link_elf.c)
```c
449:  firstpage = kmalloc(PAGE_SIZE, M_LINKER, M_WAITOK);     /* NOT M_ZERO */
451:  vn_rdwr(UIO_READ, vp, firstpage, PAGE_SIZE, 0, ..., &resid);
453:  nbytes = PAGE_SIZE - resid;                             /* bytes actually read */
...
489:  if (!((hdr->e_phentsize == sizeof(Elf_Phdr)) &&
490:        (hdr->e_phoff + hdr->e_phnum*sizeof(Elf_Phdr) <= PAGE_SIZE) &&
491:        (hdr->e_phoff + hdr->e_phnum*sizeof(Elf_Phdr) <= nbytes)))
492:      link_elf_error("Unreadable program headers");        /* NO error=, NO goto */
...
500:  phdr = (Elf_Phdr *) (firstpage + hdr->e_phoff);          /* unbounded */
505:      switch (phdr->p_type) {                               /* wild read */
```
The if-body at 492 contains ONLY the kprintf — the missing
`error = ENOEXEC; goto out;` (present in FreeBSD's link_elf.c) makes both
the `<= PAGE_SIZE` and the `<= nbytes` conditions decorative:
- `e_phoff` (64-bit, file-controlled) is added to the firstpage pointer and
  dereferenced with no bound → arbitrary-offset kernel read (the
  `phdr-wild` panic).
- For short files, everything past `nbytes` in the kmalloc'd page is
  uninitialized heap (no M_ZERO) and is parsed as Ehdr/phdr content
  (`phdr-short`), so load decisions are driven by stale kernel heap.
