summaryrefslogtreecommitdiff
path: root/arch/riscv/kernel/ptrace.c
diff options
context:
space:
mode:
Diffstat (limited to 'arch/riscv/kernel/ptrace.c')
-rw-r--r--arch/riscv/kernel/ptrace.c233
1 files changed, 219 insertions, 14 deletions
diff --git a/arch/riscv/kernel/ptrace.c b/arch/riscv/kernel/ptrace.c
index ea67e9fb7a58..793bcee46182 100644
--- a/arch/riscv/kernel/ptrace.c
+++ b/arch/riscv/kernel/ptrace.c
@@ -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,
@@ -95,9 +99,12 @@ static int riscv_vr_get(struct task_struct *target,
struct __riscv_v_ext_state *vstate = &target->thread.vstate;
struct __riscv_v_regset_state ptrace_vstate;
- if (!riscv_v_vstate_query(task_pt_regs(target)))
+ if (!(has_vector() || has_xtheadvector()))
return -EINVAL;
+ if (!riscv_v_vstate_query(task_pt_regs(target)))
+ return -ENODATA;
+
/*
* Ensure the vector registers have been saved to the memory before
* copying them to membuf.
@@ -121,6 +128,92 @@ static int riscv_vr_get(struct task_struct *target,
return membuf_write(&to, vstate->datap, riscv_v_vsize);
}
+static int invalid_ptrace_v_csr(struct __riscv_v_ext_state *vstate,
+ struct __riscv_v_regset_state *ptrace)
+{
+ unsigned long vsew, vlmul, vfrac, vl;
+ unsigned long elen, vlen;
+ unsigned long sew, lmul;
+ unsigned long reserved;
+
+ vlen = vstate->vlenb * 8;
+ if (vstate->vlenb != ptrace->vlenb)
+ return 1;
+
+ /* do not allow to set vcsr/vxrm/vxsat reserved bits */
+ reserved = ~(CSR_VXSAT_MASK | (CSR_VXRM_MASK << CSR_VXRM_SHIFT));
+ if (ptrace->vcsr & reserved)
+ return 1;
+
+ if (has_vector()) {
+ /* do not allow to set vtype reserved bits and vill bit */
+ reserved = ~(VTYPE_VSEW | VTYPE_VLMUL | VTYPE_VMA | VTYPE_VTA);
+ if (ptrace->vtype & reserved)
+ return 1;
+
+ elen = riscv_has_extension_unlikely(RISCV_ISA_EXT_ZVE64X) ? 64 : 32;
+ vsew = (ptrace->vtype & VTYPE_VSEW) >> VTYPE_VSEW_SHIFT;
+ sew = 8 << vsew;
+
+ if (sew > elen)
+ return 1;
+
+ vfrac = (ptrace->vtype & VTYPE_VLMUL_FRAC);
+ vlmul = (ptrace->vtype & VTYPE_VLMUL);
+
+ /* RVV 1.0 spec 3.4.2: VLMUL(0x4) reserved */
+ if (vlmul == 4)
+ return 1;
+
+ /* RVV 1.0 spec 3.4.2: (LMUL < SEW_min / ELEN) reserved */
+ if (vlmul == 5 && elen == 32)
+ return 1;
+
+ /* for zero vl verify that at least one element is possible */
+ vl = ptrace->vl ? ptrace->vl : 1;
+
+ if (vfrac) {
+ /* integer 1/LMUL: VL =< VLMAX = VLEN / SEW / LMUL */
+ lmul = 2 << (3 - (vlmul - vfrac));
+ if (vlen < vl * sew * lmul)
+ return 1;
+ } else {
+ /* integer LMUL: VL =< VLMAX = LMUL * VLEN / SEW */
+ lmul = 1 << vlmul;
+ if (vl * sew > lmul * vlen)
+ return 1;
+ }
+ }
+
+ if (has_xtheadvector()) {
+ /* do not allow to set vtype reserved bits and vill bit */
+ reserved = ~(VTYPE_VSEW_THEAD | VTYPE_VLMUL_THEAD | VTYPE_VEDIV_THEAD);
+ if (ptrace->vtype & reserved)
+ return 1;
+
+ /*
+ * THead ISA Extension spec chapter 16:
+ * divided element extension ('Zvediv') is not part of XTheadVector
+ */
+ if (ptrace->vtype & VTYPE_VEDIV_THEAD)
+ return 1;
+
+ vsew = (ptrace->vtype & VTYPE_VSEW_THEAD) >> VTYPE_VSEW_THEAD_SHIFT;
+ sew = 8 << vsew;
+
+ vlmul = (ptrace->vtype & VTYPE_VLMUL_THEAD);
+ lmul = 1 << vlmul;
+
+ /* for zero vl verify that at least one element is possible */
+ vl = ptrace->vl ? ptrace->vl : 1;
+
+ if (vl * sew > lmul * vlen)
+ return 1;
+ }
+
+ return 0;
+}
+
static int riscv_vr_set(struct task_struct *target,
const struct user_regset *regset,
unsigned int pos, unsigned int count,
@@ -130,16 +223,19 @@ static int riscv_vr_set(struct task_struct *target,
struct __riscv_v_ext_state *vstate = &target->thread.vstate;
struct __riscv_v_regset_state ptrace_vstate;
- if (!riscv_v_vstate_query(task_pt_regs(target)))
+ if (!(has_vector() || has_xtheadvector()))
return -EINVAL;
+ if (!riscv_v_vstate_query(task_pt_regs(target)))
+ return -ENODATA;
+
/* Copy rest of the vstate except datap */
ret = user_regset_copyin(&pos, &count, &kbuf, &ubuf, &ptrace_vstate, 0,
sizeof(struct __riscv_v_regset_state));
if (unlikely(ret))
return ret;
- if (vstate->vlenb != ptrace_vstate.vlenb)
+ if (invalid_ptrace_v_csr(vstate, &ptrace_vstate))
return -EINVAL;
vstate->vstart = ptrace_vstate.vstart;
@@ -153,6 +249,17 @@ static int riscv_vr_set(struct task_struct *target,
0, riscv_v_vsize);
return ret;
}
+
+static int riscv_vr_active(struct task_struct *target, const struct user_regset *regset)
+{
+ if (!(has_vector() || has_xtheadvector()))
+ return -ENODEV;
+
+ if (!riscv_v_vstate_query(task_pt_regs(target)))
+ return 0;
+
+ return regset->n;
+}
#endif
#ifdef CONFIG_RISCV_ISA_SUPM
@@ -184,9 +291,90 @@ static int tagged_addr_ctrl_set(struct task_struct *target,
}
#endif
-static const struct user_regset riscv_user_regset[] = {
+#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_BRANCH_LANDING_PAD_EN_STATE;
+ user_cfi.cfi_status.cfi_state |= is_indir_lp_locked(target) ?
+ PTRACE_CFI_BRANCH_LANDING_PAD_LOCK_STATE : 0;
+ user_cfi.cfi_status.cfi_state |= (regs->status & SR_ELP) ?
+ PTRACE_CFI_BRANCH_EXPECTED_LANDING_PAD_STATE : 0;
+ }
+
+ if (is_shstk_enabled(target)) {
+ user_cfi.cfi_status.cfi_state |= (PTRACE_CFI_SHADOW_STACK_EN_STATE |
+ PTRACE_CFI_SHADOW_STACK_PTR_STATE);
+ user_cfi.cfi_status.cfi_state |= is_shstk_locked(target) ?
+ PTRACE_CFI_SHADOW_STACK_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_BRANCH_LANDING_PAD_EN_STATE | PTRACE_CFI_BRANCH_LANDING_PAD_LOCK_STATE |
+ PTRACE_CFI_SHADOW_STACK_EN_STATE | PTRACE_CFI_SHADOW_STACK_LOCK_STATE)) ||
+ (user_cfi.cfi_status.cfi_state & PTRACE_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_BRANCH_EXPECTED_LANDING_PAD_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_SHADOW_STACK_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] = {
- .core_note_type = NT_PRSTATUS,
+ USER_REGSET_NOTE_TYPE(PRSTATUS),
.n = ELF_NGREG,
.size = sizeof(elf_greg_t),
.align = sizeof(elf_greg_t),
@@ -195,7 +383,7 @@ static const struct user_regset riscv_user_regset[] = {
},
#ifdef CONFIG_FPU
[REGSET_F] = {
- .core_note_type = NT_PRFPREG,
+ USER_REGSET_NOTE_TYPE(PRFPREG),
.n = ELF_NFPREG,
.size = sizeof(elf_fpreg_t),
.align = sizeof(elf_fpreg_t),
@@ -205,18 +393,17 @@ static const struct user_regset riscv_user_regset[] = {
#endif
#ifdef CONFIG_RISCV_ISA_V
[REGSET_V] = {
- .core_note_type = NT_RISCV_VECTOR,
+ USER_REGSET_NOTE_TYPE(RISCV_VECTOR),
.align = 16,
- .n = ((32 * RISCV_MAX_VLENB) +
- sizeof(struct __riscv_v_regset_state)) / sizeof(__u32),
.size = sizeof(__u32),
.regset_get = riscv_vr_get,
.set = riscv_vr_set,
+ .active = riscv_vr_active,
},
#endif
#ifdef CONFIG_RISCV_ISA_SUPM
[REGSET_TAGGED_ADDR_CTRL] = {
- .core_note_type = NT_RISCV_TAGGED_ADDR_CTRL,
+ USER_REGSET_NOTE_TYPE(RISCV_TAGGED_ADDR_CTRL),
.n = 1,
.size = sizeof(long),
.align = sizeof(long),
@@ -224,6 +411,16 @@ static const struct user_regset riscv_user_regset[] = {
.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 = {
@@ -233,6 +430,14 @@ static const struct user_regset_view riscv_user_native_view = {
.n = ARRAY_SIZE(riscv_user_regset),
};
+#ifdef CONFIG_RISCV_ISA_V
+void __init update_regset_vector_info(unsigned long size)
+{
+ riscv_user_regset[REGSET_V].n = (size + sizeof(struct __riscv_v_regset_state)) /
+ sizeof(__u32);
+}
+#endif
+
struct pt_regs_offset {
const char *name;
int offset;
@@ -372,15 +577,15 @@ static int compat_riscv_gpr_set(struct task_struct *target,
struct compat_user_regs_struct cregs;
ret = user_regset_copyin(&pos, &count, &kbuf, &ubuf, &cregs, 0, -1);
-
- cregs_to_regs(&cregs, task_pt_regs(target));
+ if (!ret)
+ cregs_to_regs(&cregs, task_pt_regs(target));
return ret;
}
static const struct user_regset compat_riscv_user_regset[] = {
[REGSET_X] = {
- .core_note_type = NT_PRSTATUS,
+ USER_REGSET_NOTE_TYPE(PRSTATUS),
.n = ELF_NGREG,
.size = sizeof(compat_elf_greg_t),
.align = sizeof(compat_elf_greg_t),
@@ -389,7 +594,7 @@ static const struct user_regset compat_riscv_user_regset[] = {
},
#ifdef CONFIG_FPU
[REGSET_F] = {
- .core_note_type = NT_PRFPREG,
+ USER_REGSET_NOTE_TYPE(PRFPREG),
.n = ELF_NFPREG,
.size = sizeof(elf_fpreg_t),
.align = sizeof(elf_fpreg_t),