x86/shstk: Handle signals for shadow stack
authorRick Edgecombe <rick.p.edgecombe@intel.com>
Tue, 13 Jun 2023 00:10:57 +0000 (17:10 -0700)
committerDave Hansen <dave.hansen@linux.intel.com>
Wed, 2 Aug 2023 22:01:50 +0000 (15:01 -0700)
When a signal is handled, the context is pushed to the stack before
handling it. For shadow stacks, since the shadow stack only tracks return
addresses, there isn't any state that needs to be pushed. However, there
are still a few things that need to be done. These things are visible to
userspace and which will be kernel ABI for shadow stacks.

One is to make sure the restorer address is written to shadow stack, since
the signal handler (if not changing ucontext) returns to the restorer, and
the restorer calls sigreturn. So add the restorer on the shadow stack
before handling the signal, so there is not a conflict when the signal
handler returns to the restorer.

The other thing to do is to place some type of checkable token on the
thread's shadow stack before handling the signal and check it during
sigreturn. This is an extra layer of protection to hamper attackers
calling sigreturn manually as in SROP-like attacks.

For this token the shadow stack data format defined earlier can be used.
Have the data pushed be the previous SSP. In the future the sigreturn
might want to return back to a different stack. Storing the SSP (instead
of a restore offset or something) allows for future functionality that
may want to restore to a different stack.

So, when handling a signal push
 - the SSP pointing in the shadow stack data format
 - the restorer address below the restore token.

In sigreturn, verify SSP is stored in the data format and pop the shadow
stack.

Co-developed-by: Yu-cheng Yu <yu-cheng.yu@intel.com>
Signed-off-by: Yu-cheng Yu <yu-cheng.yu@intel.com>
Signed-off-by: Rick Edgecombe <rick.p.edgecombe@intel.com>
Signed-off-by: Dave Hansen <dave.hansen@linux.intel.com>
Reviewed-by: Borislav Petkov (AMD) <bp@alien8.de>
Reviewed-by: Kees Cook <keescook@chromium.org>
Acked-by: Mike Rapoport (IBM) <rppt@kernel.org>
Tested-by: Pengfei Xu <pengfei.xu@intel.com>
Tested-by: John Allen <john.allen@amd.com>
Tested-by: Kees Cook <keescook@chromium.org>
Link: https://lore.kernel.org/all/20230613001108.3040476-32-rick.p.edgecombe%40intel.com
arch/x86/include/asm/shstk.h
arch/x86/kernel/shstk.c
arch/x86/kernel/signal.c
arch/x86/kernel/signal_64.c

index d4a5c7b10cb58037de5007d465382f905e1d5753..ecb23a8ca47d28429fa50e38f7d9c5e906067019 100644 (file)
@@ -6,6 +6,7 @@
 #include <linux/types.h>
 
 struct task_struct;
+struct ksignal;
 
 #ifdef CONFIG_X86_USER_SHADOW_STACK
 struct thread_shstk {
@@ -18,6 +19,8 @@ void reset_thread_features(void);
 unsigned long shstk_alloc_thread_stack(struct task_struct *p, unsigned long clone_flags,
                                       unsigned long stack_size);
 void shstk_free(struct task_struct *p);
+int setup_signal_shadow_stack(struct ksignal *ksig);
+int restore_signal_shadow_stack(void);
 #else
 static inline long shstk_prctl(struct task_struct *task, int option,
                               unsigned long arg2) { return -EINVAL; }
@@ -26,6 +29,8 @@ static inline unsigned long shstk_alloc_thread_stack(struct task_struct *p,
                                                     unsigned long clone_flags,
                                                     unsigned long stack_size) { return 0; }
 static inline void shstk_free(struct task_struct *p) {}
+static inline int setup_signal_shadow_stack(struct ksignal *ksig) { return 0; }
+static inline int restore_signal_shadow_stack(void) { return 0; }
 #endif /* CONFIG_X86_USER_SHADOW_STACK */
 
 #endif /* __ASSEMBLY__ */
index e22928c63ffcf8960215e84546c6bb3476e162d5..f02e8ea4f1b5ebd2a18ecef52f7aec6875663aeb 100644 (file)
@@ -232,6 +232,101 @@ static int get_shstk_data(unsigned long *data, unsigned long __user *addr)
        return 0;
 }
 
+static int shstk_push_sigframe(unsigned long *ssp)
+{
+       unsigned long target_ssp = *ssp;
+
+       /* Token must be aligned */
+       if (!IS_ALIGNED(target_ssp, 8))
+               return -EINVAL;
+
+       *ssp -= SS_FRAME_SIZE;
+       if (put_shstk_data((void *__user)*ssp, target_ssp))
+               return -EFAULT;
+
+       return 0;
+}
+
+static int shstk_pop_sigframe(unsigned long *ssp)
+{
+       unsigned long token_addr;
+       int err;
+
+       err = get_shstk_data(&token_addr, (unsigned long __user *)*ssp);
+       if (unlikely(err))
+               return err;
+
+       /* Restore SSP aligned? */
+       if (unlikely(!IS_ALIGNED(token_addr, 8)))
+               return -EINVAL;
+
+       /* SSP in userspace? */
+       if (unlikely(token_addr >= TASK_SIZE_MAX))
+               return -EINVAL;
+
+       *ssp = token_addr;
+
+       return 0;
+}
+
+int setup_signal_shadow_stack(struct ksignal *ksig)
+{
+       void __user *restorer = ksig->ka.sa.sa_restorer;
+       unsigned long ssp;
+       int err;
+
+       if (!cpu_feature_enabled(X86_FEATURE_USER_SHSTK) ||
+           !features_enabled(ARCH_SHSTK_SHSTK))
+               return 0;
+
+       if (!restorer)
+               return -EINVAL;
+
+       ssp = get_user_shstk_addr();
+       if (unlikely(!ssp))
+               return -EINVAL;
+
+       err = shstk_push_sigframe(&ssp);
+       if (unlikely(err))
+               return err;
+
+       /* Push restorer address */
+       ssp -= SS_FRAME_SIZE;
+       err = write_user_shstk_64((u64 __user *)ssp, (u64)restorer);
+       if (unlikely(err))
+               return -EFAULT;
+
+       fpregs_lock_and_load();
+       wrmsrl(MSR_IA32_PL3_SSP, ssp);
+       fpregs_unlock();
+
+       return 0;
+}
+
+int restore_signal_shadow_stack(void)
+{
+       unsigned long ssp;
+       int err;
+
+       if (!cpu_feature_enabled(X86_FEATURE_USER_SHSTK) ||
+           !features_enabled(ARCH_SHSTK_SHSTK))
+               return 0;
+
+       ssp = get_user_shstk_addr();
+       if (unlikely(!ssp))
+               return -EINVAL;
+
+       err = shstk_pop_sigframe(&ssp);
+       if (unlikely(err))
+               return err;
+
+       fpregs_lock_and_load();
+       wrmsrl(MSR_IA32_PL3_SSP, ssp);
+       fpregs_unlock();
+
+       return 0;
+}
+
 void shstk_free(struct task_struct *tsk)
 {
        struct thread_shstk *shstk = &tsk->thread.shstk;
index cfeec3ee877ebb9c4779a3ce9fe77ab172f685d4..65fe2094da59b88148f09253921009d3b6c6840f 100644 (file)
@@ -40,6 +40,7 @@
 #include <asm/syscall.h>
 #include <asm/sigframe.h>
 #include <asm/signal.h>
+#include <asm/shstk.h>
 
 static inline int is_ia32_compat_frame(struct ksignal *ksig)
 {
index 0e808c72bf7eaa330814e747f28177577e095663..cacf2ede62175d40dabf24d5606fcef9d363cc1e 100644 (file)
@@ -175,6 +175,9 @@ int x64_setup_rt_frame(struct ksignal *ksig, struct pt_regs *regs)
        frame = get_sigframe(ksig, regs, sizeof(struct rt_sigframe), &fp);
        uc_flags = frame_uc_flags(regs);
 
+       if (setup_signal_shadow_stack(ksig))
+               return -EFAULT;
+
        if (!user_access_begin(frame, sizeof(*frame)))
                return -EFAULT;
 
@@ -260,6 +263,9 @@ SYSCALL_DEFINE0(rt_sigreturn)
        if (!restore_sigcontext(regs, &frame->uc.uc_mcontext, uc_flags))
                goto badframe;
 
+       if (restore_signal_shadow_stack())
+               goto badframe;
+
        if (restore_altstack(&frame->uc.uc_stack))
                goto badframe;