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)
PoC verification
Evidence pack
findings/poc/DF-3032 Β· 5 files| File | Type | Description | Size | |
|---|---|---|---|---|
| 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.
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) beforevn_unlock(fvp)(:799).ext2_vnops.c:976:relookup(fdvp, &fvp, fcnp)can return a different inode (xp = VTOI(fvp)at:1012) than theipthe flag was set on.ext2_vnops.c:1025-1031: thexp != ipbranch is empty by design ("we can't panic here" β contrasting withsys/vfs/ufs/ufs_vnops.c:1199-1203which panics for directories).:1079(xp->i_flag &= ~IN_RENAME) executes only in theelse(xp == ip) branch;:1094/:1098execute only viabad:/out:. Thexp != ipsuccess path returns at:1086with the flag still set.- Consumer of the stale flag:
ext2_vnops.c:754-755β every future rename with this directory as source returnsEINVALuntil the inode is reclaimed (ext2_reclaimfrees 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.
Recommended fix
fix.diff β clear IN_RENAME on ip in the xp != ip branch when
doingdirectory.
Fix verification
not_testableConfirmed 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).
No comments yet.