# DF-0834: ufs_checkpath infinite loop on crafted cyclic `..` directory entries

**Severity:** Medium — local DoS via crafted UFS/FFS image (uninterruptible kernel hang)
**CWE:** CWE-835 (Infinite Loop / Loop with Unreachable Exit Condition)
**Status:** REPRODUCED on unpatched `#0`; FIX VALIDATED (single-fix kernel rebuild).

This is the **UFS analog of DF-0824** (ext2_checkpath) — identical bug class,
identical fix pattern. The two checkpath routines are line-for-line twins.

## Summary

`ufs_checkpath()` (`sys/vfs/ufs/ufs_lookup.c:1130-1166`) walks a target
directory's `..` parent chain to verify that the source of a rename is not an
ancestor of the target. The walk is a `for(;;)` loop whose only exits are:

| Line | Exit condition |
|------|----------------|
| 1131 | `vp->v_type != VDIR` (ENOTDIR) |
| 1138 | `vn_rdwr` I/O error |
| 1148-1150 | malformed `..` name (ENOTDIR) |
| 1154 | `dotdot_ino == source->i_number` (EINVAL) |
| 1158 | `dotdot_ino == rootino` (reached root) |
| 1162 | `VFS_VGET` error |

**There is NO depth limit, NO cycle-visited tracking, and NO signal-pending
check.** A crafted UFS image where directory A's `..` points to B and B's `..`
points back to A (neither being the source being renamed nor the root inode)
makes the loop alternate A→B→A→B→… forever, each iteration calling `vget()` /
`vput()` on the two cached vnodes.

Such cyclic `..` entries are **impossible to create online** — `mkdir`/`rename`
always set `..` to the true parent and hard-linking directories is forbidden —
but trivially created offline with a byte-patch of the `..` inode field
(`dotdot_ino`, offset 12 in a directory's first data block per `struct
dirtemplate`, `sys/vfs/ufs/dir.h:136`). The threat model is a **malicious UFS
image mounted by an admin** (or a user if `vfs.usermount=1` + a root-created
image owned by the user), then triggered by renaming a directory into the
cyclic parent.

## Reproduce

```sh
# on the DragonFlyBSD guest as root, in this directory:
./build.sh           # compiles patch_ufs (the image byte-patcher)
./craft_image.sh     # builds df0834.img: newfs + mkdir S/A/B + forge A<->B cycle
./run.sh             # mount + `mv S A/S_moved` -> ufs_checkpath infinite loop
```

| Kernel | Result |
|--------|--------|
| Unpatched `#0` | `mv S A/S_moved` hangs forever; kernel thread at ~100% CPU, SIGKILL-proof, vnode pinned |
| Fixed kernel (`UFS_CHECKPATH_MAXDEPTH=64`) | `mv: rename S to A/S_moved: Invalid argument`; returns in 0s |

See `VERDICT.md` for the full mechanism, `run.log` for baseline proof,
`fix_run.log` for the fixed-kernel proof, and `fix.diff` for the patch.
