Re: [GIT PULL] fuse update for 6.19
From: Al Viro
Date: Sun Apr 12 2026 - 02:13:12 EST
On Wed, Jan 14, 2026 at 04:23:04PM +0100, Miklos Szeredi wrote:
> Posted a short patchset fixing this.
>
> I really think there's no point in doing the get-ref, drop-ref dance.
> Retrieving the dentry from any source will require locking and that
> needs to nest d_lock with or without the refcount manipulation.
>
> So I kept the d_dispose_if_unused() API, but added a note to the
> function doc that additional locking is necessary to prevent eviction.
Looking at that thing again, I really hate how subtle it is ;-/
Consider the lockless case in your ->d_release() and try to write down
the reasons why it's safe. Among the other things, kfree_rcu() is there
to protect RCU callers of ->d_revalidate(); fair enough, but it quietly
doubles as delaying that freeing past the scope of dentry_hash[].lock in
which you'd done RB_CLEAR_NODE(&fd->node) that had pushed ->d_release()
to lockless path. Another side of that fun is the proof that if
fuse_dentry_tree_work() sees a dentry, it won't get freed under us.
Locked case of ->d_release() is easy; proving that the lockless one
is OK without explict barriers is more interesting. Basically, all
insertions are ordered wrt ->d_release() (on ->d_lock), so if the value
we are observing in RB_EMPTY_NODE() has come from those we will hit the
locked case. If the value we observe has come from RB_CLEAR_NODE() in
earlier fuse_dentry_tree_work() (these are ordered on dentry_hash[].lock,
wrt each other and insertions), there mustn't have been any subsequent
insertions, or ->d_release() would've observed the effect of those.
If the value has come from the _same_ fuse_dentry_tree_work(), the
implicit barrier in spin_lock() would've ordered that store wrt beginning
of the scope, and since dentry_free() is ordered wrt ->d_release()
the callback of call_rcu() in there wouldn't run until the the end of
the scope in question. So I think it's OK, but having it done without
a single comment either on barriers or on memory safety... Ouch.
Note that we *can* run into a dentry getting killed just after
RB_CLEAR_NODE() in there; it's just that store to ->d_flags of dying
or killed dentry is safe under ->d_lock and d_dispose_if_unused() is
a no-op for dying and killed ones - it's not just "If dentry has no
external references, move it to shrink list" as your comment in
fs/dcache.c says. And in your case it very much does have an
external reference - it's just that it's an equivalent of RCU one,
with all the joy that inflicts upon the user.
FWIW, right now I'm still looking for the source of the UAF
reported by jlayton; I think this stuff in fuse can be excluded as
a possible cause, but I'm not happy with that primitive at all... ;-/