[PATCH 3/3] arm64: module: Use text-poke for late PLT/veneer emission
From: Rui Qi
Date: Sat Oct 10 2026 - 04:54:54 EST
module_emit_plt_entry() and module_emit_veneer_for_adrp() write PLT
entries into the .plt section by direct assignment. Once a module is
fully formed its .plt is read-only/executable, so a late (livepatch)
relocation that falls back to PLT emission -- an out-of-range
R_AARCH64_CALL26/JUMP26, or an ADRP under CONFIG_ARM64_ERRATUM_843419
-- triggers a synchronous Data Abort and panics.
This is the same ROX premise that commit 91b89a634487
("arm64/module: Use text-poke API for late relocations.") handled for
the relocation writes through WRITE_PLACE(); the PLT/veneer emitters
were left using direct assignment.
The late path is reachable without remapping:
klp_write_section_relocs() applies .klp.rela.* sections through
apply_relocate_add() with the patch module already MODULE_STATE_LIVE,
so .plt is ROX.
Route the emitters through the text-poke API, mirroring WRITE_PLACE().
Fixes: 91b89a634487d5614e51ee773a889ed57f5551ca ("arm64/module: Use text-poke API for late relocations.")
Cc: stable@xxxxxxxxxxxxxxx
Signed-off-by: Rui Qi <qirui.001@xxxxxxxxxxxxx>
---
arch/arm64/kernel/module-plts.c | 35 ++++++++++++++++++++++++++++++---
1 file changed, 32 insertions(+), 3 deletions(-)
diff --git a/arch/arm64/kernel/module-plts.c b/arch/arm64/kernel/module-plts.c
index 7afd370da9f4..fa89ed014bd1 100644
--- a/arch/arm64/kernel/module-plts.c
+++ b/arch/arm64/kernel/module-plts.c
@@ -9,6 +9,29 @@
#include <linux/module.h>
#include <linux/moduleloader.h>
#include <linux/sort.h>
+#include <linux/text-patching.h>
+
+/*
+ * The .plt section is marked ROX once the module is fully formed, so
+ * late (livepatch) relocations that emit PLT entries cannot use direct
+ * assignment. Mirror WRITE_PLACE() and route the write through the
+ * text-poke API when the module is already loaded.
+ *
+ * Return: 0 on success, -EINVAL if the text-poke write failed.
+ */
+static int plt_entry_write(struct plt_entry *plt,
+ struct plt_entry *entry, struct module *mod)
+{
+ if (mod->state == MODULE_STATE_UNFORMED) {
+ *plt = *entry;
+ return 0;
+ }
+
+ if (!aarch64_insn_copy(plt, entry, sizeof(*plt)))
+ return -EINVAL;
+
+ return 0;
+}
static struct plt_entry __get_adrp_add_pair(u64 dst, u64 pc,
enum aarch64_insn_register reg)
@@ -76,11 +99,14 @@ u64 module_emit_plt_entry(struct module *mod, Elf64_Shdr *sechdrs,
int i = pltsec->plt_num_entries;
int j = i - 1;
u64 val = sym->st_value + rela->r_addend;
+ struct plt_entry entry;
if (is_forbidden_offset_for_adrp(&plt[i].adrp))
i++;
- plt[i] = get_plt_entry(val, &plt[i]);
+ entry = get_plt_entry(val, &plt[i]);
+ if (plt_entry_write(&plt[i], &entry, mod))
+ return 0;
/*
* Check if the entry we just created is a duplicate. Given that the
@@ -107,6 +133,7 @@ u64 module_emit_veneer_for_adrp(struct module *mod, Elf64_Shdr *sechdrs,
int i = pltsec->plt_num_entries++;
u32 br;
int rd;
+ struct plt_entry entry;
if (WARN_ON(pltsec->plt_num_entries > pltsec->plt_max_entries))
return 0;
@@ -121,8 +148,10 @@ u64 module_emit_veneer_for_adrp(struct module *mod, Elf64_Shdr *sechdrs,
br = aarch64_insn_gen_branch_imm((u64)&plt[i].br, (u64)loc + 4,
AARCH64_INSN_BRANCH_NOLINK);
- plt[i] = __get_adrp_add_pair(val, (u64)&plt[i], rd);
- plt[i].br = cpu_to_le32(br);
+ entry = __get_adrp_add_pair(val, (u64)&plt[i], rd);
+ entry.br = cpu_to_le32(br);
+ if (plt_entry_write(&plt[i], &entry, mod))
+ return 0;
return (u64)&plt[i];
}
--
2.20.1