[PATCH v26 21/28] riscv: kernel command line option to opt out of user cfi

Deepak Gupta via B4 Relay
2025-12-11T17:21:08Z

This commit adds a kernel command line option to disable part or all of
user cfi. User backward cfi and forward cfi can be controlled
independently. Kernel command line parameter "riscv_nousercfi" can take
the following values:
- "all" : Disable forward and backward cfi both.
- "bcfi" : Disable backward cfi.
- "fcfi" : Disable forward cfi

Tested-by: Valentin Haudiquet <valentin.haudiquet@canonical.com>
Signed-off-by: Deepak Gupta <debug@rivosinc.com>
---
Documentation/admin-guide/kernel-parameters.txt | 8 ++++
arch/riscv/include/asm/usercfi.h | 9 ++++
arch/riscv/kernel/cpufeature.c | 7 ++-
arch/riscv/kernel/usercfi.c | 59 ++++++++++++++++++++-----
4 files changed, 70 insertions(+), 13 deletions(-)

diff --git a/Documentation/admin-guide/kernel-parameters.txt b/Documentation/admin-guide/kernel-parameters.txt
index 6c42061ca20e..a72bf9fc6f06 100644
--- a/Documentation/admin-guide/kernel-parameters.txt
+++ b/Documentation/admin-guide/kernel-parameters.txt
@@ -6453,6 +6453,14 @@
replacement properties are not found. See the Kconfig
entry for RISCV_ISA_FALLBACK.

+ riscv_nousercfi=
+ all Disable user cfi ABI to userspace even if cpu extensions
+ are available.
+ bcfi Disable user backward cfi ABI to userspace even if
+ shadow stack extension is available.
+ fcfi Disable user forward cfi ABI to userspace even if landing
+ pad extension is available.
+
ro [KNL] Mount root device read-only on boot

rodata= [KNL,EARLY]
diff --git a/arch/riscv/include/asm/usercfi.h b/arch/riscv/include/asm/usercfi.h
index ec4b8a53eb74..f7fa9d602aae 100644
--- a/arch/riscv/include/asm/usercfi.h
+++ b/arch/riscv/include/asm/usercfi.h
@@ -5,6 +5,10 @@
#ifndef _ASM_RISCV_USERCFI_H
#define _ASM_RISCV_USERCFI_H

+#define CMDLINE_DISABLE_RISCV_USERCFI_FCFI 1
+#define CMDLINE_DISABLE_RISCV_USERCFI_BCFI 2
+#define CMDLINE_DISABLE_RISCV_USERCFI 3
+
#ifndef __ASSEMBLER__
#include <linux/types.h>
#include <linux/prctl.h>
@@ -12,8 +16,10 @@

struct task_struct;
struct kernel_clone_args;
+extern unsigned long riscv_nousercfi;

#ifdef CONFIG_RISCV_USER_CFI
+
struct cfi_state {
unsigned long ubcfi_en : 1; /* Enable for backward cfi. */
unsigned long ubcfi_locked : 1;
@@ -83,6 +89,9 @@ void set_indir_lp_lock(struct task_struct *task);

#endif /* CONFIG_RISCV_USER_CFI */

+bool is_user_shstk_enabled(void);
+bool is_user_lpad_enabled(void);
+
#endif /* __ASSEMBLER__ */

#endif /* _ASM_RISCV_USERCFI_H */
diff --git a/arch/riscv/kernel/cpufeature.c b/arch/riscv/kernel/cpufeature.c
index 17b9e77bafc3..32ad463545fa 100644
--- a/arch/riscv/kernel/cpufeature.c
+++ b/arch/riscv/kernel/cpufeature.c
@@ -28,6 +28,7 @@
#include <asm/vector.h>
#include <asm/vendor_extensions.h>
#include <asm/vendor_extensions/thead.h>
+#include <asm/usercfi.h>

#define NUM_ALPHA_EXTS ('z' - 'a' + 1)

@@ -277,7 +278,8 @@ static int riscv_ext_svadu_validate(const struct riscv_isa_ext_data *data,
static int riscv_cfilp_validate(const struct riscv_isa_ext_data *data,
const unsigned long *isa_bitmap)
{
- if (!IS_ENABLED(CONFIG_RISCV_USER_CFI))
+ if (!IS_ENABLED(CONFIG_RISCV_USER_CFI) ||
+ (riscv_nousercfi & CMDLINE_DISABLE_RISCV_USERCFI_FCFI))
return -EINVAL;

return 0;
@@ -286,7 +288,8 @@ static int riscv_cfilp_validate(const struct riscv_isa_ext_data *data,
static int riscv_cfiss_validate(const struct riscv_isa_ext_data *data,
const unsigned long *isa_bitmap)
{
- if (!IS_ENABLED(CONFIG_RISCV_USER_CFI))
+ if (!IS_ENABLED(CONFIG_RISCV_USER_CFI) ||
+ (riscv_nousercfi & CMDLINE_DISABLE_RISCV_USERCFI_BCFI))
return -EINVAL;

return 0;
diff --git a/arch/riscv/kernel/usercfi.c b/arch/riscv/kernel/usercfi.c
index 7f01befabd11..27b36034ea85 100644
--- a/arch/riscv/kernel/usercfi.c
+++ b/arch/riscv/kernel/usercfi.c
@@ -17,6 +17,8 @@
#include <asm/csr.h>
#include <asm/usercfi.h>

+unsigned long riscv_nousercfi;
+
#define SHSTK_ENTRY_SIZE sizeof(void *)

bool is_shstk_enabled(struct task_struct *task)
@@ -59,7 +61,7 @@ unsigned long get_active_shstk(struct task_struct *task)

void set_shstk_status(struct task_struct *task, bool enable)
{
- if (!cpu_supports_shadow_stack())
+ if (!is_user_shstk_enabled())
return;

task->thread_info.user_cfi_state.ubcfi_en = enable ? 1 : 0;
@@ -89,7 +91,7 @@ bool is_indir_lp_locked(struct task_struct *task)

void set_indir_lp_status(struct task_struct *task, bool enable)
{
- if (!cpu_supports_indirect_br_lp_instr())
+ if (!is_user_lpad_enabled())
return;

task->thread_info.user_cfi_state.ufcfi_en = enable ? 1 : 0;
@@ -259,7 +261,7 @@ SYSCALL_DEFINE3(map_shadow_stack, unsigned long, addr, unsigned long, size, unsi
bool set_tok = flags & SHADOW_STACK_SET_TOKEN;
unsigned long aligned_size = 0;

- if (!cpu_supports_shadow_stack())
+ if (!is_user_shstk_enabled())
return -EOPNOTSUPP;

/* Anything other than set token should result in invalid param */
@@ -306,7 +308,7 @@ unsigned long shstk_alloc_thread_stack(struct task_struct *tsk,
unsigned long addr, size;

/* If shadow stack is not supported, return 0 */
- if (!cpu_supports_shadow_stack())
+ if (!is_user_shstk_enabled())
return 0;

/*
@@ -352,7 +354,7 @@ void shstk_release(struct task_struct *tsk)
{
unsigned long base = 0, size = 0;
/* If shadow stack is not supported or not enabled, nothing to release */
- if (!cpu_supports_shadow_stack() || !is_shstk_enabled(tsk))
+ if (!is_user_shstk_enabled() || !is_shstk_enabled(tsk))
return;

/*
@@ -381,7 +383,7 @@ int arch_get_shadow_stack_status(struct task_struct *t, unsigned long __user *st
{
unsigned long bcfi_status = 0;

- if (!cpu_supports_shadow_stack())
+ if (!is_user_shstk_enabled())
return -EINVAL;

/* this means shadow stack is enabled on the task */
@@ -395,7 +397,7 @@ int arch_set_shadow_stack_status(struct task_struct *t, unsigned long status)
unsigned long size = 0, addr = 0;
bool enable_shstk = false;

- if (!cpu_supports_shadow_stack())
+ if (!is_user_shstk_enabled())
return -EINVAL;

/* Reject unknown flags */
@@ -448,7 +450,7 @@ int arch_lock_shadow_stack_status(struct task_struct *task,
unsigned long arg)
{
/* If shtstk not supported or not enabled on task, nothing to lock here */
- if (!cpu_supports_shadow_stack() ||
+ if (!is_user_shstk_enabled() ||
!is_shstk_enabled(task) || arg != 0)
return -EINVAL;

@@ -461,7 +463,7 @@ int arch_get_indir_br_lp_status(struct task_struct *t, unsigned long __user *sta
{
unsigned long fcfi_status = 0;

- if (!cpu_supports_indirect_br_lp_instr())
+ if (!is_user_lpad_enabled())
return -EINVAL;

/* indirect branch tracking is enabled on the task or not */
@@ -474,7 +476,7 @@ int arch_set_indir_br_lp_status(struct task_struct *t, unsigned long status)
{
bool enable_indir_lp = false;

- if (!cpu_supports_indirect_br_lp_instr())
+ if (!is_user_lpad_enabled())
return -EINVAL;

/* indirect branch tracking is locked and further can't be modified by user */
@@ -498,7 +500,7 @@ int arch_lock_indir_br_lp_status(struct task_struct *task,
* If indirect branch tracking is not supported or not enabled on task,
* nothing to lock here
*/
- if (!cpu_supports_indirect_br_lp_instr() ||
+ if (!is_user_lpad_enabled() ||
!is_indir_lp_enabled(task) || arg != 0)
return -EINVAL;

@@ -506,3 +508,38 @@ int arch_lock_indir_br_lp_status(struct task_struct *task,

return 0;
}
+
+bool is_user_shstk_enabled(void)
+{
+ return (cpu_supports_shadow_stack() &&
+ !(riscv_nousercfi & CMDLINE_DISABLE_RISCV_USERCFI_BCFI));
+}
+
+bool is_user_lpad_enabled(void)
+{
+ return (cpu_supports_indirect_br_lp_instr() &&
+ !(riscv_nousercfi & CMDLINE_DISABLE_RISCV_USERCFI_FCFI));
+}
+
+static int __init setup_global_riscv_enable(char *str)
+{
+ if (strcmp(str, "all") == 0)
+ riscv_nousercfi = CMDLINE_DISABLE_RISCV_USERCFI;
+
+ if (strcmp(str, "fcfi") == 0)
+ riscv_nousercfi |= CMDLINE_DISABLE_RISCV_USERCFI_FCFI;
+
+ if (strcmp(str, "bcfi") == 0)
+ riscv_nousercfi |= CMDLINE_DISABLE_RISCV_USERCFI_BCFI;
+
+ if (riscv_nousercfi)
+ pr_info("riscv user cfi disabled via cmdline "
+ "shadow stack status : %s, landing pad status : %s\n",
+ (riscv_nousercfi & CMDLINE_DISABLE_RISCV_USERCFI_BCFI) ? "disabled" :
+ "enabled", (riscv_nousercfi & CMDLINE_DISABLE_RISCV_USERCFI_FCFI) ?
+ "disabled" : "enabled");
+
+ return 1;
+}
+
+__setup("riscv_nousercfi=", setup_global_riscv_enable);

--
2.43.0