Skip to content

Commit 432c6ba

Browse files
paulburtonralfbaechle
authored andcommitted
MIPS: Use per-mm page to execute branch delay slot instructions
In some cases the kernel needs to execute an instruction from the delay slot of an emulated branch instruction. These cases include: - Emulated floating point branch instructions (bc1[ft]l?) for systems which don't include an FPU, or upon which the kernel is run with the "nofpu" parameter. - MIPSr6 systems running binaries targeting older revisions of the architecture, which may include branch instructions whose encodings are no longer valid in MIPSr6. Executing instructions from such delay slots is done by writing the instruction to memory followed by a trap, as part of an "emuframe", and executing it. This avoids the requirement of an emulator for the entire MIPS instruction set. Prior to this patch such emuframes are written to the user stack and executed from there. This patch moves FP branch delay emuframes off of the user stack and into a per-mm page. Allocating a page per-mm leaves userland with access to only what it had access to previously, and compared to other solutions is relatively simple. When a thread requires a delay slot emulation, it is allocated a frame. A thread may only have one frame allocated at any one time, since it may only ever be executing one instruction at any one time. In order to ensure that we can free up allocated frame later, its index is recorded in struct thread_struct. In the typical case, after executing the delay slot instruction we'll execute a break instruction with the BRK_MEMU code. This traps back to the kernel & leads to a call to do_dsemulret which frees the allocated frame & moves the user PC back to the instruction that would have executed following the emulated branch. In some cases the delay slot instruction may be invalid, such as a branch, or may trigger an exception. In these cases the BRK_MEMU break instruction will not be hit. In order to ensure that frames are freed this patch introduces dsemul_thread_cleanup() and calls it to free any allocated frame upon thread exit. If the instruction generated an exception & leads to a signal being delivered to the thread, or indeed if a signal simply happens to be delivered to the thread whilst it is executing from the struct emuframe, then we need to take care to exit the frame appropriately. This is done by either rolling back the user PC to the branch or advancing it to the continuation PC prior to signal delivery, using dsemul_thread_rollback(). If this were not done then a sigreturn would return to the struct emuframe, and if that frame had meanwhile been used in response to an emulated branch instruction within the signal handler then we would execute the wrong user code. Whilst a user could theoretically place something like a compact branch to self in a delay slot and cause their thread to become stuck in an infinite loop with the frame never being deallocated, this would: - Only affect the users single process. - Be architecturally invalid since there would be a branch in the delay slot, which is forbidden. - Be extremely unlikely to happen by mistake, and provide a program with no more ability to harm the system than a simple infinite loop would. If a thread requires a delay slot emulation & no frame is available to it (ie. the process has enough other threads that all frames are currently in use) then the thread joins a waitqueue. It will sleep until a frame is freed by another thread in the process. Since we now know whether a thread has an allocated frame due to our tracking of its index, the cookie field of struct emuframe is removed as we can be more certain whether we have a valid frame. Since a thread may only ever have a single frame at any given time, the epc field of struct emuframe is also removed & the PC to continue from is instead stored in struct thread_struct. Together these changes simplify & shrink struct emuframe somewhat, allowing twice as many frames to fit into the page allocated for them. The primary benefit of this patch is that we are now free to mark the user stack non-executable where that is possible. Signed-off-by: Paul Burton <paul.burton@imgtec.com> Cc: Leonid Yegoshin <leonid.yegoshin@imgtec.com> Cc: Maciej Rozycki <maciej.rozycki@imgtec.com> Cc: Faraz Shahbazker <faraz.shahbazker@imgtec.com> Cc: Raghu Gandham <raghu.gandham@imgtec.com> Cc: Matthew Fortune <matthew.fortune@imgtec.com> Cc: linux-mips@linux-mips.org Patchwork: https://patchwork.linux-mips.org/patch/13764/ Signed-off-by: Ralf Baechle <ralf@linux-mips.org>
1 parent 33799a6 commit 432c6ba

File tree

12 files changed

+391
-133
lines changed

12 files changed

+391
-133
lines changed

arch/mips/Kconfig

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,7 @@ config MIPS
6464
select GENERIC_TIME_VSYSCALL
6565
select ARCH_CLOCKSOURCE_DATA
6666
select HANDLE_DOMAIN_IRQ
67+
select HAVE_EXIT_THREAD
6768

6869
menu "Machine selection"
6970

arch/mips/include/asm/dsemul.h

Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
/*
2+
* Copyright (C) 2016 Imagination Technologies
3+
* Author: Paul Burton <paul.burton@imgtec.com>
4+
*
5+
* This program is free software; you can redistribute it and/or modify it
6+
* under the terms of the GNU General Public License as published by the
7+
* Free Software Foundation; either version 2 of the License, or (at your
8+
* option) any later version.
9+
*/
10+
11+
#ifndef __MIPS_ASM_DSEMUL_H__
12+
#define __MIPS_ASM_DSEMUL_H__
13+
14+
#include <asm/break.h>
15+
#include <asm/inst.h>
16+
17+
/* Break instruction with special math emu break code set */
18+
#define BREAK_MATH(micromips) (((micromips) ? 0x7 : 0xd) | (BRK_MEMU << 16))
19+
20+
/* When used as a frame index, indicates the lack of a frame */
21+
#define BD_EMUFRAME_NONE ((int)BIT(31))
22+
23+
struct mm_struct;
24+
struct pt_regs;
25+
struct task_struct;
26+
27+
/**
28+
* mips_dsemul() - 'Emulate' an instruction from a branch delay slot
29+
* @regs: User thread register context.
30+
* @ir: The instruction to be 'emulated'.
31+
* @branch_pc: The PC of the branch instruction.
32+
* @cont_pc: The PC to continue at following 'emulation'.
33+
*
34+
* Emulate or execute an arbitrary MIPS instruction within the context of
35+
* the current user thread. This is used primarily to handle instructions
36+
* in the delay slots of emulated branch instructions, for example FP
37+
* branch instructions on systems without an FPU.
38+
*
39+
* Return: Zero on success, negative if ir is a NOP, signal number on failure.
40+
*/
41+
extern int mips_dsemul(struct pt_regs *regs, mips_instruction ir,
42+
unsigned long branch_pc, unsigned long cont_pc);
43+
44+
/**
45+
* do_dsemulret() - Return from a delay slot 'emulation' frame
46+
* @xcp: User thread register context.
47+
*
48+
* Call in response to the BRK_MEMU break instruction used to return to
49+
* the kernel from branch delay slot 'emulation' frames following a call
50+
* to mips_dsemul(). Restores the user thread PC to the value that was
51+
* passed as the cpc parameter to mips_dsemul().
52+
*
53+
* Return: True if an emulation frame was returned from, else false.
54+
*/
55+
extern bool do_dsemulret(struct pt_regs *xcp);
56+
57+
/**
58+
* dsemul_thread_cleanup() - Cleanup thread 'emulation' frame
59+
* @tsk: The task structure associated with the thread
60+
*
61+
* If the thread @tsk has a branch delay slot 'emulation' frame
62+
* allocated to it then free that frame.
63+
*
64+
* Return: True if a frame was freed, else false.
65+
*/
66+
extern bool dsemul_thread_cleanup(struct task_struct *tsk);
67+
68+
/**
69+
* dsemul_thread_rollback() - Rollback from an 'emulation' frame
70+
* @regs: User thread register context.
71+
*
72+
* If the current thread, whose register context is represented by @regs,
73+
* is executing within a delay slot 'emulation' frame then exit that
74+
* frame. The PC will be rolled back to the branch if the instruction
75+
* that was being 'emulated' has not yet executed, or advanced to the
76+
* continuation PC if it has.
77+
*
78+
* Return: True if a frame was exited, else false.
79+
*/
80+
extern bool dsemul_thread_rollback(struct pt_regs *regs);
81+
82+
/**
83+
* dsemul_mm_cleanup() - Cleanup per-mm delay slot 'emulation' state
84+
* @mm: The struct mm_struct to cleanup state for.
85+
*
86+
* Cleanup state for the given @mm, ensuring that any memory allocated
87+
* for delay slot 'emulation' book-keeping is freed. This is to be called
88+
* before @mm is freed in order to avoid memory leaks.
89+
*/
90+
extern void dsemul_mm_cleanup(struct mm_struct *mm);
91+
92+
#endif /* __MIPS_ASM_DSEMUL_H__ */

arch/mips/include/asm/fpu_emulator.h

Lines changed: 3 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@
2424
#define _ASM_FPU_EMULATOR_H
2525

2626
#include <linux/sched.h>
27-
#include <asm/break.h>
27+
#include <asm/dsemul.h>
2828
#include <asm/thread_info.h>
2929
#include <asm/inst.h>
3030
#include <asm/local.h>
@@ -60,27 +60,16 @@ do { \
6060
#define MIPS_FPU_EMU_INC_STATS(M) do { } while (0)
6161
#endif /* CONFIG_DEBUG_FS */
6262

63-
extern int mips_dsemul(struct pt_regs *regs, mips_instruction ir,
64-
unsigned long cpc);
65-
extern int do_dsemulret(struct pt_regs *xcp);
6663
extern int fpu_emulator_cop1Handler(struct pt_regs *xcp,
6764
struct mips_fpu_struct *ctx, int has_fpu,
6865
void *__user *fault_addr);
6966
int process_fpemu_return(int sig, void __user *fault_addr,
7067
unsigned long fcr31);
68+
int isBranchInstr(struct pt_regs *regs, struct mm_decoded_insn dec_insn,
69+
unsigned long *contpc);
7170
int mm_isBranchInstr(struct pt_regs *regs, struct mm_decoded_insn dec_insn,
7271
unsigned long *contpc);
7372

74-
/*
75-
* Instruction inserted following the badinst to further tag the sequence
76-
*/
77-
#define BD_COOKIE 0x0000bd36 /* tne $0, $0 with baggage */
78-
79-
/*
80-
* Break instruction with special math emu break code set
81-
*/
82-
#define BREAK_MATH(micromips) (((micromips) ? 0x7 : 0xd) | (BRK_MEMU << 16))
83-
8473
#define SIGNALLING_NAN 0x7ff800007ff80000LL
8574

8675
static inline void fpu_emulator_init_fpu(void)

arch/mips/include/asm/mmu.h

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,20 @@
22
#define __ASM_MMU_H
33

44
#include <linux/atomic.h>
5+
#include <linux/spinlock.h>
6+
#include <linux/wait.h>
57

68
typedef struct {
79
unsigned long asid[NR_CPUS];
810
void *vdso;
911
atomic_t fp_mode_switching;
12+
13+
/* lock to be held whilst modifying fp_bd_emupage_allocmap */
14+
spinlock_t bd_emupage_lock;
15+
/* bitmap tracking allocation of fp_bd_emupage */
16+
unsigned long *bd_emupage_allocmap;
17+
/* wait queue for threads requiring an emuframe */
18+
wait_queue_head_t bd_emupage_queue;
1019
} mm_context_t;
1120

1221
#endif /* __ASM_MMU_H */

arch/mips/include/asm/mmu_context.h

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
#include <linux/smp.h>
1717
#include <linux/slab.h>
1818
#include <asm/cacheflush.h>
19+
#include <asm/dsemul.h>
1920
#include <asm/hazards.h>
2021
#include <asm/tlbflush.h>
2122
#include <asm-generic/mm_hooks.h>
@@ -128,6 +129,10 @@ init_new_context(struct task_struct *tsk, struct mm_struct *mm)
128129

129130
atomic_set(&mm->context.fp_mode_switching, 0);
130131

132+
mm->context.bd_emupage_allocmap = NULL;
133+
spin_lock_init(&mm->context.bd_emupage_lock);
134+
init_waitqueue_head(&mm->context.bd_emupage_queue);
135+
131136
return 0;
132137
}
133138

@@ -162,6 +167,7 @@ static inline void switch_mm(struct mm_struct *prev, struct mm_struct *next,
162167
*/
163168
static inline void destroy_context(struct mm_struct *mm)
164169
{
170+
dsemul_mm_cleanup(mm);
165171
}
166172

167173
#define deactivate_mm(tsk, mm) do { } while (0)

arch/mips/include/asm/processor.h

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,12 +11,14 @@
1111
#ifndef _ASM_PROCESSOR_H
1212
#define _ASM_PROCESSOR_H
1313

14+
#include <linux/atomic.h>
1415
#include <linux/cpumask.h>
1516
#include <linux/threads.h>
1617

1718
#include <asm/cachectl.h>
1819
#include <asm/cpu.h>
1920
#include <asm/cpu-info.h>
21+
#include <asm/dsemul.h>
2022
#include <asm/mipsregs.h>
2123
#include <asm/prefetch.h>
2224

@@ -78,7 +80,11 @@ extern unsigned int vced_count, vcei_count;
7880

7981
#endif
8082

81-
#define STACK_TOP (TASK_SIZE & PAGE_MASK)
83+
/*
84+
* One page above the stack is used for branch delay slot "emulation".
85+
* See dsemul.c for details.
86+
*/
87+
#define STACK_TOP ((TASK_SIZE & PAGE_MASK) - PAGE_SIZE)
8288

8389
/*
8490
* This decides where the kernel will search for a free chunk of vm
@@ -256,6 +262,12 @@ struct thread_struct {
256262

257263
/* Saved fpu/fpu emulator stuff. */
258264
struct mips_fpu_struct fpu FPU_ALIGN;
265+
/* Assigned branch delay slot 'emulation' frame */
266+
atomic_t bd_emu_frame;
267+
/* PC of the branch from a branch delay slot 'emulation' */
268+
unsigned long bd_emu_branch_pc;
269+
/* PC to continue from following a branch delay slot 'emulation' */
270+
unsigned long bd_emu_cont_pc;
259271
#ifdef CONFIG_MIPS_MT_FPAFF
260272
/* Emulated instruction count */
261273
unsigned long emulated_fp;
@@ -323,6 +335,10 @@ struct thread_struct {
323335
* FPU affinity state (null if not FPAFF) \
324336
*/ \
325337
FPAFF_INIT \
338+
/* Delay slot emulation */ \
339+
.bd_emu_frame = ATOMIC_INIT(BD_EMUFRAME_NONE), \
340+
.bd_emu_branch_pc = 0, \
341+
.bd_emu_cont_pc = 0, \
326342
/* \
327343
* Saved DSP stuff \
328344
*/ \

arch/mips/kernel/mips-r2-to-r6-emul.c

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -283,7 +283,7 @@ static int jr_func(struct pt_regs *regs, u32 ir)
283283
err = mipsr6_emul(regs, nir);
284284
if (err > 0) {
285285
regs->cp0_epc = nepc;
286-
err = mips_dsemul(regs, nir, cepc);
286+
err = mips_dsemul(regs, nir, epc, cepc);
287287
if (err == SIGILL)
288288
err = SIGEMT;
289289
MIPS_R2_STATS(dsemul);
@@ -1033,7 +1033,7 @@ int mipsr2_decoder(struct pt_regs *regs, u32 inst, unsigned long *fcr31)
10331033
if (nir) {
10341034
err = mipsr6_emul(regs, nir);
10351035
if (err > 0) {
1036-
err = mips_dsemul(regs, nir, cpc);
1036+
err = mips_dsemul(regs, nir, epc, cpc);
10371037
if (err == SIGILL)
10381038
err = SIGEMT;
10391039
MIPS_R2_STATS(dsemul);
@@ -1082,7 +1082,7 @@ int mipsr2_decoder(struct pt_regs *regs, u32 inst, unsigned long *fcr31)
10821082
if (nir) {
10831083
err = mipsr6_emul(regs, nir);
10841084
if (err > 0) {
1085-
err = mips_dsemul(regs, nir, cpc);
1085+
err = mips_dsemul(regs, nir, epc, cpc);
10861086
if (err == SIGILL)
10871087
err = SIGEMT;
10881088
MIPS_R2_STATS(dsemul);
@@ -1149,7 +1149,7 @@ int mipsr2_decoder(struct pt_regs *regs, u32 inst, unsigned long *fcr31)
11491149
if (nir) {
11501150
err = mipsr6_emul(regs, nir);
11511151
if (err > 0) {
1152-
err = mips_dsemul(regs, nir, cpc);
1152+
err = mips_dsemul(regs, nir, epc, cpc);
11531153
if (err == SIGILL)
11541154
err = SIGEMT;
11551155
MIPS_R2_STATS(dsemul);

arch/mips/kernel/process.c

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@
3030
#include <asm/asm.h>
3131
#include <asm/bootinfo.h>
3232
#include <asm/cpu.h>
33+
#include <asm/dsemul.h>
3334
#include <asm/dsp.h>
3435
#include <asm/fpu.h>
3536
#include <asm/msa.h>
@@ -68,11 +69,22 @@ void start_thread(struct pt_regs * regs, unsigned long pc, unsigned long sp)
6869
lose_fpu(0);
6970
clear_thread_flag(TIF_MSA_CTX_LIVE);
7071
clear_used_math();
72+
atomic_set(&current->thread.bd_emu_frame, BD_EMUFRAME_NONE);
7173
init_dsp();
7274
regs->cp0_epc = pc;
7375
regs->regs[29] = sp;
7476
}
7577

78+
void exit_thread(struct task_struct *tsk)
79+
{
80+
/*
81+
* User threads may have allocated a delay slot emulation frame.
82+
* If so, clean up that allocation.
83+
*/
84+
if (!(current->flags & PF_KTHREAD))
85+
dsemul_thread_cleanup(tsk);
86+
}
87+
7688
int arch_dup_task_struct(struct task_struct *dst, struct task_struct *src)
7789
{
7890
/*
@@ -159,6 +171,8 @@ int copy_thread(unsigned long clone_flags, unsigned long usp,
159171
clear_tsk_thread_flag(p, TIF_FPUBOUND);
160172
#endif /* CONFIG_MIPS_MT_FPAFF */
161173

174+
atomic_set(&p->thread.bd_emu_frame, BD_EMUFRAME_NONE);
175+
162176
if (clone_flags & CLONE_SETTLS)
163177
ti->tp_value = regs->regs[7];
164178

arch/mips/kernel/signal.c

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -772,6 +772,14 @@ static void handle_signal(struct ksignal *ksig, struct pt_regs *regs)
772772
struct mips_abi *abi = current->thread.abi;
773773
void *vdso = current->mm->context.vdso;
774774

775+
/*
776+
* If we were emulating a delay slot instruction, exit that frame such
777+
* that addresses in the sigframe are as expected for userland and we
778+
* don't have a problem if we reuse the thread's frame for an
779+
* instruction within the signal handler.
780+
*/
781+
dsemul_thread_rollback(regs);
782+
775783
if (regs->regs[0]) {
776784
switch(regs->regs[2]) {
777785
case ERESTART_RESTARTBLOCK:

arch/mips/kernel/vdso.c

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -107,6 +107,16 @@ int arch_setup_additional_pages(struct linux_binprm *bprm, int uses_interp)
107107
if (down_write_killable(&mm->mmap_sem))
108108
return -EINTR;
109109

110+
/* Map delay slot emulation page */
111+
base = mmap_region(NULL, STACK_TOP, PAGE_SIZE,
112+
VM_READ|VM_WRITE|VM_EXEC|
113+
VM_MAYREAD|VM_MAYWRITE|VM_MAYEXEC,
114+
0);
115+
if (IS_ERR_VALUE(base)) {
116+
ret = base;
117+
goto out;
118+
}
119+
110120
/*
111121
* Determine total area size. This includes the VDSO data itself, the
112122
* data page, and the GIC user page if present. Always create a mapping

arch/mips/math-emu/cp1emu.c

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -434,8 +434,8 @@ static int microMIPS32_to_MIPS32(union mips_instruction *insn_ptr)
434434
* a single subroutine should be used across both
435435
* modules.
436436
*/
437-
static int isBranchInstr(struct pt_regs *regs, struct mm_decoded_insn dec_insn,
438-
unsigned long *contpc)
437+
int isBranchInstr(struct pt_regs *regs, struct mm_decoded_insn dec_insn,
438+
unsigned long *contpc)
439439
{
440440
union mips_instruction insn = (union mips_instruction)dec_insn.insn;
441441
unsigned int fcr31;
@@ -1268,7 +1268,7 @@ static int cop1Emulate(struct pt_regs *xcp, struct mips_fpu_struct *ctx,
12681268
* instruction in the dslot.
12691269
*/
12701270
sig = mips_dsemul(xcp, ir,
1271-
contpc);
1271+
bcpc, contpc);
12721272
if (sig < 0)
12731273
break;
12741274
if (sig)
@@ -1323,7 +1323,7 @@ static int cop1Emulate(struct pt_regs *xcp, struct mips_fpu_struct *ctx,
13231323
* Single step the non-cp1
13241324
* instruction in the dslot
13251325
*/
1326-
sig = mips_dsemul(xcp, ir, contpc);
1326+
sig = mips_dsemul(xcp, ir, bcpc, contpc);
13271327
if (sig < 0)
13281328
break;
13291329
if (sig)

0 commit comments

Comments
 (0)