Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux

MIPS: Fix race condition in lazy cache flushing.

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.

[ralf@linux-mips.org: Folded in patch 9346 to fix highmem issue.]

Signed-off-by: Lars Persson <larper@axis.com>
Cc: linux-mips@linux-mips.org
Cc: paul.burton@imgtec.com
Cc: linux-kernel@vger.kernel.org
Patchwork: https://patchwork.linux-mips.org/patch/9346/
Patchwork: https://patchwork.linux-mips.org/patch/9738/
Signed-off-by: Ralf Baechle <ralf@linux-mips.org>

authored by

Lars Persson and committed by
Ralf Baechle
4d46a67a 5b9593f3

+35 -15
+23 -15
arch/mips/include/asm/cacheflush.h
··· 29 29 * - flush_icache_all() flush the entire instruction cache 30 30 * - flush_data_cache_page() flushes a page from the data cache 31 31 */ 32 + 33 + /* 34 + * This flag is used to indicate that the page pointed to by a pte 35 + * is dirty and requires cleaning before returning it to the user. 36 + */ 37 + #define PG_dcache_dirty PG_arch_1 38 + 39 + #define Page_dcache_dirty(page) \ 40 + test_bit(PG_dcache_dirty, &(page)->flags) 41 + #define SetPageDcacheDirty(page) \ 42 + set_bit(PG_dcache_dirty, &(page)->flags) 43 + #define ClearPageDcacheDirty(page) \ 44 + clear_bit(PG_dcache_dirty, &(page)->flags) 45 + 32 46 extern void (*flush_cache_all)(void); 33 47 extern void (*__flush_cache_all)(void); 34 48 extern void (*flush_cache_mm)(struct mm_struct *mm); ··· 51 37 unsigned long start, unsigned long end); 52 38 extern void (*flush_cache_page)(struct vm_area_struct *vma, unsigned long page, unsigned long pfn); 53 39 extern void __flush_dcache_page(struct page *page); 40 + extern void __flush_icache_page(struct vm_area_struct *vma, struct page *page); 54 41 55 42 #define ARCH_IMPLEMENTS_FLUSH_DCACHE_PAGE 1 56 43 static inline void flush_dcache_page(struct page *page) 57 44 { 58 - if (cpu_has_dc_aliases || !cpu_has_ic_fills_f_dc) 45 + if (cpu_has_dc_aliases) 59 46 __flush_dcache_page(page); 60 - 47 + else if (!cpu_has_ic_fills_f_dc) 48 + SetPageDcacheDirty(page); 61 49 } 62 50 63 51 #define flush_dcache_mmap_lock(mapping) do { } while (0) ··· 77 61 static inline void flush_icache_page(struct vm_area_struct *vma, 78 62 struct page *page) 79 63 { 64 + if (!cpu_has_ic_fills_f_dc && (vma->vm_flags & VM_EXEC) && 65 + Page_dcache_dirty(page)) { 66 + __flush_icache_page(vma, page); 67 + ClearPageDcacheDirty(page); 68 + } 80 69 } 81 70 82 71 extern void (*flush_icache_range)(unsigned long start, unsigned long end); ··· 115 94 extern void (*flush_icache_all)(void); 116 95 extern void (*local_flush_data_cache_page)(void * addr); 117 96 extern void (*flush_data_cache_page)(unsigned long addr); 118 - 119 - /* 120 - * This flag is used to indicate that the page pointed to by a pte 121 - * is dirty and requires cleaning before returning it to the user. 122 - */ 123 - #define PG_dcache_dirty PG_arch_1 124 - 125 - #define Page_dcache_dirty(page) \ 126 - test_bit(PG_dcache_dirty, &(page)->flags) 127 - #define SetPageDcacheDirty(page) \ 128 - set_bit(PG_dcache_dirty, &(page)->flags) 129 - #define ClearPageDcacheDirty(page) \ 130 - clear_bit(PG_dcache_dirty, &(page)->flags) 131 97 132 98 /* Run kernel code uncached, useful for cache probing functions. */ 133 99 unsigned long run_uncached(void *func);
+12
arch/mips/mm/cache.c
··· 119 119 120 120 EXPORT_SYMBOL(__flush_anon_page); 121 121 122 + void __flush_icache_page(struct vm_area_struct *vma, struct page *page) 123 + { 124 + unsigned long addr; 125 + 126 + if (PageHighMem(page)) 127 + return; 128 + 129 + addr = (unsigned long) page_address(page); 130 + flush_data_cache_page(addr); 131 + } 132 + EXPORT_SYMBOL_GPL(__flush_icache_page); 133 + 122 134 void __update_cache(struct vm_area_struct *vma, unsigned long address, 123 135 pte_t pte) 124 136 {