diff options
Diffstat (limited to 'arch/x86/kernel/uprobes.c')
-rw-r--r-- | arch/x86/kernel/uprobes.c | 107 |
1 files changed, 103 insertions, 4 deletions
diff --git a/arch/x86/kernel/uprobes.c b/arch/x86/kernel/uprobes.c index a3755d293a48..85c7ef23d99f 100644 --- a/arch/x86/kernel/uprobes.c +++ b/arch/x86/kernel/uprobes.c | |||
@@ -528,11 +528,11 @@ static int default_pre_xol_op(struct arch_uprobe *auprobe, struct pt_regs *regs) | |||
528 | return 0; | 528 | return 0; |
529 | } | 529 | } |
530 | 530 | ||
531 | static int push_ret_address(struct pt_regs *regs, unsigned long ip) | 531 | static int emulate_push_stack(struct pt_regs *regs, unsigned long val) |
532 | { | 532 | { |
533 | unsigned long new_sp = regs->sp - sizeof_long(); | 533 | unsigned long new_sp = regs->sp - sizeof_long(); |
534 | 534 | ||
535 | if (copy_to_user((void __user *)new_sp, &ip, sizeof_long())) | 535 | if (copy_to_user((void __user *)new_sp, &val, sizeof_long())) |
536 | return -EFAULT; | 536 | return -EFAULT; |
537 | 537 | ||
538 | regs->sp = new_sp; | 538 | regs->sp = new_sp; |
@@ -566,7 +566,7 @@ static int default_post_xol_op(struct arch_uprobe *auprobe, struct pt_regs *regs | |||
566 | regs->ip += correction; | 566 | regs->ip += correction; |
567 | } else if (auprobe->defparam.fixups & UPROBE_FIX_CALL) { | 567 | } else if (auprobe->defparam.fixups & UPROBE_FIX_CALL) { |
568 | regs->sp += sizeof_long(); /* Pop incorrect return address */ | 568 | regs->sp += sizeof_long(); /* Pop incorrect return address */ |
569 | if (push_ret_address(regs, utask->vaddr + auprobe->defparam.ilen)) | 569 | if (emulate_push_stack(regs, utask->vaddr + auprobe->defparam.ilen)) |
570 | return -ERESTART; | 570 | return -ERESTART; |
571 | } | 571 | } |
572 | /* popf; tell the caller to not touch TF */ | 572 | /* popf; tell the caller to not touch TF */ |
@@ -655,7 +655,7 @@ static bool branch_emulate_op(struct arch_uprobe *auprobe, struct pt_regs *regs) | |||
655 | * | 655 | * |
656 | * But there is corner case, see the comment in ->post_xol(). | 656 | * But there is corner case, see the comment in ->post_xol(). |
657 | */ | 657 | */ |
658 | if (push_ret_address(regs, new_ip)) | 658 | if (emulate_push_stack(regs, new_ip)) |
659 | return false; | 659 | return false; |
660 | } else if (!check_jmp_cond(auprobe, regs)) { | 660 | } else if (!check_jmp_cond(auprobe, regs)) { |
661 | offs = 0; | 661 | offs = 0; |
@@ -665,6 +665,16 @@ static bool branch_emulate_op(struct arch_uprobe *auprobe, struct pt_regs *regs) | |||
665 | return true; | 665 | return true; |
666 | } | 666 | } |
667 | 667 | ||
668 | static bool push_emulate_op(struct arch_uprobe *auprobe, struct pt_regs *regs) | ||
669 | { | ||
670 | unsigned long *src_ptr = (void *)regs + auprobe->push.reg_offset; | ||
671 | |||
672 | if (emulate_push_stack(regs, *src_ptr)) | ||
673 | return false; | ||
674 | regs->ip += auprobe->push.ilen; | ||
675 | return true; | ||
676 | } | ||
677 | |||
668 | static int branch_post_xol_op(struct arch_uprobe *auprobe, struct pt_regs *regs) | 678 | static int branch_post_xol_op(struct arch_uprobe *auprobe, struct pt_regs *regs) |
669 | { | 679 | { |
670 | BUG_ON(!branch_is_call(auprobe)); | 680 | BUG_ON(!branch_is_call(auprobe)); |
@@ -703,6 +713,10 @@ static const struct uprobe_xol_ops branch_xol_ops = { | |||
703 | .post_xol = branch_post_xol_op, | 713 | .post_xol = branch_post_xol_op, |
704 | }; | 714 | }; |
705 | 715 | ||
716 | static const struct uprobe_xol_ops push_xol_ops = { | ||
717 | .emulate = push_emulate_op, | ||
718 | }; | ||
719 | |||
706 | /* Returns -ENOSYS if branch_xol_ops doesn't handle this insn */ | 720 | /* Returns -ENOSYS if branch_xol_ops doesn't handle this insn */ |
707 | static int branch_setup_xol_ops(struct arch_uprobe *auprobe, struct insn *insn) | 721 | static int branch_setup_xol_ops(struct arch_uprobe *auprobe, struct insn *insn) |
708 | { | 722 | { |
@@ -750,6 +764,87 @@ static int branch_setup_xol_ops(struct arch_uprobe *auprobe, struct insn *insn) | |||
750 | return 0; | 764 | return 0; |
751 | } | 765 | } |
752 | 766 | ||
767 | /* Returns -ENOSYS if push_xol_ops doesn't handle this insn */ | ||
768 | static int push_setup_xol_ops(struct arch_uprobe *auprobe, struct insn *insn) | ||
769 | { | ||
770 | u8 opc1 = OPCODE1(insn), reg_offset = 0; | ||
771 | |||
772 | if (opc1 < 0x50 || opc1 > 0x57) | ||
773 | return -ENOSYS; | ||
774 | |||
775 | if (insn->length > 2) | ||
776 | return -ENOSYS; | ||
777 | if (insn->length == 2) { | ||
778 | /* only support rex_prefix 0x41 (x64 only) */ | ||
779 | #ifdef CONFIG_X86_64 | ||
780 | if (insn->rex_prefix.nbytes != 1 || | ||
781 | insn->rex_prefix.bytes[0] != 0x41) | ||
782 | return -ENOSYS; | ||
783 | |||
784 | switch (opc1) { | ||
785 | case 0x50: | ||
786 | reg_offset = offsetof(struct pt_regs, r8); | ||
787 | break; | ||
788 | case 0x51: | ||
789 | reg_offset = offsetof(struct pt_regs, r9); | ||
790 | break; | ||
791 | case 0x52: | ||
792 | reg_offset = offsetof(struct pt_regs, r10); | ||
793 | break; | ||
794 | case 0x53: | ||
795 | reg_offset = offsetof(struct pt_regs, r11); | ||
796 | break; | ||
797 | case 0x54: | ||
798 | reg_offset = offsetof(struct pt_regs, r12); | ||
799 | break; | ||
800 | case 0x55: | ||
801 | reg_offset = offsetof(struct pt_regs, r13); | ||
802 | break; | ||
803 | case 0x56: | ||
804 | reg_offset = offsetof(struct pt_regs, r14); | ||
805 | break; | ||
806 | case 0x57: | ||
807 | reg_offset = offsetof(struct pt_regs, r15); | ||
808 | break; | ||
809 | } | ||
810 | #else | ||
811 | return -ENOSYS; | ||
812 | #endif | ||
813 | } else { | ||
814 | switch (opc1) { | ||
815 | case 0x50: | ||
816 | reg_offset = offsetof(struct pt_regs, ax); | ||
817 | break; | ||
818 | case 0x51: | ||
819 | reg_offset = offsetof(struct pt_regs, cx); | ||
820 | break; | ||
821 | case 0x52: | ||
822 | reg_offset = offsetof(struct pt_regs, dx); | ||
823 | break; | ||
824 | case 0x53: | ||
825 | reg_offset = offsetof(struct pt_regs, bx); | ||
826 | break; | ||
827 | case 0x54: | ||
828 | reg_offset = offsetof(struct pt_regs, sp); | ||
829 | break; | ||
830 | case 0x55: | ||
831 | reg_offset = offsetof(struct pt_regs, bp); | ||
832 | break; | ||
833 | case 0x56: | ||
834 | reg_offset = offsetof(struct pt_regs, si); | ||
835 | break; | ||
836 | case 0x57: | ||
837 | reg_offset = offsetof(struct pt_regs, di); | ||
838 | break; | ||
839 | } | ||
840 | } | ||
841 | |||
842 | auprobe->push.reg_offset = reg_offset; | ||
843 | auprobe->push.ilen = insn->length; | ||
844 | auprobe->ops = &push_xol_ops; | ||
845 | return 0; | ||
846 | } | ||
847 | |||
753 | /** | 848 | /** |
754 | * arch_uprobe_analyze_insn - instruction analysis including validity and fixups. | 849 | * arch_uprobe_analyze_insn - instruction analysis including validity and fixups. |
755 | * @mm: the probed address space. | 850 | * @mm: the probed address space. |
@@ -771,6 +866,10 @@ int arch_uprobe_analyze_insn(struct arch_uprobe *auprobe, struct mm_struct *mm, | |||
771 | if (ret != -ENOSYS) | 866 | if (ret != -ENOSYS) |
772 | return ret; | 867 | return ret; |
773 | 868 | ||
869 | ret = push_setup_xol_ops(auprobe, &insn); | ||
870 | if (ret != -ENOSYS) | ||
871 | return ret; | ||
872 | |||
774 | /* | 873 | /* |
775 | * Figure out which fixups default_post_xol_op() will need to perform, | 874 | * Figure out which fixups default_post_xol_op() will need to perform, |
776 | * and annotate defparam->fixups accordingly. | 875 | * and annotate defparam->fixups accordingly. |