ceph: fix RCU case handling in ceph_d_revalidate()

For RCU case ->d_revalidate() is called with rcu_read_lock() and
without pinning the dentry passed to it.  Which means that it
can't rely upon ->d_inode remaining stable; that's the reason
for d_inode_rcu(), actually.

Make sure we don't reload ->d_inode there.

Cc: stable@vger.kernel.org
Signed-off-by: Al Viro <viro@zeniv.linux.org.uk>
Signed-off-by: Jeff Layton <jlayton@kernel.org>
Signed-off-by: Ilya Dryomov <idryomov@gmail.com>
This commit is contained in:
Al Viro 2019-10-29 13:50:19 +00:00 committed by Ilya Dryomov
parent ea60ed6fcf
commit aa8dd81673
1 changed files with 8 additions and 7 deletions

View File

@ -1553,36 +1553,37 @@ static int ceph_d_revalidate(struct dentry *dentry, unsigned int flags)
{ {
int valid = 0; int valid = 0;
struct dentry *parent; struct dentry *parent;
struct inode *dir; struct inode *dir, *inode;
if (flags & LOOKUP_RCU) { if (flags & LOOKUP_RCU) {
parent = READ_ONCE(dentry->d_parent); parent = READ_ONCE(dentry->d_parent);
dir = d_inode_rcu(parent); dir = d_inode_rcu(parent);
if (!dir) if (!dir)
return -ECHILD; return -ECHILD;
inode = d_inode_rcu(dentry);
} else { } else {
parent = dget_parent(dentry); parent = dget_parent(dentry);
dir = d_inode(parent); dir = d_inode(parent);
inode = d_inode(dentry);
} }
dout("d_revalidate %p '%pd' inode %p offset %lld\n", dentry, dout("d_revalidate %p '%pd' inode %p offset %lld\n", dentry,
dentry, d_inode(dentry), ceph_dentry(dentry)->offset); dentry, inode, ceph_dentry(dentry)->offset);
/* always trust cached snapped dentries, snapdir dentry */ /* always trust cached snapped dentries, snapdir dentry */
if (ceph_snap(dir) != CEPH_NOSNAP) { if (ceph_snap(dir) != CEPH_NOSNAP) {
dout("d_revalidate %p '%pd' inode %p is SNAPPED\n", dentry, dout("d_revalidate %p '%pd' inode %p is SNAPPED\n", dentry,
dentry, d_inode(dentry)); dentry, inode);
valid = 1; valid = 1;
} else if (d_really_is_positive(dentry) && } else if (inode && ceph_snap(inode) == CEPH_SNAPDIR) {
ceph_snap(d_inode(dentry)) == CEPH_SNAPDIR) {
valid = 1; valid = 1;
} else { } else {
valid = dentry_lease_is_valid(dentry, flags); valid = dentry_lease_is_valid(dentry, flags);
if (valid == -ECHILD) if (valid == -ECHILD)
return valid; return valid;
if (valid || dir_lease_is_valid(dir, dentry)) { if (valid || dir_lease_is_valid(dir, dentry)) {
if (d_really_is_positive(dentry)) if (inode)
valid = ceph_is_any_caps(d_inode(dentry)); valid = ceph_is_any_caps(inode);
else else
valid = 1; valid = 1;
} }