aboutsummaryrefslogtreecommitdiffstats
path: root/arch/x86/kernel/kprobes.c
diff options
context:
space:
mode:
authorMasami Hiramatsu <mhiramat@redhat.com>2009-08-13 16:34:28 -0400
committerFrederic Weisbecker <fweisbec@gmail.com>2009-08-26 18:35:56 -0400
commitb46b3d70c9c017d7c4ec49f7f3ffd0af5a622277 (patch)
tree786abfe4f8480385769f0e1daf5b9f2715a18e86 /arch/x86/kernel/kprobes.c
parentca0e9badd1a39fecdd235f4bf1481b9da756e27b (diff)
kprobes: Checks probe address is instruction boudary on x86
Ensure safeness of inserting kprobes by checking whether the specified address is at the first byte of an instruction on x86. This is done by decoding probed function from its head to the probe point. Signed-off-by: Masami Hiramatsu <mhiramat@redhat.com> Acked-by: Ananth N Mavinakayanahalli <ananth@in.ibm.com> Cc: Avi Kivity <avi@redhat.com> Cc: Andi Kleen <ak@linux.intel.com> Cc: Christoph Hellwig <hch@infradead.org> Cc: Frank Ch. Eigler <fche@redhat.com> Cc: H. Peter Anvin <hpa@zytor.com> Cc: Ingo Molnar <mingo@elte.hu> Cc: Jason Baron <jbaron@redhat.com> Cc: Jim Keniston <jkenisto@us.ibm.com> Cc: K.Prasad <prasad@linux.vnet.ibm.com> Cc: Lai Jiangshan <laijs@cn.fujitsu.com> Cc: Li Zefan <lizf@cn.fujitsu.com> Cc: Przemysław Pawełczyk <przemyslaw@pawelczyk.it> Cc: Roland McGrath <roland@redhat.com> Cc: Sam Ravnborg <sam@ravnborg.org> Cc: Srikar Dronamraju <srikar@linux.vnet.ibm.com> Cc: Steven Rostedt <rostedt@goodmis.org> Cc: Tom Zanussi <tzanussi@gmail.com> Cc: Vegard Nossum <vegard.nossum@gmail.com> LKML-Reference: <20090813203428.31965.21939.stgit@localhost.localdomain> Signed-off-by: Frederic Weisbecker <fweisbec@gmail.com>
Diffstat (limited to 'arch/x86/kernel/kprobes.c')
-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)