Re: [PATCH v2 1/2] arm64: module: Emit BTI veneers for cross-section calls

From: Catalin Marinas

Date: Wed Nov 04 2026 - 11:33:31 EST


On Mon, Oct 05, 2026 at 01:05:13PM +0200, Ard Biesheuvel wrote:
> From: Ard Biesheuvel <ardb@xxxxxxxxxx>
>
> The compiler is permitted to omit BTI landing pads from static functions
> that never have their address taken, but are only called directly, even
> if those calls originate from other code sections.
>
> This means that calls into a module's .text section from .init.text,
> which may need to be routed via a PLT if .text is out of direct
> branching range, may result in BTI exceptions due to the indirect calls
> performed by the PLT veneers. (Note that calls to .init.text from .text
> are not allowed.)
>
> The 'solution' is to emit yet another veneer - this is what the ELF
> psABI for AArch64 mandates in this case.
>
> So derive an upper bound for the number of veneers that may be needed in
> the core module region to ensure that any call from init code that ends
> up needing a PLT can be directed at a veneer with a BTI landing pad, and
> allocate the additional space.
>
> Then, emit these veneers as needed, i.e., only when emitting a PLT entry
> for a call from an init code section to a normal code section in the
> same module. In practice, this only occurs when a module's .init.text
> happens to be allocated far away from its .text section, which might
> happen when the initial 128M 'near' module region runs out of space
> between allocating the core module and allocating its init region.
>
> Signed-off-by: Ard Biesheuvel <ardb@xxxxxxxxxx>
> ---
> arch/arm64/Kconfig | 7 +-
> arch/arm64/include/asm/module.h | 12 ++
> arch/arm64/include/asm/module.lds.h | 3 +
> arch/arm64/kernel/module-plts.c | 128 +++++++++++++++++++-
> 4 files changed, 140 insertions(+), 10 deletions(-)
>
> diff --git a/arch/arm64/Kconfig b/arch/arm64/Kconfig
> index cab741a695f5..23879cb05d6b 100644
> --- a/arch/arm64/Kconfig
> +++ b/arch/arm64/Kconfig
> @@ -210,7 +210,8 @@ config ARM64
> select HAVE_HW_BREAKPOINT if PERF_EVENTS
> select HAVE_IOREMAP_PROT
> select HAVE_IRQ_TIME_ACCOUNTING
> - select HAVE_LIVEPATCH
> + # https://lore.kernel.org/all/20260812162058.612202-5-ardb@xxxxxxxxxx/
> + select HAVE_LIVEPATCH if !ARM64_BTI_KERNEL
> select HAVE_MOD_ARCH_SPECIFIC
> select HAVE_NMI
> select HAVE_PERF_EVENTS
> @@ -2198,10 +2199,6 @@ config ARM64_BTI_KERNEL
> depends on CC_HAS_BRANCH_PROT_PAC_RET_BTI
> # https://gcc.gnu.org/bugzilla/show_bug.cgi?id=94697
> depends on !CC_IS_GCC || GCC_VERSION >= 100100
> - # https://gcc.gnu.org/bugzilla/show_bug.cgi?id=106671
> - depends on !CC_IS_GCC
> - # https://github.com/llvm/llvm-project/issues/215547
> - depends on !CC_IS_CLANG || CLANG_VERSION < 210000
> depends on (!FUNCTION_GRAPH_TRACER || DYNAMIC_FTRACE_WITH_ARGS)
> help
> Build the kernel with Branch Target Identification annotations

I think this should be the other way around (and keep HAVE_LIVEPATCH
unconditional):

config ARM64_BTI_KERNEL
...
depends on !LIVEPATCH || (CC_IS_CLANG && CLANG_VERSION < 210000)

I also wonder whether dropping the compiler dependency should come with
some sanity check/assert for (the unlikely case) of a large vmlinux,
similar to one of Josh's patches or a boot-time check that skips
enabling BTI. At least until we get some generic compiler/linker
workaround for large images:

https://lore.kernel.org/all/8932e59a4f6e8383f37078dada1f23639b85a035.1786768375.git.jpoimboe@xxxxxxxxxx/

There are some Sashiko comments. I'd ignore the big-endian ones since we
removed it and it had been marked broken for some time anyway. But the
other points look valid.

https://sashiko.dev/#/patchset/20261005110511.157016-4-ardb+git@xxxxxxxxxx

Apart from those, I don't have other technical objections to the patch
but I wonder whether we'll find alternative options going forward that
make this fix redundant. E.g. allocating .init.text and .text together
(you had an RFC) or a new execmem_alloc_near() to try to place them
nearby.

In the meantime, would something like the following work for us instead
of the veneers? We won't free the .init.text module section but this
should be small. There's the ppc64 precedent.

bool module_init_section(const char *name)
{
if (system_supports_bti_kernel() && strstarts(name, ".init.text"))
return false;

return strstarts(name, ".init");
}

--
Catalin