Unverified Commit 63584611 authored by Christian Brauner's avatar Christian Brauner
Browse files

Merge patch series "fuse: fixes and cleanups for expired dentry eviction"

Miklos Szeredi <mszeredi@redhat.com> says:

This mini series fixes issues with the stale dentry cleanup patches
added in this cycle.  In particular commit ab84ad59 ("fuse: new work
queue to periodically invalidate expired dentries") allowed a race
resulting in UAF.

* patches from https://patch.msgid.link/20260114145344.468856-1-mszeredi@redhat.com:
  vfs: document d_dispose_if_unused()
  fuse: shrink once after all buckets have been scanned
  fuse: clean up fuse_dentry_tree_work()
  fuse: add need_resched() before unlocking bucket
  fuse: make sure dentry is evicted if stale
  fuse: fix race when disposing stale dentries

Link: https://patch.msgid.link/20260114145344.468856-1-mszeredi@redhat.com


Signed-off-by: default avatarChristian Brauner <brauner@kernel.org>
parents 4973d956 79d11311
Loading
Loading
Loading
Loading
+10 −0
Original line number Diff line number Diff line
@@ -1104,6 +1104,16 @@ struct dentry *d_find_alias_rcu(struct inode *inode)
	return de;
}

/**
 * d_dispose_if_unused - move unreferenced dentries to shrink list
 * @dentry: dentry in question
 * @dispose: head of shrink list
 *
 * If dentry has no external references, move it to shrink list.
 *
 * NOTE!!! The caller is responsible for preventing eviction of the dentry by
 * holding dentry->d_inode->i_lock or equivalent.
 */
void d_dispose_if_unused(struct dentry *dentry, struct list_head *dispose)
{
	spin_lock(&dentry->d_lock);
+14 −15
Original line number Diff line number Diff line
@@ -169,20 +169,26 @@ static void fuse_dentry_tree_work(struct work_struct *work)
		node = rb_first(&dentry_hash[i].tree);
		while (node) {
			fd = rb_entry(node, struct fuse_dentry, node);
			if (time_after64(get_jiffies_64(), fd->time)) {
			if (!time_before64(fd->time, get_jiffies_64()))
				break;

			rb_erase(&fd->node, &dentry_hash[i].tree);
			RB_CLEAR_NODE(&fd->node);
				spin_unlock(&dentry_hash[i].lock);
			spin_lock(&fd->dentry->d_lock);
			/* If dentry is still referenced, let next dput release it */
			fd->dentry->d_flags |= DCACHE_OP_DELETE;
			spin_unlock(&fd->dentry->d_lock);
			d_dispose_if_unused(fd->dentry, &dispose);
			if (need_resched()) {
				spin_unlock(&dentry_hash[i].lock);
				cond_resched();
				spin_lock(&dentry_hash[i].lock);
			} else
				break;
			}
			node = rb_first(&dentry_hash[i].tree);
		}
		spin_unlock(&dentry_hash[i].lock);
		shrink_dentry_list(&dispose);
	}
	shrink_dentry_list(&dispose);

	if (inval_wq)
		schedule_delayed_work(&dentry_tree_work,
@@ -479,18 +485,12 @@ static int fuse_dentry_init(struct dentry *dentry)
	return 0;
}

static void fuse_dentry_prune(struct dentry *dentry)
static void fuse_dentry_release(struct dentry *dentry)
{
	struct fuse_dentry *fd = dentry->d_fsdata;

	if (!RB_EMPTY_NODE(&fd->node))
		fuse_dentry_tree_del_node(dentry);
}

static void fuse_dentry_release(struct dentry *dentry)
{
	struct fuse_dentry *fd = dentry->d_fsdata;

	kfree_rcu(fd, rcu);
}

@@ -527,7 +527,6 @@ const struct dentry_operations fuse_dentry_operations = {
	.d_revalidate	= fuse_dentry_revalidate,
	.d_delete	= fuse_dentry_delete,
	.d_init		= fuse_dentry_init,
	.d_prune	= fuse_dentry_prune,
	.d_release	= fuse_dentry_release,
	.d_automount	= fuse_dentry_automount,
};