linux-mips
[Top] [All Lists]

[PATCH 2/2] MIPS: Fix race condition in lazy cache flushing.

To: linux-mips@linux-mips.org, ralf@linux-mips.org, paul.burton@imgtec.com
Subject: [PATCH 2/2] MIPS: Fix race condition in lazy cache flushing.
From: Lars Persson <lars.persson@axis.com>
Date: Thu, 26 Feb 2015 14:16:03 +0100
Cc: linux-kernel@vger.kernel.org, Lars Persson <larper@axis.com>
In-reply-to: <1424956563-29535-1-git-send-email-larper@axis.com>
List-archive: <http://www.linux-mips.org/archives/linux-mips/>
List-help: <mailto:ecartis@linux-mips.org?Subject=help>
List-id: linux-mips <linux-mips.eddie.linux-mips.org>
List-owner: <mailto:ralf@linux-mips.org>
List-post: <mailto:linux-mips@linux-mips.org>
List-software: Ecartis version 1.0.0
List-subscribe: <mailto:ecartis@linux-mips.org?subject=subscribe%20linux-mips>
List-unsubscribe: <mailto:ecartis@linux-mips.org?subject=unsubscribe%20linux-mips>
Original-recipient: rfc822;linux-mips@linux-mips.org
References: <1424956563-29535-1-git-send-email-larper@axis.com>
Sender: linux-mips-bounce@linux-mips.org
The lazy cache flushing implemented in the MIPS kernel suffers from a
race condition that is exposed by do_set_pte() in mm/memory.c.

A pre-condition is a file-system that writes to the page from the CPU
in its readpage method and then calls flush_dcache_page(). One example
is ubifs. Another pre-condition is that the dcache flush is postponed
in __flush_dcache_page().

Upon a page fault for an executable mapping not existing in the
page-cache, the following will happen:
1. Write to the page
2. flush_dcache_page
3. flush_icache_page
4. set_pte_at
5. update_mmu_cache (commits the flush of a dcache-dirty page)

Between steps 4 and 5 another thread can hit the same page and it will
encounter a valid pte. Because the data still is in the L1 dcache the CPU
will fetch stale data from L2 into the icache and execute garbage.

This fix moves the commit of the cache flush to step 3 to close the
race window. It also reduces the amount of flushes on non-executable
mappings because we never enter __flush_dcache_page() for non-aliasing
CPUs.

Regressions can occur in drivers that mistakenly relies on the
flush_dcache_page() in get_user_pages() for DMA operations.

Signed-off-by: Lars Persson <larper@axis.com>
---
 arch/mips/include/asm/cacheflush.h |   35 ++++++++++++++++++++---------------
 1 file changed, 20 insertions(+), 15 deletions(-)

diff --git a/arch/mips/include/asm/cacheflush.h 
b/arch/mips/include/asm/cacheflush.h
index e08381a..37d5cf9 100644
--- a/arch/mips/include/asm/cacheflush.h
+++ b/arch/mips/include/asm/cacheflush.h
@@ -29,6 +29,20 @@
  *  - flush_icache_all() flush the entire instruction cache
  *  - flush_data_cache_page() flushes a page from the data cache
  */
+
+ /*
+ * This flag is used to indicate that the page pointed to by a pte
+ * is dirty and requires cleaning before returning it to the user.
+ */
+#define PG_dcache_dirty                        PG_arch_1
+
+#define Page_dcache_dirty(page)                \
+       test_bit(PG_dcache_dirty, &(page)->flags)
+#define SetPageDcacheDirty(page)       \
+       set_bit(PG_dcache_dirty, &(page)->flags)
+#define ClearPageDcacheDirty(page)     \
+       clear_bit(PG_dcache_dirty, &(page)->flags)
+
 extern void (*flush_cache_all)(void);
 extern void (*__flush_cache_all)(void);
 extern void (*flush_cache_mm)(struct mm_struct *mm);
@@ -41,9 +55,10 @@ extern void __flush_dcache_page(struct page *page);
 #define ARCH_IMPLEMENTS_FLUSH_DCACHE_PAGE 1
 static inline void flush_dcache_page(struct page *page)
 {
-       if (cpu_has_dc_aliases || !cpu_has_ic_fills_f_dc)
+       if (cpu_has_dc_aliases)
                __flush_dcache_page(page);
-
+       else if (!cpu_has_ic_fills_f_dc)
+               SetPageDcacheDirty(page);
 }
 
 #define flush_dcache_mmap_lock(mapping)                do { } while (0)
@@ -61,6 +76,9 @@ static inline void flush_anon_page(struct vm_area_struct *vma,
 static inline void flush_icache_page(struct vm_area_struct *vma,
        struct page *page)
 {
+       if (!cpu_has_ic_fills_f_dc && (vma->vm_flags & VM_EXEC) &&
+           Page_dcache_dirty(page))
+               __flush_dcache_page(page);
 }
 
 extern void (*flush_icache_range)(unsigned long start, unsigned long end);
@@ -95,19 +113,6 @@ extern void (*flush_icache_all)(void);
 extern void (*local_flush_data_cache_page)(void * addr);
 extern void (*flush_data_cache_page)(unsigned long addr);
 
-/*
- * This flag is used to indicate that the page pointed to by a pte
- * is dirty and requires cleaning before returning it to the user.
- */
-#define PG_dcache_dirty                        PG_arch_1
-
-#define Page_dcache_dirty(page)                \
-       test_bit(PG_dcache_dirty, &(page)->flags)
-#define SetPageDcacheDirty(page)       \
-       set_bit(PG_dcache_dirty, &(page)->flags)
-#define ClearPageDcacheDirty(page)     \
-       clear_bit(PG_dcache_dirty, &(page)->flags)
-
 /* Run kernel code uncached, useful for cache probing functions. */
 unsigned long run_uncached(void *func);
 
-- 
1.7.10.4


<Prev in Thread] Current Thread [Next in Thread>