|
48 | 48 | #include <linux/preempt.h> |
49 | 49 | #include <linux/module.h> |
50 | 50 | #include <linux/kdebug.h> |
| 51 | +#include <linux/kallsyms.h> |
51 | 52 |
|
52 | 53 | #include <asm/cacheflush.h> |
53 | 54 | #include <asm/desc.h> |
54 | 55 | #include <asm/pgtable.h> |
55 | 56 | #include <asm/uaccess.h> |
56 | 57 | #include <asm/alternative.h> |
| 58 | +#include <asm/insn.h> |
57 | 59 |
|
58 | 60 | void jprobe_return_end(void); |
59 | 61 |
|
@@ -244,6 +246,75 @@ static int __kprobes can_boost(kprobe_opcode_t *opcodes) |
244 | 246 | } |
245 | 247 | } |
246 | 248 |
|
| 249 | +/* Recover the probed instruction at addr for further analysis. */ |
| 250 | +static 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 */ |
| 276 | +static char __dummy_buf[KSYM_NAME_LEN]; |
| 277 | + |
| 278 | +/* Check if paddr is at an instruction boundary */ |
| 279 | +static 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 | 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 |
|
360 | 431 | int __kprobes arch_prepare_kprobe(struct kprobe *p) |
361 | 432 | { |
| 433 | + if (!can_probe((unsigned long)p->addr)) |
| 434 | + return -EILSEQ; |
362 | 435 | /* insn: must be on special executable page on x86. */ |
363 | 436 | p->ainsn.insn = get_insn_slot(); |
364 | 437 | if (!p->ainsn.insn) |
|
0 commit comments