From 165affd6f095323d953b0ed5823ec4d0db3b7d0d Mon Sep 17 00:00:00 2001 From: Josh Poimboeuf Date: Sun, 2 Aug 2026 20:24:23 -0700 Subject: [PATCH 01/20] objtool/klp: Fix module name normalization for paths with dots When .modinfo has no "name=" tag, __find_modname() falls back to converting the object's build-tree path to a runtime module name by stripping directory components, converting '-' to '_' and truncating the file extension. It does all that in a single pass over the entire path, so the first dot anywhere in the path ends the name. For an object built in a directory whose name contains a dot, e.g. "drivers/foo-1.0/bar.o", the result is a bogus module name. Strip the directory components up front so only the basename is scanned for the extension separator. Fixes: dd590d4d57eb ("objtool/klp: Introduce klp diff subcommand for diffing object files") Reported-by: Sashiko Signed-off-by: Josh Poimboeuf Signed-off-by: Ingo Molnar Cc: live-patching@vger.kernel.org Link: https://patch.msgid.link/9017b4609553bed16674e8f924d34691cbc2b2c1.1785727106.git.jpoimboe@kernel.org --- tools/objtool/klp-diff.c | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/tools/objtool/klp-diff.c b/tools/objtool/klp-diff.c index f8787d7d1454..aeb99d572300 100644 --- a/tools/objtool/klp-diff.c +++ b/tools/objtool/klp-diff.c @@ -1140,7 +1140,7 @@ static struct export *find_export(struct symbol *sym) static const char *__find_modname(struct elfs *e) { struct section *sec; - char *name; + char *name, *slash; sec = find_section_by_name(e->orig, ".modinfo"); if (!sec) { @@ -1158,10 +1158,12 @@ static const char *__find_modname(struct elfs *e) return NULL; } + slash = strrchr(name, '/'); + if (slash) + name = slash + 1; + for (char *c = name; *c; c++) { - if (*c == '/') - name = c + 1; - else if (*c == '-') + if (*c == '-') *c = '_'; else if (*c == '.') { *c = '\0'; From 8668bf91e0508abeb8e98b4edd89032be02228a1 Mon Sep 17 00:00:00 2001 From: Joe Lawrence Date: Sun, 2 Aug 2026 20:24:24 -0700 Subject: [PATCH 02/20] objtool/klp: Normalize Module.symvers paths to module names Module.symvers contains build-tree object paths as module identifiers (e.g., "arch/x86/kvm/kvm") rather than runtime module names ("kvm"). Objtool's clone_reloc_klp() uses this field directly for exported symbols, while unexported symbols correctly go through __find_modname(). This means that exported symbol relocations may land in a .klp.rela section named with the build path rather than the module name. That is a crash waiting to happen: the kernel's livepatch loader silently skips this relocation because it doesn't match the expected klp_object name. The unresolved relocation sits in the newly activated code, crashing when executed. Normalize export->mod at Module.symvers read time using the same logic as __find_modname() (refactored into a shared normalize_modname() helper). Fixes: dd590d4d57eb ("objtool/klp: Introduce klp diff subcommand for diffing object files") Reported-by: Ben Procknow Signed-off-by: Joe Lawrence Signed-off-by: Josh Poimboeuf Signed-off-by: Ingo Molnar Reviewed-by: Miroslav Benes Cc: live-patching@vger.kernel.org Link: https://patch.msgid.link/dbe1b72931bd3c31b751fd0729613d9f2226fff6.1785727106.git.jpoimboe@kernel.org --- tools/objtool/klp-diff.c | 49 ++++++++++++++++++++++++++++------------ 1 file changed, 34 insertions(+), 15 deletions(-) diff --git a/tools/objtool/klp-diff.c b/tools/objtool/klp-diff.c index aeb99d572300..15d37d955af0 100644 --- a/tools/objtool/klp-diff.c +++ b/tools/objtool/klp-diff.c @@ -83,6 +83,35 @@ static char *escape_str(const char *orig) return new; } +/* + * Convert a build-tree object path to a runtime module name: strip + * directory components, replace '-' with '_', and remove file + * extensions. Examples: + * + * "arch/x86/kvm/kvm" -> "kvm" + * "arch/x86/kvm/kvm-intel" -> "kvm_intel". + * + * Used by read_exports() to normalize Module.symvers entries and by + * __find_modname() as a fallback when .modinfo lacks a "name=" tag. + */ +static char *normalize_modname(char *name) +{ + char *slash = strrchr(name, '/'); + + if (slash) + name = slash + 1; + + for (char *c = name; *c; c++) { + if (*c == '-') + *c = '_'; + else if (*c == '.') { + *c = '\0'; + break; + } + } + return name; +} + static int read_exports(void) { const char *symvers = "Module.symvers"; @@ -150,6 +179,9 @@ static int read_exports(void) return -1; } + if (strcmp(export->mod, "vmlinux")) + export->mod = normalize_modname(export->mod); + export->sym = strdup(sym); if (!export->sym) { ERROR_GLIBC("strdup"); @@ -1140,7 +1172,7 @@ static struct export *find_export(struct symbol *sym) static const char *__find_modname(struct elfs *e) { struct section *sec; - char *name, *slash; + char *name; sec = find_section_by_name(e->orig, ".modinfo"); if (!sec) { @@ -1158,20 +1190,7 @@ static const char *__find_modname(struct elfs *e) return NULL; } - slash = strrchr(name, '/'); - if (slash) - name = slash + 1; - - for (char *c = name; *c; c++) { - if (*c == '-') - *c = '_'; - else if (*c == '.') { - *c = '\0'; - break; - } - } - - return name; + return normalize_modname(name); } /* Get the object's module name as defined by the kernel (and klp_object) */ From 5ca8c91d1ea6534842e7e0065d15104d802506cd Mon Sep 17 00:00:00 2001 From: Josh Poimboeuf Date: Sun, 2 Aug 2026 20:24:25 -0700 Subject: [PATCH 03/20] objtool/klp: Fix false module dependencies caused by dead relocs When creating a klp reloc, klp-diff keeps the original relocation but converts the referenced symbol to an UNDEF/WEAK placeholder tombstone symbol, which gets fully disabled later by klp post-link. The tombstone symbol is only needed to avoid confusing objtool when it does the final run on the patch module. However, for references to exported symbols, modpost sees the reference to the tombstone symbol as a real reference to an exported symbol, resulting in a false module dependency getting created. Further, for a reference to a tombstone symbol which is exported into a module namespace, e.g. via EXPORT_SYMBOL_FOR_KVM_INTERNAL(), modpost can't satisfy the dependency, resulting in a warning like the following: module ... uses symbol kvm_flush_remote_tlbs from namespace module:kvm-amd,kvm-intel, but does not import it. Rename the placeholder tombstone symbols to ".klp.tombstone." so modpost no longer recognizes them. Fixes: dd590d4d57eb ("objtool/klp: Introduce klp diff subcommand for diffing object files") Reported-by: Ben Procknow Reported-by: Joe Lawrence Signed-off-by: Josh Poimboeuf Signed-off-by: Ingo Molnar Cc: live-patching@vger.kernel.org Link: https://lore.kernel.org/20260720145658.1103243-5-joe.lawrence@redhat.com Link: https://patch.msgid.link/9548393f4d89ec3b498f4f69aa6ef6b9bb7150fe.1785727106.git.jpoimboe@kernel.org --- tools/objtool/elf.c | 13 +++++++++++++ tools/objtool/include/objtool/klp.h | 2 ++ tools/objtool/klp-diff.c | 16 ++++++++++++---- 3 files changed, 27 insertions(+), 4 deletions(-) diff --git a/tools/objtool/elf.c b/tools/objtool/elf.c index 33c95a74a51b..a791f4ea6ec1 100644 --- a/tools/objtool/elf.c +++ b/tools/objtool/elf.c @@ -23,6 +23,7 @@ #include #include #include +#include #include static ssize_t demangled_name_len(const char *name); @@ -626,6 +627,18 @@ static int read_symbols(struct elf *elf) return -1; } + /* + * "klp diff" renames the placeholder symbols of KLP relocs to + * hide them from modpost. Hide the prefix from the rest of + * objtool so its many name-based heuristics (noreturns, + * uaccess safe list, ...) still see the original symbol name. + * + * st_name is left alone, so the renamed symbol is preserved in + * the output file. + */ + if (strstarts(sym->name, KLP_TOMBSTONE_PREFIX)) + sym->name += strlen(KLP_TOMBSTONE_PREFIX); + if ((sym->sym.st_shndx > SHN_UNDEF && sym->sym.st_shndx < SHN_LORESERVE) || (shndx_data && sym->sym.st_shndx == SHN_XINDEX)) { diff --git a/tools/objtool/include/objtool/klp.h b/tools/objtool/include/objtool/klp.h index 6f60cf05db86..aab6db42052d 100644 --- a/tools/objtool/include/objtool/klp.h +++ b/tools/objtool/include/objtool/klp.h @@ -23,6 +23,8 @@ #define KLP_RELOCS_SEC "__klp_relocs" #define KLP_STRINGS_SEC ".rodata.klp.str1.1" +#define KLP_TOMBSTONE_PREFIX ".klp.tombstone." + struct klp_reloc { void *offset; void *sym; diff --git a/tools/objtool/klp-diff.c b/tools/objtool/klp-diff.c index 15d37d955af0..75ba0e060a34 100644 --- a/tools/objtool/klp-diff.c +++ b/tools/objtool/klp-diff.c @@ -1362,6 +1362,7 @@ static int clone_reloc_klp(struct elfs *e, struct reloc *patched_reloc, s64 addend = reloc_addend(patched_reloc); const char *sym_modname, *sym_orig_name; static struct section *klp_relocs; + char tombstone_name[SYM_NAME_LEN]; struct symbol *sym, *klp_sym; unsigned long klp_reloc_off; char sym_name[SYM_NAME_LEN]; @@ -1376,15 +1377,22 @@ static int clone_reloc_klp(struct elfs *e, struct reloc *patched_reloc, /* * Keep the original reloc intact for now to avoid breaking objtool run * which relies on proper relocations for many of its features. This - * will be disabled later by "objtool klp post-link". + * reloc now targets a functionally dead tombstone symbol and will be + * disabled later by "objtool klp post-link". * - * Convert it to UNDEF (and WEAK to avoid modpost warnings). + * Convert the symbol to UNDEF/WEAK and rename to + * .klp.tombstone.sym_name to prevent modpost from printing warnings or + * creating false module dependencies. The prefix is hidden from the + * objtool run itself by read_symbols(). */ sym = patched_sym->clone; if (!sym) { - /* STB_WEAK: avoid modpost undefined symbol warnings */ - sym = elf_create_symbol(e->out, patched_sym->name, NULL, + if (snprintf_check(tombstone_name, SYM_NAME_LEN, + KLP_TOMBSTONE_PREFIX "%s", patched_sym->name)) + return -1; + + sym = elf_create_symbol(e->out, tombstone_name, NULL, STB_WEAK, patched_sym->type, 0, 0); if (!sym) return -1; From f5f762fc93d0b81d30b815a2f96320909ad10eb3 Mon Sep 17 00:00:00 2001 From: Josh Poimboeuf Date: Sun, 2 Aug 2026 20:24:26 -0700 Subject: [PATCH 04/20] objtool/klp: Skip hidden directories when finding objects klp-build's find_objects() scans the whole tree for vmlinux.o and .ko files, pruning only klp-tmp/ and .git/. Development tools can leave other dot-directories in the tree. Kernel objects never live under hidden directories, so prune them all. Signed-off-by: Josh Poimboeuf Signed-off-by: Ingo Molnar Cc: live-patching@vger.kernel.org Link: https://patch.msgid.link/6c8eaa9feb17e3811f4ef7733fd7288b7f489183.1785727106.git.jpoimboe@kernel.org --- scripts/livepatch/klp-build | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/scripts/livepatch/klp-build b/scripts/livepatch/klp-build index c4a7acf8edc3..a8c103ce7763 100755 --- a/scripts/livepatch/klp-build +++ b/scripts/livepatch/klp-build @@ -575,8 +575,9 @@ find_objects() { local opts=("$@") # Find root-level vmlinux.o and non-root-level .ko files, - # excluding klp-tmp/ and .git/ - find "$PWD" \( -path "$TMP_DIR" -o -path "$PWD/.git" -o -regex "$PWD/[^/][^/]*\.ko" \) -prune -o \ + # excluding klp-tmp/ and hidden directories. + find "$PWD" -mindepth 1 \ + \( -path "$TMP_DIR" -o -name ".*" -o -regex "$PWD/[^/][^/]*\.ko" \) -prune -o \ -type f "${opts[@]}" \ \( -name "*.ko" -o -path "$PWD/vmlinux.o" \) \ -printf '%P\n' From 029223d301620bc4e1086696047b0d5d6eba5edd Mon Sep 17 00:00:00 2001 From: Josh Poimboeuf Date: Sun, 2 Aug 2026 20:24:27 -0700 Subject: [PATCH 05/20] objtool/klp: Add .klp.symid for sympos disambiguation Livepatch identifies a duplicate-named symbol by its position (sympos) among same-named kallsyms entries, which for vmlinux are counted in ascending address order in the final linked kernel. That order can't be reliably derived from vmlinux.o: the final link reorders sub-sections (.text.unlikely*, .data..*, etc). Bridge the gap with a new .klp.symid section which can be used to correlate symbols between vmlinux.o and vmlinux so that klp-diff can reliably determine the sympos. The table can't survive --gc-sections: keeping it alive would keep every duplicate-named symbol's section alive, so the reference kernel would stop matching the one which ships. klp-build rejects CONFIG_LD_DEAD_CODE_DATA_ELIMINATION instead. Nothing is lost today: x86_64 is the only HAVE_KLP_BUILD arch and doesn't select HAVE_LD_DEAD_CODE_DATA_ELIMINATION, arm64 and s390 have never selected it either, and on powerpc, it's still EXPERIMENTAL and disabled by every distro kernel. This is the build-time half of reliable vmlinux sympos computation; "objtool klp diff" will consume the table in a subsequent commit. Signed-off-by: Josh Poimboeuf Signed-off-by: Ingo Molnar Cc: live-patching@vger.kernel.org Link: https://patch.msgid.link/64d50f077b569f47883c015cdb7079edb068efe8.1785727106.git.jpoimboe@kernel.org --- include/asm-generic/vmlinux.lds.h | 10 +- scripts/Makefile.vmlinux_o | 3 + scripts/livepatch/klp-build | 5 + scripts/mod/modpost.c | 1 + tools/objtool/Build | 1 + tools/objtool/builtin-check.c | 7 ++ tools/objtool/check.c | 7 ++ tools/objtool/include/objtool/builtin.h | 1 + tools/objtool/include/objtool/klp.h | 15 +++ tools/objtool/klp-symid.c | 117 ++++++++++++++++++++++++ 10 files changed, 166 insertions(+), 1 deletion(-) create mode 100644 tools/objtool/klp-symid.c diff --git a/include/asm-generic/vmlinux.lds.h b/include/asm-generic/vmlinux.lds.h index 5659f4b5a125..ee9c5d354a85 100644 --- a/include/asm-generic/vmlinux.lds.h +++ b/include/asm-generic/vmlinux.lds.h @@ -839,12 +839,20 @@ .stab.index 0 : { *(.stab.index) } \ .stab.indexstr 0 : { *(.stab.indexstr) } +#ifdef CONFIG_KLP_BUILD +#define KLP_SYMID \ + .klp.symid 0 : { *(.klp.symid) } +#else +#define KLP_SYMID +#endif + /* Required sections not related to debugging. */ #define ELF_DETAILS \ .comment 0 : { *(.comment) } \ .symtab 0 : { *(.symtab) } \ .strtab 0 : { *(.strtab) } \ - .shstrtab 0 : { *(.shstrtab) } + .shstrtab 0 : { *(.shstrtab) } \ + KLP_SYMID #define MODINFO \ .modinfo : { *(.modinfo) . = ALIGN(8); } diff --git a/scripts/Makefile.vmlinux_o b/scripts/Makefile.vmlinux_o index 527352c222ff..24a3a4fd271c 100644 --- a/scripts/Makefile.vmlinux_o +++ b/scripts/Makefile.vmlinux_o @@ -47,6 +47,9 @@ endif vmlinux-objtool-args-$(CONFIG_NOINSTR_VALIDATION) += --noinstr \ $(if $(or $(CONFIG_MITIGATION_UNRET_ENTRY),$(CONFIG_MITIGATION_SRSO)), --unret) +# Only used for builds initiated by klp-build +vmlinux-objtool-args-$(if $(KLP_SYMIDS),y) += --klp-symids + objtool-args = $(vmlinux-objtool-args-y) --link # Link of vmlinux.o used for section mismatch analysis diff --git a/scripts/livepatch/klp-build b/scripts/livepatch/klp-build index a8c103ce7763..f94e324ff53c 100755 --- a/scripts/livepatch/klp-build +++ b/scripts/livepatch/klp-build @@ -271,6 +271,9 @@ validate_config() { [[ -v CONFIG_GCC_PLUGIN_RANDSTRUCT ]] && \ die "kernel option 'CONFIG_GCC_PLUGIN_RANDSTRUCT' not supported" + [[ -v CONFIG_LD_DEAD_CODE_DATA_ELIMINATION ]] && \ + die "kernel option 'CONFIG_LD_DEAD_CODE_DATA_ELIMINATION' not supported" + [[ -v CONFIG_AS_IS_LLVM ]] && \ [[ "$CONFIG_AS_VERSION" -lt 200000 ]] && \ die "Clang assembler version < 20 not supported" @@ -555,6 +558,8 @@ build_kernel() { # cmd+=("KBUILD_MODPOST_WARN=1") + cmd+=("KLP_SYMIDS=1") + if [[ -v VERBOSE ]]; then cmd+=("V=1") else diff --git a/scripts/mod/modpost.c b/scripts/mod/modpost.c index a7b72a81d248..027944fe35b4 100644 --- a/scripts/mod/modpost.c +++ b/scripts/mod/modpost.c @@ -767,6 +767,7 @@ static const char *const section_white_list[] = ".llvm.call-graph-profile", /* call graph */ "__llvm_covfun", "__llvm_covmap", + ".klp.symid", /* objtool --klp-symids */ NULL }; diff --git a/tools/objtool/Build b/tools/objtool/Build index 93a37b0dfd31..506f89bed808 100644 --- a/tools/objtool/Build +++ b/tools/objtool/Build @@ -6,6 +6,7 @@ objtool-y += check.o objtool-y += special.o objtool-y += builtin-check.o objtool-y += elf.o +objtool-y += klp-symid.o objtool-y += objtool.o objtool-$(BUILD_DISAS) += disas.o diff --git a/tools/objtool/builtin-check.c b/tools/objtool/builtin-check.c index 118c3de2f293..75b11dc85010 100644 --- a/tools/objtool/builtin-check.c +++ b/tools/objtool/builtin-check.c @@ -76,6 +76,7 @@ static const struct option check_options[] = { OPT_STRING_OPTARG('d', "disas", &opts.disas, "function-pattern", "disassemble functions", "*"), OPT_CALLBACK_OPTARG('h', "hacks", NULL, NULL, "jump_label,noinstr,skylake", "patch toolchain bugs/limitations", parse_hacks), OPT_BOOLEAN('i', "ibt", &opts.ibt, "validate and annotate IBT"), + OPT_BOOLEAN(0, "klp-symids", &opts.klp_symids, "generate .klp.symids for duplicate symbol disambiguation"), OPT_BOOLEAN('m', "mcount", &opts.mcount, "annotate mcount/fentry calls for ftrace"), OPT_BOOLEAN(0, "noabs", &opts.noabs, "reject absolute references in allocatable sections"), OPT_BOOLEAN('n', "noinstr", &opts.noinstr, "validate noinstr rules"), @@ -174,10 +175,16 @@ static bool opts_valid(void) return false; } + if (opts.klp_symids && !opts.link) { + ERROR("--klp-symids requires --link"); + return false; + } + if (opts.disas || opts.hack_jump_label || opts.hack_noinstr || opts.ibt || + opts.klp_symids || opts.mcount || opts.noabs || opts.noinstr || diff --git a/tools/objtool/check.c b/tools/objtool/check.c index f03dd59e7fca..a98d7589d8a4 100644 --- a/tools/objtool/check.c +++ b/tools/objtool/check.c @@ -15,6 +15,7 @@ #include #include #include +#include #include #include #include @@ -4923,6 +4924,12 @@ int check(struct objtool_file *file) goto out; } + if (opts.klp_symids) { + ret = klp_create_symid_sections(file); + if (ret) + goto out; + } + if (opts.noabs) warnings += check_abs_references(file); diff --git a/tools/objtool/include/objtool/builtin.h b/tools/objtool/include/objtool/builtin.h index e844e9c82b7b..349690bb1c50 100644 --- a/tools/objtool/include/objtool/builtin.h +++ b/tools/objtool/include/objtool/builtin.h @@ -16,6 +16,7 @@ struct opts { bool hack_noinstr; bool hack_skylake; bool ibt; + bool klp_symids; bool mcount; bool noabs; bool noinstr; diff --git a/tools/objtool/include/objtool/klp.h b/tools/objtool/include/objtool/klp.h index aab6db42052d..4d3c3bd462aa 100644 --- a/tools/objtool/include/objtool/klp.h +++ b/tools/objtool/include/objtool/klp.h @@ -31,6 +31,21 @@ struct klp_reloc { u32 type; }; +/* + * .klp.symid is used to correlate symbols between vmlinux.o and vmlinux, for + * calculating sympos to disambiguate duplicately-named symbols. + */ +#define KLP_SYMID_SEC ".klp.symid" + +struct klp_symid { + u64 id; + u64 addr; +}; + +struct objtool_file; + +int klp_create_symid_sections(struct objtool_file *file); + int cmd_klp_checksum(int argc, const char **argv); int cmd_klp_diff(int argc, const char **argv); int cmd_klp_post_link(int argc, const char **argv); diff --git a/tools/objtool/klp-symid.c b/tools/objtool/klp-symid.c new file mode 100644 index 000000000000..cf188cdfa607 --- /dev/null +++ b/tools/objtool/klp-symid.c @@ -0,0 +1,117 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Emit the .klp.symid table which allows "objtool klp diff" to reliably + * disambiguate duplicate-named local symbols in vmlinux. + * + * Livepatch identifies a duplicate-named symbol by its position (sympos) + * among the same-named kallsyms entries, counted in ascending address order + * in the final linked vmlinux. That order can't be derived from vmlinux.o + * alone: the final link reorders sub-sections (.text.unlikely*, .data..*, + * etc). + * + * Bridge the gap with a table which survives the final link: a single + * non-alloc section containing an array of { id, addr } entries, where + * 'id' is a unique counter identifier and 'addr' has a relocation to the + * symbol. The linker copies 'id' verbatim and resolves 'addr' to the symbol's + * final address. + * + * The table is only emitted for vmlinux.o, and only when klp-build asks for it + * with KLP_SYMIDS=1, which adds --klp-symids to the vmlinux.o objtool run. + * + * It can't survive --gc-sections, which sweeps the whole section; klp-build + * rejects CONFIG_LD_DEAD_CODE_DATA_ELIMINATION. + */ +#include + +#include +#include +#include +#include + +static const char * const discarded_secs[] = { + ".discard", + ".modinfo", + "__tracepoint_check", +}; + +static bool discarded_sec(struct section *sec) +{ + if (!(sec->sh.sh_flags & SHF_ALLOC)) + return true; + + for (int i = 0; i < ARRAY_SIZE(discarded_secs); i++) + if (strstarts(sec->name, discarded_secs[i])) + return true; + + return false; +} + +static bool symid_needed(struct elf *elf, struct symbol *sym) +{ + struct symbol *s; + + if (!is_local_sym(sym) || is_undef_sym(sym)) + return false; + + if (!is_func_sym(sym) && !is_object_sym(sym)) + return false; + + if (is_prefix_func(sym)) + return false; + + if (discarded_sec(sym->sec)) + return false; + + for_each_sym_by_name(elf, sym->name, s) { + if (s == sym || is_sec_sym(s) || is_file_sym(s) || is_undef_sym(s)) + continue; + return true; + } + + return false; +} + +int klp_create_symid_sections(struct objtool_file *file) +{ + struct elf *elf = file->elf; + struct klp_symid *symids; + struct section *sec; + struct symbol *sym; + u64 nr = 0, i = 0; + + if (!str_ends_with(objname, "vmlinux.o")) + return 0; + + for_each_sym(elf, sym) + if (symid_needed(elf, sym)) + nr++; + + if (!nr) + return 0; + + sec = elf_create_section(elf, KLP_SYMID_SEC, 0, sizeof(struct klp_symid), + SHT_PROGBITS, 8, 0); + if (!sec) + return -1; + + symids = elf_add_data(elf, sec, NULL, nr * sizeof(struct klp_symid)); + if (!symids) + return -1; + + for_each_sym(elf, sym) { + if (!symid_needed(elf, sym)) + continue; + + symids[i].id = bswap_if_needed(elf, i); + + if (!elf_create_reloc(elf, sec, + i * sizeof(struct klp_symid) + + offsetof(struct klp_symid, addr), + sym, 0, R_ABS64)) + return -1; + + i++; + } + + return 0; +} From 15fa203ef91e8a303c322eaaa8ca01a6ddaf94dc Mon Sep 17 00:00:00 2001 From: Josh Poimboeuf Date: Sun, 2 Aug 2026 20:24:28 -0700 Subject: [PATCH 06/20] objtool/klp: Fix symbol resolution for duplicate data symbols find_sympos() calculates a sympos used by livepatch to disambiguate duplicately-named symbols. For function symbols, there's a hack which counts .text.unlikely symbols before other .text symbols, matching the linker script's section ordering. Not only is the hack fragile, data symbols can have the same problem. So for example, adding a reference to pwq_cache in ep_unregister_pollwait() can trigger a corrupt sympos and a relocation to the wrong pwq_cache symbol in the livepatch module, resulting in a crash or undefined behavior. Remove the existing hack in favor of a fully deterministic solution, using the new .klp.symid table to derive the symbol-to-id mapping from the original vmlinux.o and the id-to-address mapping from the corresponding vmlinux, which can then be used to determine the exact sympos associated with the original vmlinux. Modules don't need any special treatment: the .ko has the same section/symbol ordering as the original whole-archive symbol table. Fixes: dd590d4d57eb ("objtool/klp: Introduce klp diff subcommand for diffing object files") Reported-by: Ben Procknow Reported-by: Joe Lawrence Signed-off-by: Josh Poimboeuf Signed-off-by: Ingo Molnar Cc: live-patching@vger.kernel.org Link: https://lore.kernel.org/20260710153042.3156788-1-joe.lawrence@redhat.com Link: https://lore.kernel.org/20260724221730.3126529-1-joe.lawrence@redhat.com Link: https://patch.msgid.link/919785e3bf2245db02ff6391e735d9cb139170b1.1785727106.git.jpoimboe@kernel.org --- scripts/livepatch/klp-build | 4 + tools/objtool/Build | 3 +- tools/objtool/include/objtool/klp.h | 5 + tools/objtool/klp-diff.c | 66 +---- tools/objtool/klp-sympos.c | 411 ++++++++++++++++++++++++++++ 5 files changed, 427 insertions(+), 62 deletions(-) create mode 100644 tools/objtool/klp-sympos.c diff --git a/scripts/livepatch/klp-build b/scripts/livepatch/klp-build index f94e324ff53c..b52a8489d9f6 100755 --- a/scripts/livepatch/klp-build +++ b/scripts/livepatch/klp-build @@ -611,6 +611,8 @@ copy_orig_objects() { done xtrace_restore + cp -f "$PWD/vmlinux" "$ORIG_DIR" || die "missing vmlinux" + mv -f "$TMP_DIR/build.log" "$ORIG_DIR" touch "$TIMESTAMP" touch "$ORIG_DIR/.complete" @@ -681,6 +683,8 @@ generate_checksums() { "$OBJTOOL" klp checksum "$dest" done + [[ -f "$src_dir/vmlinux" ]] && cp -f "$src_dir/vmlinux" "$dest_dir" + touch "$dest_dir/.complete" } diff --git a/tools/objtool/Build b/tools/objtool/Build index 506f89bed808..59f948628098 100644 --- a/tools/objtool/Build +++ b/tools/objtool/Build @@ -13,7 +13,8 @@ objtool-$(BUILD_DISAS) += disas.o objtool-$(BUILD_DISAS) += trace.o objtool-$(BUILD_ORC) += orc_gen.o orc_dump.o -objtool-$(BUILD_KLP) += builtin-klp.o klp-checksum.o klp-diff.o klp-post-link.o +objtool-$(BUILD_KLP) += builtin-klp.o klp-checksum.o klp-diff.o \ + klp-post-link.o klp-sympos.o objtool-y += libstring.o objtool-y += libctype.o diff --git a/tools/objtool/include/objtool/klp.h b/tools/objtool/include/objtool/klp.h index 4d3c3bd462aa..0118c2c170c3 100644 --- a/tools/objtool/include/objtool/klp.h +++ b/tools/objtool/include/objtool/klp.h @@ -43,9 +43,14 @@ struct klp_symid { }; struct objtool_file; +struct elf; +struct symbol; int klp_create_symid_sections(struct objtool_file *file); +int klp_sympos_init(struct elf *orig); +unsigned long klp_find_sympos(struct elf *elf, struct symbol *sym); + int cmd_klp_checksum(int argc, const char **argv); int cmd_klp_diff(int argc, const char **argv); int cmd_klp_post_link(int argc, const char **argv); diff --git a/tools/objtool/klp-diff.c b/tools/objtool/klp-diff.c index 75ba0e060a34..c5284d275207 100644 --- a/tools/objtool/klp-diff.c +++ b/tools/objtool/klp-diff.c @@ -898,65 +898,6 @@ static int correlate_symbols(struct elfs *e) return 0; } -/* "sympos" is used by livepatch to disambiguate duplicate symbol names */ -static unsigned long find_sympos(struct elf *elf, struct symbol *sym) -{ - bool vmlinux = str_ends_with(objname, "vmlinux.o"); - unsigned long sympos = 0, nr_matches = 0; - bool has_dup = false; - struct symbol *s; - - if (sym->bind != STB_LOCAL) - return 0; - - if (vmlinux && is_func_sym(sym)) { - /* - * HACK: Unfortunately, symbol ordering can differ between - * vmlinux.o and vmlinux due to the linker script emitting - * .text.unlikely* before .text*. Count .text.unlikely* first. - * - * TODO: Disambiguate symbols more reliably (checksums?) - */ - for_each_sym(elf, s) { - if (strstarts(s->sec->name, ".text.unlikely") && - !strcmp(s->name, sym->name)) { - nr_matches++; - if (s == sym) - sympos = nr_matches; - else - has_dup = true; - } - } - for_each_sym(elf, s) { - if (!strstarts(s->sec->name, ".text.unlikely") && - !strcmp(s->name, sym->name)) { - nr_matches++; - if (s == sym) - sympos = nr_matches; - else - has_dup = true; - } - } - } else { - for_each_sym(elf, s) { - if (!strcmp(s->name, sym->name)) { - nr_matches++; - if (s == sym) - sympos = nr_matches; - else - has_dup = true; - } - } - } - - if (!sympos) { - ERROR("can't find sympos for %s", sym->name); - return ULONG_MAX; - } - - return has_dup ? sympos : 0; -} - static int clone_sym_relocs(struct elfs *e, struct symbol *patched_sym); static struct symbol *__clone_symbol(struct elf *elf, struct symbol *patched_sym, @@ -1418,7 +1359,7 @@ static int clone_reloc_klp(struct elfs *e, struct reloc *patched_reloc, return -1; sym_orig_name = patched_sym->twin->name; - sympos = find_sympos(e->orig, patched_sym->twin); + sympos = klp_find_sympos(e->orig, patched_sym->twin); if (sympos == ULONG_MAX) return -1; } @@ -2036,7 +1977,7 @@ static int create_klp_sections(struct elfs *e) /* klp_func_ext.sympos */ BUILD_BUG_ON(sizeof(sympos) != sizeof_field(struct klp_func_ext, sympos)); - sympos = find_sympos(e->orig, sym->clone->twin); + sympos = klp_find_sympos(e->orig, sym->clone->twin); if (sympos == ULONG_MAX) return -1; memcpy(func_data + offsetof(struct klp_func_ext, sympos), &sympos, @@ -2190,6 +2131,9 @@ int cmd_klp_diff(int argc, const char **argv) if (!e.orig || !e.patched) return -1; + if (klp_sympos_init(e.orig)) + return -1; + if (read_exports()) return -1; diff --git a/tools/objtool/klp-sympos.c b/tools/objtool/klp-sympos.c new file mode 100644 index 000000000000..bbfae516d339 --- /dev/null +++ b/tools/objtool/klp-sympos.c @@ -0,0 +1,411 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Compute "sympos", the position used by livepatch to disambiguate + * duplicate symbol names in the patched object. + */ +#include +#include +#include + +#include +#include +#include +#include + +#include + +struct vmlinux_sym { + struct hlist_node hash; + const char *name; + u64 addr; +}; + +struct vmlinux_symid { + struct hlist_node hash; + u64 id; + u64 addr; +}; + +struct vmlinux_o_symid { + struct hlist_node hash; + u64 id; + unsigned int sym_idx; +}; + +static DEFINE_HASHTABLE(vmlinux_o_symids, 16); + +/* + * The original linked kernel, found next to the orig vmlinux.o. Read with raw + * libelf rather than elf_open_read(): only the symbol table and the resolved + * .klp.symid table are needed, not the (huge) instruction/reloc machinery. + * + * Both tables are built once by read_orig_vmlinux(). The Elf handle stays + * open because the hashed names point into its mmapped string table. + */ +static struct { + Elf *elf; + DECLARE_HASHTABLE(syms, 16); /* name -> address */ + DECLARE_HASHTABLE(symids, 16); /* .klp.symid id -> address */ +} vmlinux; + +/* + * Would the symbol be visible to the runtime's kallsyms-based symbol lookup? + */ +static bool vmlinux_sym_in_kallsyms(Elf *elf, GElf_Sym *sym) +{ + unsigned int type = GELF_ST_TYPE(sym->st_info); + GElf_Shdr shdr; + Elf_Scn *scn; + + if (sym->st_shndx == SHN_UNDEF || sym->st_shndx >= SHN_LORESERVE) + return false; + + if (type == STT_SECTION || type == STT_FILE) + return false; + + scn = elf_getscn(elf, sym->st_shndx); + if (!scn || !gelf_getshdr(scn, &shdr)) + return false; + + return shdr.sh_flags & SHF_ALLOC; +} + +static int read_orig_vmlinux(const char *filename) +{ + size_t shstrndx, nr_syms = 0, nr_symids = 0, strtab_idx = 0; + Elf_Data *symtab_data = NULL, *symid_data = NULL; + struct klp_symid *symids; + Elf_Scn *scn = NULL; + GElf_Ehdr ehdr; + int fd; + + fd = open(filename, O_RDONLY); + if (fd == -1) { + ERROR_GLIBC("can't open '%s'", filename); + return -1; + } + + if (elf_version(EV_CURRENT) == EV_NONE) { + ERROR_ELF("elf_version"); + return -1; + } + + vmlinux.elf = elf_begin(fd, ELF_C_READ_MMAP, NULL); + if (!vmlinux.elf) { + ERROR_ELF("elf_begin"); + return -1; + } + + if (!gelf_getehdr(vmlinux.elf, &ehdr)) { + ERROR_ELF("gelf_getehdr"); + return -1; + } + + if (elf_getshdrstrndx(vmlinux.elf, &shstrndx)) { + ERROR_ELF("elf_getshdrstrndx"); + return -1; + } + + while ((scn = elf_nextscn(vmlinux.elf, scn))) { + const char *name; + GElf_Shdr shdr; + + if (!gelf_getshdr(scn, &shdr)) { + ERROR_ELF("gelf_getshdr"); + return -1; + } + + if (shdr.sh_type == SHT_SYMTAB) { + symtab_data = elf_getdata(scn, NULL); + if (!symtab_data) { + ERROR_ELF("elf_getdata"); + return -1; + } + nr_syms = shdr.sh_size / shdr.sh_entsize; + strtab_idx = shdr.sh_link; + continue; + } + + name = elf_strptr(vmlinux.elf, shstrndx, shdr.sh_name); + if (name && !strcmp(name, KLP_SYMID_SEC)) { + if (shdr.sh_size % sizeof(struct klp_symid)) { + ERROR("%s: %s: struct klp_symid size mismatch", + filename, KLP_SYMID_SEC); + return -1; + } + symid_data = elf_getdata(scn, NULL); + if (!symid_data) { + ERROR_ELF("elf_getdata"); + return -1; + } + nr_symids = shdr.sh_size / sizeof(struct klp_symid); + } + } + + if (!symtab_data) { + ERROR("%s: missing symbol table", filename); + return -1; + } + + if (!symid_data) { + ERROR("%s: missing %s section, kernel not built with CONFIG_KLP_BUILD?", + filename, KLP_SYMID_SEC); + return -1; + } + + for (size_t i = 0; i < nr_syms; i++) { + struct vmlinux_sym *vsym; + const char *name; + GElf_Sym s; + + if (!gelf_getsym(symtab_data, i, &s)) { + ERROR_ELF("gelf_getsym"); + return -1; + } + + if (!vmlinux_sym_in_kallsyms(vmlinux.elf, &s)) + continue; + + name = elf_strptr(vmlinux.elf, strtab_idx, s.st_name); + if (!name) + continue; + + vsym = calloc(1, sizeof(*vsym)); + if (!vsym) { + ERROR_GLIBC("calloc"); + return -1; + } + + vsym->name = name; + vsym->addr = s.st_value; + hash_add(vmlinux.syms, &vsym->hash, str_hash(name)); + } + + symids = symid_data->d_buf; + + for (size_t i = 0; i < nr_symids; i++) { + struct vmlinux_symid *vsymid; + + vsymid = calloc(1, sizeof(*vsymid)); + if (!vsymid) { + ERROR_GLIBC("calloc"); + return -1; + } + + vsymid->id = __bswap_if_needed(&ehdr, symids[i].id); + vsymid->addr = __bswap_if_needed(&ehdr, symids[i].addr); + hash_add(vmlinux.symids, &vsymid->hash, vsymid->id); + } + + /* the fd and Elf handle stay open, the hashed names live in the mmap */ + return 0; +} + +/* + * Read the orig vmlinux.o's .klp.symid table, an array of entries whose 'addr' + * fields have relocs to the symbols they describe. + */ +static int read_vmlinux_o_symids(struct elf *vmlinux_o) +{ + struct section *sec; + + for_each_sec(vmlinux_o, sec) { + unsigned long nr; + + if (strcmp(sec->name, KLP_SYMID_SEC)) + continue; + + if (sec_size(sec) % sizeof(struct klp_symid)) { + ERROR("%s: %s: struct klp_symid size mismatch", + vmlinux_o->name, KLP_SYMID_SEC); + return -1; + } + + nr = sec_size(sec) / sizeof(struct klp_symid); + + for (unsigned long i = 0; i < nr; i++) { + unsigned long offset = i * sizeof(struct klp_symid); + struct vmlinux_o_symid *entry; + struct klp_symid *symid; + struct reloc *reloc; + + entry = calloc(1, sizeof(*entry)); + if (!entry) { + ERROR_GLIBC("calloc"); + return -1; + } + + symid = sec->data->d_buf + offset; + entry->id = bswap_if_needed(vmlinux_o, symid->id); + + reloc = find_reloc_by_dest(vmlinux_o, sec, + offset + offsetof(struct klp_symid, addr)); + if (!reloc) { + ERROR("%s: missing reloc for %s entry", + vmlinux_o->name, KLP_SYMID_SEC); + return -1; + } + entry->sym_idx = reloc->sym->idx; + + hash_add(vmlinux_o_symids, &entry->hash, entry->sym_idx); + } + } + + return 0; +} + +int klp_sympos_init(struct elf *orig) +{ + char *filename; + int ret; + + if (!str_ends_with(objname, "vmlinux.o")) + return 0; + + if (read_vmlinux_o_symids(orig)) + return -1; + + filename = strndup(objname, strlen(objname) - 2); + if (!filename) { + ERROR_GLIBC("strndup"); + return -1; + } + + ret = read_orig_vmlinux(filename); + free(filename); + + return ret; +} + +/* Find the symbol's id in the orig vmlinux.o's .klp.symid table */ +static int find_vmlinux_o_symid(struct symbol *sym, u64 *id) +{ + struct vmlinux_o_symid *entry; + + hash_for_each_possible(vmlinux_o_symids, entry, hash, sym->idx) { + if (entry->sym_idx == sym->idx) { + *id = entry->id; + return 0; + } + } + + ERROR("no %s entry for symbol %s in orig vmlinux.o", KLP_SYMID_SEC, + sym->name); + return -1; +} + +/* Find the symbol's final address in the orig vmlinux's .klp.symid table */ +static int find_vmlinux_symid_addr(u64 id, u64 *addr) +{ + struct vmlinux_symid *symid; + + hash_for_each_possible(vmlinux.symids, symid, hash, id) { + if (symid->id == id) { + *addr = symid->addr; + return 0; + } + } + + return -1; +} + +/* + * Find the sympos of a vmlinux-local symbol by ranking its final address + * among the duplicately named symbols in the linked orig vmlinux, replicating + * the order in which kallsyms_on_each_match_symbol() counts them. + */ +static unsigned long find_vmlinux_sympos(struct symbol *sym) +{ + unsigned long nr_matches = 0, sympos = 1; + u32 key = str_hash(sym->name); + struct vmlinux_sym *vsym; + bool found = false; + u64 id, addr; + + hash_for_each_possible(vmlinux.syms, vsym, hash, key) + if (!strcmp(vsym->name, sym->name)) + nr_matches++; + + if (!nr_matches) { + ERROR("can't find symbol %s in orig vmlinux", sym->name); + return ULONG_MAX; + } + + /* + * Unique symbols don't need disambiguating. They also have no + * .klp.symid entry, which is only emitted for names duplicated in + * vmlinux.o, so the lookups below would fail. + */ + if (nr_matches == 1) + return 0; + + if (find_vmlinux_o_symid(sym, &id)) + return ULONG_MAX; + + if (find_vmlinux_symid_addr(id, &addr)) { + ERROR("no %s entry for symbol %s in orig vmlinux", KLP_SYMID_SEC, + sym->name); + return ULONG_MAX; + } + + hash_for_each_possible(vmlinux.syms, vsym, hash, key) { + if (strcmp(vsym->name, sym->name)) + continue; + + if (vsym->addr < addr) + sympos++; + else if (vsym->addr == addr) + found = true; + } + + if (!found) { + ERROR("%s address mismatch for symbol %s, stale orig vmlinux?", + KLP_SYMID_SEC, sym->name); + return ULONG_MAX; + } + + return sympos; +} + +/* + * "sympos" is used by livepatch to disambiguate duplicate symbol names. + */ +unsigned long klp_find_sympos(struct elf *elf, struct symbol *sym) +{ + unsigned long sympos = 0, nr_matches = 0; + bool has_dup = false; + struct symbol *s; + + if (sym->bind != STB_LOCAL) + return 0; + + /* + * vmlinux: the final link reorders symbols relative to vmlinux.o, + * so the position needs to be derived from the linked orig vmlinux via + * the .klp.symid table. + */ + if (vmlinux.elf) + return find_vmlinux_sympos(sym); + + /* + * modules: the final .ko preserves symbol table order, so a + * symtab-order count here matches the runtime count done by + * module_kallsyms_on_each_symbol(). + */ + for_each_sym(elf, s) { + if (!strcmp(s->name, sym->name)) { + nr_matches++; + if (s == sym) + sympos = nr_matches; + else + has_dup = true; + } + } + + if (!sympos) { + ERROR("can't find sympos for %s", sym->name); + return ULONG_MAX; + } + + return has_dup ? sympos : 0; +} From 6e5716b187faf50e5d33f0986ea25827cbdaa294 Mon Sep 17 00:00:00 2001 From: Thomas Huth Date: Fri, 19 Jun 2026 17:57:33 +0200 Subject: [PATCH 07/20] objtool: Replace __ASSEMBLY__ with __ASSEMBLER__ in header files While the GCC and Clang compilers already define __ASSEMBLER__ automatically when compiling assembly code, __ASSEMBLY__ is a macro that only gets defined by the Makefiles in the kernel. This can be very confusing when switching between userspace and kernelspace coding, or when dealing with uapi headers that rather should use __ASSEMBLER__ instead. So let's standardize now on the __ASSEMBLER__ macro that is provided by the compilers. This is a completely mechanical patch (done with a simple "sed -i" statement). Signed-off-by: Thomas Huth Signed-off-by: Peter Zijlstra (Intel) Link: https://patch.msgid.link/20260619155733.58852-1-thuth@redhat.com --- include/linux/objtool.h | 8 ++++---- include/linux/objtool_types.h | 4 ++-- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/include/linux/objtool.h b/include/linux/objtool.h index 9a00e701454c..af2e68e496e5 100644 --- a/include/linux/objtool.h +++ b/include/linux/objtool.h @@ -7,7 +7,7 @@ #ifdef CONFIG_OBJTOOL -#ifndef __ASSEMBLY__ +#ifndef __ASSEMBLER__ #define UNWIND_HINT(type, sp_reg, sp_offset, signal) \ "987: \n\t" \ @@ -53,7 +53,7 @@ #define __ASM_BREF(label) label ## b -#else /* __ASSEMBLY__ */ +#else /* __ASSEMBLER__ */ /* * In asm, there are two kinds of code: normal C-type callable functions and @@ -102,11 +102,11 @@ #endif .endm -#endif /* __ASSEMBLY__ */ +#endif /* __ASSEMBLER__ */ #else /* !CONFIG_OBJTOOL */ -#ifndef __ASSEMBLY__ +#ifndef __ASSEMBLER__ #define UNWIND_HINT(type, sp_reg, sp_offset, signal) "\n\t" #define STACK_FRAME_NON_STANDARD(func) diff --git a/include/linux/objtool_types.h b/include/linux/objtool_types.h index c6def4049b1a..c24e9ea39269 100644 --- a/include/linux/objtool_types.h +++ b/include/linux/objtool_types.h @@ -2,7 +2,7 @@ #ifndef _LINUX_OBJTOOL_TYPES_H #define _LINUX_OBJTOOL_TYPES_H -#ifndef __ASSEMBLY__ +#ifndef __ASSEMBLER__ #include @@ -18,7 +18,7 @@ struct unwind_hint { u8 signal; }; -#endif /* __ASSEMBLY__ */ +#endif /* __ASSEMBLER__ */ /* * UNWIND_HINT_TYPE_UNDEFINED: A blind spot in ORC coverage which can result in From 6e70aef598a2779389be297afda9f61b3d19759f Mon Sep 17 00:00:00 2001 From: Ingo Molnar Date: Tue, 11 Aug 2026 10:44:38 +0200 Subject: [PATCH 08/20] objtool/headers: Sync tools/include/linux/objtool_types.h with include/linux/objtool_types.h Sync up the kernel and tooling headers to fix this build warning: Warning: Kernel ABI header at 'tools/include/linux/objtool_types.h' differs from latest version at 'include/linux/objtool_types.h' Fixes: 6e5716b187fa ("objtool: Replace __ASSEMBLY__ with __ASSEMBLER__ in header files") Cc: Thomas Huth Cc: Peter Zijlstra (Intel) Signed-off-by: Ingo Molnar --- tools/include/linux/objtool_types.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tools/include/linux/objtool_types.h b/tools/include/linux/objtool_types.h index c6def4049b1a..c24e9ea39269 100644 --- a/tools/include/linux/objtool_types.h +++ b/tools/include/linux/objtool_types.h @@ -2,7 +2,7 @@ #ifndef _LINUX_OBJTOOL_TYPES_H #define _LINUX_OBJTOOL_TYPES_H -#ifndef __ASSEMBLY__ +#ifndef __ASSEMBLER__ #include @@ -18,7 +18,7 @@ struct unwind_hint { u8 signal; }; -#endif /* __ASSEMBLY__ */ +#endif /* __ASSEMBLER__ */ /* * UNWIND_HINT_TYPE_UNDEFINED: A blind spot in ORC coverage which can result in From 3a83d9cd3d0e66c0ef99ce84f972ed26f50c990f Mon Sep 17 00:00:00 2001 From: Josh Poimboeuf Date: Fri, 7 Aug 2026 14:37:46 -0700 Subject: [PATCH 09/20] objtool/klp: Fix vmlinux .klp.symid link error for .no_trim_symbol symbols Testing klp-build with arm64 produced the following linker error during the original kernel build: `__notrim.1' referenced in section `.klp.symid' of vmlinux.o: defined in discarded section `.no_trim_symbol' of vmlinux.o symbol_get() puts a static __notrim[] in .no_trim_symbol, which GCC names __notrim.1, __notrim.2, etc. Two or more built-in translation units calling symbol_get() thus produce duplicate names, resulting in corresponding .klp.symid references which trigger the above error. Add .no_trim_symbol to the discarded section list so its symbols don't get symids. Note this issue is not specific to arm64: it just needs two built-in symbol_get() callers. arm64 trips over it easily because it has KVM always compiled in vmlinux, whereas on x86 it's typically a module. Fixes: 029223d30162 ("objtool/klp: Add .klp.symid for sympos disambiguation") Acked-by: Song Liu Acked-by: Joe Lawrence Link: https://patch.msgid.link/5a3cc4cded743167dd0878220201d80f7e48e5d7.1786138493.git.jpoimboe@kernel.org Signed-off-by: Josh Poimboeuf --- tools/objtool/klp-symid.c | 1 + 1 file changed, 1 insertion(+) diff --git a/tools/objtool/klp-symid.c b/tools/objtool/klp-symid.c index cf188cdfa607..21d8708013ab 100644 --- a/tools/objtool/klp-symid.c +++ b/tools/objtool/klp-symid.c @@ -31,6 +31,7 @@ static const char * const discarded_secs[] = { ".discard", ".modinfo", + ".no_trim_symbol", "__tracepoint_check", }; From 1ea786097cd79522b76cbd41beeb8f84ef3a76f4 Mon Sep 17 00:00:00 2001 From: Josh Poimboeuf Date: Fri, 7 Aug 2026 14:37:47 -0700 Subject: [PATCH 10/20] objtool/klp: Fix size of empty special section entries create_fake_symbols() sizes each ANNOTATE_DATA_SPECIAL entry from the offset of the next annotation, falling back to the end of the section for the last entry. But the last entry is detected by a zero size, which also happens for an *empty* entry: ALTERNATIVE(oldinstr, "", ft) still annotates its zero-length replacement, at the same offset as the next entry's annotation. So every empty replacement gets a fake symbol spanning the entire rest of .altinstr_replacement. That's harmless today only because find_symbol_containing() picks the smaller of two overlapping symbols. Track whether a next annotation was found rather than inferring it from the size. A zero-length fake symbol is fine: find_symbol_containing() skips those, so the properly sized symbol at the same offset still wins. Fixes: dd590d4d57eb ("objtool/klp: Introduce klp diff subcommand for diffing object files") Acked-by: Song Liu Acked-by: Joe Lawrence Link: https://patch.msgid.link/913e691c5009397df832c7c9a18cd5cf71b42737.1786138493.git.jpoimboe@kernel.org Signed-off-by: Josh Poimboeuf --- tools/objtool/klp-diff.c | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/tools/objtool/klp-diff.c b/tools/objtool/klp-diff.c index c5284d275207..b4220e12a8e2 100644 --- a/tools/objtool/klp-diff.c +++ b/tools/objtool/klp-diff.c @@ -1628,13 +1628,17 @@ static int create_fake_symbols(struct elf *elf) for_each_reloc(sec->rsec, reloc) { unsigned long offset, size; struct reloc *next_reloc; + bool last = true; if (annotype(elf, sec, reloc) != ANNOTYPE_DATA_SPECIAL) continue; offset = reloc_addend(reloc); - size = 0; + /* + * Find the start of the next entry so the fake symbol size can + * be calculated. + */ next_reloc = reloc; for_each_reloc_continue(sec->rsec, next_reloc) { if (annotype(elf, sec, next_reloc) != ANNOTYPE_DATA_SPECIAL || @@ -1642,10 +1646,15 @@ static int create_fake_symbols(struct elf *elf) continue; size = reloc_addend(next_reloc) - offset; + last = false; break; } - if (!size) + /* + * If no next entry found, this is the last entry, so its size + * is from the current offset to the end of the section. + */ + if (last) size = sec_size(reloc->sym->sec) - offset; if (create_fake_symbol(elf, reloc->sym->sec, offset, size)) From 636f230ce21e2730072c58280f1fa0bed4a00eed Mon Sep 17 00:00:00 2001 From: Josh Poimboeuf Date: Fri, 7 Aug 2026 14:37:48 -0700 Subject: [PATCH 11/20] objtool/klp: Ignore replacement offset of empty x86 alternatives An x86 alternative with an empty replacement, e.g. the second entry of ALTERNATIVE_2("orig", "repl", ft1, "", ft2) has a replacementlen of zero. Its replacement offset still gets a relocation, but the label it points at is the end of the previous replacement, which is also the beginning of the *next* alternative's replacement. The value is meaningless; get_alt_entry() already ignores it for that reason. klp diff doesn't ignore it. When such an alternative belongs to a changed function, cloning its relocations drags in the unrelated neighboring replacement, along with everything that replacement references. On an x86 clang/lto build an empty alternative in meminfo_proc_show() pulled in the replacement of an alternative in proc_kcore_init(), silently emitting a klp relocation against init text which has long since been freed by the time the patch is applied. Add arch_alt_ignore_new_reloc() and skip such relocations when cloning. This has to be arch specific: on arm64 a zero-length replacement instead identifies an alternative callback, whose replacement offset points at the callback function and must be preserved. Fixes: dd590d4d57eb ("objtool/klp: Introduce klp diff subcommand for diffing object files") Acked-by: Song Liu Acked-by: Joe Lawrence Link: https://patch.msgid.link/7a885b70974795c3417f3358869e62aafd4ef783.1786138493.git.jpoimboe@kernel.org Signed-off-by: Josh Poimboeuf --- tools/objtool/arch/x86/special.c | 27 +++++++++++++++++++++++++ tools/objtool/include/objtool/special.h | 7 +++++++ tools/objtool/klp-diff.c | 6 +++++- 3 files changed, 39 insertions(+), 1 deletion(-) diff --git a/tools/objtool/arch/x86/special.c b/tools/objtool/arch/x86/special.c index e817a3fff449..1e84c81bfcd8 100644 --- a/tools/objtool/arch/x86/special.c +++ b/tools/objtool/arch/x86/special.c @@ -1,6 +1,7 @@ // SPDX-License-Identifier: GPL-2.0-or-later #include +#include #include #include #include @@ -9,6 +10,32 @@ /* cpu feature name array generated from cpufeatures.h */ #include "cpu-feature-names.c" +/* + * An alternative with an empty replacement, e.g. the second entry of + * + * ALTERNATIVE_2("orig", "repl", ft1, "", ft2) + * + * still gets a relocation for its replacement offset. But the label it points + * at is the end of the previous entry's replacement, which is also the + * beginning of the *next* entry's replacement. The value is meaningless: it's + * only ever used with a length of zero. + */ +bool arch_alt_ignore_new_reloc(struct section *sec, unsigned long offset) +{ + unsigned long entry_off; + + if (strcmp(sec->name, ".altinstructions")) + return false; + + entry_off = offset - (offset % ALT_ENTRY_SIZE); + + if (offset - entry_off != ALT_NEW_OFFSET) + return false; + + return !*(unsigned char *)(sec->data->d_buf + entry_off + + ALT_NEW_LEN_OFFSET); +} + void arch_handle_alternative(struct special_alt *alt) { static struct special_alt *group, *prev; diff --git a/tools/objtool/include/objtool/special.h b/tools/objtool/include/objtool/special.h index 121c3761899c..620dbf6cb0e5 100644 --- a/tools/objtool/include/objtool/special.h +++ b/tools/objtool/include/objtool/special.h @@ -32,6 +32,13 @@ int special_get_alts(struct elf *elf, struct list_head *alts); void arch_handle_alternative(struct special_alt *alt); +/* + * Should the reloc at @offset -- the "new" (replacement) field of a special + * section group entry -- be ignored? The meaning of a zero-length replacement + * is arch specific, so the arch decides. + */ +bool arch_alt_ignore_new_reloc(struct section *sec, unsigned long offset); + bool arch_support_alt_relocation(struct special_alt *special_alt, struct instruction *insn, struct reloc *reloc); diff --git a/tools/objtool/klp-diff.c b/tools/objtool/klp-diff.c index b4220e12a8e2..df2fe014ec7e 100644 --- a/tools/objtool/klp-diff.c +++ b/tools/objtool/klp-diff.c @@ -12,7 +12,7 @@ #include #include #include -#include +#include #include #include @@ -1538,6 +1538,10 @@ static int clone_sym_relocs(struct elfs *e, struct symbol *patched_sym) !strcmp(patched_reloc->sym->sec->name, ".altinstr_aux")) continue; + if (arch_alt_ignore_new_reloc(patched_sym->sec, + reloc_offset(patched_reloc))) + continue; + ret = convert_reloc_sym(e->patched, patched_reloc); if (ret < 0) { ERROR_FUNC(patched_rsec->base, reloc_offset(patched_reloc), From 69f361b8a7a2f65c1bb236ea0899cdaad7267653 Mon Sep 17 00:00:00 2001 From: Josh Poimboeuf Date: Fri, 7 Aug 2026 14:37:49 -0700 Subject: [PATCH 12/20] objtool/klp: Explicitly disallow patching or referencing init code/data Explicitly disallow the patching and referencing of init code/data. Otherwise it could potentially introduce some odd edge cases depending on whether the target object's init section has been freed yet (note that the init code still exists in the target module when doing late module patching). Such edge cases include sympos calculation and the patching and/or referencing of non-existent (init-freed) code/data. Not to mention the inherent differences in behavior that occur when the init code is only patched *some* of the time depending on module loading order or kernel config. Acked-by: Joe Lawrence Acked-by: Song Liu Link: https://patch.msgid.link/516e14f84cfbffa27dc19d3dcf35097504097966.1786138493.git.jpoimboe@kernel.org Signed-off-by: Josh Poimboeuf --- tools/objtool/klp-sympos.c | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/tools/objtool/klp-sympos.c b/tools/objtool/klp-sympos.c index bbfae516d339..dfca9dd74681 100644 --- a/tools/objtool/klp-sympos.c +++ b/tools/objtool/klp-sympos.c @@ -367,6 +367,11 @@ static unsigned long find_vmlinux_sympos(struct symbol *sym) return sympos; } +static bool is_init_sym(struct symbol *sym) +{ + return strstarts(sym->sec->name, ".init"); +} + /* * "sympos" is used by livepatch to disambiguate duplicate symbol names. */ @@ -376,6 +381,11 @@ unsigned long klp_find_sympos(struct elf *elf, struct symbol *sym) bool has_dup = false; struct symbol *s; + if (is_init_sym(sym)) { + ERROR("%s: can't patch or reference init code/data", sym->name); + return ULONG_MAX; + } + if (sym->bind != STB_LOCAL) return 0; From 07f14d6af9d7791fdc2b44a3dd0693e42c71d2b8 Mon Sep 17 00:00:00 2001 From: Josh Poimboeuf Date: Fri, 7 Aug 2026 14:37:50 -0700 Subject: [PATCH 13/20] objtool/klp: Fix cross-module klp relocation section naming A klp relocation section is .klp.rela.., where objname is the object being patched. klp-build wrongly derives objname from where the referenced symbol lives, not where it's referenced. For a cross-module reference like patched can_isotp code calling can.ko's can_rx_unregister(), that gives .klp.rela.can..text rather than .klp.rela.can_isotp..text. Unless the patch happens to patch can.ko as well, the relocation never gets applied and the call goes off into the weeds. Name the intermediate section __klp_relocs. so post-link can read the patched object's name from there. Fixes: dd590d4d57eb ("objtool/klp: Introduce klp diff subcommand for diffing object files") Reported-by: Joe Lawrence Link: https://lore.kernel.org/20260720145658.1103243-2-joe.lawrence@redhat.com Acked-by: Song Liu Acked-by: Joe Lawrence Link: https://patch.msgid.link/ee93a08f3e55e76ffa67d04e283917ddaa893f09.1786138493.git.jpoimboe@kernel.org Signed-off-by: Josh Poimboeuf --- tools/objtool/include/objtool/klp.h | 10 ++++-- tools/objtool/klp-diff.c | 17 +++++++-- tools/objtool/klp-post-link.c | 53 +++++++++++++++++------------ 3 files changed, 52 insertions(+), 28 deletions(-) diff --git a/tools/objtool/include/objtool/klp.h b/tools/objtool/include/objtool/klp.h index 0118c2c170c3..646d8e1f12ef 100644 --- a/tools/objtool/include/objtool/klp.h +++ b/tools/objtool/include/objtool/klp.h @@ -14,11 +14,15 @@ #define KLP_FUNCS_SEC ".init.klp_funcs" /* - * __klp_relocs is an intermediate section which are created by klp diff and - * converted into KLP symbols/relas by "objtool klp post-link". This is needed - * to work around the linker, which doesn't preserve SHN_LIVEPATCH or + * __klp_relocs. are intermediate sections which are created by klp + * diff and converted into KLP symbols/relas by "objtool klp post-link". This + * is needed to work around the linker, which doesn't preserve SHN_LIVEPATCH or * SHF_RELA_LIVEPATCH, nor does it support having two RELA sections for a * single PROGBITS section. + * + * "objname" is the name of the object being patched ("vmlinux" or a module + * name). post-link uses it to name the resulting + * .klp.rela.objname.section_name sections. */ #define KLP_RELOCS_SEC "__klp_relocs" #define KLP_STRINGS_SEC ".rodata.klp.str1.1" diff --git a/tools/objtool/klp-diff.c b/tools/objtool/klp-diff.c index df2fe014ec7e..e266a54b059d 100644 --- a/tools/objtool/klp-diff.c +++ b/tools/objtool/klp-diff.c @@ -1381,8 +1381,8 @@ static int clone_reloc_klp(struct elfs *e, struct reloc *patched_reloc, } /* - * Create the __klp_relocs entry. This will be converted to an actual - * KLP rela by "objtool klp post-link". + * Create the __klp_relocs. entry. This will be converted to + * an actual KLP rela by "objtool klp post-link". * * This intermediate step is necessary to prevent corruption by the * linker, which doesn't know how to properly handle two rela sections @@ -1390,7 +1390,18 @@ static int clone_reloc_klp(struct elfs *e, struct reloc *patched_reloc, */ if (!klp_relocs) { - klp_relocs = elf_create_section(e->out, KLP_RELOCS_SEC, 0, + const char *objname = find_modname(e); + char sec_name[SEC_NAME_LEN]; + + if (!objname) + return -1; + + /* section format: __klp_relocs.objname */ + if (snprintf_check(sec_name, SEC_NAME_LEN, + KLP_RELOCS_SEC ".%s", objname)) + return -1; + + klp_relocs = elf_create_section(e->out, sec_name, 0, 0, SHT_PROGBITS, 8, SHF_ALLOC); if (!klp_relocs) return -1; diff --git a/tools/objtool/klp-post-link.c b/tools/objtool/klp-post-link.c index c013e39957b1..350d20495897 100644 --- a/tools/objtool/klp-post-link.c +++ b/tools/objtool/klp-post-link.c @@ -19,19 +19,11 @@ #include #include -static int fix_klp_relocs(struct elf *elf) +static int fix_klp_reloc_sec(struct elf *elf, struct section *symtab, + struct section *klp_relocs) { - struct section *symtab, *klp_relocs; - - klp_relocs = find_section_by_name(elf, KLP_RELOCS_SEC); - if (!klp_relocs) - return 0; - - symtab = find_section_by_name(elf, ".symtab"); - if (!symtab) { - ERROR("missing .symtab"); - return -1; - } + /* section format: __klp_relocs.sec_objname */ + const char *sec_objname = klp_relocs->name + strlen(KLP_RELOCS_SEC "."); for (int i = 0; i < sec_size(klp_relocs) / sizeof(struct klp_reloc); i++) { struct klp_reloc *klp_reloc; @@ -39,7 +31,6 @@ static int fix_klp_relocs(struct elf *elf) struct section *sec, *tmp, *klp_rsec; unsigned long offset; struct reloc *reloc; - char sym_modname[64]; char rsec_name[SEC_NAME_LEN]; u64 addend; struct symbol *sym, *klp_sym; @@ -55,7 +46,7 @@ static int fix_klp_relocs(struct elf *elf) reloc = find_reloc_by_dest(elf, klp_relocs, klp_reloc_off + offsetof(struct klp_reloc, offset)); if (!reloc) { - ERROR("malformed " KLP_RELOCS_SEC " section"); + ERROR("malformed %s section", klp_relocs->name); return -1; } @@ -66,17 +57,13 @@ static int fix_klp_relocs(struct elf *elf) reloc = find_reloc_by_dest(elf, klp_relocs, klp_reloc_off + offsetof(struct klp_reloc, sym)); if (!reloc) { - ERROR("malformed " KLP_RELOCS_SEC " section"); + ERROR("malformed %s section", klp_relocs->name); return -1; } klp_sym = reloc->sym; addend = reloc_addend(reloc); - /* symbol format: .klp.sym.modname.sym_name,sympos */ - if (sscanf(klp_sym->name + strlen(KLP_SYM_PREFIX), "%55[^.]", sym_modname) != 1) - ERROR("can't find modname in klp symbol '%s'", klp_sym->name); - /* * Create the KLP rela: */ @@ -84,7 +71,7 @@ static int fix_klp_relocs(struct elf *elf) /* section format: .klp.rela.sec_objname.section_name */ if (snprintf_check(rsec_name, SEC_NAME_LEN, KLP_RELOC_SEC_PREFIX "%s.%s", - sym_modname, sec->name)) + sec_objname, sec->name)) return -1; klp_rsec = find_section_by_name(elf, rsec_name); @@ -134,10 +121,32 @@ static int fix_klp_relocs(struct elf *elf) return 0; } +static int fix_klp_relocs(struct elf *elf) +{ + struct section *symtab, *sec; + + symtab = find_section_by_name(elf, ".symtab"); + if (!symtab) { + ERROR("missing .symtab"); + return -1; + } + + for_each_sec(elf, sec) { + if (strncmp(sec->name, KLP_RELOCS_SEC ".", + strlen(KLP_RELOCS_SEC "."))) + continue; + + if (fix_klp_reloc_sec(elf, symtab, sec)) + return -1; + } + + return 0; +} + /* * This runs on the livepatch module after all other linking has been done. It - * converts the intermediate __klp_relocs section into proper KLP relocs to be - * processed by livepatch. This needs to run last to avoid linker wreckage. + * converts the intermediate __klp_relocs.* sections into proper KLP relocs to + * be processed by livepatch. This needs to run last to avoid linker wreckage. * Linkers don't tend to handle the "two rela sections for a single base * section" case very well, nor do they appreciate SHN_LIVEPATCH. */ From 86a697572c6271083694025eefa8de518eeef623 Mon Sep 17 00:00:00 2001 From: Josh Poimboeuf Date: Fri, 7 Aug 2026 14:37:51 -0700 Subject: [PATCH 14/20] objtool/klp: Don't match local symbols against exports While cloning a reloc, klp diff calls find_export() to determine whether the referenced symbol is exported. That decides whether the reference needs a klp reloc, which object the klp symbol belongs to, and whether the symbol's data needs to be copied into the patch module. But find_export() matches purely on symbol name, so a static function or variable which happens to share its name with an export is mistaken for a reference to that export: - klp_reloc_needed() creates a klp reloc pointing at the exporting module's symbol rather than the local one. For a vmlinux export it skips the klp reloc altogether, leaving a normal reloc which the module loader resolves to the vmlinux symbol. - clone_reloc() treats the symbol as external and clones it without its data, leaving a dangling reference. - validate_special_section_klp_reloc() attributes a static branch or call key to the wrong module, and for a vmlinux export skips the unsupported-key check entirely. Exports are always global, so ignore local symbols in find_export(). Fixes: dd590d4d57eb ("objtool/klp: Introduce klp diff subcommand for diffing object files") Acked-by: Song Liu Acked-by: Joe Lawrence Link: https://patch.msgid.link/dafc7c017d1f62e2190a20dc84a52fbfc4c70210.1786138493.git.jpoimboe@kernel.org Signed-off-by: Josh Poimboeuf --- tools/objtool/klp-diff.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/tools/objtool/klp-diff.c b/tools/objtool/klp-diff.c index e266a54b059d..a7cdba08b1de 100644 --- a/tools/objtool/klp-diff.c +++ b/tools/objtool/klp-diff.c @@ -1102,6 +1102,9 @@ static struct export *find_export(struct symbol *sym) { struct export *export; + if (is_local_sym(sym)) + return NULL; + hash_for_each_possible(exports, export, hash, str_hash(sym->name)) { if (!strcmp(export->sym, sym->name)) return export; From 72d76d0c18ebd40f6a32df875ff40528564849c1 Mon Sep 17 00:00:00 2001 From: Joe Lawrence Date: Fri, 7 Aug 2026 14:37:52 -0700 Subject: [PATCH 15/20] objtool/klp: Allow new references to module exports klp_reloc_needed() returns true for module exports to support late-module patching. However, clone_reloc_klp() unconditionally rejects symbols without a twin (i.e., new references added by the patch), even when the symbol is a known export from Module.symvers. Relax the check: allow new references to exported symbols by only erroring on !twin when there is no export. The export metadata from Module.symvers provides sufficient context to emit the klp-relocation without a twin. For a module export that isn't sufficient on its own though, as the resulting klp relocation will only be resolved at patch-enable time if the exporting module is loaded. If the original (unpatched) module already depends on the exporting module, the dependency is safe: the module loader ensures the dependency is satisfied before the patched module can be loaded, so the klp relocation target will exist. However, if the patch introduces a reference to a module that the original doesn't depend on, there is no such guarantee. The exporting module could be absent or could be unloaded at any time, leading to a relocation failure or use-after-free. So also add a build-time check: when a new symbol reference (no twin) targets a module export, verify that the original module already has at least one UNDEF symbol resolving to that same exporting module. If not, error out with a diagnostic message. Signed-off-by: Joe Lawrence Acked-by: Song Liu Acked-by: Joe Lawrence Link: https://patch.msgid.link/e0d725acb4774747f0e271308b4ca33daae2d5db.1786138493.git.jpoimboe@kernel.org Signed-off-by: Josh Poimboeuf --- tools/objtool/klp-diff.c | 35 +++++++++++++++++++++++++++++++++-- 1 file changed, 33 insertions(+), 2 deletions(-) diff --git a/tools/objtool/klp-diff.c b/tools/objtool/klp-diff.c index a7cdba08b1de..0211e5c87160 100644 --- a/tools/objtool/klp-diff.c +++ b/tools/objtool/klp-diff.c @@ -1295,6 +1295,28 @@ static int convert_reloc_sym(struct elf *elf, struct reloc *reloc) return convert_reloc_secsym_to_sym(elf, reloc); } +/* + * Check if the original module already has a dependency on dep_mod, i.e. it + * already references at least one export from that module. + */ +static bool has_module_dep(struct elfs *e, const char *dep_mod) +{ + struct symbol *sym; + + for_each_sym(e->orig, sym) { + struct export *exp; + + if (!is_undef_sym(sym) || is_weak_sym(sym)) + continue; + + exp = find_export(sym); + if (exp && !strcmp(exp->mod, dep_mod)) + return true; + } + + return false; +} + /* * Convert a regular relocation to a klp relocation (sort of). */ @@ -1314,8 +1336,17 @@ static int clone_reloc_klp(struct elfs *e, struct reloc *patched_reloc, unsigned long sympos; if (!patched_sym->twin) { - ERROR("unexpected klp reloc for new symbol %s", patched_sym->name); - return -1; + if (!export) { + ERROR("unexpected klp reloc for new symbol %s", patched_sym->name); + return -1; + } + + if (strcmp(export->mod, "vmlinux") && + !has_module_dep(e, export->mod)) { + ERROR("%s: new reference to %s (exported by %s) would create an undeclared module dependency", + patched_sym->name, export->sym, export->mod); + return -1; + } } /* From 4cd3cfb8b54feca89a682720434092a87bfa4967 Mon Sep 17 00:00:00 2001 From: Josh Poimboeuf Date: Fri, 7 Aug 2026 14:37:53 -0700 Subject: [PATCH 16/20] objtool/klp: Fix relocations for EXPORT_SYMBOL_FOR_MODULES() symbols EXPORT_SYMBOL_FOR_MODULES() puts a symbol in a "module:" namespace, which the module loader grants access to by matching the importing module's name against that list. klp_reloc_needed() only creates a klp reloc for module-owned exports; a vmlinux export gets a normal reloc. For a vmlinux symbol exported with EXPORT_SYMBOL_FOR_MODULES(), using a normal reloc results in a modpost failure in klp-build: ERROR: modpost: module livepatch-foo uses symbol mpol_shared_policy_lookup from namespace module:kvm, but does not import it. And the modpost error is correct: even with that error removed, the patch module would fail to load: livepatch_foo: module uses symbol (mpol_shared_policy_lookup) from namespace module:kvm, but does not import it. livepatch_foo: Unknown symbol mpol_shared_policy_lookup (err -22) Treat it like an unexported symbol by using a klp reloc. Note this only affects "module:" namespaces. Ordinary namespaced exports continue to work with normal relocs thanks to copy_import_ns(), which propagates the patched object's import_ns tags to the patch module. Fixes: dd590d4d57eb ("objtool/klp: Introduce klp diff subcommand for diffing object files") Reported-by: Joe Lawrence Link: https://lore.kernel.org/6a6608f4-0a05-4d75-8b7f-edddfac9c5d4@redhat.com Acked-by: Joe Lawrence Acked-by: Song Liu Link: https://patch.msgid.link/fe5a00818e06ec613344d41d5944de054fcd8832.1786138493.git.jpoimboe@kernel.org Signed-off-by: Josh Poimboeuf --- tools/objtool/klp-diff.c | 28 +++++++++++++++++++++++----- 1 file changed, 23 insertions(+), 5 deletions(-) diff --git a/tools/objtool/klp-diff.c b/tools/objtool/klp-diff.c index 0211e5c87160..1f05646f3e9c 100644 --- a/tools/objtool/klp-diff.c +++ b/tools/objtool/klp-diff.c @@ -30,7 +30,9 @@ struct elfs { struct export { struct hlist_node hash; - char *mod, *sym; + char *mod; + char *sym; + bool mod_ns; }; bool debug, debug_correlate, debug_clone; @@ -135,7 +137,7 @@ static int read_exports(void) } while (fgets(line, 1024, file)) { - char *sym, *mod, *type; + char *sym, *mod, *type, *namespace; struct export *export; sym = strchr(line, '\t'); @@ -162,6 +164,14 @@ static int read_exports(void) *type++ = '\0'; + namespace = strchr(type, '\t'); + if (!namespace) { + ERROR("malformed Module.symvers (namespace) at line %d", line_num); + return -1; + } + + *namespace++ = '\0'; + if (*sym == '\0' || *mod == '\0') { ERROR("malformed Module.symvers at line %d", line_num); return -1; @@ -188,6 +198,9 @@ static int read_exports(void) return -1; } + /* EXPORT_SYMBOL_FOR_MODULES() */ + export->mod_ns = strstarts(namespace, "module:"); + hash_add(exports, &export->hash, str_hash(sym)); } @@ -1175,11 +1188,16 @@ static bool klp_reloc_needed(struct reloc *patched_reloc) * clusterfunk that is late module patching, the patch module is * allowed to be loaded before any modules it depends on. * - * If exported by vmlinux, a normal reloc will do. + * If exported by vmlinux to all modules, a normal reloc will do. */ export = find_export(patched_sym); - if (export) - return strcmp(export->mod, "vmlinux"); + if (export) { + if (strcmp(export->mod, "vmlinux")) + return true; + + /* EXPORT_SYMBOL_FOR_MODULES() gets a klp reloc */ + return export->mod_ns; + } if (!patched_sym->twin) { /* From 51c1de13486315ad46a74b40ec17124916a08277 Mon Sep 17 00:00:00 2001 From: Josh Poimboeuf Date: Fri, 7 Aug 2026 14:37:54 -0700 Subject: [PATCH 17/20] objtool/klp: Fix line numbers in Module.symvers parse errors read_exports() reports the offending line number when it fails to parse Module.symvers. The counter is initialized to 1 but never incremented, so every error blames line 1 regardless of where the bad line is. Acked-by: Joe Lawrence Acked-by: Song Liu Link: https://patch.msgid.link/133e16bb0c7cb916f10bbfb017eba525449ad1d6.1786138493.git.jpoimboe@kernel.org Signed-off-by: Josh Poimboeuf --- tools/objtool/klp-diff.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/tools/objtool/klp-diff.c b/tools/objtool/klp-diff.c index 1f05646f3e9c..cdc63abab618 100644 --- a/tools/objtool/klp-diff.c +++ b/tools/objtool/klp-diff.c @@ -118,7 +118,7 @@ static int read_exports(void) { const char *symvers = "Module.symvers"; char line[1024], *path = NULL; - unsigned int line_num = 1; + unsigned int line_num = 0; FILE *file; file = fopen(symvers, "r"); @@ -140,6 +140,8 @@ static int read_exports(void) char *sym, *mod, *type, *namespace; struct export *export; + line_num++; + sym = strchr(line, '\t'); if (!sym) { ERROR("malformed Module.symvers (sym) at line %d", line_num); From 6656cf1e975aa152c082012b820efffaebd49979 Mon Sep 17 00:00:00 2001 From: Puranjay Mohan Date: Fri, 7 Aug 2026 11:16:29 -0700 Subject: [PATCH 18/20] objtool/klp: Fix vmlinux .klp.symid link error for .exitcall.exit symbols Building a kernel via klp-build fails to link: `__exitcall_aes_mod_exit' referenced in section `.klp.symid' of vmlinux.o: defined in discarded section `.exitcall.exit' of vmlinux.o `__exitcall_dax_exit' referenced in section `.klp.symid' of vmlinux.o: defined in discarded section `.exitcall.exit' of vmlinux.o `__exitcall_hid_exit' referenced in section `.klp.symid' of vmlinux.o: defined in discarded section `.exitcall.exit' of vmlinux.o `__exitcall_usb_serial_module_exit' referenced in section `.klp.symid' of vmlinux.o: defined in discarded section `.exitcall.exit' of vmlinux.o module_exit() on a built-in emits a static __exitcall_$fn pointer into .exitcall.exit, which vmlinux.lds.h discards unconditionally via EXIT_CALL. When two built-in translation units define a module_exit() function of the same name, the resulting local symbols collide, symid_needed() sees a duplicate and emits a .klp.symid entry for each, referencing symbols the linker then throws away. Same-named module_exit() functions are not rare: dax_exit drivers/dax/device.c, drivers/dax/fsdev.c hid_exit drivers/hid/hid-core.c, drivers/hid/usbhid/hid-core.c aes_mod_exit arch/arm64/crypto/aes-ce-ccm-glue.c, lib/crypto/aes.c usb_serial_module_exit module_usb_serial_driver() expands to this fixed name in each of its ~49 users The last one makes the collision structural rather than accidental: any kernel with two built-in USB serial drivers has it. This is not arch specific either; it only requires the objects to be built in rather than modular, which is why a monolithic config trips it while a typical distro config does not. Add .exitcall.exit to the discarded section list so its symbols don't get symids. This is the same failure mode as "objtool/klp: Fix vmlinux .klp.symid link error for .no_trim_symbol symbols", for another unconditionally discarded allocated section. Fixes: 029223d30162 ("objtool/klp: Add .klp.symid for sympos disambiguation") Signed-off-by: Puranjay Mohan Link: https://patch.msgid.link/m24igzlbxf.fsf@kernel.org Signed-off-by: Josh Poimboeuf --- tools/objtool/klp-symid.c | 1 + 1 file changed, 1 insertion(+) diff --git a/tools/objtool/klp-symid.c b/tools/objtool/klp-symid.c index 21d8708013ab..b19f76dff13e 100644 --- a/tools/objtool/klp-symid.c +++ b/tools/objtool/klp-symid.c @@ -30,6 +30,7 @@ static const char * const discarded_secs[] = { ".discard", + ".exitcall.exit", ".modinfo", ".no_trim_symbol", "__tracepoint_check", From 7df1638df97b2aaaff4731b72d4940053257c952 Mon Sep 17 00:00:00 2001 From: Josh Poimboeuf Date: Sat, 8 Aug 2026 16:17:05 -0700 Subject: [PATCH 19/20] objtool/klp: Fix .kcfi_traps special section extraction create_fake_symbols() creates a symbol per entry for special sections. It does so in two steps: first for the sections which have ANNOTATE_DATA_SPECIAL annotations, then for the rest, using entsize or the reloc count to infer the entry size. The second step skips the sections already handled by the first one by looking for a symbol at offset 0. That heuristic is too fuzzy: with Clang and CONFIG_CFI, it misfires on .kcfi_traps because Clang emits a .Ltmp* assembler-local label at the start of the section, so no symbols are created and clone_special_sections() extracts nothing. klp-build still reports SUCCESS, but the livepatch module has no __kcfi_traps section and the traps for the patched functions are lost. Look for the actual fake symbols created by the first step instead. Fixes: da4326573ae8d ("objtool/klp: Fix kCFI trap handling") Reported-by: Joe Lawrence Closes: https://lore.kernel.org/r/akQNqlfFC0T5pcMa@redhat.com Acked-by: Song Liu Link: https://patch.msgid.link/8faaead205b219607b6fc2359ae743be824056eb.1786230311.git.jpoimboe@kernel.org Signed-off-by: Josh Poimboeuf --- tools/objtool/include/objtool/elf.h | 1 + tools/objtool/klp-diff.c | 26 ++++++++++++++++++++++++-- 2 files changed, 25 insertions(+), 2 deletions(-) diff --git a/tools/objtool/include/objtool/elf.h b/tools/objtool/include/objtool/elf.h index d9c44df9cc76..a82517a76a0f 100644 --- a/tools/objtool/include/objtool/elf.h +++ b/tools/objtool/include/objtool/elf.h @@ -97,6 +97,7 @@ struct symbol { u8 included : 1; u8 klp : 1; u8 dont_correlate : 1; + u8 fake : 1; struct list_head pv_target; struct reloc *relocs; struct section *group_sec; diff --git a/tools/objtool/klp-diff.c b/tools/objtool/klp-diff.c index cdc63abab618..a66049e0726a 100644 --- a/tools/objtool/klp-diff.c +++ b/tools/objtool/klp-diff.c @@ -1630,6 +1630,7 @@ static int create_fake_symbol(struct elf *elf, struct section *sec, unsigned long offset, size_t size) { char name[SYM_NAME_LEN]; + struct symbol *sym; unsigned int type; static int ctr; char *c; @@ -1646,7 +1647,24 @@ static int create_fake_symbol(struct elf *elf, struct section *sec, * while still allowing objdump to disassemble it. */ type = is_text_sec(sec) ? STT_NOTYPE : STT_OBJECT; - return elf_create_symbol(elf, name, sec, STB_LOCAL, type, offset, size) ? 0 : -1; + + sym = elf_create_symbol(elf, name, sec, STB_LOCAL, type, offset, size); + if (!sym) + return -1; + + sym->fake = 1; + return 0; +} + +static bool has_fake_symbols(struct section *sec) +{ + struct symbol *sym; + + sec_for_each_sym(sec, sym) + if (sym->fake) + return true; + + return false; } /* @@ -1738,7 +1756,11 @@ static int create_fake_symbols(struct elf *elf) unsigned int entry_size; unsigned long offset; - if (!is_special_section(sec) || find_symbol_by_offset(sec, 0)) + if (!is_special_section(sec)) + continue; + + /* Skip sections already handled by step 1 above */ + if (has_fake_symbols(sec)) continue; if (!sec->rsec) { From d8a2860b4a366bfa8acb3d64da2c546ea26d2091 Mon Sep 17 00:00:00 2001 From: Josh Poimboeuf Date: Fri, 14 Aug 2026 19:36:34 -0700 Subject: [PATCH 20/20] objtool/klp: Fix vmlinux klp relocations for EXPORT_SYMBOL_FOR_MODULES() When a module function references a vmlinux symbol which is exported with EXPORT_SYMBOL_FOR_MODULES(), a patch to that function needs to use a klp reloc. Currently, livepatch fails to load such a module: livepatch: invalid access to vmlinux symbol 'get_task_policy' from module-specific livepatch relocation section livepatch: failed to initialize patch 'livepatch_test' for module 'testmod' (-22) livepatch: patch 'livepatch_test' failed for module 'testmod', refusing to load module 'testmod' klp diff puts all klp relocs in __klp_relocs., so post-link names the section .klp.rela.., which the kernel rejects for vmlinux symbols. Commit 07f14d6af9d77 ("objtool/klp: Fix cross-module klp relocation section naming") changed the meaning of objname in the klp rela section name to be where the referenced symbol is referenced rather than where it lives. That premise only holds for symbols in a module: the relocs get applied when the patched module gets patched, and the module dependency guarantees the referenced module is loaded by then. A vmlinux symbol needs the opposite. It's always resolvable, and it has to be applied when the patch module loads, before the module loader initializes the patch module's special sections, which may reference it. That's why livepatch rejects vmlinux symbols in module-specific sections. Use "vmlinux" as the section objname when the referenced symbol lives in vmlinux. This moves such klp relocs from .klp.rela.kvm..text to .klp.rela.vmlinux..text. Fixes: 07f14d6af9d77 ("objtool/klp: Fix cross-module klp relocation section naming") Reported-by: Dylan Hatch Signed-off-by: Josh Poimboeuf Signed-off-by: Ingo Molnar Acked-by: Song Liu Link: https://patch.msgid.link/f8e3b9fae109903a6aafb2a33310e4afdcebf58e.1786761327.git.jpoimboe@kernel.org Closes: https://lore.kernel.org/CADBMgpz7iWC0=t=_gE-tfvv0mTPq4kg0qQ2zgPH8DVPE6eQ9Kw@mail.gmail.com --- tools/objtool/include/objtool/klp.h | 5 +++-- tools/objtool/klp-diff.c | 35 ++++++++++++++++++----------- 2 files changed, 25 insertions(+), 15 deletions(-) diff --git a/tools/objtool/include/objtool/klp.h b/tools/objtool/include/objtool/klp.h index 646d8e1f12ef..c57775d78c71 100644 --- a/tools/objtool/include/objtool/klp.h +++ b/tools/objtool/include/objtool/klp.h @@ -20,8 +20,9 @@ * SHF_RELA_LIVEPATCH, nor does it support having two RELA sections for a * single PROGBITS section. * - * "objname" is the name of the object being patched ("vmlinux" or a module - * name). post-link uses it to name the resulting + * "objname" is the object whose loading gates the relocation: "vmlinux" for + * references to vmlinux symbols, otherwise the name of the module being + * patched. post-link uses it to name the resulting * .klp.rela.objname.section_name sections. */ #define KLP_RELOCS_SEC "__klp_relocs" diff --git a/tools/objtool/klp-diff.c b/tools/objtool/klp-diff.c index a66049e0726a..16681a76f13d 100644 --- a/tools/objtool/klp-diff.c +++ b/tools/objtool/klp-diff.c @@ -1344,13 +1344,14 @@ static int clone_reloc_klp(struct elfs *e, struct reloc *patched_reloc, struct section *sec, unsigned long offset, struct export *export) { + const char *sym_modname, *sym_orig_name, *sec_objname; struct symbol *patched_sym = patched_reloc->sym; s64 addend = reloc_addend(patched_reloc); - const char *sym_modname, *sym_orig_name; - static struct section *klp_relocs; char tombstone_name[SYM_NAME_LEN]; struct symbol *sym, *klp_sym; unsigned long klp_reloc_off; + struct section *klp_relocs; + char sec_name[SEC_NAME_LEN]; char sym_name[SYM_NAME_LEN]; struct klp_reloc klp_reloc; unsigned long sympos; @@ -1441,20 +1442,28 @@ static int clone_reloc_klp(struct elfs *e, struct reloc *patched_reloc, * This intermediate step is necessary to prevent corruption by the * linker, which doesn't know how to properly handle two rela sections * applying to the same base section. + * + * The objname decides when the reloc gets applied. A reference to a + * vmlinux symbol goes in the vmlinux section so it gets applied when + * the patch module loads. Everything else goes in the patched + * object's section, applied when the patched module is loaded. */ + if (!strcmp(sym_modname, "vmlinux")) { + sec_objname = "vmlinux"; + } else { + sec_objname = find_modname(e); + if (!sec_objname) + return -1; + } + + /* section format: __klp_relocs.objname */ + if (snprintf_check(sec_name, SEC_NAME_LEN, + KLP_RELOCS_SEC ".%s", sec_objname)) + return -1; + + klp_relocs = find_section_by_name(e->out, sec_name); if (!klp_relocs) { - const char *objname = find_modname(e); - char sec_name[SEC_NAME_LEN]; - - if (!objname) - return -1; - - /* section format: __klp_relocs.objname */ - if (snprintf_check(sec_name, SEC_NAME_LEN, - KLP_RELOCS_SEC ".%s", objname)) - return -1; - klp_relocs = elf_create_section(e->out, sec_name, 0, 0, SHT_PROGBITS, 8, SHF_ALLOC); if (!klp_relocs)