]> git.hungrycats.org Git - linux/commitdiff
[PATCH] mm: get_user_pages vs. try_to_unmap
authorHugh Dickins <hugh@veritas.com>
Sat, 5 Jun 2004 03:52:39 +0000 (20:52 -0700)
committerLinus Torvalds <torvalds@ppc970.osdl.org>
Sat, 5 Jun 2004 03:52:39 +0000 (20:52 -0700)
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>
mm/rmap.c

index 871e76b9c25e20895cde181f08307e3800af5923..cd1b579746e40e914b6e833f7d0f420e8c3844f4 100644 (file)
--- 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);