ceph: Fix race between hole punch and page fault

Ceph has a following race between hole punching and page fault:

CPU1                                  CPU2
ceph_fallocate()
  ...
  ceph_zero_pagecache_range()
                                      ceph_filemap_fault()
                                        faults in page in the range being
                                        punched
  ceph_zero_objects()

And now we have a page in punched range with invalid data. Fix the
problem by using mapping->invalidate_lock similarly to other
filesystems. Note that using invalidate_lock also fixes a similar race
wrt ->readpage().

CC: Jeff Layton <jlayton@kernel.org>
CC: ceph-devel@vger.kernel.org
Reviewed-by: Jeff Layton <jlayton@kernel.org>
Signed-off-by: Jan Kara <jack@suse.cz>
This commit is contained in:
Jan Kara 2021-04-22 16:38:26 +02:00
parent 8bcbbe9c7c
commit 057ba5b245
2 changed files with 8 additions and 3 deletions

View File

@ -1395,9 +1395,11 @@ static vm_fault_t ceph_filemap_fault(struct vm_fault *vmf)
ret = VM_FAULT_SIGBUS; ret = VM_FAULT_SIGBUS;
} else { } else {
struct address_space *mapping = inode->i_mapping; struct address_space *mapping = inode->i_mapping;
struct page *page = find_or_create_page(mapping, 0, struct page *page;
mapping_gfp_constraint(mapping,
~__GFP_FS)); filemap_invalidate_lock_shared(mapping);
page = find_or_create_page(mapping, 0,
mapping_gfp_constraint(mapping, ~__GFP_FS));
if (!page) { if (!page) {
ret = VM_FAULT_OOM; ret = VM_FAULT_OOM;
goto out_inline; goto out_inline;
@ -1418,6 +1420,7 @@ static vm_fault_t ceph_filemap_fault(struct vm_fault *vmf)
vmf->page = page; vmf->page = page;
ret = VM_FAULT_MAJOR | VM_FAULT_LOCKED; ret = VM_FAULT_MAJOR | VM_FAULT_LOCKED;
out_inline: out_inline:
filemap_invalidate_unlock_shared(mapping);
dout("filemap_fault %p %llu read inline data ret %x\n", dout("filemap_fault %p %llu read inline data ret %x\n",
inode, off, ret); inode, off, ret);
} }

View File

@ -2088,6 +2088,7 @@ static long ceph_fallocate(struct file *file, int mode,
if (ret < 0) if (ret < 0)
goto unlock; goto unlock;
filemap_invalidate_lock(inode->i_mapping);
ceph_zero_pagecache_range(inode, offset, length); ceph_zero_pagecache_range(inode, offset, length);
ret = ceph_zero_objects(inode, offset, length); ret = ceph_zero_objects(inode, offset, length);
@ -2100,6 +2101,7 @@ static long ceph_fallocate(struct file *file, int mode,
if (dirty) if (dirty)
__mark_inode_dirty(inode, dirty); __mark_inode_dirty(inode, dirty);
} }
filemap_invalidate_unlock(inode->i_mapping);
ceph_put_cap_refs(ci, got); ceph_put_cap_refs(ci, got);
unlock: unlock: