Skip to content

Commit c86aa7b

Browse files
mjkravetztorvalds
authored andcommitted
hugetlbfs: Use i_mmap_rwsem to fix page fault/truncate race
hugetlbfs page faults can race with truncate and hole punch operations. Current code in the page fault path attempts to handle this by 'backing out' operations if we encounter the race. One obvious omission in the current code is removing a page newly added to the page cache. This is pretty straight forward to address, but there is a more subtle and difficult issue of backing out hugetlb reservations. To handle this correctly, the 'reservation state' before page allocation needs to be noted so that it can be properly backed out. There are four distinct possibilities for reservation state: shared/reserved, shared/no-resv, private/reserved and private/no-resv. Backing out a reservation may require memory allocation which could fail so that needs to be taken into account as well. Instead of writing the required complicated code for this rare occurrence, just eliminate the race. i_mmap_rwsem is now held in read mode for the duration of page fault processing. Hold i_mmap_rwsem longer in truncation and hold punch code to cover the call to remove_inode_hugepages. With this modification, code in remove_inode_hugepages checking for races becomes 'dead' as it can not longer happen. Remove the dead code and expand comments to explain reasoning. Similarly, checks for races with truncation in the page fault path can be simplified and removed. [mike.kravetz@oracle.com: incorporat suggestions from Kirill] Link: http://lkml.kernel.org/r/20181222223013.22193-3-mike.kravetz@oracle.com Link: http://lkml.kernel.org/r/20181218223557.5202-3-mike.kravetz@oracle.com Fixes: ebed4bf ("hugetlb: fix absurd HugePages_Rsvd") Signed-off-by: Mike Kravetz <mike.kravetz@oracle.com> Acked-by: Kirill A. Shutemov <kirill.shutemov@linux.intel.com> 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: Davidlohr Bueso <dave@stgolabs.net> Cc: Prakash Sangappa <prakash.sangappa@oracle.com> Cc: <stable@vger.kernel.org> Signed-off-by: Andrew Morton <akpm@linux-foundation.org> Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
1 parent b43a999 commit c86aa7b

File tree

2 files changed

+38
-44
lines changed

2 files changed

+38
-44
lines changed

fs/hugetlbfs/inode.c

Lines changed: 28 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -383,17 +383,16 @@ hugetlb_vmdelete_list(struct rb_root_cached *root, pgoff_t start, pgoff_t end)
383383
* truncation is indicated by end of range being LLONG_MAX
384384
* In this case, we first scan the range and release found pages.
385385
* After releasing pages, hugetlb_unreserve_pages cleans up region/reserv
386-
* maps and global counts. Page faults can not race with truncation
387-
* in this routine. hugetlb_no_page() prevents page faults in the
388-
* truncated range. It checks i_size before allocation, and again after
389-
* with the page table lock for the page held. The same lock must be
390-
* acquired to unmap a page.
386+
* maps and global counts.
391387
* hole punch is indicated if end is not LLONG_MAX
392388
* In the hole punch case we scan the range and release found pages.
393389
* Only when releasing a page is the associated region/reserv map
394390
* deleted. The region/reserv map for ranges without associated
395-
* pages are not modified. Page faults can race with hole punch.
396-
* This is indicated if we find a mapped page.
391+
* pages are not modified.
392+
*
393+
* Callers of this routine must hold the i_mmap_rwsem in write mode to prevent
394+
* races with page faults.
395+
*
397396
* Note: If the passed end of range value is beyond the end of file, but
398397
* not LLONG_MAX this routine still performs a hole punch operation.
399398
*/
@@ -423,32 +422,14 @@ static void remove_inode_hugepages(struct inode *inode, loff_t lstart,
423422

424423
for (i = 0; i < pagevec_count(&pvec); ++i) {
425424
struct page *page = pvec.pages[i];
426-
u32 hash;
427425

428426
index = page->index;
429-
hash = hugetlb_fault_mutex_hash(h, current->mm,
430-
&pseudo_vma,
431-
mapping, index, 0);
432-
mutex_lock(&hugetlb_fault_mutex_table[hash]);
433-
434427
/*
435-
* If page is mapped, it was faulted in after being
436-
* unmapped in caller. Unmap (again) now after taking
437-
* the fault mutex. The mutex will prevent faults
438-
* until we finish removing the page.
439-
*
440-
* This race can only happen in the hole punch case.
441-
* Getting here in a truncate operation is a bug.
428+
* A mapped page is impossible as callers should unmap
429+
* all references before calling. And, i_mmap_rwsem
430+
* prevents the creation of additional mappings.
442431
*/
443-
if (unlikely(page_mapped(page))) {
444-
BUG_ON(truncate_op);
445-
446-
i_mmap_lock_write(mapping);
447-
hugetlb_vmdelete_list(&mapping->i_mmap,
448-
index * pages_per_huge_page(h),
449-
(index + 1) * pages_per_huge_page(h));
450-
i_mmap_unlock_write(mapping);
451-
}
432+
VM_BUG_ON(page_mapped(page));
452433

453434
lock_page(page);
454435
/*
@@ -470,7 +451,6 @@ static void remove_inode_hugepages(struct inode *inode, loff_t lstart,
470451
}
471452

472453
unlock_page(page);
473-
mutex_unlock(&hugetlb_fault_mutex_table[hash]);
474454
}
475455
huge_pagevec_release(&pvec);
476456
cond_resched();
@@ -482,9 +462,20 @@ static void remove_inode_hugepages(struct inode *inode, loff_t lstart,
482462

483463
static void hugetlbfs_evict_inode(struct inode *inode)
484464
{
465+
struct address_space *mapping = inode->i_mapping;
485466
struct resv_map *resv_map;
486467

468+
/*
469+
* The vfs layer guarantees that there are no other users of this
470+
* inode. Therefore, it would be safe to call remove_inode_hugepages
471+
* without holding i_mmap_rwsem. We acquire and hold here to be
472+
* consistent with other callers. Since there will be no contention
473+
* on the semaphore, overhead is negligible.
474+
*/
475+
i_mmap_lock_write(mapping);
487476
remove_inode_hugepages(inode, 0, LLONG_MAX);
477+
i_mmap_unlock_write(mapping);
478+
488479
resv_map = (struct resv_map *)inode->i_mapping->private_data;
489480
/* root inode doesn't have the resv_map, so we should check it */
490481
if (resv_map)
@@ -505,8 +496,8 @@ static int hugetlb_vmtruncate(struct inode *inode, loff_t offset)
505496
i_mmap_lock_write(mapping);
506497
if (!RB_EMPTY_ROOT(&mapping->i_mmap.rb_root))
507498
hugetlb_vmdelete_list(&mapping->i_mmap, pgoff, 0);
508-
i_mmap_unlock_write(mapping);
509499
remove_inode_hugepages(inode, offset, LLONG_MAX);
500+
i_mmap_unlock_write(mapping);
510501
return 0;
511502
}
512503

@@ -540,8 +531,8 @@ static long hugetlbfs_punch_hole(struct inode *inode, loff_t offset, loff_t len)
540531
hugetlb_vmdelete_list(&mapping->i_mmap,
541532
hole_start >> PAGE_SHIFT,
542533
hole_end >> PAGE_SHIFT);
543-
i_mmap_unlock_write(mapping);
544534
remove_inode_hugepages(inode, hole_start, hole_end);
535+
i_mmap_unlock_write(mapping);
545536
inode_unlock(inode);
546537
}
547538

@@ -624,7 +615,11 @@ static long hugetlbfs_fallocate(struct file *file, int mode, loff_t offset,
624615
/* addr is the offset within the file (zero based) */
625616
addr = index * hpage_size;
626617

627-
/* mutex taken here, fault path and hole punch */
618+
/*
619+
* fault mutex taken here, protects against fault path
620+
* and hole punch. inode_lock previously taken protects
621+
* against truncation.
622+
*/
628623
hash = hugetlb_fault_mutex_hash(h, mm, &pseudo_vma, mapping,
629624
index, addr);
630625
mutex_lock(&hugetlb_fault_mutex_table[hash]);

mm/hugetlb.c

Lines changed: 10 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -3755,16 +3755,16 @@ static vm_fault_t hugetlb_no_page(struct mm_struct *mm,
37553755
}
37563756

37573757
/*
3758-
* Use page lock to guard against racing truncation
3759-
* before we get page_table_lock.
3758+
* We can not race with truncation due to holding i_mmap_rwsem.
3759+
* Check once here for faults beyond end of file.
37603760
*/
3761+
size = i_size_read(mapping->host) >> huge_page_shift(h);
3762+
if (idx >= size)
3763+
goto out;
3764+
37613765
retry:
37623766
page = find_lock_page(mapping, idx);
37633767
if (!page) {
3764-
size = i_size_read(mapping->host) >> huge_page_shift(h);
3765-
if (idx >= size)
3766-
goto out;
3767-
37683768
/*
37693769
* Check for page in userfault range
37703770
*/
@@ -3854,9 +3854,6 @@ static vm_fault_t hugetlb_no_page(struct mm_struct *mm,
38543854
}
38553855

38563856
ptl = huge_pte_lock(h, mm, ptep);
3857-
size = i_size_read(mapping->host) >> huge_page_shift(h);
3858-
if (idx >= size)
3859-
goto backout;
38603857

38613858
ret = 0;
38623859
if (!huge_pte_none(huge_ptep_get(ptep)))
@@ -3959,8 +3956,10 @@ vm_fault_t hugetlb_fault(struct mm_struct *mm, struct vm_area_struct *vma,
39593956

39603957
/*
39613958
* Acquire i_mmap_rwsem before calling huge_pte_alloc and hold
3962-
* until finished with ptep. This prevents huge_pmd_unshare from
3963-
* being called elsewhere and making the ptep no longer valid.
3959+
* until finished with ptep. This serves two purposes:
3960+
* 1) It prevents huge_pmd_unshare from being called elsewhere
3961+
* and making the ptep no longer valid.
3962+
* 2) It synchronizes us with file truncation.
39643963
*
39653964
* ptep could have already be assigned via huge_pte_offset. That
39663965
* is OK, as huge_pte_alloc will return the same value unless

0 commit comments

Comments
 (0)