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

load_section truncated-file check bypassed by 64-bit wrap of (off_t)filsz + offset

Field Value
ID DF-2720
Status new
Severity Low
CVSS 3.1 CVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:U/C:N/I:N/A:N
CWE CWE-190 Integer Overflow or Wraparound
File sys/kern/imgact_elf.c
Lines 274
Area kern
Confidence certain
Discovered 2026-08-30
Pass 2 (GLM 5.3 second pass)
Bucket base:kern
Reported pending
Known CVE none
CVE match novel

Summary

The guard (off_t)filsz + offset > vp->v_filesize is evaluated in unsigned 64-bit arithmetic regardless of the cast: p_offset=0xFFFFFFFFFFFFF000 + p_filesz=0x2000 wraps to 0x1000 and passes, so a segment is mapped file-backed at object pindex 0xFFFFFFFFFFFFF (~2^52 pages past EOF). Reproduced: exec succeeds with no "truncated ELF file", the wrapped range appears in the process core, touching it SIGSEGVs cleanly, the anon tail zero-fills. No panic/leak/corruption on the INVARIANTS guest β€” hardening-grade bypass of the loader's file-bounds invariant.

-   if ((off_t)filsz + offset > vp->v_filesize || filsz > memsz) {
+   if (offset + filsz < offset || offset + filsz > vp->v_filesize ||
+       filsz > memsz) {

Timeline

  • 2026-08-30 Discovered during pass-2 audit of imgact_elf.c (GLM 5.3); bypass reproduced same run.

Discussion (0)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-2720 Β· 16 files
FileTypeDescriptionSize
make_wrapseg.py β€” 1.9 KB view raw
make_wrapseg2.py β€” 1.4 KB view raw
wrapseg β€” 8.0 KB ↓ download
wrapseg2 β€” 8.0 KB ↓ download
wrapseg3 β€” 8.0 KB ↓ download
wrapseg4 β€” 8.0 KB ↓ download
ctl_norm β€” 8.0 KB ↓ download
wrap_tail β€” 8.0 KB ↓ download
wrap_mid β€” 8.0 KB ↓ download
run.log β€” 248 B view raw
core_phdrs.txt β€” 402 B view raw
fix.diff β€” 458 B view raw
verdict.json β€” 2.3 KB view raw
VERDICT.md β€” 1.3 KB ↓ raw
README.md β€” 1.9 KB ↓ raw
env.txt β€” 268 B view raw

DF-2720 β€” load_section file-size check bypassed by 64-bit wrap ((off_t)filsz + offset)

  • File: sys/kern/imgact_elf.c:274
  • Severity: Low (check bypass; no memory-unsafety demonstrated)
  • Confidence: certain (bypass reproduced; impact assessed none)
  • Class: CWE-190 integer overflow / hardening

Root cause

if ((off_t)filsz + offset > vp->v_filesize || filsz > memsz)   /* imgact_elf.c:274 */

offset is vm_offset_t (unsigned 64-bit), so the addition is computed modulo 2^64 regardless of the (off_t) cast. With p_offset = 0xFFFFFFFFFFFFF000 and p_filesz = 0x2000 the sum wraps to 0x1000, which is ≀ file size β†’ the "truncated ELF file" check passes and the segment is mapped file-backed with object pindex 0xFFFFFFFFFFFFF000 >> 12 (~2^52 pages past EOF).

Reproduce

python3 make_wrapseg2.py  # t2_wrapfile / t2_wrapanon + controls
# guest: /tmp/t2_wrapfile ; echo $?   -> 139 (SIGSEGV)
#        /tmp/t2_wrapanon ; echo $?   -> 65   (anon tail reads zero)
# controls: t2_readfile -> 66, t2_readanon -> 65, t2_readtext -> 66

Exec succeeds (no "elf_load_section: truncated ELF file" uprintf β€” contrast with a genuinely-too-large p_filesz, which prints it), the wrapped file-backed range is present in the map (visible in the process core: vaddr=0x600000 filesz=0x2000), and touching it SIGSEGVs (vm_fault on a vnode page far beyond EOF fails instead of zero-filling). The anon tail maps and reads as zero.

Impact

None demonstrated beyond a self-inflicted SIGSEGV: the pager handles the beyond-EOF pindex gracefully on this guest (no INVARIATS panic, no leak, no corruption). The check exists precisely to prevent walking "off the end of the file object" β€” the wrap defeats it, so this is defense-in-depth.

Fix

Detect the wrap:

-   if ((off_t)filsz + offset > vp->v_filesize || filsz > memsz) {
+   if (offset + filsz < offset || offset + filsz > vp->v_filesize ||
+       filsz > memsz) {
VERDICT.md
↓ download raw

DF-2720 VERDICT

Status: reproduced (check bypass confirmed) β€” impact: none (no memory unsafety) β€” confidence: certain.

What was run

t2_* family of crafted ELF64 binaries on the INVARIANTS guest, uid 1001:

  • t2_exit77 β†’ rc=77 (harness sanity: entry/exit path correct, DF SYS_exit=1)
  • t2_readtext β†’ rc=66 (read own text, nonzero)
  • t2_readfile β†’ rc=66 (valid file-backed PT_LOAD part reads real bytes)
  • t2_readanon β†’ rc=65 (valid anon bss tail reads zero)
  • t2_wrapfile β†’ rc=139 β€” PT_LOAD with p_offset=0xFFFFFFFFFFFFF000, p_filesz=0x2000: exec succeeded (check bypassed β€” no "truncated ELF file"), the range [0x600000,0x602000) IS mapped file-backed (seen in the core: type=1 flags=6 vaddr=0x600000 filesz=0x2000), and reading it SIGSEGVs.
  • t2_wrapanon β†’ rc=65 β€” the wrapped segment's anon tail maps and reads zero, proving load_section processed the segment fully.

Impact assessment

No kernel panic (checked serial log), no leak, no corruption. The beyond-EOF vnode pindex faults cleanly through vm_fault β†’ SIGSEGV. The finding stands as a hardening/robustness issue: the truncated-file check is bypassable and the documented invariant ("fail so vm_map doesn't walk off the end of the file object", imgact_elf.c:265-270) is violated.

No exploit chain; no fix-validation build performed (impact none).

Fix verification

not_testable
baseline reproduced→ patch + rebuild →patched clean

Impact none; no fix-validation build performed (hardening only).

[]
↓ fix.diffper-fix-DF-2720

Confirmed kernel references

Detail

Evidence (decisive lines)

['findings/poc/DF-2720/run.log β€” t2_* matrix: wrapfile rc=139, wrapanon rc=65, all controls correct (exit77=77, readtext=66, readfile=66, readanon=65)', 'findings/poc/DF-2720/core_phdrs.txt β€” wrapped segment present in core at vaddr=0x600000 filesz=0x2000', 'VERDICT.md β€” narrative']

PoC changes

First asm harness used Linux syscall numbers (SYS_exit=60) producing SIGSYS noise; rebuilt with DF numbers (SYS_exit=1) and exit-code protocol (65=zero read, 66=nonzero, 139=read fault)

Verified recommended fix

Check for wrap explicitly: if (offset + filsz < offset || offset + filsz > vp->v_filesize || filsz > memsz) -> ENOEXEC

Verdict

The truncated-file check '(off_t)filsz + offset > vp->v_filesize' (imgact_elf.c:274) is computed in unsigned 64-bit arithmetic: p_offset=0xFFFFFFFFFFFFF000 + p_filesz=0x2000 wraps to 0x1000 and passes. Exec succeeds (no 'truncated ELF file'), the segment is mapped file-backed at object pindex 0xFFFFFFFFFFFFF (~2^52 pages past EOF, confirmed present in the process core), the anon tail maps and reads zero, and touching the wrapped file-backed range SIGSEGVs cleanly. No panic, leak, or corruption on the INVARIANTS guest - hardening-grade check bypass only.