β¬’ DragonFlyBSD Kernel Audit
← triage Β· dashboard
DF-0056

Heap overflow via unchecked p_filesz > p_memsz in PT_LOAD segment loading

Field Value
ID DF-0056
Status new
Severity Medium
CVSS 3.1 CVSS:3.1/AV:L/AC:L/PR:H/UI:N/S:U/C:H/I:H/A:H
CWE CWE-787 Out-of-bounds Write
File sys/kern/link_elf.c
Lines 507-563
Area kern
Confidence certain
Discovered 2026-06-29
Reported pending

Summary

link_elf_load_file never validates p_filesz <= p_memsz for PT_LOAD segments. The kmalloc'd buffer (mapsize) is derived from p_memsz (:541-543), but vn_rdwr (:554) writes p_filesz bytes into it. If p_filesz > p_memsz, the write overflows. Immediately after, bzero(segbase + p_filesz, p_memsz - p_filesz) (:562-563) underflows (both Elf64_Xword/uint64) to a huge value, zeroing far past the allocation. Attacker controls both the overflow size and the data written (file bytes at p_offset). Root-only (SYSCAP_NOKLD); the cleanest heap-overflow primitive in the ELF loaders.

Root cause

sys/kern/link_elf.c:

case PT_LOAD:                     /* :507  no p_filesz <= p_memsz check */
    segs[nsegs] = phdr;
...
mapsize = round_page(segs[1]->p_vaddr + segs[1]->p_memsz) - trunc_page(segs[0]->p_vaddr); /* :543 */
ef->address = kmalloc(mapsize, M_LINKER, M_WAITOK);  /* :546 */
...
vn_rdwr(UIO_READ, vp, segbase, segs[i]->p_filesz, ...); /* :554  writes p_filesz */
bzero(segbase + segs[i]->p_filesz, segs[i]->p_memsz - segs[i]->p_filesz); /* :562 underflow */
--- a/sys/kern/link_elf.c
+++ b/sys/kern/link_elf.c
@@ -507,6 +507,11 @@
    case PT_LOAD:
+       if (phdr->p_filesz > phdr->p_memsz) {
+       link_elf_error("p_filesz > p_memsz");
+       error = ENOEXEC;
+       goto out;
+       }
        if (nsegs == 2) {

Timeline

  • 2026-06-29 Discovered during automated file-by-file audit of sys/kern/link_elf.c.
  • pending Reported to DragonFlyBSD security contact.

Discussion (0)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-0056 Β· 12 files
FileTypeDescriptionSize
craft_ko.py trigger-source builds ELF .ko with p_filesz>p_memsz 4.3 KB view raw
df0056_overflow.ko trigger-source prebuilt malicious module 12.2 KB ↓ download
build.sh build-script run craft_ko.py 550 B view raw
run.sh run-script kldload ./df0056_overflow.ko (root) 425 B view raw
panic.txt panic-signature baseline page fault in memmove 725 B view raw
fix.diff suggested-fix reject p_filesz > p_memsz in PT_LOAD 581 B view raw
fix_run.log run-log patched kernel: ENOEXEC reject, guest up 328 B view raw
fix_dmesg.txt dmesg patched-kernel dmesg showing reject message 839 B view raw
env.txt environment uname, cc version 232 B view raw
VERDICT.md verdict full narrative + bright-line analysis 2.6 KB ↓ raw
../fix_build_combined.log build-log Combined 41-finding kernel build (rc=0, -Werror clean) 5.6 MB ↓ download
../fix_build_summary.txt build-summary Summary of the combined 41-finding kernel build 826 B view raw
VERDICT.md verdict full narrative + bright-line analysis
↓ download raw

DF-0056 β€” Heap overflow via unchecked p_filesz > p_memsz in ELF loader

Verdict: REPRODUCED (kernel heap overflow -> panic); root-only (defense-in-depth)

link_elf_load_file() (sys/kern/link_elf.c:388-...), the kernel-module loader invoked by kldload(2), sizes its buffer from p_memsz but writes p_filesz bytes into it β€” with no p_filesz <= p_memsz validation:

link_elf.c:541-543   base_vlimit = round_page(segs[1]->p_vaddr + segs[1]->p_memsz);
                     mapsize     = base_vlimit - base_vaddr;
link_elf.c:546       ef->address = kmalloc(mapsize, M_LINKER, M_WAITOK);   /* sized by memsz */
link_elf.c:554-556   vn_rdwr(UIO_READ, vp, segbase, segs[i]->p_filesz, ...);  /* writes filesz */
link_elf.c:562-563   bzero(segbase + p_filesz, p_memsz - p_filesz);           /* underflow! */

If p_filesz > p_memsz: 1. vn_rdwr writes p_filesz bytes into a buffer sized for p_memsz β†’ heap overflow of p_filesz - round_page(...p_memsz...) bytes; 2. bzero(segbase + p_filesz, p_memsz - p_filesz) β€” the length is negative, wraps to a huge size_t β†’ massive zero-fill past the buffer.

Reproduction (root only)

craft_ko.py builds a minimal ET_DYN ELF with a PT_LOAD segment where p_memsz=0x100, p_filesz=0x2000. kldload as root:

baseline (#0):  Fatal trap 12: page fault while in kernel mode
                fault virtual address = 0xfffff8011891f000   (supervisor WRITE, page not present)
                Stopped at memmove+0x10a: repe movsq (%rsi),%es:(%rdi)

The page fault is in memmove (the bcopy inside vn_rdwr) writing p_filesz bytes past the mapsize-sized heap allocation β€” the overflow is real and the bytes (0x41 from the file) are attacker-controlled.

Why this is NOT an unpriv->root escalation (bright-line rule)

sys_kldload (kern_linker.c:794) gates on caps_priv_check_self(SYSCAP_NOKLD) β€” root only. There is no unprivileged path to this write; it is a root->kernel hardening gap. The primitive (controlled heap overflow + underflow bzero) is genuine, but the privilege boundary to cross (root -> kernel) is already game-over, so it does not yield uid=0 from an unprivileged user. Reported as panic/corruption for the default kernel.

Fix (validated on a built single-fix kernel)

fix.diff adds if (phdr->p_filesz > phdr->p_memsz) { ENOEXEC; goto out; } in the PT_LOAD case, before the segment is recorded. Validated end-to-end:

patched (#1):  kldload: p_filesz > p_memsz in PT_LOAD
               KLD_RC=1, module NOT loaded, guest stays UP

vs the baseline panic above.

Fix verification

fixed

validated

see evidence pack
↓ fix.diffDragonFly 6.5-DEVELOPMENT #1: Fri Jul 17 14:28:56 UTC 2026

Confirmed kernel references

β€”

Detail

Exploit chain

none

Evidence (decisive lines)

β€”

Verdict

REPRODUCED (live panic). link_elf p_filesz>p_memsz -> heap overflow. Root-only (SYSCAP_NOKLD).