Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux

riscv/ptrace: expose riscv CFI status and state via ptrace and in core files

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>

authored by

Deepak Gupta and committed by
Paul Walmsley
2af7c9cf 9d0e75e2

+127
+30
arch/riscv/include/uapi/asm/ptrace.h
··· 131 131 unsigned long ss_ptr; /* shadow stack pointer */ 132 132 }; 133 133 134 + #define PTRACE_CFI_LP_EN_BIT 0 135 + #define PTRACE_CFI_LP_LOCK_BIT 1 136 + #define PTRACE_CFI_ELP_BIT 2 137 + #define PTRACE_CFI_SS_EN_BIT 3 138 + #define PTRACE_CFI_SS_LOCK_BIT 4 139 + #define PTRACE_CFI_SS_PTR_BIT 5 140 + 141 + #define PTRACE_CFI_LP_EN_STATE BIT(PTRACE_CFI_LP_EN_BIT) 142 + #define PTRACE_CFI_LP_LOCK_STATE BIT(PTRACE_CFI_LP_LOCK_BIT) 143 + #define PTRACE_CFI_ELP_STATE BIT(PTRACE_CFI_ELP_BIT) 144 + #define PTRACE_CFI_SS_EN_STATE BIT(PTRACE_CFI_SS_EN_BIT) 145 + #define PTRACE_CFI_SS_LOCK_STATE BIT(PTRACE_CFI_SS_LOCK_BIT) 146 + #define PTRACE_CFI_SS_PTR_STATE BIT(PTRACE_CFI_SS_PTR_BIT) 147 + 148 + #define PRACE_CFI_STATE_INVALID_MASK ~(PTRACE_CFI_LP_EN_STATE | \ 149 + PTRACE_CFI_LP_LOCK_STATE | \ 150 + PTRACE_CFI_ELP_STATE | \ 151 + PTRACE_CFI_SS_EN_STATE | \ 152 + PTRACE_CFI_SS_LOCK_STATE | \ 153 + PTRACE_CFI_SS_PTR_STATE) 154 + 155 + struct __cfi_status { 156 + __u64 cfi_state; 157 + }; 158 + 159 + struct user_cfi_state { 160 + struct __cfi_status cfi_status; 161 + __u64 shstk_ptr; 162 + }; 163 + 134 164 #endif /* __ASSEMBLER__ */ 135 165 136 166 #endif /* _UAPI_ASM_RISCV_PTRACE_H */
+95
arch/riscv/kernel/ptrace.c
··· 19 19 #include <linux/regset.h> 20 20 #include <linux/sched.h> 21 21 #include <linux/sched/task_stack.h> 22 + #include <asm/usercfi.h> 22 23 23 24 enum riscv_regset { 24 25 REGSET_X, ··· 31 30 #endif 32 31 #ifdef CONFIG_RISCV_ISA_SUPM 33 32 REGSET_TAGGED_ADDR_CTRL, 33 + #endif 34 + #ifdef CONFIG_RISCV_USER_CFI 35 + REGSET_CFI, 34 36 #endif 35 37 }; 36 38 ··· 199 195 } 200 196 #endif 201 197 198 + #ifdef CONFIG_RISCV_USER_CFI 199 + static int riscv_cfi_get(struct task_struct *target, 200 + const struct user_regset *regset, 201 + struct membuf to) 202 + { 203 + struct user_cfi_state user_cfi; 204 + struct pt_regs *regs; 205 + 206 + memset(&user_cfi, 0, sizeof(user_cfi)); 207 + regs = task_pt_regs(target); 208 + 209 + if (is_indir_lp_enabled(target)) { 210 + user_cfi.cfi_status.cfi_state |= PTRACE_CFI_LP_EN_STATE; 211 + user_cfi.cfi_status.cfi_state |= is_indir_lp_locked(target) ? 212 + PTRACE_CFI_LP_LOCK_STATE : 0; 213 + user_cfi.cfi_status.cfi_state |= (regs->status & SR_ELP) ? 214 + PTRACE_CFI_ELP_STATE : 0; 215 + } 216 + 217 + if (is_shstk_enabled(target)) { 218 + user_cfi.cfi_status.cfi_state |= (PTRACE_CFI_SS_EN_STATE | 219 + PTRACE_CFI_SS_PTR_STATE); 220 + user_cfi.cfi_status.cfi_state |= is_shstk_locked(target) ? 221 + PTRACE_CFI_SS_LOCK_STATE : 0; 222 + user_cfi.shstk_ptr = get_active_shstk(target); 223 + } 224 + 225 + return membuf_write(&to, &user_cfi, sizeof(user_cfi)); 226 + } 227 + 228 + /* 229 + * Does it make sense to allow enable / disable of cfi via ptrace? 230 + * We don't allow enable / disable / locking control via ptrace for now. 231 + * Setting the shadow stack pointer is allowed. GDB might use it to unwind or 232 + * some other fixup. Similarly gdb might want to suppress elp and may want 233 + * to reset elp state. 234 + */ 235 + static int riscv_cfi_set(struct task_struct *target, 236 + const struct user_regset *regset, 237 + unsigned int pos, unsigned int count, 238 + const void *kbuf, const void __user *ubuf) 239 + { 240 + int ret; 241 + struct user_cfi_state user_cfi; 242 + struct pt_regs *regs; 243 + 244 + regs = task_pt_regs(target); 245 + 246 + ret = user_regset_copyin(&pos, &count, &kbuf, &ubuf, &user_cfi, 0, -1); 247 + if (ret) 248 + return ret; 249 + 250 + /* 251 + * Not allowing enabling or locking shadow stack or landing pad 252 + * There is no disabling of shadow stack or landing pad via ptrace 253 + * rsvd field should be set to zero so that if those fields are needed in future 254 + */ 255 + if ((user_cfi.cfi_status.cfi_state & 256 + (PTRACE_CFI_LP_EN_STATE | PTRACE_CFI_LP_LOCK_STATE | 257 + PTRACE_CFI_SS_EN_STATE | PTRACE_CFI_SS_LOCK_STATE)) || 258 + (user_cfi.cfi_status.cfi_state & PRACE_CFI_STATE_INVALID_MASK)) 259 + return -EINVAL; 260 + 261 + /* If lpad is enabled on target and ptrace requests to set / clear elp, do that */ 262 + if (is_indir_lp_enabled(target)) { 263 + if (user_cfi.cfi_status.cfi_state & 264 + PTRACE_CFI_ELP_STATE) /* set elp state */ 265 + regs->status |= SR_ELP; 266 + else 267 + regs->status &= ~SR_ELP; /* clear elp state */ 268 + } 269 + 270 + /* If shadow stack enabled on target, set new shadow stack pointer */ 271 + if (is_shstk_enabled(target) && 272 + (user_cfi.cfi_status.cfi_state & PTRACE_CFI_SS_PTR_STATE)) 273 + set_active_shstk(target, user_cfi.shstk_ptr); 274 + 275 + return 0; 276 + } 277 + #endif 278 + 202 279 static struct user_regset riscv_user_regset[] __ro_after_init = { 203 280 [REGSET_X] = { 204 281 USER_REGSET_NOTE_TYPE(PRSTATUS), ··· 317 232 .align = sizeof(long), 318 233 .regset_get = tagged_addr_ctrl_get, 319 234 .set = tagged_addr_ctrl_set, 235 + }, 236 + #endif 237 + #ifdef CONFIG_RISCV_USER_CFI 238 + [REGSET_CFI] = { 239 + .core_note_type = NT_RISCV_USER_CFI, 240 + .align = sizeof(__u64), 241 + .n = sizeof(struct user_cfi_state) / sizeof(__u64), 242 + .size = sizeof(__u64), 243 + .regset_get = riscv_cfi_get, 244 + .set = riscv_cfi_set, 320 245 }, 321 246 #endif 322 247 };
+2
include/uapi/linux/elf.h
··· 545 545 #define NT_RISCV_VECTOR 0x901 /* RISC-V vector registers */ 546 546 #define NN_RISCV_TAGGED_ADDR_CTRL "LINUX" 547 547 #define NT_RISCV_TAGGED_ADDR_CTRL 0x902 /* RISC-V tagged address control (prctl()) */ 548 + #define NN_RISCV_USER_CFI "LINUX" 549 + #define NT_RISCV_USER_CFI 0x903 /* RISC-V shadow stack state */ 548 550 #define NN_LOONGARCH_CPUCFG "LINUX" 549 551 #define NT_LOONGARCH_CPUCFG 0xa00 /* LoongArch CPU config registers */ 550 552 #define NN_LOONGARCH_CSR "LINUX"