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)
PoC verification
Evidence pack
findings/poc/DF-3108 Β· 9 files| File | Type | Description | Size | |
|---|---|---|---|---|
| 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.
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_testableFix 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.
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.
No comments yet.