Skip to content

Commit 51f4c7e

Browse files
cmetcalf-tileragregkh
authored andcommitted
hugetlb: fix race condition in hugetlb_fault()
commit 66aebce upstream. The race is as follows: Suppose a multi-threaded task forks a new process (on cpu A), thus bumping up the ref count on all the pages. While the fork is occurring (and thus we have marked all the PTEs as read-only), another thread in the original process (on cpu B) tries to write to a huge page, taking an access violation from the write-protect and calling hugetlb_cow(). Now, suppose the fork() fails. It will undo the COW and decrement the ref count on the pages, so the ref count on the huge page drops back to 1. Meanwhile hugetlb_cow() also decrements the ref count by one on the original page, since the original address space doesn't need it any more, having copied a new page to replace the original page. This leaves the ref count at zero, and when we call unlock_page(), we panic. fork on CPU A fault on CPU B ============= ============== ... down_write(&parent->mmap_sem); down_write_nested(&child->mmap_sem); ... while duplicating vmas if error break; ... up_write(&child->mmap_sem); up_write(&parent->mmap_sem); ... down_read(&parent->mmap_sem); ... lock_page(page); handle COW page_mapcount(old_page) == 2 alloc and prepare new_page ... handle error page_remove_rmap(page); put_page(page); ... fold new_page into pte page_remove_rmap(page); put_page(page); ... oops ==> unlock_page(page); up_read(&parent->mmap_sem); The solution is to take an extra reference to the page while we are holding the lock on it. Signed-off-by: Chris Metcalf <[email protected]> Cc: Hillf Danton <[email protected]> Cc: Michal Hocko <[email protected]> Cc: KAMEZAWA Hiroyuki <[email protected]> Cc: Hugh Dickins <[email protected]> Signed-off-by: Andrew Morton <[email protected]> Signed-off-by: Linus Torvalds <[email protected]> Signed-off-by: Greg Kroah-Hartman <[email protected]>
1 parent e180414 commit 51f4c7e

File tree

1 file changed

+2
-0
lines changed

1 file changed

+2
-0
lines changed

mm/hugetlb.c

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2686,6 +2686,7 @@ int hugetlb_fault(struct mm_struct *mm, struct vm_area_struct *vma,
26862686
* so no worry about deadlock.
26872687
*/
26882688
page = pte_page(entry);
2689+
get_page(page);
26892690
if (page != pagecache_page)
26902691
lock_page(page);
26912692

@@ -2717,6 +2718,7 @@ int hugetlb_fault(struct mm_struct *mm, struct vm_area_struct *vma,
27172718
}
27182719
if (page != pagecache_page)
27192720
unlock_page(page);
2721+
put_page(page);
27202722

27212723
out_mutex:
27222724
mutex_unlock(&hugetlb_instantiation_mutex);

0 commit comments

Comments
 (0)