Skip to content

Commit ebd6867

Browse files
Hugh DickinsLinus Torvalds
authored andcommitted
[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>
1 parent a888f1f commit ebd6867

File tree

1 file changed

+17
-0
lines changed

1 file changed

+17
-0
lines changed

mm/rmap.c

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -485,6 +485,23 @@ static int try_to_unmap_one(struct page *page, struct vm_area_struct *vma)
485485
goto out_unmap;
486486
}
487487

488+
/*
489+
* Don't pull an anonymous page out from under get_user_pages.
490+
* GUP carefully breaks COW and raises page count (while holding
491+
* page_table_lock, as we have here) to make sure that the page
492+
* cannot be freed. If we unmap that page here, a user write
493+
* access to the virtual address will bring back the page, but
494+
* its raised count will (ironically) be taken to mean it's not
495+
* an exclusive swap page, do_wp_page will replace it by a copy
496+
* page, and the user never get to see the data GUP was holding
497+
* the original page for.
498+
*/
499+
if (PageSwapCache(page) &&
500+
page_count(page) != page->mapcount + 2) {
501+
ret = SWAP_FAIL;
502+
goto out_unmap;
503+
}
504+
488505
/* Nuke the page table entry. */
489506
flush_cache_page(vma, address);
490507
pteval = ptep_clear_flush(vma, address, pte);

0 commit comments

Comments
 (0)