hammer2_vop_nrename masks backend ENOENT as success and commits rename bookkeeping (tip nlink--/DELETING, iparent/name_key rewrite, cache_rename) against an unrenamed media topology
Summary
When hammer2_xop_nrename cannot find the source dirent (a race the backend itself documents: 'XXX shouldn't happen, but does under fsstress' -> ENOENT), the frontend converts ENOENT to success and still performs every success side-effect although the backend changed nothing on media: rewrites ip->meta.name_len/name_key and iparent, runs the unlink finisher on the overwrite target tip β decrementing its persistent nlinks and marking it ISUNLINKED/DELETING while its dirent survives β and commits cache_rename, diverging namecache from media. rename_stress 4-thread churn 120s did not hit the window (verdict not_reproduced, race-bound trigger, code path certain). Filesystem metadata corruption: tip drivable to nlinks 0/DELETING while still reachable; stale iparent/name_key (broken '..' after remount). Remove-the-masking fix proposed.
Discussion (0)
PoC verification
Evidence pack
findings/poc/DF-5820 Β· 3 files| File | Type | Description | Size | |
|---|---|---|---|---|
| rename_stress.c | β | 1.3 KB | view raw | |
| README.md | β | 2.4 KB | β raw | |
| verdict.json | β | 2.4 KB | view raw |
DF-5820 β hammer2_vop_nrename masks backend ENOENT as success and commits the rename bookkeeping anyway
What this pack contains
rename_stress.cβ 4-thread rename churn harness used to try to hit the documented backend ENOENT race (120 s total on the guest; no hit).- Static analysis below (code path is certain; the trigger is the race the backend itself documents: hammer2_xops.c:621 "XXX shouldn't happen, but does under fsstress").
Code path (certain)
- Backend
hammer2_xop_nrenamefails to find the source dirent and feedsHAMMER2_ERROR_ENOENT(sys/vfs/hammer2/hammer2_xops.c:620-628). - Frontend
hammer2_vop_nrenameconverts it to success: error = hammer2_error_to_errno(error); ... if (error == ENOENT) error = 0; (hammer2_vnops.c:2165-2166) - With
errornow 0 the frontend still performs every success side effect although the media topology was NOT renamed (the backend bailed before deleting the source dirent and before touching the target): - rewritesip->meta.name_len/name_key(:2174-2179), - rewritesip->meta.iparent = tdip->meta.inum(:2180-2183), - runshammer2_inode_unlink_finisher(tip, &vprecycle)on the overwrite target (:2194-2196) β decrementing tip's persistent nlinks and marking it ISUNLINKED/DELETING while its dirent still exists on media, - commitscache_rename()(:2247-2248), so the namecache diverges from the media until reclaim/remount.
Divergence consequences: link-count corruption on tip (an inode can be
driven to nlinks 0 / DELETING while still reachable through its surviving
dirent), stale iparent/name_key on ip (breaks .. resolution after
remount), and namespace divergence (source name resolvable on media after
reboot but not in the live cache).
Stress attempt result
/tmp/rename_stress /dfpoc (4 threads, 30 s + 90 s) on the hammer2 root
filesystem produced zero xop_nrename: ... ENOENT console messages β the
race window was not hit; the finding remains code-certain but
runtime-unproven on this guest (status not_reproduced).
Fix
Propagate the ENOENT instead of masking it: delete the if (error ==
ENOENT) error = 0; at hammer2_vnops.c:2165-2166 (or restrict the mask to
the exact benign sub-case, if one is ever identified, and skip all
:2174-2251 success side effects when the backend reports ENOENT).
Fix verification
not_testablenot_testable: trigger is a rare backend lookup race (upstream 'fsstress' class); not hit in 120s of targeted churn.
dmesg grep count 0 after both stress runs.
Confirmed kernel references
Detail
Evidence (decisive lines)
README.md full path:line trace; rename_stress.c harness; stress attempt produced 0 backend-ENOENT console hits in 120s.
PoC changes
wrote a 4-thread rename-churn harness (rename_stress.c) to try to hit the documented race; window not hit on a single-master guest.
Verified recommended fix
Do not mask ENOENT from the rename xop: remove 'if (error == ENOENT) error = 0;' at hammer2_vnops.c:2165-2166 so the VOP returns the error and skips all success side-effects.
Verdict
hammer2_vop_nrename converts the rename-backend's ENOENT (source dirent not found; the backend itself documents this happens under fsstress, hammer2_xops.c:621-628) into success at hammer2_vnops.c:2165-2166 and then performs every success side-effect on a media topology that was NOT renamed: rewrites ip->meta.name_len/name_key (:2174-2179) and ip->meta.iparent (:2180-2183), runs hammer2_inode_unlink_finisher on the overwrite target tip (:2194-2196, decrementing its persistent nlinks and marking it ISUNLINKED/DELETING while its dirent survives), and commits cache_rename (:2247-2248), diverging namecache from media. Code path certain; 120s of 4-thread rename churn on the guest did not hit the underlying race (zero 'xop_nrename: ... ENOENT' console messages), so it stays runtime-unproven here.
No comments yet.