# DF-2771 — VERDICT

**REPRODUCED (panic / uncontrolled kernel-memory write, root-gated).**

## Root cause (sys/kern/link_elf_obj.c)

The sizing loop at `sys/kern/link_elf_obj.c:621-633` accumulates every
SHT_PROGBITS/SHT_NOBITS `sh_size` into a plain `size_t mapsize` with **no
overflow check and no per-section cap**:

```c
621:	for (i = 0; i < hdr->e_shnum; i++) {
622:		if (shdr[i].sh_size == 0)
623:			continue;
624:		switch (shdr[i].sh_type) {
625:		case SHT_PROGBITS:
626:		case SHT_NOBITS:
627:			alignmask = shdr[i].sh_addralign - 1;
628:			mapsize += alignmask;
629:			mapsize &= ~alignmask;
630:			mapsize += shdr[i].sh_size;      /* <-- u64 wrap */
```

`sh_size` is an attacker-chosen Elf64_Xword taken verbatim from the module's
section headers. With e_shnum ≤ 65535 sections, any total ≥ 2^64 requires at
least one section ≥ 2^48, so a wrap necessarily leaves a monster section whose
load-side length is used raw:

- `:640-641` `vm_object_allocate(..., round_page(mapsize) >> PAGE_SHIFT)` — sized from the **wrapped** total (0x1010 → 2 pages);
- `:663-667` `vm_map_find(kernel_map, ..., round_page(mapsize), ...)` — 2-page wired mapping;
- `:733-760` per-section load: NOBITS → `bzero(ef->progtab[pb].addr, shdr[i].sh_size)` with the **raw** 2^64-0x1000 length.

The consistency panic at `:812-815` cannot save this — the wild `bzero` runs
first (and the placement arithmetic itself stays self-consistent under wrap).

Sibling unchecked consumers of the same field on the same path: `:734-738`
(vn_rdwr into the mapping for PROGBITS — file-backed, so not practical past
EOF) and the alignment truncation `alignmask = sh_addralign - 1` (`int`),
which was analyzed and found to either stay within the mapping or land on
`mapbase == 0` → caught by the NULL check at `:727-730` (ENOSPC).

## Reproduction (baseline, stock kernel)

Guest: `DragonFly dfbsd 6.5-DEVELOPMENT #0: Thu Jul  2 06:02:54 UTC 2026 …
X86_64_GENERIC x86_64`, securelevel -1. Trigger module built by
`gen_module.py df2771.ko mapsize-wrap` (host python3; no guest toolchain
needed): 7 sections — NULL, .text(16B PROGBITS), .symtab(1 sym), .strtab,
.shstrtab, NOBITS sh_size=0xFFFFFFFFFFFFF000 align=1, NOBITS sh_size=0x2000
align=1.

`kldload /tmp/df2771.ko` → ssh session hangs; serial console:

```
Fatal trap 12: page fault while in kernel mode
fault virtual address = 0xffffffff82602000     <- first page past the 2-page mapping
fault code = supervisor write data, page not present
Stopped at memset+0xd5: repe stosq             <- the wild bzero
```

(panic.txt, full excerpt of the decisive run.)

## Primitive characterization (why impact stops at panic)

The write is a **zero-fill of unbounded length starting inside the module's
own 2-page mapping, running forward until the first unmapped page**. The
attacker controls start (mapbase+section offsets, sub-page granularity) but
neither the written value (always 0x00) nor a stop point: the kernel_map
region above a freshly-allocated module mapping is unmapped in practice
(proven by the immediate fault at the mapping edge, VA 0xffffffff82602000),
so the deterministic outcome is a fatal supervisor-write page fault. Had a
live mapping been adjacent above the module, its contents would be zeroed
first (blind corruption), but the run still terminates in the same fault —
there is no route from this primitive to controlled execution, and the
triggering user is already root (kldload is SYSCAP_NOKLD-gated,
kern_linker.c:792, and blocked at securelevel>0, kern_linker.c:789). No leak
and no unprivileged reach — uid0 escalation is out of scope for this class
(the loader input is root-supplied), so the run is honestly closed at
`impact=panic`.

## Fix validation

`fix.diff` (this pack; identical in DF-2771/2772/2773 packs — one combined
patch): adds `LINKER_MAX_SECSIZE (1LL<<44)` cap checked in the sizing loop →
ENOEXEC "section too large" before any allocation. With e_shnum ≤ 65535 the
accumulator can then never exceed ~2^60.3, so wrap is impossible.

Applied to the guest's /usr/src copy (sys/ tree untouched in the repo),
`make -j6 nativekernel KERNCONF=X86_64_GENERIC` (build.log, 37903 lines,
-Werror clean, `--- link_elf_obj.o ---`), `make installkernel`, reboot into
`#1: Tue Sep  1 01:04:57 UTC 2026`. Re-run of the exact same PoC:

```
kldload: /tmp/df2771.ko: section too large    (dmesg)
RC=1, guest stays up, 0 panics in console log
```

Baseline panic gone on the patched kernel → fix_status=fixed.

## Negative space checked on the same path (no findings)

- `nbytes = e_shnum * e_shentsize` (:497): e_shentsize==64 is enforced
  before kmalloc; max 65535*64 fits int.
- Huge-but-unwrapped mapsize: fails cleanly in vm_map_find → ENOMEM.
- sh_addralign==0 / 2^32 truncation: mapbase clamps to 0 → caught by the
  NULL-addr check at :727-730 (ENOSPC).
- vm_object refcount handling across wiring failure/unload (:646-673,
  :900-910): correct.
