mirror of
				https://github.com/torvalds/linux.git
				synced 2025-11-04 10:40:15 +02:00 
			
		
		
		
	If there is no context switch happens after we enable V for a process,
then we return to user space with whatever left on the CPU's V registers
accessible to the process. The leaked data could belong to another
process's V-context saved from last context switch, impacting process's
confidentiality on the system.
To prevent this from happening, we clear V registers by restoring
zero'd V context after turining on V.
Fixes: cd05483724 ("riscv: Allocate user's vector context in the first-use trap")
Signed-off-by: Andy Chiu <andy.chiu@sifive.com>
Reviewed-by: Björn Töpel <bjorn@rivosinc.com>
Link: https://lore.kernel.org/r/20230627015556.12329-2-andy.chiu@sifive.com
Signed-off-by: Palmer Dabbelt <palmer@rivosinc.com>
		
	
			
		
			
				
	
	
		
			277 lines
		
	
	
	
		
			6.3 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
			
		
		
	
	
			277 lines
		
	
	
	
		
			6.3 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
// SPDX-License-Identifier: GPL-2.0-or-later
 | 
						|
/*
 | 
						|
 * Copyright (C) 2023 SiFive
 | 
						|
 * Author: Andy Chiu <andy.chiu@sifive.com>
 | 
						|
 */
 | 
						|
#include <linux/export.h>
 | 
						|
#include <linux/sched/signal.h>
 | 
						|
#include <linux/types.h>
 | 
						|
#include <linux/slab.h>
 | 
						|
#include <linux/sched.h>
 | 
						|
#include <linux/uaccess.h>
 | 
						|
#include <linux/prctl.h>
 | 
						|
 | 
						|
#include <asm/thread_info.h>
 | 
						|
#include <asm/processor.h>
 | 
						|
#include <asm/insn.h>
 | 
						|
#include <asm/vector.h>
 | 
						|
#include <asm/csr.h>
 | 
						|
#include <asm/elf.h>
 | 
						|
#include <asm/ptrace.h>
 | 
						|
#include <asm/bug.h>
 | 
						|
 | 
						|
static bool riscv_v_implicit_uacc = IS_ENABLED(CONFIG_RISCV_ISA_V_DEFAULT_ENABLE);
 | 
						|
 | 
						|
unsigned long riscv_v_vsize __read_mostly;
 | 
						|
EXPORT_SYMBOL_GPL(riscv_v_vsize);
 | 
						|
 | 
						|
int riscv_v_setup_vsize(void)
 | 
						|
{
 | 
						|
	unsigned long this_vsize;
 | 
						|
 | 
						|
	/* There are 32 vector registers with vlenb length. */
 | 
						|
	riscv_v_enable();
 | 
						|
	this_vsize = csr_read(CSR_VLENB) * 32;
 | 
						|
	riscv_v_disable();
 | 
						|
 | 
						|
	if (!riscv_v_vsize) {
 | 
						|
		riscv_v_vsize = this_vsize;
 | 
						|
		return 0;
 | 
						|
	}
 | 
						|
 | 
						|
	if (riscv_v_vsize != this_vsize) {
 | 
						|
		WARN(1, "RISCV_ISA_V only supports one vlenb on SMP systems");
 | 
						|
		return -EOPNOTSUPP;
 | 
						|
	}
 | 
						|
 | 
						|
	return 0;
 | 
						|
}
 | 
						|
 | 
						|
static bool insn_is_vector(u32 insn_buf)
 | 
						|
{
 | 
						|
	u32 opcode = insn_buf & __INSN_OPCODE_MASK;
 | 
						|
	u32 width, csr;
 | 
						|
 | 
						|
	/*
 | 
						|
	 * All V-related instructions, including CSR operations are 4-Byte. So,
 | 
						|
	 * do not handle if the instruction length is not 4-Byte.
 | 
						|
	 */
 | 
						|
	if (unlikely(GET_INSN_LENGTH(insn_buf) != 4))
 | 
						|
		return false;
 | 
						|
 | 
						|
	switch (opcode) {
 | 
						|
	case RVV_OPCODE_VECTOR:
 | 
						|
		return true;
 | 
						|
	case RVV_OPCODE_VL:
 | 
						|
	case RVV_OPCODE_VS:
 | 
						|
		width = RVV_EXRACT_VL_VS_WIDTH(insn_buf);
 | 
						|
		if (width == RVV_VL_VS_WIDTH_8 || width == RVV_VL_VS_WIDTH_16 ||
 | 
						|
		    width == RVV_VL_VS_WIDTH_32 || width == RVV_VL_VS_WIDTH_64)
 | 
						|
			return true;
 | 
						|
 | 
						|
		break;
 | 
						|
	case RVG_OPCODE_SYSTEM:
 | 
						|
		csr = RVG_EXTRACT_SYSTEM_CSR(insn_buf);
 | 
						|
		if ((csr >= CSR_VSTART && csr <= CSR_VCSR) ||
 | 
						|
		    (csr >= CSR_VL && csr <= CSR_VLENB))
 | 
						|
			return true;
 | 
						|
	}
 | 
						|
 | 
						|
	return false;
 | 
						|
}
 | 
						|
 | 
						|
static int riscv_v_thread_zalloc(void)
 | 
						|
{
 | 
						|
	void *datap;
 | 
						|
 | 
						|
	datap = kzalloc(riscv_v_vsize, GFP_KERNEL);
 | 
						|
	if (!datap)
 | 
						|
		return -ENOMEM;
 | 
						|
 | 
						|
	current->thread.vstate.datap = datap;
 | 
						|
	memset(¤t->thread.vstate, 0, offsetof(struct __riscv_v_ext_state,
 | 
						|
						    datap));
 | 
						|
	return 0;
 | 
						|
}
 | 
						|
 | 
						|
#define VSTATE_CTRL_GET_CUR(x) ((x) & PR_RISCV_V_VSTATE_CTRL_CUR_MASK)
 | 
						|
#define VSTATE_CTRL_GET_NEXT(x) (((x) & PR_RISCV_V_VSTATE_CTRL_NEXT_MASK) >> 2)
 | 
						|
#define VSTATE_CTRL_MAKE_NEXT(x) (((x) << 2) & PR_RISCV_V_VSTATE_CTRL_NEXT_MASK)
 | 
						|
#define VSTATE_CTRL_GET_INHERIT(x) (!!((x) & PR_RISCV_V_VSTATE_CTRL_INHERIT))
 | 
						|
static inline int riscv_v_ctrl_get_cur(struct task_struct *tsk)
 | 
						|
{
 | 
						|
	return VSTATE_CTRL_GET_CUR(tsk->thread.vstate_ctrl);
 | 
						|
}
 | 
						|
 | 
						|
static inline int riscv_v_ctrl_get_next(struct task_struct *tsk)
 | 
						|
{
 | 
						|
	return VSTATE_CTRL_GET_NEXT(tsk->thread.vstate_ctrl);
 | 
						|
}
 | 
						|
 | 
						|
static inline bool riscv_v_ctrl_test_inherit(struct task_struct *tsk)
 | 
						|
{
 | 
						|
	return VSTATE_CTRL_GET_INHERIT(tsk->thread.vstate_ctrl);
 | 
						|
}
 | 
						|
 | 
						|
static inline void riscv_v_ctrl_set(struct task_struct *tsk, int cur, int nxt,
 | 
						|
				    bool inherit)
 | 
						|
{
 | 
						|
	unsigned long ctrl;
 | 
						|
 | 
						|
	ctrl = cur & PR_RISCV_V_VSTATE_CTRL_CUR_MASK;
 | 
						|
	ctrl |= VSTATE_CTRL_MAKE_NEXT(nxt);
 | 
						|
	if (inherit)
 | 
						|
		ctrl |= PR_RISCV_V_VSTATE_CTRL_INHERIT;
 | 
						|
	tsk->thread.vstate_ctrl = ctrl;
 | 
						|
}
 | 
						|
 | 
						|
bool riscv_v_vstate_ctrl_user_allowed(void)
 | 
						|
{
 | 
						|
	return riscv_v_ctrl_get_cur(current) == PR_RISCV_V_VSTATE_CTRL_ON;
 | 
						|
}
 | 
						|
EXPORT_SYMBOL_GPL(riscv_v_vstate_ctrl_user_allowed);
 | 
						|
 | 
						|
bool riscv_v_first_use_handler(struct pt_regs *regs)
 | 
						|
{
 | 
						|
	u32 __user *epc = (u32 __user *)regs->epc;
 | 
						|
	u32 insn = (u32)regs->badaddr;
 | 
						|
 | 
						|
	/* Do not handle if V is not supported, or disabled */
 | 
						|
	if (!(ELF_HWCAP & COMPAT_HWCAP_ISA_V))
 | 
						|
		return false;
 | 
						|
 | 
						|
	/* If V has been enabled then it is not the first-use trap */
 | 
						|
	if (riscv_v_vstate_query(regs))
 | 
						|
		return false;
 | 
						|
 | 
						|
	/* Get the instruction */
 | 
						|
	if (!insn) {
 | 
						|
		if (__get_user(insn, epc))
 | 
						|
			return false;
 | 
						|
	}
 | 
						|
 | 
						|
	/* Filter out non-V instructions */
 | 
						|
	if (!insn_is_vector(insn))
 | 
						|
		return false;
 | 
						|
 | 
						|
	/* Sanity check. datap should be null by the time of the first-use trap */
 | 
						|
	WARN_ON(current->thread.vstate.datap);
 | 
						|
 | 
						|
	/*
 | 
						|
	 * Now we sure that this is a V instruction. And it executes in the
 | 
						|
	 * context where VS has been off. So, try to allocate the user's V
 | 
						|
	 * context and resume execution.
 | 
						|
	 */
 | 
						|
	if (riscv_v_thread_zalloc()) {
 | 
						|
		force_sig(SIGBUS);
 | 
						|
		return true;
 | 
						|
	}
 | 
						|
	riscv_v_vstate_on(regs);
 | 
						|
	riscv_v_vstate_restore(current, regs);
 | 
						|
	return true;
 | 
						|
}
 | 
						|
 | 
						|
void riscv_v_vstate_ctrl_init(struct task_struct *tsk)
 | 
						|
{
 | 
						|
	bool inherit;
 | 
						|
	int cur, next;
 | 
						|
 | 
						|
	if (!has_vector())
 | 
						|
		return;
 | 
						|
 | 
						|
	next = riscv_v_ctrl_get_next(tsk);
 | 
						|
	if (!next) {
 | 
						|
		if (READ_ONCE(riscv_v_implicit_uacc))
 | 
						|
			cur = PR_RISCV_V_VSTATE_CTRL_ON;
 | 
						|
		else
 | 
						|
			cur = PR_RISCV_V_VSTATE_CTRL_OFF;
 | 
						|
	} else {
 | 
						|
		cur = next;
 | 
						|
	}
 | 
						|
	/* Clear next mask if inherit-bit is not set */
 | 
						|
	inherit = riscv_v_ctrl_test_inherit(tsk);
 | 
						|
	if (!inherit)
 | 
						|
		next = PR_RISCV_V_VSTATE_CTRL_DEFAULT;
 | 
						|
 | 
						|
	riscv_v_ctrl_set(tsk, cur, next, inherit);
 | 
						|
}
 | 
						|
 | 
						|
long riscv_v_vstate_ctrl_get_current(void)
 | 
						|
{
 | 
						|
	if (!has_vector())
 | 
						|
		return -EINVAL;
 | 
						|
 | 
						|
	return current->thread.vstate_ctrl & PR_RISCV_V_VSTATE_CTRL_MASK;
 | 
						|
}
 | 
						|
 | 
						|
long riscv_v_vstate_ctrl_set_current(unsigned long arg)
 | 
						|
{
 | 
						|
	bool inherit;
 | 
						|
	int cur, next;
 | 
						|
 | 
						|
	if (!has_vector())
 | 
						|
		return -EINVAL;
 | 
						|
 | 
						|
	if (arg & ~PR_RISCV_V_VSTATE_CTRL_MASK)
 | 
						|
		return -EINVAL;
 | 
						|
 | 
						|
	cur = VSTATE_CTRL_GET_CUR(arg);
 | 
						|
	switch (cur) {
 | 
						|
	case PR_RISCV_V_VSTATE_CTRL_OFF:
 | 
						|
		/* Do not allow user to turn off V if current is not off */
 | 
						|
		if (riscv_v_ctrl_get_cur(current) != PR_RISCV_V_VSTATE_CTRL_OFF)
 | 
						|
			return -EPERM;
 | 
						|
 | 
						|
		break;
 | 
						|
	case PR_RISCV_V_VSTATE_CTRL_ON:
 | 
						|
		break;
 | 
						|
	case PR_RISCV_V_VSTATE_CTRL_DEFAULT:
 | 
						|
		cur = riscv_v_ctrl_get_cur(current);
 | 
						|
		break;
 | 
						|
	default:
 | 
						|
		return -EINVAL;
 | 
						|
	}
 | 
						|
 | 
						|
	next = VSTATE_CTRL_GET_NEXT(arg);
 | 
						|
	inherit = VSTATE_CTRL_GET_INHERIT(arg);
 | 
						|
	switch (next) {
 | 
						|
	case PR_RISCV_V_VSTATE_CTRL_DEFAULT:
 | 
						|
	case PR_RISCV_V_VSTATE_CTRL_OFF:
 | 
						|
	case PR_RISCV_V_VSTATE_CTRL_ON:
 | 
						|
		riscv_v_ctrl_set(current, cur, next, inherit);
 | 
						|
		return 0;
 | 
						|
	}
 | 
						|
 | 
						|
	return -EINVAL;
 | 
						|
}
 | 
						|
 | 
						|
#ifdef CONFIG_SYSCTL
 | 
						|
 | 
						|
static struct ctl_table riscv_v_default_vstate_table[] = {
 | 
						|
	{
 | 
						|
		.procname	= "riscv_v_default_allow",
 | 
						|
		.data		= &riscv_v_implicit_uacc,
 | 
						|
		.maxlen		= sizeof(riscv_v_implicit_uacc),
 | 
						|
		.mode		= 0644,
 | 
						|
		.proc_handler	= proc_dobool,
 | 
						|
	},
 | 
						|
	{ }
 | 
						|
};
 | 
						|
 | 
						|
static int __init riscv_v_sysctl_init(void)
 | 
						|
{
 | 
						|
	if (has_vector())
 | 
						|
		if (!register_sysctl("abi", riscv_v_default_vstate_table))
 | 
						|
			return -EINVAL;
 | 
						|
	return 0;
 | 
						|
}
 | 
						|
 | 
						|
#else /* ! CONFIG_SYSCTL */
 | 
						|
static int __init riscv_v_sysctl_init(void) { return 0; }
 | 
						|
#endif /* ! CONFIG_SYSCTL */
 | 
						|
 | 
						|
static int riscv_v_init(void)
 | 
						|
{
 | 
						|
	return riscv_v_sysctl_init();
 | 
						|
}
 | 
						|
core_initcall(riscv_v_init);
 |