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

MIPS: math-emu: Write-protect delay slot emulation pages

Mapping the delay slot emulation page as both writeable & executable
presents a security risk, in that if an exploit can write to & jump into
the page then it can be used as an easy way to execute arbitrary code.

Prevent this by mapping the page read-only for userland, and using
access_process_vm() with the FOLL_FORCE flag to write to it from
mips_dsemul().

This will likely be less efficient due to copy_to_user_page() performing
cache maintenance on a whole page, rather than a single line as in the
previous use of flush_cache_sigtramp(). However this delay slot
emulation code ought not to be running in any performance critical paths
anyway so this isn't really a problem, and we can probably do better in
copy_to_user_page() anyway in future.

A major advantage of this approach is that the fix is small & simple to
backport to stable kernels.

Reported-by: Andy Lutomirski <luto@kernel.org>
Signed-off-by: Paul Burton <paul.burton@mips.com>
Fixes: 432c6bacbd0c ("MIPS: Use per-mm page to execute branch delay slot instructions")
Cc: stable@vger.kernel.org # v4.8+
Cc: linux-mips@vger.kernel.org
Cc: linux-kernel@vger.kernel.org
Cc: Rich Felker <dalias@libc.org>
Cc: David Daney <david.daney@cavium.com>

+22 -20
+2 -2
arch/mips/kernel/vdso.c
··· 126 126 127 127 /* Map delay slot emulation page */ 128 128 base = mmap_region(NULL, STACK_TOP, PAGE_SIZE, 129 - VM_READ|VM_WRITE|VM_EXEC| 130 - VM_MAYREAD|VM_MAYWRITE|VM_MAYEXEC, 129 + VM_READ | VM_EXEC | 130 + VM_MAYREAD | VM_MAYWRITE | VM_MAYEXEC, 131 131 0, NULL); 132 132 if (IS_ERR_VALUE(base)) { 133 133 ret = base;
+20 -18
arch/mips/math-emu/dsemul.c
··· 214 214 { 215 215 int isa16 = get_isa16_mode(regs->cp0_epc); 216 216 mips_instruction break_math; 217 - struct emuframe __user *fr; 218 - int err, fr_idx; 217 + unsigned long fr_uaddr; 218 + struct emuframe fr; 219 + int fr_idx, ret; 219 220 220 221 /* NOP is easy */ 221 222 if (ir == 0) ··· 251 250 fr_idx = alloc_emuframe(); 252 251 if (fr_idx == BD_EMUFRAME_NONE) 253 252 return SIGBUS; 254 - fr = &dsemul_page()[fr_idx]; 255 253 256 254 /* Retrieve the appropriately encoded break instruction */ 257 255 break_math = BREAK_MATH(isa16); 258 256 259 257 /* Write the instructions to the frame */ 260 258 if (isa16) { 261 - err = __put_user(ir >> 16, 262 - (u16 __user *)(&fr->emul)); 263 - err |= __put_user(ir & 0xffff, 264 - (u16 __user *)((long)(&fr->emul) + 2)); 265 - err |= __put_user(break_math >> 16, 266 - (u16 __user *)(&fr->badinst)); 267 - err |= __put_user(break_math & 0xffff, 268 - (u16 __user *)((long)(&fr->badinst) + 2)); 259 + union mips_instruction _emul = { 260 + .halfword = { ir >> 16, ir } 261 + }; 262 + union mips_instruction _badinst = { 263 + .halfword = { break_math >> 16, break_math } 264 + }; 265 + 266 + fr.emul = _emul.word; 267 + fr.badinst = _badinst.word; 269 268 } else { 270 - err = __put_user(ir, &fr->emul); 271 - err |= __put_user(break_math, &fr->badinst); 269 + fr.emul = ir; 270 + fr.badinst = break_math; 272 271 } 273 272 274 - if (unlikely(err)) { 273 + /* Write the frame to user memory */ 274 + fr_uaddr = (unsigned long)&dsemul_page()[fr_idx]; 275 + ret = access_process_vm(current, fr_uaddr, &fr, sizeof(fr), 276 + FOLL_FORCE | FOLL_WRITE); 277 + if (unlikely(ret != sizeof(fr))) { 275 278 MIPS_FPU_EMU_INC_STATS(errors); 276 279 free_emuframe(fr_idx, current->mm); 277 280 return SIGBUS; ··· 287 282 atomic_set(&current->thread.bd_emu_frame, fr_idx); 288 283 289 284 /* Change user register context to execute the frame */ 290 - regs->cp0_epc = (unsigned long)&fr->emul | isa16; 291 - 292 - /* Ensure the icache observes our newly written frame */ 293 - flush_cache_sigtramp((unsigned long)&fr->emul); 285 + regs->cp0_epc = fr_uaddr | isa16; 294 286 295 287 return 0; 296 288 }