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); > } >