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

ext2_rename strands IN_RENAME on the source directory inode when the step-3 relookup race resolves the from-name to a different inode β€” directory permanently unrenameable (EINVAL) until reclaim

Field Value
ID DF-3032
Status new
Severity Low
CVSS 3.1 CVSS:3.1/AV:L/AC:H/PR:L/UI:N/S:U/C:N/I:N/A:L
CWE CWE-667
File sys/vfs/ext2fs/ext2_vnops.c
Lines 1025-1031 (set :760; clear sites :1079/:1094/:1098; check :755)
Area vfs/ext2fs
Confidence speculative
Discovered 2026-09-02
Pass 2 (GLM 5.3 second pass)
Bucket base:vfs
Reported pending
Known CVE none
CVE match novel

Summary

ext2_rename sets ip->i_flag |= IN_RENAME on a directory source before unlocking it. The flag is cleared only in the xp == ip branch and on the bad:/out: unwinds. The xp != ip lost-race branch (:1025-1031) is deliberately empty ('we can't panic here', contrasting with UFS which panics for directories) and returns success with IN_RENAME still set on ip β€” every future rename of that directory then fails EINVAL at the :755 check until the inode is reclaimed. The adjacent inc_nlink(ip) is correctly accounted on this path (step 2 created a real second link); only the flag leaks. A concurrent rename targeting the same from-name slot between :781 and the relookup at :976 strands the flag β€” a race-triggered, unprivileged, per-directory logic DoS with no memory-safety consequence. Not built (race, Low/speculative). Fix: clear the flag on the original inode in the lost-race branch (row diff).

Timeline

  • 2026-09-02 Discovered during pass-2 audit of ext2_vnops.c (GLM 5.3).

Discussion (0)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-3032 Β· 5 files
FileTypeDescriptionSize
README.md β€” 2.7 KB ↓ raw
VERDICT.md β€” 1.7 KB ↓ raw
fix.diff β€” 384 B view raw
manifest.json β€” 1.2 KB view raw
verdict.json β€” 1.4 KB view raw

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:

/* 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:

--- 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.

VERDICT.md
↓ download raw

DF-3032 β€” VERDICT

Status: untested (skipped by policy β€” Low severity, speculative race) Defect: present in code (certain). Exploitability: speculative (race window).

Static proof

  • ext2_vnops.c:749-763: for a directory source, ip->i_flag |= IN_RENAME (:760) before vn_unlock(fvp) (:799).
  • ext2_vnops.c:976: relookup(fdvp, &fvp, fcnp) can return a different inode (xp = VTOI(fvp) at :1012) than the ip the flag was set on.
  • ext2_vnops.c:1025-1031: the xp != ip branch is empty by design ("we can't panic here" β€” contrasting with sys/vfs/ufs/ufs_vnops.c:1199-1203 which panics for directories).
  • :1079 (xp->i_flag &= ~IN_RENAME) executes only in the else (xp == ip) branch; :1094/:1098 execute only via bad:/out:. The xp != ip success path returns at :1086 with the flag still set.
  • Consumer of the stale flag: ext2_vnops.c:754-755 β€” every future rename with this directory as source returns EINVAL until the inode is reclaimed (ext2_reclaim frees the in-core inode).

Impact ceiling

One directory per won race becomes unrenameable until unmount/reboot. No memory-safety consequence (flag only gates the :755 EINVAL and the :755 family of checks). On-disk state stays consistent; hence Low/speculative.

Why no guest run

Winning the race requires a purpose-built harness (two CPUs renaming into the same slot in a tight loop) and even then success is probabilistic; for a Low logic bug this is out of proportion. If desired, the fix can be validated by code inspection: the one-line clear is sufficient because nothing else reads IN_RENAME between :760 and the return.

fix.diff β€” clear IN_RENAME on ip in the xp != ip branch when doingdirectory.

Fix verification

not_testable
↓ fix.diffper-fix-DF-3032

Confirmed kernel references

Detail

Evidence (decisive lines)

['findings/poc/DF-3032/VERDICT.md β€” static path proof', 'findings/poc/DF-3032/fix.diff']

PoC changes

n/a β€” no PoC seed; static-proof pack only

Verified recommended fix

Clear IN_RENAME on ip in the xp != ip branch when doingdirectory (one-line flag clear before the final vputs).

Verdict

Code defect certain (IN_RENAME is cleared only on the xp==ip branch at ext2_vnops.c:1079; the xp!=ip lost-race success path strands the flag on the source directory until reclaim, making all future renames of it EINVAL at :755), exploitability speculative (requires winning a concurrent-rename race into the from-name slot between :781 and the relookup at :976). One-directory logic DoS, no memory-safety impact. Verification skipped by policy (Low/speculative).