]> git.ipfire.org Git - thirdparty/kernel/stable.git/commitdiff
riscv/signal: save and restore the shadow stack on a signal
authorDeepak Gupta <debug@rivosinc.com>
Mon, 26 Jan 2026 04:09:55 +0000 (21:09 -0700)
committerPaul Walmsley <pjw@kernel.org>
Thu, 29 Jan 2026 09:38:40 +0000 (02:38 -0700)
Save the shadow stack pointer in the sigcontext structure when
delivering a signal.  Restore the shadow stack pointer from sigcontext
on sigreturn.

As part of the save operation, the kernel uses the 'ssamoswap'
instruction to save a snapshot of the current shadow stack on the
shadow stack itself (this can be called a "save token"). During
restore on sigreturn, the kernel retrieves the save token from the top
of the shadow stack and validates it. This ensures that user mode
can't arbitrarily pivot to any shadow stack address without having a
token and thus provides a strong security assurance during the window
between signal delivery and sigreturn.

Use an ABI-compatible way of saving/restoring the shadow stack pointer
into the signal stack. This follows the vector extension, where extra
registers are placed in a form of extension header + extension body in
the stack.  The extension header indicates the size of the extra
architectural states plus the size of header itself, and a magic
identifier for the extension. Then, the extension body contains the
new architectural states in the form defined by uapi.

Signed-off-by: Andy Chiu <andy.chiu@sifive.com>
Signed-off-by: Deepak Gupta <debug@rivosinc.com>
Tested-by: Andreas Korb <andreas.korb@aisec.fraunhofer.de>
Tested-by: Valentin Haudiquet <valentin.haudiquet@canonical.com>
Link: https://patch.msgid.link/20251112-v5_user_cfi_series-v23-17-b55691eacf4f@rivosinc.com
[pjw@kernel.org: cleaned patch description, code comments; resolved checkpatch warning]
Signed-off-by: Paul Walmsley <pjw@kernel.org>
arch/riscv/include/asm/usercfi.h
arch/riscv/include/uapi/asm/ptrace.h
arch/riscv/include/uapi/asm/sigcontext.h
arch/riscv/kernel/signal.c
arch/riscv/kernel/usercfi.c

index 4501d741a609d5f7ca18ec64eb7a82c64d76e5b3..ec4b8a53eb748572836c355647d58c91e094864b 100644 (file)
@@ -8,6 +8,7 @@
 #ifndef __ASSEMBLER__
 #include <linux/types.h>
 #include <linux/prctl.h>
+#include <linux/errno.h>
 
 struct task_struct;
 struct kernel_clone_args;
@@ -34,6 +35,9 @@ bool is_shstk_locked(struct task_struct *task);
 bool is_shstk_allocated(struct task_struct *task);
 void set_shstk_lock(struct task_struct *task);
 void set_shstk_status(struct task_struct *task, bool enable);
+unsigned long get_active_shstk(struct task_struct *task);
+int restore_user_shstk(struct task_struct *tsk, unsigned long shstk_ptr);
+int save_user_shstk(struct task_struct *tsk, unsigned long *saved_shstk_ptr);
 bool is_indir_lp_enabled(struct task_struct *task);
 bool is_indir_lp_locked(struct task_struct *task);
 void set_indir_lp_status(struct task_struct *task, bool enable);
@@ -71,6 +75,12 @@ void set_indir_lp_lock(struct task_struct *task);
 
 #define set_indir_lp_lock(task) do {} while (0)
 
+#define restore_user_shstk(tsk, shstk_ptr) -EINVAL
+
+#define save_user_shstk(tsk, saved_shstk_ptr) -EINVAL
+
+#define get_active_shstk(task) 0UL
+
 #endif /* CONFIG_RISCV_USER_CFI */
 
 #endif /* __ASSEMBLER__ */
index beff8df80ac9c36914bf75bf23de6ffff1b803f1..261bfe70f60ae98d33898eb63329e24642539919 100644 (file)
@@ -127,6 +127,10 @@ struct __riscv_v_regset_state {
  */
 #define RISCV_MAX_VLENB (8192)
 
+struct __sc_riscv_cfi_state {
+       unsigned long ss_ptr;   /* shadow stack pointer */
+};
+
 #endif /* __ASSEMBLER__ */
 
 #endif /* _UAPI_ASM_RISCV_PTRACE_H */
index 748dffc9ae194c5309b903567851b0e7450525b1..d22d0815d605fe680cb752740671a9786fbe01b1 100644 (file)
@@ -10,6 +10,7 @@
 
 /* The Magic number for signal context frame header. */
 #define RISCV_V_MAGIC  0x53465457
+#define RISCV_ZICFISS_MAGIC            0x9487
 #define END_MAGIC      0x0
 
 /* The size of END signal context header. */
index dbb067e345f05b8ad9664a5721da79df8f96acdb..59784dc117e454edd8215db1e70e1851e4588606 100644 (file)
 #include <asm/vector.h>
 #include <asm/csr.h>
 #include <asm/cacheflush.h>
+#include <asm/usercfi.h>
 
 unsigned long signal_minsigstksz __ro_after_init;
 
 extern u32 __user_rt_sigreturn[2];
 static size_t riscv_v_sc_size __ro_after_init;
+static size_t riscv_zicfiss_sc_size __ro_after_init;
 
 #define DEBUG_SIG 0
 
@@ -140,6 +142,62 @@ static long __restore_v_state(struct pt_regs *regs, void __user *sc_vec)
        return copy_from_user(current->thread.vstate.datap, datap, riscv_v_vsize);
 }
 
+static long save_cfiss_state(struct pt_regs *regs, void __user *sc_cfi)
+{
+       struct __sc_riscv_cfi_state __user *state = sc_cfi;
+       unsigned long ss_ptr = 0;
+       long err = 0;
+
+       if (!is_shstk_enabled(current))
+               return 0;
+
+       /*
+        * Save a pointer to the shadow stack itself on shadow stack as a form of token.
+        * A token on the shadow stack gives the following properties:
+        * - Safe save and restore for shadow stack switching. Any save of a shadow stack
+        *   must have saved a token on the shadow stack. Similarly any restore of shadow
+        *   stack must check the token before restore. Since writing to the shadow stack with
+        *   address of the shadow stack itself is not easily allowed, a restore without a save
+        *   is quite difficult for an attacker to perform.
+        * - A natural break. A token in shadow stack provides a natural break in shadow stack
+        *   So a single linear range can be bucketed into different shadow stack segments. Any
+        *   sspopchk will detect the condition and fault to kernel as a sw check exception.
+        */
+       err |= save_user_shstk(current, &ss_ptr);
+       err |= __put_user(ss_ptr, &state->ss_ptr);
+       if (unlikely(err))
+               return -EFAULT;
+
+       return riscv_zicfiss_sc_size;
+}
+
+static long __restore_cfiss_state(struct pt_regs *regs, void __user *sc_cfi)
+{
+       struct __sc_riscv_cfi_state __user *state = sc_cfi;
+       unsigned long ss_ptr = 0;
+       long err;
+
+       /*
+        * Restore shadow stack as a form of token stored on the shadow stack itself as a safe
+        * way to restore.
+        * A token on the shadow stack gives the following properties:
+        * - Safe save and restore for shadow stack switching. Any save of shadow stack
+        *   must have saved a token on shadow stack. Similarly any restore of shadow
+        *   stack must check the token before restore. Since writing to a shadow stack with
+        *   the address of shadow stack itself is not easily allowed, a restore without a save
+        *   is quite difficult for an attacker to perform.
+        * - A natural break. A token in the shadow stack provides a natural break in shadow stack
+        *   So a single linear range can be bucketed into different shadow stack segments.
+        *   sspopchk will detect the condition and fault to kernel as a sw check exception.
+        */
+       err = __copy_from_user(&ss_ptr, &state->ss_ptr, sizeof(unsigned long));
+
+       if (unlikely(err))
+               return err;
+
+       return restore_user_shstk(current, ss_ptr);
+}
+
 struct arch_ext_priv {
        __u32 magic;
        long (*save)(struct pt_regs *regs, void __user *sc_vec);
@@ -150,6 +208,10 @@ static struct arch_ext_priv arch_ext_list[] = {
                .magic = RISCV_V_MAGIC,
                .save = &save_v_state,
        },
+       {
+               .magic = RISCV_ZICFISS_MAGIC,
+               .save = &save_cfiss_state,
+       },
 };
 
 static const size_t nr_arch_exts = ARRAY_SIZE(arch_ext_list);
@@ -202,6 +264,12 @@ static long restore_sigcontext(struct pt_regs *regs,
 
                        err = __restore_v_state(regs, sc_ext_ptr);
                        break;
+               case RISCV_ZICFISS_MAGIC:
+                       if (!is_shstk_enabled(current) || size != riscv_zicfiss_sc_size)
+                               return -EINVAL;
+
+                       err = __restore_cfiss_state(regs, sc_ext_ptr);
+                       break;
                default:
                        return -EINVAL;
                }
@@ -223,6 +291,16 @@ static size_t get_rt_frame_size(bool cal_all)
                        total_context_size += riscv_v_sc_size;
        }
 
+       if (is_shstk_enabled(current))
+               total_context_size += riscv_zicfiss_sc_size;
+
+       /*
+        * Preserved a __riscv_ctx_hdr for END signal context header if an
+        * extension uses __riscv_extra_ext_header
+        */
+       if (total_context_size)
+               total_context_size += sizeof(struct __riscv_ctx_hdr);
+
        frame_size += total_context_size;
 
        frame_size = round_up(frame_size, 16);
@@ -359,6 +437,11 @@ static int setup_rt_frame(struct ksignal *ksig, sigset_t *set,
 #ifdef CONFIG_MMU
        regs->ra = (unsigned long)VDSO_SYMBOL(
                current->mm->context.vdso, rt_sigreturn);
+
+       /* if bcfi is enabled x1 (ra) and x5 (t0) must match. not sure if we need this? */
+       if (is_shstk_enabled(current))
+               regs->t0 = regs->ra;
+
 #else
        /*
         * For the nommu case we don't have a VDSO.  Instead we push two
@@ -487,6 +570,9 @@ void __init init_rt_signal_env(void)
 {
        riscv_v_sc_size = sizeof(struct __riscv_ctx_hdr) +
                          sizeof(struct __sc_riscv_v_state) + riscv_v_vsize;
+
+       riscv_zicfiss_sc_size = sizeof(struct __riscv_ctx_hdr) +
+                         sizeof(struct __sc_riscv_cfi_state);
        /*
         * Determine the stack space required for guaranteed signal delivery.
         * The signal_minsigstksz will be populated into the AT_MINSIGSTKSZ entry
index db3ba51af5a5331b5c2d04a286af02c91be5a5d3..7cec00ca1df485c1e3a36eba69afb88d0541a82e 100644 (file)
@@ -52,6 +52,11 @@ void set_active_shstk(struct task_struct *task, unsigned long shstk_addr)
        task->thread_info.user_cfi_state.user_shdw_stk = shstk_addr;
 }
 
+unsigned long get_active_shstk(struct task_struct *task)
+{
+       return task->thread_info.user_cfi_state.user_shdw_stk;
+}
+
 void set_shstk_status(struct task_struct *task, bool enable)
 {
        if (!cpu_supports_shadow_stack())
@@ -168,6 +173,58 @@ static int create_rstor_token(unsigned long ssp, unsigned long *token_addr)
        return 0;
 }
 
+/*
+ * Save user shadow stack pointer on the shadow stack itself and return a pointer to saved location.
+ * Returns -EFAULT if unsuccessful.
+ */
+int save_user_shstk(struct task_struct *tsk, unsigned long *saved_shstk_ptr)
+{
+       unsigned long ss_ptr = 0;
+       unsigned long token_loc = 0;
+       int ret = 0;
+
+       if (!saved_shstk_ptr)
+               return -EINVAL;
+
+       ss_ptr = get_active_shstk(tsk);
+       ret = create_rstor_token(ss_ptr, &token_loc);
+
+       if (!ret) {
+               *saved_shstk_ptr = token_loc;
+               set_active_shstk(tsk, token_loc);
+       }
+
+       return ret;
+}
+
+/*
+ * Restores the user shadow stack pointer from the token on the shadow stack for task 'tsk'.
+ * Returns -EFAULT if unsuccessful.
+ */
+int restore_user_shstk(struct task_struct *tsk, unsigned long shstk_ptr)
+{
+       unsigned long token = 0;
+
+       token = amo_user_shstk((unsigned long __user *)shstk_ptr, 0);
+
+       if (token == -1)
+               return -EFAULT;
+
+       /* invalid token, return EINVAL */
+       if ((token - shstk_ptr) != SHSTK_ENTRY_SIZE) {
+               pr_info_ratelimited("%s[%d]: bad restore token in %s: pc=%p sp=%p, token=%p, shstk_ptr=%p\n",
+                                   tsk->comm, task_pid_nr(tsk), __func__,
+                                   (void *)(task_pt_regs(tsk)->epc),
+                                   (void *)(task_pt_regs(tsk)->sp),
+                                   (void *)token, (void *)shstk_ptr);
+               return -EINVAL;
+       }
+
+       /* all checks passed, set active shstk and return success */
+       set_active_shstk(tsk, token);
+       return 0;
+}
+
 static unsigned long allocate_shadow_stack(unsigned long addr, unsigned long size,
                                           unsigned long token_offset, bool set_tok)
 {