DF-4796 / verdict.json
{ "finding_id": "DF-4796", "status": "reproduced", "reproduced": 1, "impact": "leak", "confidence": "certain", "verdict": "Reproduced with a KLD that calls the ports exported reservation_object_get_fences_rcu() on a crafted reservation object (live excl fence, shared list with shared_max=64M entries so the retry krealloc asks for 512MB) under buffer-cache memory pressure (free 242MB/97MB in two windows): each NOWAIT-fail/WAITOK-success event permanently leaks one exclusive-fence reference \u2014 fence refcount 1->2 and 2->3 across two independent windows, never returning to baseline (df4796: LEAK CONFIRMED, run.log); balanced runs whenever free memory exceeds the request, exactly gating the leak on linux_reservation.c:365-374s GFP_KERNEL slow-path continue, which drops the reference acquired at :356-357. Unprivileged-reachable kernel heap leak under allocation pressure via every get_fences_rcu caller with pfence_excl!=NULL (amdgpu_vm, i915 gem busy/eof, ttm execbuf) \u2014 the pressure-gated sibling of DF-4772s per-call tail leak. Fix (dma_fence_put before continue) validated on a rebuilt kernel (#3): identical pressure windows yield base=1 after=1 leaked=0 while the same free<request relationship holds.", "exploit_chain": "", "evidence": "run.log (kern refcount growth across two pressure windows: base=1 after=2, base=2 after=3, LEAK CONFIRMED twice; balanced runs when free>sz); VERDICT.md (baseline + fixed narrative); fix.diff; env.txt", "kernel_refs": [ "sys/dev/drm/linux_reservation.c:356", "sys/dev/drm/linux_reservation.c:365", "sys/dev/drm/linux_reservation.c:369", "sys/dev/drm/linux_reservation.c:373" ], "poc_changes": "Built from scratch (no seed): df4796.c KLD crafting the reservation object with the ports own primitives (kzalloc/dma_fence_init/reservation_object_init + hand-installed fobj with shared_max=64M) and watching kref_read before/after; memory pressure from a 3GB hammer2 buffer-cache fill (reclaimable pages give the NOWAIT-fail/WAITOK-succeed asymmetry); kldload drm required first for the shim symbols.", "attempts": 4, "guest_uname": "DragonFly dfbsd 6.5-DEVELOPMENT #2/#3 baseline/fixed kernels (X86_64_GENERIC)", "runtime_sec": 3000, "guest_dirty": 0, "build_cmd": "sh build.sh (make -m /usr/share/mk KMODDIR=/root/poc/DF-4796 with drm include paths)", "run_cmd": "sh run.sh (dd 3072MB cache fill; kldload drm; kldload df4796.ko; sysctl kern.df4796.max=67108864; sysctl kern.df4796.run=1)", "code_hash": "a361a2a940b0f7970c47469881db5ec9c6490246761ab1a45f76c125640ce5c6", "notes": "Medium severity: requires allocation failure (memory pressure) so the leak rate is bounded by pressure events, but is permanent per event and unbounded in total; same unpriv reachability surface as DF-4772 (REPRODUCED per-call leak) whose else-put fix does NOT cover this path. Distinct code path (mid-loop continue vs tail). Guest currently on kernel #3 carrying both validated fixes; final vm.sh reset with-src restores stock.", "recommended_fix": "dma_fence_put(fence_excl) before the continue in reservation_object_get_fences_rcus GFP_KERNEL slow path (fix.diff, validated)", "fix_status": "fixed", "fix_kernel_uname": "DragonFly dfbsd 6.5-DEVELOPMENT #3: Wed Sep 23 23:05:43 UTC 2026 root@dfbsd:/usr/obj/usr/src/sys/X86_64_GENERIC x86_64", "fix_baseline_reproduced": 1, "fix_patched_reproduced": 0, "fix_verdict": "Baseline: two permanent leak events across two pressure windows (refcount 1->2->3). Kernel #3 rebuilt with fix.diff (put before continue): identical pressure procedure yields base=1 after=1 leaked=0 in both windows (free memory 297MB/324MB vs 512MB request \u2014 the same free<request relationship that deterministically failed NOWAIT at baseline).", "fix_evidence": "VERDICT.md; run.log; buildkernel3.log RC=0; fix.diff" } |