Implement arm64 support for patchable function pointers by emitting them as branch instructions (and a couple of NOPs in case the new target is out of range of a normal branch instruction.) Signed-off-by: Ard Biesheuvel <ard.biesheuvel@xxxxxxxxxx> --- arch/arm64/Kconfig | 1 + arch/arm64/include/asm/ffp.h | 35 ++++++++++++++++++++ arch/arm64/kernel/insn.c | 22 ++++++++++++ 3 files changed, 58 insertions(+) diff --git a/arch/arm64/Kconfig b/arch/arm64/Kconfig index 1b1a0e95c751..db8c9e51c56d 100644 --- a/arch/arm64/Kconfig +++ b/arch/arm64/Kconfig @@ -102,6 +102,7 @@ config ARM64 select HAVE_ALIGNED_STRUCT_PAGE if SLUB select HAVE_ARCH_AUDITSYSCALL select HAVE_ARCH_BITREVERSE + select HAVE_ARCH_FFP select HAVE_ARCH_HUGE_VMAP select HAVE_ARCH_JUMP_LABEL select HAVE_ARCH_KASAN if !(ARM64_16K_PAGES && ARM64_VA_BITS_48) diff --git a/arch/arm64/include/asm/ffp.h b/arch/arm64/include/asm/ffp.h new file mode 100644 index 000000000000..678dc1262218 --- /dev/null +++ b/arch/arm64/include/asm/ffp.h @@ -0,0 +1,35 @@ +/* SPDX-License-Identifier: GPL-2.0 */ + +#ifndef __ASM_FFP_H +#define __ASM_FFP_H + +struct ffp { + u32 insn[5]; + u32 def_branch; +}; + +#define DECLARE_FFP(_fn, _def) \ + extern typeof(_def) _fn; \ + extern struct ffp const __ffp_ ## _fn + +#define DEFINE_FFP(_fn, _def) \ + DECLARE_FFP(_fn, _def); \ + asm(" .pushsection \".text\", \"ax\", %progbits \n" \ + " .align 3 \n" \ + " .globl " #_fn " \n" \ + " .globl __ffp_" #_fn " \n" \ + #_fn " : \n" \ + "__ffp_" #_fn " : \n" \ + " b " #_def " \n" \ + " nop \n" \ + " nop \n" \ + " nop \n" \ + " nop \n" \ + " b " #_def " \n" \ + " .popsection \n"); \ + EXPORT_SYMBOL(__ffp_ ## _fn) + +extern void ffp_set_target(const struct ffp *m, void *new_fn); +extern void ffp_reset_target(const struct ffp *m); + +#endif diff --git a/arch/arm64/kernel/insn.c b/arch/arm64/kernel/insn.c index 2b3413549734..a2ed547fd171 100644 --- a/arch/arm64/kernel/insn.c +++ b/arch/arm64/kernel/insn.c @@ -30,6 +30,7 @@ #include <asm/cacheflush.h> #include <asm/debug-monitors.h> #include <asm/fixmap.h> +#include <asm/ffp.h> #include <asm/insn.h> #include <asm/kprobes.h> @@ -1603,3 +1604,24 @@ u32 aarch64_insn_gen_extr(enum aarch64_insn_variant variant, insn = aarch64_insn_encode_register(AARCH64_INSN_REGTYPE_RN, insn, Rn); return aarch64_insn_encode_register(AARCH64_INSN_REGTYPE_RM, insn, Rm); } + +void ffp_set_target(const struct ffp *m, void *new_fn) +{ + u32 branch = aarch64_insn_gen_branch_imm((u64)m, (u64)new_fn, + AARCH64_INSN_BRANCH_NOLINK); + + if (branch == AARCH64_BREAK_FAULT) { + /* TODO out of range - use a PLT sequence instead */ + } else { + aarch64_insn_patch_text((void *[]){ (void *)m }, &branch, 1); + } +} +EXPORT_SYMBOL(ffp_set_target); + +void ffp_reset_target(const struct ffp *m) +{ + u32 branch = le32_to_cpu(m->def_branch); + + aarch64_insn_patch_text((void *[]){ (void *)m }, &branch, 1); +} +EXPORT_SYMBOL(ffp_reset_target); -- 2.11.0