# DF-3032 — ext2_rename leaks IN_RENAME on the source directory when the
# relookup race resolves the from-name to a different inode

**Severity:** Low (race-window logic bug; permanent rename-EPERM on one directory)
**Class:** CWE-667 (improper locking) / state-machine leak
**File:** `sys/vfs/ext2fs/ext2_vnops.c:1025-1031` (set at `:760`)
**Confidence:** speculative (code path is certain; the race is hard to win)

## Summary

`ext2_rename` sets `IN_RENAME` on the source directory inode before dropping
its lock (`ext2_vnops.c:760`), and clears it exactly once at the end of step 3
— but **only inside the `xp == ip` branch**:

```c
/* ext2_vnops.c:1025-1031 */
	if (xp != ip) {
		/*
		 * From name resolves to a different inode.  IN_RENAME is
		 * not sufficient protection against timing window races
		 * so we can't panic here.
		 */
	} else {
		...
		xp->i_flag &= ~IN_RENAME;      /* :1079 — only cleared here */
	}
```

The `bad:`/`out:` unwinds also clear it (`:1094`, `:1098`), but the
**successful** `xp != ip` path falls straight through to the vputs at
`:1081-1085` and returns 0 with `IN_RENAME` still set on `ip`.  Until that
inode is evicted from the cache, every subsequent `rename(2)` touching the
directory as source fails `EINVAL` at the `:755` check — a permanent,
unprivileged-triggerable (given the race) logic DoS on one directory.

Reference behavior: DFly's ufs takes the same branch at
`sys/vfs/ufs/ufs_vnops.c:1199-1203` and panics for directories
(`panic("ufs_rename: lost dir entry")`); ext2's port chose not to panic but
leaked the flag instead.

Note the adjacent `ext2_inc_nlink(ip)` at `:781` is *not* leaked on this
path — step 2 already created a real second link for `ip`, so the +1 is the
correct accounting; only the flag is stranded.

## Race requirements (why speculative)

To reach `xp != ip` with `doingdirectory`, a concurrent operation must replace
the source directory-entry slot between the `ext2_direnter`/`ext2_dirrewrite`
of step 2 and the `relookup(fdvp, &fvp, fcnp)` at `:976` (e.g. a second rename
whose *target* is the same from-name — `IN_RENAME` only blocks renames of `ip`
itself, `:755`).  Window is narrow; not demonstrated on the guest.

## Fix

Clear the flag on the original inode when the entry was lost:

```diff
--- a/sys/vfs/ext2fs/ext2_vnops.c
+++ b/sys/vfs/ext2fs/ext2_vnops.c
@@ -1025,6 +1025,9 @@
 	if (xp != ip) {
 		/*
 		 * From name resolves to a different inode.  IN_RENAME is
 		 * not sufficient protection against timing window races
 		 * so we can't panic here.
 		 */
+		if (doingdirectory)
+			ip->i_flag &= ~IN_RENAME;
 	} else {
```

## Verification status

Skipped by policy (Low, speculative race; no runnable trigger without a
dedicated multi-thread race harness).  Static proof above; see `VERDICT.md`.
