diff options
author | Al Viro <viro@zeniv.linux.org.uk> | 2019-10-29 09:50:19 -0400 |
---|---|---|
committer | Ilya Dryomov <idryomov@gmail.com> | 2019-10-29 17:29:54 -0400 |
commit | aa8dd816732b2bab28c54bc4d2ccf3fc8a6e0892 (patch) | |
tree | 0805d90f6214b72791f9ca0cd12cc85b1dd6d3fa | |
parent | ea60ed6fcf29eebc78f2ce91491e6309ee005a01 (diff) |
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>
-rw-r--r-- | fs/ceph/dir.c | 15 |
1 files changed, 8 insertions, 7 deletions
diff --git a/fs/ceph/dir.c b/fs/ceph/dir.c index 4ca0b8ff9a72..d17a789fd856 100644 --- a/fs/ceph/dir.c +++ b/fs/ceph/dir.c | |||
@@ -1553,36 +1553,37 @@ static int ceph_d_revalidate(struct dentry *dentry, unsigned int flags) | |||
1553 | { | 1553 | { |
1554 | int valid = 0; | 1554 | int valid = 0; |
1555 | struct dentry *parent; | 1555 | struct dentry *parent; |
1556 | struct inode *dir; | 1556 | struct inode *dir, *inode; |
1557 | 1557 | ||
1558 | if (flags & LOOKUP_RCU) { | 1558 | if (flags & LOOKUP_RCU) { |
1559 | parent = READ_ONCE(dentry->d_parent); | 1559 | parent = READ_ONCE(dentry->d_parent); |
1560 | dir = d_inode_rcu(parent); | 1560 | dir = d_inode_rcu(parent); |
1561 | if (!dir) | 1561 | if (!dir) |
1562 | return -ECHILD; | 1562 | return -ECHILD; |
1563 | inode = d_inode_rcu(dentry); | ||
1563 | } else { | 1564 | } else { |
1564 | parent = dget_parent(dentry); | 1565 | parent = dget_parent(dentry); |
1565 | dir = d_inode(parent); | 1566 | dir = d_inode(parent); |
1567 | inode = d_inode(dentry); | ||
1566 | } | 1568 | } |
1567 | 1569 | ||
1568 | dout("d_revalidate %p '%pd' inode %p offset %lld\n", dentry, | 1570 | dout("d_revalidate %p '%pd' inode %p offset %lld\n", dentry, |
1569 | dentry, d_inode(dentry), ceph_dentry(dentry)->offset); | 1571 | dentry, inode, ceph_dentry(dentry)->offset); |
1570 | 1572 | ||
1571 | /* always trust cached snapped dentries, snapdir dentry */ | 1573 | /* always trust cached snapped dentries, snapdir dentry */ |
1572 | if (ceph_snap(dir) != CEPH_NOSNAP) { | 1574 | if (ceph_snap(dir) != CEPH_NOSNAP) { |
1573 | dout("d_revalidate %p '%pd' inode %p is SNAPPED\n", dentry, | 1575 | dout("d_revalidate %p '%pd' inode %p is SNAPPED\n", dentry, |
1574 | dentry, d_inode(dentry)); | 1576 | dentry, inode); |
1575 | valid = 1; | 1577 | valid = 1; |
1576 | } else if (d_really_is_positive(dentry) && | 1578 | } else if (inode && ceph_snap(inode) == CEPH_SNAPDIR) { |
1577 | ceph_snap(d_inode(dentry)) == CEPH_SNAPDIR) { | ||
1578 | valid = 1; | 1579 | valid = 1; |
1579 | } else { | 1580 | } else { |
1580 | valid = dentry_lease_is_valid(dentry, flags); | 1581 | valid = dentry_lease_is_valid(dentry, flags); |
1581 | if (valid == -ECHILD) | 1582 | if (valid == -ECHILD) |
1582 | return valid; | 1583 | return valid; |
1583 | if (valid || dir_lease_is_valid(dir, dentry)) { | 1584 | if (valid || dir_lease_is_valid(dir, dentry)) { |
1584 | if (d_really_is_positive(dentry)) | 1585 | if (inode) |
1585 | valid = ceph_is_any_caps(d_inode(dentry)); | 1586 | valid = ceph_is_any_caps(inode); |
1586 | else | 1587 | else |
1587 | valid = 1; | 1588 | valid = 1; |
1588 | } | 1589 | } |