fix proc_sys_compare() handling of in-lookup dentries
[ Upstream commit b969f9614885c20f903e1d1f9445611daf161d6d ]
There's one case where ->d_compare() can be called for an in-lookup
dentry; usually that's nothing special from ->d_compare() point of
view, but... proc_sys_compare() is weird.
The thing is, /proc/sys subdirectories can look differently for
different processes. Up to and including having the same name
resolve to different dentries - all of them hashed.
The way it's done is ->d_compare() refusing to admit a match unless
this dentry is supposed to be visible to this caller. The information
needed to discriminate between them is stored in inode; it is set
during proc_sys_lookup() and until it's done d_splice_alias() we really
can't tell who should that dentry be visible for.
Normally there's no negative dentries in /proc/sys; we can run into
a dying dentry in RCU dcache lookup, but those can be safely rejected.
However, ->d_compare() is also called for in-lookup dentries, before
they get positive - or hashed, for that matter. In case of match
we will wait until dentry leaves in-lookup state and repeat ->d_compare()
afterwards. In other words, the right behaviour is to treat the
name match as sufficient for in-lookup dentries; if dentry is not
for us, we'll see that when we recheck once proc_sys_lookup() is
done with it.
While we are at it, fix the misspelled READ_ONCE and WRITE_ONCE there.
Fixes: d9171b9345
("parallel lookups machinery, part 4 (and last)")
Reported-by: NeilBrown <neilb@brown.name>
Reviewed-by: Christian Brauner <brauner@kernel.org>
Reviewed-by: NeilBrown <neil@brown.name>
Signed-off-by: Al Viro <viro@zeniv.linux.org.uk>
Signed-off-by: Sasha Levin <sashal@kernel.org>
(cherry picked from commit f9b3d28f1f62bef49293e2eb3d5ded248341e7f8)
This commit is contained in:
parent
09f21132f0
commit
ea2d87cfca
|
@ -52,7 +52,7 @@ static void proc_evict_inode(struct inode *inode)
|
|||
|
||||
head = ei->sysctl;
|
||||
if (head) {
|
||||
RCU_INIT_POINTER(ei->sysctl, NULL);
|
||||
WRITE_ONCE(ei->sysctl, NULL);
|
||||
proc_sys_evict_inode(inode, head);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -920,17 +920,21 @@ static int proc_sys_compare(const struct dentry *dentry,
|
|||
struct ctl_table_header *head;
|
||||
struct inode *inode;
|
||||
|
||||
/* Although proc doesn't have negative dentries, rcu-walk means
|
||||
* that inode here can be NULL */
|
||||
/* AV: can it, indeed? */
|
||||
inode = d_inode_rcu(dentry);
|
||||
if (!inode)
|
||||
return 1;
|
||||
if (name->len != len)
|
||||
return 1;
|
||||
if (memcmp(name->name, str, len))
|
||||
return 1;
|
||||
head = rcu_dereference(PROC_I(inode)->sysctl);
|
||||
|
||||
// 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 = READ_ONCE(PROC_I(inode)->sysctl);
|
||||
return !head || !sysctl_is_seen(head);
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in New Issue