Check that a crafted signal frame return address (rip) is rejected
by rt_sigreturn when shadow stack is enabled. Test both 'syscall'
and 'int $0x80' (CONFIG_IA32_EMULATION) with and without SHSTK.

Signed-off-by: Richard Patel <[email protected]>
---
 .../testing/selftests/x86/test_shadow_stack.c | 110 ++++++++++++++++++
 1 file changed, 110 insertions(+)

diff --git a/tools/testing/selftests/x86/test_shadow_stack.c 
b/tools/testing/selftests/x86/test_shadow_stack.c
index 3d6ca33edba4..67baebbdbd87 100644
--- a/tools/testing/selftests/x86/test_shadow_stack.c
+++ b/tools/testing/selftests/x86/test_shadow_stack.c
@@ -735,6 +735,110 @@ int test_32bit(void)
        return !segv_triggered;
 }
 
+/*
+ * Fork and sigreturn with a crafted signal frame.
+ * Returns the child's exit code, or 0x100+signal if it was killed.
+ */
+static int crafted_sigreturn(unsigned long sp, bool ia32, bool shstk)
+{
+       int status;
+       pid_t pid;
+
+       pid = fork();
+       if (!pid) {
+               signal(SIGSEGV, SIG_DFL);
+               if (ARCH_PRCTL(shstk ? ARCH_SHSTK_ENABLE : ARCH_SHSTK_DISABLE,
+                              ARCH_SHSTK_SHSTK))
+                       _exit(1);
+               if (ia32) /* ia32 rt_sigreturn */
+                       asm volatile("movq %0, %%rsp; int $0x80; ud2"
+                                    : : "r" (sp), "a" (173));
+               else /* rt_sigreturn */
+                       asm volatile("movq %0, %%rsp; syscall; ud2"
+                                    : : "r" (sp), "a" (__NR_rt_sigreturn));
+               __builtin_unreachable();
+       }
+
+       if (pid < 0 || waitpid(pid, &status, 0) != pid)
+               return -1;
+       return WIFSIGNALED(status) ? 0x100 + WTERMSIG(status) : 
WEXITSTATUS(status);
+}
+
+struct rt_sigframe_ia32 {
+       uint32_t pretcode, sig, pinfo, puc;
+       uint8_t info[128];
+       uint32_t uc_flags, uc_link, ss_sp, ss_flags, ss_size;
+       uint16_t gs, __gsh, fs, __fsh, es, __esh, ds, __dsh;
+       uint32_t di, si, bp, sp, bx, dx, cx, ax, trapno, err, ip;
+       uint16_t cs, __csh;
+       uint32_t flags, sp_at_signal;
+       uint16_t ss, __ssh;
+       uint32_t fpstate, oldmask, cr2;
+       uint32_t uc_sigmask[2];
+       uint8_t retcode[8];
+};
+
+_Static_assert(sizeof(struct rt_sigframe_ia32) == 268, "ia32 rt_sigframe 
layout");
+
+/* This tests whether shadow stack protects sigreturn */
+int test_sigreturn(void)
+{
+       static const uint8_t target_routine[] = {
+               0xbf, 0x2a, 0x00, 0x00, 0x00,   /* mov $42, %edi */
+               0xb8, 0xe7, 0x00, 0x00, 0x00,   /* mov $231, %eax (exit_group) 
*/
+               0x0f, 0x05,                     /* syscall */
+       };
+
+       struct { uint64_t pretcode; ucontext_t uc; } *f64;
+       struct rt_sigframe_ia32 *f32;
+       void *retsite;
+       int ret = 1;
+
+       /* ia32 sigreturn truncates RIP and RSP to 32 bits */
+       retsite = mmap(0, PAGE_SIZE, PROT_READ | PROT_WRITE | PROT_EXEC,
+                      MAP_32BIT | MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
+       if (retsite == MAP_FAILED)
+               return 1;
+       memcpy(retsite, target_routine, sizeof(target_routine));
+
+       f64 = retsite + 0x100;
+       f64->uc.uc_mcontext.gregs[REG_RIP] = (unsigned long)retsite;
+       f64->uc.uc_mcontext.gregs[REG_RSP] = (unsigned long)retsite + PAGE_SIZE;
+       f64->uc.uc_mcontext.gregs[REG_CSGSFS] = 0x33; /* __USER_CS */
+
+       f32 = retsite + 0x800;
+       f32->ip = (unsigned long)retsite;
+       f32->sp = (unsigned long)retsite + PAGE_SIZE;
+       f32->cs = 0x33; /* __USER_CS (64-bit) */
+       f32->ss = 0x2b; /* __USER_DS */
+
+       if (crafted_sigreturn((unsigned long)f64 + 8, false, false) != 42) {
+               printf("[FAIL]\trt_sigreturn protection (hijack failed without 
shadow stack)\n");
+               goto out;
+       }
+       if (crafted_sigreturn((unsigned long)f64 + 8, false, true) != 0x100 + 
SIGSEGV) {
+               printf("[FAIL]\trt_sigreturn protection\n");
+               goto out;
+       }
+       printf("[OK]\trt_sigreturn protection\n");
+
+       if (crafted_sigreturn((unsigned long)f32 + 4, true, false) != 42) {
+               printf("[SKIP]\tia32 rt_sigreturn protection (int $0x80 
unavailable)\n");
+               ret = 0;
+               goto out;
+       }
+       if (crafted_sigreturn((unsigned long)f32 + 4, true, true) != 0x100 + 
SIGSEGV) {
+               printf("[FAIL]\tia32 rt_sigreturn protection\n");
+               goto out;
+       }
+       printf("[OK]\tia32 rt_sigreturn protection\n");
+       ret = 0;
+
+out:
+       munmap(retsite, PAGE_SIZE);
+       return ret;
+}
+
 static int parse_uint_from_file(const char *file, const char *fmt)
 {
        int err, ret;
@@ -1145,6 +1249,12 @@ int main(int argc, char *argv[])
                goto out;
        }
 
+       if (test_sigreturn()) {
+               ret = 1;
+               printf("[FAIL]\tsigreturn test\n");
+               goto out;
+       }
+
        if (test_uretprobe()) {
                ret = 1;
                printf("[FAIL]\turetprobe test\n");
-- 
2.52.0


Reply via email to