mirror of
https://github.com/AuxXxilium/linux_dsm_epyc7002.git
synced 2024-12-18 21:37:40 +07:00
37d22a0d79
It's possible for pages to become visible prior to update_mmu_cache running if a thread within the same address space preempts the current thread or runs simultaneously on another CPU. That is, the following scenario is possible: CPU0 CPU1 write to page flush_dcache_page flush_icache_page set_pte_at map page update_mmu_cache If CPU1 maps the page in between CPU0's set_pte_at, which marks it valid & visible, and update_mmu_cache where the dcache flush occurs then CPU1s icache will fill from stale data (unless it fills from the dcache, in which case all is good, but most MIPS CPUs don't have this property). Commit4d46a67a3e
("MIPS: Fix race condition in lazy cache flushing.") attempted to fix that by performing the dcache flush in flush_icache_page such that it occurs before the set_pte_at call makes the page visible. However it has the problem that not all code that writes to pages exposed to userland call flush_icache_page. There are many callers of set_pte_at under mm/ and only 2 of them do call flush_icache_page. Thus the race window between a page becoming visible & being coherent between the icache & dcache remains open in some cases. To illustrate some of the cases, a WARN was added to __update_cache with this patch applied that triggered in cases where a page about to be flushed from the dcache was not the last page provided to flush_icache_page. That is, backtraces were obtained for cases in which the race window is left open without this patch. The 2 standout examples follow. When forking a process: [ 15.271842] [<80417630>] __update_cache+0xcc/0x188 [ 15.277274] [<80530394>] copy_page_range+0x56c/0x6ac [ 15.282861] [<8042936c>] copy_process.part.54+0xd40/0x17ac [ 15.289028] [<80429f80>] do_fork+0xe4/0x420 [ 15.293747] [<80413808>] handle_sys+0x128/0x14c When exec'ing an ELF binary: [ 14.445964] [<80417630>] __update_cache+0xcc/0x188 [ 14.451369] [<80538d88>] move_page_tables+0x414/0x498 [ 14.457075] [<8055d848>] setup_arg_pages+0x220/0x318 [ 14.462685] [<805b0f38>] load_elf_binary+0x530/0x12a0 [ 14.468374] [<8055ec3c>] search_binary_handler+0xbc/0x214 [ 14.474444] [<8055f6c0>] do_execveat_common+0x43c/0x67c [ 14.480324] [<8055f938>] do_execve+0x38/0x44 [ 14.485137] [<80413808>] handle_sys+0x128/0x14c These code paths write into a page, call flush_dcache_page then call set_pte_at without flush_icache_page inbetween. The end result is that the icache can become corrupted & userland processes may execute unexpected or invalid code, typically resulting in a reserved instruction exception, a trap or a segfault. Fix this race condition fully by performing any cache maintenance required to keep the icache & dcache in sync in set_pte_at, before the page is made valid. This has the added bonus of ensuring the cache maintenance always happens in one location, rather than being duplicated in flush_icache_page & update_mmu_cache. It also matches the way other architectures solve the same problem (see arm, ia64 & powerpc). Signed-off-by: Paul Burton <paul.burton@imgtec.com> Reported-by: Ionela Voinescu <ionela.voinescu@imgtec.com> Cc: Lars Persson <lars.persson@axis.com> Fixes:4d46a67a3e
("MIPS: Fix race condition in lazy cache flushing.") Cc: Steven J. Hill <sjhill@realitydiluted.com> Cc: David Daney <david.daney@cavium.com> Cc: Huacai Chen <chenhc@lemote.com> Cc: Aneesh Kumar K.V <aneesh.kumar@linux.vnet.ibm.com> Cc: Andrew Morton <akpm@linux-foundation.org> Cc: Jerome Marchand <jmarchan@redhat.com> Cc: Kirill A. Shutemov <kirill.shutemov@linux.intel.com> Cc: linux-mips@linux-mips.org Cc: linux-kernel@vger.kernel.org Cc: stable <stable@vger.kernel.org> # v4.1+ Patchwork: https://patchwork.linux-mips.org/patch/12722/ Signed-off-by: Ralf Baechle <ralf@linux-mips.org>
151 lines
4.7 KiB
C
151 lines
4.7 KiB
C
/*
|
|
* This file is subject to the terms and conditions of the GNU General Public
|
|
* License. See the file "COPYING" in the main directory of this archive
|
|
* for more details.
|
|
*
|
|
* Copyright (C) 1994, 95, 96, 97, 98, 99, 2000, 01, 02, 03 by Ralf Baechle
|
|
* Copyright (C) 1999, 2000, 2001 Silicon Graphics, Inc.
|
|
*/
|
|
#ifndef _ASM_CACHEFLUSH_H
|
|
#define _ASM_CACHEFLUSH_H
|
|
|
|
/* Keep includes the same across arches. */
|
|
#include <linux/mm.h>
|
|
#include <asm/cpu-features.h>
|
|
|
|
/* Cache flushing:
|
|
*
|
|
* - flush_cache_all() flushes entire cache
|
|
* - flush_cache_mm(mm) flushes the specified mm context's cache lines
|
|
* - flush_cache_dup mm(mm) handles cache flushing when forking
|
|
* - flush_cache_page(mm, vmaddr, pfn) flushes a single page
|
|
* - flush_cache_range(vma, start, end) flushes a range of pages
|
|
* - flush_icache_range(start, end) flush a range of instructions
|
|
* - flush_dcache_page(pg) flushes(wback&invalidates) a page for dcache
|
|
*
|
|
* MIPS specific flush operations:
|
|
*
|
|
* - flush_cache_sigtramp() flush signal trampoline
|
|
* - 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);
|
|
#define flush_cache_dup_mm(mm) do { (void) (mm); } while (0)
|
|
extern void (*flush_cache_range)(struct vm_area_struct *vma,
|
|
unsigned long start, unsigned long end);
|
|
extern void (*flush_cache_page)(struct vm_area_struct *vma, unsigned long page, unsigned long pfn);
|
|
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)
|
|
__flush_dcache_page(page);
|
|
else if (!cpu_has_ic_fills_f_dc)
|
|
SetPageDcacheDirty(page);
|
|
}
|
|
|
|
#define flush_dcache_mmap_lock(mapping) do { } while (0)
|
|
#define flush_dcache_mmap_unlock(mapping) do { } while (0)
|
|
|
|
#define ARCH_HAS_FLUSH_ANON_PAGE
|
|
extern void __flush_anon_page(struct page *, unsigned long);
|
|
static inline void flush_anon_page(struct vm_area_struct *vma,
|
|
struct page *page, unsigned long vmaddr)
|
|
{
|
|
if (cpu_has_dc_aliases && PageAnon(page))
|
|
__flush_anon_page(page, vmaddr);
|
|
}
|
|
|
|
static inline void flush_icache_page(struct vm_area_struct *vma,
|
|
struct page *page)
|
|
{
|
|
}
|
|
|
|
extern void (*flush_icache_range)(unsigned long start, unsigned long end);
|
|
extern void (*local_flush_icache_range)(unsigned long start, unsigned long end);
|
|
|
|
extern void (*__flush_cache_vmap)(void);
|
|
|
|
static inline void flush_cache_vmap(unsigned long start, unsigned long end)
|
|
{
|
|
if (cpu_has_dc_aliases)
|
|
__flush_cache_vmap();
|
|
}
|
|
|
|
extern void (*__flush_cache_vunmap)(void);
|
|
|
|
static inline void flush_cache_vunmap(unsigned long start, unsigned long end)
|
|
{
|
|
if (cpu_has_dc_aliases)
|
|
__flush_cache_vunmap();
|
|
}
|
|
|
|
extern void copy_to_user_page(struct vm_area_struct *vma,
|
|
struct page *page, unsigned long vaddr, void *dst, const void *src,
|
|
unsigned long len);
|
|
|
|
extern void copy_from_user_page(struct vm_area_struct *vma,
|
|
struct page *page, unsigned long vaddr, void *dst, const void *src,
|
|
unsigned long len);
|
|
|
|
extern void (*flush_cache_sigtramp)(unsigned long addr);
|
|
extern void (*flush_icache_all)(void);
|
|
extern void (*local_flush_data_cache_page)(void * addr);
|
|
extern void (*flush_data_cache_page)(unsigned long addr);
|
|
|
|
/* Run kernel code uncached, useful for cache probing functions. */
|
|
unsigned long run_uncached(void *func);
|
|
|
|
extern void *kmap_coherent(struct page *page, unsigned long addr);
|
|
extern void kunmap_coherent(void);
|
|
extern void *kmap_noncoherent(struct page *page, unsigned long addr);
|
|
|
|
static inline void kunmap_noncoherent(void)
|
|
{
|
|
kunmap_coherent();
|
|
}
|
|
|
|
#define ARCH_HAS_FLUSH_KERNEL_DCACHE_PAGE
|
|
static inline void flush_kernel_dcache_page(struct page *page)
|
|
{
|
|
BUG_ON(cpu_has_dc_aliases && PageHighMem(page));
|
|
flush_dcache_page(page);
|
|
}
|
|
|
|
/*
|
|
* For now flush_kernel_vmap_range and invalidate_kernel_vmap_range both do a
|
|
* cache writeback and invalidate operation.
|
|
*/
|
|
extern void (*__flush_kernel_vmap_range)(unsigned long vaddr, int size);
|
|
|
|
static inline void flush_kernel_vmap_range(void *vaddr, int size)
|
|
{
|
|
if (cpu_has_dc_aliases)
|
|
__flush_kernel_vmap_range((unsigned long) vaddr, size);
|
|
}
|
|
|
|
static inline void invalidate_kernel_vmap_range(void *vaddr, int size)
|
|
{
|
|
if (cpu_has_dc_aliases)
|
|
__flush_kernel_vmap_range((unsigned long) vaddr, size);
|
|
}
|
|
|
|
#endif /* _ASM_CACHEFLUSH_H */
|