Re: [PATCH v2 27/39] x86/cet/shstk: Handle signals for shadow stack

From: Kees Cook
Date: Mon Oct 03 2022 - 16:52:45 EST


On Thu, Sep 29, 2022 at 03:29:24PM -0700, Rick Edgecombe wrote:
> From: Yu-cheng Yu <yu-cheng.yu@xxxxxxxxx>
>
> When a signal is handled normally the context is pushed to the stack
> before handling it. For shadow stacks, since the shadow stack only track's
> 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
> userspace visible 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 we can use the shadow stack data format defined earlier.
> 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.
>
> Signed-off-by: Yu-cheng Yu <yu-cheng.yu@xxxxxxxxx>
> Co-developed-by: Rick Edgecombe <rick.p.edgecombe@xxxxxxxxx>
> Signed-off-by: Rick Edgecombe <rick.p.edgecombe@xxxxxxxxx>
> Cc: Andy Lutomirski <luto@xxxxxxxxxx>
> Cc: Cyrill Gorcunov <gorcunov@xxxxxxxxx>
> Cc: Florian Weimer <fweimer@xxxxxxxxxx>
> Cc: H. Peter Anvin <hpa@xxxxxxxxx>
> Cc: Kees Cook <keescook@xxxxxxxxxxxx>
>
> ---
>
> v2:
> - Switch to new shstk signal format
>
> v1:
> - Use xsave helpers.
> - Expand commit log.
>
> Yu-cheng v27:
> - Eliminate saving shadow stack pointer to signal context.
>
> Yu-cheng v25:
> - Update commit log/comments for the sc_ext struct.
> - Use restorer address already calculated.
> - Change CONFIG_X86_CET to CONFIG_X86_SHADOW_STACK.
> - Change X86_FEATURE_CET to X86_FEATURE_SHSTK.
> - Eliminate writing to MSR_IA32_U_CET for shadow stack.
> - Change wrmsrl() to wrmsrl_safe() and handle error.
>
> arch/x86/ia32/ia32_signal.c | 1 +
> arch/x86/include/asm/cet.h | 5 ++
> arch/x86/kernel/shstk.c | 126 ++++++++++++++++++++++++++++++------
> arch/x86/kernel/signal.c | 10 +++
> 4 files changed, 123 insertions(+), 19 deletions(-)
>
> diff --git a/arch/x86/ia32/ia32_signal.c b/arch/x86/ia32/ia32_signal.c
> index c9c3859322fa..88d71b9de616 100644
> --- a/arch/x86/ia32/ia32_signal.c
> +++ b/arch/x86/ia32/ia32_signal.c
> @@ -34,6 +34,7 @@
> #include <asm/sigframe.h>
> #include <asm/sighandling.h>
> #include <asm/smap.h>
> +#include <asm/cet.h>
>
> static inline void reload_segments(struct sigcontext_32 *sc)
> {
> diff --git a/arch/x86/include/asm/cet.h b/arch/x86/include/asm/cet.h
> index 924de99e0c61..8c6fab9f402a 100644
> --- a/arch/x86/include/asm/cet.h
> +++ b/arch/x86/include/asm/cet.h
> @@ -6,6 +6,7 @@
> #include <linux/types.h>
>
> struct task_struct;
> +struct ksignal;
>
> struct thread_shstk {
> u64 base;
> @@ -22,6 +23,8 @@ int shstk_alloc_thread_stack(struct task_struct *p, unsigned long clone_flags,
> void shstk_free(struct task_struct *p);
> int shstk_disable(void);
> void reset_thread_shstk(void);
> +int setup_signal_shadow_stack(struct ksignal *ksig);
> +int restore_signal_shadow_stack(void);
> #else
> static inline long cet_prctl(struct task_struct *task, int option,
> unsigned long features) { return -EINVAL; }
> @@ -33,6 +36,8 @@ static inline int shstk_alloc_thread_stack(struct task_struct *p,
> static inline void shstk_free(struct task_struct *p) {}
> static inline int shstk_disable(void) { return -EOPNOTSUPP; }
> static inline void reset_thread_shstk(void) {}
> +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_SHADOW_STACK */
>
> #endif /* __ASSEMBLY__ */
> diff --git a/arch/x86/kernel/shstk.c b/arch/x86/kernel/shstk.c
> index 8904aef487bf..04442134aadd 100644
> --- a/arch/x86/kernel/shstk.c
> +++ b/arch/x86/kernel/shstk.c
> @@ -227,41 +227,129 @@ static int get_shstk_data(unsigned long *data, unsigned long __user *addr)
> }
>
> /*
> - * Verify the user shadow stack has a valid token on it, and then set
> - * *new_ssp according to the token.
> + * Create a restore token on shadow stack, and then push the user-mode
> + * function return address.
> */
> -static int shstk_check_rstor_token(unsigned long *new_ssp)
> +static int shstk_setup_rstor_token(unsigned long ret_addr, unsigned long *new_ssp)

Oh, hrm. Prior patch defines shstk_check_rstor_token() and
doesn't call it. This patch removes it. :P Can you please remove
shstk_check_rstor_token() from the prior patch?

> {
> - unsigned long token_addr;
> - unsigned long token;
> + unsigned long ssp, token_addr;
> + int err;
> +
> + if (!ret_addr)
> + return -EINVAL;
> +
> + ssp = get_user_shstk_addr();
> + if (!ssp)
> + return -EINVAL;
> +
> + err = create_rstor_token(ssp, &token_addr);
> + if (err)
> + return err;
> +
> + ssp = token_addr - sizeof(u64);
> + err = write_user_shstk_64((u64 __user *)ssp, (u64)ret_addr);
> +
> + if (!err)
> + *new_ssp = ssp;
> +
> + return err;
> +}
> +
> +static int shstk_push_sigframe(unsigned long *ssp)
> +{
> + unsigned long target_ssp = *ssp;
> +
> + /* Token must be aligned */
> + if (!IS_ALIGNED(*ssp, 8))
> + return -EINVAL;
>
> - token_addr = get_user_shstk_addr();
> - if (!token_addr)
> + if (!IS_ALIGNED(target_ssp, 8))
> return -EINVAL;
>
> - if (get_user(token, (unsigned long __user *)token_addr))
> + *ssp -= SS_FRAME_SIZE;
> + if (put_shstk_data((void *__user)*ssp, target_ssp))
> return -EFAULT;
>
> - /* Is mode flag correct? */
> - if (!(token & BIT(0)))
> + 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;

Why doesn't this always fail, given BIT(0) being set? I don't see it
getting cleared until the end of this function.

>
> - /* Is busy flag set? */
> - if (token & BIT(1))
> + /* SSP in userspace? */
> + if (unlikely(token_addr >= TASK_SIZE_MAX))
> return -EINVAL;

BIT(63) already got cleared by here (in get_shstk_data(), but yes,
this is still a reasonable check.

>
> - /* Mask out flags */
> - token &= ~3UL;
> + *ssp = token_addr;
> +
> + return 0;
> +}

--
Kees Cook