aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--arch/x86/kernel/kprobes.c73
1 files changed, 73 insertions, 0 deletions
diff --git a/arch/x86/kernel/kprobes.c b/arch/x86/kernel/kprobes.c
index 7b5169d2b000..aa15f3e1f64b 100644
--- a/arch/x86/kernel/kprobes.c
+++ b/arch/x86/kernel/kprobes.c
@@ -48,12 +48,14 @@
48#include <linux/preempt.h> 48#include <linux/preempt.h>
49#include <linux/module.h> 49#include <linux/module.h>
50#include <linux/kdebug.h> 50#include <linux/kdebug.h>
51#include <linux/kallsyms.h>
51 52
52#include <asm/cacheflush.h> 53#include <asm/cacheflush.h>
53#include <asm/desc.h> 54#include <asm/desc.h>
54#include <asm/pgtable.h> 55#include <asm/pgtable.h>
55#include <asm/uaccess.h> 56#include <asm/uaccess.h>
56#include <asm/alternative.h> 57#include <asm/alternative.h>
58#include <asm/insn.h>
57 59
58void jprobe_return_end(void); 60void jprobe_return_end(void);
59 61
@@ -244,6 +246,75 @@ retry:
244 } 246 }
245} 247}
246 248
249/* Recover the probed instruction at addr for further analysis. */
250static int recover_probed_instruction(kprobe_opcode_t *buf, unsigned long addr)
251{
252 struct kprobe *kp;
253 kp = get_kprobe((void *)addr);
254 if (!kp)
255 return -EINVAL;
256
257 /*
258 * Basically, kp->ainsn.insn has an original instruction.
259 * However, RIP-relative instruction can not do single-stepping
260 * at different place, fix_riprel() tweaks the displacement of
261 * that instruction. In that case, we can't recover the instruction
262 * from the kp->ainsn.insn.
263 *
264 * On the other hand, kp->opcode has a copy of the first byte of
265 * the probed instruction, which is overwritten by int3. And
266 * the instruction at kp->addr is not modified by kprobes except
267 * for the first byte, we can recover the original instruction
268 * from it and kp->opcode.
269 */
270 memcpy(buf, kp->addr, MAX_INSN_SIZE * sizeof(kprobe_opcode_t));
271 buf[0] = kp->opcode;
272 return 0;
273}
274
275/* Dummy buffers for kallsyms_lookup */
276static char __dummy_buf[KSYM_NAME_LEN];
277
278/* Check if paddr is at an instruction boundary */
279static int __kprobes can_probe(unsigned long paddr)
280{
281 int ret;
282 unsigned long addr, offset = 0;
283 struct insn insn;
284 kprobe_opcode_t buf[MAX_INSN_SIZE];
285
286 if (!kallsyms_lookup(paddr, NULL, &offset, NULL, __dummy_buf))
287 return 0;
288
289 /* Decode instructions */
290 addr = paddr - offset;
291 while (addr < paddr) {
292 kernel_insn_init(&insn, (void *)addr);
293 insn_get_opcode(&insn);
294
295 /*
296 * Check if the instruction has been modified by another
297 * kprobe, in which case we replace the breakpoint by the
298 * original instruction in our buffer.
299 */
300 if (insn.opcode.bytes[0] == BREAKPOINT_INSTRUCTION) {
301 ret = recover_probed_instruction(buf, addr);
302 if (ret)
303 /*
304 * Another debugging subsystem might insert
305 * this breakpoint. In that case, we can't
306 * recover it.
307 */
308 return 0;
309 kernel_insn_init(&insn, buf);
310 }
311 insn_get_length(&insn);
312 addr += insn.length;
313 }
314
315 return (addr == paddr);
316}
317
247/* 318/*
248 * Returns non-zero if opcode modifies the interrupt flag. 319 * Returns non-zero if opcode modifies the interrupt flag.
249 */ 320 */
@@ -359,6 +430,8 @@ static void __kprobes arch_copy_kprobe(struct kprobe *p)
359 430
360int __kprobes arch_prepare_kprobe(struct kprobe *p) 431int __kprobes arch_prepare_kprobe(struct kprobe *p)
361{ 432{
433 if (!can_probe((unsigned long)p->addr))
434 return -EILSEQ;
362 /* insn: must be on special executable page on x86. */ 435 /* insn: must be on special executable page on x86. */
363 p->ainsn.insn = get_insn_slot(); 436 p->ainsn.insn = get_insn_slot();
364 if (!p->ainsn.insn) 437 if (!p->ainsn.insn)