[PATCH] arm64/module: Fix livepatch BTI exceptions with Clang 21+

Josh Poimboeuf posted 1 patch 1 month, 3 weeks ago
arch/arm64/kernel/module-plts.c | 42 +++++++++++++++++++++++++++++----
1 file changed, 38 insertions(+), 4 deletions(-)
[PATCH] arm64/module: Fix livepatch BTI exceptions with Clang 21+
Posted by Josh Poimboeuf 1 month, 3 weeks ago
The following BTI exception was seen when loading a livepatch module:

  Internal error: Oops - BTI: 0000000036000001 [#1]  SMP
  pstate: 634004c9 (nZCv daIF +PAN -UAO +TCO +DIT -SSBS BTYPE=jc)
  pc : kill_orphaned_pgrp+0x0/0x150
  lr : do_exit+0x498/0xaf0 [livepatch_combined]

The problem is that the patch module's do_exit() is branching to a
static function in vmlinux using a module PLT veneer (indirect branch),
but the target function doesn't have a BTI landing pad.

Clang 21+ omits the landing pad for static functions which can only be
reached by a direct branch.  That's fine for ordinary modules which only
branch to global exported functions.  But livepatch modules use klp
relocations to reference arbitrary kernel symbols, and with
CONFIG_RANDOMIZE_MODULE_REGION_FULL the module is far enough from the
kernel that every R_AARCH64_CALL26 needs a PLT.

RET is exempt from BTI checking, so use it instead of BR when the target
has no landing pad, similar to what ftrace and BPF do.

This was found by testing with klp-build and Clang 21, but the issue is
not specific to klp-build.  It's inherent to any livepatch module use of
klp relocations.

Previous tests with Clang 20 did not show this problem, as older Clang
unconditionally emits "bti c" for every C function.

Fixes: fd1e0fd71f65 ("arm64: Implement HAVE_LIVEPATCH")
Signed-off-by: Josh Poimboeuf <jpoimboe@kernel.org>
---
 arch/arm64/kernel/module-plts.c | 42 +++++++++++++++++++++++++++++----
 1 file changed, 38 insertions(+), 4 deletions(-)

diff --git a/arch/arm64/kernel/module-plts.c b/arch/arm64/kernel/module-plts.c
index 7afd370da9f48..4249b477daa8b 100644
--- a/arch/arm64/kernel/module-plts.c
+++ b/arch/arm64/kernel/module-plts.c
@@ -10,6 +10,9 @@
 #include <linux/moduleloader.h>
 #include <linux/sort.h>
 
+#include <asm/cpufeature.h>
+#include <asm/text-patching.h>
+
 static struct plt_entry __get_adrp_add_pair(u64 dst, u64 pc,
 					    enum aarch64_insn_register reg)
 {
@@ -23,14 +26,45 @@ static struct plt_entry __get_adrp_add_pair(u64 dst, u64 pc,
 	return (struct plt_entry){ cpu_to_le32(adrp), cpu_to_le32(add) };
 }
 
+static bool plt_target_has_landing_pad(u64 dst)
+{
+	u32 insn;
+
+	if (!system_supports_bti_kernel())
+		return true;
+
+	if (aarch64_insn_read((void *)dst, &insn))
+		return false;
+
+	if (!aarch64_insn_is_hint(insn))
+		return false;
+
+	switch (insn & 0xFE0) {
+	case AARCH64_INSN_HINT_BTIC:
+	case AARCH64_INSN_HINT_BTIJ:
+	case AARCH64_INSN_HINT_BTIJC:
+	case AARCH64_INSN_HINT_PACIASP:
+	case AARCH64_INSN_HINT_PACIBSP:
+		return true;
+	}
+
+	return false;
+}
+
 struct plt_entry get_plt_entry(u64 dst, void *pc)
 {
+	enum aarch64_insn_branch_type type;
 	struct plt_entry plt;
-	static u32 br;
+	u32 br;
 
-	if (!br)
-		br = aarch64_insn_gen_branch_reg(AARCH64_INSN_REG_16,
-						 AARCH64_INSN_BRANCH_NOLINK);
+	/*
+	 * Livepatch modules can branch to static functions without landing
+	 * pads, in which case RET is needed.
+	 */
+	type = plt_target_has_landing_pad(dst) ? AARCH64_INSN_BRANCH_NOLINK
+					       : AARCH64_INSN_BRANCH_RETURN;
+
+	br = aarch64_insn_gen_branch_reg(AARCH64_INSN_REG_16, type);
 
 	plt = __get_adrp_add_pair(dst, (u64)pc, AARCH64_INSN_REG_16);
 	plt.br = cpu_to_le32(br);
-- 
2.54.0
Re: [PATCH] arm64/module: Fix livepatch BTI exceptions with Clang 21+
Posted by Will Deacon 1 month, 3 weeks ago
On Fri, Aug 07, 2026 at 02:46:11PM -0700, Josh Poimboeuf wrote:
> The following BTI exception was seen when loading a livepatch module:
> 
>   Internal error: Oops - BTI: 0000000036000001 [#1]  SMP
>   pstate: 634004c9 (nZCv daIF +PAN -UAO +TCO +DIT -SSBS BTYPE=jc)
>   pc : kill_orphaned_pgrp+0x0/0x150
>   lr : do_exit+0x498/0xaf0 [livepatch_combined]
> 
> The problem is that the patch module's do_exit() is branching to a
> static function in vmlinux using a module PLT veneer (indirect branch),
> but the target function doesn't have a BTI landing pad.
> 
> Clang 21+ omits the landing pad for static functions which can only be
> reached by a direct branch.  That's fine for ordinary modules which only
> branch to global exported functions.  But livepatch modules use klp
> relocations to reference arbitrary kernel symbols, and with
> CONFIG_RANDOMIZE_MODULE_REGION_FULL the module is far enough from the
> kernel that every R_AARCH64_CALL26 needs a PLT.
> 
> RET is exempt from BTI checking, so use it instead of BR when the target
> has no landing pad, similar to what ftrace and BPF do.

Hmm, doesn't that somewhat undermine the purpose of using BTI in the
kernel? Now we're going to create PLTs that can branch to arbitrary
addresses.

> This was found by testing with klp-build and Clang 21, but the issue is
> not specific to klp-build.  It's inherent to any livepatch module use of
> klp relocations.
> 
> Previous tests with Clang 20 did not show this problem, as older Clang
> unconditionally emits "bti c" for every C function.

Is there an option to restore that behaviour if CONFIG_LIVEPATCH=y?
Otherwise, I think I'd be more inclined to add yet-another dependency
to CONFIG_ARM64_BTI_KERNEL so it's disabled if LIVEPATCH is selected.

Will
Re: [PATCH] arm64/module: Fix livepatch BTI exceptions with Clang 21+
Posted by Mark Brown 1 month, 2 weeks ago
On Mon, Aug 10, 2026 at 11:31:10AM +0100, Will Deacon wrote:
> On Fri, Aug 07, 2026 at 02:46:11PM -0700, Josh Poimboeuf wrote:

> > The following BTI exception was seen when loading a livepatch module:

> >   Internal error: Oops - BTI: 0000000036000001 [#1]  SMP
> >   pstate: 634004c9 (nZCv daIF +PAN -UAO +TCO +DIT -SSBS BTYPE=jc)
> >   pc : kill_orphaned_pgrp+0x0/0x150
> >   lr : do_exit+0x498/0xaf0 [livepatch_combined]

> > This was found by testing with klp-build and Clang 21, but the issue is
> > not specific to klp-build.  It's inherent to any livepatch module use of
> > klp relocations.

> > Previous tests with Clang 20 did not show this problem, as older Clang
> > unconditionally emits "bti c" for every C function.

> Is there an option to restore that behaviour if CONFIG_LIVEPATCH=y?
> Otherwise, I think I'd be more inclined to add yet-another dependency
> to CONFIG_ARM64_BTI_KERNEL so it's disabled if LIVEPATCH is selected.

This won't even be livepatch specific I think - I expect the same issue
can be triggered for module loading by using annotations to place
functions from the same translation unit in different sections if the
sections get placed far enough apart.  That was what was impacting GCC.
Re: [PATCH] arm64/module: Fix livepatch BTI exceptions with Clang 21+
Posted by Josh Poimboeuf 1 month, 2 weeks ago
On Mon, Aug 10, 2026 at 11:31:10AM +0100, Will Deacon wrote:
> On Fri, Aug 07, 2026 at 02:46:11PM -0700, Josh Poimboeuf wrote:
> > The following BTI exception was seen when loading a livepatch module:
> > 
> >   Internal error: Oops - BTI: 0000000036000001 [#1]  SMP
> >   pstate: 634004c9 (nZCv daIF +PAN -UAO +TCO +DIT -SSBS BTYPE=jc)
> >   pc : kill_orphaned_pgrp+0x0/0x150
> >   lr : do_exit+0x498/0xaf0 [livepatch_combined]
> > 
> > The problem is that the patch module's do_exit() is branching to a
> > static function in vmlinux using a module PLT veneer (indirect branch),
> > but the target function doesn't have a BTI landing pad.
> > 
> > Clang 21+ omits the landing pad for static functions which can only be
> > reached by a direct branch.  That's fine for ordinary modules which only
> > branch to global exported functions.  But livepatch modules use klp
> > relocations to reference arbitrary kernel symbols, and with
> > CONFIG_RANDOMIZE_MODULE_REGION_FULL the module is far enough from the
> > kernel that every R_AARCH64_CALL26 needs a PLT.
> > 
> > RET is exempt from BTI checking, so use it instead of BR when the target
> > has no landing pad, similar to what ftrace and BPF do.
> 
> Hmm, doesn't that somewhat undermine the purpose of using BTI in the
> kernel? Now we're going to create PLTs that can branch to arbitrary
> addresses.

Yes, but just to clarify:

- Only with livepatch modules loaded (and we can add an
  is_livepatch_module() check).

- Only a small minority of livepatch klp relocations need it.

- There are already other instances of "ret <reg>" in the kernel in
  ftrace, BPF, and kvm.

> > This was found by testing with klp-build and Clang 21, but the issue is
> > not specific to klp-build.  It's inherent to any livepatch module use of
> > klp relocations.
> > 
> > Previous tests with Clang 20 did not show this problem, as older Clang
> > unconditionally emits "bti c" for every C function.
> 
> Is there an option to restore that behaviour if CONFIG_LIVEPATCH=y?
> Otherwise, I think I'd be more inclined to add yet-another dependency
> to CONFIG_ARM64_BTI_KERNEL so it's disabled if LIVEPATCH is selected.

Hm, looking deeper, is BTI just fundamentally broken now, independent of
livepatch?

config ARM64_BTI_KERNEL
	...
	# https://gcc.gnu.org/bugzilla/show_bug.cgi?id=106671
	depends on !CC_IS_GCC
	...

AFAICT, the reason for the "depends on !CC_IS_GCC" is that GCC was
already doing the exact same thing Clang is now doing: namely, omitting
BTI for static functions that don't have a pointer taken to them.

So Clang 21+ now has the original GCC edge case: an .init.text direct
branching to a .text function which happens to be allocated >= 128MB
away and which doesn't have BTI.

In which case I think to properly support BTI going forward we would
need two "veneers"?  Either that or remove BTI kernel support
altogether.

-- 
Josh
Re: [PATCH] arm64/module: Fix livepatch BTI exceptions with Clang 21+
Posted by sashiko-bot@kernel.org 1 month, 3 weeks ago
Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:

Pre-existing issues:
- [High] Direct assignment to read-only memory in `module_emit_plt_entry()` and `module_emit_veneer_for_adrp()` causes a Data Abort kernel panic when resolving livepatch delayed relocations, and misses instruction cache flushing.
--

--- Patch [1]: [PATCH] arm64/module: Fix livepatch BTI exceptions with Clang 21+ ---
commit b15500d2c41dfd08d4a46e3f6cffc977c910f40a
Author: Josh Poimboeuf <jpoimboe@kernel.org>

arm64/module: Fix livepatch BTI exceptions with Clang 21+

This commit fixes a BTI exception during livepatch module loading by using
RET instead of BR when branching to static functions that lack a BTI landing
pad. It introduces a check for landing pads and adjusts the branch
instruction emitted by get_plt_entry().

> diff --git a/arch/arm64/kernel/module-plts.c b/arch/arm64/kernel/module-plts.c
> index 7afd370da9f48..4249b477daa8b 100644
> --- a/arch/arm64/kernel/module-plts.c
> +++ b/arch/arm64/kernel/module-plts.c

[ ... ]

> @@ -23,14 +26,45 @@ static struct plt_entry __get_adrp_add_pair(u64 dst, u64 pc,
>  	return (struct plt_entry){ cpu_to_le32(adrp), cpu_to_le32(add) };
>  }
>  
> +static bool plt_target_has_landing_pad(u64 dst)
> +{

[Severity: High]
This is a pre-existing issue, but does direct assignment to the PLT memory
region cause a Data Abort when livepatch resolves delayed relocations?

When a target module is loaded after a livepatch is active,
apply_relocate_add() calls module_emit_plt_entry() on the livepatch module.
Because the livepatch module is already in a live state, its .plt section
has been marked read-only via strict_module_rwx().

Looking at module_emit_plt_entry() in arch/arm64/kernel/module-plts.c:

module_emit_plt_entry() {
    ...
    plt[i] = get_plt_entry(val, &plt[i]);
    ...
}

And similarly in module_emit_veneer_for_adrp():

module_emit_veneer_for_adrp() {
    ...
    plt[i] = __get_adrp_add_pair(val, (u64)&plt[i], rd);
    plt[i].br = cpu_to_le32(br);
    ...
}

Since this direct assignment bypasses __text_poke() or aarch64_insn_copy(),
does it trigger a panic due to writing to read-only memory, and also fail
to flush the instruction cache for the newly emitted PLT entry?

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/ed4fe1f95071897859ec7fbe9176246cbd4962bf.1786138806.git.jpoimboe@kernel.org?part=1