2019-06-04 11:11:32 +03:00
// SPDX-License-Identifier: GPL-2.0-only
2015-09-05 01:47:04 +03:00
/*
* mm / userfaultfd . c
*
* Copyright ( C ) 2015 Red Hat , Inc .
*/
# include <linux/mm.h>
2017-02-02 21:15:33 +03:00
# include <linux/sched/signal.h>
2015-09-05 01:47:04 +03:00
# include <linux/pagemap.h>
# include <linux/rmap.h>
# include <linux/swap.h>
# include <linux/swapops.h>
# include <linux/userfaultfd_k.h>
# include <linux/mmu_notifier.h>
2017-02-23 02:42:55 +03:00
# include <linux/hugetlb.h>
2017-02-23 02:43:34 +03:00
# include <linux/shmem_fs.h>
2015-09-05 01:47:04 +03:00
# include <asm/tlbflush.h>
# include "internal.h"
2019-12-01 04:57:55 +03:00
static __always_inline
struct vm_area_struct * find_dst_vma ( struct mm_struct * dst_mm ,
unsigned long dst_start ,
unsigned long len )
{
/*
* Make sure that the dst range is both valid and fully within a
* single existing vma .
*/
struct vm_area_struct * dst_vma ;
dst_vma = find_vma ( dst_mm , dst_start ) ;
if ( ! dst_vma )
return NULL ;
if ( dst_start < dst_vma - > vm_start | |
dst_start + len > dst_vma - > vm_end )
return NULL ;
/*
* Check the vma is registered in uffd , this is required to
* enforce the VM_MAYWRITE check done at uffd registration
* time .
*/
if ( ! dst_vma - > vm_userfaultfd_ctx . ctx )
return NULL ;
return dst_vma ;
}
2015-09-05 01:47:04 +03:00
static int mcopy_atomic_pte ( struct mm_struct * dst_mm ,
pmd_t * dst_pmd ,
struct vm_area_struct * dst_vma ,
unsigned long dst_addr ,
2015-09-05 01:47:08 +03:00
unsigned long src_addr ,
2020-04-07 06:05:41 +03:00
struct page * * pagep ,
bool wp_copy )
2015-09-05 01:47:04 +03:00
{
pte_t _dst_pte , * dst_pte ;
spinlock_t * ptl ;
void * page_kaddr ;
int ret ;
2015-09-05 01:47:08 +03:00
struct page * page ;
2018-12-01 01:09:37 +03:00
pgoff_t offset , max_off ;
struct inode * inode ;
2015-09-05 01:47:04 +03:00
2015-09-05 01:47:08 +03:00
if ( ! * pagep ) {
ret = - ENOMEM ;
page = alloc_page_vma ( GFP_HIGHUSER_MOVABLE , dst_vma , dst_addr ) ;
if ( ! page )
goto out ;
page_kaddr = kmap_atomic ( page ) ;
ret = copy_from_user ( page_kaddr ,
( const void __user * ) src_addr ,
PAGE_SIZE ) ;
kunmap_atomic ( page_kaddr ) ;
2020-06-09 07:33:54 +03:00
/* fallback to copy_from_user outside mmap_lock */
2015-09-05 01:47:08 +03:00
if ( unlikely ( ret ) ) {
2018-12-01 01:09:25 +03:00
ret = - ENOENT ;
2015-09-05 01:47:08 +03:00
* pagep = page ;
/* don't free the page */
goto out ;
}
} else {
page = * pagep ;
* pagep = NULL ;
}
2015-09-05 01:47:04 +03:00
/*
* The memory barrier inside __SetPageUptodate makes sure that
2019-12-01 04:58:17 +03:00
* preceding stores to the page contents become visible before
2015-09-05 01:47:04 +03:00
* the set_pte_at ( ) write .
*/
__SetPageUptodate ( page ) ;
ret = - ENOMEM ;
2020-06-04 02:02:24 +03:00
if ( mem_cgroup_charge ( page , dst_mm , GFP_KERNEL ) )
2015-09-05 01:47:04 +03:00
goto out_release ;
2020-04-07 06:05:41 +03:00
_dst_pte = pte_mkdirty ( mk_pte ( page , dst_vma - > vm_page_prot ) ) ;
userfaultfd: wp: apply _PAGE_UFFD_WP bit
Firstly, introduce two new flags MM_CP_UFFD_WP[_RESOLVE] for
change_protection() when used with uffd-wp and make sure the two new flags
are exclusively used. Then,
- For MM_CP_UFFD_WP: apply the _PAGE_UFFD_WP bit and remove _PAGE_RW
when a range of memory is write protected by uffd
- For MM_CP_UFFD_WP_RESOLVE: remove the _PAGE_UFFD_WP bit and recover
_PAGE_RW when write protection is resolved from userspace
And use this new interface in mwriteprotect_range() to replace the old
MM_CP_DIRTY_ACCT.
Do this change for both PTEs and huge PMDs. Then we can start to identify
which PTE/PMD is write protected by general (e.g., COW or soft dirty
tracking), and which is for userfaultfd-wp.
Since we should keep the _PAGE_UFFD_WP when doing pte_modify(), add it
into _PAGE_CHG_MASK as well. Meanwhile, since we have this new bit, we
can be even more strict when detecting uffd-wp page faults in either
do_wp_page() or wp_huge_pmd().
After we're with _PAGE_UFFD_WP, a special case is when a page is both
protected by the general COW logic and also userfault-wp. Here the
userfault-wp will have higher priority and will be handled first. Only
after the uffd-wp bit is cleared on the PTE/PMD will we continue to handle
the general COW. These are the steps on what will happen with such a
page:
1. CPU accesses write protected shared page (so both protected by
general COW and uffd-wp), blocked by uffd-wp first because in
do_wp_page we'll handle uffd-wp first, so it has higher priority
than general COW.
2. Uffd service thread receives the request, do UFFDIO_WRITEPROTECT
to remove the uffd-wp bit upon the PTE/PMD. However here we
still keep the write bit cleared. Notify the blocked CPU.
3. The blocked CPU resumes the page fault process with a fault
retry, during retry it'll notice it was not with the uffd-wp bit
this time but it is still write protected by general COW, then
it'll go though the COW path in the fault handler, copy the page,
apply write bit where necessary, and retry again.
4. The CPU will be able to access this page with write bit set.
Suggested-by: Andrea Arcangeli <aarcange@redhat.com>
Signed-off-by: Peter Xu <peterx@redhat.com>
Signed-off-by: Andrew Morton <akpm@linux-foundation.org>
Cc: Brian Geffon <bgeffon@google.com>
Cc: Pavel Emelyanov <xemul@openvz.org>
Cc: Mike Kravetz <mike.kravetz@oracle.com>
Cc: David Hildenbrand <david@redhat.com>
Cc: Martin Cracauer <cracauer@cons.org>
Cc: Mel Gorman <mgorman@suse.de>
Cc: Bobby Powers <bobbypowers@gmail.com>
Cc: Mike Rapoport <rppt@linux.vnet.ibm.com>
Cc: "Kirill A . Shutemov" <kirill@shutemov.name>
Cc: Maya Gokhale <gokhale2@llnl.gov>
Cc: Johannes Weiner <hannes@cmpxchg.org>
Cc: Marty McFadden <mcfadden8@llnl.gov>
Cc: Denis Plotnikov <dplotnikov@virtuozzo.com>
Cc: Hugh Dickins <hughd@google.com>
Cc: "Dr . David Alan Gilbert" <dgilbert@redhat.com>
Cc: Jerome Glisse <jglisse@redhat.com>
Cc: Rik van Riel <riel@redhat.com>
Cc: Shaohua Li <shli@fb.com>
Link: http://lkml.kernel.org/r/20200220163112.11409-8-peterx@redhat.com
Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
2020-04-07 06:05:49 +03:00
if ( dst_vma - > vm_flags & VM_WRITE ) {
if ( wp_copy )
_dst_pte = pte_mkuffd_wp ( _dst_pte ) ;
else
_dst_pte = pte_mkwrite ( _dst_pte ) ;
}
2015-09-05 01:47:04 +03:00
dst_pte = pte_offset_map_lock ( dst_mm , dst_pmd , dst_addr , & ptl ) ;
2018-12-01 01:09:37 +03:00
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 ;
2015-09-05 01:47:04 +03:00
if ( ! pte_none ( * dst_pte ) )
goto out_release_uncharge_unlock ;
inc_mm_counter ( dst_mm , MM_ANONPAGES ) ;
2020-06-04 02:01:57 +03:00
page_add_new_anon_rmap ( page , dst_vma , dst_addr , false ) ;
2020-08-12 04:30:40 +03:00
lru_cache_add_inactive_or_unevictable ( page , dst_vma ) ;
2015-09-05 01:47:04 +03:00
set_pte_at ( dst_mm , dst_addr , dst_pte , _dst_pte ) ;
/* No need to invalidate - it was non-present before */
update_mmu_cache ( dst_vma , dst_addr , dst_pte ) ;
pte_unmap_unlock ( dst_pte , ptl ) ;
ret = 0 ;
out :
return ret ;
out_release_uncharge_unlock :
pte_unmap_unlock ( dst_pte , ptl ) ;
out_release :
mm, fs: get rid of PAGE_CACHE_* and page_cache_{get,release} macros
PAGE_CACHE_{SIZE,SHIFT,MASK,ALIGN} macros were introduced *long* time
ago with promise that one day it will be possible to implement page
cache with bigger chunks than PAGE_SIZE.
This promise never materialized. And unlikely will.
We have many places where PAGE_CACHE_SIZE assumed to be equal to
PAGE_SIZE. And it's constant source of confusion on whether
PAGE_CACHE_* or PAGE_* constant should be used in a particular case,
especially on the border between fs and mm.
Global switching to PAGE_CACHE_SIZE != PAGE_SIZE would cause to much
breakage to be doable.
Let's stop pretending that pages in page cache are special. They are
not.
The changes are pretty straight-forward:
- <foo> << (PAGE_CACHE_SHIFT - PAGE_SHIFT) -> <foo>;
- <foo> >> (PAGE_CACHE_SHIFT - PAGE_SHIFT) -> <foo>;
- PAGE_CACHE_{SIZE,SHIFT,MASK,ALIGN} -> PAGE_{SIZE,SHIFT,MASK,ALIGN};
- page_cache_get() -> get_page();
- page_cache_release() -> put_page();
This patch contains automated changes generated with coccinelle using
script below. For some reason, coccinelle doesn't patch header files.
I've called spatch for them manually.
The only adjustment after coccinelle is revert of changes to
PAGE_CAHCE_ALIGN definition: we are going to drop it later.
There are few places in the code where coccinelle didn't reach. I'll
fix them manually in a separate patch. Comments and documentation also
will be addressed with the separate patch.
virtual patch
@@
expression E;
@@
- E << (PAGE_CACHE_SHIFT - PAGE_SHIFT)
+ E
@@
expression E;
@@
- E >> (PAGE_CACHE_SHIFT - PAGE_SHIFT)
+ E
@@
@@
- PAGE_CACHE_SHIFT
+ PAGE_SHIFT
@@
@@
- PAGE_CACHE_SIZE
+ PAGE_SIZE
@@
@@
- PAGE_CACHE_MASK
+ PAGE_MASK
@@
expression E;
@@
- PAGE_CACHE_ALIGN(E)
+ PAGE_ALIGN(E)
@@
expression E;
@@
- page_cache_get(E)
+ get_page(E)
@@
expression E;
@@
- page_cache_release(E)
+ put_page(E)
Signed-off-by: Kirill A. Shutemov <kirill.shutemov@linux.intel.com>
Acked-by: Michal Hocko <mhocko@suse.com>
Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
2016-04-01 15:29:47 +03:00
put_page ( page ) ;
2015-09-05 01:47:04 +03:00
goto out ;
}
static int mfill_zeropage_pte ( struct mm_struct * dst_mm ,
pmd_t * dst_pmd ,
struct vm_area_struct * dst_vma ,
unsigned long dst_addr )
{
pte_t _dst_pte , * dst_pte ;
spinlock_t * ptl ;
int ret ;
2018-12-01 01:09:37 +03:00
pgoff_t offset , max_off ;
struct inode * inode ;
2015-09-05 01:47:04 +03:00
_dst_pte = pte_mkspecial ( pfn_pte ( my_zero_pfn ( dst_addr ) ,
dst_vma - > vm_page_prot ) ) ;
dst_pte = pte_offset_map_lock ( dst_mm , dst_pmd , dst_addr , & ptl ) ;
2018-12-01 01:09:37 +03:00
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 ;
2015-09-05 01:47:04 +03:00
if ( ! pte_none ( * dst_pte ) )
goto out_unlock ;
set_pte_at ( dst_mm , dst_addr , dst_pte , _dst_pte ) ;
/* No need to invalidate - it was non-present before */
update_mmu_cache ( dst_vma , dst_addr , dst_pte ) ;
ret = 0 ;
out_unlock :
pte_unmap_unlock ( dst_pte , ptl ) ;
return ret ;
}
static pmd_t * mm_alloc_pmd ( struct mm_struct * mm , unsigned long address )
{
pgd_t * pgd ;
2017-03-09 17:24:07 +03:00
p4d_t * p4d ;
2015-09-05 01:47:04 +03:00
pud_t * pud ;
pgd = pgd_offset ( mm , address ) ;
2017-03-09 17:24:07 +03:00
p4d = p4d_alloc ( mm , pgd , address ) ;
if ( ! p4d )
return NULL ;
pud = pud_alloc ( mm , p4d , address ) ;
if ( ! pud )
return NULL ;
/*
* Note that we didn ' t run this because the pmd was
* missing , the * pmd may be already established and in
* turn it may also be a trans_huge_pmd .
*/
return pmd_alloc ( mm , pud , address ) ;
2015-09-05 01:47:04 +03:00
}
2017-02-23 02:42:55 +03:00
# ifdef CONFIG_HUGETLB_PAGE
/*
* __mcopy_atomic processing for HUGETLB vmas . Note that this routine is
2020-06-09 07:33:54 +03:00
* called with mmap_lock held , it will release mmap_lock before returning .
2017-02-23 02:42:55 +03:00
*/
static __always_inline ssize_t __mcopy_atomic_hugetlb ( struct mm_struct * dst_mm ,
struct vm_area_struct * dst_vma ,
unsigned long dst_start ,
unsigned long src_start ,
unsigned long len ,
bool zeropage )
{
2017-02-23 02:43:43 +03:00
int vm_alloc_shared = dst_vma - > vm_flags & VM_SHARED ;
int vm_shared = dst_vma - > vm_flags & VM_SHARED ;
2017-02-23 02:42:55 +03:00
ssize_t err ;
pte_t * dst_pte ;
unsigned long src_addr , dst_addr ;
long copied ;
struct page * page ;
unsigned long vma_hpagesize ;
pgoff_t idx ;
u32 hash ;
struct address_space * mapping ;
/*
* There is no default zero huge page for all huge page sizes as
* supported by hugetlb . A PMD_SIZE huge pages may exist as used
* by THP . Since we can not reliably insert a zero page , this
* feature is not supported .
*/
if ( zeropage ) {
2020-06-09 07:33:25 +03:00
mmap_read_unlock ( dst_mm ) ;
2017-02-23 02:42:55 +03:00
return - EINVAL ;
}
src_addr = src_start ;
dst_addr = dst_start ;
copied = 0 ;
page = NULL ;
vma_hpagesize = vma_kernel_pagesize ( dst_vma ) ;
/*
* Validate alignment based on huge page size
*/
err = - EINVAL ;
if ( dst_start & ( vma_hpagesize - 1 ) | | len & ( vma_hpagesize - 1 ) )
goto out_unlock ;
retry :
/*
2020-06-09 07:33:54 +03:00
* On routine entry dst_vma is set . If we had to drop mmap_lock and
2017-02-23 02:42:55 +03:00
* retry , dst_vma will be set to NULL and we must lookup again .
*/
if ( ! dst_vma ) {
2017-02-25 01:58:28 +03:00
err = - ENOENT ;
2019-12-01 04:57:55 +03:00
dst_vma = find_dst_vma ( dst_mm , dst_start , len ) ;
2017-02-23 02:42:55 +03:00
if ( ! dst_vma | | ! is_vm_hugetlb_page ( dst_vma ) )
goto out_unlock ;
2017-02-23 02:43:43 +03:00
2017-02-25 01:58:28 +03:00
err = - EINVAL ;
if ( vma_hpagesize ! = vma_kernel_pagesize ( dst_vma ) )
goto out_unlock ;
2017-02-23 02:43:43 +03:00
vm_shared = dst_vma - > vm_flags & VM_SHARED ;
2017-02-23 02:42:55 +03:00
}
/*
2017-02-23 02:43:43 +03:00
* If not shared , ensure the dst_vma has a anon_vma .
2017-02-23 02:42:55 +03:00
*/
err = - ENOMEM ;
2017-02-23 02:43:43 +03:00
if ( ! vm_shared ) {
if ( unlikely ( anon_vma_prepare ( dst_vma ) ) )
goto out_unlock ;
}
2017-02-23 02:42:55 +03:00
while ( src_addr < src_start + len ) {
pte_t dst_pteval ;
BUG_ON ( dst_addr > = dst_start + len ) ;
/*
hugetlbfs: use i_mmap_rwsem for more pmd sharing synchronization
Patch series "hugetlbfs: use i_mmap_rwsem for more synchronization", v2.
While discussing the issue with huge_pte_offset [1], I remembered that
there were more outstanding hugetlb races. These issues are:
1) For shared pmds, huge PTE pointers returned by huge_pte_alloc can become
invalid via a call to huge_pmd_unshare by another thread.
2) hugetlbfs page faults can race with truncation causing invalid global
reserve counts and state.
A previous attempt was made to use i_mmap_rwsem in this manner as
described at [2]. However, those patches were reverted starting with [3]
due to locking issues.
To effectively use i_mmap_rwsem to address the above issues it needs to be
held (in read mode) during page fault processing. However, during fault
processing we need to lock the page we will be adding. Lock ordering
requires we take page lock before i_mmap_rwsem. Waiting until after
taking the page lock is too late in the fault process for the
synchronization we want to do.
To address this lock ordering issue, the following patches change the lock
ordering for hugetlb pages. This is not too invasive as hugetlbfs
processing is done separate from core mm in many places. However, I don't
really like this idea. Much ugliness is contained in the new routine
hugetlb_page_mapping_lock_write() of patch 1.
The only other way I can think of to address these issues is by catching
all the races. After catching a race, cleanup, backout, retry ... etc,
as needed. This can get really ugly, especially for huge page
reservations. At one time, I started writing some of the reservation
backout code for page faults and it got so ugly and complicated I went
down the path of adding synchronization to avoid the races. Any other
suggestions would be welcome.
[1] https://lore.kernel.org/linux-mm/1582342427-230392-1-git-send-email-longpeng2@huawei.com/
[2] https://lore.kernel.org/linux-mm/20181222223013.22193-1-mike.kravetz@oracle.com/
[3] https://lore.kernel.org/linux-mm/20190103235452.29335-1-mike.kravetz@oracle.com
[4] https://lore.kernel.org/linux-mm/1584028670.7365.182.camel@lca.pw/
[5] https://lore.kernel.org/lkml/20200312183142.108df9ac@canb.auug.org.au/
This patch (of 2):
While looking at BUGs associated with invalid huge page map counts, it was
discovered and observed that a huge pte pointer could become 'invalid' and
point to another task's page table. Consider the following:
A task takes a page fault on a shared hugetlbfs file and calls
huge_pte_alloc to get a ptep. Suppose the returned ptep points to a
shared pmd.
Now, another task truncates the hugetlbfs file. As part of truncation, it
unmaps everyone who has the file mapped. If the range being truncated is
covered by a shared pmd, huge_pmd_unshare will be called. For all but the
last user of the shared pmd, huge_pmd_unshare will clear the pud pointing
to the pmd. If the task in the middle of the page fault is not the last
user, the ptep returned by huge_pte_alloc now points to another task's
page table or worse. This leads to bad things such as incorrect page
map/reference counts or invalid memory references.
To fix, expand the use of i_mmap_rwsem as follows:
- i_mmap_rwsem is held in read mode whenever huge_pmd_share is called.
huge_pmd_share is only called via huge_pte_alloc, so callers of
huge_pte_alloc take i_mmap_rwsem before calling. In addition, callers
of huge_pte_alloc continue to hold the semaphore until finished with
the ptep.
- i_mmap_rwsem is held in write mode whenever huge_pmd_unshare is called.
One problem with this scheme is that it requires taking i_mmap_rwsem
before taking the page lock during page faults. This is not the order
specified in the rest of mm code. Handling of hugetlbfs pages is mostly
isolated today. Therefore, we use this alternative locking order for
PageHuge() pages.
mapping->i_mmap_rwsem
hugetlb_fault_mutex (hugetlbfs specific page fault mutex)
page->flags PG_locked (lock_page)
To help with lock ordering issues, hugetlb_page_mapping_lock_write() is
introduced to write lock the i_mmap_rwsem associated with a page.
In most cases it is easy to get address_space via vma->vm_file->f_mapping.
However, in the case of migration or memory errors for anon pages we do
not have an associated vma. A new routine _get_hugetlb_page_mapping()
will use anon_vma to get address_space in these cases.
Signed-off-by: Mike Kravetz <mike.kravetz@oracle.com>
Signed-off-by: Andrew Morton <akpm@linux-foundation.org>
Cc: Michal Hocko <mhocko@kernel.org>
Cc: Hugh Dickins <hughd@google.com>
Cc: Naoya Horiguchi <n-horiguchi@ah.jp.nec.com>
Cc: "Aneesh Kumar K . V" <aneesh.kumar@linux.vnet.ibm.com>
Cc: Andrea Arcangeli <aarcange@redhat.com>
Cc: "Kirill A . Shutemov" <kirill.shutemov@linux.intel.com>
Cc: Davidlohr Bueso <dave@stgolabs.net>
Cc: Prakash Sangappa <prakash.sangappa@oracle.com>
Link: http://lkml.kernel.org/r/20200316205756.146666-2-mike.kravetz@oracle.com
Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
2020-04-02 07:11:05 +03:00
* Serialize via i_mmap_rwsem and hugetlb_fault_mutex .
* i_mmap_rwsem ensures the dst_pte remains valid even
* in the case of shared pmds . fault mutex prevents
* races with other faulting threads .
2017-02-23 02:42:55 +03:00
*/
2019-01-09 02:23:36 +03:00
mapping = dst_vma - > vm_file - > f_mapping ;
hugetlbfs: use i_mmap_rwsem for more pmd sharing synchronization
Patch series "hugetlbfs: use i_mmap_rwsem for more synchronization", v2.
While discussing the issue with huge_pte_offset [1], I remembered that
there were more outstanding hugetlb races. These issues are:
1) For shared pmds, huge PTE pointers returned by huge_pte_alloc can become
invalid via a call to huge_pmd_unshare by another thread.
2) hugetlbfs page faults can race with truncation causing invalid global
reserve counts and state.
A previous attempt was made to use i_mmap_rwsem in this manner as
described at [2]. However, those patches were reverted starting with [3]
due to locking issues.
To effectively use i_mmap_rwsem to address the above issues it needs to be
held (in read mode) during page fault processing. However, during fault
processing we need to lock the page we will be adding. Lock ordering
requires we take page lock before i_mmap_rwsem. Waiting until after
taking the page lock is too late in the fault process for the
synchronization we want to do.
To address this lock ordering issue, the following patches change the lock
ordering for hugetlb pages. This is not too invasive as hugetlbfs
processing is done separate from core mm in many places. However, I don't
really like this idea. Much ugliness is contained in the new routine
hugetlb_page_mapping_lock_write() of patch 1.
The only other way I can think of to address these issues is by catching
all the races. After catching a race, cleanup, backout, retry ... etc,
as needed. This can get really ugly, especially for huge page
reservations. At one time, I started writing some of the reservation
backout code for page faults and it got so ugly and complicated I went
down the path of adding synchronization to avoid the races. Any other
suggestions would be welcome.
[1] https://lore.kernel.org/linux-mm/1582342427-230392-1-git-send-email-longpeng2@huawei.com/
[2] https://lore.kernel.org/linux-mm/20181222223013.22193-1-mike.kravetz@oracle.com/
[3] https://lore.kernel.org/linux-mm/20190103235452.29335-1-mike.kravetz@oracle.com
[4] https://lore.kernel.org/linux-mm/1584028670.7365.182.camel@lca.pw/
[5] https://lore.kernel.org/lkml/20200312183142.108df9ac@canb.auug.org.au/
This patch (of 2):
While looking at BUGs associated with invalid huge page map counts, it was
discovered and observed that a huge pte pointer could become 'invalid' and
point to another task's page table. Consider the following:
A task takes a page fault on a shared hugetlbfs file and calls
huge_pte_alloc to get a ptep. Suppose the returned ptep points to a
shared pmd.
Now, another task truncates the hugetlbfs file. As part of truncation, it
unmaps everyone who has the file mapped. If the range being truncated is
covered by a shared pmd, huge_pmd_unshare will be called. For all but the
last user of the shared pmd, huge_pmd_unshare will clear the pud pointing
to the pmd. If the task in the middle of the page fault is not the last
user, the ptep returned by huge_pte_alloc now points to another task's
page table or worse. This leads to bad things such as incorrect page
map/reference counts or invalid memory references.
To fix, expand the use of i_mmap_rwsem as follows:
- i_mmap_rwsem is held in read mode whenever huge_pmd_share is called.
huge_pmd_share is only called via huge_pte_alloc, so callers of
huge_pte_alloc take i_mmap_rwsem before calling. In addition, callers
of huge_pte_alloc continue to hold the semaphore until finished with
the ptep.
- i_mmap_rwsem is held in write mode whenever huge_pmd_unshare is called.
One problem with this scheme is that it requires taking i_mmap_rwsem
before taking the page lock during page faults. This is not the order
specified in the rest of mm code. Handling of hugetlbfs pages is mostly
isolated today. Therefore, we use this alternative locking order for
PageHuge() pages.
mapping->i_mmap_rwsem
hugetlb_fault_mutex (hugetlbfs specific page fault mutex)
page->flags PG_locked (lock_page)
To help with lock ordering issues, hugetlb_page_mapping_lock_write() is
introduced to write lock the i_mmap_rwsem associated with a page.
In most cases it is easy to get address_space via vma->vm_file->f_mapping.
However, in the case of migration or memory errors for anon pages we do
not have an associated vma. A new routine _get_hugetlb_page_mapping()
will use anon_vma to get address_space in these cases.
Signed-off-by: Mike Kravetz <mike.kravetz@oracle.com>
Signed-off-by: Andrew Morton <akpm@linux-foundation.org>
Cc: Michal Hocko <mhocko@kernel.org>
Cc: Hugh Dickins <hughd@google.com>
Cc: Naoya Horiguchi <n-horiguchi@ah.jp.nec.com>
Cc: "Aneesh Kumar K . V" <aneesh.kumar@linux.vnet.ibm.com>
Cc: Andrea Arcangeli <aarcange@redhat.com>
Cc: "Kirill A . Shutemov" <kirill.shutemov@linux.intel.com>
Cc: Davidlohr Bueso <dave@stgolabs.net>
Cc: Prakash Sangappa <prakash.sangappa@oracle.com>
Link: http://lkml.kernel.org/r/20200316205756.146666-2-mike.kravetz@oracle.com
Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
2020-04-02 07:11:05 +03:00
i_mmap_lock_read ( mapping ) ;
idx = linear_page_index ( dst_vma , dst_addr ) ;
2019-12-01 04:57:02 +03:00
hash = hugetlb_fault_mutex_hash ( mapping , idx ) ;
2017-02-23 02:42:55 +03:00
mutex_lock ( & hugetlb_fault_mutex_table [ hash ] ) ;
err = - ENOMEM ;
2019-12-01 04:57:49 +03:00
dst_pte = huge_pte_alloc ( dst_mm , dst_addr , vma_hpagesize ) ;
2017-02-23 02:42:55 +03:00
if ( ! dst_pte ) {
mutex_unlock ( & hugetlb_fault_mutex_table [ hash ] ) ;
hugetlbfs: use i_mmap_rwsem for more pmd sharing synchronization
Patch series "hugetlbfs: use i_mmap_rwsem for more synchronization", v2.
While discussing the issue with huge_pte_offset [1], I remembered that
there were more outstanding hugetlb races. These issues are:
1) For shared pmds, huge PTE pointers returned by huge_pte_alloc can become
invalid via a call to huge_pmd_unshare by another thread.
2) hugetlbfs page faults can race with truncation causing invalid global
reserve counts and state.
A previous attempt was made to use i_mmap_rwsem in this manner as
described at [2]. However, those patches were reverted starting with [3]
due to locking issues.
To effectively use i_mmap_rwsem to address the above issues it needs to be
held (in read mode) during page fault processing. However, during fault
processing we need to lock the page we will be adding. Lock ordering
requires we take page lock before i_mmap_rwsem. Waiting until after
taking the page lock is too late in the fault process for the
synchronization we want to do.
To address this lock ordering issue, the following patches change the lock
ordering for hugetlb pages. This is not too invasive as hugetlbfs
processing is done separate from core mm in many places. However, I don't
really like this idea. Much ugliness is contained in the new routine
hugetlb_page_mapping_lock_write() of patch 1.
The only other way I can think of to address these issues is by catching
all the races. After catching a race, cleanup, backout, retry ... etc,
as needed. This can get really ugly, especially for huge page
reservations. At one time, I started writing some of the reservation
backout code for page faults and it got so ugly and complicated I went
down the path of adding synchronization to avoid the races. Any other
suggestions would be welcome.
[1] https://lore.kernel.org/linux-mm/1582342427-230392-1-git-send-email-longpeng2@huawei.com/
[2] https://lore.kernel.org/linux-mm/20181222223013.22193-1-mike.kravetz@oracle.com/
[3] https://lore.kernel.org/linux-mm/20190103235452.29335-1-mike.kravetz@oracle.com
[4] https://lore.kernel.org/linux-mm/1584028670.7365.182.camel@lca.pw/
[5] https://lore.kernel.org/lkml/20200312183142.108df9ac@canb.auug.org.au/
This patch (of 2):
While looking at BUGs associated with invalid huge page map counts, it was
discovered and observed that a huge pte pointer could become 'invalid' and
point to another task's page table. Consider the following:
A task takes a page fault on a shared hugetlbfs file and calls
huge_pte_alloc to get a ptep. Suppose the returned ptep points to a
shared pmd.
Now, another task truncates the hugetlbfs file. As part of truncation, it
unmaps everyone who has the file mapped. If the range being truncated is
covered by a shared pmd, huge_pmd_unshare will be called. For all but the
last user of the shared pmd, huge_pmd_unshare will clear the pud pointing
to the pmd. If the task in the middle of the page fault is not the last
user, the ptep returned by huge_pte_alloc now points to another task's
page table or worse. This leads to bad things such as incorrect page
map/reference counts or invalid memory references.
To fix, expand the use of i_mmap_rwsem as follows:
- i_mmap_rwsem is held in read mode whenever huge_pmd_share is called.
huge_pmd_share is only called via huge_pte_alloc, so callers of
huge_pte_alloc take i_mmap_rwsem before calling. In addition, callers
of huge_pte_alloc continue to hold the semaphore until finished with
the ptep.
- i_mmap_rwsem is held in write mode whenever huge_pmd_unshare is called.
One problem with this scheme is that it requires taking i_mmap_rwsem
before taking the page lock during page faults. This is not the order
specified in the rest of mm code. Handling of hugetlbfs pages is mostly
isolated today. Therefore, we use this alternative locking order for
PageHuge() pages.
mapping->i_mmap_rwsem
hugetlb_fault_mutex (hugetlbfs specific page fault mutex)
page->flags PG_locked (lock_page)
To help with lock ordering issues, hugetlb_page_mapping_lock_write() is
introduced to write lock the i_mmap_rwsem associated with a page.
In most cases it is easy to get address_space via vma->vm_file->f_mapping.
However, in the case of migration or memory errors for anon pages we do
not have an associated vma. A new routine _get_hugetlb_page_mapping()
will use anon_vma to get address_space in these cases.
Signed-off-by: Mike Kravetz <mike.kravetz@oracle.com>
Signed-off-by: Andrew Morton <akpm@linux-foundation.org>
Cc: Michal Hocko <mhocko@kernel.org>
Cc: Hugh Dickins <hughd@google.com>
Cc: Naoya Horiguchi <n-horiguchi@ah.jp.nec.com>
Cc: "Aneesh Kumar K . V" <aneesh.kumar@linux.vnet.ibm.com>
Cc: Andrea Arcangeli <aarcange@redhat.com>
Cc: "Kirill A . Shutemov" <kirill.shutemov@linux.intel.com>
Cc: Davidlohr Bueso <dave@stgolabs.net>
Cc: Prakash Sangappa <prakash.sangappa@oracle.com>
Link: http://lkml.kernel.org/r/20200316205756.146666-2-mike.kravetz@oracle.com
Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
2020-04-02 07:11:05 +03:00
i_mmap_unlock_read ( mapping ) ;
2017-02-23 02:42:55 +03:00
goto out_unlock ;
}
err = - EEXIST ;
dst_pteval = huge_ptep_get ( dst_pte ) ;
if ( ! huge_pte_none ( dst_pteval ) ) {
mutex_unlock ( & hugetlb_fault_mutex_table [ hash ] ) ;
hugetlbfs: use i_mmap_rwsem for more pmd sharing synchronization
Patch series "hugetlbfs: use i_mmap_rwsem for more synchronization", v2.
While discussing the issue with huge_pte_offset [1], I remembered that
there were more outstanding hugetlb races. These issues are:
1) For shared pmds, huge PTE pointers returned by huge_pte_alloc can become
invalid via a call to huge_pmd_unshare by another thread.
2) hugetlbfs page faults can race with truncation causing invalid global
reserve counts and state.
A previous attempt was made to use i_mmap_rwsem in this manner as
described at [2]. However, those patches were reverted starting with [3]
due to locking issues.
To effectively use i_mmap_rwsem to address the above issues it needs to be
held (in read mode) during page fault processing. However, during fault
processing we need to lock the page we will be adding. Lock ordering
requires we take page lock before i_mmap_rwsem. Waiting until after
taking the page lock is too late in the fault process for the
synchronization we want to do.
To address this lock ordering issue, the following patches change the lock
ordering for hugetlb pages. This is not too invasive as hugetlbfs
processing is done separate from core mm in many places. However, I don't
really like this idea. Much ugliness is contained in the new routine
hugetlb_page_mapping_lock_write() of patch 1.
The only other way I can think of to address these issues is by catching
all the races. After catching a race, cleanup, backout, retry ... etc,
as needed. This can get really ugly, especially for huge page
reservations. At one time, I started writing some of the reservation
backout code for page faults and it got so ugly and complicated I went
down the path of adding synchronization to avoid the races. Any other
suggestions would be welcome.
[1] https://lore.kernel.org/linux-mm/1582342427-230392-1-git-send-email-longpeng2@huawei.com/
[2] https://lore.kernel.org/linux-mm/20181222223013.22193-1-mike.kravetz@oracle.com/
[3] https://lore.kernel.org/linux-mm/20190103235452.29335-1-mike.kravetz@oracle.com
[4] https://lore.kernel.org/linux-mm/1584028670.7365.182.camel@lca.pw/
[5] https://lore.kernel.org/lkml/20200312183142.108df9ac@canb.auug.org.au/
This patch (of 2):
While looking at BUGs associated with invalid huge page map counts, it was
discovered and observed that a huge pte pointer could become 'invalid' and
point to another task's page table. Consider the following:
A task takes a page fault on a shared hugetlbfs file and calls
huge_pte_alloc to get a ptep. Suppose the returned ptep points to a
shared pmd.
Now, another task truncates the hugetlbfs file. As part of truncation, it
unmaps everyone who has the file mapped. If the range being truncated is
covered by a shared pmd, huge_pmd_unshare will be called. For all but the
last user of the shared pmd, huge_pmd_unshare will clear the pud pointing
to the pmd. If the task in the middle of the page fault is not the last
user, the ptep returned by huge_pte_alloc now points to another task's
page table or worse. This leads to bad things such as incorrect page
map/reference counts or invalid memory references.
To fix, expand the use of i_mmap_rwsem as follows:
- i_mmap_rwsem is held in read mode whenever huge_pmd_share is called.
huge_pmd_share is only called via huge_pte_alloc, so callers of
huge_pte_alloc take i_mmap_rwsem before calling. In addition, callers
of huge_pte_alloc continue to hold the semaphore until finished with
the ptep.
- i_mmap_rwsem is held in write mode whenever huge_pmd_unshare is called.
One problem with this scheme is that it requires taking i_mmap_rwsem
before taking the page lock during page faults. This is not the order
specified in the rest of mm code. Handling of hugetlbfs pages is mostly
isolated today. Therefore, we use this alternative locking order for
PageHuge() pages.
mapping->i_mmap_rwsem
hugetlb_fault_mutex (hugetlbfs specific page fault mutex)
page->flags PG_locked (lock_page)
To help with lock ordering issues, hugetlb_page_mapping_lock_write() is
introduced to write lock the i_mmap_rwsem associated with a page.
In most cases it is easy to get address_space via vma->vm_file->f_mapping.
However, in the case of migration or memory errors for anon pages we do
not have an associated vma. A new routine _get_hugetlb_page_mapping()
will use anon_vma to get address_space in these cases.
Signed-off-by: Mike Kravetz <mike.kravetz@oracle.com>
Signed-off-by: Andrew Morton <akpm@linux-foundation.org>
Cc: Michal Hocko <mhocko@kernel.org>
Cc: Hugh Dickins <hughd@google.com>
Cc: Naoya Horiguchi <n-horiguchi@ah.jp.nec.com>
Cc: "Aneesh Kumar K . V" <aneesh.kumar@linux.vnet.ibm.com>
Cc: Andrea Arcangeli <aarcange@redhat.com>
Cc: "Kirill A . Shutemov" <kirill.shutemov@linux.intel.com>
Cc: Davidlohr Bueso <dave@stgolabs.net>
Cc: Prakash Sangappa <prakash.sangappa@oracle.com>
Link: http://lkml.kernel.org/r/20200316205756.146666-2-mike.kravetz@oracle.com
Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
2020-04-02 07:11:05 +03:00
i_mmap_unlock_read ( mapping ) ;
2017-02-23 02:42:55 +03:00
goto out_unlock ;
}
err = hugetlb_mcopy_atomic_pte ( dst_mm , dst_pte , dst_vma ,
dst_addr , src_addr , & page ) ;
mutex_unlock ( & hugetlb_fault_mutex_table [ hash ] ) ;
hugetlbfs: use i_mmap_rwsem for more pmd sharing synchronization
Patch series "hugetlbfs: use i_mmap_rwsem for more synchronization", v2.
While discussing the issue with huge_pte_offset [1], I remembered that
there were more outstanding hugetlb races. These issues are:
1) For shared pmds, huge PTE pointers returned by huge_pte_alloc can become
invalid via a call to huge_pmd_unshare by another thread.
2) hugetlbfs page faults can race with truncation causing invalid global
reserve counts and state.
A previous attempt was made to use i_mmap_rwsem in this manner as
described at [2]. However, those patches were reverted starting with [3]
due to locking issues.
To effectively use i_mmap_rwsem to address the above issues it needs to be
held (in read mode) during page fault processing. However, during fault
processing we need to lock the page we will be adding. Lock ordering
requires we take page lock before i_mmap_rwsem. Waiting until after
taking the page lock is too late in the fault process for the
synchronization we want to do.
To address this lock ordering issue, the following patches change the lock
ordering for hugetlb pages. This is not too invasive as hugetlbfs
processing is done separate from core mm in many places. However, I don't
really like this idea. Much ugliness is contained in the new routine
hugetlb_page_mapping_lock_write() of patch 1.
The only other way I can think of to address these issues is by catching
all the races. After catching a race, cleanup, backout, retry ... etc,
as needed. This can get really ugly, especially for huge page
reservations. At one time, I started writing some of the reservation
backout code for page faults and it got so ugly and complicated I went
down the path of adding synchronization to avoid the races. Any other
suggestions would be welcome.
[1] https://lore.kernel.org/linux-mm/1582342427-230392-1-git-send-email-longpeng2@huawei.com/
[2] https://lore.kernel.org/linux-mm/20181222223013.22193-1-mike.kravetz@oracle.com/
[3] https://lore.kernel.org/linux-mm/20190103235452.29335-1-mike.kravetz@oracle.com
[4] https://lore.kernel.org/linux-mm/1584028670.7365.182.camel@lca.pw/
[5] https://lore.kernel.org/lkml/20200312183142.108df9ac@canb.auug.org.au/
This patch (of 2):
While looking at BUGs associated with invalid huge page map counts, it was
discovered and observed that a huge pte pointer could become 'invalid' and
point to another task's page table. Consider the following:
A task takes a page fault on a shared hugetlbfs file and calls
huge_pte_alloc to get a ptep. Suppose the returned ptep points to a
shared pmd.
Now, another task truncates the hugetlbfs file. As part of truncation, it
unmaps everyone who has the file mapped. If the range being truncated is
covered by a shared pmd, huge_pmd_unshare will be called. For all but the
last user of the shared pmd, huge_pmd_unshare will clear the pud pointing
to the pmd. If the task in the middle of the page fault is not the last
user, the ptep returned by huge_pte_alloc now points to another task's
page table or worse. This leads to bad things such as incorrect page
map/reference counts or invalid memory references.
To fix, expand the use of i_mmap_rwsem as follows:
- i_mmap_rwsem is held in read mode whenever huge_pmd_share is called.
huge_pmd_share is only called via huge_pte_alloc, so callers of
huge_pte_alloc take i_mmap_rwsem before calling. In addition, callers
of huge_pte_alloc continue to hold the semaphore until finished with
the ptep.
- i_mmap_rwsem is held in write mode whenever huge_pmd_unshare is called.
One problem with this scheme is that it requires taking i_mmap_rwsem
before taking the page lock during page faults. This is not the order
specified in the rest of mm code. Handling of hugetlbfs pages is mostly
isolated today. Therefore, we use this alternative locking order for
PageHuge() pages.
mapping->i_mmap_rwsem
hugetlb_fault_mutex (hugetlbfs specific page fault mutex)
page->flags PG_locked (lock_page)
To help with lock ordering issues, hugetlb_page_mapping_lock_write() is
introduced to write lock the i_mmap_rwsem associated with a page.
In most cases it is easy to get address_space via vma->vm_file->f_mapping.
However, in the case of migration or memory errors for anon pages we do
not have an associated vma. A new routine _get_hugetlb_page_mapping()
will use anon_vma to get address_space in these cases.
Signed-off-by: Mike Kravetz <mike.kravetz@oracle.com>
Signed-off-by: Andrew Morton <akpm@linux-foundation.org>
Cc: Michal Hocko <mhocko@kernel.org>
Cc: Hugh Dickins <hughd@google.com>
Cc: Naoya Horiguchi <n-horiguchi@ah.jp.nec.com>
Cc: "Aneesh Kumar K . V" <aneesh.kumar@linux.vnet.ibm.com>
Cc: Andrea Arcangeli <aarcange@redhat.com>
Cc: "Kirill A . Shutemov" <kirill.shutemov@linux.intel.com>
Cc: Davidlohr Bueso <dave@stgolabs.net>
Cc: Prakash Sangappa <prakash.sangappa@oracle.com>
Link: http://lkml.kernel.org/r/20200316205756.146666-2-mike.kravetz@oracle.com
Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
2020-04-02 07:11:05 +03:00
i_mmap_unlock_read ( mapping ) ;
2017-02-23 02:43:43 +03:00
vm_alloc_shared = vm_shared ;
2017-02-23 02:42:55 +03:00
cond_resched ( ) ;
2018-12-01 01:09:25 +03:00
if ( unlikely ( err = = - ENOENT ) ) {
2020-06-09 07:33:25 +03:00
mmap_read_unlock ( dst_mm ) ;
2017-02-23 02:42:55 +03:00
BUG_ON ( ! page ) ;
err = copy_huge_page_from_user ( page ,
( const void __user * ) src_addr ,
2019-12-01 04:57:49 +03:00
vma_hpagesize / PAGE_SIZE ,
true ) ;
2017-02-23 02:42:55 +03:00
if ( unlikely ( err ) ) {
err = - EFAULT ;
goto out ;
}
2020-06-09 07:33:25 +03:00
mmap_read_lock ( dst_mm ) ;
2017-02-23 02:42:55 +03:00
dst_vma = NULL ;
goto retry ;
} else
BUG_ON ( page ) ;
if ( ! err ) {
dst_addr + = vma_hpagesize ;
src_addr + = vma_hpagesize ;
copied + = vma_hpagesize ;
if ( fatal_signal_pending ( current ) )
err = - EINTR ;
}
if ( err )
break ;
}
out_unlock :
2020-06-09 07:33:25 +03:00
mmap_read_unlock ( dst_mm ) ;
2017-02-23 02:42:55 +03:00
out :
2017-02-23 02:43:16 +03:00
if ( page ) {
/*
* We encountered an error and are about to free a newly
2017-02-23 02:43:43 +03:00
* allocated huge page .
*
* Reservation handling is very subtle , and is different for
* private and shared mappings . See the routine
* restore_reserve_on_error for details . Unfortunately , we
* can not call restore_reserve_on_error now as it would
2020-06-09 07:33:54 +03:00
* require holding mmap_lock .
2017-02-23 02:43:43 +03:00
*
* If a reservation for the page existed in the reservation
* map of a private mapping , the map was modified to indicate
* the reservation was consumed when the page was allocated .
* We clear the PagePrivate flag now so that the global
2017-02-23 02:43:16 +03:00
* reserve count will not be incremented in free_huge_page .
* The reservation map will still indicate the reservation
* was consumed and possibly prevent later page allocation .
2017-02-23 02:43:43 +03:00
* This is better than leaking a global reservation . If no
* reservation existed , it is still safe to clear PagePrivate
* as no adjustments to reservation counts were made during
* allocation .
*
* The reservation map for shared mappings indicates which
* pages have reservations . When a huge page is allocated
* for an address with a reservation , no change is made to
* the reserve map . In this case PagePrivate will be set
* to indicate that the global reservation count should be
* incremented when the page is freed . This is the desired
* behavior . However , when a huge page is allocated for an
* address without a reservation a reservation entry is added
* to the reservation map , and PagePrivate will not be set .
* When the page is freed , the global reserve count will NOT
* be incremented and it will appear as though we have leaked
* reserved page . In this case , set PagePrivate so that the
* global reserve count will be incremented to match the
* reservation map entry which was created .
*
* Note that vm_alloc_shared is based on the flags of the vma
* for which the page was originally allocated . dst_vma could
* be different or NULL on error .
2017-02-23 02:43:16 +03:00
*/
2017-02-23 02:43:43 +03:00
if ( vm_alloc_shared )
SetPagePrivate ( page ) ;
else
ClearPagePrivate ( page ) ;
2017-02-23 02:42:55 +03:00
put_page ( page ) ;
2017-02-23 02:43:16 +03:00
}
2017-02-23 02:42:55 +03:00
BUG_ON ( copied < 0 ) ;
BUG_ON ( err > 0 ) ;
BUG_ON ( ! copied & & ! err ) ;
return copied ? copied : err ;
}
# else /* !CONFIG_HUGETLB_PAGE */
/* fail at build time if gcc attempts to use this */
extern ssize_t __mcopy_atomic_hugetlb ( struct mm_struct * dst_mm ,
struct vm_area_struct * dst_vma ,
unsigned long dst_start ,
unsigned long src_start ,
unsigned long len ,
bool zeropage ) ;
# endif /* CONFIG_HUGETLB_PAGE */
2017-09-07 02:23:06 +03:00
static __always_inline ssize_t mfill_atomic_pte ( struct mm_struct * dst_mm ,
pmd_t * dst_pmd ,
struct vm_area_struct * dst_vma ,
unsigned long dst_addr ,
unsigned long src_addr ,
struct page * * page ,
2020-04-07 06:05:41 +03:00
bool zeropage ,
bool wp_copy )
2017-09-07 02:23:06 +03:00
{
ssize_t err ;
2018-12-01 01:09:28 +03:00
/*
* The normal page fault path for a shmem will invoke the
* fault , fill the hole in the file and COW it right away . The
* result generates plain anonymous memory . So when we are
* asked to fill an hole in a MAP_PRIVATE shmem mapping , we ' ll
* generate anonymous memory directly without actually filling
* the hole . For the MAP_PRIVATE case the robustness check
* only happens in the pagetable ( to verify it ' s still none )
* and not in the radix tree .
*/
if ( ! ( dst_vma - > vm_flags & VM_SHARED ) ) {
2017-09-07 02:23:06 +03:00
if ( ! zeropage )
err = mcopy_atomic_pte ( dst_mm , dst_pmd , dst_vma ,
2020-04-07 06:05:41 +03:00
dst_addr , src_addr , page ,
wp_copy ) ;
2017-09-07 02:23:06 +03:00
else
err = mfill_zeropage_pte ( dst_mm , dst_pmd ,
dst_vma , dst_addr ) ;
} else {
2020-04-07 06:05:41 +03:00
VM_WARN_ON_ONCE ( wp_copy ) ;
2017-09-07 02:23:09 +03:00
if ( ! zeropage )
2017-09-07 02:23:06 +03:00
err = shmem_mcopy_atomic_pte ( dst_mm , dst_pmd ,
dst_vma , dst_addr ,
src_addr , page ) ;
2017-09-07 02:23:09 +03:00
else
err = shmem_mfill_zeropage_pte ( dst_mm , dst_pmd ,
dst_vma , dst_addr ) ;
2017-09-07 02:23:06 +03:00
}
return err ;
}
2015-09-05 01:47:04 +03:00
static __always_inline ssize_t __mcopy_atomic ( struct mm_struct * dst_mm ,
unsigned long dst_start ,
unsigned long src_start ,
unsigned long len ,
userfaultfd: prevent non-cooperative events vs mcopy_atomic races
If a process monitored with userfaultfd changes it's memory mappings or
forks() at the same time as uffd monitor fills the process memory with
UFFDIO_COPY, the actual creation of page table entries and copying of
the data in mcopy_atomic may happen either before of after the memory
mapping modifications and there is no way for the uffd monitor to
maintain consistent view of the process memory layout.
For instance, let's consider fork() running in parallel with
userfaultfd_copy():
process | uffd monitor
---------------------------------+------------------------------
fork() | userfaultfd_copy()
... | ...
dup_mmap() | down_read(mmap_sem)
down_write(mmap_sem) | /* create PTEs, copy data */
dup_uffd() | up_read(mmap_sem)
copy_page_range() |
up_write(mmap_sem) |
dup_uffd_complete() |
/* notify monitor */ |
If the userfaultfd_copy() takes the mmap_sem first, the new page(s) will
be present by the time copy_page_range() is called and they will appear
in the child's memory mappings. However, if the fork() is the first to
take the mmap_sem, the new pages won't be mapped in the child's address
space.
If the pages are not present and child tries to access them, the monitor
will get page fault notification and everything is fine. However, if
the pages *are present*, the child can access them without uffd
noticing. And if we copy them into child it'll see the wrong data.
Since we are talking about background copy, we'd need to decide whether
the pages should be copied or not regardless #PF notifications.
Since userfaultfd monitor has no way to determine what was the order,
let's disallow userfaultfd_copy in parallel with the non-cooperative
events. In such case we return -EAGAIN and the uffd monitor can
understand that userfaultfd_copy() clashed with a non-cooperative event
and take an appropriate action.
Link: http://lkml.kernel.org/r/1527061324-19949-1-git-send-email-rppt@linux.vnet.ibm.com
Signed-off-by: Mike Rapoport <rppt@linux.vnet.ibm.com>
Acked-by: Pavel Emelyanov <xemul@virtuozzo.com>
Cc: Andrea Arcangeli <aarcange@redhat.com>
Cc: Mike Kravetz <mike.kravetz@oracle.com>
Cc: Andrei Vagin <avagin@virtuozzo.com>
Signed-off-by: Andrew Morton <akpm@linux-foundation.org>
Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
2018-06-08 03:09:25 +03:00
bool zeropage ,
2020-04-07 06:05:41 +03:00
bool * mmap_changing ,
__u64 mode )
2015-09-05 01:47:04 +03:00
{
struct vm_area_struct * dst_vma ;
ssize_t err ;
pmd_t * dst_pmd ;
unsigned long src_addr , dst_addr ;
2015-09-05 01:47:08 +03:00
long copied ;
struct page * page ;
2020-04-07 06:05:41 +03:00
bool wp_copy ;
2015-09-05 01:47:04 +03:00
/*
* Sanitize the command parameters :
*/
BUG_ON ( dst_start & ~ PAGE_MASK ) ;
BUG_ON ( len & ~ PAGE_MASK ) ;
/* Does the address range wrap, or is the span zero-sized? */
BUG_ON ( src_start + len < = src_start ) ;
BUG_ON ( dst_start + len < = dst_start ) ;
2015-09-05 01:47:08 +03:00
src_addr = src_start ;
dst_addr = dst_start ;
copied = 0 ;
page = NULL ;
retry :
2020-06-09 07:33:25 +03:00
mmap_read_lock ( dst_mm ) ;
2015-09-05 01:47:04 +03:00
userfaultfd: prevent non-cooperative events vs mcopy_atomic races
If a process monitored with userfaultfd changes it's memory mappings or
forks() at the same time as uffd monitor fills the process memory with
UFFDIO_COPY, the actual creation of page table entries and copying of
the data in mcopy_atomic may happen either before of after the memory
mapping modifications and there is no way for the uffd monitor to
maintain consistent view of the process memory layout.
For instance, let's consider fork() running in parallel with
userfaultfd_copy():
process | uffd monitor
---------------------------------+------------------------------
fork() | userfaultfd_copy()
... | ...
dup_mmap() | down_read(mmap_sem)
down_write(mmap_sem) | /* create PTEs, copy data */
dup_uffd() | up_read(mmap_sem)
copy_page_range() |
up_write(mmap_sem) |
dup_uffd_complete() |
/* notify monitor */ |
If the userfaultfd_copy() takes the mmap_sem first, the new page(s) will
be present by the time copy_page_range() is called and they will appear
in the child's memory mappings. However, if the fork() is the first to
take the mmap_sem, the new pages won't be mapped in the child's address
space.
If the pages are not present and child tries to access them, the monitor
will get page fault notification and everything is fine. However, if
the pages *are present*, the child can access them without uffd
noticing. And if we copy them into child it'll see the wrong data.
Since we are talking about background copy, we'd need to decide whether
the pages should be copied or not regardless #PF notifications.
Since userfaultfd monitor has no way to determine what was the order,
let's disallow userfaultfd_copy in parallel with the non-cooperative
events. In such case we return -EAGAIN and the uffd monitor can
understand that userfaultfd_copy() clashed with a non-cooperative event
and take an appropriate action.
Link: http://lkml.kernel.org/r/1527061324-19949-1-git-send-email-rppt@linux.vnet.ibm.com
Signed-off-by: Mike Rapoport <rppt@linux.vnet.ibm.com>
Acked-by: Pavel Emelyanov <xemul@virtuozzo.com>
Cc: Andrea Arcangeli <aarcange@redhat.com>
Cc: Mike Kravetz <mike.kravetz@oracle.com>
Cc: Andrei Vagin <avagin@virtuozzo.com>
Signed-off-by: Andrew Morton <akpm@linux-foundation.org>
Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
2018-06-08 03:09:25 +03:00
/*
* If memory mappings are changing because of non - cooperative
* operation ( e . g . mremap ) running in parallel , bail out and
* request the user to retry later
*/
err = - EAGAIN ;
if ( mmap_changing & & READ_ONCE ( * mmap_changing ) )
goto out_unlock ;
2015-09-05 01:47:04 +03:00
/*
* Make sure the vma is not shared , that the dst range is
* both valid and fully within a single existing vma .
*/
2017-02-25 01:58:28 +03:00
err = - ENOENT ;
2019-12-01 04:57:55 +03:00
dst_vma = find_dst_vma ( dst_mm , dst_start , len ) ;
2017-02-23 02:43:34 +03:00
if ( ! dst_vma )
goto out_unlock ;
2015-09-05 01:47:04 +03:00
2017-02-25 01:58:28 +03:00
err = - EINVAL ;
/*
* shmem_zero_setup is invoked in mmap for MAP_ANONYMOUS | MAP_SHARED but
* it will overwrite vm_ops , so vma_is_anonymous must return false .
*/
if ( WARN_ON_ONCE ( vma_is_anonymous ( dst_vma ) & &
dst_vma - > vm_flags & VM_SHARED ) )
goto out_unlock ;
2020-04-07 06:05:41 +03:00
/*
* validate ' mode ' now that we know the dst_vma : don ' t allow
* a wrprotect copy if the userfaultfd didn ' t register as WP .
*/
wp_copy = mode & UFFDIO_COPY_MODE_WP ;
if ( wp_copy & & ! ( dst_vma - > vm_flags & VM_UFFD_WP ) )
goto out_unlock ;
2017-02-23 02:42:55 +03:00
/*
* If this is a HUGETLB vma , pass off to appropriate routine
*/
if ( is_vm_hugetlb_page ( dst_vma ) )
return __mcopy_atomic_hugetlb ( dst_mm , dst_vma , dst_start ,
src_start , len , zeropage ) ;
2017-02-23 02:43:34 +03:00
if ( ! vma_is_anonymous ( dst_vma ) & & ! vma_is_shmem ( dst_vma ) )
2015-09-05 01:47:08 +03:00
goto out_unlock ;
2015-09-05 01:47:04 +03:00
/*
* Ensure the dst_vma has a anon_vma or this page
* would get a NULL anon_vma when moved in the
* dst_vma .
*/
err = - ENOMEM ;
2018-12-01 01:09:28 +03:00
if ( ! ( dst_vma - > vm_flags & VM_SHARED ) & &
unlikely ( anon_vma_prepare ( dst_vma ) ) )
2015-09-05 01:47:08 +03:00
goto out_unlock ;
2015-09-05 01:47:04 +03:00
2015-09-05 01:47:08 +03:00
while ( src_addr < src_start + len ) {
2015-09-05 01:47:04 +03:00
pmd_t dst_pmdval ;
2015-09-05 01:47:08 +03:00
2015-09-05 01:47:04 +03:00
BUG_ON ( dst_addr > = dst_start + len ) ;
2015-09-05 01:47:08 +03:00
2015-09-05 01:47:04 +03:00
dst_pmd = mm_alloc_pmd ( dst_mm , dst_addr ) ;
if ( unlikely ( ! dst_pmd ) ) {
err = - ENOMEM ;
break ;
}
dst_pmdval = pmd_read_atomic ( dst_pmd ) ;
/*
* If the dst_pmd is mapped as THP don ' t
* override it and just be strict .
*/
if ( unlikely ( pmd_trans_huge ( dst_pmdval ) ) ) {
err = - EEXIST ;
break ;
}
if ( unlikely ( pmd_none ( dst_pmdval ) ) & &
mm: treewide: remove unused address argument from pte_alloc functions
Patch series "Add support for fast mremap".
This series speeds up the mremap(2) syscall by copying page tables at
the PMD level even for non-THP systems. There is concern that the extra
'address' argument that mremap passes to pte_alloc may do something
subtle architecture related in the future that may make the scheme not
work. Also we find that there is no point in passing the 'address' to
pte_alloc since its unused. This patch therefore removes this argument
tree-wide resulting in a nice negative diff as well. Also ensuring
along the way that the enabled architectures do not do anything funky
with the 'address' argument that goes unnoticed by the optimization.
Build and boot tested on x86-64. Build tested on arm64. The config
enablement patch for arm64 will be posted in the future after more
testing.
The changes were obtained by applying the following Coccinelle script.
(thanks Julia for answering all Coccinelle questions!).
Following fix ups were done manually:
* Removal of address argument from pte_fragment_alloc
* Removal of pte_alloc_one_fast definitions from m68k and microblaze.
// Options: --include-headers --no-includes
// Note: I split the 'identifier fn' line, so if you are manually
// running it, please unsplit it so it runs for you.
virtual patch
@pte_alloc_func_def depends on patch exists@
identifier E2;
identifier fn =~
"^(__pte_alloc|pte_alloc_one|pte_alloc|__pte_alloc_kernel|pte_alloc_one_kernel)$";
type T2;
@@
fn(...
- , T2 E2
)
{ ... }
@pte_alloc_func_proto_noarg depends on patch exists@
type T1, T2, T3, T4;
identifier fn =~ "^(__pte_alloc|pte_alloc_one|pte_alloc|__pte_alloc_kernel|pte_alloc_one_kernel)$";
@@
(
- T3 fn(T1, T2);
+ T3 fn(T1);
|
- T3 fn(T1, T2, T4);
+ T3 fn(T1, T2);
)
@pte_alloc_func_proto depends on patch exists@
identifier E1, E2, E4;
type T1, T2, T3, T4;
identifier fn =~
"^(__pte_alloc|pte_alloc_one|pte_alloc|__pte_alloc_kernel|pte_alloc_one_kernel)$";
@@
(
- T3 fn(T1 E1, T2 E2);
+ T3 fn(T1 E1);
|
- T3 fn(T1 E1, T2 E2, T4 E4);
+ T3 fn(T1 E1, T2 E2);
)
@pte_alloc_func_call depends on patch exists@
expression E2;
identifier fn =~
"^(__pte_alloc|pte_alloc_one|pte_alloc|__pte_alloc_kernel|pte_alloc_one_kernel)$";
@@
fn(...
-, E2
)
@pte_alloc_macro depends on patch exists@
identifier fn =~
"^(__pte_alloc|pte_alloc_one|pte_alloc|__pte_alloc_kernel|pte_alloc_one_kernel)$";
identifier a, b, c;
expression e;
position p;
@@
(
- #define fn(a, b, c) e
+ #define fn(a, b) e
|
- #define fn(a, b) e
+ #define fn(a) e
)
Link: http://lkml.kernel.org/r/20181108181201.88826-2-joelaf@google.com
Signed-off-by: Joel Fernandes (Google) <joel@joelfernandes.org>
Suggested-by: Kirill A. Shutemov <kirill@shutemov.name>
Acked-by: Kirill A. Shutemov <kirill@shutemov.name>
Cc: Michal Hocko <mhocko@kernel.org>
Cc: Julia Lawall <Julia.Lawall@lip6.fr>
Cc: Kirill A. Shutemov <kirill@shutemov.name>
Cc: William Kucharski <william.kucharski@oracle.com>
Signed-off-by: Andrew Morton <akpm@linux-foundation.org>
Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
2019-01-04 02:28:34 +03:00
unlikely ( __pte_alloc ( dst_mm , dst_pmd ) ) ) {
2015-09-05 01:47:04 +03:00
err = - ENOMEM ;
break ;
}
/* If an huge pmd materialized from under us fail */
if ( unlikely ( pmd_trans_huge ( * dst_pmd ) ) ) {
err = - EFAULT ;
break ;
}
BUG_ON ( pmd_none ( * dst_pmd ) ) ;
BUG_ON ( pmd_trans_huge ( * dst_pmd ) ) ;
2017-09-07 02:23:06 +03:00
err = mfill_atomic_pte ( dst_mm , dst_pmd , dst_vma , dst_addr ,
2020-04-07 06:05:41 +03:00
src_addr , & page , zeropage , wp_copy ) ;
2015-09-05 01:47:04 +03:00
cond_resched ( ) ;
2018-12-01 01:09:25 +03:00
if ( unlikely ( err = = - ENOENT ) ) {
2015-09-05 01:47:08 +03:00
void * page_kaddr ;
2020-06-09 07:33:25 +03:00
mmap_read_unlock ( dst_mm ) ;
2015-09-05 01:47:08 +03:00
BUG_ON ( ! page ) ;
page_kaddr = kmap ( page ) ;
err = copy_from_user ( page_kaddr ,
( const void __user * ) src_addr ,
PAGE_SIZE ) ;
kunmap ( page ) ;
if ( unlikely ( err ) ) {
err = - EFAULT ;
goto out ;
}
goto retry ;
} else
BUG_ON ( page ) ;
2015-09-05 01:47:04 +03:00
if ( ! err ) {
dst_addr + = PAGE_SIZE ;
src_addr + = PAGE_SIZE ;
copied + = PAGE_SIZE ;
if ( fatal_signal_pending ( current ) )
err = - EINTR ;
}
if ( err )
break ;
}
2015-09-05 01:47:08 +03:00
out_unlock :
2020-06-09 07:33:25 +03:00
mmap_read_unlock ( dst_mm ) ;
2015-09-05 01:47:08 +03:00
out :
if ( page )
mm, fs: get rid of PAGE_CACHE_* and page_cache_{get,release} macros
PAGE_CACHE_{SIZE,SHIFT,MASK,ALIGN} macros were introduced *long* time
ago with promise that one day it will be possible to implement page
cache with bigger chunks than PAGE_SIZE.
This promise never materialized. And unlikely will.
We have many places where PAGE_CACHE_SIZE assumed to be equal to
PAGE_SIZE. And it's constant source of confusion on whether
PAGE_CACHE_* or PAGE_* constant should be used in a particular case,
especially on the border between fs and mm.
Global switching to PAGE_CACHE_SIZE != PAGE_SIZE would cause to much
breakage to be doable.
Let's stop pretending that pages in page cache are special. They are
not.
The changes are pretty straight-forward:
- <foo> << (PAGE_CACHE_SHIFT - PAGE_SHIFT) -> <foo>;
- <foo> >> (PAGE_CACHE_SHIFT - PAGE_SHIFT) -> <foo>;
- PAGE_CACHE_{SIZE,SHIFT,MASK,ALIGN} -> PAGE_{SIZE,SHIFT,MASK,ALIGN};
- page_cache_get() -> get_page();
- page_cache_release() -> put_page();
This patch contains automated changes generated with coccinelle using
script below. For some reason, coccinelle doesn't patch header files.
I've called spatch for them manually.
The only adjustment after coccinelle is revert of changes to
PAGE_CAHCE_ALIGN definition: we are going to drop it later.
There are few places in the code where coccinelle didn't reach. I'll
fix them manually in a separate patch. Comments and documentation also
will be addressed with the separate patch.
virtual patch
@@
expression E;
@@
- E << (PAGE_CACHE_SHIFT - PAGE_SHIFT)
+ E
@@
expression E;
@@
- E >> (PAGE_CACHE_SHIFT - PAGE_SHIFT)
+ E
@@
@@
- PAGE_CACHE_SHIFT
+ PAGE_SHIFT
@@
@@
- PAGE_CACHE_SIZE
+ PAGE_SIZE
@@
@@
- PAGE_CACHE_MASK
+ PAGE_MASK
@@
expression E;
@@
- PAGE_CACHE_ALIGN(E)
+ PAGE_ALIGN(E)
@@
expression E;
@@
- page_cache_get(E)
+ get_page(E)
@@
expression E;
@@
- page_cache_release(E)
+ put_page(E)
Signed-off-by: Kirill A. Shutemov <kirill.shutemov@linux.intel.com>
Acked-by: Michal Hocko <mhocko@suse.com>
Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
2016-04-01 15:29:47 +03:00
put_page ( page ) ;
2015-09-05 01:47:04 +03:00
BUG_ON ( copied < 0 ) ;
BUG_ON ( err > 0 ) ;
BUG_ON ( ! copied & & ! err ) ;
return copied ? copied : err ;
}
ssize_t mcopy_atomic ( struct mm_struct * dst_mm , unsigned long dst_start ,
userfaultfd: prevent non-cooperative events vs mcopy_atomic races
If a process monitored with userfaultfd changes it's memory mappings or
forks() at the same time as uffd monitor fills the process memory with
UFFDIO_COPY, the actual creation of page table entries and copying of
the data in mcopy_atomic may happen either before of after the memory
mapping modifications and there is no way for the uffd monitor to
maintain consistent view of the process memory layout.
For instance, let's consider fork() running in parallel with
userfaultfd_copy():
process | uffd monitor
---------------------------------+------------------------------
fork() | userfaultfd_copy()
... | ...
dup_mmap() | down_read(mmap_sem)
down_write(mmap_sem) | /* create PTEs, copy data */
dup_uffd() | up_read(mmap_sem)
copy_page_range() |
up_write(mmap_sem) |
dup_uffd_complete() |
/* notify monitor */ |
If the userfaultfd_copy() takes the mmap_sem first, the new page(s) will
be present by the time copy_page_range() is called and they will appear
in the child's memory mappings. However, if the fork() is the first to
take the mmap_sem, the new pages won't be mapped in the child's address
space.
If the pages are not present and child tries to access them, the monitor
will get page fault notification and everything is fine. However, if
the pages *are present*, the child can access them without uffd
noticing. And if we copy them into child it'll see the wrong data.
Since we are talking about background copy, we'd need to decide whether
the pages should be copied or not regardless #PF notifications.
Since userfaultfd monitor has no way to determine what was the order,
let's disallow userfaultfd_copy in parallel with the non-cooperative
events. In such case we return -EAGAIN and the uffd monitor can
understand that userfaultfd_copy() clashed with a non-cooperative event
and take an appropriate action.
Link: http://lkml.kernel.org/r/1527061324-19949-1-git-send-email-rppt@linux.vnet.ibm.com
Signed-off-by: Mike Rapoport <rppt@linux.vnet.ibm.com>
Acked-by: Pavel Emelyanov <xemul@virtuozzo.com>
Cc: Andrea Arcangeli <aarcange@redhat.com>
Cc: Mike Kravetz <mike.kravetz@oracle.com>
Cc: Andrei Vagin <avagin@virtuozzo.com>
Signed-off-by: Andrew Morton <akpm@linux-foundation.org>
Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
2018-06-08 03:09:25 +03:00
unsigned long src_start , unsigned long len ,
2020-04-07 06:05:41 +03:00
bool * mmap_changing , __u64 mode )
2015-09-05 01:47:04 +03:00
{
userfaultfd: prevent non-cooperative events vs mcopy_atomic races
If a process monitored with userfaultfd changes it's memory mappings or
forks() at the same time as uffd monitor fills the process memory with
UFFDIO_COPY, the actual creation of page table entries and copying of
the data in mcopy_atomic may happen either before of after the memory
mapping modifications and there is no way for the uffd monitor to
maintain consistent view of the process memory layout.
For instance, let's consider fork() running in parallel with
userfaultfd_copy():
process | uffd monitor
---------------------------------+------------------------------
fork() | userfaultfd_copy()
... | ...
dup_mmap() | down_read(mmap_sem)
down_write(mmap_sem) | /* create PTEs, copy data */
dup_uffd() | up_read(mmap_sem)
copy_page_range() |
up_write(mmap_sem) |
dup_uffd_complete() |
/* notify monitor */ |
If the userfaultfd_copy() takes the mmap_sem first, the new page(s) will
be present by the time copy_page_range() is called and they will appear
in the child's memory mappings. However, if the fork() is the first to
take the mmap_sem, the new pages won't be mapped in the child's address
space.
If the pages are not present and child tries to access them, the monitor
will get page fault notification and everything is fine. However, if
the pages *are present*, the child can access them without uffd
noticing. And if we copy them into child it'll see the wrong data.
Since we are talking about background copy, we'd need to decide whether
the pages should be copied or not regardless #PF notifications.
Since userfaultfd monitor has no way to determine what was the order,
let's disallow userfaultfd_copy in parallel with the non-cooperative
events. In such case we return -EAGAIN and the uffd monitor can
understand that userfaultfd_copy() clashed with a non-cooperative event
and take an appropriate action.
Link: http://lkml.kernel.org/r/1527061324-19949-1-git-send-email-rppt@linux.vnet.ibm.com
Signed-off-by: Mike Rapoport <rppt@linux.vnet.ibm.com>
Acked-by: Pavel Emelyanov <xemul@virtuozzo.com>
Cc: Andrea Arcangeli <aarcange@redhat.com>
Cc: Mike Kravetz <mike.kravetz@oracle.com>
Cc: Andrei Vagin <avagin@virtuozzo.com>
Signed-off-by: Andrew Morton <akpm@linux-foundation.org>
Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
2018-06-08 03:09:25 +03:00
return __mcopy_atomic ( dst_mm , dst_start , src_start , len , false ,
2020-04-07 06:05:41 +03:00
mmap_changing , mode ) ;
2015-09-05 01:47:04 +03:00
}
ssize_t mfill_zeropage ( struct mm_struct * dst_mm , unsigned long start ,
userfaultfd: prevent non-cooperative events vs mcopy_atomic races
If a process monitored with userfaultfd changes it's memory mappings or
forks() at the same time as uffd monitor fills the process memory with
UFFDIO_COPY, the actual creation of page table entries and copying of
the data in mcopy_atomic may happen either before of after the memory
mapping modifications and there is no way for the uffd monitor to
maintain consistent view of the process memory layout.
For instance, let's consider fork() running in parallel with
userfaultfd_copy():
process | uffd monitor
---------------------------------+------------------------------
fork() | userfaultfd_copy()
... | ...
dup_mmap() | down_read(mmap_sem)
down_write(mmap_sem) | /* create PTEs, copy data */
dup_uffd() | up_read(mmap_sem)
copy_page_range() |
up_write(mmap_sem) |
dup_uffd_complete() |
/* notify monitor */ |
If the userfaultfd_copy() takes the mmap_sem first, the new page(s) will
be present by the time copy_page_range() is called and they will appear
in the child's memory mappings. However, if the fork() is the first to
take the mmap_sem, the new pages won't be mapped in the child's address
space.
If the pages are not present and child tries to access them, the monitor
will get page fault notification and everything is fine. However, if
the pages *are present*, the child can access them without uffd
noticing. And if we copy them into child it'll see the wrong data.
Since we are talking about background copy, we'd need to decide whether
the pages should be copied or not regardless #PF notifications.
Since userfaultfd monitor has no way to determine what was the order,
let's disallow userfaultfd_copy in parallel with the non-cooperative
events. In such case we return -EAGAIN and the uffd monitor can
understand that userfaultfd_copy() clashed with a non-cooperative event
and take an appropriate action.
Link: http://lkml.kernel.org/r/1527061324-19949-1-git-send-email-rppt@linux.vnet.ibm.com
Signed-off-by: Mike Rapoport <rppt@linux.vnet.ibm.com>
Acked-by: Pavel Emelyanov <xemul@virtuozzo.com>
Cc: Andrea Arcangeli <aarcange@redhat.com>
Cc: Mike Kravetz <mike.kravetz@oracle.com>
Cc: Andrei Vagin <avagin@virtuozzo.com>
Signed-off-by: Andrew Morton <akpm@linux-foundation.org>
Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
2018-06-08 03:09:25 +03:00
unsigned long len , bool * mmap_changing )
2015-09-05 01:47:04 +03:00
{
2020-04-07 06:05:41 +03:00
return __mcopy_atomic ( dst_mm , start , 0 , len , true , mmap_changing , 0 ) ;
2015-09-05 01:47:04 +03:00
}
2020-04-07 06:06:09 +03:00
int mwriteprotect_range ( struct mm_struct * dst_mm , unsigned long start ,
unsigned long len , bool enable_wp , bool * mmap_changing )
{
struct vm_area_struct * dst_vma ;
pgprot_t newprot ;
int err ;
/*
* Sanitize the command parameters :
*/
BUG_ON ( start & ~ PAGE_MASK ) ;
BUG_ON ( len & ~ PAGE_MASK ) ;
/* Does the address range wrap, or is the span zero-sized? */
BUG_ON ( start + len < = start ) ;
2020-06-09 07:33:25 +03:00
mmap_read_lock ( dst_mm ) ;
2020-04-07 06:06:09 +03:00
/*
* If memory mappings are changing because of non - cooperative
* operation ( e . g . mremap ) running in parallel , bail out and
* request the user to retry later
*/
err = - EAGAIN ;
if ( mmap_changing & & READ_ONCE ( * mmap_changing ) )
goto out_unlock ;
err = - ENOENT ;
dst_vma = find_dst_vma ( dst_mm , start , len ) ;
/*
* Make sure the vma is not shared , that the dst range is
* both valid and fully within a single existing vma .
*/
if ( ! dst_vma | | ( dst_vma - > vm_flags & VM_SHARED ) )
goto out_unlock ;
if ( ! userfaultfd_wp ( dst_vma ) )
goto out_unlock ;
if ( ! vma_is_anonymous ( dst_vma ) )
goto out_unlock ;
if ( enable_wp )
newprot = vm_get_page_prot ( dst_vma - > vm_flags & ~ ( VM_WRITE ) ) ;
else
newprot = vm_get_page_prot ( dst_vma - > vm_flags ) ;
change_protection ( dst_vma , start , start + len , newprot ,
enable_wp ? MM_CP_UFFD_WP : MM_CP_UFFD_WP_RESOLVE ) ;
err = 0 ;
out_unlock :
2020-06-09 07:33:25 +03:00
mmap_read_unlock ( dst_mm ) ;
2020-04-07 06:06:09 +03:00
return err ;
}