diff options
author | Hugh Dickins <hugh@veritas.com> | 2004-06-04 20:52:39 -0700 |
---|---|---|
committer | Linus Torvalds <torvalds@ppc970.osdl.org> | 2004-06-04 20:52:39 -0700 |
commit | ebd6867f019324dfef20af06a2238c2b37641fa9 (patch) | |
tree | ab7d46775ce18d5a41e5cbde42ca187600c32271 /mm | |
parent | a888f1f5a259fc9ee58d207560af30eb81dc220b (diff) | |
download | history-ebd6867f019324dfef20af06a2238c2b37641fa9.tar.gz |
[PATCH] mm: get_user_pages vs. try_to_unmap
Andrea Arcangeli's fix to an ironic weakness with get_user_pages.
try_to_unmap_one must check page_count against page->mapcount before unmapping
a swapcache page: because the raised pagecount by which get_user_pages ensures
the page cannot be freed, will cause any write fault to see that page as not
exclusively owned, and therefore a copy page will be substituted for it - the
reverse of what's intended.
rmap.c was entirely free of such page_count heuristics before, I tried hard to
avoid putting this in. But Andrea's fix rarely gives a false positive; and
although it might be nicer to change exclusive_swap_page etc. to rely on
page->mapcount instead, it seems likely that we'll want to get rid of
page->mapcount later, so better not to entrench its use.
Signed-off-by: Hugh Dickins <hugh@veritas.com>
Signed-off-by: Andrew Morton <akpm@osdl.org>
Signed-off-by: Linus Torvalds <torvalds@osdl.org>
Diffstat (limited to 'mm')
-rw-r--r-- | mm/rmap.c | 17 |
1 files changed, 17 insertions, 0 deletions
diff --git a/mm/rmap.c b/mm/rmap.c index 871e76b9c25e20..cd1b579746e40e 100644 --- a/mm/rmap.c +++ b/mm/rmap.c @@ -485,6 +485,23 @@ static int try_to_unmap_one(struct page *page, struct vm_area_struct *vma) goto out_unmap; } + /* + * Don't pull an anonymous page out from under get_user_pages. + * GUP carefully breaks COW and raises page count (while holding + * page_table_lock, as we have here) to make sure that the page + * cannot be freed. If we unmap that page here, a user write + * access to the virtual address will bring back the page, but + * its raised count will (ironically) be taken to mean it's not + * an exclusive swap page, do_wp_page will replace it by a copy + * page, and the user never get to see the data GUP was holding + * the original page for. + */ + if (PageSwapCache(page) && + page_count(page) != page->mapcount + 2) { + ret = SWAP_FAIL; + goto out_unmap; + } + /* Nuke the page table entry. */ flush_cache_page(vma, address); pteval = ptep_clear_flush(vma, address, pte); |