Adding return uprobe test for shadow stack and making sure it's
working properly. Borrowed some of the code from bpf selftests.

Signed-off-by: Jiri Olsa <jo...@kernel.org>
---
 .../testing/selftests/x86/test_shadow_stack.c | 145 ++++++++++++++++++
 1 file changed, 145 insertions(+)

diff --git a/tools/testing/selftests/x86/test_shadow_stack.c 
b/tools/testing/selftests/x86/test_shadow_stack.c
index 757e6527f67e..e3501b7e2ecc 100644
--- a/tools/testing/selftests/x86/test_shadow_stack.c
+++ b/tools/testing/selftests/x86/test_shadow_stack.c
@@ -34,6 +34,7 @@
 #include <sys/ptrace.h>
 #include <sys/signal.h>
 #include <linux/elf.h>
+#include <linux/perf_event.h>
 
 /*
  * Define the ABI defines if needed, so people can run the tests
@@ -681,6 +682,144 @@ int test_32bit(void)
        return !segv_triggered;
 }
 
+static int parse_uint_from_file(const char *file, const char *fmt)
+{
+       int err, ret;
+       FILE *f;
+
+       f = fopen(file, "re");
+       if (!f) {
+               err = -errno;
+               printf("failed to open '%s': %d\n", file, err);
+               return err;
+       }
+       err = fscanf(f, fmt, &ret);
+       if (err != 1) {
+               err = err == EOF ? -EIO : -errno;
+               printf("failed to parse '%s': %d\n", file, err);
+               fclose(f);
+               return err;
+       }
+       fclose(f);
+       return ret;
+}
+
+static int determine_uprobe_perf_type(void)
+{
+       const char *file = "/sys/bus/event_source/devices/uprobe/type";
+
+       return parse_uint_from_file(file, "%d\n");
+}
+
+static int determine_uprobe_retprobe_bit(void)
+{
+       const char *file = 
"/sys/bus/event_source/devices/uprobe/format/retprobe";
+
+       return parse_uint_from_file(file, "config:%d\n");
+}
+
+static ssize_t get_uprobe_offset(const void *addr)
+{
+       size_t start, end, base;
+       char buf[256];
+       bool found = false;
+       FILE *f;
+
+       f = fopen("/proc/self/maps", "r");
+       if (!f)
+               return -errno;
+
+       while (fscanf(f, "%zx-%zx %s %zx %*[^\n]\n", &start, &end, buf, &base) 
== 4) {
+               if (buf[2] == 'x' && (uintptr_t)addr >= start && 
(uintptr_t)addr < end) {
+                       found = true;
+                       break;
+               }
+       }
+
+       fclose(f);
+
+       if (!found)
+               return -ESRCH;
+
+       return (uintptr_t)addr - start + base;
+}
+
+static __attribute__((noinline)) void uretprobe_trigger(void)
+{
+       asm volatile ("");
+}
+
+/*
+ * This test setups return uprobe, which is sensitive to shadow stack
+ * (crashes without extra fix). After executing the uretprobe we fail
+ * the test if we receive SIGSEGV, no crash means we're good.
+ *
+ * Helper functions above borrowed from bpf selftests.
+ */
+static int test_uretprobe(void)
+{
+       const size_t attr_sz = sizeof(struct perf_event_attr);
+       const char *file = "/proc/self/exe";
+       int bit, fd = 0, type, err = 1;
+       struct perf_event_attr attr;
+       struct sigaction sa = {};
+       ssize_t offset;
+
+       type = determine_uprobe_perf_type();
+       if (type < 0) {
+               if (type == -ENOENT)
+                       printf("[SKIP]\tUretprobe test, uprobes are not 
available\n");
+               return 0;
+       }
+
+       offset = get_uprobe_offset(uretprobe_trigger);
+       if (offset < 0)
+               return 1;
+
+       bit = determine_uprobe_retprobe_bit();
+       if (bit < 0)
+               return 1;
+
+       sa.sa_sigaction = segv_gp_handler;
+       sa.sa_flags = SA_SIGINFO;
+       if (sigaction(SIGSEGV, &sa, NULL))
+               return 1;
+
+       /* Setup return uprobe through perf event interface. */
+       memset(&attr, 0, attr_sz);
+       attr.size = attr_sz;
+       attr.type = type;
+       attr.config = 1 << bit;
+       attr.config1 = (__u64) (unsigned long) file;
+       attr.config2 = offset;
+
+       fd = syscall(__NR_perf_event_open, &attr, 0 /* pid */, -1 /* cpu */,
+                    -1 /* group_fd */, PERF_FLAG_FD_CLOEXEC);
+       if (fd < 0)
+               goto out;
+
+       if (sigsetjmp(jmp_buffer, 1))
+               goto out;
+
+       ARCH_PRCTL(ARCH_SHSTK_ENABLE, ARCH_SHSTK_SHSTK);
+
+       /*
+        * This either segfaults and goes through sigsetjmp above
+        * or succeeds and we're good.
+        */
+       uretprobe_trigger();
+
+       printf("[OK]\tUretprobe test\n");
+       err = 0;
+
+out:
+       ARCH_PRCTL(ARCH_SHSTK_DISABLE, ARCH_SHSTK_SHSTK);
+       signal(SIGSEGV, SIG_DFL);
+       if (fd)
+               close(fd);
+       return err;
+}
+
 void segv_handler_ptrace(int signum, siginfo_t *si, void *uc)
 {
        /* The SSP adjustment caused a segfault. */
@@ -867,6 +1006,12 @@ int main(int argc, char *argv[])
                goto out;
        }
 
+       if (test_uretprobe()) {
+               ret = 1;
+               printf("[FAIL]\turetprobe test\n");
+               goto out;
+       }
+
        return ret;
 
 out:
-- 
2.45.1


Reply via email to