DF-2746 / verdict.json
{ "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" ] } |