|
12 | 12 | #include <linux/ptrace.h> |
13 | 13 | #include <linux/uprobes.h> |
14 | 14 | #include <linux/uaccess.h> |
| 15 | +#include <linux/syscalls.h> |
15 | 16 |
|
16 | 17 | #include <linux/kdebug.h> |
17 | 18 | #include <asm/processor.h> |
@@ -308,6 +309,122 @@ static int uprobe_init_insn(struct arch_uprobe *auprobe, struct insn *insn, bool |
308 | 309 | } |
309 | 310 |
|
310 | 311 | #ifdef CONFIG_X86_64 |
| 312 | + |
| 313 | +asm ( |
| 314 | + ".pushsection .rodata\n" |
| 315 | + ".global uretprobe_trampoline_entry\n" |
| 316 | + "uretprobe_trampoline_entry:\n" |
| 317 | + "pushq %rax\n" |
| 318 | + "pushq %rcx\n" |
| 319 | + "pushq %r11\n" |
| 320 | + "movq $" __stringify(__NR_uretprobe) ", %rax\n" |
| 321 | + "syscall\n" |
| 322 | + ".global uretprobe_syscall_check\n" |
| 323 | + "uretprobe_syscall_check:\n" |
| 324 | + "popq %r11\n" |
| 325 | + "popq %rcx\n" |
| 326 | + |
| 327 | + /* The uretprobe syscall replaces stored %rax value with final |
| 328 | + * return address, so we don't restore %rax in here and just |
| 329 | + * call ret. |
| 330 | + */ |
| 331 | + "retq\n" |
| 332 | + ".global uretprobe_trampoline_end\n" |
| 333 | + "uretprobe_trampoline_end:\n" |
| 334 | + ".popsection\n" |
| 335 | +); |
| 336 | + |
| 337 | +extern u8 uretprobe_trampoline_entry[]; |
| 338 | +extern u8 uretprobe_trampoline_end[]; |
| 339 | +extern u8 uretprobe_syscall_check[]; |
| 340 | + |
| 341 | +void *arch_uprobe_trampoline(unsigned long *psize) |
| 342 | +{ |
| 343 | + static uprobe_opcode_t insn = UPROBE_SWBP_INSN; |
| 344 | + struct pt_regs *regs = task_pt_regs(current); |
| 345 | + |
| 346 | + /* |
| 347 | + * At the moment the uretprobe syscall trampoline is supported |
| 348 | + * only for native 64-bit process, the compat process still uses |
| 349 | + * standard breakpoint. |
| 350 | + */ |
| 351 | + if (user_64bit_mode(regs)) { |
| 352 | + *psize = uretprobe_trampoline_end - uretprobe_trampoline_entry; |
| 353 | + return uretprobe_trampoline_entry; |
| 354 | + } |
| 355 | + |
| 356 | + *psize = UPROBE_SWBP_INSN_SIZE; |
| 357 | + return &insn; |
| 358 | +} |
| 359 | + |
| 360 | +static unsigned long trampoline_check_ip(void) |
| 361 | +{ |
| 362 | + unsigned long tramp = uprobe_get_trampoline_vaddr(); |
| 363 | + |
| 364 | + return tramp + (uretprobe_syscall_check - uretprobe_trampoline_entry); |
| 365 | +} |
| 366 | + |
| 367 | +SYSCALL_DEFINE0(uretprobe) |
| 368 | +{ |
| 369 | + struct pt_regs *regs = task_pt_regs(current); |
| 370 | + unsigned long err, ip, sp, r11_cx_ax[3]; |
| 371 | + |
| 372 | + if (regs->ip != trampoline_check_ip()) |
| 373 | + goto sigill; |
| 374 | + |
| 375 | + err = copy_from_user(r11_cx_ax, (void __user *)regs->sp, sizeof(r11_cx_ax)); |
| 376 | + if (err) |
| 377 | + goto sigill; |
| 378 | + |
| 379 | + /* expose the "right" values of r11/cx/ax/sp to uprobe_consumer/s */ |
| 380 | + regs->r11 = r11_cx_ax[0]; |
| 381 | + regs->cx = r11_cx_ax[1]; |
| 382 | + regs->ax = r11_cx_ax[2]; |
| 383 | + regs->sp += sizeof(r11_cx_ax); |
| 384 | + regs->orig_ax = -1; |
| 385 | + |
| 386 | + ip = regs->ip; |
| 387 | + sp = regs->sp; |
| 388 | + |
| 389 | + uprobe_handle_trampoline(regs); |
| 390 | + |
| 391 | + /* |
| 392 | + * Some of the uprobe consumers has changed sp, we can do nothing, |
| 393 | + * just return via iret. |
| 394 | + * .. or shadow stack is enabled, in which case we need to skip |
| 395 | + * return through the user space stack address. |
| 396 | + */ |
| 397 | + if (regs->sp != sp || shstk_is_enabled()) |
| 398 | + return regs->ax; |
| 399 | + regs->sp -= sizeof(r11_cx_ax); |
| 400 | + |
| 401 | + /* for the case uprobe_consumer has changed r11/cx */ |
| 402 | + r11_cx_ax[0] = regs->r11; |
| 403 | + r11_cx_ax[1] = regs->cx; |
| 404 | + |
| 405 | + /* |
| 406 | + * ax register is passed through as return value, so we can use |
| 407 | + * its space on stack for ip value and jump to it through the |
| 408 | + * trampoline's ret instruction |
| 409 | + */ |
| 410 | + r11_cx_ax[2] = regs->ip; |
| 411 | + regs->ip = ip; |
| 412 | + |
| 413 | + err = copy_to_user((void __user *)regs->sp, r11_cx_ax, sizeof(r11_cx_ax)); |
| 414 | + if (err) |
| 415 | + goto sigill; |
| 416 | + |
| 417 | + /* ensure sysret, see do_syscall_64() */ |
| 418 | + regs->r11 = regs->flags; |
| 419 | + regs->cx = regs->ip; |
| 420 | + |
| 421 | + return regs->ax; |
| 422 | + |
| 423 | +sigill: |
| 424 | + force_sig(SIGILL); |
| 425 | + return -1; |
| 426 | +} |
| 427 | + |
311 | 428 | /* |
312 | 429 | * If arch_uprobe->insn doesn't use rip-relative addressing, return |
313 | 430 | * immediately. Otherwise, rewrite the instruction so that it accesses |
|
0 commit comments