Unverified Commit cab01237 authored by NeilBrown's avatar NeilBrown Committed by Christian Brauner
Browse files

fuse: fix conversion of fuse_reverse_inval_entry() to start_removing()



The recent conversion of fuse_reverse_inval_entry() to use
start_removing() was wrong.
As Val Packett points out the original code did not call ->lookup
while the new code does.  This can lead to a deadlock.

Rather than using full_name_hash() and d_lookup() as the old code
did, we can use try_lookup_noperm() which combines these.  Then
the result can be given to start_removing_dentry() to get the required
locks for removal.  We then double check that the name hasn't
changed.

As 'dir' needs to be used several times now, we load the dput() until
the end, and initialise to NULL so dput() is always safe.

Reported-by: default avatarVal Packett <val@packett.cool>
Closes: https://lore.kernel.org/all/6713ea38-b583-4c86-b74a-bea55652851d@packett.cool


Fixes: c9ba789d ("VFS: introduce start_creating_noperm() and start_removing_noperm()")
Signed-off-by: default avatarNeilBrown <neil@brown.name>
Link: https://patch.msgid.link/176454037897.634289.3566631742434963788@noble.neil.brown.name


Signed-off-by: default avatarChristian Brauner <brauner@kernel.org>
parent 0f61b186
Loading
Loading
Loading
Loading
+16 −7
Original line number Diff line number Diff line
@@ -1584,8 +1584,8 @@ int fuse_reverse_inval_entry(struct fuse_conn *fc, u64 parent_nodeid,
{
	int err = -ENOTDIR;
	struct inode *parent;
	struct dentry *dir;
	struct dentry *entry;
	struct dentry *dir = NULL;
	struct dentry *entry = NULL;

	parent = fuse_ilookup(fc, parent_nodeid, NULL);
	if (!parent)
@@ -1598,11 +1598,19 @@ int fuse_reverse_inval_entry(struct fuse_conn *fc, u64 parent_nodeid,
	dir = d_find_alias(parent);
	if (!dir)
		goto put_parent;

	entry = start_removing_noperm(dir, name);
	dput(dir);
	while (!entry) {
		struct dentry *child = try_lookup_noperm(name, dir);
		if (!child || IS_ERR(child))
			goto put_parent;
		entry = start_removing_dentry(dir, child);
		dput(child);
		if (IS_ERR(entry))
			goto put_parent;
		if (!d_same_name(entry, dir, name)) {
			end_removing(entry);
			entry = NULL;
		}
	}

	fuse_dir_changed(parent);
	if (!(flags & FUSE_EXPIRE_ONLY))
@@ -1640,6 +1648,7 @@ int fuse_reverse_inval_entry(struct fuse_conn *fc, u64 parent_nodeid,

	end_removing(entry);
 put_parent:
	dput(dir);
	iput(parent);
	return err;
}