Re: [syzbot] [hfs?] WARNING in hfsplus_unlink

From: Edward Adam Davis
Date: Sun Nov 24 2024 - 10:33:47 EST


After the rename syscall fails, the unlink syscall triggers a warning in
drop_nlink() because the i_nlink value is 0.

When unlink succeeds but rename_cat fails in rename, the i_nlink and flags
values of the inode of the new dentry should be restored.

#syz test

diff --git a/fs/hfsplus/dir.c b/fs/hfsplus/dir.c
index f5c4b3e31a1c..b489d22409e7 100644
--- a/fs/hfsplus/dir.c
+++ b/fs/hfsplus/dir.c
@@ -534,7 +534,7 @@ static int hfsplus_rename(struct mnt_idmap *idmap,
struct inode *new_dir, struct dentry *new_dentry,
unsigned int flags)
{
- int res;
+ int res, unlinked_new = 0;

if (flags & ~RENAME_NOREPLACE)
return -EINVAL;
@@ -543,8 +543,10 @@ static int hfsplus_rename(struct mnt_idmap *idmap,
if (d_really_is_positive(new_dentry)) {
if (d_is_dir(new_dentry))
res = hfsplus_rmdir(new_dir, new_dentry);
- else
+ else {
res = hfsplus_unlink(new_dir, new_dentry);
+ unlinked_new = res == 0 && d_inode(new_dentry)->i_flags & S_DEAD;
+ }
if (res)
return res;
}
@@ -554,6 +556,12 @@ static int hfsplus_rename(struct mnt_idmap *idmap,
new_dir, &new_dentry->d_name);
if (!res)
new_dentry->d_fsdata = old_dentry->d_fsdata;
+ else if (unlinked_new) {
+ struct inode *inode = d_inode(new_dentry);
+ set_nlink(inode, inode->i_nlink + 1);
+ inode->i_flags &= ~S_DEAD;
+ }
+
return res;
}