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

vn_read has no VDIR gate: read() on a directory fd is dispatched to VOP_READ, and ffs_read explicitly accepts VDIR, exposing raw directory blocks (deleted-entry residue) instead of returning EISDIR

Field Value
ID DF-2756
Status new
Severity Low
CVSS 3.1 CVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:U/C:L/I:N/A:N
CWE CWE-668 Exposure of Resource to Wrong Sphere
File sys/kern/vfs_vnops.c
Lines 749-754 (ffs acceptance ufs_readwrite.c:81-85)
Area kern
Confidence certain
Discovered 2026-08-30
Pass 2 (GLM 5.3 second pass)
Bucket kernleak
Reported pending
Known CVE none
CVE match novel

Summary

vn_read performs no v_type check, and ffs_read treats VDIR as legal (else if (vp->v_type != VREG && vp->v_type != VDIR) panic), so on UFS-family mounts read() on a directory fd returns raw on-disk directory blocks β€” violating POSIX's mandatory EISDIR and disclosing inter-entry slack space (d_reclen padding) containing residue of removed entries (names, inode numbers) from before the reader existed. On-disk data residue, not kernel memory. hammer2 and tmpfs reject dir reads (verified), so impact requires a UFS/ext2 mount.

Add if (vp->v_type == VDIR) return (EISDIR); at the common layer in vn_read() before taking the vnode lock (mirrors FreeBSD), and/or make ffs_read return EISDIR for VDIR.

Timeline

  • 2026-08-30 Discovered during pass-2 audit of vfs_vnops.c (GLM 5.3).

Discussion (0)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-2756 Β· 4 files
FileTypeDescriptionSize
dirread2.c β€” 1.7 KB view raw
dirread.c β€” 1.9 KB view raw
run.log β€” 872 B view raw
fix.diff β€” 1.3 KB view raw

Fix verification

fixed
baseline reproduced→ patch + rebuild →patched clean

Patched kernel: vn_read returns EISDIR for VDIR descriptors - read() on /boot now returns 0 bytes (error) instead of raw directory blocks; the deleted-entry residue path is closed.

dirread2 on patched kernel: total read = 0, residue hits = 0 (baseline: 1024 bytes, 8 residue hits)
↓ fix.diffDragonFly dfbsd 6.5-DEVELOPMENT #1: Sat Oct 24 17:14:55 UTC 2026 root@dfbsd:/usr/obj/usr/src/sys/X86_64_GENERIC x86_64 (v26 fix-all kernel; sys_generic.c fix-v2 rebuild pending A/B)

Confirmed kernel references

Detail

Exploit chain

unpriv open(/boot) -> read() -> raw UFS dir blocks incl. deleted-entry residue (names + inodes of removed files)

Evidence (decisive lines)

dirread2.c; df2756_dirread.log: read=1024, 8x ZZsecretsalt residue hits in dirent slack

PoC changes

first parser used 8-byte dirent header; UFS uses fileno u32/reclen u16/namlen u8/type u8 - fixed

Verified recommended fix

--- sys/kern/vfs_vnops.c 2026-06-29 12:51:19.000000000 +0000

Verdict

Reproduced as an unprivileged user on the stock guest. vn_read (sys/kern/vfs_vnops.c:729-759) performs no v_type check, and UFS has no native vop_nresolve (lookups go through vop_compat) while ffs_read accepts VDIR - so read() on a directory descriptor of the /boot UFS mount returns raw on-disk directory blocks instead of EISDIR (first run: read(/boot) = 512 bytes of dirent data; full drain 1024 bytes). After root created and deleted eight files named ZZsecretsalt[1-8].file, the unprivileged reader found the deleted names 8 times in the raw dirent slack (offsets 468..688), disclosing deleted-entry names and inode numbers of files that existed before the reader had any access. This is on-disk residue disclosure (POSIX EISDIR violation), not kernel memory. hammer2 and tmpfs gate directory read (verified by the audit); the bug is live on UFS-family mounts.