[RFC PATCH 2/9] arm64: kernel: add arch support for patchable function pointers

From: Ard Biesheuvel
Date: Fri Oct 05 2018 - 04:14:34 EST


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