The Linux Kernel Mailing List
 help / color / mirror / Atom feed
* [PATCH 00/14] objtool/klp: sympos/module/alternative/etc fixes
@ 2026-08-03  3:24 Josh Poimboeuf
  2026-08-03  3:24 ` [PATCH 01/14] objtool/klp: Fix module name normalization for paths with dots Josh Poimboeuf
                   ` (13 more replies)
  0 siblings, 14 replies; 24+ messages in thread
From: Josh Poimboeuf @ 2026-08-03  3:24 UTC (permalink / raw)
  To: x86
  Cc: linux-kernel, live-patching, Peter Zijlstra, Joe Lawrence,
	Miroslav Benes, Petr Mladek, Song Liu, Luis Chamberlain,
	Petr Pavlu, Daniel Gomez, Sami Tolvanen, linux-modules

This consolidates fixes for the klp-build issues reported by Joe over
the last several weeks, plus some more things I found while
testing/reviewing.

Fun stuff like symbol resolution, module dependencies, alternatives.

MODULE MAINTAINERS: see patches 7 and 8, you can probably ignore the
rest.

Joe Lawrence (2):
  objtool/klp: Normalize Module.symvers paths to module names
  objtool/klp: Allow new references to module exports

Josh Poimboeuf (12):
  objtool/klp: Fix module name normalization for paths with dots
  objtool/klp: Fix false module dependencies caused by dead relocs
  objtool/klp: Skip hidden directories when finding objects
  objtool/klp: Add .klp.symid for sympos disambiguation
  objtool/klp: Fix symbol resolution for duplicate data symbols
  module: Add module_kallsyms_on_each_core_symbol()
  objtool/klp,livepatch: Resolve module symbols against core kallsyms
  objtool/klp: Fix size of empty special section entries
  objtool/klp: Ignore replacement offset of empty x86 alternatives
  objtool/klp: Explicitly disallow patching or referencing init
    code/data
  objtool/klp: Fix cross-module klp relocation section naming
  objtool/klp: Don't match local symbols against exports

 include/asm-generic/vmlinux.lds.h       |  10 +-
 include/linux/module.h                  |  11 +
 kernel/livepatch/core.c                 |   2 +-
 kernel/module/kallsyms.c                |  46 +++
 scripts/Makefile.vmlinux_o              |   3 +
 scripts/livepatch/klp-build             |  17 +-
 scripts/mod/modpost.c                   |   1 +
 tools/objtool/Build                     |   4 +-
 tools/objtool/arch/x86/special.c        |  27 ++
 tools/objtool/builtin-check.c           |   7 +
 tools/objtool/check.c                   |   7 +
 tools/objtool/elf.c                     |  13 +
 tools/objtool/include/objtool/builtin.h |   1 +
 tools/objtool/include/objtool/klp.h     |  32 +-
 tools/objtool/include/objtool/special.h |   7 +
 tools/objtool/klp-diff.c                | 192 ++++++-----
 tools/objtool/klp-post-link.c           |  53 +--
 tools/objtool/klp-symid.c               | 117 +++++++
 tools/objtool/klp-sympos.c              | 441 ++++++++++++++++++++++++
 19 files changed, 877 insertions(+), 114 deletions(-)
 create mode 100644 tools/objtool/klp-symid.c
 create mode 100644 tools/objtool/klp-sympos.c

-- 
2.54.0


^ permalink raw reply	[flat|nested] 24+ messages in thread

* [PATCH 01/14] objtool/klp: Fix module name normalization for paths with dots
  2026-08-03  3:24 [PATCH 00/14] objtool/klp: sympos/module/alternative/etc fixes Josh Poimboeuf
@ 2026-08-03  3:24 ` Josh Poimboeuf
  2026-08-03  5:49   ` [tip: objtool/core] " tip-bot2 for Josh Poimboeuf
  2026-08-03  3:24 ` [PATCH 02/14] objtool/klp: Normalize Module.symvers paths to module names Josh Poimboeuf
                   ` (12 subsequent siblings)
  13 siblings, 1 reply; 24+ messages in thread
From: Josh Poimboeuf @ 2026-08-03  3:24 UTC (permalink / raw)
  To: x86
  Cc: linux-kernel, live-patching, Peter Zijlstra, Joe Lawrence,
	Miroslav Benes, Petr Mladek, Song Liu, Luis Chamberlain,
	Petr Pavlu, Daniel Gomez, Sami Tolvanen, linux-modules, Sashiko

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 <sashiko-bot@kernel.org>
Signed-off-by: Josh Poimboeuf <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';
-- 
2.54.0


^ permalink raw reply related	[flat|nested] 24+ messages in thread

* [PATCH 02/14] objtool/klp: Normalize Module.symvers paths to module names
  2026-08-03  3:24 [PATCH 00/14] objtool/klp: sympos/module/alternative/etc fixes Josh Poimboeuf
  2026-08-03  3:24 ` [PATCH 01/14] objtool/klp: Fix module name normalization for paths with dots Josh Poimboeuf
@ 2026-08-03  3:24 ` Josh Poimboeuf
  2026-08-03  5:49   ` [tip: objtool/core] " tip-bot2 for Joe Lawrence
  2026-08-03  3:24 ` [PATCH 03/14] objtool/klp: Fix false module dependencies caused by dead relocs Josh Poimboeuf
                   ` (11 subsequent siblings)
  13 siblings, 1 reply; 24+ messages in thread
From: Josh Poimboeuf @ 2026-08-03  3:24 UTC (permalink / raw)
  To: x86
  Cc: linux-kernel, live-patching, Peter Zijlstra, Joe Lawrence,
	Miroslav Benes, Petr Mladek, Song Liu, Luis Chamberlain,
	Petr Pavlu, Daniel Gomez, Sami Tolvanen, linux-modules,
	Ben Procknow

From: Joe Lawrence <joe.lawrence@redhat.com>

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 <bprockno@redhat.com>
Signed-off-by: Joe Lawrence <joe.lawrence@redhat.com>
Reviewed-by: Miroslav Benes <mbenes@suse.cz>
Signed-off-by: Josh Poimboeuf <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) */
-- 
2.54.0


^ permalink raw reply related	[flat|nested] 24+ messages in thread

* [PATCH 03/14] objtool/klp: Fix false module dependencies caused by dead relocs
  2026-08-03  3:24 [PATCH 00/14] objtool/klp: sympos/module/alternative/etc fixes Josh Poimboeuf
  2026-08-03  3:24 ` [PATCH 01/14] objtool/klp: Fix module name normalization for paths with dots Josh Poimboeuf
  2026-08-03  3:24 ` [PATCH 02/14] objtool/klp: Normalize Module.symvers paths to module names Josh Poimboeuf
@ 2026-08-03  3:24 ` Josh Poimboeuf
  2026-08-03  5:49   ` [tip: objtool/core] " tip-bot2 for Josh Poimboeuf
  2026-08-03  3:24 ` [PATCH 04/14] objtool/klp: Skip hidden directories when finding objects Josh Poimboeuf
                   ` (10 subsequent siblings)
  13 siblings, 1 reply; 24+ messages in thread
From: Josh Poimboeuf @ 2026-08-03  3:24 UTC (permalink / raw)
  To: x86
  Cc: linux-kernel, live-patching, Peter Zijlstra, Joe Lawrence,
	Miroslav Benes, Petr Mladek, Song Liu, Luis Chamberlain,
	Petr Pavlu, Daniel Gomez, Sami Tolvanen, linux-modules,
	Ben Procknow

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.<name>" so
modpost no longer recognizes them.

Fixes: dd590d4d57eb ("objtool/klp: Introduce klp diff subcommand for diffing object files")
Reported-by: Ben Procknow <bprockno@redhat.com>
Reported-by: Joe Lawrence <joe.lawrence@redhat.com>
Link: https://lore.kernel.org/20260720145658.1103243-5-joe.lawrence@redhat.com
Signed-off-by: Josh Poimboeuf <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 <linux/log2.h>
 #include <objtool/builtin.h>
 #include <objtool/elf.h>
+#include <objtool/klp.h>
 #include <objtool/warn.h>
 
 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;
-- 
2.54.0


^ permalink raw reply related	[flat|nested] 24+ messages in thread

* [PATCH 04/14] objtool/klp: Skip hidden directories when finding objects
  2026-08-03  3:24 [PATCH 00/14] objtool/klp: sympos/module/alternative/etc fixes Josh Poimboeuf
                   ` (2 preceding siblings ...)
  2026-08-03  3:24 ` [PATCH 03/14] objtool/klp: Fix false module dependencies caused by dead relocs Josh Poimboeuf
@ 2026-08-03  3:24 ` Josh Poimboeuf
  2026-08-03  5:49   ` [tip: objtool/core] " tip-bot2 for Josh Poimboeuf
  2026-08-03  3:24 ` [PATCH 05/14] objtool/klp: Add .klp.symid for sympos disambiguation Josh Poimboeuf
                   ` (9 subsequent siblings)
  13 siblings, 1 reply; 24+ messages in thread
From: Josh Poimboeuf @ 2026-08-03  3:24 UTC (permalink / raw)
  To: x86
  Cc: linux-kernel, live-patching, Peter Zijlstra, Joe Lawrence,
	Miroslav Benes, Petr Mladek, Song Liu, Luis Chamberlain,
	Petr Pavlu, Daniel Gomez, Sami Tolvanen, linux-modules

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 <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'
-- 
2.54.0


^ permalink raw reply related	[flat|nested] 24+ messages in thread

* [PATCH 05/14] objtool/klp: Add .klp.symid for sympos disambiguation
  2026-08-03  3:24 [PATCH 00/14] objtool/klp: sympos/module/alternative/etc fixes Josh Poimboeuf
                   ` (3 preceding siblings ...)
  2026-08-03  3:24 ` [PATCH 04/14] objtool/klp: Skip hidden directories when finding objects Josh Poimboeuf
@ 2026-08-03  3:24 ` Josh Poimboeuf
  2026-08-03  5:49   ` [tip: objtool/core] " tip-bot2 for Josh Poimboeuf
  2026-08-03  3:24 ` [PATCH 06/14] objtool/klp: Fix symbol resolution for duplicate data symbols Josh Poimboeuf
                   ` (8 subsequent siblings)
  13 siblings, 1 reply; 24+ messages in thread
From: Josh Poimboeuf @ 2026-08-03  3:24 UTC (permalink / raw)
  To: x86
  Cc: linux-kernel, live-patching, Peter Zijlstra, Joe Lawrence,
	Miroslav Benes, Petr Mladek, Song Liu, Luis Chamberlain,
	Petr Pavlu, Daniel Gomez, Sami Tolvanen, linux-modules

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 <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 10b18cf9c360..4e6366663be1 100644
--- a/tools/objtool/check.c
+++ b/tools/objtool/check.c
@@ -15,6 +15,7 @@
 #include <objtool/arch.h>
 #include <objtool/disas.h>
 #include <objtool/check.h>
+#include <objtool/klp.h>
 #include <objtool/special.h>
 #include <objtool/trace.h>
 #include <objtool/warn.h>
@@ -4922,6 +4923,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 <linux/string.h>
+
+#include <objtool/objtool.h>
+#include <objtool/warn.h>
+#include <objtool/endianness.h>
+#include <objtool/klp.h>
+
+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;
+}
-- 
2.54.0


^ permalink raw reply related	[flat|nested] 24+ messages in thread

* [PATCH 06/14] objtool/klp: Fix symbol resolution for duplicate data symbols
  2026-08-03  3:24 [PATCH 00/14] objtool/klp: sympos/module/alternative/etc fixes Josh Poimboeuf
                   ` (4 preceding siblings ...)
  2026-08-03  3:24 ` [PATCH 05/14] objtool/klp: Add .klp.symid for sympos disambiguation Josh Poimboeuf
@ 2026-08-03  3:24 ` Josh Poimboeuf
  2026-08-03  5:49   ` [tip: objtool/core] " tip-bot2 for Josh Poimboeuf
  2026-08-03  3:24 ` [PATCH 07/14] module: Add module_kallsyms_on_each_core_symbol() Josh Poimboeuf
                   ` (7 subsequent siblings)
  13 siblings, 1 reply; 24+ messages in thread
From: Josh Poimboeuf @ 2026-08-03  3:24 UTC (permalink / raw)
  To: x86
  Cc: linux-kernel, live-patching, Peter Zijlstra, Joe Lawrence,
	Miroslav Benes, Petr Mladek, Song Liu, Luis Chamberlain,
	Petr Pavlu, Daniel Gomez, Sami Tolvanen, linux-modules,
	Ben Procknow

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 <bprockno@redhat.com>
Reported-by: Joe Lawrence <joe.lawrence@redhat.com>
Link: https://lore.kernel.org/20260710153042.3156788-1-joe.lawrence@redhat.com
Link: https://lore.kernel.org/20260724221730.3126529-1-joe.lawrence@redhat.com
Signed-off-by: Josh Poimboeuf <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 <stdlib.h>
+#include <string.h>
+#include <fcntl.h>
+
+#include <objtool/objtool.h>
+#include <objtool/warn.h>
+#include <objtool/endianness.h>
+#include <objtool/klp.h>
+
+#include <linux/string.h>
+
+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;
+}
-- 
2.54.0


^ permalink raw reply related	[flat|nested] 24+ messages in thread

* [PATCH 07/14] module: Add module_kallsyms_on_each_core_symbol()
  2026-08-03  3:24 [PATCH 00/14] objtool/klp: sympos/module/alternative/etc fixes Josh Poimboeuf
                   ` (5 preceding siblings ...)
  2026-08-03  3:24 ` [PATCH 06/14] objtool/klp: Fix symbol resolution for duplicate data symbols Josh Poimboeuf
@ 2026-08-03  3:24 ` Josh Poimboeuf
  2026-08-03  6:24   ` Josh Poimboeuf
  2026-08-03  3:24 ` [PATCH 08/14] objtool/klp,livepatch: Resolve module symbols against core kallsyms Josh Poimboeuf
                   ` (6 subsequent siblings)
  13 siblings, 1 reply; 24+ messages in thread
From: Josh Poimboeuf @ 2026-08-03  3:24 UTC (permalink / raw)
  To: x86
  Cc: linux-kernel, live-patching, Peter Zijlstra, Joe Lawrence,
	Miroslav Benes, Petr Mladek, Song Liu, Luis Chamberlain,
	Petr Pavlu, Daniel Gomez, Sami Tolvanen, linux-modules

module_kallsyms_on_each_symbol() iterates mod->kallsyms, which points at
the full init symbol table until do_init_module() swaps it out.  The set
of symbols it reports thus differs based on whether init memory has been
freed yet.

Add module_kallsyms_on_each_core_symbol() for callers which need a
symbol's position to be the same before and after that swap.
core_kallsyms is fully populated by add_kallsyms() before the module
leaves MODULE_STATE_UNFORMED, so it's readable on both paths.

Signed-off-by: Josh Poimboeuf <jpoimboe@kernel.org>
---
 include/linux/module.h   | 11 ++++++++++
 kernel/module/kallsyms.c | 46 ++++++++++++++++++++++++++++++++++++++++
 2 files changed, 57 insertions(+)

diff --git a/include/linux/module.h b/include/linux/module.h
index 7566815fabbe..4ea4522a5fc5 100644
--- a/include/linux/module.h
+++ b/include/linux/module.h
@@ -947,6 +947,9 @@ static inline bool module_sig_ok(struct module *module)
 int module_kallsyms_on_each_symbol(const char *modname,
 				   int (*fn)(void *, const char *, unsigned long),
 				   void *data);
+int module_kallsyms_on_each_core_symbol(const char *modname,
+					int (*fn)(void *, const char *, unsigned long),
+					void *data);
 
 /* For kallsyms to ask for address resolution.  namebuf should be at
  * least KSYM_NAME_LEN long: a pointer to namebuf is returned if
@@ -984,6 +987,14 @@ static inline int module_kallsyms_on_each_symbol(const char *modname,
 	return -EOPNOTSUPP;
 }
 
+static inline int
+module_kallsyms_on_each_core_symbol(const char *modname,
+				    int (*fn)(void *, const char *, unsigned long),
+				    void *data)
+{
+	return -EOPNOTSUPP;
+}
+
 /* For kallsyms to ask for address resolution.  NULL means not found. */
 static inline int module_address_lookup(unsigned long addr,
 						unsigned long *symbolsize,
diff --git a/kernel/module/kallsyms.c b/kernel/module/kallsyms.c
index 0fc11e45df9b..3a959c3c9f9b 100644
--- a/kernel/module/kallsyms.c
+++ b/kernel/module/kallsyms.c
@@ -494,3 +494,49 @@ int module_kallsyms_on_each_symbol(const char *modname,
 	mutex_unlock(&module_mutex);
 	return ret;
 }
+
+/*
+ * Iterate @modname's cut-down core symbol table, rather than mod->kallsyms
+ * which points at the full init symbol table until do_init_module() swaps it
+ * out.  For callers which need a symbol's position to be the same before and
+ * after that swap.
+ *
+ * core_kallsyms is populated by add_kallsyms(), which runs before the module
+ * leaves MODULE_STATE_UNFORMED.
+ *
+ * Unlike module_kallsyms_on_each_symbol(), @modname is required.
+ */
+int module_kallsyms_on_each_core_symbol(const char *modname,
+					int (*fn)(void *, const char *, unsigned long),
+					void *data)
+{
+	struct mod_kallsyms *kallsyms;
+	struct module *mod;
+	unsigned int i;
+	int ret = 0;
+
+	if (!modname)
+		return -EINVAL;
+
+	guard(mutex)(&module_mutex);
+
+	mod = find_module_all(modname, strlen(modname), false);
+	if (!mod)
+		return -ENOENT;
+
+	kallsyms = &mod->core_kallsyms;
+
+	for (i = 0; i < kallsyms->num_symtab; i++) {
+		const Elf_Sym *sym = &kallsyms->symtab[i];
+
+		if (sym->st_shndx == SHN_UNDEF)
+			continue;
+
+		ret = fn(data, kallsyms_symbol_name(kallsyms, i),
+			 kallsyms_symbol_value(sym));
+		if (ret)
+			break;
+	}
+
+	return ret;
+}
-- 
2.54.0


^ permalink raw reply related	[flat|nested] 24+ messages in thread

* [PATCH 08/14] objtool/klp,livepatch: Resolve module symbols against core kallsyms
  2026-08-03  3:24 [PATCH 00/14] objtool/klp: sympos/module/alternative/etc fixes Josh Poimboeuf
                   ` (6 preceding siblings ...)
  2026-08-03  3:24 ` [PATCH 07/14] module: Add module_kallsyms_on_each_core_symbol() Josh Poimboeuf
@ 2026-08-03  3:24 ` Josh Poimboeuf
  2026-08-03  6:26   ` Josh Poimboeuf
  2026-08-03  3:24 ` [PATCH 09/14] objtool/klp: Fix size of empty special section entries Josh Poimboeuf
                   ` (5 subsequent siblings)
  13 siblings, 1 reply; 24+ messages in thread
From: Josh Poimboeuf @ 2026-08-03  3:24 UTC (permalink / raw)
  To: x86
  Cc: linux-kernel, live-patching, Peter Zijlstra, Joe Lawrence,
	Miroslav Benes, Petr Mladek, Song Liu, Luis Chamberlain,
	Petr Pavlu, Daniel Gomez, Sami Tolvanen, linux-modules

For patching a module, klp_find_sympos() counts every symbol table entry
whose name matches.  However, the module loader only includes symbols
matched by is_core_symbol().

The runtime count is inconsistent as well.  It's done by
module_kallsyms_on_each_symbol(), which iterates the full init symbol
table until do_init_module() swaps in the cut-down core table, so the
same symbol can have different positions depending on whether init
memory has been freed yet.

Define a module's sympos as its position in the core symbol table, which
is what sympos already means for a live module and what users see in
/proc/kallsyms.  Enforce that on both ends: count with
module_kallsyms_on_each_core_symbol() at runtime, and mirror the
is_core_symbol() filter in objtool with a new mod_sym_in_kallsyms()
helper.

The init-layout half of the filter is only correct if .exit sections are
core sections, which requires CONFIG_MODULE_UNLOAD, otherwise .exit code
is laid out as part of init memory and freed after module init.  Enforce
CONFIG_MODULE_UNLOAD to ensure that behavior is deterministic.

Symbols which exist only in init sections are no longer resolvable, but
they never were once the module went live, and init memory is freed
after module init anyway.

Fixes: dd590d4d57eb ("objtool/klp: Introduce klp diff subcommand for diffing object files")
Fixes: b2b018ef4867 ("livepatch: add old_sympos as disambiguator field to klp_func")
Signed-off-by: Josh Poimboeuf <jpoimboe@kernel.org>
---
 kernel/livepatch/core.c     |  2 +-
 scripts/livepatch/klp-build |  3 +++
 tools/objtool/klp-sympos.c  | 22 +++++++++++++++++++++-
 3 files changed, 25 insertions(+), 2 deletions(-)

diff --git a/kernel/livepatch/core.c b/kernel/livepatch/core.c
index 28d15ba58a26..a05cd2c38caa 100644
--- a/kernel/livepatch/core.c
+++ b/kernel/livepatch/core.c
@@ -168,7 +168,7 @@ static int klp_find_object_symbol(const char *objname, const char *name,
 	};
 
 	if (objname)
-		module_kallsyms_on_each_symbol(objname, klp_find_callback, &args);
+		module_kallsyms_on_each_core_symbol(objname, klp_find_callback, &args);
 	else
 		kallsyms_on_each_match_symbol(klp_match_callback, name, &args);
 
diff --git a/scripts/livepatch/klp-build b/scripts/livepatch/klp-build
index b52a8489d9f6..9b375e018d76 100755
--- a/scripts/livepatch/klp-build
+++ b/scripts/livepatch/klp-build
@@ -265,6 +265,9 @@ validate_config() {
 	[[ -v CONFIG_KLP_BUILD ]] ||			\
 		die "CONFIG_KLP_BUILD not enabled"
 
+	[[ -v CONFIG_MODULE_UNLOAD ]] ||		\
+		die "kernel option 'CONFIG_MODULE_UNLOAD' required"
+
 	[[ -v CONFIG_GCC_PLUGIN_LATENT_ENTROPY ]] &&	\
 		die "kernel option 'CONFIG_GCC_PLUGIN_LATENT_ENTROPY' not supported"
 
diff --git a/tools/objtool/klp-sympos.c b/tools/objtool/klp-sympos.c
index bbfae516d339..34bb8d1971bd 100644
--- a/tools/objtool/klp-sympos.c
+++ b/tools/objtool/klp-sympos.c
@@ -367,6 +367,17 @@ static unsigned long find_vmlinux_sympos(struct symbol *sym)
 	return sympos;
 }
 
+static bool mod_sym_in_kallsyms(struct symbol *sym)
+{
+	if (is_undef_sym(sym))
+		return false;
+
+	if (!(sym->sec->sh.sh_flags & SHF_ALLOC))
+		return false;
+
+	return !strstarts(sym->sec->name, ".init");
+}
+
 /*
  * "sympos" is used by livepatch to disambiguate duplicate symbol names.
  */
@@ -387,12 +398,21 @@ unsigned long klp_find_sympos(struct elf *elf, struct symbol *sym)
 	if (vmlinux.elf)
 		return find_vmlinux_sympos(sym);
 
+	if (!mod_sym_in_kallsyms(sym)) {
+		ERROR("symbol %s is not visible to module kallsyms, can't compute sympos",
+		      sym->name);
+		return ULONG_MAX;
+	}
+
 	/*
 	 * 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().
+	 * module_kallsyms_on_each_core_symbol().
 	 */
 	for_each_sym(elf, s) {
+		if (!mod_sym_in_kallsyms(s))
+			continue;
+
 		if (!strcmp(s->name, sym->name)) {
 			nr_matches++;
 			if (s == sym)
-- 
2.54.0


^ permalink raw reply related	[flat|nested] 24+ messages in thread

* [PATCH 09/14] objtool/klp: Fix size of empty special section entries
  2026-08-03  3:24 [PATCH 00/14] objtool/klp: sympos/module/alternative/etc fixes Josh Poimboeuf
                   ` (7 preceding siblings ...)
  2026-08-03  3:24 ` [PATCH 08/14] objtool/klp,livepatch: Resolve module symbols against core kallsyms Josh Poimboeuf
@ 2026-08-03  3:24 ` Josh Poimboeuf
  2026-08-03  3:24 ` [PATCH 10/14] objtool/klp: Ignore replacement offset of empty x86 alternatives Josh Poimboeuf
                   ` (4 subsequent siblings)
  13 siblings, 0 replies; 24+ messages in thread
From: Josh Poimboeuf @ 2026-08-03  3:24 UTC (permalink / raw)
  To: x86
  Cc: linux-kernel, live-patching, Peter Zijlstra, Joe Lawrence,
	Miroslav Benes, Petr Mladek, Song Liu, Luis Chamberlain,
	Petr Pavlu, Daniel Gomez, Sami Tolvanen, linux-modules

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")
Signed-off-by: Josh Poimboeuf <jpoimboe@kernel.org>
---
 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 c5284d275207..257e7f924928 100644
--- a/tools/objtool/klp-diff.c
+++ b/tools/objtool/klp-diff.c
@@ -1628,6 +1628,7 @@ 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;
@@ -1642,10 +1643,11 @@ static int create_fake_symbols(struct elf *elf)
 				continue;
 
 			size = reloc_addend(next_reloc) - offset;
+			last = false;
 			break;
 		}
 
-		if (!size)
+		if (last)
 			size = sec_size(reloc->sym->sec) - offset;
 
 		if (create_fake_symbol(elf, reloc->sym->sec, offset, size))
-- 
2.54.0


^ permalink raw reply related	[flat|nested] 24+ messages in thread

* [PATCH 10/14] objtool/klp: Ignore replacement offset of empty x86 alternatives
  2026-08-03  3:24 [PATCH 00/14] objtool/klp: sympos/module/alternative/etc fixes Josh Poimboeuf
                   ` (8 preceding siblings ...)
  2026-08-03  3:24 ` [PATCH 09/14] objtool/klp: Fix size of empty special section entries Josh Poimboeuf
@ 2026-08-03  3:24 ` Josh Poimboeuf
  2026-08-03  3:24 ` [PATCH 11/14] objtool/klp: Explicitly disallow patching or referencing init code/data Josh Poimboeuf
                   ` (3 subsequent siblings)
  13 siblings, 0 replies; 24+ messages in thread
From: Josh Poimboeuf @ 2026-08-03  3:24 UTC (permalink / raw)
  To: x86
  Cc: linux-kernel, live-patching, Peter Zijlstra, Joe Lawrence,
	Miroslav Benes, Petr Mladek, Song Liu, Luis Chamberlain,
	Petr Pavlu, Daniel Gomez, Sami Tolvanen, linux-modules

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")
Signed-off-by: Josh Poimboeuf <jpoimboe@kernel.org>
---
 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 <string.h>
 
+#include <arch/special.h>
 #include <objtool/special.h>
 #include <objtool/builtin.h>
 #include <objtool/warn.h>
@@ -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 257e7f924928..cf6fc88bc979 100644
--- a/tools/objtool/klp-diff.c
+++ b/tools/objtool/klp-diff.c
@@ -12,7 +12,7 @@
 #include <objtool/arch.h>
 #include <objtool/klp.h>
 #include <objtool/util.h>
-#include <arch/special.h>
+#include <objtool/special.h>
 
 #include <linux/align.h>
 #include <linux/objtool_types.h>
@@ -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),
-- 
2.54.0


^ permalink raw reply related	[flat|nested] 24+ messages in thread

* [PATCH 11/14] objtool/klp: Explicitly disallow patching or referencing init code/data
  2026-08-03  3:24 [PATCH 00/14] objtool/klp: sympos/module/alternative/etc fixes Josh Poimboeuf
                   ` (9 preceding siblings ...)
  2026-08-03  3:24 ` [PATCH 10/14] objtool/klp: Ignore replacement offset of empty x86 alternatives Josh Poimboeuf
@ 2026-08-03  3:24 ` Josh Poimboeuf
  2026-08-03  3:24 ` [PATCH 12/14] objtool/klp: Fix cross-module klp relocation section naming Josh Poimboeuf
                   ` (2 subsequent siblings)
  13 siblings, 0 replies; 24+ messages in thread
From: Josh Poimboeuf @ 2026-08-03  3:24 UTC (permalink / raw)
  To: x86
  Cc: linux-kernel, live-patching, Peter Zijlstra, Joe Lawrence,
	Miroslav Benes, Petr Mladek, Song Liu, Luis Chamberlain,
	Petr Pavlu, Daniel Gomez, Sami Tolvanen, linux-modules

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.

Signed-off-by: Josh Poimboeuf <jpoimboe@kernel.org>
---
 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 34bb8d1971bd..822ac53cfa47 100644
--- a/tools/objtool/klp-sympos.c
+++ b/tools/objtool/klp-sympos.c
@@ -378,6 +378,11 @@ static bool mod_sym_in_kallsyms(struct symbol *sym)
 	return !strstarts(sym->sec->name, ".init");
 }
 
+static bool is_init_sym(struct symbol *sym)
+{
+	return strstarts(sym->sec->name, ".init");
+}
+
 /*
  * "sympos" is used by livepatch to disambiguate duplicate symbol names.
  */
@@ -387,6 +392,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;
 
-- 
2.54.0


^ permalink raw reply related	[flat|nested] 24+ messages in thread

* [PATCH 12/14] objtool/klp: Fix cross-module klp relocation section naming
  2026-08-03  3:24 [PATCH 00/14] objtool/klp: sympos/module/alternative/etc fixes Josh Poimboeuf
                   ` (10 preceding siblings ...)
  2026-08-03  3:24 ` [PATCH 11/14] objtool/klp: Explicitly disallow patching or referencing init code/data Josh Poimboeuf
@ 2026-08-03  3:24 ` Josh Poimboeuf
  2026-08-03  3:24 ` [PATCH 13/14] objtool/klp: Don't match local symbols against exports Josh Poimboeuf
  2026-08-03  3:24 ` [PATCH 14/14] objtool/klp: Allow new references to module exports Josh Poimboeuf
  13 siblings, 0 replies; 24+ messages in thread
From: Josh Poimboeuf @ 2026-08-03  3:24 UTC (permalink / raw)
  To: x86
  Cc: linux-kernel, live-patching, Peter Zijlstra, Joe Lawrence,
	Miroslav Benes, Petr Mladek, Song Liu, Luis Chamberlain,
	Petr Pavlu, Daniel Gomez, Sami Tolvanen, linux-modules

A klp relocation section is .klp.rela.<objname>.<secname>, 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.<objname> 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 <joe.lawrence@redhat.com>
Link: https://lore.kernel.org/20260720145658.1103243-2-joe.lawrence@redhat.com
Signed-off-by: Josh Poimboeuf <jpoimboe@kernel.org>
---
 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.<objname> 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 cf6fc88bc979..e00dbe053a6e 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.<objname> 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 <objtool/util.h>
 #include <linux/livepatch_external.h>
 
-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.
  */
-- 
2.54.0


^ permalink raw reply related	[flat|nested] 24+ messages in thread

* [PATCH 13/14] objtool/klp: Don't match local symbols against exports
  2026-08-03  3:24 [PATCH 00/14] objtool/klp: sympos/module/alternative/etc fixes Josh Poimboeuf
                   ` (11 preceding siblings ...)
  2026-08-03  3:24 ` [PATCH 12/14] objtool/klp: Fix cross-module klp relocation section naming Josh Poimboeuf
@ 2026-08-03  3:24 ` Josh Poimboeuf
  2026-08-03  3:24 ` [PATCH 14/14] objtool/klp: Allow new references to module exports Josh Poimboeuf
  13 siblings, 0 replies; 24+ messages in thread
From: Josh Poimboeuf @ 2026-08-03  3:24 UTC (permalink / raw)
  To: x86
  Cc: linux-kernel, live-patching, Peter Zijlstra, Joe Lawrence,
	Miroslav Benes, Petr Mladek, Song Liu, Luis Chamberlain,
	Petr Pavlu, Daniel Gomez, Sami Tolvanen, linux-modules

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")
Signed-off-by: Josh Poimboeuf <jpoimboe@kernel.org>
---
 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 e00dbe053a6e..e0dc22cef2c3 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;
-- 
2.54.0


^ permalink raw reply related	[flat|nested] 24+ messages in thread

* [PATCH 14/14] objtool/klp: Allow new references to module exports
  2026-08-03  3:24 [PATCH 00/14] objtool/klp: sympos/module/alternative/etc fixes Josh Poimboeuf
                   ` (12 preceding siblings ...)
  2026-08-03  3:24 ` [PATCH 13/14] objtool/klp: Don't match local symbols against exports Josh Poimboeuf
@ 2026-08-03  3:24 ` Josh Poimboeuf
  13 siblings, 0 replies; 24+ messages in thread
From: Josh Poimboeuf @ 2026-08-03  3:24 UTC (permalink / raw)
  To: x86
  Cc: linux-kernel, live-patching, Peter Zijlstra, Joe Lawrence,
	Miroslav Benes, Petr Mladek, Song Liu, Luis Chamberlain,
	Petr Pavlu, Daniel Gomez, Sami Tolvanen, linux-modules

From: Joe Lawrence <joe.lawrence@redhat.com>

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 <joe.lawrence@redhat.com>
Signed-off-by: Josh Poimboeuf <jpoimboe@kernel.org>
---
 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 e0dc22cef2c3..abda2e5c17f7 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;
+		}
 	}
 
 	/*
-- 
2.54.0


^ permalink raw reply related	[flat|nested] 24+ messages in thread

* [tip: objtool/core] objtool/klp: Fix symbol resolution for duplicate data symbols
  2026-08-03  3:24 ` [PATCH 06/14] objtool/klp: Fix symbol resolution for duplicate data symbols Josh Poimboeuf
@ 2026-08-03  5:49   ` tip-bot2 for Josh Poimboeuf
  0 siblings, 0 replies; 24+ messages in thread
From: tip-bot2 for Josh Poimboeuf @ 2026-08-03  5:49 UTC (permalink / raw)
  To: linux-tip-commits
  Cc: Ben Procknow, Joe Lawrence, Josh Poimboeuf, Ingo Molnar,
	live-patching, x86, linux-kernel

The following commit has been merged into the objtool/core branch of tip:

Commit-ID:     15fa203ef91e8a303c322eaaa8ca01a6ddaf94dc
Gitweb:        https://git.kernel.org/tip/15fa203ef91e8a303c322eaaa8ca01a6ddaf94dc
Author:        Josh Poimboeuf <jpoimboe@kernel.org>
AuthorDate:    Sun, 02 Aug 2026 20:24:28 -07:00
Committer:     Ingo Molnar <mingo@kernel.org>
CommitterDate: Mon, 03 Aug 2026 07:12:39 +02:00

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 <bprockno@redhat.com>
Reported-by: Joe Lawrence <joe.lawrence@redhat.com>
Signed-off-by: Josh Poimboeuf <jpoimboe@kernel.org>
Signed-off-by: Ingo Molnar <mingo@kernel.org>
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 f94e324..b52a848 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 506f89b..59f9486 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 4d3c3bd..0118c2c 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 75ba0e0..c5284d2 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 0000000..bbfae51
--- /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 <stdlib.h>
+#include <string.h>
+#include <fcntl.h>
+
+#include <objtool/objtool.h>
+#include <objtool/warn.h>
+#include <objtool/endianness.h>
+#include <objtool/klp.h>
+
+#include <linux/string.h>
+
+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;
+}

^ permalink raw reply related	[flat|nested] 24+ messages in thread

* [tip: objtool/core] objtool/klp: Add .klp.symid for sympos disambiguation
  2026-08-03  3:24 ` [PATCH 05/14] objtool/klp: Add .klp.symid for sympos disambiguation Josh Poimboeuf
@ 2026-08-03  5:49   ` tip-bot2 for Josh Poimboeuf
  2026-08-03  6:12     ` sashiko-bot
  0 siblings, 1 reply; 24+ messages in thread
From: tip-bot2 for Josh Poimboeuf @ 2026-08-03  5:49 UTC (permalink / raw)
  To: linux-tip-commits
  Cc: Josh Poimboeuf, Ingo Molnar, live-patching, x86, linux-kernel

The following commit has been merged into the objtool/core branch of tip:

Commit-ID:     029223d301620bc4e1086696047b0d5d6eba5edd
Gitweb:        https://git.kernel.org/tip/029223d301620bc4e1086696047b0d5d6eba5edd
Author:        Josh Poimboeuf <jpoimboe@kernel.org>
AuthorDate:    Sun, 02 Aug 2026 20:24:27 -07:00
Committer:     Ingo Molnar <mingo@kernel.org>
CommitterDate: Mon, 03 Aug 2026 07:12:39 +02:00

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 <jpoimboe@kernel.org>
Signed-off-by: Ingo Molnar <mingo@kernel.org>
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 5659f4b..ee9c5d3 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 527352c..24a3a4f 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 a8c103c..f94e324 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 a7b72a8..027944f 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 93a37b0..506f89b 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 118c3de..75b11dc 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 f03dd59..a98d758 100644
--- a/tools/objtool/check.c
+++ b/tools/objtool/check.c
@@ -15,6 +15,7 @@
 #include <objtool/arch.h>
 #include <objtool/disas.h>
 #include <objtool/check.h>
+#include <objtool/klp.h>
 #include <objtool/special.h>
 #include <objtool/trace.h>
 #include <objtool/warn.h>
@@ -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 e844e9c..349690b 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 aab6db4..4d3c3bd 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 0000000..cf188cd
--- /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 <linux/string.h>
+
+#include <objtool/objtool.h>
+#include <objtool/warn.h>
+#include <objtool/endianness.h>
+#include <objtool/klp.h>
+
+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;
+}

^ permalink raw reply related	[flat|nested] 24+ messages in thread

* [tip: objtool/core] objtool/klp: Skip hidden directories when finding objects
  2026-08-03  3:24 ` [PATCH 04/14] objtool/klp: Skip hidden directories when finding objects Josh Poimboeuf
@ 2026-08-03  5:49   ` tip-bot2 for Josh Poimboeuf
  0 siblings, 0 replies; 24+ messages in thread
From: tip-bot2 for Josh Poimboeuf @ 2026-08-03  5:49 UTC (permalink / raw)
  To: linux-tip-commits
  Cc: Josh Poimboeuf, Ingo Molnar, live-patching, x86, linux-kernel

The following commit has been merged into the objtool/core branch of tip:

Commit-ID:     f5f762fc93d0b81d30b815a2f96320909ad10eb3
Gitweb:        https://git.kernel.org/tip/f5f762fc93d0b81d30b815a2f96320909ad10eb3
Author:        Josh Poimboeuf <jpoimboe@kernel.org>
AuthorDate:    Sun, 02 Aug 2026 20:24:26 -07:00
Committer:     Ingo Molnar <mingo@kernel.org>
CommitterDate: Mon, 03 Aug 2026 07:12:39 +02:00

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 <jpoimboe@kernel.org>
Signed-off-by: Ingo Molnar <mingo@kernel.org>
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 c4a7acf..a8c103c 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'

^ permalink raw reply related	[flat|nested] 24+ messages in thread

* [tip: objtool/core] objtool/klp: Fix false module dependencies caused by dead relocs
  2026-08-03  3:24 ` [PATCH 03/14] objtool/klp: Fix false module dependencies caused by dead relocs Josh Poimboeuf
@ 2026-08-03  5:49   ` tip-bot2 for Josh Poimboeuf
  0 siblings, 0 replies; 24+ messages in thread
From: tip-bot2 for Josh Poimboeuf @ 2026-08-03  5:49 UTC (permalink / raw)
  To: linux-tip-commits
  Cc: Ben Procknow, Joe Lawrence, Josh Poimboeuf, Ingo Molnar,
	live-patching, x86, linux-kernel

The following commit has been merged into the objtool/core branch of tip:

Commit-ID:     5ca8c91d1ea6534842e7e0065d15104d802506cd
Gitweb:        https://git.kernel.org/tip/5ca8c91d1ea6534842e7e0065d15104d802506cd
Author:        Josh Poimboeuf <jpoimboe@kernel.org>
AuthorDate:    Sun, 02 Aug 2026 20:24:25 -07:00
Committer:     Ingo Molnar <mingo@kernel.org>
CommitterDate: Mon, 03 Aug 2026 07:12:38 +02:00

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.<name>" so
modpost no longer recognizes them.

Fixes: dd590d4d57eb ("objtool/klp: Introduce klp diff subcommand for diffing object files")
Reported-by: Ben Procknow <bprockno@redhat.com>
Reported-by: Joe Lawrence <joe.lawrence@redhat.com>
Signed-off-by: Josh Poimboeuf <jpoimboe@kernel.org>
Signed-off-by: Ingo Molnar <mingo@kernel.org>
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 33c95a7..a791f4e 100644
--- a/tools/objtool/elf.c
+++ b/tools/objtool/elf.c
@@ -23,6 +23,7 @@
 #include <linux/log2.h>
 #include <objtool/builtin.h>
 #include <objtool/elf.h>
+#include <objtool/klp.h>
 #include <objtool/warn.h>
 
 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 6f60cf0..aab6db4 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 15d37d9..75ba0e0 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;

^ permalink raw reply related	[flat|nested] 24+ messages in thread

* [tip: objtool/core] objtool/klp: Normalize Module.symvers paths to module names
  2026-08-03  3:24 ` [PATCH 02/14] objtool/klp: Normalize Module.symvers paths to module names Josh Poimboeuf
@ 2026-08-03  5:49   ` tip-bot2 for Joe Lawrence
  0 siblings, 0 replies; 24+ messages in thread
From: tip-bot2 for Joe Lawrence @ 2026-08-03  5:49 UTC (permalink / raw)
  To: linux-tip-commits
  Cc: Ben Procknow, Joe Lawrence, Josh Poimboeuf, Ingo Molnar,
	Miroslav Benes, live-patching, x86, linux-kernel

The following commit has been merged into the objtool/core branch of tip:

Commit-ID:     8668bf91e0508abeb8e98b4edd89032be02228a1
Gitweb:        https://git.kernel.org/tip/8668bf91e0508abeb8e98b4edd89032be02228a1
Author:        Joe Lawrence <joe.lawrence@redhat.com>
AuthorDate:    Sun, 02 Aug 2026 20:24:24 -07:00
Committer:     Ingo Molnar <mingo@kernel.org>
CommitterDate: Mon, 03 Aug 2026 07:12:38 +02:00

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 <bprockno@redhat.com>
Signed-off-by: Joe Lawrence <joe.lawrence@redhat.com>
Signed-off-by: Josh Poimboeuf <jpoimboe@kernel.org>
Signed-off-by: Ingo Molnar <mingo@kernel.org>
Reviewed-by: Miroslav Benes <mbenes@suse.cz>
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 aeb99d5..15d37d9 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) */

^ permalink raw reply related	[flat|nested] 24+ messages in thread

* [tip: objtool/core] objtool/klp: Fix module name normalization for paths with dots
  2026-08-03  3:24 ` [PATCH 01/14] objtool/klp: Fix module name normalization for paths with dots Josh Poimboeuf
@ 2026-08-03  5:49   ` tip-bot2 for Josh Poimboeuf
  0 siblings, 0 replies; 24+ messages in thread
From: tip-bot2 for Josh Poimboeuf @ 2026-08-03  5:49 UTC (permalink / raw)
  To: linux-tip-commits
  Cc: Sashiko, Josh Poimboeuf, Ingo Molnar, live-patching, x86,
	linux-kernel

The following commit has been merged into the objtool/core branch of tip:

Commit-ID:     165affd6f095323d953b0ed5823ec4d0db3b7d0d
Gitweb:        https://git.kernel.org/tip/165affd6f095323d953b0ed5823ec4d0db3b7d0d
Author:        Josh Poimboeuf <jpoimboe@kernel.org>
AuthorDate:    Sun, 02 Aug 2026 20:24:23 -07:00
Committer:     Ingo Molnar <mingo@kernel.org>
CommitterDate: Mon, 03 Aug 2026 07:12:38 +02:00

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 <sashiko-bot@kernel.org>
Signed-off-by: Josh Poimboeuf <jpoimboe@kernel.org>
Signed-off-by: Ingo Molnar <mingo@kernel.org>
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 f8787d7..aeb99d5 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';

^ permalink raw reply related	[flat|nested] 24+ messages in thread

* Re: [tip: objtool/core] objtool/klp: Add .klp.symid for sympos disambiguation
  2026-08-03  5:49   ` [tip: objtool/core] " tip-bot2 for Josh Poimboeuf
@ 2026-08-03  6:12     ` sashiko-bot
  0 siblings, 0 replies; 24+ messages in thread
From: sashiko-bot @ 2026-08-03  6:12 UTC (permalink / raw)
  To: linux-tip-commits
  Cc: Ingo Molnar, x86, live-patching, Josh Poimboeuf, linux-kernel

Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
- [Low] The `.exitcall.exit` section is discarded during the final vmlinux link, but `klp_create_symid_sections` incorrectly generates `.klp.symid` relocations to duplicate static symbols within it.
--

--- Patch [1]: [tip: objtool/core] objtool/klp: Add .klp.symid for sympos disambiguation ---
commit 95a71e72a77916e00e9b21eb4e2fc9a9e4c3258d
Author: Josh Poimboeuf <jpoimboe@kernel.org>
Date:   Mon Aug 3 05:49:39 2026 +0000

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. This commit adds a
new .klp.symid section to bridge the gap and correlate symbols between
vmlinux.o and vmlinux.

Link: https://patch.msgid.link/64d50f077b569f47883c015cdb7079edb068efe8.1785727106.git.jpoimboe@kernel.org

> diff --git a/tools/objtool/klp-symid.c b/tools/objtool/klp-symid.c
> new file mode 100644
> index 0000000000000..cf188cdfa6079
> --- /dev/null
> +++ b/tools/objtool/klp-symid.c
[ ... ]
> +static const char * const discarded_secs[] = {
> +	".discard",
> +	".modinfo",
> +	"__tracepoint_check",
> +};

[Severity: Low]
Does this list need to include ".exitcall.exit"?

When built-in components use module_exit() with identical function
names (like sm4_exit in multiple x86 crypto modules), duplicate static
variables are created in the .exitcall.exit section.

Because this section is missing from discarded_secs[], symid_needed()
generates R_ABS64 relocations in .klp.symid to these symbols.

Since .exitcall.exit is unconditionally discarded by the linker during the
final vmlinux link, this can cause a fatal linker error: ".klp.symid refers
to discarded section .exitcall.exit".

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/178573617913.1210945.4137827646322421739.tip-bot2@tip-bot2?part=1

^ permalink raw reply	[flat|nested] 24+ messages in thread

* Re: [PATCH 07/14] module: Add module_kallsyms_on_each_core_symbol()
  2026-08-03  3:24 ` [PATCH 07/14] module: Add module_kallsyms_on_each_core_symbol() Josh Poimboeuf
@ 2026-08-03  6:24   ` Josh Poimboeuf
  0 siblings, 0 replies; 24+ messages in thread
From: Josh Poimboeuf @ 2026-08-03  6:24 UTC (permalink / raw)
  To: x86
  Cc: linux-kernel, live-patching, Peter Zijlstra, Joe Lawrence,
	Miroslav Benes, Petr Mladek, Song Liu, Luis Chamberlain,
	Petr Pavlu, Daniel Gomez, Sami Tolvanen, linux-modules

On Sun, Aug 02, 2026 at 08:24:29PM -0700, Josh Poimboeuf wrote:
> module_kallsyms_on_each_symbol() iterates mod->kallsyms, which points at
> the full init symbol table until do_init_module() swaps it out.  The set
> of symbols it reports thus differs based on whether init memory has been
> freed yet.
> 
> Add module_kallsyms_on_each_core_symbol() for callers which need a
> symbol's position to be the same before and after that swap.
> core_kallsyms is fully populated by add_kallsyms() before the module
> leaves MODULE_STATE_UNFORMED, so it's readable on both paths.
> 
> Signed-off-by: Josh Poimboeuf <jpoimboe@kernel.org>

NAK

The Sashiko comment made me realize this is not worth adding kernel code
for.  A duplicate symbol name between init and non-init module code is
somewhere between exceedingly rare and non-existent.  I'll submit
another patch separately which will make it a build error for now.

-- 
Josh

^ permalink raw reply	[flat|nested] 24+ messages in thread

* Re: [PATCH 08/14] objtool/klp,livepatch: Resolve module symbols against core kallsyms
  2026-08-03  3:24 ` [PATCH 08/14] objtool/klp,livepatch: Resolve module symbols against core kallsyms Josh Poimboeuf
@ 2026-08-03  6:26   ` Josh Poimboeuf
  0 siblings, 0 replies; 24+ messages in thread
From: Josh Poimboeuf @ 2026-08-03  6:26 UTC (permalink / raw)
  To: x86
  Cc: linux-kernel, live-patching, Peter Zijlstra, Joe Lawrence,
	Miroslav Benes, Petr Mladek, Song Liu, Luis Chamberlain,
	Petr Pavlu, Daniel Gomez, Sami Tolvanen, linux-modules

On Sun, Aug 02, 2026 at 08:24:30PM -0700, Josh Poimboeuf wrote:
> For patching a module, klp_find_sympos() counts every symbol table entry
> whose name matches.  However, the module loader only includes symbols
> matched by is_core_symbol().
> 
> The runtime count is inconsistent as well.  It's done by
> module_kallsyms_on_each_symbol(), which iterates the full init symbol
> table until do_init_module() swaps in the cut-down core table, so the
> same symbol can have different positions depending on whether init
> memory has been freed yet.
> 
> Define a module's sympos as its position in the core symbol table, which
> is what sympos already means for a live module and what users see in
> /proc/kallsyms.  Enforce that on both ends: count with
> module_kallsyms_on_each_core_symbol() at runtime, and mirror the
> is_core_symbol() filter in objtool with a new mod_sym_in_kallsyms()
> helper.
> 
> The init-layout half of the filter is only correct if .exit sections are
> core sections, which requires CONFIG_MODULE_UNLOAD, otherwise .exit code
> is laid out as part of init memory and freed after module init.  Enforce
> CONFIG_MODULE_UNLOAD to ensure that behavior is deterministic.
> 
> Symbols which exist only in init sections are no longer resolvable, but
> they never were once the module went live, and init memory is freed
> after module init anyway.
> 
> Fixes: dd590d4d57eb ("objtool/klp: Introduce klp diff subcommand for diffing object files")
> Fixes: b2b018ef4867 ("livepatch: add old_sympos as disambiguator field to klp_func")
> Signed-off-by: Josh Poimboeuf <jpoimboe@kernel.org>

NAK - as mentioned for the previous patch, this is a theoretical edge
case which I will fix in a different way.

-- 
Josh

^ permalink raw reply	[flat|nested] 24+ messages in thread

end of thread, other threads:[~2026-08-03  6:26 UTC | newest]

Thread overview: 24+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-08-03  3:24 [PATCH 00/14] objtool/klp: sympos/module/alternative/etc fixes Josh Poimboeuf
2026-08-03  3:24 ` [PATCH 01/14] objtool/klp: Fix module name normalization for paths with dots Josh Poimboeuf
2026-08-03  5:49   ` [tip: objtool/core] " tip-bot2 for Josh Poimboeuf
2026-08-03  3:24 ` [PATCH 02/14] objtool/klp: Normalize Module.symvers paths to module names Josh Poimboeuf
2026-08-03  5:49   ` [tip: objtool/core] " tip-bot2 for Joe Lawrence
2026-08-03  3:24 ` [PATCH 03/14] objtool/klp: Fix false module dependencies caused by dead relocs Josh Poimboeuf
2026-08-03  5:49   ` [tip: objtool/core] " tip-bot2 for Josh Poimboeuf
2026-08-03  3:24 ` [PATCH 04/14] objtool/klp: Skip hidden directories when finding objects Josh Poimboeuf
2026-08-03  5:49   ` [tip: objtool/core] " tip-bot2 for Josh Poimboeuf
2026-08-03  3:24 ` [PATCH 05/14] objtool/klp: Add .klp.symid for sympos disambiguation Josh Poimboeuf
2026-08-03  5:49   ` [tip: objtool/core] " tip-bot2 for Josh Poimboeuf
2026-08-03  6:12     ` sashiko-bot
2026-08-03  3:24 ` [PATCH 06/14] objtool/klp: Fix symbol resolution for duplicate data symbols Josh Poimboeuf
2026-08-03  5:49   ` [tip: objtool/core] " tip-bot2 for Josh Poimboeuf
2026-08-03  3:24 ` [PATCH 07/14] module: Add module_kallsyms_on_each_core_symbol() Josh Poimboeuf
2026-08-03  6:24   ` Josh Poimboeuf
2026-08-03  3:24 ` [PATCH 08/14] objtool/klp,livepatch: Resolve module symbols against core kallsyms Josh Poimboeuf
2026-08-03  6:26   ` Josh Poimboeuf
2026-08-03  3:24 ` [PATCH 09/14] objtool/klp: Fix size of empty special section entries Josh Poimboeuf
2026-08-03  3:24 ` [PATCH 10/14] objtool/klp: Ignore replacement offset of empty x86 alternatives Josh Poimboeuf
2026-08-03  3:24 ` [PATCH 11/14] objtool/klp: Explicitly disallow patching or referencing init code/data Josh Poimboeuf
2026-08-03  3:24 ` [PATCH 12/14] objtool/klp: Fix cross-module klp relocation section naming Josh Poimboeuf
2026-08-03  3:24 ` [PATCH 13/14] objtool/klp: Don't match local symbols against exports Josh Poimboeuf
2026-08-03  3:24 ` [PATCH 14/14] objtool/klp: Allow new references to module exports Josh Poimboeuf

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox