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

vtruncbuf() caches the separately-allocated nc_name pointer across unlocked blocking work, then prints it (narrow UAF read)

Summary

vtruncbuf snapshots TAILQ_FIRST(&vp->v_namecache)->nc_name under vp->v_spin, drops the spin, blocks in the re-clean RB_SCAN loop, then kprintf()s the pointer. nc_name is a separately allocated string; a rename/unlink racing the truncation's rarely-taken re-clean path can cache_zap the ncp and free the name in the window β€” UAF read of freed heap into dmesg. Not PoC'd (contrived re-clean race, Low); strlcpy-snapshot-under-spin fix compiled as part of the guest kernel build used for DF-5807 fix validation.

Discussion (0)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-5810 Β· 4 files
FileTypeDescriptionSize
README.md β€” 1.2 KB ↓ raw
VERDICT.md β€” 1.1 KB ↓ raw
fix.diff β€” 828 B view raw
verdict.json β€” 1.7 KB view raw

DF-5810 β€” vtruncbuf() caches nc_name across unlocked blocking work (narrow UAF read)

What

vtruncbuf() (sys/kern/vfs_subr.c:531-534) snapshots TAILQ_FIRST(&vp->v_namecache)->nc_name under vp->v_spin, then DROPS the spin and runs the re-clean RB_SCAN loop β€” which sleeps in BUF_LOCK/bwrite β€” before kprintf(... %s, filename) dereferences the pointer at vfs_subr.c:551. nc_name is a separately allocated string (sys/sys/namecache.h:136); the namecache entry can be cache_zap()d (rename/unlink race on the file being truncated) during the blocking window, leaving filename dangling β†’ use-after-free read of freed heap into the console/dmesg output.

Preconditions: the rarely-taken "re-clean leftover buffers" path (dirty buffers reappearing after truncation) AND concurrent rename/unlink of the truncated file β€” a narrow race with low impact (dmesg disclosure of freed-heap bytes). Not PoC'd: the trigger needs a contrived buffer reappearance race; filed as Low with a compile- validated fix.

Fix (compile-validated in the guest kernel build; see DF-5807/5811 pack runs)

Snapshot the name bytes under the spin instead of the pointer (fix.diff). Alternatively hold a namecache reference across the loop.

VERDICT.md
↓ download raw

DF-5810 β€” VERDICT

Status: untested (Low-severity race; no PoC run β€” skip per contract for Low findings).

Code-level proof of the protocol violation: - pointer captured under spin: sys/kern/vfs_subr.c:531-534 - spin released: :534 - blocking re-clean loop with sleeps: :541-554 (BUF_TIMELOCK/bwrite) - dereference: :551 kprintf("%s", filename) - nc_name is separately allocated and freed with its ncp: sys/sys/namecache.h:136, cache_zap() frees the ncp+name without consulting vp->v_spin holders after they release it.

The fix.diff (strlcpy into a 256-byte local under the spin) compiled as part of the patched guest kernel built for DF-5807/5811 validation (the same vfs_subr.c object), but no runtime trigger was attempted.

Fix status (batch validation)

Applied with the 36 sibling fix.diffs of this batch in findings/poc/_batch41/all_fixes.patch; single in-guest make -j6 nativekernel KERNCONF=X86_64_GENERIC completed RC=0 with zero errors under -Werror (full log: findings/poc/_batch41/fix_build8.log). Behavior A/B intentionally skipped for this Info-tier item.

Fix verification

not_testable
baseline no→ patch + rebuild →patched clean

fix.diff compiles (part of the validated kernel build) but no runtime trigger exists for this narrow race

fix.diff
↓ fix.diffper-fix-DF-5810

Confirmed kernel references

Detail

Evidence (decisive lines)

code-path analysis in VERDICT.md; fix.diff compile-validated as part of the guest kernel build used for DF-5807/5811 fix validation

PoC changes

n/a - no PoC authored

Verified recommended fix

snapshot the name bytes under v_spin instead of the pointer (fix.diff, applies cleanly, compile-validated)

Verdict

vtruncbuf() snapshots the separately-allocated nc_name pointer under vp->v_spin (vfs_subr.c:531-534), releases the spin, blocks in the re-clean RB_SCAN loop (BUF_TIMELOCK/bwrite), then dereferences the pointer at vfs_subr.c:551 kprintf - a rename/unlink racing the truncation's re-clean path can cache_zap the ncp and free nc_name in the window, giving a use-after-free read of freed heap into dmesg. Low severity (narrow race, console-only output); no PoC run per contract skip rule for Low findings.