[PATCH v2 00/62] objtool,livepatch: klp-build livepatch module generation

Josh Poimboeuf posted 62 patches 7 months, 1 week ago
There is a newer version of this series
MAINTAINERS                                   |    3 +-
arch/Kconfig                                  |    3 +
arch/s390/include/asm/nospec-insn.h           |    2 +-
arch/s390/kernel/vmlinux.lds.S                |    2 +-
arch/x86/Kconfig                              |    1 +
arch/x86/include/asm/alternative.h            |    7 +-
arch/x86/include/asm/asm.h                    |   20 +-
arch/x86/include/asm/bug.h                    |   44 +-
arch/x86/include/asm/jump_label.h             |   32 +-
arch/x86/kernel/alternative.c                 |   53 +-
arch/x86/kernel/kprobes/opt.c                 |    4 -
arch/x86/kernel/module.c                      |   15 +-
arch/x86/kernel/unwind_orc.c                  |    2 +
include/asm-generic/vmlinux.lds.h             |   40 +-
include/linux/compiler.h                      |    8 +-
include/linux/elfnote.h                       |   13 +-
include/linux/init.h                          |    3 +-
include/linux/jump_label.h                    |   20 +-
include/linux/livepatch.h                     |   25 +-
include/linux/livepatch_external.h            |   76 +
include/linux/livepatch_helpers.h             |   68 +
include/linux/objtool.h                       |   16 +-
kernel/extable.c                              |    2 +
kernel/livepatch/core.c                       |    8 +-
scripts/Makefile.lib                          |    6 +-
scripts/Makefile.modfinal                     |   18 +-
scripts/Makefile.vmlinux_o                    |    2 +-
scripts/link-vmlinux.sh                       |    3 +-
scripts/livepatch/fix-patch-lines             |   79 +
scripts/livepatch/init.c                      |  108 ++
scripts/livepatch/klp-build                   |  781 +++++++++
scripts/mod/modpost.c                         |    5 +
scripts/module.lds.S                          |   26 +-
tools/include/linux/interval_tree_generic.h   |    2 +-
tools/include/linux/livepatch_external.h      |   76 +
tools/objtool/Build                           |    4 +-
tools/objtool/Makefile                        |   48 +-
tools/objtool/arch/loongarch/decode.c         |    6 +-
tools/objtool/arch/powerpc/decode.c           |    6 +-
tools/objtool/arch/x86/decode.c               |   68 +-
tools/objtool/arch/x86/special.c              |    2 +-
tools/objtool/builtin-check.c                 |   70 +-
tools/objtool/builtin-klp.c                   |   53 +
tools/objtool/check.c                         |  690 +++++---
tools/objtool/elf.c                           |  812 ++++++---
tools/objtool/include/objtool/arch.h          |    5 +-
tools/objtool/include/objtool/builtin.h       |    6 +-
tools/objtool/include/objtool/check.h         |    6 +-
tools/objtool/include/objtool/checksum.h      |   43 +
.../objtool/include/objtool/checksum_types.h  |   25 +
tools/objtool/include/objtool/elf.h           |  186 +-
tools/objtool/include/objtool/klp.h           |   35 +
tools/objtool/include/objtool/objtool.h       |    6 +-
tools/objtool/include/objtool/warn.h          |   40 +
tools/objtool/klp-diff.c                      | 1504 +++++++++++++++++
tools/objtool/klp-post-link.c                 |  165 ++
tools/objtool/objtool.c                       |   42 +-
tools/objtool/orc_gen.c                       |    8 +-
tools/objtool/special.c                       |    4 +-
tools/objtool/sync-check.sh                   |    1 +
tools/objtool/weak.c                          |    7 +
61 files changed, 4687 insertions(+), 728 deletions(-)
create mode 100644 include/linux/livepatch_external.h
create mode 100644 include/linux/livepatch_helpers.h
create mode 100755 scripts/livepatch/fix-patch-lines
create mode 100644 scripts/livepatch/init.c
create mode 100755 scripts/livepatch/klp-build
create mode 100644 tools/include/linux/livepatch_external.h
create mode 100644 tools/objtool/builtin-klp.c
create mode 100644 tools/objtool/include/objtool/checksum.h
create mode 100644 tools/objtool/include/objtool/checksum_types.h
create mode 100644 tools/objtool/include/objtool/klp.h
create mode 100644 tools/objtool/klp-diff.c
create mode 100644 tools/objtool/klp-post-link.c
[PATCH v2 00/62] objtool,livepatch: klp-build livepatch module generation
Posted by Josh Poimboeuf 7 months, 1 week ago
This series introduces new objtool features and a klp-build script to
generate livepatch modules using a source .patch as input.

This builds on concepts from the longstanding out-of-tree kpatch [1]
project which began in 2012 and has been used for many years to generate
livepatch modules for production kernels.  However, this is a complete
rewrite which incorporates hard-earned lessons from 12+ years of
maintaining kpatch.

Key improvements compared to kpatch-build:

  - Integrated with objtool: Leverages objtool's existing control-flow
    graph analysis to help detect changed functions.

  - Works on vmlinux.o: Supports late-linked objects, making it
    compatible with LTO, IBT, and similar.

  - Simplified code base: ~3k fewer lines of code.

  - Upstream: No more out-of-tree #ifdef hacks, far less cruft.

  - Cleaner internals: Vastly simplified logic for symbol/section/reloc
    inclusion and special section extraction.

  - Robust __LINE__ macro handling: Avoids false positive binary diffs
    caused by the __LINE__ macro by introducing a fix-patch-lines script
    which injects #line directives into the source .patch to preserve
    the original line numbers at compile time.

The primary user interface is the klp-build script which does the
following:

  - Builds an original kernel with -function-sections and
    -fdata-sections, plus objtool function checksumming.

  - Applies the .patch file and rebuilds the kernel using the same
    options.

  - Runs 'objtool klp diff' to detect changed functions and generate
    intermediate binary diff objects.

  - Builds a kernel module which links the diff objects with some
    livepatch module init code (scripts/livepatch/init.c).

  - Finalizes the livepatch module (aka work around linker wreckage)
    using 'objtool klp post-link'.

I've tested with a variety of patches on defconfig and Fedora-config
kernels with both GCC and Clang.

These patches can also be found at:

  git://git.kernel.org/pub/scm/linux/kernel/git/jpoimboe/linux.git klp-build-v2

Please test!

[1] https://github.com/dynup/kpatch


Changes since the RFC: https://lore.kernel.org/cover.1725334260.git.jpoimboe@kernel.org
- Too many to list.  Doubled the patch count exactly ;-)


Josh Poimboeuf (62):
  s390/vmlinux.lds.S: Prevent thunk functions from getting placed with
    normal text
  vmlinux.lds: Unify TEXT_MAIN, DATA_MAIN, and related macros
  x86/module: Improve relocation error messages
  x86/kprobes: Remove STACK_FRAME_NON_STANDARD annotation
  compiler: Tweak __UNIQUE_ID() naming
  compiler.h: Make addressable symbols less of an eyesore
  elfnote: Change ELFNOTE() to use __UNIQUE_ID()
  kbuild: Remove 'kmod_' prefix from __KBUILD_MODNAME
  modpost: Ignore unresolved section bounds symbols
  x86/alternative: Refactor INT3 call emulation selftest
  objtool: Make find_symbol_containing() less arbitrary
  objtool: Speed up SHT_GROUP reindexing
  objtool: Fix broken error handling in read_symbols()
  objtool: Propagate elf_truncate_section() error in elf_write()
  objtool: Add empty symbols to the symbol tree again
  objtool: Fix interval tree insertion for zero-length symbols
  objtool: Fix weak symbol detection
  objtool: Fix x86 addend calculation
  objtool: Fix __pa_symbol() relocation handling
  objtool: Fix "unexpected end of section" warning for alternatives
  objtool: Check for missing annotation entries in read_annotate()
  objtool: Const string cleanup
  objtool: Clean up compiler flag usage
  objtool: Remove .parainstructions reference
  objtool: Convert elf iterator macros to use 'struct elf'
  objtool: Add section/symbol type helpers
  objtool: Mark .cold subfunctions
  objtool: Fix weak symbol hole detection for .cold functions
  objtool: Mark prefix functions
  objtool: Simplify reloc offset calculation in unwind_read_hints()
  objtool: Avoid emptying lists for duplicate sections
  objtool: Suppress section skipping warnings with --dryrun
  objtool: Rename --Werror to --werror
  objtool: Reindent check_options[]
  objtool: Refactor add_jump_destinations()
  objtool: Simplify special symbol handling in elf_update_symbol()
  objtool: Generalize elf_create_symbol()
  objtool: Generalize elf_create_section()
  objtool: Add elf_create_data()
  objtool: Introduce elf_create_reloc() and elf_init_reloc()
  objtool: Add elf_create_file()
  kbuild,x86: Fix module permissions for __jump_table and __bug_table
  x86/alternative: Define ELF section entry size for alternatives
  x86/jump_label: Define ELF section entry size for jump table
  x86/extable: Define ELF section entry size for exception tables
  x86/bug: Define ELF section entry size for the bug table
  x86/orc: Define ELF section entry size for unwind hints
  objtool: Make STACK_FRAME_NON_STANDARD consistent
  kbuild,objtool: Defer objtool validation step for CONFIG_LIVEPATCH
  objtool/klp: Add --checksum option to generate per-function checksums
  objtool/klp: Add --debug-checksum=<funcs> to show per-instruction
    checksums
  objtool/klp: Introduce klp diff subcommand for diffing object files
  objtool/klp: Add --debug option to show cloning decisions
  objtool/klp: Add post-link subcommand to finalize livepatch modules
  objtool: Disallow duplicate prefix symbols
  objtool: Add base objtool support for livepatch modules
  livepatch/klp-build: Introduce fix-patch-lines script to avoid
    __LINE__ diff noise
  livepatch/klp-build: Add stub init code for livepatch modules
  livepatch/klp-build: Introduce klp-build script for generating
    livepatch modules
  livepatch/klp-build: Add --debug option to show cloning decisions
  livepatch/klp-build: Add --show-first-changed option to show function
    divergence
  livepatch: Introduce source code helpers for livepatch modules

 MAINTAINERS                                   |    3 +-
 arch/Kconfig                                  |    3 +
 arch/s390/include/asm/nospec-insn.h           |    2 +-
 arch/s390/kernel/vmlinux.lds.S                |    2 +-
 arch/x86/Kconfig                              |    1 +
 arch/x86/include/asm/alternative.h            |    7 +-
 arch/x86/include/asm/asm.h                    |   20 +-
 arch/x86/include/asm/bug.h                    |   44 +-
 arch/x86/include/asm/jump_label.h             |   32 +-
 arch/x86/kernel/alternative.c                 |   53 +-
 arch/x86/kernel/kprobes/opt.c                 |    4 -
 arch/x86/kernel/module.c                      |   15 +-
 arch/x86/kernel/unwind_orc.c                  |    2 +
 include/asm-generic/vmlinux.lds.h             |   40 +-
 include/linux/compiler.h                      |    8 +-
 include/linux/elfnote.h                       |   13 +-
 include/linux/init.h                          |    3 +-
 include/linux/jump_label.h                    |   20 +-
 include/linux/livepatch.h                     |   25 +-
 include/linux/livepatch_external.h            |   76 +
 include/linux/livepatch_helpers.h             |   68 +
 include/linux/objtool.h                       |   16 +-
 kernel/extable.c                              |    2 +
 kernel/livepatch/core.c                       |    8 +-
 scripts/Makefile.lib                          |    6 +-
 scripts/Makefile.modfinal                     |   18 +-
 scripts/Makefile.vmlinux_o                    |    2 +-
 scripts/link-vmlinux.sh                       |    3 +-
 scripts/livepatch/fix-patch-lines             |   79 +
 scripts/livepatch/init.c                      |  108 ++
 scripts/livepatch/klp-build                   |  781 +++++++++
 scripts/mod/modpost.c                         |    5 +
 scripts/module.lds.S                          |   26 +-
 tools/include/linux/interval_tree_generic.h   |    2 +-
 tools/include/linux/livepatch_external.h      |   76 +
 tools/objtool/Build                           |    4 +-
 tools/objtool/Makefile                        |   48 +-
 tools/objtool/arch/loongarch/decode.c         |    6 +-
 tools/objtool/arch/powerpc/decode.c           |    6 +-
 tools/objtool/arch/x86/decode.c               |   68 +-
 tools/objtool/arch/x86/special.c              |    2 +-
 tools/objtool/builtin-check.c                 |   70 +-
 tools/objtool/builtin-klp.c                   |   53 +
 tools/objtool/check.c                         |  690 +++++---
 tools/objtool/elf.c                           |  812 ++++++---
 tools/objtool/include/objtool/arch.h          |    5 +-
 tools/objtool/include/objtool/builtin.h       |    6 +-
 tools/objtool/include/objtool/check.h         |    6 +-
 tools/objtool/include/objtool/checksum.h      |   43 +
 .../objtool/include/objtool/checksum_types.h  |   25 +
 tools/objtool/include/objtool/elf.h           |  186 +-
 tools/objtool/include/objtool/klp.h           |   35 +
 tools/objtool/include/objtool/objtool.h       |    6 +-
 tools/objtool/include/objtool/warn.h          |   40 +
 tools/objtool/klp-diff.c                      | 1504 +++++++++++++++++
 tools/objtool/klp-post-link.c                 |  165 ++
 tools/objtool/objtool.c                       |   42 +-
 tools/objtool/orc_gen.c                       |    8 +-
 tools/objtool/special.c                       |    4 +-
 tools/objtool/sync-check.sh                   |    1 +
 tools/objtool/weak.c                          |    7 +
 61 files changed, 4687 insertions(+), 728 deletions(-)
 create mode 100644 include/linux/livepatch_external.h
 create mode 100644 include/linux/livepatch_helpers.h
 create mode 100755 scripts/livepatch/fix-patch-lines
 create mode 100644 scripts/livepatch/init.c
 create mode 100755 scripts/livepatch/klp-build
 create mode 100644 tools/include/linux/livepatch_external.h
 create mode 100644 tools/objtool/builtin-klp.c
 create mode 100644 tools/objtool/include/objtool/checksum.h
 create mode 100644 tools/objtool/include/objtool/checksum_types.h
 create mode 100644 tools/objtool/include/objtool/klp.h
 create mode 100644 tools/objtool/klp-diff.c
 create mode 100644 tools/objtool/klp-post-link.c

-- 
2.49.0
Re: [PATCH v2 00/62] objtool,livepatch: klp-build livepatch module generation
Posted by Josh Poimboeuf 6 months ago
On Fri, May 09, 2025 at 01:16:24PM -0700, Josh Poimboeuf wrote:
> I've tested with a variety of patches on defconfig and Fedora-config
> kernels with both GCC and Clang.
> 
> These patches can also be found at:
> 
>   git://git.kernel.org/pub/scm/linux/kernel/git/jpoimboe/linux.git klp-build-v2
> 
> Please test!

I found a nasty bug while trying to patch copy_process().  The wrong
version of __refcount_add.constprop.0() was being called due to a bad
klp rela sympos value, causing refcount overflow/UAF warnings and hangs.

The problem was a mismatch between the sympos order of the vmlinux.o
archive (which klp-diff uses) and the final vmlinux.

The linker script manually emits .text.unlikely before .text instead of
emitting in them in section table order.  So if a function name exists
in both sections, the calculated sympos (based on vmlinux.o) is wrong.

In my test kernel with GCC 14, only 25 out of 136,931 functions (0.018%)
have this problem.  It was my lucky day.

The below hack fixes it by starting the sympos counting with
.text.unlikely*.  It's a bit fragile, but it works fine for now.

I'm thinking the final fix would involve adding a checksum field to
kallsyms.  Then sympos would no longer be needed.  But that will need to
come later.

diff --git a/tools/include/linux/string.h b/tools/include/linux/string.h
index 8499f509f03e..51ad3cf4fa82 100644
--- a/tools/include/linux/string.h
+++ b/tools/include/linux/string.h
@@ -44,6 +44,20 @@ static inline bool strstarts(const char *str, const char *prefix)
 	return strncmp(str, prefix, strlen(prefix)) == 0;
 }
 
+/*
+ * Checks if a string ends with another.
+ */
+static inline bool str_ends_with(const char *str, const char *substr)
+{
+	size_t len = strlen(str);
+	size_t sublen = strlen(substr);
+
+	if (sublen > len)
+		return false;
+
+	return !strcmp(str + len - sublen, substr);
+}
+
 extern char * __must_check skip_spaces(const char *);
 
 extern char *strim(char *);
diff --git a/tools/objtool/check.c b/tools/objtool/check.c
index b5494b5ca78f..47ee010a7852 100644
--- a/tools/objtool/check.c
+++ b/tools/objtool/check.c
@@ -187,20 +187,6 @@ static bool is_sibling_call(struct instruction *insn)
 	return (is_static_jump(insn) && insn_call_dest(insn));
 }
 
-/*
- * Checks if a string ends with another.
- */
-static bool str_ends_with(const char *s, const char *sub)
-{
-	const int slen = strlen(s);
-	const int sublen = strlen(sub);
-
-	if (sublen > slen)
-		return 0;
-
-	return !memcmp(s + slen - sublen, sub, sublen);
-}
-
 /*
  * Checks if a function is a Rust "noreturn" one.
  */
diff --git a/tools/objtool/klp-diff.c b/tools/objtool/klp-diff.c
index 5276964ef123..0290cbc90c16 100644
--- a/tools/objtool/klp-diff.c
+++ b/tools/objtool/klp-diff.c
@@ -439,6 +439,7 @@ static int correlate_symbols(struct elfs *e)
 /* "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;
@@ -446,13 +447,43 @@ static unsigned long find_sympos(struct elf *elf, struct symbol *sym)
 	if (sym->bind != STB_LOCAL)
 		return 0;
 
-	for_each_sym(elf, s) {
-		if (!strcmp(s->name, sym->name)) {
-			nr_matches++;
-			if (s == sym)
-				sympos = nr_matches;
-			else
-				has_dup = true;
+	if (vmlinux && sym->type == STT_FUNC) {
+		/*
+		 * 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;
+			}
 		}
 	}