|
34 | 34 | #include <sys/ptrace.h>
|
35 | 35 | #include <sys/signal.h>
|
36 | 36 | #include <linux/elf.h>
|
| 37 | +#include <linux/perf_event.h> |
37 | 38 |
|
38 | 39 | /*
|
39 | 40 | * Define the ABI defines if needed, so people can run the tests
|
@@ -734,6 +735,144 @@ int test_32bit(void)
|
734 | 735 | return !segv_triggered;
|
735 | 736 | }
|
736 | 737 |
|
| 738 | +static int parse_uint_from_file(const char *file, const char *fmt) |
| 739 | +{ |
| 740 | + int err, ret; |
| 741 | + FILE *f; |
| 742 | + |
| 743 | + f = fopen(file, "re"); |
| 744 | + if (!f) { |
| 745 | + err = -errno; |
| 746 | + printf("failed to open '%s': %d\n", file, err); |
| 747 | + return err; |
| 748 | + } |
| 749 | + err = fscanf(f, fmt, &ret); |
| 750 | + if (err != 1) { |
| 751 | + err = err == EOF ? -EIO : -errno; |
| 752 | + printf("failed to parse '%s': %d\n", file, err); |
| 753 | + fclose(f); |
| 754 | + return err; |
| 755 | + } |
| 756 | + fclose(f); |
| 757 | + return ret; |
| 758 | +} |
| 759 | + |
| 760 | +static int determine_uprobe_perf_type(void) |
| 761 | +{ |
| 762 | + const char *file = "/sys/bus/event_source/devices/uprobe/type"; |
| 763 | + |
| 764 | + return parse_uint_from_file(file, "%d\n"); |
| 765 | +} |
| 766 | + |
| 767 | +static int determine_uprobe_retprobe_bit(void) |
| 768 | +{ |
| 769 | + const char *file = "/sys/bus/event_source/devices/uprobe/format/retprobe"; |
| 770 | + |
| 771 | + return parse_uint_from_file(file, "config:%d\n"); |
| 772 | +} |
| 773 | + |
| 774 | +static ssize_t get_uprobe_offset(const void *addr) |
| 775 | +{ |
| 776 | + size_t start, end, base; |
| 777 | + char buf[256]; |
| 778 | + bool found = false; |
| 779 | + FILE *f; |
| 780 | + |
| 781 | + f = fopen("/proc/self/maps", "r"); |
| 782 | + if (!f) |
| 783 | + return -errno; |
| 784 | + |
| 785 | + while (fscanf(f, "%zx-%zx %s %zx %*[^\n]\n", &start, &end, buf, &base) == 4) { |
| 786 | + if (buf[2] == 'x' && (uintptr_t)addr >= start && (uintptr_t)addr < end) { |
| 787 | + found = true; |
| 788 | + break; |
| 789 | + } |
| 790 | + } |
| 791 | + |
| 792 | + fclose(f); |
| 793 | + |
| 794 | + if (!found) |
| 795 | + return -ESRCH; |
| 796 | + |
| 797 | + return (uintptr_t)addr - start + base; |
| 798 | +} |
| 799 | + |
| 800 | +static __attribute__((noinline)) void uretprobe_trigger(void) |
| 801 | +{ |
| 802 | + asm volatile (""); |
| 803 | +} |
| 804 | + |
| 805 | +/* |
| 806 | + * This test setups return uprobe, which is sensitive to shadow stack |
| 807 | + * (crashes without extra fix). After executing the uretprobe we fail |
| 808 | + * the test if we receive SIGSEGV, no crash means we're good. |
| 809 | + * |
| 810 | + * Helper functions above borrowed from bpf selftests. |
| 811 | + */ |
| 812 | +static int test_uretprobe(void) |
| 813 | +{ |
| 814 | + const size_t attr_sz = sizeof(struct perf_event_attr); |
| 815 | + const char *file = "/proc/self/exe"; |
| 816 | + int bit, fd = 0, type, err = 1; |
| 817 | + struct perf_event_attr attr; |
| 818 | + struct sigaction sa = {}; |
| 819 | + ssize_t offset; |
| 820 | + |
| 821 | + type = determine_uprobe_perf_type(); |
| 822 | + if (type < 0) { |
| 823 | + if (type == -ENOENT) |
| 824 | + printf("[SKIP]\tUretprobe test, uprobes are not available\n"); |
| 825 | + return 0; |
| 826 | + } |
| 827 | + |
| 828 | + offset = get_uprobe_offset(uretprobe_trigger); |
| 829 | + if (offset < 0) |
| 830 | + return 1; |
| 831 | + |
| 832 | + bit = determine_uprobe_retprobe_bit(); |
| 833 | + if (bit < 0) |
| 834 | + return 1; |
| 835 | + |
| 836 | + sa.sa_sigaction = segv_gp_handler; |
| 837 | + sa.sa_flags = SA_SIGINFO; |
| 838 | + if (sigaction(SIGSEGV, &sa, NULL)) |
| 839 | + return 1; |
| 840 | + |
| 841 | + /* Setup return uprobe through perf event interface. */ |
| 842 | + memset(&attr, 0, attr_sz); |
| 843 | + attr.size = attr_sz; |
| 844 | + attr.type = type; |
| 845 | + attr.config = 1 << bit; |
| 846 | + attr.config1 = (__u64) (unsigned long) file; |
| 847 | + attr.config2 = offset; |
| 848 | + |
| 849 | + fd = syscall(__NR_perf_event_open, &attr, 0 /* pid */, -1 /* cpu */, |
| 850 | + -1 /* group_fd */, PERF_FLAG_FD_CLOEXEC); |
| 851 | + if (fd < 0) |
| 852 | + goto out; |
| 853 | + |
| 854 | + if (sigsetjmp(jmp_buffer, 1)) |
| 855 | + goto out; |
| 856 | + |
| 857 | + ARCH_PRCTL(ARCH_SHSTK_ENABLE, ARCH_SHSTK_SHSTK); |
| 858 | + |
| 859 | + /* |
| 860 | + * This either segfaults and goes through sigsetjmp above |
| 861 | + * or succeeds and we're good. |
| 862 | + */ |
| 863 | + uretprobe_trigger(); |
| 864 | + |
| 865 | + printf("[OK]\tUretprobe test\n"); |
| 866 | + err = 0; |
| 867 | + |
| 868 | +out: |
| 869 | + ARCH_PRCTL(ARCH_SHSTK_DISABLE, ARCH_SHSTK_SHSTK); |
| 870 | + signal(SIGSEGV, SIG_DFL); |
| 871 | + if (fd) |
| 872 | + close(fd); |
| 873 | + return err; |
| 874 | +} |
| 875 | + |
737 | 876 | void segv_handler_ptrace(int signum, siginfo_t *si, void *uc)
|
738 | 877 | {
|
739 | 878 | /* The SSP adjustment caused a segfault. */
|
@@ -926,6 +1065,12 @@ int main(int argc, char *argv[])
|
926 | 1065 | goto out;
|
927 | 1066 | }
|
928 | 1067 |
|
| 1068 | + if (test_uretprobe()) { |
| 1069 | + ret = 1; |
| 1070 | + printf("[FAIL]\turetprobe test\n"); |
| 1071 | + goto out; |
| 1072 | + } |
| 1073 | + |
929 | 1074 | return ret;
|
930 | 1075 |
|
931 | 1076 | out:
|
|
0 commit comments