userfaultfd: shmem: add i_size checks
Subsystems (4)
[email protected]
maintainerPATCH 14/31 userfaultfd: shmem: add i_size checks
From: Andrea Arcangeli <[email protected]>
Subject: userfaultfd: shmem: add i_size checks
With MAP_SHARED: recheck the i_size after taking the PT lock, to serialize against truncate with the PT lock. Delete the page from the pagecache if the i_size_read check fails.
With MAP_PRIVATE: check the i_size after the PT lock before mapping anonymous memory or zeropages into the MAP_PRIVATE shmem mapping.
A mostly irrelevant cleanup: like we do the delete_from_page_cache() pagecache removal after dropping the PT lock, the PT lock is a spinlock so drop it before the sleepable page lock.
Fixes 4c27fe4c4c84 ("userfaultfd: shmem: add shmem_mcopy_atomic_pte for userfaultfd support")
Signed-off-by Andrea Arcangeli <[email protected]>
Reviewed-by Mike Rapoport <[email protected]>
Reviewed-by Hugh Dickins <[email protected]>
Reported-by Jann Horn <[email protected]>
Cc <[email protected]>
Cc "Dr. David Alan Gilbert" <[email protected]>
Cc Mike Kravetz <[email protected]>
Cc Peter Xu <[email protected]>
Signed-off-by Andrew Morton <[email protected]>
---
mm/shmem.c | 18 ++++++++++++++++-- mm/userfaultfd.c | 26 ++++++++++++++++++++++++-- 2 files changed, 40 insertions(+), 4 deletions(-)
mm/shmem.c renamed from mm/shmem.c~userfaultfd-shmem-add-i_size-checks +16 -2
@@ -2216,6 +2216,7 @@ static int shmem_mfill_atomic_pte(structstruct page *page;pte_t _dst_pte, *dst_pte;int ret; +pgoff_t offset, max_off; ret = -ENOMEM ;if (!shmem_inode_acct_block (inode,1 ))
@@ -2253,6 +2254,12 @@ static int shmem_mfill_atomic_pte(struct__SetPageSwapBacked (page);__SetPageUptodate (page); + ret = -EFAULT ; + offset =linear_page_index (dst_vma, dst_addr); + max_off =DIV_ROUND_UP (i_size_read (inode),PAGE_SIZE ); +if (unlikely (offset >= max_off)) + goto out_release; + ret =mem_cgroup_try_charge_delay (page, dst_mm, gfp, &memcg, false);if (ret) goto out_release;
@@ -2268,8 +2275,14 @@ static int shmem_mfill_atomic_pte(structif (dst_vma->vm_flags &VM_WRITE ) _dst_pte =pte_mkwrite (pte_mkdirty (_dst_pte)); - ret = -EEXIST ; dst_pte =pte_offset_map_lock (dst_mm, dst_pmd, dst_addr, &ptl); + + ret = -EFAULT ; + max_off =DIV_ROUND_UP (i_size_read (inode),PAGE_SIZE ); +if (unlikely (offset >= max_off)) + goto out_release_uncharge_unlock; + + ret = -EEXIST ;if (!pte_none (*dst_pte)) goto out_release_uncharge_unlock;
@@ -2287,13 +2300,14 @@ static int shmem_mfill_atomic_pte(struct/* No need to invalidate - it was non-present before */ update_mmu_cache (dst_vma, dst_addr, dst_pte); -unlock_page (page);pte_unmap_unlock (dst_pte, ptl); +unlock_page (page); ret =0 ; out:return ret; out_release_uncharge_unlock:pte_unmap_unlock (dst_pte, ptl); +delete_from_page_cache (page); out_release_uncharge:mem_cgroup_cancel_charge (page, memcg, false); out_release:
mm/userfaultfd.c renamed from mm/userfaultfd.c~userfaultfd-shmem-add-i_size-checks +24 -2
@@ -33,6 +33,8 @@ static int mcopy_atomic_pte(struct mm_stvoid *page_kaddr;int ret;struct page *page; +pgoff_t offset, max_off; +struct inode *inode;if (!*pagep) { ret = -ENOMEM ;
@@ -73,8 +75,17 @@ static int mcopy_atomic_pte(struct mm_stif (dst_vma->vm_flags &VM_WRITE ) _dst_pte =pte_mkwrite (pte_mkdirty (_dst_pte)); - ret = -EEXIST ; dst_pte =pte_offset_map_lock (dst_mm, dst_pmd, dst_addr, &ptl); +if (dst_vma->vm_file) { +/* the shmem MAP_PRIVATE case requires checking the i_size */ + inode = dst_vma->vm_file->f_inode; + offset =linear_page_index (dst_vma, dst_addr); + max_off =DIV_ROUND_UP (i_size_read (inode),PAGE_SIZE ); + ret = -EFAULT ; +if (unlikely (offset >= max_off)) + goto out_release_uncharge_unlock; + } + ret = -EEXIST ;if (!pte_none (*dst_pte)) goto out_release_uncharge_unlock;
@@ -108,11 +119,22 @@ static int mfill_zeropage_pte(struct mm_pte_t _dst_pte, *dst_pte;spinlock_t *ptl;int ret; +pgoff_t offset, max_off; +struct inode *inode; _dst_pte =pte_mkspecial (pfn_pte (my_zero_pfn (dst_addr), dst_vma->vm_page_prot)); - ret = -EEXIST ; dst_pte =pte_offset_map_lock (dst_mm, dst_pmd, dst_addr, &ptl); +if (dst_vma->vm_file) { +/* the shmem MAP_PRIVATE case requires checking the i_size */ + inode = dst_vma->vm_file->f_inode; + offset =linear_page_index (dst_vma, dst_addr); + max_off =DIV_ROUND_UP (i_size_read (inode),PAGE_SIZE ); + ret = -EFAULT ; +if (unlikely (offset >= max_off)) + goto out_unlock; + } + ret = -EEXIST ;if (!pte_none (*dst_pte)) goto out_unlock;set_pte_at (dst_mm, dst_addr, dst_pte, _dst_pte);
_