Commit 4a601714 authored by Jeremy Linton's avatar Jeremy Linton Committed by Will Deacon
Browse files

arm64: uprobes: Add GCS support to uretprobes



Ret probes work by changing the value in the link register at
the probe location to return to the probe rather than the calling
routine. Thus the GCS needs to be updated with this address as well.

Since its possible to insert probes at locations where the
current value of the LR doesn't match the GCS state this needs
to be detected and handled in order to maintain the existing
no-fault behavior.

Co-developed-by: default avatarSteve Capper <steve.capper@arm.com>
Signed-off-by: default avatarSteve Capper <steve.capper@arm.com>
Signed-off-by: default avatarJeremy Linton <jeremy.linton@arm.com>
Reviewed-by: default avatarCatalin Marinas <catalin.marinas@arm.com>
[will: Add '__force' to gcspr casts in arch_uretprobe_hijack_return_addr()]
Signed-off-by: default avatarWill Deacon <will@kernel.org>
parent efb07ac5
Loading
Loading
Loading
Loading
+33 −0
Original line number Diff line number Diff line
@@ -6,6 +6,7 @@
#include <linux/ptrace.h>
#include <linux/uprobes.h>
#include <asm/cacheflush.h>
#include <asm/gcs.h>

#include "decode-insn.h"

@@ -159,11 +160,43 @@ arch_uretprobe_hijack_return_addr(unsigned long trampoline_vaddr,
				  struct pt_regs *regs)
{
	unsigned long orig_ret_vaddr;
	unsigned long gcs_ret_vaddr;
	int err = 0;
	u64 gcspr;

	orig_ret_vaddr = procedure_link_pointer(regs);

	if (task_gcs_el0_enabled(current)) {
		gcspr = read_sysreg_s(SYS_GCSPR_EL0);
		gcs_ret_vaddr = get_user_gcs((__force unsigned long __user *)gcspr, &err);
		if (err) {
			force_sig(SIGSEGV);
			goto out;
		}

		/*
		 * If the LR and GCS return addr don't match, then some kind of PAC
		 * signing or control flow occurred since entering the probed function.
		 * Likely because the user is attempting to retprobe on an instruction
		 * that isn't a function boundary or inside a leaf function. Explicitly
		 * abort this retprobe because it will generate a GCS exception.
		 */
		if (gcs_ret_vaddr != orig_ret_vaddr) {
			orig_ret_vaddr = -1;
			goto out;
		}

		put_user_gcs(trampoline_vaddr, (__force unsigned long __user *)gcspr, &err);
		if (err) {
			force_sig(SIGSEGV);
			goto out;
		}
	}

	/* Replace the return addr with trampoline addr */
	procedure_link_pointer_set(regs, trampoline_vaddr);

out:
	return orig_ret_vaddr;
}