[<prev] [next>] [<thread-prev] [thread-next>] [day] [month] [year] [list]
Message-ID: <20250615235714.GG1880847@ZenIV>
Date: Mon, 16 Jun 2025 00:57:14 +0100
From: Al Viro <viro@...iv.linux.org.uk>
To: NeilBrown <neil@...wn.name>
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, 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).
->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...
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;
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