]> git.ipfire.org Git - thirdparty/kernel/stable.git/commitdiff
riscv/ptrace: expose riscv CFI status and state via ptrace and in core files
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)
Expose a new register type NT_RISCV_USER_CFI for risc-v CFI status and
state. Intentionally, both landing pad and shadow stack status and
state are rolled into the CFI state. Creating two different
NT_RISCV_USER_XXX would not be useful and would waste a note
type. Enabling, disabling and locking the CFI feature is not allowed
via ptrace set interface. However, setting 'elp' state or setting
shadow stack pointer are allowed via the ptrace set interface.  It is
expected that 'gdb' might need to fixup 'elp' state or 'shadow stack'
pointer.

Signed-off-by: Deepak Gupta <debug@rivosinc.com>
Tested-by: Andreas Korb <andreas.korb@aisec.fraunhofer.de> # QEMU, custom CVA6
Tested-by: Valentin Haudiquet <valentin.haudiquet@canonical.com>
Link: https://patch.msgid.link/20251112-v5_user_cfi_series-v23-19-b55691eacf4f@rivosinc.com
[pjw@kernel.org: updated to apply; cleaned patch description and comments; addressed checkpatch issues]
Signed-off-by: Paul Walmsley <pjw@kernel.org>
arch/riscv/include/uapi/asm/ptrace.h
arch/riscv/kernel/ptrace.c
include/uapi/linux/elf.h

index 261bfe70f60ae98d33898eb63329e24642539919..18988a5f1a630b833439acbc7d2be2a2136f10e6 100644 (file)
@@ -131,6 +131,36 @@ struct __sc_riscv_cfi_state {
        unsigned long ss_ptr;   /* shadow stack pointer */
 };
 
+#define PTRACE_CFI_LP_EN_BIT   0
+#define PTRACE_CFI_LP_LOCK_BIT 1
+#define PTRACE_CFI_ELP_BIT     2
+#define PTRACE_CFI_SS_EN_BIT   3
+#define PTRACE_CFI_SS_LOCK_BIT 4
+#define PTRACE_CFI_SS_PTR_BIT  5
+
+#define PTRACE_CFI_LP_EN_STATE         BIT(PTRACE_CFI_LP_EN_BIT)
+#define PTRACE_CFI_LP_LOCK_STATE       BIT(PTRACE_CFI_LP_LOCK_BIT)
+#define PTRACE_CFI_ELP_STATE           BIT(PTRACE_CFI_ELP_BIT)
+#define PTRACE_CFI_SS_EN_STATE         BIT(PTRACE_CFI_SS_EN_BIT)
+#define PTRACE_CFI_SS_LOCK_STATE       BIT(PTRACE_CFI_SS_LOCK_BIT)
+#define PTRACE_CFI_SS_PTR_STATE                BIT(PTRACE_CFI_SS_PTR_BIT)
+
+#define PRACE_CFI_STATE_INVALID_MASK   ~(PTRACE_CFI_LP_EN_STATE | \
+                                         PTRACE_CFI_LP_LOCK_STATE | \
+                                         PTRACE_CFI_ELP_STATE | \
+                                         PTRACE_CFI_SS_EN_STATE | \
+                                         PTRACE_CFI_SS_LOCK_STATE | \
+                                         PTRACE_CFI_SS_PTR_STATE)
+
+struct __cfi_status {
+       __u64 cfi_state;
+};
+
+struct user_cfi_state {
+       struct __cfi_status     cfi_status;
+       __u64 shstk_ptr;
+};
+
 #endif /* __ASSEMBLER__ */
 
 #endif /* _UAPI_ASM_RISCV_PTRACE_H */
index e6272d74572f97fbe78bf60cba9daeab2188f174..57e257d459e80d753be0b519423cff4d3cc9cabf 100644 (file)
@@ -19,6 +19,7 @@
 #include <linux/regset.h>
 #include <linux/sched.h>
 #include <linux/sched/task_stack.h>
+#include <asm/usercfi.h>
 
 enum riscv_regset {
        REGSET_X,
@@ -31,6 +32,9 @@ enum riscv_regset {
 #ifdef CONFIG_RISCV_ISA_SUPM
        REGSET_TAGGED_ADDR_CTRL,
 #endif
+#ifdef CONFIG_RISCV_USER_CFI
+       REGSET_CFI,
+#endif
 };
 
 static int riscv_gpr_get(struct task_struct *target,
@@ -195,6 +199,87 @@ static int tagged_addr_ctrl_set(struct task_struct *target,
 }
 #endif
 
+#ifdef CONFIG_RISCV_USER_CFI
+static int riscv_cfi_get(struct task_struct *target,
+                        const struct user_regset *regset,
+                        struct membuf to)
+{
+       struct user_cfi_state user_cfi;
+       struct pt_regs *regs;
+
+       memset(&user_cfi, 0, sizeof(user_cfi));
+       regs = task_pt_regs(target);
+
+       if (is_indir_lp_enabled(target)) {
+               user_cfi.cfi_status.cfi_state |= PTRACE_CFI_LP_EN_STATE;
+               user_cfi.cfi_status.cfi_state |= is_indir_lp_locked(target) ?
+                                                PTRACE_CFI_LP_LOCK_STATE : 0;
+               user_cfi.cfi_status.cfi_state |= (regs->status & SR_ELP) ?
+                                               PTRACE_CFI_ELP_STATE : 0;
+       }
+
+       if (is_shstk_enabled(target)) {
+               user_cfi.cfi_status.cfi_state |= (PTRACE_CFI_SS_EN_STATE |
+                                                 PTRACE_CFI_SS_PTR_STATE);
+               user_cfi.cfi_status.cfi_state |= is_shstk_locked(target) ?
+                                                PTRACE_CFI_SS_LOCK_STATE : 0;
+               user_cfi.shstk_ptr = get_active_shstk(target);
+       }
+
+       return membuf_write(&to, &user_cfi, sizeof(user_cfi));
+}
+
+/*
+ * Does it make sense to allow enable / disable of cfi via ptrace?
+ * We don't allow enable / disable / locking control via ptrace for now.
+ * Setting the shadow stack pointer is allowed. GDB might use it to unwind or
+ * some other fixup. Similarly gdb might want to suppress elp and may want
+ * to reset elp state.
+ */
+static int riscv_cfi_set(struct task_struct *target,
+                        const struct user_regset *regset,
+                        unsigned int pos, unsigned int count,
+                        const void *kbuf, const void __user *ubuf)
+{
+       int ret;
+       struct user_cfi_state user_cfi;
+       struct pt_regs *regs;
+
+       regs = task_pt_regs(target);
+
+       ret = user_regset_copyin(&pos, &count, &kbuf, &ubuf, &user_cfi, 0, -1);
+       if (ret)
+               return ret;
+
+       /*
+        * Not allowing enabling or locking shadow stack or landing pad
+        * There is no disabling of shadow stack or landing pad via ptrace
+        * rsvd field should be set to zero so that if those fields are needed in future
+        */
+       if ((user_cfi.cfi_status.cfi_state &
+            (PTRACE_CFI_LP_EN_STATE | PTRACE_CFI_LP_LOCK_STATE |
+             PTRACE_CFI_SS_EN_STATE | PTRACE_CFI_SS_LOCK_STATE)) ||
+            (user_cfi.cfi_status.cfi_state & PRACE_CFI_STATE_INVALID_MASK))
+               return -EINVAL;
+
+       /* If lpad is enabled on target and ptrace requests to set / clear elp, do that */
+       if (is_indir_lp_enabled(target)) {
+               if (user_cfi.cfi_status.cfi_state &
+                   PTRACE_CFI_ELP_STATE) /* set elp state */
+                       regs->status |= SR_ELP;
+               else
+                       regs->status &= ~SR_ELP; /* clear elp state */
+       }
+
+       /* If shadow stack enabled on target, set new shadow stack pointer */
+       if (is_shstk_enabled(target) &&
+           (user_cfi.cfi_status.cfi_state & PTRACE_CFI_SS_PTR_STATE))
+               set_active_shstk(target, user_cfi.shstk_ptr);
+
+       return 0;
+}
+#endif
+
 static struct user_regset riscv_user_regset[] __ro_after_init = {
        [REGSET_X] = {
                USER_REGSET_NOTE_TYPE(PRSTATUS),
@@ -234,6 +319,16 @@ static struct user_regset riscv_user_regset[] __ro_after_init = {
                .set = tagged_addr_ctrl_set,
        },
 #endif
+#ifdef CONFIG_RISCV_USER_CFI
+       [REGSET_CFI] = {
+               .core_note_type = NT_RISCV_USER_CFI,
+               .align = sizeof(__u64),
+               .n = sizeof(struct user_cfi_state) / sizeof(__u64),
+               .size = sizeof(__u64),
+               .regset_get = riscv_cfi_get,
+               .set = riscv_cfi_set,
+       },
+#endif
 };
 
 static const struct user_regset_view riscv_user_native_view = {
index 819ded2d39de2bfcfca3c25a52c8b8cd51a01c12..ee30dcd80901fc1fca274339466caf9dcf9f3b85 100644 (file)
@@ -545,6 +545,8 @@ typedef struct elf64_shdr {
 #define NT_RISCV_VECTOR        0x901           /* RISC-V vector registers */
 #define NN_RISCV_TAGGED_ADDR_CTRL "LINUX"
 #define NT_RISCV_TAGGED_ADDR_CTRL 0x902        /* RISC-V tagged address control (prctl()) */
+#define NN_RISCV_USER_CFI      "LINUX"
+#define NT_RISCV_USER_CFI      0x903           /* RISC-V shadow stack state */
 #define NN_LOONGARCH_CPUCFG    "LINUX"
 #define NT_LOONGARCH_CPUCFG    0xa00   /* LoongArch CPU config registers */
 #define NN_LOONGARCH_CSR       "LINUX"