ARM: Flush user mapping on VIVT processors when copying a page
authorRussell King <rmk+kernel@arm.linux.org.uk>
Mon, 5 Oct 2009 14:34:22 +0000 (15:34 +0100)
committerRussell King <rmk+kernel@arm.linux.org.uk>
Mon, 5 Oct 2009 14:42:16 +0000 (15:42 +0100)
Steven Walter <stevenrwalter@gmail.com> writes:
> I've been tracking down an instance of userspace data corruption,
> and I believe I have found a window during fork where data can be
> lost.  The corruption is occurring on an ARMv5 system with VIVT
> caches.  Here's the scenario in question.  Thread A is forking,
> Thread B is running in userspace:
>
> Thread A: flush_cache_mm() (dup_mmap)
> Thread B: writes to a page in the above mm
> Thread A: pte_wrprotect() the above page (copy_one_pte)
> Thread B: writes to the same page again
>
> During thread B's second write, he'll take a fault and enter the
> do_wp_page() case.  We'll end up calling copy_page(), which notably
> uses the kernel virtual addresses for the old and new pages.  This
> means that the new page does not necessarily have the data from the
> first write.  Now there are two conflicting copies of the same
> cache-line in dcache.  If the userspace cache-line flushes before
> the kernel cache-line, we lose the changes made during the first
> write.  do_wp_page does call flush_dcache_page on the newly-copied
> page, but there's still a window where the CPU could flush the
> userspace cache-line before then.

Resolve this by flushing the user mapping before copying the page
on processors with a writeback VIVT cache.

Note: this does have a performance impact, and so needs further
consideration before being merged - can we optimize out some of
the cache flushes if, eg, we know that the page isn't yet mapped?

Thread: <e06498070903061426o5875ad13hc6328aa0d3f08ed7@mail.gmail.com>
Signed-off-by: Russell King <rmk+kernel@arm.linux.org.uk>
arch/arm/mm/copypage-feroceon.c
arch/arm/mm/copypage-v4wb.c
arch/arm/mm/copypage-xsc3.c

index e2e8d29c548b1a24d3bdc0b0a09654026b68200d..5eb4fd93893dfa113cddf7944eeb063d968965a2 100644 (file)
@@ -74,6 +74,7 @@ void feroceon_copy_user_highpage(struct page *to, struct page *from,
 
        kto = kmap_atomic(to, KM_USER0);
        kfrom = kmap_atomic(from, KM_USER1);
+       flush_cache_page(vma, vaddr, page_to_pfn(from));
        feroceon_copy_user_page(kto, kfrom);
        kunmap_atomic(kfrom, KM_USER1);
        kunmap_atomic(kto, KM_USER0);
index e9920f68b76b634accab85db4f469495dc553bcf..7c2eb55cd4a9263c9d3eb3e17d590cac0cc5d9b3 100644 (file)
@@ -54,6 +54,7 @@ void v4wb_copy_user_highpage(struct page *to, struct page *from,
 
        kto = kmap_atomic(to, KM_USER0);
        kfrom = kmap_atomic(from, KM_USER1);
+       flush_cache_page(vma, vaddr, page_to_pfn(from));
        v4wb_copy_user_page(kto, kfrom);
        kunmap_atomic(kfrom, KM_USER1);
        kunmap_atomic(kto, KM_USER0);
index 18ae05d5829c58512dd1c73639efb9041b4d8986..747ad4140fc75527d9c7b7409caf98e59504894d 100644 (file)
@@ -77,6 +77,7 @@ void xsc3_mc_copy_user_highpage(struct page *to, struct page *from,
 
        kto = kmap_atomic(to, KM_USER0);
        kfrom = kmap_atomic(from, KM_USER1);
+       flush_cache_page(vma, vaddr, page_to_pfn(from));
        xsc3_mc_copy_user_page(kto, kfrom);
        kunmap_atomic(kfrom, KM_USER1);
        kunmap_atomic(kto, KM_USER0);