Skip to content

Commit adcc81f

Browse files
committed
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: 432c6ba ("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>
1 parent 41e486f commit adcc81f

File tree

2 files changed

+22
-20
lines changed

2 files changed

+22
-20
lines changed

arch/mips/kernel/vdso.c

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -126,8 +126,8 @@ int arch_setup_additional_pages(struct linux_binprm *bprm, int uses_interp)
126126

127127
/* Map delay slot emulation page */
128128
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,
131131
0, NULL);
132132
if (IS_ERR_VALUE(base)) {
133133
ret = base;

arch/mips/math-emu/dsemul.c

Lines changed: 20 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -214,8 +214,9 @@ int mips_dsemul(struct pt_regs *regs, mips_instruction ir,
214214
{
215215
int isa16 = get_isa16_mode(regs->cp0_epc);
216216
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;
219220

220221
/* NOP is easy */
221222
if (ir == 0)
@@ -250,27 +251,31 @@ int mips_dsemul(struct pt_regs *regs, mips_instruction ir,
250251
fr_idx = alloc_emuframe();
251252
if (fr_idx == BD_EMUFRAME_NONE)
252253
return SIGBUS;
253-
fr = &dsemul_page()[fr_idx];
254254

255255
/* Retrieve the appropriately encoded break instruction */
256256
break_math = BREAK_MATH(isa16);
257257

258258
/* Write the instructions to the frame */
259259
if (isa16) {
260-
err = __put_user(ir >> 16,
261-
(u16 __user *)(&fr->emul));
262-
err |= __put_user(ir & 0xffff,
263-
(u16 __user *)((long)(&fr->emul) + 2));
264-
err |= __put_user(break_math >> 16,
265-
(u16 __user *)(&fr->badinst));
266-
err |= __put_user(break_math & 0xffff,
267-
(u16 __user *)((long)(&fr->badinst) + 2));
260+
union mips_instruction _emul = {
261+
.halfword = { ir >> 16, ir }
262+
};
263+
union mips_instruction _badinst = {
264+
.halfword = { break_math >> 16, break_math }
265+
};
266+
267+
fr.emul = _emul.word;
268+
fr.badinst = _badinst.word;
268269
} else {
269-
err = __put_user(ir, &fr->emul);
270-
err |= __put_user(break_math, &fr->badinst);
270+
fr.emul = ir;
271+
fr.badinst = break_math;
271272
}
272273

273-
if (unlikely(err)) {
274+
/* Write the frame to user memory */
275+
fr_uaddr = (unsigned long)&dsemul_page()[fr_idx];
276+
ret = access_process_vm(current, fr_uaddr, &fr, sizeof(fr),
277+
FOLL_FORCE | FOLL_WRITE);
278+
if (unlikely(ret != sizeof(fr))) {
274279
MIPS_FPU_EMU_INC_STATS(errors);
275280
free_emuframe(fr_idx, current->mm);
276281
return SIGBUS;
@@ -282,10 +287,7 @@ int mips_dsemul(struct pt_regs *regs, mips_instruction ir,
282287
atomic_set(&current->thread.bd_emu_frame, fr_idx);
283288

284289
/* Change user register context to execute the frame */
285-
regs->cp0_epc = (unsigned long)&fr->emul | isa16;
286-
287-
/* Ensure the icache observes our newly written frame */
288-
flush_cache_sigtramp((unsigned long)&fr->emul);
290+
regs->cp0_epc = fr_uaddr | isa16;
289291

290292
return 0;
291293
}

0 commit comments

Comments
 (0)