objtool/arm64: Port klp-build to arm64
Subsystems (8)
Josh Poimboeuf
maintainerv3 cover objtool/arm64: Port klp-build to arm64
Based on tip/objtool/core.
v3:
- Too many changes to list. Did a lot of testing and fixed a bunch of issues (many of which have already been merged in tip/objtool/core).
v2: https://lore.kernel.org/[email protected]
- patches 1-2 were merged, rebase on tip/master
- improve commit message for "objtool: Extricate checksum calculation from validate_branch()"
- add review tags
v1: https://lore.kernel.org/[email protected]
Port objtool and the klp-build tooling (for building livepatch modules) to arm64.
Note this doesn't bring all the objtool bells and whistles to arm64, nor any of the CFG reverse engineering. This only adds the bare minimum needed for 'objtool --checksum'.
And note that objtool still doesn't get enabled at all for normal arm64 kernel builds, so this doesn't affect any users other than those running klp-build directly.
Josh Poimboeuf (21):
klp-build: Reject patches to init/*.c
arm64: Annotate intra-function calls
arm64: Fix EFI linking with -fdata-sections
arm64: Rename TRAMP_VALIAS -> TRAMP_VALIAS_ASM in asm-offsets
arm64: vdso: Discard .discard.* sections
arm64: Annotate special section entries
crypto: arm64: Move data to .rodata
objtool: Allow setting --mnop without --mcount
kbuild: Only run objtool if there is at least one command
objtool: Ignore jumps to the end of the function for checksum runs
objtool: Allow empty alternatives
objtool: Refactor elf_add_data() to use a growable data buffer
objtool: Reuse string references
objtool: Prevent kCFI hashes from being decoded as instructions objtool/klp: Add arm64 support for prefix/PFE detection objtool/klp: Filter arm64 mapping symbols in find_symbol_by_offset() objtool/klp: Don't correlate arm64 mapping symbols objtool/klp: Clone inline alternative replacements objtool/klp: Introduce objtool for arm64
klp-build: Support cross-compilation
klp-build: Add arm64 syscall patching macro
arch/arm64/Kconfig | 2 + arch/arm64/include/asm/alternative-macros.h | 27 +- arch/arm64/include/asm/asm-bug.h | 2 + arch/arm64/include/asm/asm-extable.h | 21 +- arch/arm64/include/asm/jump_label.h | 2 + arch/arm64/kernel/asm-offsets.c | 7 +- arch/arm64/kernel/entry.S | 10 +- arch/arm64/kernel/proton-pack.c | 12 +- arch/arm64/kernel/vdso/vdso.lds.S | 1 + arch/arm64/kernel/vmlinux.lds.S | 2 +- arch/x86/boot/startup/Makefile | 2 +- include/linux/annotate.h | 14 +- include/linux/livepatch_helpers.h | 19 ++ include/linux/objtool_types.h | 1 + lib/crypto/arm64/sha2-armv8.pl | 18 +- scripts/Makefile.build | 4 +- scripts/Makefile.lib | 52 ++-- scripts/Makefile.vmlinux_o | 15 +- scripts/livepatch/klp-build | 24 +- tools/include/linux/objtool_types.h | 1 + tools/objtool/Makefile | 4 + tools/objtool/arch/arm64/Build | 2 + tools/objtool/arch/arm64/decode.c | 177 +++++++++++++ .../arch/arm64/include/arch/cfi_regs.h | 11 + tools/objtool/arch/arm64/include/arch/elf.h | 15 ++ .../objtool/arch/arm64/include/arch/special.h | 21 ++ tools/objtool/arch/arm64/special.c | 21 ++ tools/objtool/arch/x86/include/arch/elf.h | 2 + tools/objtool/builtin-check.c | 5 - tools/objtool/check.c | 65 +++-- tools/objtool/elf.c | 170 +++++++------ tools/objtool/include/objtool/elf.h | 48 +++- tools/objtool/klp-diff.c | 237 ++++++++++++++++-- 33 files changed, 819 insertions(+), 195 deletions(-) create mode 100644 tools/objtool/arch/arm64/Build create mode 100644 tools/objtool/arch/arm64/decode.c create mode 100644 tools/objtool/arch/arm64/include/arch/cfi_regs.h create mode 100644 tools/objtool/arch/arm64/include/arch/elf.h create mode 100644 tools/objtool/arch/arm64/include/arch/special.h create mode 100644 tools/objtool/arch/arm64/special.c
Signature · 1 line
2.53.0
Josh Poimboeuf
maintainerPATCH v3 1/21 klp-build: Reject patches to init/*.c
init/Makefile hard-codes -fno-function-sections and -fno-data-sections, overriding the klp-build flags needed for patch generation.
Don't allow any changes to those files; being init code they aren't really patchable anyway.
--- scripts/livepatch/klp-build | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-)
scripts/livepatch/klp-build +1 -1
@@ -362,7 +362,7 @@ check_unsupported_patches() { for file in "${files[@]}"; do case "$file" in - lib/*|*/vdso/*|*/realmode/rm/*|*.S) + lib/*|*/vdso/*|*/realmode/rm/*|init/*|*.S) die "${patch}: unsupported patch to $file" ;; esac
Signature · 1 line
2.53.0
Re: [PATCH v3 01/21] klp-build: Reject patches to init/*.c
Quoted 8 lines · Josh Poimboeuf
init/Makefile hard-codes -fno-function-sections and -fno-data-sections, overriding the klp-build flags needed for patch generation.
Don't allow any changes to those files; being init code they aren't really patchable anyway.
Signed-off-by Josh Poimboeuf <[email protected]>
Re: [PATCH v3 01/21] klp-build: Reject patches to init/*.c
In reply to Josh PoimboeufQuoted 8 lines · Josh Poimboeuf
init/Makefile hard-codes -fno-function-sections and -fno-data-sections, overriding the klp-build flags needed for patch generation.
Don't allow any changes to those files; being init code they aren't really patchable anyway.
[...]
Signature · 1 line
Miroslav
Josh Poimboeuf
maintainerPATCH v3 2/21 arm64: Annotate intra-function calls
In reply to Josh PoimboeufIn preparation for enabling objtool on arm64, annotate intra-function calls.
--- arch/arm64/kernel/entry.S | 2 ++ arch/arm64/kernel/proton-pack.c | 12 +++++++----- 2 files changed, 9 insertions(+), 5 deletions(-)
arch/arm64/kernel/entry.S +2 -0
@@ -10,6 +10,7 @@ #include <linux/arm-smccc.h> #include <linux/init.h> #include <linux/linkage.h> +#include <linux/annotate.h> #include <asm/alternative.h> #include <asm/assembler.h>
@@ -705,6 +706,7 @@ alternative_else_nop_endif * entry onto the return stack and using a RET instruction to * enter the full-fat kernel vectors. */ + ANNOTATE_INTRA_FUNCTION_CALL bl 2f b . 2:
arch/arm64/kernel/proton-pack.c +7 -5
@@ -24,6 +24,7 @@#include <linux/nospec.h> #include <linux/prctl.h> #include <linux/sched/task_stack.h> +#include <linux/annotate.h> #include <asm/debug-monitors.h> #include <asm/insn.h>
@@ -245,11 +246,12 @@ static noinstr void qcom_link_stack_sanitisation(void) {u64 tmp; - asmvolatile ("mov %0, x30 \n" -".rept 16 \n" -"bl . + 4 \n" -".endr \n" -"mov x30, %0 \n" + asmvolatile ("mov %0, x30 \n" +".rept 16 \n" +ANNOTATE_INTRA_FUNCTION_CALL " \n" +"bl . + 4 \n" +".endr \n" +"mov x30, %0 \n" :"=&r" (tmp)); }
Signature · 1 line
2.53.0
Re: [PATCH v3 02/21] arm64: Annotate intra-function calls
Quoted 2 lines · Josh Poimboeuf
In preparation for enabling objtool on arm64, annotate intra-function calls.
Signature · 1 line
Miroslav
Josh Poimboeuf
maintainerPATCH v3 3/21 arm64: Fix EFI linking with -fdata-sections
In reply to Josh PoimboeufWhen building with -fdata-sections, the .init.bss section gets split up into a bunch of .init.bss.<var> sections. Make sure they get linked into .init.data.
--- arch/arm64/kernel/vmlinux.lds.S | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-)
arch/arm64/kernel/vmlinux.lds.S +1 -1
@@ -281,7 +281,7 @@ SECTIONS INIT_CALLS CON_INITCALL INIT_RAM_FS - *(.init.altinstructions .init.bss) /* from the EFI stub */ + *(.init.altinstructions .init.bss .init.bss.*) /* from the EFI stub */ } .exit.data : { EXIT_DATA
Signature · 1 line
2.53.0
Re: [PATCH v3 03/21] arm64: Fix EFI linking with -fdata-sections
Quoted 3 lines · Josh Poimboeuf
When building with -fdata-sections, the .init.bss section gets split up into a bunch of .init.bss.<var> sections. Make sure they get linked into .init.data.
Signature · 1 line
Miroslav
Josh Poimboeuf
maintainerPATCH v3 4/21 arm64: Rename TRAMP_VALIAS -> TRAMP_VALIAS_ASM in asm-offsets
In reply to Josh PoimboeufRename the asm-offsets TRAMP_VALIAS macro to TRAMP_VALIAS_ASM, following the naming convention already used by PIE_E0_ASM and PIE_E1_ASM. This disambiguates the asm-offsets-generated constant from the C macro of the same name defined in fixmap.h and vectors.h.
This is needed by a later patch which adds new includes to asm-offsets.c that would otherwise conflict with the C version.
--- arch/arm64/kernel/asm-offsets.c | 2 +- arch/arm64/kernel/entry.S | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-)
arch/arm64/kernel/asm-offsets.c +1 -1
@@ -153,7 +153,7 @@ int main(void)DEFINE (ARM64_FTR_SYSVAL , offsetof(struct arm64_ftr_reg , sys_val));BLANK ();#ifdef CONFIG_UNMAP_KERNEL_AT_EL0 -DEFINE (TRAMP_VALIAS ,TRAMP_VALIAS ); +DEFINE (TRAMP_VALIAS_ASM ,TRAMP_VALIAS );#endif #ifdef CONFIG_ARM_SDE_INTERFACE DEFINE (SDEI_EVENT_INTREGS , offsetof(struct sdei_registered_event , interrupted_regs));
arch/arm64/kernel/entry.S +4 -4
@@ -102,7 +102,7 @@ .endm .macro tramp_alias, dst, sym - .set .Lalias\@, TRAMP_VALIAS + \sym - .entry.tramp.text + .set .Lalias\@, TRAMP_VALIAS_ASM + \sym - .entry.tramp.text movz \dst, :abs_g2_s:.Lalias\@ movk \dst, :abs_g1_nc:.Lalias\@ movk \dst, :abs_g0_nc:.Lalias\@
@@ -626,10 +626,10 @@ SYM_CODE_END(ret_to_user) #ifdef CONFIG_QCOM_FALKOR_ERRATUM_1003 alternative_if ARM64_WORKAROUND_QCOM_FALKOR_E1003 /* ASID already in \tmp[63:48] */ - movk \tmp, #:abs_g2_nc:(TRAMP_VALIAS >> 12) - movk \tmp, #:abs_g1_nc:(TRAMP_VALIAS >> 12) + movk \tmp, #:abs_g2_nc:(TRAMP_VALIAS_ASM >> 12) + movk \tmp, #:abs_g1_nc:(TRAMP_VALIAS_ASM >> 12) /* 2MB boundary containing the vectors, so we nobble the walk cache */ - movk \tmp, #:abs_g0_nc:((TRAMP_VALIAS & ~(SZ_2M - 1)) >> 12) + movk \tmp, #:abs_g0_nc:((TRAMP_VALIAS_ASM & ~(SZ_2M - 1)) >> 12) isb tlbi vae1, \tmp dsb nsh
Signature · 1 line
2.53.0
Re: [PATCH v3 04/21] arm64: Rename TRAMP_VALIAS -> TRAMP_VALIAS_ASM in asm-offsets
Quoted 10 lines · Josh Poimboeuf
Rename the asm-offsets
TRAMP_VALIASmacro toTRAMP_VALIAS_ASM, following the naming convention already used byPIE_E0_ASMandPIE_E1_ASM. This disambiguates the asm-offsets-generated constant from the C macro of the same name defined infixmap.handvectors.h.This is needed by a later patch which adds new includes to
asm-offsets.cthat would otherwise conflict with the C version.Signed-off-by Josh Poimboeuf <[email protected]>
Re: [PATCH v3 04/21] arm64: Rename TRAMP_VALIAS -> TRAMP_VALIAS_ASM in asm-offsets
In reply to Josh PoimboeufQuoted 9 lines · Josh Poimboeuf
Rename the asm-offsets
TRAMP_VALIASmacro toTRAMP_VALIAS_ASM, following the naming convention already used byPIE_E0_ASMandPIE_E1_ASM. This disambiguates the asm-offsets-generated constant from the C macro of the same name defined infixmap.handvectors.h.This is needed by a later patch which adds new includes to
asm-offsets.cthat would otherwise conflict with the C version.[...]
Signature · 1 line
Miroslav
Josh Poimboeuf
maintainerPATCH v3 5/21 arm64: vdso: Discard .discard.* sections
In reply to Josh PoimboeufIn preparation for enabling objtool on arm64, add .discard.* to the vDSO's /DISCARD/ section so objtool annotations don't cause orphan section warnings or leak into the final vDSO binary.
--- arch/arm64/kernel/vdso/vdso.lds.S | 1 + 1 file changed, 1 insertion(+)
arch/arm64/kernel/vdso/vdso.lds.S +1 -0
@@ -39,6 +39,7 @@ SECTIONS /DISCARD/ : { *(.note.GNU-stack .note.gnu.property) *(.ARM.attributes) + *(.discard.*) } .note : { *(.note.*) } :text :note
Signature · 1 line
2.53.0
Josh Poimboeuf
maintainerPATCH v3 6/21 arm64: Annotate special section entries
In reply to Josh PoimboeufIn preparation for adding arm64 support for "objtool klp checksum/diff" to enable livepatch module generation, annotate special section entries.
This will allow objtool to determine the size and location of the entries and to extract them when needed.
A new ANNOTATE_DATA_SPECIAL_END annotation is added to mark the end of special data blocks, which is needed because arm64's replacement instructions are emitted in .text rather than .altinstr_replacement, so there's otherwise no way to determine where the last replacement block ends.
--- arch/arm64/include/asm/alternative-macros.h | 27 ++++++++++++++++----- arch/arm64/include/asm/asm-bug.h | 2 ++ arch/arm64/include/asm/asm-extable.h | 21 ++++++++++------ arch/arm64/include/asm/jump_label.h | 2 ++ arch/arm64/kernel/asm-offsets.c | 5 ++++ include/linux/annotate.h | 14 ++++++++++- include/linux/objtool_types.h | 1 + tools/include/linux/objtool_types.h | 1 + tools/objtool/klp-diff.c | 5 +++- 9 files changed, 62 insertions(+), 16 deletions(-)
arch/arm64/include/asm/alternative-macros.h +21 -6
@@ -3,11 +3,16 @@#define __ASM_ALTERNATIVE_MACROS_H#include <linux/const.h> +#include <linux/annotate.h> #include <vdso/bits.h> #include <asm/cpucaps.h> #include <asm/insn-def.h> +#ifndef COMPILE_OFFSETS +#include <asm/asm-offsets.h> +#endif +/* * Binutils 2.27.0 can't handle a 'UL' suffix on constants, so for the assembly * macros below we must use we must use `(1 << ARM64_CB_SHIFT)`.
@@ -58,15 +63,18 @@"661:\n\t" \ oldinstr"\n" \"662:\n" \ -".pushsection .altinstructions,\"a\"\n" \ +".pushsection .altinstructions,\"aM\", @progbits, " \ + __stringify(ALT_INSTR_SIZE) "\n" \ ALTINSTR_ENTRY(cpucap) \".popsection\n" \".subsection 1\n" \ +ANNOTATE_DATA_SPECIAL "\n" \"663:\n\t" \ newinstr"\n" \"664:\n\t" \".org . - (664b-663b) + (662b-661b)\n\t" \".org . - (662b-661b) + (664b-663b)\n\t" \ +ANNOTATE_DATA_SPECIAL_END "\n\t" \".previous\n" \".endif\n"
@@ -75,7 +83,8 @@"661:\n\t" \ oldinstr"\n" \"662:\n" \ -".pushsection .altinstructions,\"a\"\n" \ +".pushsection .altinstructions,\"aM\", @progbits, " \ + __stringify(ALT_INSTR_SIZE) "\n" \ ALTINSTR_ENTRY_CB(cpucap, cb) \".popsection\n" \"663:\n\t" \
@@ -102,13 +111,15 @@ .macro alternative_insn insn1, insn2, cap, enable = 1 .if \enable 661: \insn1 -662: .pushsection .altinstructions, "a" +662: .pushsection .altinstructions, "aM", @progbits, ALT_INSTR_SIZE altinstruction_entry 661b, 663f, \cap, 662b-661b, 664f-663f .popsection .subsection 1 + ANNOTATE_DATA_SPECIAL 663: \insn2 664: .org . - (664b-663b) + (662b-661b) .org . - (662b-661b) + (664b-663b) +ANNOTATE_DATA_SPECIAL_END .previous .endif .endm
@@ -137,7 +148,7 @@*/ .macro alternative_if_not cap .set .Lasm_alt_mode,0 - .pushsection .altinstructions,"a" + .pushsection .altinstructions, "aM", @progbits, ALT_INSTR_SIZE altinstruction_entry 661f, 663f, \cap, 662f-661f, 664f-663f .popsection 661:
@@ -145,17 +156,18 @@ .macro alternative_if cap .set .Lasm_alt_mode,1 - .pushsection .altinstructions,"a" + .pushsection .altinstructions, "aM", @progbits, ALT_INSTR_SIZE altinstruction_entry 663f, 661f, \cap, 664f-663f, 662f-661f .popsection .subsection 1 .align 2 /* So GAS knows label 661 is suitably aligned */ + ANNOTATE_DATA_SPECIAL 661: .endm .macro alternative_cb cap, cb .set .Lasm_alt_mode,0 - .pushsection .altinstructions,"a" + .pushsection .altinstructions, "aM", @progbits, ALT_INSTR_SIZE altinstruction_entry 661f, \cb, (1 << ARM64_CB_SHIFT) | \cap, 662f-661f, 0 .popsection 661:
@@ -168,7 +180,9 @@ 662: .if .Lasm_alt_mode==0 .subsection 1 + ANNOTATE_DATA_SPECIAL .else +ANNOTATE_DATA_SPECIAL_END .previous .endif 663:
@@ -182,6 +196,7 @@ .org . - (664b-663b) + (662b-661b) .org . - (662b-661b) + (664b-663b) .if .Lasm_alt_mode==0 +ANNOTATE_DATA_SPECIAL_END .previous .endif .endm
arch/arm64/include/asm/asm-bug.h +2 -0
@@ -5,6 +5,7 @@*/ #define __ASM_ASM_BUG_H +#include <linux/annotate.h> #include <asm/brk-imm.h> #ifdef CONFIG_DEBUG_BUGVERBOSE
@@ -24,6 +25,7 @@#define __BUG_ENTRY_START \ .pushsection __bug_table,"aw"; \ .align 2; \ + __ANNOTATE_DATA_SPECIAL; \ 14470: .long 14471f - .; \#define __BUG_ENTRY_END \
arch/arm64/include/asm/asm-extable.h +13 -8
@@ -5,6 +5,10 @@#include <linux/bits.h> #include <asm/gpr-num.h> +#ifndef COMPILE_OFFSETS +#include <asm/asm-offsets.h> +#endif +#define EX_TYPE_NONE 0#define EX_TYPE_BPF 1#define EX_TYPE_UACCESS_ERR_ZERO 2
@@ -29,13 +33,13 @@#ifdef __ASSEMBLER__ -#define __ASM_EXTABLE_RAW (insn, fixup, type, data) \ - .pushsection __ex_table, "a"; \ - .align 2; \ - .long ((insn) - .); \ - .long ((fixup) - .); \ - .short (type); \ - .short (data); \ +#define __ASM_EXTABLE_RAW (insn, fixup, type, data) \ + .pushsection __ex_table, "aM", @progbits, EXTABLE_SIZE; \ + .align 2; \ + .long ((insn) - .); \ + .long ((fixup) - .); \ + .short (type); \ + .short (data); \ .popsection;#define EX_DATA_REG (reg, gpr) \
@@ -82,7 +86,8 @@#include <linux/stringify.h> #define __ASM_EXTABLE_RAW (insn, fixup, type, data) \ - ".pushsection __ex_table, \"a\"\n" \ + ".pushsection __ex_table, \"aM\", @progbits, "\ + __stringify(EXTABLE_SIZE) "\n" \ ".align 2\n" \ ".long ((" insn ") - .)\n" \ ".long ((" fixup ") - .)\n" \
arch/arm64/include/asm/jump_label.h +2 -0
@@ -11,6 +11,7 @@#ifndef __ASSEMBLER__#include <linux/types.h> +#include <linux/annotate.h> #include <asm/insn.h> #define HAVE_JUMP_LABEL_BATCH
@@ -19,6 +20,7 @@#define JUMP_TABLE_ENTRY (key, label) \ ".pushsection __jump_table, \"aw\"\n\t" \ ".align 3\n\t" \ + ANNOTATE_DATA_SPECIAL "\n\t" \ ".long 1b - ., " label " - .\n\t" \ ".quad " key " - .\n\t" \ ".popsection\n\t"
arch/arm64/kernel/asm-offsets.c +5 -0
@@ -23,6 +23,8 @@#include <asm/suspend.h> #include <linux/kbuild.h> #include <linux/arm-smccc.h> +#include <asm/alternative.h> +#include <asm/extable.h> int main (void ) {
@@ -185,5 +187,8 @@ int main(void)#endif DEFINE (PIE_E0_ASM ,PIE_E0 );DEFINE (PIE_E1_ASM ,PIE_E1 ); +BLANK (); +DEFINE (ALT_INSTR_SIZE ,sizeof (struct alt_instr )); +DEFINE (EXTABLE_SIZE ,sizeof (struct exception_table_entry ));return 0 ; }
include/linux/annotate.h +13 -1
@@ -3,6 +3,7 @@#define _LINUX_ANNOTATE_H#include <linux/objtool_types.h> +#include <linux/stringify.h> #ifdef CONFIG_OBJTOOL
@@ -11,6 +12,10 @@ .long label - ., type; \ .popsection +#define __ASM_ANNOTATE_DATA (type) \ +912: \ + __ASM_ANNOTATE(.discard.annotate_data, 912b, type) +#ifndef __ASSEMBLY__#define ASM_ANNOTATE_LABEL (label, type) \
@@ -39,6 +44,9 @@#endif /* __ASSEMBLY__ */ #else /* !CONFIG_OBJTOOL */ + +#define __ASM_ANNOTATE_DATA (type) +#ifndef __ASSEMBLY__#define ASM_ANNOTATE_LABEL (label, type) ""#define ASM_ANNOTATE (type)
@@ -106,10 +114,12 @@#define ANNOTATE_NOCFI_SYM (sym) asm(ASM_ANNOTATE_LABEL(sym, ANNOTYPE_NOCFI))/* -* Annotate a special section entry. This emables livepatch module generation +* Annotate a special section entry. This enables livepatch module generation * to find and extract individual special section entries as needed. */ #define ANNOTATE_DATA_SPECIAL ASM_ANNOTATE_DATA(ANNOTYPE_DATA_SPECIAL) +#define __ANNOTATE_DATA_SPECIAL __ASM_ANNOTATE_DATA(ANNOTYPE_DATA_SPECIAL) +#define ANNOTATE_DATA_SPECIAL_END ASM_ANNOTATE_DATA(ANNOTYPE_DATA_SPECIAL_END)#else /* __ASSEMBLY__ */ #define ANNOTATE_NOENDBR ANNOTATE type=ANNOTYPE_NOENDBR
@@ -122,6 +132,8 @@#define ANNOTATE_REACHABLE ANNOTATE type=ANNOTYPE_REACHABLE#define ANNOTATE_NOCFI_SYM ANNOTATE type=ANNOTYPE_NOCFI#define ANNOTATE_DATA_SPECIAL ANNOTATE_DATA type=ANNOTYPE_DATA_SPECIAL +#define __ANNOTATE_DATA_SPECIAL __ASM_ANNOTATE_DATA(ANNOTYPE_DATA_SPECIAL) +#define ANNOTATE_DATA_SPECIAL_END ANNOTATE_DATA type=ANNOTYPE_DATA_SPECIAL_END#endif /* __ASSEMBLY__ */ #endif /* _LINUX_ANNOTATE_H */
include/linux/objtool_types.h +1 -0
@@ -68,5 +68,6 @@ struct unwind_hint {#define ANNOTYPE_NOCFI 9#define ANNOTYPE_DATA_SPECIAL 1 +#define ANNOTYPE_DATA_SPECIAL_END 2#endif /* _LINUX_OBJTOOL_TYPES_H */
tools/include/linux/objtool_types.h +1 -0
@@ -68,5 +68,6 @@ struct unwind_hint {#define ANNOTYPE_NOCFI 9#define ANNOTYPE_DATA_SPECIAL 1 +#define ANNOTYPE_DATA_SPECIAL_END 2#endif /* _LINUX_OBJTOOL_TYPES_H */
tools/objtool/klp-diff.c +4 -1
@@ -1667,7 +1667,10 @@ static int create_fake_symbols(struct elf *elf) size =0 ; next_reloc = reloc;for_each_reloc_continue (sec->rsec, next_reloc) { -if (annotype (elf, sec, next_reloc) !=ANNOTYPE_DATA_SPECIAL || +unsigned int next_type =annotype (elf, sec, next_reloc); + +if ((next_type !=ANNOTYPE_DATA_SPECIAL && + next_type !=ANNOTYPE_DATA_SPECIAL_END ) || next_reloc->sym->sec != reloc->sym->sec)continue ;
Signature · 1 line
2.53.0
Josh Poimboeuf
maintainerPATCH v3 7/21 crypto: arm64: Move data to .rodata
In reply to Josh PoimboeufData embedded in .text pollutes i-cache and confuses objtool and other tools that try to disassemble it. Move it to .rodata.
--- lib/crypto/arm64/sha2-armv8.pl | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-)
lib/crypto/arm64/sha2-armv8.pl +12 -6
@@ -207,12 +207,13 @@ $func: ___ $code.=<<___ if ($SZ==4); #ifndef __KERNEL__ + adrp x17,.LOPENSSL_armcap_P + add x17,x17,:lo12:.LOPENSSL_armcap_P # ifdef __ILP32__ - ldrsw x16,.LOPENSSL_armcap_P + ldrsw x16,[x17] # else - ldr x16,.LOPENSSL_armcap_P + ldr x16,[x17] # endif - adr x17,.LOPENSSL_armcap_P add x16,x16,x17 ldr w16,[x16] tst w16,#ARMV8_SHA256
@@ -237,7 +238,8 @@ $code.=<<___; ldp $E,$F,[$ctx,#4*$SZ] add $num,$inp,$num,lsl#`log(16*$SZ)/log(2)` // end of input ldp $G,$H,[$ctx,#6*$SZ] - adr $Ktbl,.LK$BITS + adrp $Ktbl,.LK$BITS + add $Ktbl,$Ktbl,:lo12:.LK$BITS stp $ctx,$num,[x29,#96] .Loop:
@@ -286,6 +288,7 @@ $code.=<<___; ret .size $func,.-$func +.pushsection .rodata .align 6 .type .LK$BITS,%object .LK$BITS:
@@ -365,6 +368,7 @@ $code.=<<___; #endif .asciz "SHA$BITS block transform for ARMv8, CRYPTOGAMS by <appro\@openssl.org>" .align 2 +.popsection ___ if ($SZ==4) {
@@ -385,7 +389,8 @@ sha256_block_armv8: add x29,sp,#0 ld1.32 {$ABCD,$EFGH},[$ctx] - adr $Ktbl,.LK256 + adrp $Ktbl,.LK256 + add $Ktbl,$Ktbl,:lo12:.LK256 .Loop_hw: ld1 {@MSG[0]-@MSG[3]},[$inp],#64
@@ -648,7 +653,8 @@ sha256_block_neon: mov x29, sp sub sp,sp,#16*4 - adr $Ktbl,.LK256 + adrp $Ktbl,.LK256 + add $Ktbl,$Ktbl,:lo12:.LK256 add $num,$inp,$num,lsl#6 // len to point at the end of inp ld1.8 {@X[0]},[$inp], #16
Signature · 1 line
2.53.0
Josh Poimboeuf
maintainerPATCH v3 8/21 objtool: Allow setting --mnop without --mcount
In reply to Josh PoimboeufInstead of returning an error for --mnop without --mcount, just silently ignore it. This will help simplify kbuild's handling of objtool args.
--- tools/objtool/builtin-check.c | 5 ----- 1 file changed, 5 deletions(-)
tools/objtool/builtin-check.c +0 -5
@@ -144,11 +144,6 @@ int cmd_parse_options(int argc, const char **argv, const char * const usage[])static bool opts_valid (void ) { -if (opts.mnop && !opts.mcount) { -ERROR ("--mnop requires --mcount" ); -return false; - } -if (opts.noinstr && !opts.link) {ERROR ("--noinstr requires --link" );return false;
Signature · 1 line
2.53.0
Josh Poimboeuf
maintainerPATCH v3 9/21 kbuild: Only run objtool if there is at least one command
In reply to Josh PoimboeufSplit the objtool args into commands and options, such that if no commands have been enabled, objtool doesn't run.
This is in preparation for enabling objtool and klp-build for arm64.
--- arch/x86/boot/startup/Makefile | 2 +- scripts/Makefile.build | 4 +-- scripts/Makefile.lib | 52 ++++++++++++++++++---------------- scripts/Makefile.vmlinux_o | 15 ++++------ 4 files changed, 36 insertions(+), 37 deletions(-)
arch/x86/boot/startup/Makefile +1 -1
@@ -36,7 +36,7 @@ $(patsubst %.o,$(obj)/%.o,$(lib-y)): OBJECT_FILES_NON_STANDARD := y # relocations, even if other objtool actions are being deferred. # $(pi-objs): objtool-enabled = 1 -$(pi-objs): objtool-args = $(if $(delay-objtool),--dry-run,$(objtool-args-y)) --noabs +$(pi-objs): objtool-args = $(if $(delay-objtool),--dry-run,$(objtool-cmds-y) $(objtool-opts-y)) --noabs # # Confine the startup code by prefixing all symbols with __pi_ (for position
scripts/Makefile.build +2 -2
@@ -277,7 +277,7 @@ endif # CONFIG_FTRACE_MCOUNT_USE_RECORDMCOUNT is-standard-object = $(if $(filter-out y%, $(OBJECT_FILES_NON_STANDARD_$(target-stem).o)$(OBJECT_FILES_NON_STANDARD)n),$(is-kernel-object)) ifdef CONFIG_OBJTOOL -$(obj)/%.o: private objtool-enabled = $(if $(is-standard-object),$(if $(delay-objtool),$(is-single-obj-m),y)) +$(obj)/%.o: private objtool-enabled = $(and $(is-standard-object),$(objtool-cmds-y),$(if $(delay-objtool),$(is-single-obj-m),y)) endif ifneq ($(findstring 1, $(KBUILD_EXTRA_WARN)),)
@@ -501,7 +501,7 @@ define rule_ld_multi_m $(call cmd,gen_objtooldep) endef -$(multi-obj-m): private objtool-enabled := $(delay-objtool) +$(multi-obj-m): private objtool-enabled := $(if $(objtool-cmds-y),$(delay-objtool)) $(multi-obj-m): private part-of-module := y $(multi-obj-m): %.o: %.mod FORCE $(call if_changed_rule,ld_multi_m)
scripts/Makefile.lib +28 -24
65 lines not on this page · raw message
scripts/Makefile.vmlinux_o +5 -10
@@ -36,18 +36,13 @@ endif # For !delay-objtool + CONFIG_NOINSTR_VALIDATION, it runs on both translation # units and vmlinux.o, with the latter only used for noinstr/unret validation. -objtool-enabled := $(or $(delay-objtool),$(CONFIG_NOINSTR_VALIDATION)) - -ifeq ($(delay-objtool),y) -vmlinux-objtool-args-y += $(objtool-args-y) -else -vmlinux-objtool-args-$(CONFIG_OBJTOOL_WERROR) += --werror +ifneq ($(delay-objtool),y) +objtool-cmds-y = +objtool-opts-y += --link endif -vmlinux-objtool-args-$(CONFIG_NOINSTR_VALIDATION) += --noinstr \ - $(if $(or $(CONFIG_MITIGATION_UNRET_ENTRY),$(CONFIG_MITIGATION_SRSO)), --unret) - -objtool-args = $(vmlinux-objtool-args-y) --link +objtool-cmds-$(CONFIG_NOINSTR_VALIDATION) += --noinstr \ + $(if $(or $(CONFIG_MITIGATION_UNRET_ENTRY),$(CONFIG_MITIGATION_SRSO)), --unret) # Link of vmlinux.o used for section mismatch analysis # ---------------------------------------------------------------------------
Signature · 1 line
2.53.0
[email protected]
AI reviewRe: [PATCH v3 09/21] kbuild: Only run objtool if there is at least one command
Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider: - [Low] Leaking objtool options into vmlinux.o validation causes a fatal build failure (`--cfi requires…
Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
- [Low] Leaking objtool options into vmlinux.o validation causes a fatal build failure (--cfi requires --prefix) and silently alters validation behavior. --
commit 68e454f3a8ccd3d638c67c092eee264996286695
Author: Josh Poimboeuf <[email protected]>
kbuild: Only run objtool if there is at least one command
This commit splits the objtool arguments into commands and options to prevent objtool from running when no commands are enabled.
Quoted 4 lines
scripts/Makefile.vmlinux_o +0
-0
[ ... ]
scripts/Makefile.vmlinux_o endif · line 41
Quoted 13 lines
scripts/Makefile.vmlinux_o +3
-6@@ -36,18 +36,13 @@ endif # For !delay-objtool + CONFIG_NOINSTR_VALIDATION, it runs on both translation # units and vmlinux.o, with the latter only used for noinstr/unret validation. -objtool-enabled := $(or $(delay-objtool),$(CONFIG_NOINSTR_VALIDATION)) - -ifeq ($(delay-objtool),y) -vmlinux-objtool-args-y += $(objtool-args-y) -else -vmlinux-objtool-args-$(CONFIG_OBJTOOL_WERROR) += --werror +ifneq ($(delay-objtool),y) +objtool-cmds-y = +objtool-opts-y += --link
Does this code unintentionally retain the options from scripts/Makefile.lib?
Since objtool-cmds-y is cleared here, commands like --prefix are dropped. However, because objtool-opts-y is only appended to, options like --cfi remain active.
This causes objtool to fail with a fatal error: "--cfi requires --prefix" during vmlinux.o validation on configurations with KCFI.
Should objtool-opts-y be assigned directly using = or := instead of appended?
Quoted 1 line
scripts/Makefile.vmlinux_o +0
-0endif
Signature · 1 line
Sashiko AI review · https://sashiko.dev/#/patchset/[email protected]?part=9
Josh Poimboeuf
maintainerRe: [PATCH v3 09/21] kbuild: Only run objtool if there is at least one command
Quoted 37 lines · On Thu, May 14, 2026 at 10:25:15PM +0000, [email protected] wrote:
Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
- [Low] Leaking objtool options into vmlinux.o validation causes a fatal build failure (--cfi requires --prefix) and silently alters validation behavior. --commit
68e454f3a8ccd3d638c67c092eee264996286695
Author: Josh Poimboeuf <[email protected]>kbuild: Only run objtool if there is at least one command
This commit splits the objtool arguments into commands and options to prevent objtool from running when no commands are enabled.
Quoted 4 lines
scripts/Makefile.vmlinux_o +0
-0[ ... ]
scripts/Makefile.vmlinux_o endif · line 41
Quoted 13 lines
scripts/Makefile.vmlinux_o +3
-6@@ -36,18 +36,13 @@ endif # For !delay-objtool + CONFIG_NOINSTR_VALIDATION, it runs on both translation # units and vmlinux.o, with the latter only used for noinstr/unret validation. -objtool-enabled := $(or $(delay-objtool),$(CONFIG_NOINSTR_VALIDATION)) - -ifeq ($(delay-objtool),y) -vmlinux-objtool-args-y += $(objtool-args-y) -else -vmlinux-objtool-args-$(CONFIG_OBJTOOL_WERROR) += --werror +ifneq ($(delay-objtool),y) +objtool-cmds-y = +objtool-opts-y += --linkDoes this code unintentionally retain the options from
scripts/Makefile.lib?Since objtool-cmds-y is cleared here, commands like --prefix are dropped. However, because objtool-opts-y is only appended to, options like --cfi remain active.
Quoted 5 lines
This causes objtool to fail with a fatal error: "--cfi requires --prefix" during vmlinux.o validation on configurations with KCFI.
Should objtool-opts-y be assigned directly using = or := instead of appended?
Indeed, adding this on top:
scripts/Makefile.vmlinux_o +2 -1
@@ -38,7 +38,8 @@ endif ifneq ($(delay-objtool),y) objtool-cmds-y = -objtool-opts-y += --link +objtool-opts-y = --link +objtool-opts-$(CONFIG_OBJTOOL_WERROR) += --werror endif objtool-cmds-$(CONFIG_NOINSTR_VALIDATION) += --noinstr \
Josh Poimboeuf
maintainerPATCH v3 10/21 objtool: Ignore jumps to the end of the function for checksum runs
In reply to Josh PoimboeufSometimes Clang arm64 code jumps to the end of the function for UB. No need to make that an error for checksum runs.
--- tools/objtool/check.c | 42 +++++++++++++++++++++++------------------- 1 file changed, 23 insertions(+), 19 deletions(-)
tools/objtool/check.c +23 -19
@@ -37,6 +37,22 @@ struct disas_context *objtool_disas_ctx;size_t sym_name_max_len; +static bool validate_branch_enabled (void ) +{ +return opts.stackval || + opts.orc || + opts.uaccess; +} + +static bool alts_needed (void ) +{ +return validate_branch_enabled () || + opts.noinstr || + opts.hack_jump_label || + opts.disas || + opts.checksum; +} +struct instruction *find_insn (struct objtool_file *file,struct section *sec,unsigned long offset) {
@@ -1593,10 +1609,14 @@ static int add_jump_destinations(struct objtool_file *file)/* * GCOV/KCOV dead code can jump to the end of * the function/section. +* +* Clang on arm64 also does this sometimes for +* undefined behavior. */ -if (file->ignore_unreachables && func && - dest_sec == insn->sec && - dest_off == func->offset + func->len) +if (!validate_branch_enabled () || + (file->ignore_unreachables && func && + dest_sec == insn->sec && + dest_off == func->offset + func->len))continue ; ERROR_INSN(insn, "can't find jump dest instruction at %s",
@@ -2584,22 +2604,6 @@ static void mark_holes(struct objtool_file *file) } } -static bool validate_branch_enabled (void ) -{ -return opts.stackval || - opts.orc || - opts.uaccess; -} - -static bool alts_needed (void ) -{ -return validate_branch_enabled () || - opts.noinstr || - opts.hack_jump_label || - opts.disas || - opts.checksum; -} -int decode_file (struct objtool_file *file) {arch_initial_func_cfi_state (&initial_func_cfi);
Signature · 1 line
2.53.0
Peter Zijlstra
maintainerRe: [PATCH v3 10/21] objtool: Ignore jumps to the end of the function for checksum runs
Quoted 2 lines · Josh Poimboeuf
Sometimes Clang arm64 code jumps to the end of the function for UB. No need to make that an error for checksum runs.
I'm not sure I understand the rationale. If the compiler trips UB, we want to be complaining about that no?
Same as when clang just stops code-gen, also something we commonly see when it hits UB.
Josh Poimboeuf
maintainerRe: [PATCH v3 10/21] objtool: Ignore jumps to the end of the function for checksum runs
Quoted 9 lines · Peter Zijlstra
Quoted 2 lines · Josh Poimboeuf
Sometimes Clang arm64 code jumps to the end of the function for UB. No need to make that an error for checksum runs.
I'm not sure I understand the rationale. If the compiler trips UB, we want to be complaining about that no?
Same as when clang just stops code-gen, also something we commonly see when it hits UB.
Right, but I guess the question is, should we always warn about that, or should we only warn about it when doing stack validation or ORC or some other CFG-traversing thing?
The checksum thing is only used when manually enabled by klp-build (never for normal kernel builds), so it would be a weird spot to report the Clang "jumping to the end of the function" UB.
If we care about that for arm64 then the UB check probably belongs in a new standalone objtool command that would get used during a regular kernel build.
Signature · 1 line
Josh
[email protected]
AI reviewRe: [PATCH v3 10/21] objtool: Ignore jumps to the end of the function for checksum runs
In reply to Josh PoimboeufThank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider: - [Medium] Missing parentheses in the jump destination check causes objtool to silently ignore all missing…
Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
- [Medium] Missing parentheses in the jump destination check causes objtool to silently ignore all missing jump targets when branch validation is disabled, instead of only jumps to the end of the function. --
commit aa068d4053e8b92ff17082c7a023821e72f5d625
Author: Josh Poimboeuf <[email protected]>
objtool: Ignore jumps to the end of the function for checksum runs
Sometimes Clang arm64 code jumps to the end of the function for UB. This commit ignores these jumps to the end of the function for checksum runs to avoid errors.
Quoted 4 lines
tools/objtool/check.c +0
-0
[ ... ]
Quoted 6 lines
tools/objtool/check.c +2
-0@@ -1593,10 +1609,14 @@ static int add_jump_destinations(struct objtool_file *file)/* * GCOV/KCOV dead code can jump to the end of * the function/section. +* +* Clang on arm64 also does this sometimes for
tools/objtool/check.c static int add_jump_destinations(struct objtool_file *file) · line 1620
Quoted 10 lines
tools/objtool/check.c +5
-3+* undefined behavior. */ -if (file->ignore_unreachables && func && - dest_sec == insn->sec && - dest_off == func->offset + func->len) +if (!validate_branch_enabled () || + (file->ignore_unreachables && func && + dest_sec == insn->sec && + dest_off == func->offset + func->len))continue ;
Does this condition silently ignore all missing jump targets when branch validation is disabled?
Because of the || without proper parenthetical grouping around the first two terms, if !validate_branch_enabled() is true (which it is for --checksum or --noinstr runs), the rest of the condition short-circuits.
This appears to cause objtool to skip the error reporting and silently leave the jump destination as NULL for any unresolvable jump in the binary, instead of only those jumping to the end of the function.
Should the logic be grouped to restrict the exception to the end of the function?
if ((!validate_branch_enabled() || file->ignore_unreachables) && func && dest_sec == insn->sec && dest_off == func->offset + func->len)
Signature · 1 line
Sashiko AI review · https://sashiko.dev/#/patchset/[email protected]?part=10
Josh Poimboeuf
maintainerRe: [PATCH v3 10/21] objtool: Ignore jumps to the end of the function for checksum runs
Quoted 48 lines · On Thu, May 14, 2026 at 10:30:20PM +0000, [email protected] wrote:
Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
- [Medium] Missing parentheses in the jump destination check causes objtool to silently ignore all missing jump targets when branch validation is disabled, instead of only jumps to the end of the function. --commit
aa068d4053e8b92ff17082c7a023821e72f5d625
Author: Josh Poimboeuf <[email protected]>objtool: Ignore jumps to the end of the function for checksum runs
Sometimes Clang arm64 code jumps to the end of the function for UB. This commit ignores these jumps to the end of the function for checksum runs to avoid errors.
Quoted 4 lines
tools/objtool/check.c +0
-0[ ... ]
Quoted 6 lines
tools/objtool/check.c +2
-0@@ -1593,10 +1609,14 @@ static int add_jump_destinations(struct objtool_file *file)/* * GCOV/KCOV dead code can jump to the end of * the function/section. +* +* Clang on arm64 also does this sometimes for tools/objtool/check.c static int add_jump_destinations(struct objtool_file *file) · line 1620
Quoted 10 lines
tools/objtool/check.c +5
-3+* undefined behavior. */ -if (file->ignore_unreachables && func && - dest_sec == insn->sec && - dest_off == func->offset + func->len) +if (!validate_branch_enabled () || + (file->ignore_unreachables && func && + dest_sec == insn->sec && + dest_off == func->offset + func->len))continue ;Does this condition silently ignore all missing jump targets when branch validation is disabled?
Because of the || without proper parenthetical grouping around the first two terms, if !validate_branch_enabled() is true (which it is for --checksum or --noinstr runs), the rest of the condition short-circuits.
This appears to cause objtool to skip the error reporting and silently leave the jump destination as NULL for any unresolvable jump in the binary, instead of only those jumping to the end of the function.
Quoted 7 lines
Should the logic be grouped to restrict the exception to the end of the function?
if ((!validate_branch_enabled() || file->ignore_unreachables) && func && dest_sec == insn->sec && dest_off == func->offset + func->len)
Yes, we should tighten that up.
Signature · 1 line
Josh
Josh Poimboeuf
maintainerPATCH v3 11/21 objtool: Allow empty alternatives
In reply to Josh Poimboeufarm64 can have empty alternatives, which are effectively no-ops. Ignore them. While at it, fix a memory leak.
--- tools/objtool/check.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-)
tools/objtool/check.c +4 -4
@@ -1953,6 +1953,9 @@ static int add_special_section_alts(struct objtool_file *file)list_for_each_entry_safe (special_alt, tmp, &special_alts, list) { +if (special_alt->group && !special_alt->orig_len) + goto next; + orig_insn =find_insn (file, special_alt->orig_sec, special_alt->orig_off);if (!orig_insn) {
@@ -1973,10 +1976,6 @@ static int add_special_section_alts(struct objtool_file *file) }if (special_alt->group) { -if (!special_alt->orig_len) { -ERROR_INSN (orig_insn,"empty alternative entry" ); -continue ; - }if (handle_group_alt (file, special_alt, orig_insn, &new_insn))return -1 ;
@@ -2014,6 +2013,7 @@ static int add_special_section_alts(struct objtool_file *file) a->next = alt; } +next:list_del (&special_alt->list);free (special_alt); }
Signature · 1 line
2.53.0
Peter Zijlstra
maintainerRe: [PATCH v3 11/21] objtool: Allow empty alternatives
Quoted 2 lines · Josh Poimboeuf
arm64 can have empty alternatives, which are effectively no-ops. Ignore them. While at it, fix a memory leak.
How does this happen?
Josh Poimboeuf
maintainerRe: [PATCH v3 11/21] objtool: Allow empty alternatives
Quoted 5 lines · Peter Zijlstra
Quoted 2 lines · Josh Poimboeuf
arm64 can have empty alternatives, which are effectively no-ops. Ignore them. While at it, fix a memory leak.
How does this happen?
Yeah, that commit log sucks...
The case I stumbled on was the dcache_by_myline_op_nosync macro:
alternative_if ARM64_WORKAROUND_4311569
.ifnc \op, cvau mov \start, \tmp mov \tmp, xzr cbnz \start, .Ldcache_op\@ .endif
alternative_else_nop_endif
That's actually a bit weird, the .ifnc should probably be outside the alternative_if. Let me try fixing that instead.
Signature · 1 line
Josh
Josh Poimboeuf
maintainerRe: [PATCH v3 11/21] objtool: Allow empty alternatives
Quoted 17 lines · Josh Poimboeuf
Quoted 5 lines · Peter Zijlstra
Quoted 2 lines · Josh Poimboeuf
arm64 can have empty alternatives, which are effectively no-ops. Ignore them. While at it, fix a memory leak.
How does this happen?
Yeah, that commit log sucks...
The case I stumbled on was the
dcache_by_myline_op_nosyncmacro:
alternative_ifARM64_WORKAROUND_4311569.ifnc \op, cvau mov \start, \tmp mov \tmp, xzr cbnz \start, .Ldcache_op\@ .endif
Quoted 4 lines
alternative_else_nop_endifThat's actually a bit weird, the .ifnc should probably be outside the
alternative_if. Let me try fixing that instead.
I'll probably drop the above patch (leaving empty alternatives as a hard error) and replace it with this one:
commit 5dd83bdddff38f4df2829b05f876e57215b2e850
Author: Josh Poimboeuf <[email protected]> Date: Fri Aug 7 16:41:10 2026 -0700
arm64: Remove unnecessary empty alternatives
The code in arch/arm64/mm/cache.S creates three empty alternatives, all from calling the dcache_by_myline_op_nosync asm macro. If \op == cvau, it creates an empty alternative for ARM64_WORKAROUND_4311569.
Since orig_len == 0, the alternative patching code mostly ignores it, though it does do an unnecessary clean_dcache_range_nopatch(). It also triggers an objtool error ("empty alternative entry").
Clean it up by moving the .ifnc check outside the alternative emit.
arch/arm64/include/asm/assembler.h +2 -2
@@ -404,13 +404,13 @@ alternative_else_nop_endif add \start, \start, \linesz cmp \start, \end b.lo .Ldcache_op\@ -alternative_if ARM64_WORKAROUND_4311569 .ifnc \op, cvau +alternative_if ARM64_WORKAROUND_4311569 mov \start, \tmp mov \tmp, xzr cbnz \start, .Ldcache_op\@ - .endif alternative_else_nop_endif + .endif _cond_uaccess_extable .Ldcache_op\@, \fixup .endm
Josh Poimboeuf
maintainerPATCH v3 12/21 objtool: Refactor elf_add_data() to use a growable data buffer
In reply to Josh PoimboeufInstead of calling elf_newdata() for each new piece of data with its own separate buffer, keep it all in the same growable buffer so the section's entire data can be accessed if needed.
--- tools/objtool/elf.c | 123 ++++++++++++++-------------- tools/objtool/include/objtool/elf.h | 13 ++- 2 files changed, 71 insertions(+), 65 deletions(-)
tools/objtool/elf.c +60 -63
@@ -1134,9 +1134,6 @@ static int read_relocs(struct elf *elf) rsec->base->rsec = rsec; -/* nr_alloc_relocs=0: libelf owns d_buf */ - rsec->nr_alloc_relocs =0 ; - rsec->relocs =calloc (sec_num_entries (rsec),sizeof (*reloc));if (!rsec->relocs) {ERROR_GLIBC ("calloc" );
@@ -1395,7 +1392,7 @@ unsigned int elf_add_string(struct elf *elf, struct section *strtab, const charvoid *elf_add_data (struct elf *elf,struct section *sec,const void *data,size_t size) { -unsigned long offset; +unsigned long offset, size_old, size_new, alloc_size_old, alloc_size_new;Elf_Scn *s;if (!sec->sh.sh_addralign) {
@@ -1409,30 +1406,55 @@ void *elf_add_data(struct elf *elf, struct section *sec, const void *data, size_return NULL ; } - sec->data =elf_newdata (s);if (!sec->data) { -ERROR_ELF ("elf_newdata" ); -return NULL ; + sec->data =elf_newdata (s); +if (!sec->data) { +ERROR_ELF ("elf_newdata" ); +return NULL ; + } + + sec->data->d_align = sec->sh.sh_addralign; } - sec->data->d_buf =calloc (1 , size); -if (!sec->data->d_buf) { -ERROR_GLIBC ("calloc" ); -return NULL ; + size_old = sec->data->d_size; + offset =ALIGN (size_old, sec->sh.sh_addralign); + size_new = offset + size; + +if (!sec->data_overallocated) + alloc_size_old = size_old; +else + alloc_size_old =max (64UL ,roundup_pow_of_two (size_old ? :1 )); + + alloc_size_new =max (64UL ,roundup_pow_of_two (size_new ? :1 )); + +if (alloc_size_new > alloc_size_old) { +void *orig_buf = sec->data->d_buf; + + sec->data->d_buf =calloc (1 , alloc_size_new); +if (!sec->data->d_buf) { +ERROR_GLIBC ("calloc" ); +return NULL ; + } + +if (size_old) +memcpy (sec->data->d_buf, orig_buf, size_old); + +if (orig_buf && sec->data_owned) +free (orig_buf); + + sec->data_owned =1 ; + sec->data_overallocated =1 ; }if (data) -memcpy (sec->data->d_buf, data, size); - - sec->data->d_size = size; - sec->data->d_align = sec->sh.sh_addralign; - - offset =ALIGN (sec_size (sec), sec->sh.sh_addralign); - sec->sh.sh_size = offset + size; +memcpy (sec->data->d_buf + offset, data, size); +else +memset (sec->data->d_buf + offset,0 , size); + sec->data->d_size = size_new; + sec->sh.sh_size = size_new;mark_sec_changed (elf, sec, true); - -return sec->data->d_buf; +return sec->data->d_buf + offset; } struct section *elf_create_section(struct elf *elf, const char *name,
@@ -1483,6 +1505,8 @@ struct section *elf_create_section(struct elf *elf, const char *name,ERROR_GLIBC ("calloc" );return NULL ; } + + sec->data_owned =1 ; }if (!gelf_getshdr (s, &sec->sh)) {
@@ -1533,60 +1557,33 @@ static int elf_alloc_reloc(struct elf *elf, struct section *rsec)struct reloc *old_relocs, *old_relocs_end, *new_relocs;unsigned int nr_relocs_old =sec_num_entries (rsec);unsigned int nr_relocs_new = nr_relocs_old +1 ; -unsigned long nr_alloc; +unsigned long nr_alloc_old =0 , nr_alloc_new;struct symbol *sym; -if (!rsec->data) { - rsec->data =elf_newdata (elf_getscn (elf->elf, rsec->idx)); -if (!rsec->data) { -ERROR_ELF ("elf_newdata" ); -return -1 ; - } +if (!elf_add_data (elf, rsec,NULL ,elf_rela_size (elf))) +return -1 ; - rsec->data->d_align =1 ; - rsec->data->d_type =ELF_T_RELA ; - rsec->data->d_buf =NULL ; - } + rsec->data->d_type =ELF_T_RELA ; - rsec->data->d_size = nr_relocs_new *elf_rela_size (elf); - rsec->sh.sh_size = rsec->data->d_size; +if (rsec->relocs_overallocated) + nr_alloc_old =max (64UL ,roundup_pow_of_two (nr_relocs_old ? :1 )); +else + nr_alloc_old = nr_relocs_old; - nr_alloc =max (64UL ,roundup_pow_of_two (nr_relocs_new)); -if (nr_alloc <= rsec->nr_alloc_relocs) + nr_alloc_new =max (64UL ,roundup_pow_of_two (nr_relocs_new ? :1 )); + +if (nr_alloc_old == nr_alloc_new)return 0 ; -if (rsec->data->d_buf && !rsec->nr_alloc_relocs) { -void *orig_buf = rsec->data->d_buf; - -/* -* The original d_buf is owned by libelf so it can't be -* realloced. -*/ - rsec->data->d_buf =malloc (nr_alloc *elf_rela_size (elf)); -if (!rsec->data->d_buf) { -ERROR_GLIBC ("malloc" ); -return -1 ; - } -memcpy (rsec->data->d_buf, orig_buf, - nr_relocs_old *elf_rela_size (elf)); - }else { - rsec->data->d_buf =realloc (rsec->data->d_buf, - nr_alloc *elf_rela_size (elf)); -if (!rsec->data->d_buf) { -ERROR_GLIBC ("realloc" ); -return -1 ; - } - } - - rsec->nr_alloc_relocs = nr_alloc; - - old_relocs = rsec->relocs; - new_relocs =calloc (nr_alloc,sizeof (struct reloc )); + new_relocs =calloc (nr_alloc_new,sizeof (struct reloc ));if (!new_relocs) {ERROR_GLIBC ("calloc" );return -1 ; } + rsec->relocs_overallocated =1 ; + + old_relocs = rsec->relocs;if (!old_relocs) goto done;
@@ -1631,6 +1628,7 @@ static int elf_alloc_reloc(struct elf *elf, struct section *rsec) }free (old_relocs); + done: rsec->relocs = new_relocs;return 0 ;
@@ -1660,7 +1658,6 @@ struct section *elf_create_rela_section(struct elf *elf, struct section *sec,if (nr_relocs) { rsec->data->d_type =ELF_T_RELA ; - rsec->nr_alloc_relocs = nr_relocs; rsec->relocs =calloc (nr_relocs,sizeof (struct reloc ));if (!rsec->relocs) {ERROR_GLIBC ("calloc" );
tools/objtool/include/objtool/elf.h +11 -2
@@ -58,9 +58,18 @@ struct section {Elf_Data *data;const char *name;int idx; -bool _changed, text, rodata, noinstr, init, truncate; + u32 _changed : 1, + text : 1, + rodata : 1, + noinstr : 1, + init : 1, + truncate : 1, + data_owned : 1, + data_overallocated : 1, + relocs_overallocated : 1; +/* 23 bit hole */ +struct reloc *relocs; -unsigned long nr_alloc_relocs;struct section *twin; };
Signature · 1 line
2.53.0
[email protected]
AI reviewRe: [PATCH v3 12/21] objtool: Refactor elf_add_data() to use a growable data buffer
Thank you for your contribution! Sashiko AI review found 2 potential issue(s) to consider: - [Medium] An integer overflow in `elf_add_data()` due to an uncontrolled `sh_addralign` value allows an…
Thank you for your contribution! Sashiko AI review found 2 potential issue(s) to consider:
- [Medium] An integer overflow in elf_add_data() due to an uncontrolled sh_addralign value allows an out-of-bounds heap write.
- [Low] In elf_add_data, a failed calloc for the new data buffer overwrites sec->data->d_buf with NULL and leaks the original buffer. --
commit 02f18d399bba6e3d751363b4e85f507e3f288e34
Author: Josh Poimboeuf <[email protected]>
objtool: Refactor elf_add_data() to use a growable data buffer
Instead of calling elf_newdata() for each new piece of data with its own separate buffer, keep it all in the same growable buffer so the section's entire data can be accessed if needed.
Quoted 3 lines
tools/objtool/elf.c +0
-0
[ ... ]
Quoted 6 lines
tools/objtool/elf.c +0
-1@@ -1409,30 +1406,55 @@ void *elf_add_data(struct elf *elf, struct section *sec, const void *data, size_return NULL ; } - sec->data =elf_newdata (s);if (!sec->data) {
[ ... ]
tools/objtool/elf.c void *elf_add_data(struct elf *elf, struct section *sec, const void *data, size_ · line 1421
Quoted 10 lines
tools/objtool/elf.c +4
-4+ sec->data->d_align = sec->sh.sh_addralign; } - sec->data->d_buf =calloc (1 , size); -if (!sec->data->d_buf) { -ERROR_GLIBC ("calloc" ); -return NULL ; + size_old = sec->data->d_size; + offset =ALIGN (size_old, sec->sh.sh_addralign); + size_new = offset + size;
Could this ALIGN calculation overflow if a malformed ELF file provides an extremely large sh_addralign value?
If offset wraps around, it seems size_new might also wrap, potentially causing roundup_pow_of_two to return a small allocation size. Could this lead to an out-of-bounds write during the subsequent memcpy further down in this function?
Quoted 11 lines
tools/objtool/elf.c +5
-4-if (data) -memcpy (sec->data->d_buf, data, size); +if (!sec->data_overallocated) + alloc_size_old = size_old; +else + alloc_size_old =max (64UL ,roundup_pow_of_two (size_old ? :1 )); - sec->data->d_size = size; - sec->data->d_align = sec->sh.sh_addralign; + alloc_size_new =max (64UL ,roundup_pow_of_two (size_new ? :1 ));
tools/objtool/elf.c
Quoted 10 lines
tools/objtool/elf.c +8
-2- offset =ALIGN (sec_size (sec), sec->sh.sh_addralign); - sec->sh.sh_size = offset + size; +if (alloc_size_new > alloc_size_old) { +void *orig_buf = sec->data->d_buf; + + sec->data->d_buf =calloc (1 , alloc_size_new); +if (!sec->data->d_buf) { +ERROR_GLIBC ("calloc" ); +return NULL ; + }
If calloc fails here, does assigning its result directly to sec->data->d_buf clobber the valid pointer to orig_buf?
It appears orig_buf is never freed on this error path, which might leak the original buffer if it was previously owned by objtool. Would it be safer to keep the original buffer pointer intact on allocation failure?
Signature · 1 line
Sashiko AI review · https://sashiko.dev/#/patchset/[email protected]?part=12
Josh Poimboeuf
maintainerRe: [PATCH v3 12/21] objtool: Refactor elf_add_data() to use a growable data buffer
Quoted 10 lines · On Thu, May 14, 2026 at 11:13:55PM +0000, [email protected] wrote:
tools/objtool/elf.c void *elf_add_data(struct elf *elf, struct section *sec, const void *data, size_ · line 1421
Quoted 7 lines
tools/objtool/elf.c +3
-4- sec->data->d_buf =calloc (1 , size); -if (!sec->data->d_buf) { -ERROR_GLIBC ("calloc" ); -return NULL ; + size_old = sec->data->d_size; + offset =ALIGN (size_old, sec->sh.sh_addralign); + size_new = offset + size;Could this ALIGN calculation overflow if a malformed ELF file provides an extremely large
sh_addralignvalue?
Doesn't seem realistic...
Quoted 25 lines
Quoted 11 lines
(quoted diff) +5
-4- if (data) - memcpy(sec->data->d_buf, data, size); + if (!sec->data_overallocated) + alloc_size_old = size_old; + else + alloc_size_old = max(64UL, roundup_pow_of_two(size_old ? : 1)); - sec->data->d_size = size; - sec->data->d_align = sec->sh.sh_addralign; + alloc_size_new = max(64UL, roundup_pow_of_two(size_new ? : 1));Review
Quoted 10 lines
(quoted diff) +8
-2- offset = ALIGN(sec_size(sec), sec->sh.sh_addralign); - sec->sh.sh_size = offset + size; + if (alloc_size_new > alloc_size_old) { + void *orig_buf = sec->data->d_buf; + + sec->data->d_buf = calloc(1, alloc_size_new); + if (!sec->data->d_buf) { + ERROR_GLIBC("calloc"); + return NULL; + }If calloc fails here, does assigning its result directly to
sec->data->d_bufclobber the valid pointer toorig_buf?
Quoted 3 lines
It appears
orig_bufis never freed on this error path, which might leak the original buffer if it was previously owned by objtool. Would it be safer to keep the original buffer pointer intact on allocation failure?
A memory leak on the error path is fine, it's about to exit anyway.
Signature · 1 line
Josh
Josh Poimboeuf
maintainerPATCH v3 13/21 objtool: Reuse string references
In reply to Josh PoimboeufFor duplicate strings, elf_add_string() just blindly adds duplicates.
That can be a problem for arm64 which often uses two consecutive instructions (and corresponding relocations) to put an address into a register, like:
d8: 90000001 adrp x1, 0 <meminfo_proc_show> d8: R_AARCH64_ADR_PREL_PG_HI21 .rodata.meminfo_proc_show.str1.8 dc: 91000021 add x1, x1, #0x0 dc: R_AARCH64_ADD_ABS_LO12_NC .rodata.meminfo_proc_show.str1.8
Referencing two different addresses in the ADRP+ADD pair would corrupt the memory access. Avoid that by detecting and reusing duplicates when cloning string relocs.
--- tools/objtool/elf.c | 29 +++++++++++++++++++++++------ tools/objtool/include/objtool/elf.h | 3 ++- tools/objtool/klp-diff.c | 4 +++- 3 files changed, 28 insertions(+), 8 deletions(-)
tools/objtool/elf.c +23 -6
@@ -1366,9 +1366,27 @@ struct elf *elf_create_file(GElf_Ehdr *ehdr, const char *name)return elf; } -unsigned int elf_add_string (struct elf *elf,struct section *strtab,const char *str) +int elf_find_string (struct elf *elf,struct section *strtab,const char *str) { -unsigned int offset; +char *d_buf; +int i; + +if (!strtab->data) +return -1 ; + + d_buf = strtab->data->d_buf; + +for (i =0 ; i < strtab->data->d_size; i +=strlen (d_buf + i) +1 ) { +if (!strcmp (d_buf + i, str)) +return i; + } + +return -1 ; +} + +int elf_add_string (struct elf *elf,struct section *strtab,const char *str) +{ +void *data;if (!strtab) strtab =find_section_by_name (elf,".strtab" );
@@ -1382,12 +1400,11 @@ unsigned int elf_add_string(struct elf *elf, struct section *strtab, const charreturn -1 ; } - offset =ALIGN (sec_size (strtab), strtab->sh.sh_addralign); - -if (!elf_add_data (elf, strtab, str,strlen (str) +1 )) + data =elf_add_data (elf, strtab, str,strlen (str) +1 ); +if (!data)return -1 ; -return offset; +return data - strtab->data->d_buf; }void *elf_add_data (struct elf *elf,struct section *sec,const void *data,size_t size)
tools/objtool/include/objtool/elf.h +2 -1
@@ -187,7 +187,8 @@ struct symbol *elf_create_section_symbol(struct elf *elf, struct section *sec);void *elf_add_data (struct elf *elf,struct section *sec,const void *data,size_t size); -unsigned int elf_add_string (struct elf *elf,struct section *strtab,const char *str); +int elf_find_string (struct elf *elf,struct section *strtab,const char *str); +int elf_add_string (struct elf *elf,struct section *strtab,const char *str); struct reloc *elf_create_reloc(struct elf *elf, struct section *sec, unsigned long offset, struct symbol *sym,
tools/objtool/klp-diff.c +3 -1
@@ -1509,7 +1509,9 @@ static int clone_reloc(struct elfs *e, struct reloc *patched_reloc,__dbg_clone ("\"%s\"" ,escape_str (str)); - addend =elf_add_string (e->out, out_sym->sec, str); + addend =elf_find_string (e->out, out_sym->sec, str); +if (addend ==-1 ) + addend =elf_add_string (e->out, out_sym->sec, str);if (addend ==-1 )return -1 ; }
Signature · 1 line
2.53.0
Josh Poimboeuf
maintainerPATCH v3 14/21 objtool: Prevent kCFI hashes from being decoded as instructions
In reply to Josh PoimboeufOn arm64 with CONFIG_CFI=y, Clang places a 4-byte kCFI type hash immediately before each address-taken function entry. Since these hashes are in the text section, objtool tries to decode them, leading to unpredictable results (e.g., "unannotated intra-function call").
arm64 uses mapping symbols to annotate where code ends and data begins (and vice versa). Use those to just mark such "instructions" as NOP so objtool will ignore them.
--- tools/objtool/check.c | 15 +++++++++++++++ tools/objtool/include/objtool/elf.h | 3 +++ 2 files changed, 18 insertions(+)
tools/objtool/check.c +15 -0
@@ -25,6 +25,7 @@#include <linux/kernel.h> #include <linux/static_call_types.h> #include <linux/string.h> +#include <linux/kconfig.h> static unsigned long nr_cfi, nr_cfi_reused, nr_cfi_cache;
@@ -428,6 +429,8 @@ static int decode_instructions(struct objtool_file *file)for_each_sec (file->elf, sec) {struct instruction *insns =NULL ; +struct symbol *map_sym; +bool is_data = false;u8 prev_len =0 ;u8 idx =0 ;
@@ -454,6 +457,8 @@ static int decode_instructions(struct objtool_file *file)if (!strcmp (sec->name,".init.text" ) && !opts.module) sec->init = true; + map_sym = list_first_entry(&sec->symbol_list, struct symbol, list); +for (offset =0 ; offset <sec_size (sec); offset += insn->len) {if (!insns || idx ==INSN_CHUNK_MAX ) { insns =calloc (INSN_CHUNK_SIZE ,sizeof (*insn));
@@ -478,6 +483,16 @@ static int decode_instructions(struct objtool_file *file) prev_len = insn->len; +/* Use mapping symbols to skip data in text sections */ +sec_for_each_sym_from (sec, map_sym) { +if (map_sym->offset > offset) +break ; +if (is_mapping_sym (map_sym)) + is_data =is_data_mapping_sym (map_sym); + } +if (is_data) + insn->type =INSN_NOP ; +/* * By default, "ud2" is a dead end unless otherwise * annotated, because GCC 7 inserts it for certain
tools/objtool/include/objtool/elf.h +3 -0
@@ -507,6 +507,9 @@ static inline void set_sym_next_reloc(struct reloc *reloc, struct reloc *next)#define sec_for_each_sym (sec, sym) \ list_for_each_entry(sym, &sec->symbol_list, list) +#define sec_for_each_sym_from (sec, sym) \ + list_for_each_entry_from(sym, &sec->symbol_list, list) +#define sec_prev_sym (sym) \ sym->sec && sym->list.prev != &sym->sec->symbol_list ? \ list_prev_entry(sym, list) : NULL
Signature · 1 line
2.53.0
[email protected]
AI reviewRe: [PATCH v3 14/21] objtool: Prevent kCFI hashes from being decoded as instructions
Thank you for your contribution! Sashiko AI review found 2 potential issue(s) to consider: - [High] Build failure due to undeclared mapping symbol functions. - [Low] Unused header inclusion. --
Thank you for your contribution! Sashiko AI review found 2 potential issue(s) to consider:
- [High] Build failure due to undeclared mapping symbol functions.
- [Low] Unused header inclusion.
--
commit 4ad78a24a64cd6a2d10b9a2f9c0d306fba3e6d19
Author: Josh Poimboeuf <[email protected]>
objtool: Prevent kCFI hashes from being decoded as instructions
On arm64 with CONFIG_CFI=y, Clang places a 4-byte kCFI type hash immediately before each address-taken function entry. This commit uses mapping symbols to mark such data as NOP in text sections to prevent objtool from decoding them.
tools/objtool/check.c line 28
Quoted 9 lines
tools/objtool/check.c +1
-0@@ -25,6 +25,7 @@#include <linux/kernel.h> #include <linux/static_call_types.h> #include <linux/string.h> +#include <linux/kconfig.h>
This isn't a bug, but is this include needed? It doesn't appear that any Kconfig configuration macros are being used in the modified code.
[ ... ]
tools/objtool/check.c static int decode_instructions(struct objtool_file *file) · line 491
Quoted 10 lines
tools/objtool/check.c +6
-0@@ -478,6 +483,16 @@ static int decode_instructions(struct objtool_file *file) prev_len = insn->len; +/* Use mapping symbols to skip data in text sections */ +sec_for_each_sym_from (sec, map_sym) { +if (map_sym->offset > offset) +break ; +if (is_mapping_sym (map_sym)) + is_data =is_data_mapping_sym (map_sym);
Could this cause a build failure across architectures here? It appears is_mapping_sym() and is_data_mapping_sym() are neither declared nor defined anywhere in the codebase at this point in the patch series.
If these functions are only introduced in a subsequent commit, applying this patch independently might result in an implicit declaration of function error, breaking git bisectability.
Quoted 5 lines
tools/objtool/check.c +4
-0+ } +if (is_data) + insn->type =INSN_NOP ; +/*
Signature · 1 line
Sashiko AI review · https://sashiko.dev/#/patchset/[email protected]?part=14
Josh Poimboeuf
maintainerRe: [PATCH v3 14/21] objtool: Prevent kCFI hashes from being decoded as instructions
Quoted 15 lines · On Fri, May 15, 2026 at 12:16:51AM +0000, [email protected] wrote:
Thank you for your contribution! Sashiko AI review found 2 potential issue(s) to consider:
- [High] Build failure due to undeclared mapping symbol functions.
- [Low] Unused header inclusion.
--commit
4ad78a24a64cd6a2d10b9a2f9c0d306fba3e6d19
Author: Josh Poimboeuf <[email protected]>objtool: Prevent kCFI hashes from being decoded as instructions
On arm64 with
CONFIG_CFI=y, Clang places a 4-byte kCFI type hash immediately before each address-taken function entry. This commit uses mapping symbols to mark such data as NOP in text sections to prevent objtool from decoding them.
Quoted 12 lines
tools/objtool/check.c line 28
Quoted 9 lines
tools/objtool/check.c +1
-0@@ -25,6 +25,7 @@#include <linux/kernel.h> #include <linux/static_call_types.h> #include <linux/string.h> +#include <linux/kconfig.h> This isn't a bug, but is this include needed? It doesn't appear that any
Kconfigconfiguration macros are being used in the modified code.
Fixed.
Quoted 18 lines
[ ... ]
tools/objtool/check.c static int decode_instructions(struct objtool_file *file) · line 491
Quoted 10 lines
tools/objtool/check.c +6
-0@@ -478,6 +483,16 @@ static int decode_instructions(struct objtool_file *file) prev_len = insn->len; +/* Use mapping symbols to skip data in text sections */ +sec_for_each_sym_from (sec, map_sym) { +if (map_sym->offset > offset) +break ; +if (is_mapping_sym (map_sym)) + is_data =is_data_mapping_sym (map_sym);Could this cause a build failure across architectures here? It appears
is_mapping_sym()andis_data_mapping_sym()are neither declared nor defined anywhere in the codebase at this point in the patch series.
Quoted 3 lines
If these functions are only introduced in a subsequent commit, applying this patch independently might result in an implicit declaration of function error, breaking git bisectability.
Fixes.
Signature · 1 line
Josh
Joe Lawrence
maintainerRe: [PATCH v3 14/21] objtool: Prevent kCFI hashes from being decoded as instructions
In reply to Josh PoimboeufQuoted 10 lines · Josh Poimboeuf
On arm64 with
CONFIG_CFI=y, Clang places a 4-byte kCFI type hash immediately before each address-taken function entry. Since these hashes are in the text section, objtool tries to decode them, leading to unpredictable results (e.g., "unannotated intra-function call").arm64 uses mapping symbols to annotate where code ends and data begins (and vice versa). Use those to just mark such "instructions" as NOP so objtool will ignore them.
Signed-off-by Josh Poimboeuf <[email protected]>
Hi Josh,
While continuing down the klp-build unit test path, I found a bug in kCFI special-section handling. I don't think it's directly related to this patch, though it was the motivation to try testing kCFI + klp-build together.
This looks like the same Clang/klp-diff issue as commit f7ceffd21a8a ("objtool/klp: Fix kCFI prefix finding/cloning"). That commit fixed prefix finding in .text, while this one is in .kcfi_traps and causes create_fake_symbols() to skip the entire section, so clone_special_sections() extracts nothing. (klp-build still exits SUCCESS, but the livepatch .ko has no __kcfi_traps.)
That means da4326573ae8 ("objtool/klp: Fix kCFI trap handling"), which added .kcfi_traps to the special-section list, would be incomplete for the fake-symbol path.
Bug report as follows:
Kernel Config
=============
Setup LLVM and CFI, plus livepatching requirements on top of the default x86 config:
$ make clean $ make LLVM=1 defconfig
# For livepatching
$ ./scripts/config --file .config \
--set-val CONFIG_FTRACE y \
--set-val CONFIG_KALLSYMS_ALL y \
--set-val CONFIG_FUNCTION_TRACER y \
--set-val CONFIG_DYNAMIC_FTRACE y \
--set-val CONFIG_DYNAMIC_DEBUG y \
--set-val CONFIG_LIVEPATCH y
# For CFI
$ ./scripts/config --file .config \
--set-val CONFIG_CFI y
$ make LLVM=1 olddefconfigremotes/origin/objtool/urgent
$ make LLVM=1 -j$(nproc)Livepatch
=========
fs/proc/base.c +2 -0
@@ -809,6 +809,8 @@ static int proc_single_show(struct seq_file *m, void *v)if (!task)return -ESRCH ; +pr_debug ("test: proc_single_show\n" ); + ret =PROC_I (inode)->op.proc_show (m, ns, pid, task);put_task_struct (task);
klp-build
=========
The klp-build looks good and exits with SUCCESS:
$ LLVM=1 scripts/livepatch/klp-build -T klp-cfi-traps.patch 2>&1 | tee out Validating patch(es) Building original kernel Copying original object files Fixing patch(es) Building patched kernel Copying patched object files Generating original checksums Generating patched checksums Diffing objects vmlinux.o: changed function: proc_single_show Building patch module: livepatch-klp-cfi-traps.ko SUCCESS
The patched vmlinux.o contains a per-function .kcfi_traps section associated with .text.proc_single_show, but klp-build does not copy the traps section into the livepatch module:
$ llvm-readelf --wide --sections klp-tmp/3-checksum-patched/vmlinux.o
Section Headers:
[Nr] Name Type Address Off Size ES Flg Lk Inf Al [74992] .text.proc_single_show PROGBITS 0000000000000000 164d470 0000f5 00 AX 0 0 16 [74993] .kcfi_traps PROGBITS 0000000000000000 164d565 000004 00 AL 74992 0 1 [74994] __patchable_function_entries PROGBITS 0000000000000000 164d570 000008 00 WAL 74992 0 8 [74995] .rela.text.proc_single_show RELA 0000000000000000 164d578 000120 18 I 299696 74992 8 [74996] .rela.kcfi_traps RELA 0000000000000000 164d698 000018 18 I 299696 74993 8 [74997] .rela__patchable_function_entries RELA 0000000000000000 164d6b0 000018 18 I 299696 74994 8
$ llvm-readelf --wide --relocs klp-tmp/3-checksum-patched/vmlinux.o | \
awk '$0 ~ "at offset 0x164d698" {p=1; print; next} /^Relocation section/ {p=0} p'
Relocation section '.rela.kcfi_traps' at offset 0x164d698 contains 1 entries:
Offset Info Type Symbol's Value Symbol's Name + Addend
0000000000000000 00016d6700000002 R_X86_64_PC32 0000000000000000 .text.proc_single_show + 6b$ llvm-readelf --wide --sections livepatch-klp-cfi-traps.ko | grep .kcfi_traps (none)
The root cause is that create_fake_symbols() skips the entire special section if any symbol exists at offset 0. But Clang places a .Ltmp* label at the start of .kcfi_traps, so no per-entry fake symbols are created and clone_special_sections() extracts nothing.
static int create_fake_symbols(struct elf *elf)
{
...
/*
* 2) Make symbols for sh_entsize, and simple arrays of pointers:
*/
entsize:
for_each_sec(elf, sec) {
unsigned int entry_size;
unsigned long offset;
if (!is_special_section(sec) || find_symbol_by_offset(sec, 0))
continue;
$ llvm-readelf --wide --symbols klp-tmp/3-checksum-patched/vmlinux.o | \
awk '$7 == 74993'
93266: 0000000000000000 0 NOTYPE LOCAL DEFAULT 74993 .Ltmp78
93544: 0000000000000000 0 SECTION LOCAL DEFAULT 74993 .kcfi_trapsPossible fix: ignore .L* assembler-local labels at section offset 0 using the existing is_local_label() helper.
-->8-- -->8-- -->8-- -->8-- -->8-- -->8-- -->8-- -->8-- -->8-- -->8--
tools/objtool/klp-diff.c +10 -1
@@ -1860,8 +1860,17 @@ static int create_fake_symbols(struct elf *elf)for_each_sec (elf, sec) {unsigned int entry_size;unsigned long offset; +struct symbol *sym_at_0; -if (!is_special_section (sec) ||find_symbol_by_offset (sec,0 )) +if (!is_special_section (sec)) +continue ; + +/* +* Clang may place assembler-local .L* labels at offset 0; +* they must not prevent per-entry fake symbol creation. +*/ + sym_at_0 =find_symbol_by_offset (sec,0 ); +if (sym_at_0 && !is_local_label (sym_at_0))continue ;if (!sec->rsec) {
-->8-- -->8-- -->8-- -->8-- -->8-- -->8-- -->8-- -->8-- -->8-- -->8--
With that allowance, the section propagates through to the livepatch.ko:
$ llvm-readelf --wide --sections livepatch-klp-cfi-traps.ko | grep kcfi_traps
[ 5] __kcfi_traps PROGBITS 0000000000000000 0000b8 000004 00 AL 0 0 1
[27] .rela__kcfi_traps RELA 0000000000000000 001410 000018 18 I 51 5 8 $ llvm-readelf --wide --relocs livepatch-klp-cfi-traps.ko | \
awk '$0 ~ ".rela__kcfi_traps" {p=1; print; next} /^Relocation section/ {p=0} p'
Relocation section '.rela__kcfi_traps' at offset 0x1410 contains 1 entries:
Offset Info Type Symbol's Value Symbol's Name + Addend
0000000000000000 0000000b00000002 R_X86_64_PC32 0000000000000010 proc_single_show + 5bAdditionally, how about a follow-on patch that detects and warns when special sections should have been included, but are missing for whatever reason? Something like:
-->8-- -->8-- -->8-- -->8-- -->8-- -->8-- -->8-- -->8-- -->8-- -->8--
tools/objtool/klp-diff.c +39 -1
@@ -2029,12 +2029,33 @@ static int validate_special_section_klp_reloc(struct elfs *e, struct symbol *symreturn ret; } +/* True if any relocation in sec references a changed (included) function. */ +static bool special_section_refs_included_func (struct elf *elf,struct section *sec) +{ +struct reloc *reloc; + +if (!sec->rsec) +return false; + +for_each_reloc (sec->rsec, reloc) { +if (convert_reloc_sym (elf, reloc)) +continue ; + +if (reloc->sym->included &&is_func_sym (reloc->sym)) +return true; + } + +return false; +} +static int clone_special_section (struct elfs *e,struct section *patched_sec) {bool is_pfe = !strcmp (patremotes/origin/objtool/urgentched_sec->name,"__patchable_function_entries" );struct section *out_sec =NULL ;struct reloc *patched_reloc;struct symbol *patched_sym; +unsigned int cloned =0 ; +unsigned int skipped =0 ;/* * Extract all special section symbols (and their dependencies) which
@@ -2053,13 +2074,17 @@ static int clone_special_section(struct elfs *e, struct section *patched_sec) ret =validate_special_section_klp_reloc (e, patched_sym);if (ret <0 )return -1 ; -if (ret >0 ) +if (ret >0 ) { + skipped++;continue ; + } out_sym =clone_symbol (e, patched_sym, true);if (!out_sym)return -1 ; + cloned++; +if (!is_pfe || (out_sec && out_sec->sh.sh_link))continue ;
@@ -2075,6 +2100,19 @@ static int clone_special_section(struct elfs *e, struct section *patched_sec) out_sec->sh.sh_link = patched_reloc->sym->clone->sec->idx; } +/* +* Detect extraction failures: the patched object references +* changed functions from this section, but nothing was cloned and +* nothing was intentionally skipped (e.g. disabled tracepoints). +*/ +if (special_section_refs_included_func (e->patched, patched_sec) && + cloned ==0 && skipped ==0 ) { + out_sec =find_section_by_name (e->out, patched_sec->name); +if (!out_sec || !sec_size (out_sec)) +WARN ("%s: %s missing from output despite references to changed functions" , + objname, patched_sec->name); + } +return 0 ; }
-->8-- -->8-- -->8-- -->8-- -->8-- -->8-- -->8-- -->8-- -->8-- -->8-- H
k
wHappy to spin the .L* fix out as a separate patch post based on your tklp-build-arm64 or other branch (Fixes: da4326573ae8), with the warn-on-empty-extraction as an optional follow-up if you'd like that too.
--
Joe
Josh Poimboeuf
maintainerRe: [PATCH v3 14/21] objtool: Prevent kCFI hashes from being decoded as instructions
Quoted 41 lines · Joe Lawrence
The root cause is that
create_fake_symbols()skips the entire special section if any symbol exists at offset 0. But Clang places a .Ltmp* label at the start of .kcfi_traps, so no per-entry fake symbols are created andclone_special_sections()extracts nothing.static int create_fake_symbols(struct elf *elf) { ... /* * 2) Make symbols for sh_entsize, and simple arrays of pointers: */ entsize: for_each_sec(elf, sec) { unsigned int entry_size; unsigned long offset; if (!is_special_section(sec) || find_symbol_by_offset(sec, 0)) continue; $ llvm-readelf --wide --symbols klp-tmp/3-checksum-patched/vmlinux.o | \ awk '$7 == 74993' 93266: 0000000000000000 0 NOTYPE LOCAL DEFAULT 74993 .Ltmp78 93544: 0000000000000000 0 SECTION LOCAL DEFAULT 74993 .kcfi_trapsPossible fix: ignore .L* assembler-local labels at section offset 0 using the existing
is_local_label()helper.-->8-- -->8-- -->8-- -->8-- -->8-- -->8-- -->8-- -->8-- -->8-- -->8--
tools/objtool/klp-diff.c +1
-1@@ -1860,8 +1860,17 @@ static int create_fake_symbols(struct elf *elf)for_each_sec (elf, sec) {unsigned int entry_size;unsigned long offset; +struct symbol *sym_at_0; -if (!is_special_section (sec) ||find_symbol_by_offset (sec,0 ))
tools/objtool/klp-diff.c static int create_fake_symbols(struct elf *elf) · line 1874
Quoted 10 lines
tools/objtool/klp-diff.c +9
-0+if (!is_special_section (sec)) +continue ; + +/* +* Clang may place assembler-local .L* labels at offset 0; +* they must not prevent per-entry fake symbol creation. +*/ + sym_at_0 =find_symbol_by_offset (sec,0 ); +if (sym_at_0 && !is_local_label (sym_at_0))continue ;
Thanks, I'll fix this with my other series of fixes in the next version.
find_symbol_by_offset() might not work because there can be multiple symbols at offset 0. Instead we can just traverse the symbols in the section, looking for sym->fake.
Signature · 1 line
Josh
Josh Poimboeuf
maintainerPATCH v3 15/21 objtool/klp: Add arm64 support for prefix/PFE detection
In reply to Josh PoimboeufAdd arm64 support for detecting prefixed areas before functions (for kCFI or ftrace with call ops), and __patchable_function_entries (for ftrace with call ops or args).
--- tools/objtool/arch/x86/include/arch/elf.h | 2 + tools/objtool/elf.c | 13 ++ tools/objtool/include/objtool/elf.h | 22 +++ tools/objtool/klp-diff.c | 166 ++++++++++++++++++++-- 4 files changed, 192 insertions(+), 11 deletions(-)
tools/objtool/arch/x86/include/arch/elf.h +2 -0
@@ -10,4 +10,6 @@#define R_TEXT32 R_X86_64_PC32#define R_TEXT64 R_X86_64_PC32 +#define ARCH_HAS_PREFIX_SYMBOLS 1 +#endif /* _OBJTOOL_ARCH_ELF */
tools/objtool/elf.c +13 -0
@@ -274,6 +274,19 @@ struct symbol *find_func_containing(struct section *sec, unsigned long offset)return NULL ; } +struct symbol *find_data_mapping_sym (struct section *sec,unsigned long offset) +{ +struct rb_root_cached *tree = (struct rb_root_cached *)&sec->symbol_tree; +struct symbol *sym; + +__sym_for_each (sym, tree, offset, offset) { +if (sym->offset == offset &&is_data_mapping_sym (sym)) +return sym; + } + +return NULL ; +} +struct symbol *find_symbol_by_name (const struct elf *elf,const char *name) {struct symbol *sym;
tools/objtool/include/objtool/elf.h +22 -0
@@ -130,6 +130,7 @@ struct elf {struct list_head sections;struct list_head symbols;unsigned long num_relocs; +int pfe_offset;int symbol_bits;int symbol_name_bits;
@@ -229,6 +230,7 @@ struct reloc *find_reloc_by_dest(const struct elf *elf, struct section *sec, unsstruct reloc *find_reloc_by_dest_range (const struct elf *elf,struct section *sec,unsigned long offset,unsigned int len);struct symbol *find_func_containing (struct section *sec,unsigned long offset); +struct symbol *find_data_mapping_sym (struct section *sec,unsigned long offset);/* * Try to see if it's a whole archive (vmlinux.o or module).
@@ -295,6 +297,26 @@ static inline bool is_notype_sym(struct symbol *sym)return sym->type ==STT_NOTYPE ; } +/* +* ARM64 mapping symbols ($d, $x, $a, __pi_$d, etc) which mark transitions +* between code and data. +*/ +static inline bool is_mapping_sym (struct symbol *sym) +{ +return is_notype_sym (sym) &&strchr (sym->name,'$' ); +} + +static inline bool is_data_mapping_sym (struct symbol *sym) +{ +const char *dollar; + +if (!is_mapping_sym (sym)) +return false; + + dollar =strchr (sym->name,'$' ); +return dollar && dollar[1 ] =='d' ; +} +static inline bool is_global_sym (struct symbol *sym) {return sym->bind ==STB_GLOBAL ;
tools/objtool/klp-diff.c +155 -11
257 lines not on this page · raw message
Signature · 1 line
2.53.0
Josh Poimboeuf
maintainerPATCH v3 16/21 objtool/klp: Filter arm64 mapping symbols in find_symbol_by_offset()
In reply to Josh PoimboeufARM64 ELF objects contain $d/$x mapping symbols (STT_NOTYPE) at offset 0 in data/text sections. These aren't "real" symbols so filter them from find_symbol_by_offset(), consistent with the existing section symbol filter.
--- tools/objtool/elf.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-)
tools/objtool/elf.c +1 -1
@@ -159,7 +159,7 @@ struct symbol *find_symbol_by_offset(struct section *sec, unsigned long offset)struct symbol *sym;__sym_for_each (sym, tree, offset, offset) { -if (sym->offset == offset && !is_sec_sym (sym)) +if (sym->offset == offset && !is_sec_sym (sym) && !is_mapping_sym (sym))return sym->alias; }
Signature · 1 line
2.53.0
Re: [PATCH v3 16/21] objtool/klp: Filter arm64 mapping symbols in find_symbol_by_offset()
Quoted 7 lines · Josh Poimboeuf
ARM64 ELF objects contain $d/$x mapping symbols (
STT_NOTYPE) at offset 0 in data/text sections. These aren't "real" symbols so filter them fromfind_symbol_by_offset(), consistent with the existing section symbol filter.Signed-off-by Josh Poimboeuf <[email protected]>
Josh Poimboeuf
maintainerPATCH v3 17/21 objtool/klp: Don't correlate arm64 mapping symbols
In reply to Josh PoimboeufARM64 ELF files contain mapping symbols ($d, $x, $a, etc.) which mark transitions between code and data. There are thousands of them per object file, all sharing the same few names.
They aren't "real" symbols so there's no need to correlate them.
--- tools/objtool/klp-diff.c | 1 + 1 file changed, 1 insertion(+)
tools/objtool/klp-diff.c +1 -0
@@ -501,6 +501,7 @@ static bool dont_correlate(struct symbol *sym)is_prefix_func (sym) ||is_uncorrelated_static_local (sym) ||is_local_label (sym) || +is_mapping_sym (sym) ||is_string_sec (sym->sec) ||is_anonymous_rodata (sym) || is_initcall_sym(sym) ||
Signature · 1 line
2.53.0
[email protected]
AI reviewRe: [PATCH v3 17/21] objtool/klp: Don't correlate arm64 mapping symbols
Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider: - [Medium] The `is_mapping_sym()` function relies on an overly broad substring match (`strchr(sym->name…
Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
- [Medium] The is_mapping_sym() function relies on an overly broad substring match (strchr(sym->name, '$')), which risks falsely classifying any unannotated (STT_NOTYPE) symbol containing a $ as an ARM64 mapping symbol. --
commit fb925cb10f7a9c6ab8ecefca87d3cd3b4e4da879
Author: Josh Poimboeuf <[email protected]>
objtool/klp: Don't correlate arm64 mapping symbols
This commit updates the livepatch diff tool to skip the correlation of arm64 mapping symbols. These symbols mark transitions between code and data but are not standard symbols that require correlation.
tools/objtool/klp-diff.c static bool dont_correlate(struct symbol *sym) · line 504
Quoted 9 lines
tools/objtool/klp-diff.c +1
-0@@ -501,6 +501,7 @@ static bool dont_correlate(struct symbol *sym)is_prefix_func (sym) ||is_uncorrelated_static_local (sym) ||is_local_label (sym) || + is_mapping_sym(sym) ||
Does this code risk falsely classifying user-defined or macro-generated assembly labels as mapping symbols?
Looking at the definition:
tools/objtool/include/objtool/elf.h:is_mapping_sym() {
return is_notype_sym(sym) && strchr(sym->name, '$');
}Because it relies on a broad substring match, could this exclude valid unannotated STT_NOTYPE symbols containing a $ from correlation across all architectures?
Triggering a livepatching failure would require an unusual edge case, but would it be safer to use a stricter match for standard arm64 mapping symbol formats like $x or $d?
Quoted 2 lines
tools/objtool/klp-diff.c +0
-0is_string_sec (sym->sec) || is_anonymous_rodata(sym) ||
[ ... ]
Signature · 1 line
Sashiko AI review · https://sashiko.dev/#/patchset/[email protected]?part=17
Josh Poimboeuf
maintainerRe: [PATCH v3 17/21] objtool/klp: Don't correlate arm64 mapping symbols
Quoted 32 lines · On Fri, May 15, 2026 at 01:19:47AM +0000, [email protected] wrote:
Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
- [Medium] Theis_mapping_sym()function relies on an overly broad substring match (strchr(sym->name, '$')), which risks falsely classifying any unannotated (STT_NOTYPE) symbol containing a$as an ARM64 mapping symbol. --commit
fb925cb10f7a9c6ab8ecefca87d3cd3b4e4da879
Author: Josh Poimboeuf <[email protected]>objtool/klp: Don't correlate arm64 mapping symbols
This commit updates the livepatch diff tool to skip the correlation of arm64 mapping symbols. These symbols mark transitions between code and data but are not standard symbols that require correlation.
tools/objtool/klp-diff.c static bool dont_correlate(struct symbol *sym) · line 504
Quoted 9 lines
tools/objtool/klp-diff.c +1
-0@@ -501,6 +501,7 @@ static bool dont_correlate(struct symbol *sym)is_prefix_func (sym) ||is_uncorrelated_static_local (sym) ||is_local_label (sym) || + is_mapping_sym(sym) ||Does this code risk falsely classifying user-defined or macro-generated assembly labels as mapping symbols?
Looking at the definition:
tools/objtool/include/objtool/elf.h:is_mapping_sym() { return is_notype_sym(sym) && strchr(sym->name, '$'); }
Quoted 7 lines
Because it relies on a broad substring match, could this exclude valid unannotated
STT_NOTYPEsymbols containing a $ from correlation across all architectures?Triggering a livepatching failure would require an unusual edge case, but would it be safer to use a stricter match for standard arm64 mapping symbol formats like $x or $d?
Not realistic.
Signature · 1 line
Josh
Josh Poimboeuf
maintainerPATCH v3 18/21 objtool/klp: Clone inline alternative replacements
In reply to Josh PoimboeufUnlike x86-64, arm64 places alternative replacement instructions in .text, immediately after the affected function.
So if the replacement instructions have PC-relative branches without relocations, their offsets relative to the function have to remain constant.
Achieve that by cloning the function's alternative replacements immediately after cloning the function itself.
--- tools/objtool/elf.c | 9 +++-- tools/objtool/include/objtool/elf.h | 7 +++- tools/objtool/klp-diff.c | 63 ++++++++++++++++++++++++----- 3 files changed, 65 insertions(+), 14 deletions(-)
tools/objtool/elf.c +5 -4
@@ -1413,14 +1413,15 @@ int elf_add_string(struct elf *elf, struct section *strtab, const char *str)return -1 ; } - data =elf_add_data (elf, strtab, str,strlen (str) +1 ); + data =elf_add_data (elf, strtab, str,strlen (str) +1 , true);if (!data)return -1 ;return data - strtab->data->d_buf; } -void *elf_add_data (struct elf *elf,struct section *sec,const void *data,size_t size) +void *elf_add_data (struct elf *elf,struct section *sec,const void *data, +size_t size,bool align) {unsigned long offset, size_old, size_new, alloc_size_old, alloc_size_new;Elf_Scn *s;
@@ -1447,7 +1448,7 @@ void *elf_add_data(struct elf *elf, struct section *sec, const void *data, size_ } size_old = sec->data->d_size; - offset =ALIGN (size_old, sec->sh.sh_addralign); + offset =ALIGN (size_old, align ? sec->sh.sh_addralign :1 ); size_new = offset + size;if (!sec->data_overallocated)
@@ -1590,7 +1591,7 @@ static int elf_alloc_reloc(struct elf *elf, struct section *rsec)unsigned long nr_alloc_old =0 , nr_alloc_new;struct symbol *sym; -if (!elf_add_data (elf, rsec,NULL ,elf_rela_size (elf))) +if (!elf_add_data (elf, rsec,NULL ,elf_rela_size (elf), true))return -1 ; rsec->data->d_type =ELF_T_RELA ;
tools/objtool/include/objtool/elf.h +6 -1
@@ -106,6 +106,8 @@ struct symbol { u8 included : 1; u8 klp : 1; u8 dont_correlate : 1; + u8 fake : 1; + u8 unalign : 1;struct list_head pv_target;struct reloc *relocs;struct section *group_sec;
@@ -186,7 +188,7 @@ struct symbol *elf_create_symbol(struct elf *elf, const char *name,struct symbol *elf_create_section_symbol (struct elf *elf,struct section *sec);void *elf_add_data (struct elf *elf,struct section *sec,const void *data, -size_t size); +size_t size,bool align);int elf_find_string (struct elf *elf,struct section *strtab,const char *str);int elf_add_string (struct elf *elf,struct section *strtab,const char *str);
@@ -532,6 +534,9 @@ static inline void set_sym_next_reloc(struct reloc *reloc, struct reloc *next)#define sec_for_each_sym_from (sec, sym) \ list_for_each_entry_from(sym, &sec->symbol_list, list) +#define sec_for_each_sym_continue (sec, sym) \ + list_for_each_entry_continue(sym, &sec->symbol_list, list) +#define sec_prev_sym (sym) \ sym->sec && sym->list.prev != &sym->sec->symbol_list ? \ list_prev_entry(sym, list) : NULL
tools/objtool/klp-diff.c +54 -9
130 lines not on this page · raw message
Signature · 1 line
2.53.0
Josh Poimboeuf
maintainerPATCH v3 19/21 objtool/klp: Introduce objtool for arm64
In reply to Josh PoimboeufAdd basic support for arm64 on objtool. Only "objtool klp" subcommands are currently supported.
--- arch/arm64/Kconfig | 2 + tools/objtool/Makefile | 4 + tools/objtool/arch/arm64/Build | 2 + tools/objtool/arch/arm64/decode.c | 177 ++++++++++++++++++ .../arch/arm64/include/arch/cfi_regs.h | 11 ++ tools/objtool/arch/arm64/include/arch/elf.h | 15 ++ .../objtool/arch/arm64/include/arch/special.h | 21 +++ tools/objtool/arch/arm64/special.c | 21 +++ 8 files changed, 253 insertions(+) create mode 100644 tools/objtool/arch/arm64/Build create mode 100644 tools/objtool/arch/arm64/decode.c create mode 100644 tools/objtool/arch/arm64/include/arch/cfi_regs.h create mode 100644 tools/objtool/arch/arm64/include/arch/elf.h create mode 100644 tools/objtool/arch/arm64/include/arch/special.h create mode 100644 tools/objtool/arch/arm64/special.c
arch/arm64/Kconfig +2 -0
@@ -210,9 +210,11 @@ config ARM64 select HAVE_HW_BREAKPOINT if PERF_EVENTS select HAVE_IOREMAP_PROT select HAVE_IRQ_TIME_ACCOUNTING + select HAVE_KLP_BUILD select HAVE_LIVEPATCH select HAVE_MOD_ARCH_SPECIFIC select HAVE_NMI + select HAVE_OBJTOOL select HAVE_PERF_EVENTS select HAVE_PERF_EVENTS_NMI if ARM64_PSEUDO_NMI select HAVE_PERF_REGS
tools/objtool/Makefile +4 -0
@@ -11,6 +11,10 @@ ifeq ($(SRCARCH),loongarch) BUILD_ORC := y endif +ifeq ($(SRCARCH),arm64) + ARCH_HAS_KLP := y +endif + ifeq ($(ARCH_HAS_KLP),y) HAVE_XXHASH = $(shell printf "$(pound)include <xxhash.h>\nXXH3_state_t *state;int main() {}" | \ $(HOSTCC) $(HOSTCFLAGS) -xc - -o /dev/null -lxxhash 2> /dev/null && echo y || echo n)
tools/objtool/arch/arm64/Build new file +2 -0
@@ -0,0 +1,2 @@ +objtool-y += decode.o +objtool-y += special.o
tools/objtool/arch/arm64/decode.c new file +177 -0
177 lines not on this page · raw message
tools/objtool/arch/arm64/include/arch/cfi_regs.h new file +11 -0
@@ -0,0 +1,11 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +#ifndef _OBJTOOL_ARCH_CFI_REGS_H +#define _OBJTOOL_ARCH_CFI_REGS_H + +/* These aren't actually used for arm64 */ +#define CFI_BP 0 +#define CFI_SP 0 +#define CFI_RA 0 +#define CFI_NUM_REGS 2 + +#endif /* _OBJTOOL_ARCH_CFI_REGS_H */
tools/objtool/arch/arm64/include/arch/elf.h new file +15 -0
@@ -0,0 +1,15 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +#ifndef _OBJTOOL_ARCH_ELF_H +#define _OBJTOOL_ARCH_ELF_H + +#define R_NONE R_AARCH64_NONE +#define R_ABS64 R_AARCH64_ABS64 +#define R_ABS32 R_AARCH64_ABS32 +#define R_DATA32 R_AARCH64_PREL32 +#define R_DATA64 R_AARCH64_PREL64 +#define R_TEXT32 R_AARCH64_PREL32 +#define R_TEXT64 R_AARCH64_PREL64 + +#define ARCH_HAS_INLINE_ALTS 1 + +#endif /* _OBJTOOL_ARCH_ELF_H */
tools/objtool/arch/arm64/include/arch/special.h new file +21 -0
@@ -0,0 +1,21 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +#ifndef _OBJTOOL_ARCH_SPECIAL_H +#define _OBJTOOL_ARCH_SPECIAL_H + +#define EX_ENTRY_SIZE 12 +#define EX_ORIG_OFFSET 0 +#define EX_NEW_OFFSET 4 + +#define JUMP_ENTRY_SIZE 16 +#define JUMP_ORIG_OFFSET 0 +#define JUMP_NEW_OFFSET 4 +#define JUMP_KEY_OFFSET 8 + +#define ALT_ENTRY_SIZE 12 +#define ALT_ORIG_OFFSET 0 +#define ALT_NEW_OFFSET 4 +#define ALT_FEATURE_OFFSET 8 +#define ALT_ORIG_LEN_OFFSET 10 +#define ALT_NEW_LEN_OFFSET 11 + +#endif /* _OBJTOOL_ARCH_SPECIAL_H */
tools/objtool/arch/arm64/special.c new file +21 -0
@@ -0,0 +1,21 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +#include <objtool/special.h> + +bool arch_support_alt_relocation (struct special_alt *special_alt, +struct instruction *insn, +struct reloc *reloc) +{ +return true; +} + +struct reloc *arch_find_switch_table (struct objtool_file *file, +struct instruction *insn, +unsigned long *table_size) +{ +return NULL ; +} + +const char *arch_cpu_feature_name (int feature_number) +{ +return NULL ; +}
Signature · 1 line
2.53.0
[email protected]
AI reviewRe: [PATCH v3 19/21] objtool/klp: Introduce objtool for arm64
Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider: - [Medium] The arm64 instruction decoder misclassifies trap instructions (e.g., `brk #0x800` used for…
Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
- [Medium] The arm64 instruction decoder misclassifies trap instructions (e.g., brk #0x800 used for BUG()) as INSN_OTHER instead of INSN_BUG. --
commit dd63f0c03bab6a47f34cfcb310454460653638ed
Author: Josh Poimboeuf <[email protected]>
objtool/klp: Introduce objtool for arm64
This commit adds basic support for arm64 in objtool, initially enabling only the "objtool klp" subcommands.
Quoted 5 lines
tools/objtool/arch/arm64/decode.c new file +0
-0
[ ... ]
Quoted 4 lines
tools/objtool/arch/arm64/decode.c new file +4
-0+int arch_decode_instruction (struct objtool_file *file,const struct section *sec, +unsigned long offset,unsigned int maxlen, +struct instruction *insn) +{
[ ... ]
Quoted 4 lines
tools/objtool/arch/arm64/decode.c new file +4
-0+if (ins ==0xd503201f ) { +/* NOP: static branch */ + insn->type =INSN_NOP ; + }else if ((ins &0xfc000000 ) ==0x14000000 ) {
[ ... ]
tools/objtool/arch/arm64/decode.c line 87
Quoted 7 lines
tools/objtool/arch/arm64/decode.c new file +7
-0+ }else if ((ins &0x7e000000 ) ==0x36000000 ) { +/* TBZ/TBNZ: intra-TU sibling call */ + insn->type =INSN_JUMP_CONDITIONAL ; + insn->immediate =sign_extend64 ((ins >>5 ) &0x3fff ,13 ); + }else { + insn->type =INSN_OTHER ; + }
Does this code misclassify trap instructions?
On arm64, BUG() is implemented using the brk instruction (such as brk #0x800). Since the decoder doesn't appear to check for brk, it will fall through to the else block and classify it as INSN_OTHER.
If INSN_BUG is misclassified as a normal instruction, won't this lose dead-end propagation and corrupt objtool's control-flow analysis?
Quoted 4 lines
tools/objtool/arch/arm64/decode.c new file +4
-0+ + insn->len =4 ; +return 0 ; +}
Signature · 1 line
Sashiko AI review · https://sashiko.dev/#/patchset/[email protected]?part=19
Josh Poimboeuf
maintainerRe: [PATCH v3 19/21] objtool/klp: Introduce objtool for arm64
Quoted 44 lines · On Fri, May 15, 2026 at 02:08:45AM +0000, [email protected] wrote:
Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
- [Medium] The arm64 instruction decoder misclassifies trap instructions (e.g.,brk #0x800used forBUG()) asINSN_OTHERinstead ofINSN_BUG. --commit
dd63f0c03bab6a47f34cfcb310454460653638ed
Author: Josh Poimboeuf <[email protected]>objtool/klp: Introduce objtool for arm64
This commit adds basic support for arm64 in objtool, initially enabling only the "objtool klp" subcommands.
Quoted 5 lines
tools/objtool/arch/arm64/decode.c new file +0
-0[ ... ]
Quoted 4 lines
tools/objtool/arch/arm64/decode.c new file +4
-0+int arch_decode_instruction (struct objtool_file *file,const struct section *sec, +unsigned long offset,unsigned int maxlen, +struct instruction *insn) +{[ ... ]
Quoted 4 lines
tools/objtool/arch/arm64/decode.c new file +4
-0+if (ins ==0xd503201f ) { +/* NOP: static branch */ + insn->type =INSN_NOP ; + }else if ((ins &0xfc000000 ) ==0x14000000 ) {[ ... ]
tools/objtool/arch/arm64/decode.c line 87
Quoted 7 lines
tools/objtool/arch/arm64/decode.c new file +7
-0+ }else if ((ins &0x7e000000 ) ==0x36000000 ) { +/* TBZ/TBNZ: intra-TU sibling call */ + insn->type =INSN_JUMP_CONDITIONAL ; + insn->immediate =sign_extend64 ((ins >>5 ) &0x3fff ,13 ); + }else { + insn->type =INSN_OTHER ; + }Does this code misclassify trap instructions?
Quoted 6 lines
On arm64,
BUG()is implemented using the brk instruction (such as brk #0x800). Since the decoder doesn't appear to check for brk, it will fall through to the else block and classify it asINSN_OTHER.If
INSN_BUGis misclassified as a normal instruction, won't this lose dead-end propagation and corrupt objtool's control-flow analysis?
Nope, as the comment above this says:
/* * These are the bare minimum needed for static branch detection and * checksum calculations. */
For arm64 we don't have any reason to care about INSN_BUG.
Signature · 1 line
Josh
Josh Poimboeuf
maintainerPATCH v3 20/21 klp-build: Support cross-compilation
In reply to Josh PoimboeufAdd support for cross-compilation. The user must export ARCH, and either CROSS_COMPILE or LLVM as needed.
--- scripts/livepatch/klp-build | 22 +++++++++++++++++++++- 1 file changed, 21 insertions(+), 1 deletion(-)
scripts/livepatch/klp-build +21 -1
@@ -432,6 +432,25 @@ validate_patches() { revert_patches } +cross_compile_init() { + if [[ -v LLVM && -n "$LLVM" ]]; then + local prefix="" + local suffix="" + + if [[ "$LLVM" == */ ]]; then + # LLVM=/path/to/bin/ + prefix="$LLVM" + elif [[ "$LLVM" == -* ]]; then + # LLVM=-14 + suffix="$LLVM" + fi + + OBJCOPY="${prefix}llvm-objcopy${suffix}" + else + OBJCOPY="${CROSS_COMPILE:-}objcopy" + fi +} + do_init() { # We're not yet smart enough to handle anything other than in-tree # builds in pwd.
@@ -462,6 +481,7 @@ do_init() { validate_config set_module_name set_kernelversion + cross_compile_init } # Refresh the patch hunk headers, specifically the line numbers and counts.
@@ -871,7 +891,7 @@ build_patch_module() { cp -f "$kmod_file" "$kmod_file.orig" # Work around issue where slight .config change makes corrupt BTF - objcopy --remove-section=.BTF "$kmod_file" + "$OBJCOPY" --remove-section=.BTF "$kmod_file" # Fix (and work around) linker wreckage for klp syms / relocs "$OBJTOOL" klp post-link "$kmod_file" || die "objtool klp post-link failed"
Signature · 1 line
2.53.0
Josh Poimboeuf
maintainerPATCH v3 21/21 klp-build: Add arm64 syscall patching macro
In reply to Josh PoimboeufAdd arm64 support for KLP_SYSCALL_DEFINEx(), mirroring the arm64 __SYSCALL_DEFINEx() pattern from arch/arm64/include/asm/syscall_wrapper.h.
--- include/linux/livepatch_helpers.h | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+)
include/linux/livepatch_helpers.h +19 -0
@@ -72,6 +72,25 @@ } \ static inline long __klp_do_sys##name(__MAP(x,__SC_DECL,__VA_ARGS__)) +#elif defined(CONFIG_ARM64 ) + +#define __KLP_SYSCALL_DEFINEx (x, name, ...) \ + static long __se_sys##name(__MAP(x,__SC_LONG,__VA_ARGS__)); \ + static inline long __klp_do_sys##name(__MAP(x,__SC_DECL,__VA_ARGS__));\ + asmlinkage long __arm64_sys##name(const struct pt_regs *regs); \ + asmlinkage long __arm64_sys##name(const struct pt_regs *regs) \ + { \ + return __se_sys##name(SC_ARM64_REGS_TO_ARGS(x,__VA_ARGS__));\ + } \ + static long __se_sys##name(__MAP(x,__SC_LONG,__VA_ARGS__)) \ + { \ + long ret = __klp_do_sys##name(__MAP(x,__SC_CAST,__VA_ARGS__));\ + __MAP(x,__SC_TEST,__VA_ARGS__); \ + __PROTECT(x, ret,__MAP(x,__SC_ARGS,__VA_ARGS__)); \ + return ret; \ + } \ + static inline long __klp_do_sys##name(__MAP(x,__SC_DECL,__VA_ARGS__)) +#endif #endif /* _LINUX_LIVEPATCH_HELPERS_H */
Signature · 1 line
2.53.0
[email protected]
AI reviewRe: [PATCH v3 21/21] klp-build: Add arm64 syscall patching macro
Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider: - [Medium] The arm64 __KLP_SYSCALL_DEFINEx macro omits the ALLOW_ERROR_INJECTION declaration, silently…
Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
- [Medium] The arm64 __KLP_SYSCALL_DEFINEx macro omits the ALLOW_ERROR_INJECTION declaration, silently breaking BPF error injection for livepatched syscalls. --
commit b4d1a9ea352f196a7fd3e7198fdade51440d08e5
Author: Josh Poimboeuf <[email protected]>
klp-build: Add arm64 syscall patching macro
This commit adds arm64 support for KLP_SYSCALL_DEFINEx(). The commit message states it is mirroring the arm64 __SYSCALL_DEFINEx() pattern from arch/arm64/include/asm/syscall_wrapper.h.
Quoted 8 lines
include/linux/livepatch_helpers.h +0
-0@@ -72,6 +72,25 @@ } \ static inline long __klp_do_sys##name(__MAP(x,__SC_DECL,__VA_ARGS__))
include/linux/livepatch_helpers.h line 84
Quoted 10 lines
include/linux/livepatch_helpers.h +10
-0+#elif defined(CONFIG_ARM64 ) + +#define __KLP_SYSCALL_DEFINEx (x, name, ...) \ + static long __se_sys##name(__MAP(x,__SC_LONG,__VA_ARGS__)); \ + static inline long __klp_do_sys##name(__MAP(x,__SC_DECL,__VA_ARGS__));\ + asmlinkage long __arm64_sys##name(const struct pt_regs *regs); \ + asmlinkage long __arm64_sys##name(const struct pt_regs *regs) \ + { \ + return __se_sys##name(SC_ARM64_REGS_TO_ARGS(x,__VA_ARGS__));\ + } \
Does this macro definition omit the ALLOW_ERROR_INJECTION declaration?
The standard arm64 __SYSCALL_DEFINEx() macro explicitly registers the top-level syscall handler with ALLOW_ERROR_INJECTION() to enable BPF error injection workflows like bpf_override_return(). The x86 implementation correctly preserves this functionality.
Because this macro is missing that declaration, it looks like it might introduce a regression where BPF cannot inject errors into the replacement syscalls in a livepatch module on arm64.
[ ... ]
Signature · 1 line
Sashiko AI review · https://sashiko.dev/#/patchset/[email protected]?part=21
Josh Poimboeuf
maintainerRe: [PATCH v3 21/21] klp-build: Add arm64 syscall patching macro
Quoted 34 lines · On Fri, May 15, 2026 at 02:44:39AM +0000, [email protected] wrote:
Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
- [Medium] The arm64__KLP_SYSCALL_DEFINExmacro omits theALLOW_ERROR_INJECTIONdeclaration, silently breaking BPF error injection for livepatched syscalls. --commit
b4d1a9ea352f196a7fd3e7198fdade51440d08e5
Author: Josh Poimboeuf <[email protected]>klp-build: Add arm64 syscall patching macro
This commit adds arm64 support for
KLP_SYSCALL_DEFINEx(). The commit message states it is mirroring the arm64__SYSCALL_DEFINEx()pattern fromarch/arm64/include/asm/syscall_wrapper.h.Quoted 8 lines
include/linux/livepatch_helpers.h +0
-0@@ -72,6 +72,25 @@ } \ static inline long __klp_do_sys##name(__MAP(x,__SC_DECL,__VA_ARGS__))include/linux/livepatch_helpers.h line 84
Quoted 10 lines
include/linux/livepatch_helpers.h +10
-0+#elif defined(CONFIG_ARM64 ) + +#define __KLP_SYSCALL_DEFINEx (x, name, ...) \ + static long __se_sys##name(__MAP(x,__SC_LONG,__VA_ARGS__)); \ + static inline long __klp_do_sys##name(__MAP(x,__SC_DECL,__VA_ARGS__));\ + asmlinkage long __arm64_sys##name(const struct pt_regs *regs); \ + asmlinkage long __arm64_sys##name(const struct pt_regs *regs) \ + { \ + return __se_sys##name(SC_ARM64_REGS_TO_ARGS(x,__VA_ARGS__));\ + } \Does this macro definition omit the
ALLOW_ERROR_INJECTIONdeclaration?
Quoted 8 lines
The standard arm64
__SYSCALL_DEFINEx()macro explicitly registers the top-level syscall handler withALLOW_ERROR_INJECTION()to enable BPF error injection workflows likebpf_override_return(). The x86 implementation correctly preserves this functionality.Because this macro is missing that declaration, it looks like it might introduce a regression where BPF cannot inject errors into the replacement syscalls in a livepatch module on arm64.
Fixed.
Signature · 1 line
Josh