[<prev] [next>] [<thread-prev] [day] [month] [year] [list]
Message-id: <175004219130.608730.907040844486871388@noble.neil.brown.name>
Date: Mon, 16 Jun 2025 12:49:51 +1000
From: "NeilBrown" <neil@...wn.name>
To: "Al Viro" <viro@...iv.linux.org.uk>
Cc: "Kees Cook" <kees@...nel.org>, "Joel Granados" <joel.granados@...nel.org>,
linux-fsdevel@...r.kernel.org, "LKML" <linux-kernel@...r.kernel.org>
Subject:
Re: [PATCH v3?] proc_sysctl: remove rcu_dereference() for accessing ->sysctl
On Mon, 16 Jun 2025, Al Viro wrote:
> On Mon, Jun 16, 2025 at 09:00:39AM +1000, NeilBrown wrote:
> >
> > The rcu_dereference() call in proc_sys_compare() is problematic as
> > ->d_compare is not guaranteed to be called with rcu_read_lock() held and
> > rcu_dereference() can cause a warning when used without that lock.
> >
> > Specifically d_alloc_parallel() will call ->d_compare() without
> > rcu_read_lock(), but with ->d_lock to ensure stability. In this case
> > ->d_inode is usually NULL so the rcu_dereference() will normally not be
> > reached, but it is possible that ->d_inode was set while waiting for
> > ->d_lock which could lead to the warning.
>
> Huh?
>
> There are two call sites of d_same_name() in d_alloc_parallel() - one
> in the loop (under rcu_read_lock()) and another after the thing we
> are comparing has ceased to be in-lookup. The latter is under ->d_lock,
> stabilizing everything (and it really can't run into NULL ->d_inode
> for /proc/sys/ stuff).
Ok, so ->d_inode will always be non-NULL here, so the rcu_dereference()
will always cause a warning if that code is reached for a proc_sysctl dentry.
>
> ->d_compare() instances are guaranteed dentry->d_lock or rcu_read_lock();
> in the latter case we'll either recheck or validate on previously sampled
> ->d_seq. And the second call in d_alloc_parallel() is just that - recheck
> under ->d_lock.
>
> Just use rcu_dereference_check(...., spin_is_locked(&dentry->d_lock)) and
> be done with that...
We could - but that would be misleading. And it would still cause a
sparse warning because ->sysctl isn't marked __rcu.
The reality is that ->sysctl does not need rcu protection. There is no
concurrent update except that it can be set to NULL which is pointless.
For the entire public life of the inode - whenever ->d_compare could
possibly run - there is precisely one "struct ctl_table_header"
associated with the inode.
Once we remove the unnecessary RCU_INIT_POINTER, the ->sysctl pointer is
completely stable and not needing any protection at all. So it would be
misleading to leave the rcu_dereference{_check}() there.
>
> The part where we have a somewhat wrong behaviour is not the second call
> in d_alloc_parallel() - it's the first one. Something like this
>
> static int proc_sys_compare(const struct dentry *dentry,
> unsigned int len, const char *str, const struct qstr *name)
> {
> struct ctl_table_header *head;
> struct inode *inode;
>
> if (name->len != len)
> return 1;
> if (memcmp(name->name, str, len))
> return 1;
>
> // false positive is fine here - we'll recheck anyway
> if (d_in_lookup(dentry))
> return 0;
I wonder if it would be good to document that d_compare on a
d_in_lookup() dentry will always be re-checked. I agree this is a good
way to avoid the possible duplicate dentries.
But this is fixing a different problem than the one I'm trying to fix.
I'm just trying to remove a possible warning and to do so in a way the
makes the code consistent.
Thanks,
NeilBrown
>
> inode = d_inode_rcu(dentry);
> // we just might have run into dentry in the middle of __dentry_kill()
> if (!inode)
> return 1;
> head = rcu_dereference_check(PROC_I(inode)->sysctl,
> spin_is_locked(&dentry->d_lock));
> return !head || !sysctl_is_seen(head);
> }
>
Powered by blists - more mailing lists