DragonFlyBSD Kernel Audit
DF-2746 / verdict.json
← back to finding ↓ download raw
{
  "finding_id": "DF-2746",
  "status": "reproduced",
  "reproduced": 1,
  "impact": "none",
  "confidence": "certain",
  "verdict": "Reproduced as unprivileged user on the stock INVARIANTS X86_64_GENERIC kernel: both byte-vector maintenance loops in sys_mincore() (gap-fill vm_mmap.c:920-927, tail-fill vm_mmap.c:961-968) execute subyte(vec + lastvecindex, 0) with lastvecindex == -1, so whenever the first kernel-reported page is not page 0 of the scanned range the kernel stores one NUL byte at vec[-1], one byte below the user-supplied pointer. Stable across 3 runs; both trigger shapes (fully-unmapped range; leading hole) hit. Impact rigorously bounded at none-for-privilege: std_subyte rejects addresses >= VM_MAX_USER_ADDRESS (support.s:632-634), so no kernel-memory write exists; the corruption stays inside the caller's own user address space (value always 0 or a MINCORE_* nibble). Secondary manifestation: vecindex/lastvecindex are int while OFF_TO_IDX() yields vm_pindex_t, so ranges >= 2^31 pages (>= 8 TiB of the 128 TiB user space) truncate the index and redirect writes by +/-2 GiB (still user-space only). Filed Low: POSIX contract violation with in-process single-byte corruption adjacent to the caller-chosen vec. fix.diff (write vec + lastvecindex + 1; widen indices to vm_pindex_t) validated by full in-guest kernel rebuild: baseline VULNERABLE(2)/exit 0 vs patched not-vulnerable/exit 2 with vec[-1] sentinels intact and correct mincore data.",
  "exploit_chain": "No escalation chain exists. Attacker controls vec placement, so the kernel-mediated write (value 0x00, one byte below vec, or MINCORE nibbles at truncated +/-2GiB offsets) lands only in the calling process's own user memory - memory the same process can already write directly. std_subyte's VM_MAX_USER_ADDRESS check blocks any kernel-range target (verified support.s:632-634). Ceiling = silent in-app corruption of the byte preceding the vector (e.g. heap chunk metadata/length prefix the app did not expose to the attacker).",
  "evidence": [
    "findings/poc/DF-2746/run.log - baseline stock kernel: 'vec[-1] = 0x00 (must still be 0x5A)' both tests, RESULT: VULNERABLE (2), unpriv user, 3 runs",
    "findings/poc/DF-2746/run.fixed.log - patched kernel #1 (2026-08-31 15:19:46): sentinels intact 0x5A, vec[0]=0x00/vec[1]=0x1f correct, RESULT: not vulnerable, exit 2",
    "findings/poc/DF-2746/fix_build.info - patched-kernel uname -a + build/install completion stamps",
    "findings/poc/DF-2746/fix.diff - 4 hunks: subyte(vec + lastvecindex + 1, 0) in both loops + vm_pindex_t indices",
    "sys/vm/vm_mmap.c:791,829,920-927,932,961-968 - int indices, -1 sentinel, both buggy write sites",
    "sys/platform/pc64/x86_64/support.s:625-644 - std_subyte user-range check that bounds the primitive to user space"
  ],
  "kernel_refs": [
    "sys/vm/vm_mmap.c:791",
    "sys/vm/vm_mmap.c:829",
    "sys/vm/vm_mmap.c:920",
    "sys/vm/vm_mmap.c:921",
    "sys/vm/vm_mmap.c:932",
    "sys/vm/vm_mmap.c:961",
    "sys/vm/vm_mmap.c:962",
    "sys/platform/pc64/x86_64/support.s:632"
  ],
  "poc_changes": "Seed PoC written fresh for this finding (none existed). Final version includes <stdlib.h> (first build warned about implicit exit), two trigger shapes (tail-fill and gap-fill loops), adjacent-page sentinel placement to observe the byte below vec, and exit-code contract (0=vulnerable, 2=fixed).",
  "attempts": 1,
  "guest_uname": "DragonFly dfbsd 6.5-DEVELOPMENT DragonFly 6.5-DEVELOPMENT #0: Thu Jul  2 06:02:54 UTC 2026 root@dfbsd:/usr/obj/usr/src/sys/X86_64_GENERIC x86_64 (baseline) / #1: Mon Aug 31 15:19:46 UTC 2026 (patched)",
  "runtime_sec": 2700,
  "guest_dirty": 0,
  "build_cmd": "cc -O -o /tmp/mincore_oob /tmp/mincore_oob.c",
  "run_cmd": "/tmp/mincore_oob   (as unprivileged user via vm.sh run_user)",
  "code_hash": "0a5968d3c5c38648affac134a4ea4296fc0f7cf80902fa2534bbee3cf24d87b8",
  "notes": "Pass-2 audit of sys/vm/vm_mmap.c; this was the only new finding (known DF-0949/DF-0950 not re-reported). Negative results with weight: kern_mmap/vm_mmap wrap math (size+=pageoff / round_page / 'size < ulen') is overflow-safe on all paths; MAP_FIXED/TRYFIXED bounds enforce VM_MIN/MAX_USER_ADDRESS and the top-2MB AMD-bug sentinel is exactly VM_MAX_USER_ADDRESS=0x7FFFFFFFE00000; vm_map_hint + vm_map_findspace wrap-check alignment and length; MADV asymmetry (madvise behav >= MADV_CONTROL_END vs mcontrol >) is intentional since MADV_CONTROL_END==MADV_SETMAP==11 and SETMAP requires an existing VM_MAPTYPE_VPAGETABLE entry (vm_map.c:2393), which needs vm.vkernel_enable; munlockall's RB_FOREACH is safe because vm_fault_unwire never drops the map lock (vm_fault.c:2658-2698) unlike vm_fault_wire (vm_fault.c:2624-2625, the DF-0949 mechanism); mlock/munlock are root-only on all platforms because pmap_wired_count no longer exists anywhere in the tree (dead #ifdef, RLIMIT_MEMLOCK branch unreachable, rlimit TOCTOU dead code); sys_vmspace_mmap skips the RLIMIT_VMEM check for foreign vmspaces but vmspace_create is gated on vm.vkernel_enable (vm_vmspace.c:155, default 0); mincore round_page(len) wrap for len near 2^64 is a silent no-op success (no writes, correctness only); sys_munlock is missing the post-round 'size < uap->len' wrap check (unwires a smaller range; correctness only, root-only); negative mmap offsets are accepted and become huge unsigned foff (correctness, pindex lookups miss); mmap at address 0 is permitted (no mmap_min_addr) - upstream stance, hardening note only.",
  "recommended_fix": "In sys_mincore() write at vec + lastvecindex + 1 in the gap-fill and tail-fill zero loops and widen vecindex/lastvecindex to vm_pindex_t (see fix.diff).",
  "fix_status": "fixed",
  "fix_kernel_uname": "DragonFly dfbsd 6.5-DEVELOPMENT #1: Mon Aug 31 15:19:46 UTC 2026 root@dfbsd:/usr/obj/usr/src/sys/X86_64_GENERIC x86_64",
  "fix_baseline_reproduced": 1,
  "fix_patched_reproduced": 0,
  "fix_verdict": "vm.sh reset with-src; patch -p1 applied clean (4/4 hunks) to guest /usr/src; make -j4 nativekernel KERNCONF=X86_64_GENERIC completed 15:23:54 UTC; installkernel completed 15:27:04 UTC; rebooted into #1. Exact same PoC on patched kernel: vec[-1] sentinels intact (0x5A), correct vec contents (0x00 unmapped / 0x1f in-core), RESULT: not vulnerable, exit 2. Baseline vs patched delta is exactly the bug.",
  "fix_evidence": [
    "findings/poc/DF-2746/run.log (baseline VULNERABLE) vs run.fixed.log (patched not vulnerable)",
    "findings/poc/DF-2746/fix_build.info (patched uname + build/install stamps)",
    "findings/poc/DF-2746/fix.diff"
  ]
}