89 lines
2.6 KiB
Diff
89 lines
2.6 KiB
Diff
From: Chris Metcalf <cmetcalf@tilera.com>
|
|
Date: Thu, 12 Apr 2012 12:49:15 -0700
|
|
Subject: [PATCH] hugetlb: fix race condition in hugetlb_fault()
|
|
|
|
commit 66aebce747eaf9bc456bf1f1b217d8db843031d0 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 <cmetcalf@tilera.com>
|
|
Cc: Hillf Danton <dhillf@gmail.com>
|
|
Cc: Michal Hocko <mhocko@suse.cz>
|
|
Cc: KAMEZAWA Hiroyuki <kamezawa.hiroyu@jp.fujitsu.com>
|
|
Cc: Hugh Dickins <hughd@google.com>
|
|
Cc: <stable@vger.kernel.org>
|
|
Signed-off-by: Andrew Morton <akpm@linux-foundation.org>
|
|
Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
|
|
---
|
|
mm/hugetlb.c | 2 ++
|
|
1 file changed, 2 insertions(+)
|
|
|
|
diff --git a/mm/hugetlb.c b/mm/hugetlb.c
|
|
index b8ce6f4..cd65cb1 100644
|
|
--- a/mm/hugetlb.c
|
|
+++ b/mm/hugetlb.c
|
|
@@ -2791,6 +2791,7 @@ int hugetlb_fault(struct mm_struct *mm, struct vm_area_struct *vma,
|
|
* so no worry about deadlock.
|
|
*/
|
|
page = pte_page(entry);
|
|
+ get_page(page);
|
|
if (page != pagecache_page)
|
|
lock_page(page);
|
|
|
|
@@ -2822,6 +2823,7 @@ out_page_table_lock:
|
|
}
|
|
if (page != pagecache_page)
|
|
unlock_page(page);
|
|
+ put_page(page);
|
|
|
|
out_mutex:
|
|
mutex_unlock(&hugetlb_instantiation_mutex);
|
|
--
|
|
1.7.9.5
|
|
|