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

ufs_dirbad panic()s a RW-mounted UFS on any mangled directory entry β€” one flipped byte = reboot-loopable kernel panic (UFS instance of ext2's DF-3083)

Summary

ufs_dirbad() panics whenever the lookup walk finds an entry failing the forward-progress check on a non-MNT_RDONLY mount (:608-609, reached from :269 for any entry with d_reclen==0 or exceeding the DIRBLKSIZ remainder - attacker-controlled disk bytes). One flipped byte in any directory block of a mounted image makes the first name resolution inside that directory panic the kernel; re-fires on every subsequent access, reboot-loopable when the image is part of boot/automount. Untrusted/corrupted UFS image mounted RW; ANY user resolving a name inside the corrupted directory panics the box ('ls' alone does not trigger - VOP_READDIR uses ufs_readdir's lenient walk; a lookup inside the directory does). VERIFIED: flip d_reclen 12->516, mount RW, stat inside the dir -> 'panic: ufs_dirbad: bad dir' with ufs_dirbad<-ufs_lookup<-vop_nresolve backtrace, guest down; plus 3 incidental reproductions during DF-3106 PoC development. Fix: downgrade to report-only (the walk already skips forward past mangled entries); same direction as DF-3083.

Discussion (0)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-3108 Β· 9 files
FileTypeDescriptionSize
patch_one.c β€” 2.1 KB view raw
repro.sh β€” 1.5 KB view raw
run.log β€” 198 B view raw
panic.txt β€” 2.6 KB view raw
env.txt β€” 608 B view raw
README.md β€” 2.1 KB ↓ raw
VERDICT.md β€” 2.0 KB ↓ raw
verdict.json β€” 3.0 KB view raw
manifest.json β€” 829 B view raw

DF-3108 β€” ufs_dirbad() panics a RW-mounted UFS on any mangled directory entry

What

601: ufs_dirbad(struct inode *ip, doff_t offset, char *how)
608:    if ((mp->mnt_flag & MNT_RDONLY) == 0)
609:        panic("ufs_dirbad: bad dir");

ufs_lookup()'s walk calls this (:269, "mangled entry"; also :435, "i_size too small") whenever an entry fails the forward-progress check β€” e.g. d_reclen == 0 or d_reclen > DIRBLKSIZ - (off & (DIRBLKSIZ-1)), i.e. one flipped byte in any directory block. On a filesystem mounted read-write the kernel panics instantly.

This is the UFS instance of the fail-stop pattern filed for ext2 as DF-3083 (ext2_dirbad): mounting an untrusted/corrupted image and doing a single path lookup inside the affected directory reboots the machine; the panic re-triggers on every boot script / user that touches the mount, making it reboot-loopable when the crafted image is involved in boot or autofs.

Reproduce

cc -O2 -o patch_one patch_one.c     # flips /d's first d_reclen: 12 -> 516
./repro.sh                          # newfs image, corrupt 1 byte, mount RW,
                                     # stat /mnt/df3108/d/x  -> walk inside /d

Observed (guest, stock INVARIANTS kernel; also re-confirmed on the DF-3106-patched kernel β€” independent bug):

/dev/vn7: bad dir ino 3 at offset 0: mangled entry
panic: ufs_dirbad: bad dir
ufs_dirbad() at ufs_dirbad+0x4b
ufs_lookup() at ufs_lookup+0x47a
vop_old_lookup() ... vop_nresolve()

Note: ls alone does NOT trigger it (VOP_READDIR uses ufs_readdir's lenient walk) β€” resolving a name inside the corrupted directory does. Three additional incidental reproductions were captured during DF-3106's PoC development (mangled entries at offsets 168/500/508 in vn-mounted RW images; identical panic signature).

Fix direction

Downgrade the RW case from panic() to a printf + error return (the walk already skips forward past mangled entries), matching the direction upstream discussions have taken for ext2_dirbad (DF-3083): a corrupted filesystem image must not be able to reboot the machine once mounted.

VERDICT.md
↓ download raw

DF-3108 β€” VERDICT

Status: reproduced. Impact: panic (reboot-loopable DoS from a crafted/corrupted UFS image mounted RW). Confidence: certain.

  • Root cause: sys/vfs/ufs/ufs_lookup.c:608-609 β€” ufs_dirbad() panics whenever the lookup walk (:264-274) finds an entry failing the forward-progress check on a non-MNT_RDONLY mount. One flipped byte in any directory block suffices.
  • Deliberate repro: patch_one flips /d's first entry d_reclen 12 -> 516 (516 > 512 - 0); stat /mnt/df3108/d/x (name resolution INSIDE /d) -> walk flags "mangled entry" -> panic (run.log, panic.txt; guest down). Reconfirmed on the kernel patched for DF-3106 β€” independent bug.
  • Incidental corroboration during DF-3106 PoC development: three more mangled-entry panics at offsets 168/500/508 from vn-mounted RW crafted images, identical signature (ufs_dirbad <- ufs_lookup <- vop_nresolve).
  • Threat: mounting an untrusted image (root or vfs.usermount=1); the first name lookup inside the corrupted directory by ANY user panics the kernel; panic re-fires on every subsequent touch (reboot-loopable when the image is part of boot/automount flows).
  • Not fixed by DF-3106's fix.diff (different code path); the deliberate repro above ran ON the DF-3106-patched kernel and still panicked.
  • Fix: replace panic() with a non-fatal report in ufs_dirbad's RW case (the walk already continues past mangled entries); aligns with DF-3083's ext2 direction. Not validated in this run (kernel-build budget spent on DF-3106's mandatory memcorrupt fix validation); trivial to re-run with the same harness after any candidate patch.

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.

(No fix.diff for this item: documented recommendation only - see Fix above.)

Fix verification

not_testable
baseline reproduced→ patch + rebuild →patched clean

Fix not built this run (kernel-build budget spent on DF-3106's mandatory memcorrupt fix validation); the repro harness is trivially re-runnable against a candidate ufs_dirbad de-panicking patch. Note the deliberate repro ran ON the DF-3106-patched kernel and still panicked β€” independent code path.

per-fix-DF-3108

Confirmed kernel references

Detail

Exploit chain

crafted/corrupted UFS image -> mount RW -> any user resolves a name inside the corrupted directory -> ufs_lookup walk flags 'mangled entry' -> ufs_dirbad -> panic('ufs_dirbad: bad dir') -> reboot; re-fires on every subsequent access

Evidence (decisive lines)

["findings/poc/DF-3108/panic.txt: '/dev/vn7: bad dir ino 3 at offset 0: mangled entry' + 'panic: ufs_dirbad: bad dir' + ufs_lookup backtrace", 'findings/poc/DF-3108/run.log: one-byte flip (reclen 12 -> 516), mount, stat inside /d, guest down']

PoC changes

Initial trigger used ls (VOP_READDIR uses ufs_readdir's lenient walk β€” no panic); changed to resolving a name inside the corrupted directory (stat /mnt/df3108/d/x) which drives ufs_lookup's walk.

Verified recommended fix

In ufs_dirbad(), do not panic on RW mounts: report and return (the walk already skips forward past mangled entries), matching the DF-3083 direction for ext2_dirbad.

Verdict

Reproduced deterministically: one flipped byte (d_reclen 12 -> 516) in a directory of a vn-mounted RW UFS image makes the first name resolution inside that directory panic the kernel via ufs_dirbad (ufs_lookup.c:608-609). Deliberate repro on the guest captured 'panic: ufs_dirbad: bad dir' with ufs_dirbad <- ufs_lookup <- vop_nresolve backtrace; reconfirmed on the DF-3106-patched kernel (independent code path), plus three incidental identical panics during DF-3106 PoC development (offsets 168/500/508). Threat: untrusted/corrupted image mounted RW (root or vfs.usermount=1); reboot-loopable. UFS instance of the ext2_dirbad fail-stop pattern filed as DF-3083.