mirror of
https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
synced 2026-02-17 19:30:11 -05:00
Uprobes need more advanced read, push, and pop userspace GCS functionality. Implement those features using the existing gcsstr() and copy_from_user(). Its important to note that GCS pages can be read by normal instructions, but the hardware validates that pages used by GCS specific operations, have a GCS privilege set. We aren't validating this in load_user_gcs because it requires stabilizing the VMA over the read which may fault. Signed-off-by: Jeremy Linton <jeremy.linton@arm.com> Reviewed-by: Catalin Marinas <catalin.marinas@arm.com> Reviewed-by: Mark Brown <broonie@kernel.org> [will: Add '__force' to gcspr cast in pop_user_gcs()] Signed-off-by: Will Deacon <will@kernel.org>
197 lines
4.1 KiB
C
197 lines
4.1 KiB
C
/* SPDX-License-Identifier: GPL-2.0-only */
|
|
/*
|
|
* Copyright (C) 2023 ARM Ltd.
|
|
*/
|
|
#ifndef __ASM_GCS_H
|
|
#define __ASM_GCS_H
|
|
|
|
#include <asm/types.h>
|
|
#include <asm/uaccess.h>
|
|
|
|
struct kernel_clone_args;
|
|
struct ksignal;
|
|
|
|
static inline void gcsb_dsync(void)
|
|
{
|
|
asm volatile(".inst 0xd503227f" : : : "memory");
|
|
}
|
|
|
|
static inline void gcsstr(u64 *addr, u64 val)
|
|
{
|
|
register u64 *_addr __asm__ ("x0") = addr;
|
|
register long _val __asm__ ("x1") = val;
|
|
|
|
/* GCSSTTR x1, [x0] */
|
|
asm volatile(
|
|
".inst 0xd91f1c01\n"
|
|
:
|
|
: "rZ" (_val), "r" (_addr)
|
|
: "memory");
|
|
}
|
|
|
|
static inline void gcsss1(u64 Xt)
|
|
{
|
|
asm volatile (
|
|
"sys #3, C7, C7, #2, %0\n"
|
|
:
|
|
: "rZ" (Xt)
|
|
: "memory");
|
|
}
|
|
|
|
static inline u64 gcsss2(void)
|
|
{
|
|
u64 Xt;
|
|
|
|
asm volatile(
|
|
"SYSL %0, #3, C7, C7, #3\n"
|
|
: "=r" (Xt)
|
|
:
|
|
: "memory");
|
|
|
|
return Xt;
|
|
}
|
|
|
|
#define PR_SHADOW_STACK_SUPPORTED_STATUS_MASK \
|
|
(PR_SHADOW_STACK_ENABLE | PR_SHADOW_STACK_WRITE | PR_SHADOW_STACK_PUSH)
|
|
|
|
#ifdef CONFIG_ARM64_GCS
|
|
|
|
static inline bool task_gcs_el0_enabled(struct task_struct *task)
|
|
{
|
|
return task->thread.gcs_el0_mode & PR_SHADOW_STACK_ENABLE;
|
|
}
|
|
|
|
void gcs_set_el0_mode(struct task_struct *task);
|
|
void gcs_free(struct task_struct *task);
|
|
void gcs_preserve_current_state(void);
|
|
unsigned long gcs_alloc_thread_stack(struct task_struct *tsk,
|
|
const struct kernel_clone_args *args);
|
|
|
|
static inline int gcs_check_locked(struct task_struct *task,
|
|
unsigned long new_val)
|
|
{
|
|
unsigned long cur_val = task->thread.gcs_el0_mode;
|
|
|
|
cur_val &= task->thread.gcs_el0_locked;
|
|
new_val &= task->thread.gcs_el0_locked;
|
|
|
|
if (cur_val != new_val)
|
|
return -EBUSY;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static inline int gcssttr(unsigned long __user *addr, unsigned long val)
|
|
{
|
|
register unsigned long __user *_addr __asm__ ("x0") = addr;
|
|
register unsigned long _val __asm__ ("x1") = val;
|
|
int err = 0;
|
|
|
|
/* GCSSTTR x1, [x0] */
|
|
asm volatile(
|
|
"1: .inst 0xd91f1c01\n"
|
|
"2: \n"
|
|
_ASM_EXTABLE_UACCESS_ERR(1b, 2b, %w0)
|
|
: "+r" (err)
|
|
: "rZ" (_val), "r" (_addr)
|
|
: "memory");
|
|
|
|
return err;
|
|
}
|
|
|
|
static inline void put_user_gcs(unsigned long val, unsigned long __user *addr,
|
|
int *err)
|
|
{
|
|
int ret;
|
|
|
|
if (!access_ok((char __user *)addr, sizeof(u64))) {
|
|
*err = -EFAULT;
|
|
return;
|
|
}
|
|
|
|
uaccess_ttbr0_enable();
|
|
ret = gcssttr(addr, val);
|
|
if (ret != 0)
|
|
*err = ret;
|
|
uaccess_ttbr0_disable();
|
|
}
|
|
|
|
static inline void push_user_gcs(unsigned long val, int *err)
|
|
{
|
|
u64 gcspr = read_sysreg_s(SYS_GCSPR_EL0);
|
|
|
|
gcspr -= sizeof(u64);
|
|
put_user_gcs(val, (unsigned long __user *)gcspr, err);
|
|
if (!*err)
|
|
write_sysreg_s(gcspr, SYS_GCSPR_EL0);
|
|
}
|
|
|
|
/*
|
|
* Unlike put/push_user_gcs() above, get/pop_user_gsc() doesn't
|
|
* validate the GCS permission is set on the page being read. This
|
|
* differs from how the hardware works when it consumes data stored at
|
|
* GCSPR. Callers should ensure this is acceptable.
|
|
*/
|
|
static inline u64 get_user_gcs(unsigned long __user *addr, int *err)
|
|
{
|
|
unsigned long ret;
|
|
u64 load = 0;
|
|
|
|
/* Ensure previous GCS operation are visible before we read the page */
|
|
gcsb_dsync();
|
|
ret = copy_from_user(&load, addr, sizeof(load));
|
|
if (ret != 0)
|
|
*err = ret;
|
|
return load;
|
|
}
|
|
|
|
static inline u64 pop_user_gcs(int *err)
|
|
{
|
|
u64 gcspr = read_sysreg_s(SYS_GCSPR_EL0);
|
|
u64 read_val;
|
|
|
|
read_val = get_user_gcs((__force unsigned long __user *)gcspr, err);
|
|
if (!*err)
|
|
write_sysreg_s(gcspr + sizeof(u64), SYS_GCSPR_EL0);
|
|
|
|
return read_val;
|
|
}
|
|
|
|
#else
|
|
|
|
static inline bool task_gcs_el0_enabled(struct task_struct *task)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
static inline void gcs_set_el0_mode(struct task_struct *task) { }
|
|
static inline void gcs_free(struct task_struct *task) { }
|
|
static inline void gcs_preserve_current_state(void) { }
|
|
static inline void put_user_gcs(unsigned long val, unsigned long __user *addr,
|
|
int *err) { }
|
|
static inline void push_user_gcs(unsigned long val, int *err) { }
|
|
|
|
static inline unsigned long gcs_alloc_thread_stack(struct task_struct *tsk,
|
|
const struct kernel_clone_args *args)
|
|
{
|
|
return -ENOTSUPP;
|
|
}
|
|
static inline int gcs_check_locked(struct task_struct *task,
|
|
unsigned long new_val)
|
|
{
|
|
return 0;
|
|
}
|
|
static inline u64 get_user_gcs(unsigned long __user *addr, int *err)
|
|
{
|
|
*err = -EFAULT;
|
|
return 0;
|
|
}
|
|
static inline u64 pop_user_gcs(int *err)
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
#endif
|
|
|
|
#endif
|