# DF-2808 VERDICT — reproduced (panic / kernel heap overflow, root-gated)

**Status: reproduced.** Verified on the 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).

## How it manifests

`kldload /tmp/seg-overflow.ko` (as root) panics the guest:

```
Fatal trap 12: page fault while in kernel mode
cpuid = 3; lapic id = 3
fault virtual address  = 0xfffff80116e69000
fault code             = supervisor write data, page not present   <-- WRITE
Stopped at      memmove+0x10a:       repe movsq (%rsi),%es:(%rdi)
```

(run.log, panic.txt). The fault is a **supervisor WRITE** inside the
`memmove` that `vn_rdwr`/`uiomove` uses to copy file bytes into the module
mapping — i.e. the overflow ran off the end of the mapping while still
copying attacker file data. Everything between the end of the 1-page
kmalloc (0x1000 bytes, M_LINKER) and the faulting page
(`0xfffff80116e69000`, ≈64 KB later) was overwritten with `0x41` bytes
read from the module file before the first unmapped page stopped the copy.

## Root cause (path:line)

`sys/kern/link_elf.c:541-543`:

```c
base_vaddr  = trunc_page(segs[0]->p_vaddr);
base_vlimit = round_page(segs[1]->p_vaddr + segs[1]->p_memsz);
mapsize     = base_vlimit - base_vaddr;
```

The mapping size accounts only for segs[0]'s *start* and segs[1]'s *end*.
`segs[0]`'s own end (`p_vaddr + p_memsz`) is never checked against
`mapsize`, nor is segment ordering, nor 64-bit wraparound of
`p_vaddr + p_memsz`. `sys/kern/link_elf.c:552-564` then reads
`p_filesz` file bytes at `mapbase + p_vaddr - base_vaddr` and bzeros
`p_memsz - p_filesz` — both unbounded by the actual allocation when the
arithmetic lies.

The comment at `sys/kern/link_elf.c:496-499` documents the assumption
("We rely on there being exactly two load segments, text and data, in that
order") but nothing enforces it. This is the DF-2771 analogue
(mapsize accumulation overflow in link_elf_obj.c) expressed through THIS
file's distinct program-header-driven code path, and it is strictly more
general than DF-0056 (which needs `p_filesz > p_memsz` within one segment):
here both segments satisfy `p_filesz <= p_memsz` and the overflow still
happens because the allocation is sized from the wrong expression.

## Exploit chain (characterization)

Primitive: contiguous kernel-heap overflow with 100% file-controlled
content, overflow length and start offset chosen by the module author
(segs[0].p_filesz up to file size, segs[1] placement controlling mapsize).
Consumers of adjacent M_LINKER/slab memory at the moment of load include
previously-loaded module mappings and linker_file structures.

Honest ceiling: `kldload` is root-gated (`SYSCAP_NOKLD`,
`sys/kern/kern_linker.c` kldload syscall), so the attacker model is
"malicious root-supplied module file" — the same class as DF-0056. For
that attacker, a working .ko is already arbitrary kernel code; the bug's
operational impact is (a) kernel memory corruption/panic from a file that
should have been rejected with ENOEXEC, and (b) it defeats any
defense-in-depth scheme that sandboxes or whitelists module *content*
(e.g. signed-hash whitelisting of known modules) because memory outside
the loader's allocation is modified. No unprivileged→root chain exists
through this path (verified: kldload requires uid 0 / kern.securelevel
checks).

## Fix validation

`fix.diff` (this pack) adds, before the mapping is sized:

1. per-segment `p_filesz <= p_memsz` (completes DF-0056's missing check),
2. per-segment `p_vaddr + p_memsz` wraparound rejection,
3. `segs[0]->p_vaddr + segs[0]->p_memsz <= segs[1]->p_vaddr + segs[1]->p_memsz`
   (ordering + seg0-extent-inside-mapsize),
4. `mapsize != 0`,
5. `nsegs == 2` before any segs[] dereference.

Built as `make -j6 nativekernel KERNCONF=X86_64_GENERIC` in the guest,
installed, rebooted. Re-run of the exact PoC: rejected with
`kldload: Overlapping or out-of-order segments`, RC!=0, guest stays up,
zero panics. See fix_build.log / run_fix.log.
