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

ARM: 8043/1: uprobes need icache flush after xol write

After instruction write into xol area, on ARM V7
architecture code need to flush dcache and icache to sync
them up for given set of addresses. Having just
'flush_dcache_page(page)' call is not enough - it is
possible to have stale instruction sitting in icache
for given xol area slot address.

Introduce arch_uprobe_ixol_copy weak function
that by default calls uprobes copy_to_page function and
than flush_dcache_page function and on ARM define new one
that handles xol slot copy in ARM specific way

flush_uprobe_xol_access function shares/reuses implementation
with/of flush_ptrace_access function and takes care of writing
instruction to user land address space on given variety of
different cache types on ARM CPUs. Because
flush_uprobe_xol_access does not have vma around
flush_ptrace_access was split into two parts. First that
retrieves set of condition from vma and common that receives
those conditions as flags.

Note ARM cache flush function need kernel address
through which instruction write happened, so instead
of using uprobes copy_to_page function changed
code to explicitly map page and do memcpy.

Note arch_uprobe_copy_ixol function, in similar way as
copy_to_user_page function, has preempt_disable/preempt_enable.

Signed-off-by: Victor Kamensky <victor.kamensky@linaro.org>
Acked-by: Oleg Nesterov <oleg@redhat.com>
Reviewed-by: David A. Long <dave.long@linaro.org>
Signed-off-by: Russell King <rmk+kernel@arm.linux.org.uk>

authored by

Victor Kamensky and committed by
Russell King
72e6ae28 166aaf39

+70 -13
+2
arch/arm/include/asm/cacheflush.h
··· 487 487 int set_memory_x(unsigned long addr, int numpages); 488 488 int set_memory_nx(unsigned long addr, int numpages); 489 489 490 + void flush_uprobe_xol_access(struct page *page, unsigned long uaddr, 491 + void *kaddr, unsigned long len); 490 492 #endif
+20
arch/arm/kernel/uprobes.c
··· 113 113 return 0; 114 114 } 115 115 116 + void arch_uprobe_copy_ixol(struct page *page, unsigned long vaddr, 117 + void *src, unsigned long len) 118 + { 119 + void *xol_page_kaddr = kmap_atomic(page); 120 + void *dst = xol_page_kaddr + (vaddr & ~PAGE_MASK); 121 + 122 + preempt_disable(); 123 + 124 + /* Initialize the slot */ 125 + memcpy(dst, src, len); 126 + 127 + /* flush caches (dcache/icache) */ 128 + flush_uprobe_xol_access(page, vaddr, dst, len); 129 + 130 + preempt_enable(); 131 + 132 + kunmap_atomic(xol_page_kaddr); 133 + } 134 + 135 + 116 136 int arch_uprobe_pre_xol(struct arch_uprobe *auprobe, struct pt_regs *regs) 117 137 { 118 138 struct uprobe_task *utask = current->utask;
+28 -5
arch/arm/mm/flush.c
··· 104 104 #define flush_icache_alias(pfn,vaddr,len) do { } while (0) 105 105 #endif 106 106 107 + #define FLAG_PA_IS_EXEC 1 108 + #define FLAG_PA_CORE_IN_MM 2 109 + 107 110 static void flush_ptrace_access_other(void *args) 108 111 { 109 112 __flush_icache_all(); 110 113 } 111 114 112 - static 113 - void flush_ptrace_access(struct vm_area_struct *vma, struct page *page, 114 - unsigned long uaddr, void *kaddr, unsigned long len) 115 + static inline 116 + void __flush_ptrace_access(struct page *page, unsigned long uaddr, void *kaddr, 117 + unsigned long len, unsigned int flags) 115 118 { 116 119 if (cache_is_vivt()) { 117 - if (cpumask_test_cpu(smp_processor_id(), mm_cpumask(vma->vm_mm))) { 120 + if (flags & FLAG_PA_CORE_IN_MM) { 118 121 unsigned long addr = (unsigned long)kaddr; 119 122 __cpuc_coherent_kern_range(addr, addr + len); 120 123 } ··· 131 128 } 132 129 133 130 /* VIPT non-aliasing D-cache */ 134 - if (vma->vm_flags & VM_EXEC) { 131 + if (flags & FLAG_PA_IS_EXEC) { 135 132 unsigned long addr = (unsigned long)kaddr; 136 133 if (icache_is_vipt_aliasing()) 137 134 flush_icache_alias(page_to_pfn(page), uaddr, len); ··· 141 138 smp_call_function(flush_ptrace_access_other, 142 139 NULL, 1); 143 140 } 141 + } 142 + 143 + static 144 + void flush_ptrace_access(struct vm_area_struct *vma, struct page *page, 145 + unsigned long uaddr, void *kaddr, unsigned long len) 146 + { 147 + unsigned int flags = 0; 148 + if (cpumask_test_cpu(smp_processor_id(), mm_cpumask(vma->vm_mm))) 149 + flags |= FLAG_PA_CORE_IN_MM; 150 + if (vma->vm_flags & VM_EXEC) 151 + flags |= FLAG_PA_IS_EXEC; 152 + __flush_ptrace_access(page, uaddr, kaddr, len, flags); 153 + } 154 + 155 + void flush_uprobe_xol_access(struct page *page, unsigned long uaddr, 156 + void *kaddr, unsigned long len) 157 + { 158 + unsigned int flags = FLAG_PA_CORE_IN_MM|FLAG_PA_IS_EXEC; 159 + 160 + __flush_ptrace_access(page, uaddr, kaddr, len, flags); 144 161 } 145 162 146 163 /*
+3
include/linux/uprobes.h
··· 32 32 struct mm_struct; 33 33 struct inode; 34 34 struct notifier_block; 35 + struct page; 35 36 36 37 #define UPROBE_HANDLER_REMOVE 1 37 38 #define UPROBE_HANDLER_MASK 1 ··· 128 127 extern void arch_uprobe_abort_xol(struct arch_uprobe *aup, struct pt_regs *regs); 129 128 extern unsigned long arch_uretprobe_hijack_return_addr(unsigned long trampoline_vaddr, struct pt_regs *regs); 130 129 extern bool __weak arch_uprobe_ignore(struct arch_uprobe *aup, struct pt_regs *regs); 130 + extern void __weak arch_uprobe_copy_ixol(struct page *page, unsigned long vaddr, 131 + void *src, unsigned long len); 131 132 #else /* !CONFIG_UPROBES */ 132 133 struct uprobes_state { 133 134 };
+17 -8
kernel/events/uprobes.c
··· 1296 1296 if (unlikely(!xol_vaddr)) 1297 1297 return 0; 1298 1298 1299 - /* Initialize the slot */ 1300 - copy_to_page(area->page, xol_vaddr, 1301 - &uprobe->arch.ixol, sizeof(uprobe->arch.ixol)); 1302 - /* 1303 - * We probably need flush_icache_user_range() but it needs vma. 1304 - * This should work on supported architectures too. 1305 - */ 1306 - flush_dcache_page(area->page); 1299 + arch_uprobe_copy_ixol(area->page, xol_vaddr, 1300 + &uprobe->arch.ixol, sizeof(uprobe->arch.ixol)); 1307 1301 1308 1302 return xol_vaddr; 1309 1303 } ··· 1338 1344 1339 1345 tsk->utask->xol_vaddr = 0; 1340 1346 } 1347 + } 1348 + 1349 + void __weak arch_uprobe_copy_ixol(struct page *page, unsigned long vaddr, 1350 + void *src, unsigned long len) 1351 + { 1352 + /* Initialize the slot */ 1353 + copy_to_page(page, vaddr, src, len); 1354 + 1355 + /* 1356 + * We probably need flush_icache_user_range() but it needs vma. 1357 + * This should work on most of architectures by default. If 1358 + * architecture needs to do something different it can define 1359 + * its own version of the function. 1360 + */ 1361 + flush_dcache_page(page); 1341 1362 } 1342 1363 1343 1364 /**