Commit 4e4e36dc authored by Will Deacon's avatar Will Deacon
Browse files

Merge branch 'for-next/uprobes' into for-next/core

* for-next/uprobes:
  arm64: probes: Fix incorrect bl/blr address and register usage
  uprobes: uprobe_warn should use passed task
  arm64: Kconfig: Remove GCS restrictions on UPROBES
  arm64: uprobes: Add GCS support to uretprobes
  arm64: probes: Add GCS support to bl/blr/ret
  arm64: uaccess: Add additional userspace GCS accessors
  arm64: uaccess: Move existing GCS accessors definitions to gcs.h
  arm64: probes: Break ret out from bl/blr
parents c7c7eb4f ea87c553
Loading
Loading
Loading
Loading
+0 −1
Original line number Diff line number Diff line
@@ -2213,7 +2213,6 @@ config ARM64_GCS
	default y
	select ARCH_HAS_USER_SHADOW_STACK
	select ARCH_USES_HIGH_VMA_FLAGS
	depends on !UPROBES
	help
	  Guarded Control Stack (GCS) provides support for a separate
	  stack with restricted access which contains only return
+90 −1
Original line number Diff line number Diff line
@@ -21,7 +21,7 @@ static inline void gcsstr(u64 *addr, u64 val)
	register u64 *_addr __asm__ ("x0") = addr;
	register long _val __asm__ ("x1") = val;

	/* GCSSTTR x1, x0 */
	/* GCSSTTR x1, [x0] */
	asm volatile(
		".inst 0xd91f1c01\n"
		:
@@ -81,6 +81,82 @@ static inline int gcs_check_locked(struct task_struct *task,
	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)
@@ -91,6 +167,10 @@ static inline bool task_gcs_el0_enabled(struct task_struct *task)
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)
{
@@ -101,6 +181,15 @@ static inline int gcs_check_locked(struct task_struct *task,
{
	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

+0 −40
Original line number Diff line number Diff line
@@ -502,44 +502,4 @@ static inline size_t probe_subpage_writeable(const char __user *uaddr,

#endif /* CONFIG_ARCH_HAS_SUBPAGE_FAULTS */

#ifdef CONFIG_ARM64_GCS

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();
}


#endif /* CONFIG_ARM64_GCS */

#endif /* __ASM_UACCESS_H */
+4 −3
Original line number Diff line number Diff line
@@ -108,9 +108,10 @@ arm_probe_decode_insn(u32 insn, struct arch_probe_insn *api)
	    aarch64_insn_is_bl(insn)) {
		api->handler = simulate_b_bl;
	} else if (aarch64_insn_is_br(insn) ||
	    aarch64_insn_is_blr(insn) ||
	    aarch64_insn_is_ret(insn)) {
		api->handler = simulate_br_blr_ret;
		aarch64_insn_is_blr(insn)) {
		api->handler = simulate_br_blr;
	} else if (aarch64_insn_is_ret(insn)) {
		api->handler = simulate_ret;
	} else {
		/*
		 * Instruction cannot be stepped out-of-line and we don't
+42 −8
Original line number Diff line number Diff line
@@ -13,6 +13,7 @@
#include <asm/traps.h>

#include "simulate-insn.h"
#include "asm/gcs.h"

#define bbl_displacement(insn)		\
	sign_extend32(((insn) & 0x3ffffff) << 2, 27)
@@ -49,6 +50,21 @@ static inline u32 get_w_reg(struct pt_regs *regs, int reg)
	return lower_32_bits(pt_regs_read_reg(regs, reg));
}

static inline int update_lr(struct pt_regs *regs, long addr)
{
	int err = 0;

	if (user_mode(regs) && task_gcs_el0_enabled(current)) {
		push_user_gcs(addr, &err);
		if (err) {
			force_sig(SIGSEGV);
			return err;
		}
	}
	procedure_link_pointer_set(regs, addr);
	return err;
}

static bool __kprobes check_cbz(u32 opcode, struct pt_regs *regs)
{
	int xn = opcode & 0x1f;
@@ -107,9 +123,9 @@ simulate_b_bl(u32 opcode, long addr, struct pt_regs *regs)
{
	int disp = bbl_displacement(opcode);

	/* Link register is x30 */
	if (opcode & (1 << 31))
		set_x_reg(regs, 30, addr + 4);
		if (update_lr(regs, addr + 4))
			return;

	instruction_pointer_set(regs, addr + disp);
}
@@ -126,16 +142,34 @@ simulate_b_cond(u32 opcode, long addr, struct pt_regs *regs)
}

void __kprobes
simulate_br_blr_ret(u32 opcode, long addr, struct pt_regs *regs)
simulate_br_blr(u32 opcode, long addr, struct pt_regs *regs)
{
	int xn = (opcode >> 5) & 0x1f;
	u64 b_target = get_x_reg(regs, xn);

	/* update pc first in case we're doing a "blr lr" */
	instruction_pointer_set(regs, get_x_reg(regs, xn));

	/* Link register is x30 */
	if (((opcode >> 21) & 0x3) == 1)
		set_x_reg(regs, 30, addr + 4);
		if (update_lr(regs, addr + 4))
			return;

	instruction_pointer_set(regs, b_target);
}

void __kprobes
simulate_ret(u32 opcode, long addr, struct pt_regs *regs)
{
	u64 ret_addr;
	int err = 0;
	int xn = (opcode >> 5) & 0x1f;
	u64 r_target = get_x_reg(regs, xn);

	if (user_mode(regs) && task_gcs_el0_enabled(current)) {
		ret_addr = pop_user_gcs(&err);
		if (err || ret_addr != r_target) {
			force_sig(SIGSEGV);
			return;
		}
	}
	instruction_pointer_set(regs, r_target);
}

void __kprobes
Loading