~ [ source navigation ] ~ [ diff markup ] ~ [ identifier search ] ~

TOMOYO Linux Cross Reference
Linux/arch/mips/math-emu/dsemul.c

Version: ~ [ linux-5.2-rc1 ] ~ [ linux-5.1.2 ] ~ [ linux-5.0.16 ] ~ [ linux-4.20.17 ] ~ [ linux-4.19.43 ] ~ [ linux-4.18.20 ] ~ [ linux-4.17.19 ] ~ [ linux-4.16.18 ] ~ [ linux-4.15.18 ] ~ [ linux-4.14.119 ] ~ [ linux-4.13.16 ] ~ [ linux-4.12.14 ] ~ [ linux-4.11.12 ] ~ [ linux-4.10.17 ] ~ [ linux-4.9.176 ] ~ [ linux-4.8.17 ] ~ [ linux-4.7.10 ] ~ [ linux-4.6.7 ] ~ [ linux-4.5.7 ] ~ [ linux-4.4.179 ] ~ [ linux-4.3.6 ] ~ [ linux-4.2.8 ] ~ [ linux-4.1.52 ] ~ [ linux-4.0.9 ] ~ [ linux-3.19.8 ] ~ [ linux-3.18.139 ] ~ [ linux-3.17.8 ] ~ [ linux-3.16.67 ] ~ [ linux-3.15.10 ] ~ [ linux-3.14.79 ] ~ [ linux-3.13.11 ] ~ [ linux-3.12.74 ] ~ [ linux-3.11.10 ] ~ [ linux-3.10.108 ] ~ [ linux-3.9.11 ] ~ [ linux-3.8.13 ] ~ [ linux-3.7.10 ] ~ [ linux-3.6.11 ] ~ [ linux-3.5.7 ] ~ [ linux-3.4.113 ] ~ [ linux-3.3.8 ] ~ [ linux-3.2.102 ] ~ [ linux-3.1.10 ] ~ [ linux-3.0.101 ] ~ [ linux-2.6.39.4 ] ~ [ linux-2.6.38.8 ] ~ [ linux-2.6.37.6 ] ~ [ linux-2.6.36.4 ] ~ [ linux-2.6.35.14 ] ~ [ linux-2.6.34.15 ] ~ [ linux-2.6.33.20 ] ~ [ linux-2.6.32.71 ] ~ [ linux-2.6.0 ] ~ [ linux-2.4.37.11 ] ~ [ unix-v6-master ] ~ [ ccs-tools-1.8.5 ] ~ [ policy-sample ] ~
Architecture: ~ [ i386 ] ~ [ alpha ] ~ [ m68k ] ~ [ mips ] ~ [ ppc ] ~ [ sparc ] ~ [ sparc64 ] ~

  1 // SPDX-License-Identifier: GPL-2.0
  2 #include <linux/err.h>
  3 #include <linux/slab.h>
  4 #include <linux/mm_types.h>
  5 #include <linux/sched/task.h>
  6 
  7 #include <asm/branch.h>
  8 #include <asm/cacheflush.h>
  9 #include <asm/fpu_emulator.h>
 10 #include <asm/inst.h>
 11 #include <asm/mipsregs.h>
 12 #include <linux/uaccess.h>
 13 
 14 /**
 15  * struct emuframe - The 'emulation' frame structure
 16  * @emul:       The instruction to 'emulate'.
 17  * @badinst:    A break instruction to cause a return to the kernel.
 18  *
 19  * This structure defines the frames placed within the delay slot emulation
 20  * page in response to a call to mips_dsemul(). Each thread may be allocated
 21  * only one frame at any given time. The kernel stores within it the
 22  * instruction to be 'emulated' followed by a break instruction, then
 23  * executes the frame in user mode. The break causes a trap to the kernel
 24  * which leads to do_dsemulret() being called unless the instruction in
 25  * @emul causes a trap itself, is a branch, or a signal is delivered to
 26  * the thread. In these cases the allocated frame will either be reused by
 27  * a subsequent delay slot 'emulation', or be freed during signal delivery or
 28  * upon thread exit.
 29  *
 30  * This approach is used because:
 31  *
 32  * - Actually emulating all instructions isn't feasible. We would need to
 33  *   be able to handle instructions from all revisions of the MIPS ISA,
 34  *   all ASEs & all vendor instruction set extensions. This would be a
 35  *   whole lot of work & continual maintenance burden as new instructions
 36  *   are introduced, and in the case of some vendor extensions may not
 37  *   even be possible. Thus we need to take the approach of actually
 38  *   executing the instruction.
 39  *
 40  * - We must execute the instruction within user context. If we were to
 41  *   execute the instruction in kernel mode then it would have access to
 42  *   kernel resources without very careful checks, leaving us with a
 43  *   high potential for security or stability issues to arise.
 44  *
 45  * - We used to place the frame on the users stack, but this requires
 46  *   that the stack be executable. This is bad for security so the
 47  *   per-process page is now used instead.
 48  *
 49  * - The instruction in @emul may be something entirely invalid for a
 50  *   delay slot. The user may (intentionally or otherwise) place a branch
 51  *   in a delay slot, or a kernel mode instruction, or something else
 52  *   which generates an exception. Thus we can't rely upon the break in
 53  *   @badinst always being hit. For this reason we track the index of the
 54  *   frame allocated to each thread, allowing us to clean it up at later
 55  *   points such as signal delivery or thread exit.
 56  *
 57  * - The user may generate a fake struct emuframe if they wish, invoking
 58  *   the BRK_MEMU break instruction themselves. We must therefore not
 59  *   trust that BRK_MEMU means there's actually a valid frame allocated
 60  *   to the thread, and must not allow the user to do anything they
 61  *   couldn't already.
 62  */
 63 struct emuframe {
 64         mips_instruction        emul;
 65         mips_instruction        badinst;
 66 };
 67 
 68 static const int emupage_frame_count = PAGE_SIZE / sizeof(struct emuframe);
 69 
 70 static inline __user struct emuframe *dsemul_page(void)
 71 {
 72         return (__user struct emuframe *)STACK_TOP;
 73 }
 74 
 75 static int alloc_emuframe(void)
 76 {
 77         mm_context_t *mm_ctx = &current->mm->context;
 78         int idx;
 79 
 80 retry:
 81         spin_lock(&mm_ctx->bd_emupage_lock);
 82 
 83         /* Ensure we have an allocation bitmap */
 84         if (!mm_ctx->bd_emupage_allocmap) {
 85                 mm_ctx->bd_emupage_allocmap =
 86                         kcalloc(BITS_TO_LONGS(emupage_frame_count),
 87                                               sizeof(unsigned long),
 88                                 GFP_ATOMIC);
 89 
 90                 if (!mm_ctx->bd_emupage_allocmap) {
 91                         idx = BD_EMUFRAME_NONE;
 92                         goto out_unlock;
 93                 }
 94         }
 95 
 96         /* Attempt to allocate a single bit/frame */
 97         idx = bitmap_find_free_region(mm_ctx->bd_emupage_allocmap,
 98                                       emupage_frame_count, 0);
 99         if (idx < 0) {
100                 /*
101                  * Failed to allocate a frame. We'll wait until one becomes
102                  * available. We unlock the page so that other threads actually
103                  * get the opportunity to free their frames, which means
104                  * technically the result of bitmap_full may be incorrect.
105                  * However the worst case is that we repeat all this and end up
106                  * back here again.
107                  */
108                 spin_unlock(&mm_ctx->bd_emupage_lock);
109                 if (!wait_event_killable(mm_ctx->bd_emupage_queue,
110                         !bitmap_full(mm_ctx->bd_emupage_allocmap,
111                                      emupage_frame_count)))
112                         goto retry;
113 
114                 /* Received a fatal signal - just give in */
115                 return BD_EMUFRAME_NONE;
116         }
117 
118         /* Success! */
119         pr_debug("allocate emuframe %d to %d\n", idx, current->pid);
120 out_unlock:
121         spin_unlock(&mm_ctx->bd_emupage_lock);
122         return idx;
123 }
124 
125 static void free_emuframe(int idx, struct mm_struct *mm)
126 {
127         mm_context_t *mm_ctx = &mm->context;
128 
129         spin_lock(&mm_ctx->bd_emupage_lock);
130 
131         pr_debug("free emuframe %d from %d\n", idx, current->pid);
132         bitmap_clear(mm_ctx->bd_emupage_allocmap, idx, 1);
133 
134         /* If some thread is waiting for a frame, now's its chance */
135         wake_up(&mm_ctx->bd_emupage_queue);
136 
137         spin_unlock(&mm_ctx->bd_emupage_lock);
138 }
139 
140 static bool within_emuframe(struct pt_regs *regs)
141 {
142         unsigned long base = (unsigned long)dsemul_page();
143 
144         if (regs->cp0_epc < base)
145                 return false;
146         if (regs->cp0_epc >= (base + PAGE_SIZE))
147                 return false;
148 
149         return true;
150 }
151 
152 bool dsemul_thread_cleanup(struct task_struct *tsk)
153 {
154         int fr_idx;
155 
156         /* Clear any allocated frame, retrieving its index */
157         fr_idx = atomic_xchg(&tsk->thread.bd_emu_frame, BD_EMUFRAME_NONE);
158 
159         /* If no frame was allocated, we're done */
160         if (fr_idx == BD_EMUFRAME_NONE)
161                 return false;
162 
163         task_lock(tsk);
164 
165         /* Free the frame that this thread had allocated */
166         if (tsk->mm)
167                 free_emuframe(fr_idx, tsk->mm);
168 
169         task_unlock(tsk);
170         return true;
171 }
172 
173 bool dsemul_thread_rollback(struct pt_regs *regs)
174 {
175         struct emuframe __user *fr;
176         int fr_idx;
177 
178         /* Do nothing if we're not executing from a frame */
179         if (!within_emuframe(regs))
180                 return false;
181 
182         /* Find the frame being executed */
183         fr_idx = atomic_read(&current->thread.bd_emu_frame);
184         if (fr_idx == BD_EMUFRAME_NONE)
185                 return false;
186         fr = &dsemul_page()[fr_idx];
187 
188         /*
189          * If the PC is at the emul instruction, roll back to the branch. If
190          * PC is at the badinst (break) instruction, we've already emulated the
191          * instruction so progress to the continue PC. If it's anything else
192          * then something is amiss & the user has branched into some other area
193          * of the emupage - we'll free the allocated frame anyway.
194          */
195         if (msk_isa16_mode(regs->cp0_epc) == (unsigned long)&fr->emul)
196                 regs->cp0_epc = current->thread.bd_emu_branch_pc;
197         else if (msk_isa16_mode(regs->cp0_epc) == (unsigned long)&fr->badinst)
198                 regs->cp0_epc = current->thread.bd_emu_cont_pc;
199 
200         atomic_set(&current->thread.bd_emu_frame, BD_EMUFRAME_NONE);
201         free_emuframe(fr_idx, current->mm);
202         return true;
203 }
204 
205 void dsemul_mm_cleanup(struct mm_struct *mm)
206 {
207         mm_context_t *mm_ctx = &mm->context;
208 
209         kfree(mm_ctx->bd_emupage_allocmap);
210 }
211 
212 int mips_dsemul(struct pt_regs *regs, mips_instruction ir,
213                 unsigned long branch_pc, unsigned long cont_pc)
214 {
215         int isa16 = get_isa16_mode(regs->cp0_epc);
216         mips_instruction break_math;
217         unsigned long fr_uaddr;
218         struct emuframe fr;
219         int fr_idx, ret;
220 
221         /* NOP is easy */
222         if (ir == 0)
223                 return -1;
224 
225         /* microMIPS instructions */
226         if (isa16) {
227                 union mips_instruction insn = { .word = ir };
228 
229                 /* NOP16 aka MOVE16 $0, $0 */
230                 if ((ir >> 16) == MM_NOP16)
231                         return -1;
232 
233                 /* ADDIUPC */
234                 if (insn.mm_a_format.opcode == mm_addiupc_op) {
235                         unsigned int rs;
236                         s32 v;
237 
238                         rs = (((insn.mm_a_format.rs + 0xe) & 0xf) + 2);
239                         v = regs->cp0_epc & ~3;
240                         v += insn.mm_a_format.simmediate << 2;
241                         regs->regs[rs] = (long)v;
242                         return -1;
243                 }
244         }
245 
246         pr_debug("dsemul 0x%08lx cont at 0x%08lx\n", regs->cp0_epc, cont_pc);
247 
248         /* Allocate a frame if we don't already have one */
249         fr_idx = atomic_read(&current->thread.bd_emu_frame);
250         if (fr_idx == BD_EMUFRAME_NONE)
251                 fr_idx = alloc_emuframe();
252         if (fr_idx == BD_EMUFRAME_NONE)
253                 return SIGBUS;
254 
255         /* Retrieve the appropriately encoded break instruction */
256         break_math = BREAK_MATH(isa16);
257 
258         /* Write the instructions to the frame */
259         if (isa16) {
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;
269         } else {
270                 fr.emul = ir;
271                 fr.badinst = break_math;
272         }
273 
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))) {
279                 MIPS_FPU_EMU_INC_STATS(errors);
280                 free_emuframe(fr_idx, current->mm);
281                 return SIGBUS;
282         }
283 
284         /* Record the PC of the branch, PC to continue from & frame index */
285         current->thread.bd_emu_branch_pc = branch_pc;
286         current->thread.bd_emu_cont_pc = cont_pc;
287         atomic_set(&current->thread.bd_emu_frame, fr_idx);
288 
289         /* Change user register context to execute the frame */
290         regs->cp0_epc = fr_uaddr | isa16;
291 
292         return 0;
293 }
294 
295 bool do_dsemulret(struct pt_regs *xcp)
296 {
297         /* Cleanup the allocated frame, returning if there wasn't one */
298         if (!dsemul_thread_cleanup(current)) {
299                 MIPS_FPU_EMU_INC_STATS(errors);
300                 return false;
301         }
302 
303         /* Set EPC to return to post-branch instruction */
304         xcp->cp0_epc = current->thread.bd_emu_cont_pc;
305         pr_debug("dsemulret to 0x%08lx\n", xcp->cp0_epc);
306         MIPS_FPU_EMU_INC_STATS(ds_emul);
307         return true;
308 }
309 

~ [ source navigation ] ~ [ diff markup ] ~ [ identifier search ] ~

kernel.org | git.kernel.org | LWN.net | Project Home | Wiki (Japanese) | Wiki (English) | SVN repository | Mail admin

Linux® is a registered trademark of Linus Torvalds in the United States and other countries.
TOMOYO® is a registered trademark of NTT DATA CORPORATION.

osdn.jp