* Re: [PATCH 3/8] LoongArch: Add special section entry sizes for KLP support
From: George Guo @ 2026-06-08 10:33 UTC (permalink / raw)
To: WangYuli
Cc: ardb, chenhuacai, guodongtai, jiaxun.yang, jikos, joe.lawrence,
jpoimboe, kernel, linux-kernel, liukexin, live-patching, llvm,
loongarch, mbenes, nathan, nick.desaulniers+lkml, peterz, pmladek,
rostedt, xry111, yangtiezhu
In-Reply-To: <20260605083839.67474-1-wangyuli@aosc.io>
Hi WangYuli,
Thanks for the review -- all three points are addressed in v2:
https://lore.kernel.org/all/20260608100406.322598-1-dongtai.guo@linux.dev/
> Have you actually tested your changes with LLVM?
Yes, now confirmed: the affected files build cleanly with
'make LLVM=1' (clang). You were right that this had not been properly
exercised before -- the writable special sections were the real problem.
> BTW, "awM" is dangerous, pls see how did x86 do.
Fixed. __bug_table and __jump_table are writable, and SHF_MERGE cannot be
combined with SHF_WRITE (Clang rejects it). v2 drops "awM"/entsize on
those two and marks each entry with ANNOTATE_DATA_SPECIAL instead, just
as x86 and arm64 do; SHF_MERGE is kept only for the read-only
.altinstructions/__ex_table.
> And head.S doesn't need to include asm-offsets.h, it seems
> stackframe.h has included it.
Correct, removed -- head.S already pulls it in via <asm/stackframe.h>.
Thanks,
George
^ permalink raw reply
* Re: [PATCH 5/8] LoongArch: fix kernel panic with -fPIC for same-compilation-unit symbol references
From: George Guo @ 2026-06-08 10:23 UTC (permalink / raw)
To: Xi Ruoyao, chenhuacai, jpoimboe, peterz, jikos, mbenes, pmladek
Cc: kernel, joe.lawrence, rostedt, ardb, nathan,
nick.desaulniers+lkml, yangtiezhu, jiaxun.yang, liukexin,
loongarch, live-patching, llvm, linux-kernel, George Guo
In-Reply-To: <046da9c8886dcb35d6427b7467ce551ef1426480.camel@xry111.site>
On Fri, 2026-06-05 at 16:10 +0800, Xi Ruoyao wrote:
> This is incorrect.
>
> With GCC and Clang, -fPIC does not mean generating position independent
> code. It means generating position independent code *suitable for
> SVR4-style shared objects*.
>
> The kernel has nothing to do with SVR4-style shared objects. The
> kernel modules are not SVR4 shared objects but relocatable ELF object
> files.
You are right, and thank you for the correction. The commit message was
wrong: with CONFIG_RELOCATABLE=y the kernel is already built with -fPIE,
so the reference is not "absolute addressing" as I claimed. I will fix
the description in v2.
> If you only need position independent code (without the shared object
> stuff) you should use -fPIE instead. The kernel is already built with
> -fPIE if CONFIG_RELOCATABLE=y.
>
> Thus if you only need position independent code you should make live
> patching depend on CONFIG_RELOCATABLE.
This is the part I should have explained better. CONFIG_RELOCATABLE=y is
already set in my config, so the kernel is already -fPIE, and the panic
still happens. I also tried building the KLP objects with -fPIE
explicitly and it does not help -- only -fPIC fixes it.
The reason is not "position independence" but GOT indirection, which is
exactly the SVR4-shared-object behaviour you mention. A minimal
'return uts_sem;' (uts_sem defined in the same TU) compiles like this on
loongarch64 -- same conclusion under both gcc 14.2.1 and clang 19.1.6:
-fPIE:
pcalau12i $a0, ... R_LARCH_PCALA_HI20 uts_sem
addi.d $a0, $a0, ... R_LARCH_PCALA_LO12 uts_sem
ld.w $a0, $a0, 0
-fPIC:
pcalau12i $a0, ... R_LARCH_GOT_PC_HI20 uts_sem
ld.d $a0, $a0, ... R_LARCH_GOT_PC_LO12 uts_sem
ld.w $a0, $a0, 0
So under -fPIE the access is direct (no GOT); under -fPIC it goes through
the GOT. (As an aside, gcc additionally collapses the -fPIE reference
onto a local anchor .LANCHOR0, leaving no named 'uts_sem' relocation at
all; clang keeps the name but still emits the direct, non-GOT form.)
klp-build extracts the patched function out of its original object file
and links it into a separate livepatch module, while the referenced
symbol (e.g. 'uts_sem') stays in the core kernel. The klp relocation
machinery resolves such cross-object references through GOT entries it
creates in the patch module. The direct R_LARCH_PCALA reference emitted
under -fPIE has no GOT slot for it to fix up, so once the function is
moved into the module its target is wrong and it faults.
So this is one of the cases where the KLP machinery genuinely relies on
the GOT-indirect ("shared object style") access that -fPIC provides and
-fPIE deliberately optimizes away. In v2 I will keep -fPIC but rewrite
the commit message to explain this GOT-vs-direct-PCALA reason instead of
the incorrect "absolute addressing" wording.
I can also include the readelf -r / objdump diff (R_LARCH_PCALA_* under
-fPIE vs R_LARCH_GOT_PC_* under -fPIC) in the v2 commit message if that
is useful.
Thanks again for the review.
^ permalink raw reply
* [PATCH v2 8/8] LoongArch: Add livepatch build (KLP) support
From: George Guo @ 2026-06-08 10:08 UTC (permalink / raw)
To: chenhuacai, jpoimboe, peterz, jikos, mbenes, pmladek
Cc: kernel, joe.lawrence, rostedt, ardb, nathan,
nick.desaulniers+lkml, yangtiezhu, jiaxun.yang, liukexin,
guodongtai, xry111, wangyuli, loongarch, live-patching, llvm,
linux-kernel
In-Reply-To: <20260608100852.325413-1-dongtai.guo@linux.dev>
From: George Guo <guodongtai@kylinos.cn>
This allows automated livepatch module generation using objtool.
Co-developed-by: Kexin Liu <liukexin@kylinos.cn>
Signed-off-by: Kexin Liu <liukexin@kylinos.cn>
Signed-off-by: George Guo <guodongtai@kylinos.cn>
---
arch/loongarch/Kconfig | 1 +
tools/objtool/Makefile | 3 ++-
2 files changed, 3 insertions(+), 1 deletion(-)
diff --git a/arch/loongarch/Kconfig b/arch/loongarch/Kconfig
index 3b042dbb2c41..1dbf51ba9d6a 100644
--- a/arch/loongarch/Kconfig
+++ b/arch/loongarch/Kconfig
@@ -159,6 +159,7 @@ config LOONGARCH
select HAVE_IOREMAP_PROT
select HAVE_IRQ_EXIT_ON_IRQ_STACK
select HAVE_IRQ_TIME_ACCOUNTING
+ select HAVE_KLP_BUILD
select HAVE_KPROBES
select HAVE_KPROBES_ON_FTRACE
select HAVE_KRETPROBES
diff --git a/tools/objtool/Makefile b/tools/objtool/Makefile
index 94aabeee9736..83d645675ed9 100644
--- a/tools/objtool/Makefile
+++ b/tools/objtool/Makefile
@@ -8,7 +8,8 @@ ifeq ($(SRCARCH),x86)
endif
ifeq ($(SRCARCH),loongarch)
- BUILD_ORC := y
+ BUILD_ORC := y
+ ARCH_HAS_KLP := y
endif
ifeq ($(SRCARCH),arm64)
--
2.25.1
^ permalink raw reply related
* [PATCH v2 7/8] klp-build: Add LoongArch syscall patching macro
From: George Guo @ 2026-06-08 10:08 UTC (permalink / raw)
To: chenhuacai, jpoimboe, peterz, jikos, mbenes, pmladek
Cc: kernel, joe.lawrence, rostedt, ardb, nathan,
nick.desaulniers+lkml, yangtiezhu, jiaxun.yang, liukexin,
guodongtai, xry111, wangyuli, loongarch, live-patching, llvm,
linux-kernel
In-Reply-To: <20260608100852.325413-1-dongtai.guo@linux.dev>
From: George Guo <guodongtai@kylinos.cn>
Add LoongArch support for KLP_SYSCALL_DEFINEx(), mirroring the generic
__SYSCALL_DEFINEx() pattern from include/linux/syscalls.h.
Co-developed-by: Kexin Liu <liukexin@kylinos.cn>
Signed-off-by: Kexin Liu <liukexin@kylinos.cn>
Signed-off-by: George Guo <guodongtai@kylinos.cn>
---
include/linux/livepatch_helpers.h | 22 ++++++++++++++++++++++
1 file changed, 22 insertions(+)
diff --git a/include/linux/livepatch_helpers.h b/include/linux/livepatch_helpers.h
index 4b647b83865f..a8aad067177b 100644
--- a/include/linux/livepatch_helpers.h
+++ b/include/linux/livepatch_helpers.h
@@ -91,6 +91,28 @@
} \
static inline long __klp_do_sys##name(__MAP(x,__SC_DECL,__VA_ARGS__))
+#elif defined(CONFIG_LOONGARCH)
+
+#define __KLP_SYSCALL_DEFINEx(x, name, ...) \
+ __diag_push(); \
+ __diag_ignore(GCC, 8, "-Wattribute-alias", \
+ "Type aliasing is used to sanitize syscall arguments");\
+ asmlinkage long sys##name(__MAP(x,__SC_DECL,__VA_ARGS__)) \
+ __attribute__((alias(__stringify(__se_sys##name)))); \
+ ALLOW_ERROR_INJECTION(sys##name, ERRNO); \
+ static inline long __klp_do_sys##name(__MAP(x,__SC_DECL,__VA_ARGS__));\
+ asmlinkage long __se_sys##name(__MAP(x,__SC_LONG,__VA_ARGS__)); \
+ asmlinkage long __attribute__((optimize("-fno-optimize-sibling-calls")))\
+ __se_sys##name(__MAP(x,__SC_LONG,__VA_ARGS__)) \
+ { \
+ long ret = __klp_do_sys##name(__MAP(x,__SC_CAST,__VA_ARGS__));\
+ __MAP(x,__SC_TEST,__VA_ARGS__); \
+ __PROTECT(x, ret,__MAP(x,__SC_ARGS,__VA_ARGS__)); \
+ return ret; \
+ } \
+ __diag_pop(); \
+ static inline long __klp_do_sys##name(__MAP(x,__SC_DECL,__VA_ARGS__))
+
#endif
#endif /* _LINUX_LIVEPATCH_HELPERS_H */
--
2.25.1
^ permalink raw reply related
* [PATCH v2 6/8] objtool/klp: Add LoongArch jump opcode bytes support
From: George Guo @ 2026-06-08 10:08 UTC (permalink / raw)
To: chenhuacai, jpoimboe, peterz, jikos, mbenes, pmladek
Cc: kernel, joe.lawrence, rostedt, ardb, nathan,
nick.desaulniers+lkml, yangtiezhu, jiaxun.yang, liukexin,
guodongtai, xry111, wangyuli, loongarch, live-patching, llvm,
linux-kernel
In-Reply-To: <20260608100852.325413-1-dongtai.guo@linux.dev>
From: George Guo <guodongtai@kylinos.cn>
Implement arch_jump_opcode_bytes() for LoongArch so that klp checksums
for jump/call instructions with non-relocated destination offsets are
position-independent, mirroring the x86 and arm64 implementations.
Mask out the embedded branch offset while preserving the opcode and the
register operands, which differ per instruction format:
- B/BL (reg0i26): no regs -> 0xfc000000
- BEQZ/BNEZ/BCEQZ/BCNEZ (reg1i21): keep rj/cj -> 0xfc0003e0
- JIRL/BEQ/BNE/.../BGEU (reg2i16): keep rj/rd -> 0xfc0003ff
Co-developed-by: Kexin Liu <liukexin@kylinos.cn>
Signed-off-by: Kexin Liu <liukexin@kylinos.cn>
Signed-off-by: George Guo <guodongtai@kylinos.cn>
---
tools/objtool/arch/loongarch/decode.c | 41 +++++++++++++++++++++++++++
1 file changed, 41 insertions(+)
diff --git a/tools/objtool/arch/loongarch/decode.c b/tools/objtool/arch/loongarch/decode.c
index 674e4efd138f..12facd0cc8d1 100644
--- a/tools/objtool/arch/loongarch/decode.c
+++ b/tools/objtool/arch/loongarch/decode.c
@@ -432,6 +432,47 @@ unsigned long arch_jump_table_sym_offset(struct reloc *reloc, struct reloc *tabl
}
}
+size_t arch_jump_opcode_bytes(struct objtool_file *file, struct instruction *insn,
+ unsigned char *buf)
+{
+ union loongarch_instruction *code;
+ u32 insn_word;
+
+ insn_word = le32toh(*(u32 *)(insn->sec->data->d_buf + insn->offset));
+ code = (union loongarch_instruction *)&insn_word;
+
+ switch (code->reg0i26_format.opcode) {
+ case b_op:
+ case bl_op:
+ /* reg0i26: 26-bit offset, no register operands */
+ insn_word &= 0xfc000000;
+ break;
+ case beqz_op:
+ case bnez_op:
+ case bceqz_op: /* == bcnez_op */
+ /* reg1i21: keep opcode + rj/cj at bits[9:5] */
+ insn_word &= 0xfc0003e0;
+ break;
+ case jirl_op:
+ case beq_op:
+ case bne_op:
+ case blt_op:
+ case bge_op:
+ case bltu_op:
+ case bgeu_op:
+ /* reg2i16: keep opcode + rj/rd at bits[9:0] */
+ insn_word &= 0xfc0003ff;
+ break;
+ default:
+ break;
+ }
+
+ insn_word = htole32(insn_word);
+ memcpy(buf, &insn_word, sizeof(insn_word));
+
+ return LOONGARCH_INSN_SIZE;
+}
+
#ifdef DISAS
int arch_disas_info_init(struct disassemble_info *dinfo)
--
2.25.1
^ permalink raw reply related
* [PATCH v2 5/8] LoongArch: Fix EFI linking with -fdata-sections
From: George Guo @ 2026-06-08 10:08 UTC (permalink / raw)
To: chenhuacai, jpoimboe, peterz, jikos, mbenes, pmladek
Cc: kernel, joe.lawrence, rostedt, ardb, nathan,
nick.desaulniers+lkml, yangtiezhu, jiaxun.yang, liukexin,
guodongtai, xry111, wangyuli, loongarch, live-patching, llvm,
linux-kernel
In-Reply-To: <20260608100852.325413-1-dongtai.guo@linux.dev>
From: George Guo <guodongtai@kylinos.cn>
When building with func-fdata-sections, the .init.bss section gets split
up into a bunch of .init.bss.<var> sections. Make sure they get linked
into .init.data.
Co-developed-by: Kexin Liu <liukexin@kylinos.cn>
Signed-off-by: Kexin Liu <liukexin@kylinos.cn>
Signed-off-by: George Guo <guodongtai@kylinos.cn>
---
arch/loongarch/kernel/vmlinux.lds.S | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/arch/loongarch/kernel/vmlinux.lds.S b/arch/loongarch/kernel/vmlinux.lds.S
index 840d944c2f73..dc5fa017db94 100644
--- a/arch/loongarch/kernel/vmlinux.lds.S
+++ b/arch/loongarch/kernel/vmlinux.lds.S
@@ -96,7 +96,7 @@ SECTIONS
#endif
.init.bss : {
- *(.init.bss)
+ *(.init.bss .init.bss.*)
}
. = ALIGN(PECOFF_SEGMENT_ALIGN);
__initdata_end = .;
--
2.25.1
^ permalink raw reply related
* [PATCH v2 4/8] livepatch/klp-build: build LoongArch with -fPIC to keep GOT-indirect symbol references
From: George Guo @ 2026-06-08 10:08 UTC (permalink / raw)
To: chenhuacai, jpoimboe, peterz, jikos, mbenes, pmladek
Cc: kernel, joe.lawrence, rostedt, ardb, nathan,
nick.desaulniers+lkml, yangtiezhu, jiaxun.yang, liukexin,
guodongtai, xry111, wangyuli, loongarch, live-patching, llvm,
linux-kernel
In-Reply-To: <20260608100852.325413-1-dongtai.guo@linux.dev>
From: George Guo <guodongtai@kylinos.cn>
On LoongArch, klp-build livepatch modules panic when a patched function
references a global defined in the same compilation unit (e.g.
SYSCALL_DEFINE1(newuname) -> 'uts_sem' in kernel/sys.c).
With CONFIG_RELOCATABLE=y the kernel is already -fPIE, so this is not
absolute addressing; the problem is GOT indirection. For a same-unit
global, -fPIE emits a direct PC-relative reference (R_LARCH_PCALA_*) and
skips the GOT, while -fPIC routes it through the GOT (R_LARCH_GOT_PC_*).
klp-build extracts the patched function into a separate module while
'uts_sem' stays in the core kernel, and the klp relocation machinery can
only redirect such a cross-object reference through a GOT entry. The
direct -fPIE reference has no GOT slot to fix up, so once the function is
relocated its target is wrong and it faults.
Force -fPIC for LoongArch KLP builds; -fPIE is not enough, as it
optimizes away the very GOT indirection KLP relies on.
This depends on the preceding patch: -fPIC is passed via KCFLAGS, but the
arch adds -fPIE via KBUILD_CFLAGS_KERNEL, which kbuild applies after
KCFLAGS (so -fPIE would win). That patch's command-line
KBUILD_CFLAGS_KERNEL= assignment replaces the arch value and drops -fPIE,
letting -fPIC take effect. The two patches must stay together.
Co-developed-by: Kexin Liu <liukexin@kylinos.cn>
Signed-off-by: Kexin Liu <liukexin@kylinos.cn>
Signed-off-by: George Guo <guodongtai@kylinos.cn>
---
scripts/livepatch/klp-build | 19 +++++++++++++++++--
1 file changed, 17 insertions(+), 2 deletions(-)
diff --git a/scripts/livepatch/klp-build b/scripts/livepatch/klp-build
index 27fe8824ef12..42cd58aff3d8 100755
--- a/scripts/livepatch/klp-build
+++ b/scripts/livepatch/klp-build
@@ -557,8 +557,15 @@ build_kernel() {
local cmd=()
local ARCH_KBUILD_CFLAGS_KERNEL=""
+ local ARCH_KCFLAGS=""
if [[ -v CONFIG_LOONGARCH && "$CONFIG_LOONGARCH" == "y" ]]; then
+ # -fPIC replaces the kernel's -fPIE (added under CONFIG_RELOCATABLE);
+ # without that config there is no -fPIE to replace.
+ [[ "${CONFIG_RELOCATABLE:-}" == "y" ]] || \
+ die "LoongArch klp-build requires CONFIG_RELOCATABLE=y"
+ ARCH_KCFLAGS="-fPIC"
+
# -mdirect-extern-access only exists under explicit relocs, and this
# function replaces KBUILD_CFLAGS_KERNEL wholesale (safe only then;
# the non-explicit build puts -Wa,-mla-global-with-pcrel there).
@@ -599,8 +606,16 @@ build_kernel() {
cmd+=("-s")
fi
cmd+=("-j$JOBS")
- cmd+=("KCFLAGS=-ffunction-sections -fdata-sections")
- cmd+=("KBUILD_CFLAGS_KERNEL=$ARCH_KBUILD_CFLAGS_KERNEL")
+ cmd+=("KCFLAGS=-ffunction-sections -fdata-sections${ARCH_KCFLAGS:+ $ARCH_KCFLAGS}")
+ # -fPIC is added for KLP via KCFLAGS above; the arch adds -fPIE via
+ # KBUILD_CFLAGS_KERNEL, which kbuild places after KCFLAGS on the
+ # built-in compile line. -fPIC/-fPIE is last-one-wins, so -fPIE would
+ # win. Setting KBUILD_CFLAGS_KERNEL on the command line replaces the
+ # arch value (not append), which drops -fPIE and lets -fPIC win. Only
+ # do this when an arch needs it (LoongArch).
+ if [[ -n "$ARCH_KBUILD_CFLAGS_KERNEL" ]]; then
+ cmd+=("KBUILD_CFLAGS_KERNEL=$ARCH_KBUILD_CFLAGS_KERNEL")
+ fi
cmd+=("vmlinux")
cmd+=("modules")
--
2.25.1
^ permalink raw reply related
* [PATCH v2 3/8] livepatch/klp-build: disable direct-extern-access for LoongArch to fix kernel panic
From: George Guo @ 2026-06-08 10:08 UTC (permalink / raw)
To: chenhuacai, jpoimboe, peterz, jikos, mbenes, pmladek
Cc: kernel, joe.lawrence, rostedt, ardb, nathan,
nick.desaulniers+lkml, yangtiezhu, jiaxun.yang, liukexin,
guodongtai, xry111, wangyuli, loongarch, live-patching, llvm,
linux-kernel
In-Reply-To: <20260608100852.325413-1-dongtai.guo@linux.dev>
From: George Guo <guodongtai@kylinos.cn>
On LoongArch systems, livepatch modules containing references to
EXTERNAL global variables trigger kernel panics when the core kernel is
built with the -mdirect-extern-access optimization.
Root cause:
The -mdirect-extern-access optimization replaces GOT-based external
symbol access with direct addressing for improved performance. However,
this breaks the kernel module loading mechanism, which relies on GOT
entries for proper relocation of EXTERNAL symbol references. Direct
access to global variables from livepatch modules causes invalid memory
accesses and kernel panics.
Solution:
For LoongArch klp builds, conditionally disable direct-extern-access by
adding:
- -mno-direct-extern-access for GCC builds
- -fno-direct-access-external-data for Clang builds
See also commit 38b10b269d04 ("LoongArch: Tweak CFLAGS for Clang
compatibility"), which added -mdirect-extern-access to the kernel as a
nice-to-have optimization that reduces GOT accesses.
Note this is passed as a command-line KBUILD_CFLAGS_KERNEL= assignment,
which replaces arch/loongarch/Makefile's KBUILD_CFLAGS_KERNEL wholesale
rather than appending to it. As a side effect it also drops the -fPIE
that the arch adds under CONFIG_RELOCATABLE. That is intentional and
relied upon by the next patch ("LoongArch: ... -fPIC ..."), which adds
-fPIC via KCFLAGS: kbuild applies KBUILD_CFLAGS_KERNEL after KCFLAGS for
built-in objects (see modkern_cflags in scripts/Makefile.lib), so a
retained -fPIE would otherwise win over that -fPIC. The two patches must
stay together.
Co-developed-by: Kexin Liu <liukexin@kylinos.cn>
Signed-off-by: Kexin Liu <liukexin@kylinos.cn>
Signed-off-by: George Guo <guodongtai@kylinos.cn>
---
scripts/livepatch/klp-build | 19 +++++++++++++++++++
1 file changed, 19 insertions(+)
diff --git a/scripts/livepatch/klp-build b/scripts/livepatch/klp-build
index e83973567c87..27fe8824ef12 100755
--- a/scripts/livepatch/klp-build
+++ b/scripts/livepatch/klp-build
@@ -556,6 +556,24 @@ build_kernel() {
local log="$TMP_DIR/build.log"
local cmd=()
+ local ARCH_KBUILD_CFLAGS_KERNEL=""
+
+ if [[ -v CONFIG_LOONGARCH && "$CONFIG_LOONGARCH" == "y" ]]; then
+ # -mdirect-extern-access only exists under explicit relocs, and this
+ # function replaces KBUILD_CFLAGS_KERNEL wholesale (safe only then;
+ # the non-explicit build puts -Wa,-mla-global-with-pcrel there).
+ [[ "${CONFIG_AS_HAS_EXPLICIT_RELOCS:-}" == "y" ]] || \
+ die "LoongArch klp-build requires CONFIG_AS_HAS_EXPLICIT_RELOCS=y"
+
+ if [[ "${CONFIG_CC_IS_CLANG:-}" == "y" ]]; then
+ ARCH_KBUILD_CFLAGS_KERNEL="-fno-direct-access-external-data"
+ else
+ ARCH_KBUILD_CFLAGS_KERNEL="-mno-direct-extern-access"
+ fi
+
+ status "LoongArch detected: adding $ARCH_KBUILD_CFLAGS_KERNEL to KBUILD_CFLAGS_KERNEL"
+ fi
+
cmd=("make")
# When a patch to a kernel module references a newly created unexported
@@ -582,6 +600,7 @@ build_kernel() {
fi
cmd+=("-j$JOBS")
cmd+=("KCFLAGS=-ffunction-sections -fdata-sections")
+ cmd+=("KBUILD_CFLAGS_KERNEL=$ARCH_KBUILD_CFLAGS_KERNEL")
cmd+=("vmlinux")
cmd+=("modules")
--
2.25.1
^ permalink raw reply related
* [PATCH v2 2/8] LoongArch: Mark special sections for KLP support
From: George Guo @ 2026-06-08 10:08 UTC (permalink / raw)
To: chenhuacai, jpoimboe, peterz, jikos, mbenes, pmladek
Cc: kernel, joe.lawrence, rostedt, ardb, nathan,
nick.desaulniers+lkml, yangtiezhu, jiaxun.yang, liukexin,
guodongtai, xry111, wangyuli, loongarch, live-patching, llvm,
linux-kernel
In-Reply-To: <20260608100852.325413-1-dongtai.guo@linux.dev>
From: George Guo <guodongtai@kylinos.cn>
objtool needs to split the kernel's special sections into per-entry
symbols when generating livepatch modules. Mark them so it can:
- .altinstructions and __ex_table are read-only, so set an explicit
entry size via SHF_MERGE (ALT_INSTR_SIZE / EXTABLE_SIZE).
- __bug_table and __jump_table are writable. SHF_MERGE cannot be
combined with SHF_WRITE (Clang rejects it), so annotate each entry
with ANNOTATE_DATA_SPECIAL instead, mirroring x86/arm64.
Co-developed-by: Kexin Liu <liukexin@kylinos.cn>
Signed-off-by: Kexin Liu <liukexin@kylinos.cn>
Signed-off-by: George Guo <guodongtai@kylinos.cn>
---
arch/loongarch/include/asm/alternative-asm.h | 5 +++--
arch/loongarch/include/asm/alternative.h | 6 ++++--
arch/loongarch/include/asm/asm-extable.h | 10 ++++++++--
arch/loongarch/include/asm/bug.h | 1 +
arch/loongarch/include/asm/jump_label.h | 2 ++
arch/loongarch/kernel/asm-offsets.c | 20 ++++++++++++++++++++
6 files changed, 38 insertions(+), 6 deletions(-)
diff --git a/arch/loongarch/include/asm/alternative-asm.h b/arch/loongarch/include/asm/alternative-asm.h
index 7dc29bd9b2f0..d940d9d2c0e1 100644
--- a/arch/loongarch/include/asm/alternative-asm.h
+++ b/arch/loongarch/include/asm/alternative-asm.h
@@ -5,6 +5,7 @@
#ifdef __ASSEMBLER__
#include <asm/asm.h>
+#include <generated/asm-offsets.h>
/*
* Issue one struct alt_instr descriptor entry (need to put it into
@@ -33,7 +34,7 @@
.fill - (((144f-143f)-(141b-140b)) > 0) * ((144f-143f)-(141b-140b)) / 4, 4, 0x03400000
142 :
- .pushsection .altinstructions, "a"
+ .pushsection .altinstructions, "aM", @progbits, ALT_INSTR_SIZE
altinstruction_entry 140b, 143f, \feature, 142b-140b, 144f-143f
.popsection
@@ -63,7 +64,7 @@
(alt_max_short(new_len1, new_len2) - (old_len)) / 4, 4, 0x03400000
142 :
- .pushsection .altinstructions, "a"
+ .pushsection .altinstructions, "aM", @progbits, ALT_INSTR_SIZE
altinstruction_entry 140b, 143f, \feature1, 142b-140b, 144f-143f, 142b-141b
altinstruction_entry 140b, 144f, \feature2, 142b-140b, 145f-144f, 142b-141b
.popsection
diff --git a/arch/loongarch/include/asm/alternative.h b/arch/loongarch/include/asm/alternative.h
index b5bae21fb3c8..8f7712ed2f4e 100644
--- a/arch/loongarch/include/asm/alternative.h
+++ b/arch/loongarch/include/asm/alternative.h
@@ -70,7 +70,8 @@ extern void apply_alternatives(struct alt_instr *start, struct alt_instr *end);
/* alternative assembly primitive: */
#define ALTERNATIVE(oldinstr, newinstr, feature) \
OLDINSTR(oldinstr, 1) \
- ".pushsection .altinstructions,\"a\"\n" \
+ ".pushsection .altinstructions, \"aM\", @progbits, " \
+ __stringify(ALT_INSTR_SIZE) "\n" \
ALTINSTR_ENTRY(feature, 1) \
".popsection\n" \
".subsection 1\n" \
@@ -79,7 +80,8 @@ extern void apply_alternatives(struct alt_instr *start, struct alt_instr *end);
#define ALTERNATIVE_2(oldinstr, newinstr1, feature1, newinstr2, feature2)\
OLDINSTR_2(oldinstr, 1, 2) \
- ".pushsection .altinstructions,\"a\"\n" \
+ ".pushsection .altinstructions, \"aM\", @progbits, " \
+ __stringify(ALT_INSTR_SIZE) "\n" \
ALTINSTR_ENTRY(feature1, 1) \
ALTINSTR_ENTRY(feature2, 2) \
".popsection\n" \
diff --git a/arch/loongarch/include/asm/asm-extable.h b/arch/loongarch/include/asm/asm-extable.h
index d60bdf2e6377..2d7eef7c0b39 100644
--- a/arch/loongarch/include/asm/asm-extable.h
+++ b/arch/loongarch/include/asm/asm-extable.h
@@ -7,10 +7,15 @@
#define EX_TYPE_UACCESS_ERR_ZERO 2
#define EX_TYPE_BPF 3
+#ifndef COMPILE_OFFSETS
+#include <asm/asm-offsets.h>
+#endif
+
#ifdef __ASSEMBLER__
#define __ASM_EXTABLE_RAW(insn, fixup, type, data) \
- .pushsection __ex_table, "a"; \
+ .pushsection __ex_table, "aM", @progbits, \
+ EXTABLE_SIZE; \
.balign 4; \
.long ((insn) - .); \
.long ((fixup) - .); \
@@ -29,7 +34,8 @@
#include <asm/gpr-num.h>
#define __ASM_EXTABLE_RAW(insn, fixup, type, data) \
- ".pushsection __ex_table, \"a\"\n" \
+ ".pushsection __ex_table, \"aM\", @progbits, "\
+ __stringify(EXTABLE_SIZE) "\n" \
".balign 4\n" \
".long ((" insn ") - .)\n" \
".long ((" fixup ") - .)\n" \
diff --git a/arch/loongarch/include/asm/bug.h b/arch/loongarch/include/asm/bug.h
index d090a5bec5eb..14e3acbaad8d 100644
--- a/arch/loongarch/include/asm/bug.h
+++ b/arch/loongarch/include/asm/bug.h
@@ -25,6 +25,7 @@
#define __BUG_ENTRY(cond_str, flags) \
.pushsection __bug_table, "aw"; \
.align 2; \
+ __ANNOTATE_DATA_SPECIAL; \
10000: .long 10001f - .; \
_BUGVERBOSE_LOCATION(WARN_CONDITION_STR(cond_str) __FILE__, __LINE__) \
.short flags; \
diff --git a/arch/loongarch/include/asm/jump_label.h b/arch/loongarch/include/asm/jump_label.h
index 7ef4ae3abf08..d20f0fa24b6b 100644
--- a/arch/loongarch/include/asm/jump_label.h
+++ b/arch/loongarch/include/asm/jump_label.h
@@ -12,6 +12,7 @@
#include <linux/types.h>
#include <linux/stringify.h>
#include <asm/asm.h>
+#include <linux/objtool.h>
#define HAVE_JUMP_LABEL_BATCH
@@ -27,6 +28,7 @@
#define JUMP_TABLE_ENTRY(key, label) \
".pushsection __jump_table, \"aw\" \n\t" \
".align " __stringify(PTRLOG) " \n\t" \
+ ANNOTATE_DATA_SPECIAL "\n\t" \
".long 1b - ., " label " - . \n\t" \
JUMP_LABEL_TYPE key " - . \n\t" \
".popsection \n\t"
diff --git a/arch/loongarch/kernel/asm-offsets.c b/arch/loongarch/kernel/asm-offsets.c
index 2cc953f113ac..470323aa022c 100644
--- a/arch/loongarch/kernel/asm-offsets.c
+++ b/arch/loongarch/kernel/asm-offsets.c
@@ -18,6 +18,8 @@
#include <asm/ftrace.h>
#include <asm/sigframe.h>
#include <vdso/datapage.h>
+#include <asm/alternative.h>
+#include <asm/extable.h>
static void __used output_ptreg_defines(void)
{
@@ -323,3 +325,21 @@ static void __used output_vdso_defines(void)
DEFINE(__VDSO_PAGES, VDSO_NR_PAGES);
BLANK();
}
+
+static void __used output_extable_defines(void)
+{
+ COMMENT("LoongArch exception table entry offsets.");
+
+ DEFINE(EXTABLE_SIZE, sizeof(struct exception_table_entry));
+ BLANK();
+}
+
+static void __used output_alt_instr_defines(void)
+{
+ COMMENT("LoongArch alternative instructions offsets.");
+
+ DEFINE(ALT_INSTR_SIZE, sizeof(struct alt_instr));
+ BLANK();
+}
+
+
--
2.25.1
^ permalink raw reply related
* [PATCH v2 1/8] objtool/LoongArch: Add arch_adjusted_addend() for KLP support
From: George Guo @ 2026-06-08 10:08 UTC (permalink / raw)
To: chenhuacai, jpoimboe, peterz, jikos, mbenes, pmladek
Cc: kernel, joe.lawrence, rostedt, ardb, nathan,
nick.desaulniers+lkml, yangtiezhu, jiaxun.yang, liukexin,
guodongtai, xry111, wangyuli, loongarch, live-patching, llvm,
linux-kernel
In-Reply-To: <20260608100852.325413-1-dongtai.guo@linux.dev>
From: George Guo <guodongtai@kylinos.cn>
Implement arch_adjusted_addend() to return the relocation addend value.
Co-developed-by: Kexin Liu <liukexin@kylinos.cn>
Signed-off-by: Kexin Liu <liukexin@kylinos.cn>
Signed-off-by: George Guo <guodongtai@kylinos.cn>
---
tools/objtool/arch/loongarch/decode.c | 5 +++++
1 file changed, 5 insertions(+)
diff --git a/tools/objtool/arch/loongarch/decode.c b/tools/objtool/arch/loongarch/decode.c
index 6cd288150f49..674e4efd138f 100644
--- a/tools/objtool/arch/loongarch/decode.c
+++ b/tools/objtool/arch/loongarch/decode.c
@@ -34,6 +34,11 @@ s64 arch_insn_adjusted_addend(struct instruction *insn, struct reloc *reloc)
return reloc_addend(reloc);
}
+u64 arch_adjusted_addend(struct reloc *reloc)
+{
+ return reloc_addend(reloc);
+}
+
bool arch_pc_relative_reloc(struct reloc *reloc)
{
return false;
--
2.25.1
^ permalink raw reply related
* [PATCH v2 0/8] LoongArch: Add livepatch build (KLP) support
From: George Guo @ 2026-06-08 10:08 UTC (permalink / raw)
To: chenhuacai, jpoimboe, peterz, jikos, mbenes, pmladek
Cc: kernel, joe.lawrence, rostedt, ardb, nathan,
nick.desaulniers+lkml, yangtiezhu, jiaxun.yang, liukexin,
guodongtai, xry111, wangyuli, loongarch, live-patching, llvm,
linux-kernel
This series adds LoongArch support for the klp-build livepatch tooling,
enabling automated livepatch module generation using objtool on
LoongArch.
It is based on Josh Poimboeuf's klp-build series [1] (see base-commit
below) and extends it with the LoongArch-specific objtool and build
pieces, plus a few LoongArch toolchain/relocation fixes required to make
livepatch modules load and run correctly.
Overview (the feature is wired up last so the series stays bisectable:
objtool's KLP arch hooks are all in place before ARCH_HAS_KLP is turned
on):
- Patches 1 and 6 add the objtool hooks LoongArch needs:
arch_adjusted_addend() and arch_jump_opcode_bytes() (the latter makes
klp checksums for jump/call instructions position-independent,
mirroring x86/arm64).
- Patch 2 marks the special sections so objtool can split them into
per-entry symbols: SHF_MERGE entry sizes for the read-only
.altinstructions/__ex_table, and ANNOTATE_DATA_SPECIAL for the
writable __bug_table/__jump_table.
- Patches 3-4 fix kernel panics seen when loading livepatch modules,
caused by LoongArch code-generation/relocation assumptions:
* disable -mdirect-extern-access for klp builds (the module loader
relies on GOT entries for external symbol references),
* build with -fPIC so same-compilation-unit symbol references stay
GOT-indirect and can be relocated into the livepatch module
(-fPIE is not enough: it emits direct, non-GOT references).
- Patch 5 fixes EFI linking when building with -fdata-sections.
- Patch 7 adds KLP_SYSCALL_DEFINEx() support for LoongArch.
- Patch 8 wires up LoongArch klp-build (selects HAVE_KLP_BUILD and
enables objtool's KLP support).
Testing:
- objtool builds cleanly for LoongArch.
- Livepatch modules were generated and loaded on a LoongArch machine;
the panics addressed by patches 3-4 no longer reproduce.
Changes since v1 [2]:
- Mark the writable special sections (__bug_table, __jump_table) with
ANNOTATE_DATA_SPECIAL instead of SHF_MERGE+entsize, which is invalid
for writable sections and rejected by Clang; SHF_MERGE is kept only
for the read-only .altinstructions/__ex_table. Also dropped a
redundant asm-offsets.h include in head.S. (WangYuli)
- Rewrote the -fPIC commit message: the root cause is GOT indirection,
not "absolute addressing". With CONFIG_RELOCATABLE=y the kernel is
already built -fPIE, yet -fPIE still emits direct, non-GOT references
for same-compilation-unit symbols, so -fPIC is required. (Xi Ruoyao)
- Set the LoongArch CFLAGS in build_kernel(), where the variables are
actually used; in v1 they were in clean_kernel() and had no effect.
(WangYuli)
- Gated the klp-build changes on CONFIG_RELOCATABLE=y and
CONFIG_AS_HAS_EXPLICIT_RELOCS=y, and only override KBUILD_CFLAGS_KERNEL
when needed, so other arches/configs are unaffected.
- Reordered the series so the enabling patch ("Add livepatch build (KLP)
support") comes last, keeping every commit buildable/bisectable.
Patch numbers therefore differ from v1.
[1] https://git.kernel.org/pub/scm/linux/kernel/git/jpoimboe/linux.git/log/?h=klp-build-arm64
[2] https://lore.kernel.org/all/20260604065317.219777-1-dongtai.guo@linux.dev/
George Guo (8):
objtool/LoongArch: Add arch_adjusted_addend() for KLP support
LoongArch: Mark special sections for KLP support
livepatch/klp-build: disable direct-extern-access for LoongArch to fix
kernel panic
livepatch/klp-build: build LoongArch with -fPIC to keep GOT-indirect
symbol references
LoongArch: Fix EFI linking with -fdata-sections
objtool/klp: Add LoongArch jump opcode bytes support
klp-build: Add LoongArch syscall patching macro
LoongArch: Add livepatch build (KLP) support
arch/loongarch/Kconfig | 1 +
arch/loongarch/include/asm/alternative-asm.h | 5 ++-
arch/loongarch/include/asm/alternative.h | 6 ++-
arch/loongarch/include/asm/asm-extable.h | 10 ++++-
arch/loongarch/include/asm/bug.h | 1 +
arch/loongarch/include/asm/jump_label.h | 2 +
arch/loongarch/kernel/asm-offsets.c | 20 +++++++++
arch/loongarch/kernel/vmlinux.lds.S | 2 +-
include/linux/livepatch_helpers.h | 22 ++++++++++
scripts/livepatch/klp-build | 36 ++++++++++++++-
tools/objtool/Makefile | 3 +-
tools/objtool/arch/loongarch/decode.c | 46 ++++++++++++++++++++
12 files changed, 145 insertions(+), 9 deletions(-)
base-commit: 85afaba140a4b9f20fe8c8a64b24fc85f022d981
--
2.25.1
^ permalink raw reply
* Re: [PATCH v3 3/7] livepatch: Support scoped atomic replace using replace_set
From: Yafang Shao @ 2026-06-07 14:00 UTC (permalink / raw)
To: sashiko-reviews
Cc: joe.lawrence, mbenes, jikos, song, jpoimboe, pmladek,
live-patching
In-Reply-To: <20260607133313.EE8A21F00893@smtp.kernel.org>
On Sun, Jun 7, 2026 at 9:33 PM <sashiko-bot@kernel.org> wrote:
>
> Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
> - [High] The newly introduced check in `klp_is_patch_compatible` causes a kernel panic if a livepatch module contains a `klp_func` entry with `old_name == NULL`.
> --
>
> commit ed678e88294b650a730cda1a47351fce99677c1e
> Author: Yafang Shao <laoar.shao@gmail.com>
>
> livepatch: Support scoped atomic replace using replace_set
>
> Convert the replace attribute from a boolean to a u32 to function as a
> "replace set." A newly loaded livepatch will now atomically replace any
> existing patch belonging to the same set.
>
> Link: https://github.com/pmladek/linux/tree/klp-state-transfer-v1-iter12 [0]
>
> [ ... ]
> > --- a/kernel/livepatch/state.c
> > +++ b/kernel/livepatch/state.c
> [ ... ]
> > + /*
> > + * Refuse loading a livepatch which would want to modify a
> > + * function which is already livepatched with the livepatch
> > + * with another "replace_set".
> > + */
> > + klp_for_each_object_static(patch, obj) {
> > + klp_for_each_object(old_patch, old_obj) {
> > + if (!!obj->name != !!old_obj->name)
> > + continue;
> > + if (obj->name && strcmp(obj->name, old_obj->name))
> > + continue;
> > + klp_for_each_func_static(obj, func) {
> > + if (klp_find_func(old_obj, func))
> > + return false;
> > + }
> > + }
> > + }
> > }
>
> [Severity: High]
> Can passing an unvalidated function to klp_find_func() cause a NULL pointer
> dereference here?
>
> If a new livepatch module is loaded with an improperly initialized klp_func
> array (e.g., accidentally missing old_name but providing new_func),
> klp_for_each_func_static() will still process it because structural
> validation in klp_init_patch() hasn't occurred yet at this point in the
> loading process.
>
> When this unvalidated func is passed as the second argument to
> klp_find_func(old_obj, func), the roles inside klp_find_func() are reversed
> from its usual context. The old_func parameter represents the new patch's
> unvalidated function, while the local func iterator represents the
> already-installed patch.
>
> Inside klp_find_func(), the check evaluates:
>
> if ((func->old_name && strcmp(old_func->old_name, func->old_name) == 0)
>
> Since the installed patch's func->old_name is guaranteed to be non-NULL,
> strcmp() will be called with old_func->old_name. If old_func->old_name is
> NULL, does this turn what would normally be a graceful -EINVAL module
> rejection into a system crash?
Oops, I missed that. It seems we should better check both func->name
and old_func->name in patch #1.
--- a/kernel/livepatch/core.c
+++ b/kernel/livepatch/core.c
@@ -82,20 +82,22 @@ static bool klp_initialized(void)
return !!klp_root_kobj;
}
+/* Check if @func is present in @obj */
static struct klp_func *klp_find_func(struct klp_object *obj,
- struct klp_func *old_func)
+ struct klp_func *func)
{
- struct klp_func *func;
+ struct klp_func *obj_func;
- klp_for_each_func(obj, func) {
+ klp_for_each_func(obj, obj_func) {
/*
* Besides identical old_sympos, also consider old_sympos
* of 0 and 1 are identical.
*/
- if ((strcmp(old_func->old_name, func->old_name) == 0) &&
- ((old_func->old_sympos == func->old_sympos) ||
- (old_func->old_sympos == 0 && func->old_sympos == 1) ||
- (old_func->old_sympos == 1 && func->old_sympos == 0))) {
+ if ((obj_func->old_name && func->old_name &&
+ strcmp(obj_func->old_name, func->old_name) == 0) &&
+ ((obj_func->old_sympos == func->old_sympos) ||
+ (obj_func->old_sympos == 0 && func->old_sympos == 1) ||
+ (obj_func->old_sympos == 1 && func->old_sympos == 0))) {
return func;
}
}
--
Regards
Yafang
^ permalink raw reply
* Re: [PATCH v3 3/7] livepatch: Support scoped atomic replace using replace_set
From: sashiko-bot @ 2026-06-07 13:33 UTC (permalink / raw)
To: joe.lawrence, mbenes, jikos, song, jpoimboe, pmladek
Cc: live-patching, Yafang Shao
In-Reply-To: <20260607131659.29281-4-laoar.shao@gmail.com>
Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
- [High] The newly introduced check in `klp_is_patch_compatible` causes a kernel panic if a livepatch module contains a `klp_func` entry with `old_name == NULL`.
--
commit ed678e88294b650a730cda1a47351fce99677c1e
Author: Yafang Shao <laoar.shao@gmail.com>
livepatch: Support scoped atomic replace using replace_set
Convert the replace attribute from a boolean to a u32 to function as a
"replace set." A newly loaded livepatch will now atomically replace any
existing patch belonging to the same set.
Link: https://github.com/pmladek/linux/tree/klp-state-transfer-v1-iter12 [0]
[ ... ]
> --- a/kernel/livepatch/state.c
> +++ b/kernel/livepatch/state.c
[ ... ]
> + /*
> + * Refuse loading a livepatch which would want to modify a
> + * function which is already livepatched with the livepatch
> + * with another "replace_set".
> + */
> + klp_for_each_object_static(patch, obj) {
> + klp_for_each_object(old_patch, old_obj) {
> + if (!!obj->name != !!old_obj->name)
> + continue;
> + if (obj->name && strcmp(obj->name, old_obj->name))
> + continue;
> + klp_for_each_func_static(obj, func) {
> + if (klp_find_func(old_obj, func))
> + return false;
> + }
> + }
> + }
> }
[Severity: High]
Can passing an unvalidated function to klp_find_func() cause a NULL pointer
dereference here?
If a new livepatch module is loaded with an improperly initialized klp_func
array (e.g., accidentally missing old_name but providing new_func),
klp_for_each_func_static() will still process it because structural
validation in klp_init_patch() hasn't occurred yet at this point in the
loading process.
When this unvalidated func is passed as the second argument to
klp_find_func(old_obj, func), the roles inside klp_find_func() are reversed
from its usual context. The old_func parameter represents the new patch's
unvalidated function, while the local func iterator represents the
already-installed patch.
Inside klp_find_func(), the check evaluates:
if ((func->old_name && strcmp(old_func->old_name, func->old_name) == 0)
Since the installed patch's func->old_name is guaranteed to be non-NULL,
strcmp() will be called with old_func->old_name. If old_func->old_name is
NULL, does this turn what would normally be a graceful -EINVAL module
rejection into a system crash?
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260607131659.29281-1-laoar.shao@gmail.com?part=3
^ permalink raw reply
* Re: [PATCH v3 4/7] livepatch: Deprecate stack_order
From: sashiko-bot @ 2026-06-07 13:31 UTC (permalink / raw)
To: pmladek, mbenes, jpoimboe, song, joe.lawrence, jikos
Cc: live-patching, Yafang Shao, Wardenjohn
In-Reply-To: <20260607131659.29281-5-laoar.shao@gmail.com>
Thank you for your contribution! Sashiko AI review found 4 potential issue(s) to consider:
New issues:
- [Medium] Returning `-1` as the dummy value for the deprecated `stack_order` sysfs attribute risks breaking backward compatibility.
- [Low] Test coverage for the deprecated `stack_order` sysfs attribute was completely removed instead of being updated.
- [Low] The deprecated `stack_order` ABI documentation is placed in the wrong directory and contains an outdated description.
Pre-existing issues:
- [High] A NULL pointer dereference occurs in `klp_find_func()` when verifying patch compatibility with a malformed livepatch that omits `.old_name`.
--
commit ea264255177cd1b15e55c6d6aa2b21dbb4abd6c2
Author: Yafang Shao <laoar.shao@gmail.com>
livepatch: Deprecate stack_order
stack_order is no longer needed for atomic-replace livepatches, as a
single function can only be modified by a unique replace_set.
To maintain backward compatibility, print a dummy value, as suggested by
sashiko-bot.
> diff --git a/Documentation/ABI/removed/sysfs-kernel-livepatch b/Documentation/ABI/removed/sysfs-kernel-livepatch
> new file mode 100644
> index 0000000000000..72c1ce0ea8647
> --- /dev/null
> +++ b/Documentation/ABI/removed/sysfs-kernel-livepatch
> @@ -0,0 +1,9 @@
> +What: /sys/kernel/livepatch/<patch>/stack_order
> +Date: Jan 2025
> +KernelVersion: 6.14.0
> +Description:
> + This attribute specifies the sequence in which live patch modules
> + are applied to the system. If multiple live patches modify the same
> + function, the implementation with the biggest 'stack_order' number
> + is used, unless a transition is currently in progress.
[Severity: Low]
Is Documentation/ABI/removed/ the right place for this file since the
attribute is still present in the kernel?
According to Documentation/ABI/README, the removed/ directory is strictly
for interfaces that have been completely deleted. Since the attribute still
exists and returns a dummy value, should this be placed in obsolete/ instead?
Also, should the description be updated to explain that the attribute is now
obsolete and just returns a dummy value, rather than claiming it specifies
the sequence in which live patch modules are applied?
> diff --git a/kernel/livepatch/core.c b/kernel/livepatch/core.c
> index 361999e5ce3da..c73eccd2cd139 100644
> --- a/kernel/livepatch/core.c
> +++ b/kernel/livepatch/core.c
[ ... ]
> @@ -436,25 +436,11 @@ static ssize_t replace_set_show(struct kobject *kobj,
> return sysfs_emit(buf, "%u\n", patch->replace_set);
> }
>
> +/* This interface is deprecated and will be removed in the next release */
> static ssize_t stack_order_show(struct kobject *kobj,
> struct kobj_attribute *attr, char *buf)
> {
> - struct klp_patch *patch, *this_patch;
> - int stack_order = 0;
> -
> - this_patch = container_of(kobj, struct klp_patch, kobj);
> -
> - mutex_lock(&klp_mutex);
> -
> - klp_for_each_patch(patch) {
> - stack_order++;
> - if (patch == this_patch)
> - break;
> - }
> -
> - mutex_unlock(&klp_mutex);
> -
> - return sysfs_emit(buf, "%d\n", stack_order);
> + return sysfs_emit(buf, "-1\n");
[Severity: Medium]
Will returning -1 break userspace tools that expect a non-negative integer
or parse this as unsigned?
The commit message mentions printing a dummy value to maintain backward
compatibility. Since the previous implementation returned a strictly positive
sequence (1, 2, ...), could we use a previously valid integer like 1 to
ensure older scripts don't fail when encountering a negative sign?
[Severity: High]
This isn't a bug introduced by this patch, but an earlier commit in this
series ("livepatch: Support scoped atomic replace using replace_set")
appears to introduce a NULL pointer dereference when a malformed patch
omits .old_name.
When klp_enable_patch() is called, it invokes klp_is_patch_compatible()
before klp_init_patch() validates that func->old_name is non-NULL.
The klp_is_patch_compatible() function passes unvalidated functions to
klp_find_func() as the old_func argument:
kernel/livepatch/state.c:klp_is_patch_compatible() {
...
if (klp_find_func(old_obj, func))
return false;
...
}
Inside klp_find_func(), old_func->old_name is unconditionally dereferenced
in the first argument to strcmp():
kernel/livepatch/core.h:klp_find_func() {
...
if ((func->old_name && strcmp(old_func->old_name, func->old_name) == 0) &&
...
}
Could this trigger a kernel panic during patch load?
> diff --git a/tools/testing/selftests/livepatch/test-sysfs.sh b/tools/testing/selftests/livepatch/test-sysfs.sh
> index 58fe1d96997cd..0c31759f34f63 100755
> --- a/tools/testing/selftests/livepatch/test-sysfs.sh
> +++ b/tools/testing/selftests/livepatch/test-sysfs.sh
> @@ -21,8 +21,6 @@ check_sysfs_rights "$MOD_LIVEPATCH" "enabled" "-rw-r--r--"
> check_sysfs_value "$MOD_LIVEPATCH" "enabled" "1"
> check_sysfs_rights "$MOD_LIVEPATCH" "force" "--w-------"
> check_sysfs_rights "$MOD_LIVEPATCH" "replace" "-r--r--r--"
> -check_sysfs_rights "$MOD_LIVEPATCH" "stack_order" "-r--r--r--"
> -check_sysfs_value "$MOD_LIVEPATCH" "stack_order" "1"
[Severity: Low]
Does removing these checks completely leave the deprecated attribute untested?
Since stack_order is intentionally kept to maintain backward compatibility,
should the test suite still verify its presence, correct permissions, and
the new dummy output? This would ensure future changes don't accidentally
remove the sysfs node before the deprecation period ends.
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260607131659.29281-1-laoar.shao@gmail.com?part=4
^ permalink raw reply
* Re: [PATCH v3 5/7] selftests/livepatch: Update tests for replace_set
From: sashiko-bot @ 2026-06-07 13:29 UTC (permalink / raw)
To: joe.lawrence, jikos, mbenes, jpoimboe, song, pmladek
Cc: live-patching, Yafang Shao
In-Reply-To: <20260607131659.29281-6-laoar.shao@gmail.com>
Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
- [Low] The `replace_set` module parameter is defined as `int` with `0644` permissions, but the `replace_set` field in `struct klp_patch` is an `unsigned int` and cannot be dynamically modified post-initialization.
--
commit 4cfbab500868bad0b10a33bdcbb852806612d546
Author: Yafang Shao <laoar.shao@gmail.com>
selftests/livepatch: Update tests for replace_set
This commit updates the livepatch selftests to replace the replace flag
with replace_set. It adjusts test cases to configure multiple livepatches
that share either the same or different replace_set values, and removes the
test case where an atomic replace disables multiple existing livepatches.
[ ... ]
> diff --git a/tools/testing/selftests/livepatch/test_modules/test_klp_atomic_replace.c b/tools/testing/selftests/livepatch/test_modules/test_klp_atomic_replace.c
> index 5af7093ca00c4..5333503f193a3 100644
> --- a/tools/testing/selftests/livepatch/test_modules/test_klp_atomic_replace.c
> +++ b/tools/testing/selftests/livepatch/test_modules/test_klp_atomic_replace.c
> @@ -7,9 +7,9 @@
> #include <linux/kernel.h>
> #include <linux/livepatch.h>
>
> -static int replace;
> -module_param(replace, int, 0644);
> -MODULE_PARM_DESC(replace, "replace (default=0)");
> +static int replace_set;
> +module_param(replace_set, int, 0644);
[Severity: Low]
The replace_set field in struct klp_patch is an unsigned int, and it cannot
be dynamically modified after initialization.
Should this module parameter be defined as uint with read-only permissions
(0444) instead of int and 0644?
Using int and 0644 allows negative values which could cause improper implicit
casts, and runtime modifications have no actual effect on the livepatch.
[ ... ]
> diff --git a/tools/testing/selftests/livepatch/test_modules/test_klp_livepatch.c b/tools/testing/selftests/livepatch/test_modules/test_klp_livepatch.c
> index aff08199de712..fedd2494d187f 100644
> --- a/tools/testing/selftests/livepatch/test_modules/test_klp_livepatch.c
> +++ b/tools/testing/selftests/livepatch/test_modules/test_klp_livepatch.c
> @@ -15,6 +15,10 @@ static int livepatch_cmdline_proc_show(struct seq_file *m, void *v)
> return 0;
> }
>
> +static int replace_set;
> +module_param(replace_set, int, 0644);
[Severity: Low]
Does this parameter also need to be updated to uint and 0444?
As noted above, the underlying struct klp_patch replace_set field is an
unsigned int and is unaffected by modifications after initialization.
[ ... ]
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260607131659.29281-1-laoar.shao@gmail.com?part=5
^ permalink raw reply
* [PATCH v3 7/7] selftests/livepatch: Add test for function conflict across replace_sets
From: Yafang Shao @ 2026-06-07 13:16 UTC (permalink / raw)
To: jpoimboe, jikos, mbenes, pmladek, joe.lawrence, song
Cc: live-patching, Yafang Shao
In-Reply-To: <20260607131659.29281-1-laoar.shao@gmail.com>
Livepatches with different replace_sets must not modify the same
function. If a second livepatch attempts to modify a function that
has already been modified by a loaded livepatch with a different
replace_set, the loading will fail. if the second livepatch
shares the same replace_set, it will load successfully.
Add a test case to verify this behavior. The test result is as follows:
$ ./test-livepatch.sh
TEST: basic function patching ... ok
TEST: multiple livepatches ... ok
TEST: module function patching ... ok
TEST: module function patching (livepatch first) ... ok
TEST: function conflict: different replace_sets ... ok <<<<
TEST: function conflict: the same replace_set ... ok <<<<
Signed-off-by: Yafang Shao <laoar.shao@gmail.com>
---
.../selftests/livepatch/test-livepatch.sh | 59 +++++++++++++++++++
.../selftests/livepatch/test_modules/Makefile | 1 +
.../test_modules/test_klp_atomic_replace2.c | 55 +++++++++++++++++
3 files changed, 115 insertions(+)
create mode 100644 tools/testing/selftests/livepatch/test_modules/test_klp_atomic_replace2.c
diff --git a/tools/testing/selftests/livepatch/test-livepatch.sh b/tools/testing/selftests/livepatch/test-livepatch.sh
index 4f8fb5620c6e..9cdfc048d71c 100755
--- a/tools/testing/selftests/livepatch/test-livepatch.sh
+++ b/tools/testing/selftests/livepatch/test-livepatch.sh
@@ -6,6 +6,7 @@
MOD_LIVEPATCH1=test_klp_livepatch
MOD_REPLACE=test_klp_atomic_replace
+MOD_REPLACE2=test_klp_atomic_replace2
MOD_TARGET=test_klp_mod_target
MOD_TARGET_PATCH=test_klp_mod_patch
@@ -203,5 +204,63 @@ livepatch: '$MOD_TARGET_PATCH': unpatching complete
% rmmod $MOD_TARGET
$MOD_TARGET: test_klp_mod_target_exit"
+# - load a livepatch that modifies function A
+# - loading another livepatch with a different replace_set that modifies
+# the same function will fail
+
+start_test "function conflict: different replace_sets"
+
+load_lp $MOD_REPLACE replace_set=0
+load_failing_mod $MOD_REPLACE2 replace_set=1
+disable_lp $MOD_REPLACE
+unload_lp $MOD_REPLACE
+
+check_result "% insmod test_modules/$MOD_REPLACE.ko replace_set=0
+livepatch: enabling patch '$MOD_REPLACE'
+livepatch: '$MOD_REPLACE': initializing patching transition
+livepatch: '$MOD_REPLACE': starting patching transition
+livepatch: '$MOD_REPLACE': completing patching transition
+livepatch: '$MOD_REPLACE': patching complete
+% insmod test_modules/$MOD_REPLACE2.ko replace_set=1
+livepatch: Livepatch patch ($MOD_REPLACE2) is not compatible with the already installed livepatches.
+insmod: ERROR: could not insert module test_modules/$MOD_REPLACE2.ko: Invalid parameters
+% echo 0 > /sys/kernel/livepatch/$MOD_REPLACE/enabled
+livepatch: '$MOD_REPLACE': initializing unpatching transition
+livepatch: '$MOD_REPLACE': starting unpatching transition
+livepatch: '$MOD_REPLACE': completing unpatching transition
+livepatch: '$MOD_REPLACE': unpatching complete
+% rmmod $MOD_REPLACE"
+
+# - load a livepatch that modifies function A
+# - loading another livepatch with the same replace_set that modifies
+# the same function will succeed
+
+start_test "function conflict: the same replace_set"
+
+load_lp $MOD_REPLACE replace_set=0
+load_lp $MOD_REPLACE2 replace_set=0
+disable_lp $MOD_REPLACE2
+unload_lp $MOD_REPLACE2
+unload_lp $MOD_REPLACE
+
+check_result "% insmod test_modules/$MOD_REPLACE.ko replace_set=0
+livepatch: enabling patch '$MOD_REPLACE'
+livepatch: '$MOD_REPLACE': initializing patching transition
+livepatch: '$MOD_REPLACE': starting patching transition
+livepatch: '$MOD_REPLACE': completing patching transition
+livepatch: '$MOD_REPLACE': patching complete
+% insmod test_modules/$MOD_REPLACE2.ko replace_set=0
+livepatch: enabling patch '$MOD_REPLACE2'
+livepatch: '$MOD_REPLACE2': initializing patching transition
+livepatch: '$MOD_REPLACE2': starting patching transition
+livepatch: '$MOD_REPLACE2': completing patching transition
+livepatch: '$MOD_REPLACE2': patching complete
+% echo 0 > /sys/kernel/livepatch/$MOD_REPLACE2/enabled
+livepatch: '$MOD_REPLACE2': initializing unpatching transition
+livepatch: '$MOD_REPLACE2': starting unpatching transition
+livepatch: '$MOD_REPLACE2': completing unpatching transition
+livepatch: '$MOD_REPLACE2': unpatching complete
+% rmmod $MOD_REPLACE2
+% rmmod $MOD_REPLACE"
exit 0
diff --git a/tools/testing/selftests/livepatch/test_modules/Makefile b/tools/testing/selftests/livepatch/test_modules/Makefile
index bdc5ae37311e..f7111d500cab 100644
--- a/tools/testing/selftests/livepatch/test_modules/Makefile
+++ b/tools/testing/selftests/livepatch/test_modules/Makefile
@@ -2,6 +2,7 @@ TESTMODS_DIR := $(realpath $(dir $(abspath $(lastword $(MAKEFILE_LIST)))))
KDIR ?= /lib/modules/$(shell uname -r)/build
obj-m += test_klp_atomic_replace.o \
+ test_klp_atomic_replace2.o \
test_klp_callbacks_busy.o \
test_klp_callbacks_demo.o \
test_klp_callbacks_demo2.o \
diff --git a/tools/testing/selftests/livepatch/test_modules/test_klp_atomic_replace2.c b/tools/testing/selftests/livepatch/test_modules/test_klp_atomic_replace2.c
new file mode 100644
index 000000000000..1dff8b6d45ab
--- /dev/null
+++ b/tools/testing/selftests/livepatch/test_modules/test_klp_atomic_replace2.c
@@ -0,0 +1,55 @@
+// SPDX-License-Identifier: GPL-2.0
+
+#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
+
+#include <linux/module.h>
+#include <linux/kernel.h>
+#include <linux/livepatch.h>
+
+static int replace_set;
+module_param(replace_set, int, 0644);
+MODULE_PARM_DESC(replace_set, "replace_set (default=0)");
+
+#include <linux/seq_file.h>
+static int livepatch_meminfo_proc_show(struct seq_file *m, void *v)
+{
+ seq_printf(m, "%s: %s\n", THIS_MODULE->name,
+ "this has been live patched");
+ return 0;
+}
+
+static struct klp_func funcs[] = {
+ {
+ .old_name = "meminfo_proc_show",
+ .new_func = livepatch_meminfo_proc_show,
+ }, {}
+};
+
+static struct klp_object objs[] = {
+ {
+ /* name being NULL means vmlinux */
+ .funcs = funcs,
+ }, {}
+};
+
+static struct klp_patch patch = {
+ .mod = THIS_MODULE,
+ .objs = objs,
+ /* set .replace_set in the init function below for demo purposes */
+};
+
+static int test_klp_atomic_replace_init(void)
+{
+ patch.replace_set = replace_set;
+ return klp_enable_patch(&patch);
+}
+
+static void test_klp_atomic_replace_exit(void)
+{
+}
+
+module_init(test_klp_atomic_replace_init);
+module_exit(test_klp_atomic_replace_exit);
+MODULE_LICENSE("GPL");
+MODULE_INFO(livepatch, "Y");
+MODULE_DESCRIPTION("Livepatch test: modified function compatibility verification");
--
2.52.0
^ permalink raw reply related
* [PATCH v3 6/7] selftests/livepatch: Add test for state ID conflict across replace_sets
From: Yafang Shao @ 2026-06-07 13:16 UTC (permalink / raw)
To: jpoimboe, jikos, mbenes, pmladek, joe.lawrence, song
Cc: live-patching, Yafang Shao
In-Reply-To: <20260607131659.29281-1-laoar.shao@gmail.com>
Livepatches with different replace_sets must not share the same state
ID. If a second livepatch attempts to reuse a state ID already
registered by a livepatch with a different replace_set, the loading
will fail.
Add a test case to verify this behavior. The test result is as follows:
$ ./test-state.sh
TEST: system state modification ... ok
TEST: taking over system state modification ... ok
TEST: compatible cumulative livepatches ... ok
TEST: incompatible cumulative livepatches ... ok
TEST: livepatches state compatibility ... ok <<<< This case
Signed-off-by: Yafang Shao <laoar.shao@gmail.com>
---
.../testing/selftests/livepatch/test-state.sh | 34 ++++
.../selftests/livepatch/test_modules/Makefile | 1 +
.../livepatch/test_modules/test_klp_state.c | 2 +-
.../livepatch/test_modules/test_klp_state2.c | 2 +-
.../livepatch/test_modules/test_klp_state4.c | 163 ++++++++++++++++++
5 files changed, 200 insertions(+), 2 deletions(-)
create mode 100644 tools/testing/selftests/livepatch/test_modules/test_klp_state4.c
diff --git a/tools/testing/selftests/livepatch/test-state.sh b/tools/testing/selftests/livepatch/test-state.sh
index 04b66380f8a0..934a2aa655bb 100755
--- a/tools/testing/selftests/livepatch/test-state.sh
+++ b/tools/testing/selftests/livepatch/test-state.sh
@@ -7,6 +7,7 @@
MOD_LIVEPATCH=test_klp_state
MOD_LIVEPATCH2=test_klp_state2
MOD_LIVEPATCH3=test_klp_state3
+MOD_LIVEPATCH4=test_klp_state4
setup_config
@@ -173,4 +174,37 @@ $MOD_LIVEPATCH2: free_loglevel_state: freeing space for the stored console_logle
livepatch: '$MOD_LIVEPATCH2': unpatching complete
% rmmod $MOD_LIVEPATCH2"
+# livepatches with different replace_set sharing the same state ID
+
+start_test "livepatches state compatibility "
+
+load_lp $MOD_LIVEPATCH
+load_failing_mod $MOD_LIVEPATCH4
+disable_lp $MOD_LIVEPATCH
+unload_lp $MOD_LIVEPATCH
+
+check_result "% insmod test_modules/$MOD_LIVEPATCH.ko
+livepatch: enabling patch '$MOD_LIVEPATCH'
+livepatch: '$MOD_LIVEPATCH': initializing patching transition
+$MOD_LIVEPATCH: pre_patch_callback: vmlinux
+$MOD_LIVEPATCH: allocate_loglevel_state: allocating space to store console_loglevel
+livepatch: '$MOD_LIVEPATCH': starting patching transition
+livepatch: '$MOD_LIVEPATCH': completing patching transition
+$MOD_LIVEPATCH: post_patch_callback: vmlinux
+$MOD_LIVEPATCH: fix_console_loglevel: fixing console_loglevel
+livepatch: '$MOD_LIVEPATCH': patching complete
+% insmod test_modules/$MOD_LIVEPATCH4.ko
+livepatch: Livepatch patch ($MOD_LIVEPATCH4) is not compatible with the already installed livepatches.
+insmod: ERROR: could not insert module test_modules/$MOD_LIVEPATCH4.ko: Invalid parameters
+% echo 0 > $SYSFS_KLP_DIR/$MOD_LIVEPATCH/enabled
+livepatch: '$MOD_LIVEPATCH': initializing unpatching transition
+$MOD_LIVEPATCH: pre_unpatch_callback: vmlinux
+$MOD_LIVEPATCH: restore_console_loglevel: restoring console_loglevel
+livepatch: '$MOD_LIVEPATCH': starting unpatching transition
+livepatch: '$MOD_LIVEPATCH': completing unpatching transition
+$MOD_LIVEPATCH: post_unpatch_callback: vmlinux
+$MOD_LIVEPATCH: free_loglevel_state: freeing space for the stored console_loglevel
+livepatch: '$MOD_LIVEPATCH': unpatching complete
+% rmmod $MOD_LIVEPATCH"
+
exit 0
diff --git a/tools/testing/selftests/livepatch/test_modules/Makefile b/tools/testing/selftests/livepatch/test_modules/Makefile
index a13d398585dc..bdc5ae37311e 100644
--- a/tools/testing/selftests/livepatch/test_modules/Makefile
+++ b/tools/testing/selftests/livepatch/test_modules/Makefile
@@ -14,6 +14,7 @@ obj-m += test_klp_atomic_replace.o \
test_klp_state.o \
test_klp_state2.o \
test_klp_state3.o \
+ test_klp_state4.o \
test_klp_syscall.o
# Ensure that KDIR exists, otherwise skip the compilation
diff --git a/tools/testing/selftests/livepatch/test_modules/test_klp_state.c b/tools/testing/selftests/livepatch/test_modules/test_klp_state.c
index 8c8829c3ec43..6ef3059483de 100644
--- a/tools/testing/selftests/livepatch/test_modules/test_klp_state.c
+++ b/tools/testing/selftests/livepatch/test_modules/test_klp_state.c
@@ -39,7 +39,7 @@ static int allocate_loglevel_state(void)
if (!loglevel_state)
return -EINVAL;
- loglevel_state->data = kzalloc(sizeof(console_loglevel), GFP_KERNEL);
+ loglevel_state->data = kzalloc_obj(console_loglevel, GFP_KERNEL);
if (!loglevel_state->data)
return -ENOMEM;
diff --git a/tools/testing/selftests/livepatch/test_modules/test_klp_state2.c b/tools/testing/selftests/livepatch/test_modules/test_klp_state2.c
index 8a79d7dcce33..908d79bc7540 100644
--- a/tools/testing/selftests/livepatch/test_modules/test_klp_state2.c
+++ b/tools/testing/selftests/livepatch/test_modules/test_klp_state2.c
@@ -46,7 +46,7 @@ static int allocate_loglevel_state(void)
if (!loglevel_state)
return -EINVAL;
- loglevel_state->data = kzalloc(sizeof(console_loglevel), GFP_KERNEL);
+ loglevel_state->data = kzalloc_obj(console_loglevel, GFP_KERNEL);
if (!loglevel_state->data)
return -ENOMEM;
diff --git a/tools/testing/selftests/livepatch/test_modules/test_klp_state4.c b/tools/testing/selftests/livepatch/test_modules/test_klp_state4.c
new file mode 100644
index 000000000000..010d1e13f6ec
--- /dev/null
+++ b/tools/testing/selftests/livepatch/test_modules/test_klp_state4.c
@@ -0,0 +1,163 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * This test is identical to test_klp_state4.c, except that replace_set is
+ * set to 4 instead of 0.
+ */
+
+#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
+
+#include <linux/slab.h>
+#include <linux/module.h>
+#include <linux/kernel.h>
+#include <linux/printk.h>
+#include <linux/livepatch.h>
+
+#define CONSOLE_LOGLEVEL_STATE 1
+#define CONSOLE_LOGLEVEL_STATE_VERSION 4
+
+static const char *const module_state[] = {
+ [MODULE_STATE_LIVE] = "[MODULE_STATE_LIVE] Normal state",
+ [MODULE_STATE_COMING] = "[MODULE_STATE_COMING] Full formed, running module_init",
+ [MODULE_STATE_GOING] = "[MODULE_STATE_GOING] Going away",
+ [MODULE_STATE_UNFORMED] = "[MODULE_STATE_UNFORMED] Still setting it up",
+};
+
+static void callback_info(const char *callback, struct klp_object *obj)
+{
+ if (obj->mod)
+ pr_info("%s: %s -> %s\n", callback, obj->mod->name,
+ module_state[obj->mod->state]);
+ else
+ pr_info("%s: vmlinux\n", callback);
+}
+
+static struct klp_patch patch;
+
+static int allocate_loglevel_state(void)
+{
+ struct klp_state *loglevel_state;
+
+ loglevel_state = klp_get_state(&patch, CONSOLE_LOGLEVEL_STATE);
+ if (!loglevel_state)
+ return -EINVAL;
+
+ loglevel_state->data = kzalloc_obj(console_loglevel, GFP_KERNEL);
+ if (!loglevel_state->data)
+ return -ENOMEM;
+
+ pr_info("%s: allocating space to store console_loglevel\n",
+ __func__);
+ return 0;
+}
+
+static void fix_console_loglevel(void)
+{
+ struct klp_state *loglevel_state;
+
+ loglevel_state = klp_get_state(&patch, CONSOLE_LOGLEVEL_STATE);
+ if (!loglevel_state)
+ return;
+
+ pr_info("%s: fixing console_loglevel\n", __func__);
+ *(int *)loglevel_state->data = console_loglevel;
+ console_loglevel = CONSOLE_LOGLEVEL_MOTORMOUTH;
+}
+
+static void restore_console_loglevel(void)
+{
+ struct klp_state *loglevel_state;
+
+ loglevel_state = klp_get_state(&patch, CONSOLE_LOGLEVEL_STATE);
+ if (!loglevel_state)
+ return;
+
+ pr_info("%s: restoring console_loglevel\n", __func__);
+ console_loglevel = *(int *)loglevel_state->data;
+}
+
+static void free_loglevel_state(void)
+{
+ struct klp_state *loglevel_state;
+
+ loglevel_state = klp_get_state(&patch, CONSOLE_LOGLEVEL_STATE);
+ if (!loglevel_state)
+ return;
+
+ pr_info("%s: freeing space for the stored console_loglevel\n",
+ __func__);
+ kfree(loglevel_state->data);
+}
+
+/* Executed on object patching (ie, patch enablement) */
+static int pre_patch_callback(struct klp_object *obj)
+{
+ callback_info(__func__, obj);
+ return allocate_loglevel_state();
+}
+
+/* Executed on object unpatching (ie, patch disablement) */
+static void post_patch_callback(struct klp_object *obj)
+{
+ callback_info(__func__, obj);
+ fix_console_loglevel();
+}
+
+/* Executed on object unpatching (ie, patch disablement) */
+static void pre_unpatch_callback(struct klp_object *obj)
+{
+ callback_info(__func__, obj);
+ restore_console_loglevel();
+}
+
+/* Executed on object unpatching (ie, patch disablement) */
+static void post_unpatch_callback(struct klp_object *obj)
+{
+ callback_info(__func__, obj);
+ free_loglevel_state();
+}
+
+static struct klp_func no_funcs[] = {
+ {}
+};
+
+static struct klp_object objs[] = {
+ {
+ .name = NULL, /* vmlinux */
+ .funcs = no_funcs,
+ .callbacks = {
+ .pre_patch = pre_patch_callback,
+ .post_patch = post_patch_callback,
+ .pre_unpatch = pre_unpatch_callback,
+ .post_unpatch = post_unpatch_callback,
+ },
+ }, { }
+};
+
+static struct klp_state states[] = {
+ {
+ .id = CONSOLE_LOGLEVEL_STATE,
+ .version = CONSOLE_LOGLEVEL_STATE_VERSION,
+ }, { }
+};
+
+static struct klp_patch patch = {
+ .mod = THIS_MODULE,
+ .objs = objs,
+ .states = states,
+ .replace_set = 4,
+};
+
+static int test_klp_callbacks_demo_init(void)
+{
+ return klp_enable_patch(&patch);
+}
+
+static void test_klp_callbacks_demo_exit(void)
+{
+}
+
+module_init(test_klp_callbacks_demo_init);
+module_exit(test_klp_callbacks_demo_exit);
+MODULE_LICENSE("GPL");
+MODULE_INFO(livepatch, "Y");
+MODULE_DESCRIPTION("Livepatch test: system state compatibility verification");
--
2.52.0
^ permalink raw reply related
* [PATCH v3 5/7] selftests/livepatch: Update tests for replace_set
From: Yafang Shao @ 2026-06-07 13:16 UTC (permalink / raw)
To: jpoimboe, jikos, mbenes, pmladek, joe.lawrence, song
Cc: live-patching, Yafang Shao
In-Reply-To: <20260607131659.29281-1-laoar.shao@gmail.com>
The changes are as follows:
- Replace "replace" with "replace_set"
- For atomic-replace test cases, we configure two different livepatches
that share the same replace_set.
- For non-atomic-replace test cases, we configure multiple livepatches,
each with a different replace_set.
- Remove the test case that an atomic replace livepatch disables
multiple existing livepatches
The result are as follows,
$ ./test-callbacks.sh
TEST: target module before livepatch ... ok
TEST: module_coming notifier ... ok
TEST: module_going notifier ... ok
TEST: module_coming and module_going notifiers ... ok
TEST: target module not present ... ok
TEST: pre-patch callback -ENODEV ... ok
TEST: module_coming + pre-patch callback -ENODEV ... ok
TEST: multiple target modules ... ok
TEST: busy target module ... ok
TEST: multiple livepatches ... ok
TEST: atomic replace ... ok
$ ./test-ftrace.sh
TEST: livepatch interaction with ftrace_enabled sysctl ... ok
TEST: trace livepatched function and check that the live patch remains in effect ... ok
TEST: livepatch a traced function and check that the live patch remains in effect ... ok
$ ./test-kprobe.sh
TEST: livepatch interaction with kprobed function with post_handler ... ok
TEST: livepatch interaction with kprobed function without post_handler ... ok
$ ./test-livepatch.sh
TEST: basic function patching ... ok
TEST: multiple livepatches ... ok
TEST: module function patching ... ok
TEST: module function patching (livepatch first) ... ok
$ ./test-shadow-vars.sh
TEST: basic shadow variable API ... ok
$ ./test-state.sh
TEST: system state modification ... ok
TEST: taking over system state modification ... ok
TEST: compatible cumulative livepatches ... ok
TEST: incompatible cumulative livepatches ... ok
$ ./test-syscall.sh
TEST: patch getpid syscall while being heavily hammered ... ok
Signed-off-by: Yafang Shao <laoar.shao@gmail.com>
---
.../selftests/livepatch/test-callbacks.sh | 33 +++---
.../selftests/livepatch/test-livepatch.sh | 100 +-----------------
.../testing/selftests/livepatch/test-sysfs.sh | 22 ++--
.../test_modules/test_klp_atomic_replace.c | 10 +-
.../test_modules/test_klp_callbacks_demo.c | 6 ++
.../test_modules/test_klp_callbacks_demo2.c | 10 +-
.../test_modules/test_klp_livepatch.c | 6 ++
.../livepatch/test_modules/test_klp_state.c | 2 +-
.../livepatch/test_modules/test_klp_state2.c | 2 +-
9 files changed, 55 insertions(+), 136 deletions(-)
diff --git a/tools/testing/selftests/livepatch/test-callbacks.sh b/tools/testing/selftests/livepatch/test-callbacks.sh
index 2a03deb26a12..692da8ea4c25 100755
--- a/tools/testing/selftests/livepatch/test-callbacks.sh
+++ b/tools/testing/selftests/livepatch/test-callbacks.sh
@@ -451,8 +451,8 @@ $MOD_TARGET_BUSY: busymod_work_func exit
$MOD_TARGET_BUSY: ${MOD_TARGET_BUSY}_exit"
-# Test loading multiple livepatches. This test-case is mainly for comparing
-# with the next test-case.
+# Test loading multiple livepatches sharing different replace_set.
+# This test-case is mainly for comparing with the next test-case.
#
# - Load and unload two livepatches, pre and post (un)patch callbacks
# execute as each patch progresses through its (un)patching
@@ -460,14 +460,14 @@ $MOD_TARGET_BUSY: ${MOD_TARGET_BUSY}_exit"
start_test "multiple livepatches"
-load_lp $MOD_LIVEPATCH
-load_lp $MOD_LIVEPATCH2
+load_lp $MOD_LIVEPATCH replace_set=0
+load_lp $MOD_LIVEPATCH2 replace_set=1
disable_lp $MOD_LIVEPATCH2
disable_lp $MOD_LIVEPATCH
unload_lp $MOD_LIVEPATCH2
unload_lp $MOD_LIVEPATCH
-check_result "% insmod test_modules/$MOD_LIVEPATCH.ko
+check_result "% insmod test_modules/$MOD_LIVEPATCH.ko replace_set=0
livepatch: enabling patch '$MOD_LIVEPATCH'
livepatch: '$MOD_LIVEPATCH': initializing patching transition
$MOD_LIVEPATCH: pre_patch_callback: vmlinux
@@ -475,7 +475,7 @@ livepatch: '$MOD_LIVEPATCH': starting patching transition
livepatch: '$MOD_LIVEPATCH': completing patching transition
$MOD_LIVEPATCH: post_patch_callback: vmlinux
livepatch: '$MOD_LIVEPATCH': patching complete
-% insmod test_modules/$MOD_LIVEPATCH2.ko
+% insmod test_modules/$MOD_LIVEPATCH2.ko replace_set=1
livepatch: enabling patch '$MOD_LIVEPATCH2'
livepatch: '$MOD_LIVEPATCH2': initializing patching transition
$MOD_LIVEPATCH2: pre_patch_callback: vmlinux
@@ -501,14 +501,13 @@ livepatch: '$MOD_LIVEPATCH': unpatching complete
% rmmod $MOD_LIVEPATCH"
-# Load multiple livepatches, but the second as an 'atomic-replace'
-# patch. When the latter loads, the original livepatch should be
-# disabled and *none* of its pre/post-unpatch callbacks executed. On
-# the other hand, when the atomic-replace livepatch is disabled, its
-# pre/post-unpatch callbacks *should* be executed.
+# Load multiple livepatches sharing the same replace_set.
+# When the latter loads, the original livepatch should be disabled and
+# *none* of its pre/post-unpatch callbacks executed. On the other hand,
+# when the atomic-replace livepatch is disabled, its pre/post-unpatch
+# callbacks *should* be executed.
#
-# - Load and unload two livepatches, the second of which has its
-# .replace flag set true.
+# - Load and unload two livepatches sharing the same replace_set
#
# - Pre and post patch callbacks are executed for both livepatches.
#
@@ -517,13 +516,13 @@ livepatch: '$MOD_LIVEPATCH': unpatching complete
start_test "atomic replace"
-load_lp $MOD_LIVEPATCH
-load_lp $MOD_LIVEPATCH2 replace=1
+load_lp $MOD_LIVEPATCH replace_set=0
+load_lp $MOD_LIVEPATCH2 replace_set=0
disable_lp $MOD_LIVEPATCH2
unload_lp $MOD_LIVEPATCH2
unload_lp $MOD_LIVEPATCH
-check_result "% insmod test_modules/$MOD_LIVEPATCH.ko
+check_result "% insmod test_modules/$MOD_LIVEPATCH.ko replace_set=0
livepatch: enabling patch '$MOD_LIVEPATCH'
livepatch: '$MOD_LIVEPATCH': initializing patching transition
$MOD_LIVEPATCH: pre_patch_callback: vmlinux
@@ -531,7 +530,7 @@ livepatch: '$MOD_LIVEPATCH': starting patching transition
livepatch: '$MOD_LIVEPATCH': completing patching transition
$MOD_LIVEPATCH: post_patch_callback: vmlinux
livepatch: '$MOD_LIVEPATCH': patching complete
-% insmod test_modules/$MOD_LIVEPATCH2.ko replace=1
+% insmod test_modules/$MOD_LIVEPATCH2.ko replace_set=0
livepatch: enabling patch '$MOD_LIVEPATCH2'
livepatch: '$MOD_LIVEPATCH2': initializing patching transition
$MOD_LIVEPATCH2: pre_patch_callback: vmlinux
diff --git a/tools/testing/selftests/livepatch/test-livepatch.sh b/tools/testing/selftests/livepatch/test-livepatch.sh
index c44c5341a2f1..4f8fb5620c6e 100755
--- a/tools/testing/selftests/livepatch/test-livepatch.sh
+++ b/tools/testing/selftests/livepatch/test-livepatch.sh
@@ -5,8 +5,6 @@
. $(dirname $0)/functions.sh
MOD_LIVEPATCH1=test_klp_livepatch
-MOD_LIVEPATCH2=test_klp_syscall
-MOD_LIVEPATCH3=test_klp_callbacks_demo
MOD_REPLACE=test_klp_atomic_replace
MOD_TARGET=test_klp_mod_target
MOD_TARGET_PATCH=test_klp_mod_patch
@@ -57,12 +55,12 @@ livepatch: '$MOD_LIVEPATCH1': unpatching complete
start_test "multiple livepatches"
-load_lp $MOD_LIVEPATCH1
+load_lp $MOD_LIVEPATCH1 replace_set=0
grep 'live patched' /proc/cmdline > /dev/kmsg
grep 'live patched' /proc/meminfo > /dev/kmsg
-load_lp $MOD_REPLACE replace=0
+load_lp $MOD_REPLACE replace_set=1
grep 'live patched' /proc/cmdline > /dev/kmsg
grep 'live patched' /proc/meminfo > /dev/kmsg
@@ -79,14 +77,14 @@ unload_lp $MOD_LIVEPATCH1
grep 'live patched' /proc/cmdline > /dev/kmsg
grep 'live patched' /proc/meminfo > /dev/kmsg
-check_result "% insmod test_modules/$MOD_LIVEPATCH1.ko
+check_result "% insmod test_modules/$MOD_LIVEPATCH1.ko replace_set=0
livepatch: enabling patch '$MOD_LIVEPATCH1'
livepatch: '$MOD_LIVEPATCH1': initializing patching transition
livepatch: '$MOD_LIVEPATCH1': starting patching transition
livepatch: '$MOD_LIVEPATCH1': completing patching transition
livepatch: '$MOD_LIVEPATCH1': patching complete
$MOD_LIVEPATCH1: this has been live patched
-% insmod test_modules/$MOD_REPLACE.ko replace=0
+% insmod test_modules/$MOD_REPLACE.ko replace_set=1
livepatch: enabling patch '$MOD_REPLACE'
livepatch: '$MOD_REPLACE': initializing patching transition
livepatch: '$MOD_REPLACE': starting patching transition
@@ -108,96 +106,6 @@ livepatch: '$MOD_LIVEPATCH1': completing unpatching transition
livepatch: '$MOD_LIVEPATCH1': unpatching complete
% rmmod $MOD_LIVEPATCH1"
-
-# - load a livepatch that modifies the output from /proc/cmdline and
-# verify correct behavior
-# - load two additional livepatches and check the number of livepatch modules
-# applied
-# - load an atomic replace livepatch and check that the other three modules were
-# disabled
-# - remove all livepatches besides the atomic replace one and verify that the
-# atomic replace livepatch is still active
-# - remove the atomic replace livepatch and verify that none are active
-
-start_test "atomic replace livepatch"
-
-load_lp $MOD_LIVEPATCH1
-
-grep 'live patched' /proc/cmdline > /dev/kmsg
-grep 'live patched' /proc/meminfo > /dev/kmsg
-
-for mod in $MOD_LIVEPATCH2 $MOD_LIVEPATCH3; do
- load_lp "$mod"
-done
-
-mods=($SYSFS_KLP_DIR/*)
-nmods=${#mods[@]}
-if [ "$nmods" -ne 3 ]; then
- die "Expecting three modules listed, found $nmods"
-fi
-
-load_lp $MOD_REPLACE replace=1
-
-grep 'live patched' /proc/cmdline > /dev/kmsg
-grep 'live patched' /proc/meminfo > /dev/kmsg
-
-loop_until 'mods=($SYSFS_KLP_DIR/*); nmods=${#mods[@]}; [[ "$nmods" -eq 1 ]]' ||
- die "Expecting only one moduled listed, found $nmods"
-
-# These modules were disabled by the atomic replace
-for mod in $MOD_LIVEPATCH3 $MOD_LIVEPATCH2 $MOD_LIVEPATCH1; do
- unload_lp "$mod"
-done
-
-grep 'live patched' /proc/cmdline > /dev/kmsg
-grep 'live patched' /proc/meminfo > /dev/kmsg
-
-disable_lp $MOD_REPLACE
-unload_lp $MOD_REPLACE
-
-grep 'live patched' /proc/cmdline > /dev/kmsg
-grep 'live patched' /proc/meminfo > /dev/kmsg
-
-check_result "% insmod test_modules/$MOD_LIVEPATCH1.ko
-livepatch: enabling patch '$MOD_LIVEPATCH1'
-livepatch: '$MOD_LIVEPATCH1': initializing patching transition
-livepatch: '$MOD_LIVEPATCH1': starting patching transition
-livepatch: '$MOD_LIVEPATCH1': completing patching transition
-livepatch: '$MOD_LIVEPATCH1': patching complete
-$MOD_LIVEPATCH1: this has been live patched
-% insmod test_modules/$MOD_LIVEPATCH2.ko
-livepatch: enabling patch '$MOD_LIVEPATCH2'
-livepatch: '$MOD_LIVEPATCH2': initializing patching transition
-livepatch: '$MOD_LIVEPATCH2': starting patching transition
-livepatch: '$MOD_LIVEPATCH2': completing patching transition
-livepatch: '$MOD_LIVEPATCH2': patching complete
-% insmod test_modules/$MOD_LIVEPATCH3.ko
-livepatch: enabling patch '$MOD_LIVEPATCH3'
-livepatch: '$MOD_LIVEPATCH3': initializing patching transition
-$MOD_LIVEPATCH3: pre_patch_callback: vmlinux
-livepatch: '$MOD_LIVEPATCH3': starting patching transition
-livepatch: '$MOD_LIVEPATCH3': completing patching transition
-$MOD_LIVEPATCH3: post_patch_callback: vmlinux
-livepatch: '$MOD_LIVEPATCH3': patching complete
-% insmod test_modules/$MOD_REPLACE.ko replace=1
-livepatch: enabling patch '$MOD_REPLACE'
-livepatch: '$MOD_REPLACE': initializing patching transition
-livepatch: '$MOD_REPLACE': starting patching transition
-livepatch: '$MOD_REPLACE': completing patching transition
-livepatch: '$MOD_REPLACE': patching complete
-$MOD_REPLACE: this has been live patched
-% rmmod $MOD_LIVEPATCH3
-% rmmod $MOD_LIVEPATCH2
-% rmmod $MOD_LIVEPATCH1
-$MOD_REPLACE: this has been live patched
-% echo 0 > $SYSFS_KLP_DIR/$MOD_REPLACE/enabled
-livepatch: '$MOD_REPLACE': initializing unpatching transition
-livepatch: '$MOD_REPLACE': starting unpatching transition
-livepatch: '$MOD_REPLACE': completing unpatching transition
-livepatch: '$MOD_REPLACE': unpatching complete
-% rmmod $MOD_REPLACE"
-
-
# - load a target module that provides /proc/test_klp_mod_target with
# original output
# - load a livepatch that patches the target module's show function
diff --git a/tools/testing/selftests/livepatch/test-sysfs.sh b/tools/testing/selftests/livepatch/test-sysfs.sh
index 0c31759f34f6..37425ad89f58 100755
--- a/tools/testing/selftests/livepatch/test-sysfs.sh
+++ b/tools/testing/selftests/livepatch/test-sysfs.sh
@@ -20,7 +20,7 @@ check_sysfs_rights "$MOD_LIVEPATCH" "" "drwxr-xr-x"
check_sysfs_rights "$MOD_LIVEPATCH" "enabled" "-rw-r--r--"
check_sysfs_value "$MOD_LIVEPATCH" "enabled" "1"
check_sysfs_rights "$MOD_LIVEPATCH" "force" "--w-------"
-check_sysfs_rights "$MOD_LIVEPATCH" "replace" "-r--r--r--"
+check_sysfs_rights "$MOD_LIVEPATCH" "replace_set" "-r--r--r--"
check_sysfs_rights "$MOD_LIVEPATCH" "transition" "-r--r--r--"
check_sysfs_value "$MOD_LIVEPATCH" "transition" "0"
check_sysfs_rights "$MOD_LIVEPATCH" "vmlinux/patched" "-r--r--r--"
@@ -86,18 +86,18 @@ test_klp_callbacks_demo: post_unpatch_callback: vmlinux
livepatch: 'test_klp_callbacks_demo': unpatching complete
% rmmod test_klp_callbacks_demo"
-start_test "sysfs test replace enabled"
+start_test "sysfs test replace_set 0"
MOD_LIVEPATCH=test_klp_atomic_replace
-load_lp $MOD_LIVEPATCH replace=1
+load_lp $MOD_LIVEPATCH replace_set=0
-check_sysfs_rights "$MOD_LIVEPATCH" "replace" "-r--r--r--"
-check_sysfs_value "$MOD_LIVEPATCH" "replace" "1"
+check_sysfs_rights "$MOD_LIVEPATCH" "replace_set" "-r--r--r--"
+check_sysfs_value "$MOD_LIVEPATCH" "replace_set" "0"
disable_lp $MOD_LIVEPATCH
unload_lp $MOD_LIVEPATCH
-check_result "% insmod test_modules/$MOD_LIVEPATCH.ko replace=1
+check_result "% insmod test_modules/$MOD_LIVEPATCH.ko replace_set=0
livepatch: enabling patch '$MOD_LIVEPATCH'
livepatch: '$MOD_LIVEPATCH': initializing patching transition
livepatch: '$MOD_LIVEPATCH': starting patching transition
@@ -110,17 +110,17 @@ livepatch: '$MOD_LIVEPATCH': completing unpatching transition
livepatch: '$MOD_LIVEPATCH': unpatching complete
% rmmod $MOD_LIVEPATCH"
-start_test "sysfs test replace disabled"
+start_test "sysfs test replace_set 1234"
-load_lp $MOD_LIVEPATCH replace=0
+load_lp $MOD_LIVEPATCH replace_set=1234
-check_sysfs_rights "$MOD_LIVEPATCH" "replace" "-r--r--r--"
-check_sysfs_value "$MOD_LIVEPATCH" "replace" "0"
+check_sysfs_rights "$MOD_LIVEPATCH" "replace_set" "-r--r--r--"
+check_sysfs_value "$MOD_LIVEPATCH" "replace_set" "1234"
disable_lp $MOD_LIVEPATCH
unload_lp $MOD_LIVEPATCH
-check_result "% insmod test_modules/$MOD_LIVEPATCH.ko replace=0
+check_result "% insmod test_modules/$MOD_LIVEPATCH.ko replace_set=1234
livepatch: enabling patch '$MOD_LIVEPATCH'
livepatch: '$MOD_LIVEPATCH': initializing patching transition
livepatch: '$MOD_LIVEPATCH': starting patching transition
diff --git a/tools/testing/selftests/livepatch/test_modules/test_klp_atomic_replace.c b/tools/testing/selftests/livepatch/test_modules/test_klp_atomic_replace.c
index 5af7093ca00c..5333503f193a 100644
--- a/tools/testing/selftests/livepatch/test_modules/test_klp_atomic_replace.c
+++ b/tools/testing/selftests/livepatch/test_modules/test_klp_atomic_replace.c
@@ -7,9 +7,9 @@
#include <linux/kernel.h>
#include <linux/livepatch.h>
-static int replace;
-module_param(replace, int, 0644);
-MODULE_PARM_DESC(replace, "replace (default=0)");
+static int replace_set;
+module_param(replace_set, int, 0644);
+MODULE_PARM_DESC(replace_set, "replace_set (default=0)");
#include <linux/seq_file.h>
static int livepatch_meminfo_proc_show(struct seq_file *m, void *v)
@@ -36,12 +36,12 @@ static struct klp_object objs[] = {
static struct klp_patch patch = {
.mod = THIS_MODULE,
.objs = objs,
- /* set .replace in the init function below for demo purposes */
+ /* set .replace_set in the init function below for demo purposes */
};
static int test_klp_atomic_replace_init(void)
{
- patch.replace = replace;
+ patch.replace_set = replace_set;
return klp_enable_patch(&patch);
}
diff --git a/tools/testing/selftests/livepatch/test_modules/test_klp_callbacks_demo.c b/tools/testing/selftests/livepatch/test_modules/test_klp_callbacks_demo.c
index 3fd8fe1cd1cc..5c3324aa4d75 100644
--- a/tools/testing/selftests/livepatch/test_modules/test_klp_callbacks_demo.c
+++ b/tools/testing/selftests/livepatch/test_modules/test_klp_callbacks_demo.c
@@ -7,6 +7,10 @@
#include <linux/kernel.h>
#include <linux/livepatch.h>
+static int replace_set;
+module_param(replace_set, int, 0644);
+MODULE_PARM_DESC(replace_set, "replace_set (default=0)");
+
static int pre_patch_ret;
module_param(pre_patch_ret, int, 0644);
MODULE_PARM_DESC(pre_patch_ret, "pre_patch_ret (default=0)");
@@ -102,10 +106,12 @@ static struct klp_object objs[] = {
static struct klp_patch patch = {
.mod = THIS_MODULE,
.objs = objs,
+ /* set .replace_set in the init function below for demo purposes */
};
static int test_klp_callbacks_demo_init(void)
{
+ patch.replace_set = replace_set;
return klp_enable_patch(&patch);
}
diff --git a/tools/testing/selftests/livepatch/test_modules/test_klp_callbacks_demo2.c b/tools/testing/selftests/livepatch/test_modules/test_klp_callbacks_demo2.c
index 5417573e80af..31347e2131a7 100644
--- a/tools/testing/selftests/livepatch/test_modules/test_klp_callbacks_demo2.c
+++ b/tools/testing/selftests/livepatch/test_modules/test_klp_callbacks_demo2.c
@@ -7,9 +7,9 @@
#include <linux/kernel.h>
#include <linux/livepatch.h>
-static int replace;
-module_param(replace, int, 0644);
-MODULE_PARM_DESC(replace, "replace (default=0)");
+static int replace_set;
+module_param(replace_set, int, 0644);
+MODULE_PARM_DESC(replace_set, "replace_set (default=0)");
static const char *const module_state[] = {
[MODULE_STATE_LIVE] = "[MODULE_STATE_LIVE] Normal state",
@@ -72,12 +72,12 @@ static struct klp_object objs[] = {
static struct klp_patch patch = {
.mod = THIS_MODULE,
.objs = objs,
- /* set .replace in the init function below for demo purposes */
+ /* set .replace_set in the init function below for demo purposes */
};
static int test_klp_callbacks_demo2_init(void)
{
- patch.replace = replace;
+ patch.replace_set = replace_set;
return klp_enable_patch(&patch);
}
diff --git a/tools/testing/selftests/livepatch/test_modules/test_klp_livepatch.c b/tools/testing/selftests/livepatch/test_modules/test_klp_livepatch.c
index aff08199de71..fedd2494d187 100644
--- a/tools/testing/selftests/livepatch/test_modules/test_klp_livepatch.c
+++ b/tools/testing/selftests/livepatch/test_modules/test_klp_livepatch.c
@@ -15,6 +15,10 @@ static int livepatch_cmdline_proc_show(struct seq_file *m, void *v)
return 0;
}
+static int replace_set;
+module_param(replace_set, int, 0644);
+MODULE_PARM_DESC(replace_set, "replace_set (default=0)");
+
static struct klp_func funcs[] = {
{
.old_name = "cmdline_proc_show",
@@ -32,10 +36,12 @@ static struct klp_object objs[] = {
static struct klp_patch patch = {
.mod = THIS_MODULE,
.objs = objs,
+ /* set .replace_set in the init function below for demo purposes */
};
static int test_klp_livepatch_init(void)
{
+ patch.replace_set = replace_set;
return klp_enable_patch(&patch);
}
diff --git a/tools/testing/selftests/livepatch/test_modules/test_klp_state.c b/tools/testing/selftests/livepatch/test_modules/test_klp_state.c
index 57a4253acb01..8c8829c3ec43 100644
--- a/tools/testing/selftests/livepatch/test_modules/test_klp_state.c
+++ b/tools/testing/selftests/livepatch/test_modules/test_klp_state.c
@@ -142,7 +142,7 @@ static struct klp_patch patch = {
.mod = THIS_MODULE,
.objs = objs,
.states = states,
- .replace = true,
+ .replace_set = 0,
};
static int test_klp_callbacks_demo_init(void)
diff --git a/tools/testing/selftests/livepatch/test_modules/test_klp_state2.c b/tools/testing/selftests/livepatch/test_modules/test_klp_state2.c
index c978ea4d5e67..8a79d7dcce33 100644
--- a/tools/testing/selftests/livepatch/test_modules/test_klp_state2.c
+++ b/tools/testing/selftests/livepatch/test_modules/test_klp_state2.c
@@ -171,7 +171,7 @@ static struct klp_patch patch = {
.mod = THIS_MODULE,
.objs = objs,
.states = states,
- .replace = true,
+ .replace_set = 0,
};
static int test_klp_callbacks_demo_init(void)
--
2.52.0
^ permalink raw reply related
* [PATCH v3 4/7] livepatch: Deprecate stack_order
From: Yafang Shao @ 2026-06-07 13:16 UTC (permalink / raw)
To: jpoimboe, jikos, mbenes, pmladek, joe.lawrence, song
Cc: live-patching, Yafang Shao, Wardenjohn
In-Reply-To: <20260607131659.29281-1-laoar.shao@gmail.com>
stack_order is no longer needed for atomic-replace livepatches, as a
single function can only be modified by a unique replace_set.
To maintain backward compatibility, print a dummy value, as suggested by
sashiko-bot.
Signed-off-by: Yafang Shao <laoar.shao@gmail.com>
Cc: Wardenjohn <zhangwarden@gmail.com>
---
.../ABI/removed/sysfs-kernel-livepatch | 9 +++
.../ABI/testing/sysfs-kernel-livepatch | 9 ---
kernel/livepatch/core.c | 20 +-----
.../testing/selftests/livepatch/test-sysfs.sh | 69 -------------------
4 files changed, 12 insertions(+), 95 deletions(-)
create mode 100644 Documentation/ABI/removed/sysfs-kernel-livepatch
diff --git a/Documentation/ABI/removed/sysfs-kernel-livepatch b/Documentation/ABI/removed/sysfs-kernel-livepatch
new file mode 100644
index 000000000000..72c1ce0ea864
--- /dev/null
+++ b/Documentation/ABI/removed/sysfs-kernel-livepatch
@@ -0,0 +1,9 @@
+What: /sys/kernel/livepatch/<patch>/stack_order
+Date: Jan 2025
+KernelVersion: 6.14.0
+Description:
+ This attribute specifies the sequence in which live patch modules
+ are applied to the system. If multiple live patches modify the same
+ function, the implementation with the biggest 'stack_order' number
+ is used, unless a transition is currently in progress.
+
diff --git a/Documentation/ABI/testing/sysfs-kernel-livepatch b/Documentation/ABI/testing/sysfs-kernel-livepatch
index 6d75235a6a2e..9d356d8b8c75 100644
--- a/Documentation/ABI/testing/sysfs-kernel-livepatch
+++ b/Documentation/ABI/testing/sysfs-kernel-livepatch
@@ -54,15 +54,6 @@ Contact: live-patching@vger.kernel.org
Description:
An attribute to show the replace_set of this livepatch.
-What: /sys/kernel/livepatch/<patch>/stack_order
-Date: Jan 2025
-KernelVersion: 6.14.0
-Description:
- This attribute specifies the sequence in which live patch modules
- are applied to the system. If multiple live patches modify the same
- function, the implementation with the biggest 'stack_order' number
- is used, unless a transition is currently in progress.
-
What: /sys/kernel/livepatch/<patch>/<object>
Date: Nov 2014
KernelVersion: 3.19.0
diff --git a/kernel/livepatch/core.c b/kernel/livepatch/core.c
index 361999e5ce3d..c73eccd2cd13 100644
--- a/kernel/livepatch/core.c
+++ b/kernel/livepatch/core.c
@@ -330,7 +330,7 @@ int klp_apply_section_relocs(struct module *pmod, Elf_Shdr *sechdrs,
* /sys/kernel/livepatch/<patch>/transition
* /sys/kernel/livepatch/<patch>/force
* /sys/kernel/livepatch/<patch>/replace_set
- * /sys/kernel/livepatch/<patch>/stack_order
+ * /sys/kernel/livepatch/<patch>/stack_order <deprecated>
* /sys/kernel/livepatch/<patch>/<object>
* /sys/kernel/livepatch/<patch>/<object>/patched
* /sys/kernel/livepatch/<patch>/<object>/<function,sympos>
@@ -436,25 +436,11 @@ static ssize_t replace_set_show(struct kobject *kobj,
return sysfs_emit(buf, "%u\n", patch->replace_set);
}
+/* This interface is deprecated and will be removed in the next release */
static ssize_t stack_order_show(struct kobject *kobj,
struct kobj_attribute *attr, char *buf)
{
- struct klp_patch *patch, *this_patch;
- int stack_order = 0;
-
- this_patch = container_of(kobj, struct klp_patch, kobj);
-
- mutex_lock(&klp_mutex);
-
- klp_for_each_patch(patch) {
- stack_order++;
- if (patch == this_patch)
- break;
- }
-
- mutex_unlock(&klp_mutex);
-
- return sysfs_emit(buf, "%d\n", stack_order);
+ return sysfs_emit(buf, "-1\n");
}
static struct kobj_attribute enabled_kobj_attr = __ATTR_RW(enabled);
diff --git a/tools/testing/selftests/livepatch/test-sysfs.sh b/tools/testing/selftests/livepatch/test-sysfs.sh
index 58fe1d96997c..0c31759f34f6 100755
--- a/tools/testing/selftests/livepatch/test-sysfs.sh
+++ b/tools/testing/selftests/livepatch/test-sysfs.sh
@@ -21,8 +21,6 @@ check_sysfs_rights "$MOD_LIVEPATCH" "enabled" "-rw-r--r--"
check_sysfs_value "$MOD_LIVEPATCH" "enabled" "1"
check_sysfs_rights "$MOD_LIVEPATCH" "force" "--w-------"
check_sysfs_rights "$MOD_LIVEPATCH" "replace" "-r--r--r--"
-check_sysfs_rights "$MOD_LIVEPATCH" "stack_order" "-r--r--r--"
-check_sysfs_value "$MOD_LIVEPATCH" "stack_order" "1"
check_sysfs_rights "$MOD_LIVEPATCH" "transition" "-r--r--r--"
check_sysfs_value "$MOD_LIVEPATCH" "transition" "0"
check_sysfs_rights "$MOD_LIVEPATCH" "vmlinux/patched" "-r--r--r--"
@@ -135,71 +133,4 @@ livepatch: '$MOD_LIVEPATCH': completing unpatching transition
livepatch: '$MOD_LIVEPATCH': unpatching complete
% rmmod $MOD_LIVEPATCH"
-start_test "sysfs test stack_order value"
-
-load_lp $MOD_LIVEPATCH
-
-check_sysfs_value "$MOD_LIVEPATCH" "stack_order" "1"
-
-load_lp $MOD_LIVEPATCH2
-
-check_sysfs_value "$MOD_LIVEPATCH2" "stack_order" "2"
-
-load_lp $MOD_LIVEPATCH3
-
-check_sysfs_value "$MOD_LIVEPATCH3" "stack_order" "3"
-
-disable_lp $MOD_LIVEPATCH2
-unload_lp $MOD_LIVEPATCH2
-
-check_sysfs_value "$MOD_LIVEPATCH" "stack_order" "1"
-check_sysfs_value "$MOD_LIVEPATCH3" "stack_order" "2"
-
-disable_lp $MOD_LIVEPATCH3
-unload_lp $MOD_LIVEPATCH3
-
-disable_lp $MOD_LIVEPATCH
-unload_lp $MOD_LIVEPATCH
-
-check_result "% insmod test_modules/$MOD_LIVEPATCH.ko
-livepatch: enabling patch '$MOD_LIVEPATCH'
-livepatch: '$MOD_LIVEPATCH': initializing patching transition
-livepatch: '$MOD_LIVEPATCH': starting patching transition
-livepatch: '$MOD_LIVEPATCH': completing patching transition
-livepatch: '$MOD_LIVEPATCH': patching complete
-% insmod test_modules/$MOD_LIVEPATCH2.ko
-livepatch: enabling patch '$MOD_LIVEPATCH2'
-livepatch: '$MOD_LIVEPATCH2': initializing patching transition
-$MOD_LIVEPATCH2: pre_patch_callback: vmlinux
-livepatch: '$MOD_LIVEPATCH2': starting patching transition
-livepatch: '$MOD_LIVEPATCH2': completing patching transition
-$MOD_LIVEPATCH2: post_patch_callback: vmlinux
-livepatch: '$MOD_LIVEPATCH2': patching complete
-% insmod test_modules/$MOD_LIVEPATCH3.ko
-livepatch: enabling patch '$MOD_LIVEPATCH3'
-livepatch: '$MOD_LIVEPATCH3': initializing patching transition
-livepatch: '$MOD_LIVEPATCH3': starting patching transition
-livepatch: '$MOD_LIVEPATCH3': completing patching transition
-livepatch: '$MOD_LIVEPATCH3': patching complete
-% echo 0 > $SYSFS_KLP_DIR/$MOD_LIVEPATCH2/enabled
-livepatch: '$MOD_LIVEPATCH2': initializing unpatching transition
-$MOD_LIVEPATCH2: pre_unpatch_callback: vmlinux
-livepatch: '$MOD_LIVEPATCH2': starting unpatching transition
-livepatch: '$MOD_LIVEPATCH2': completing unpatching transition
-$MOD_LIVEPATCH2: post_unpatch_callback: vmlinux
-livepatch: '$MOD_LIVEPATCH2': unpatching complete
-% rmmod $MOD_LIVEPATCH2
-% echo 0 > $SYSFS_KLP_DIR/$MOD_LIVEPATCH3/enabled
-livepatch: '$MOD_LIVEPATCH3': initializing unpatching transition
-livepatch: '$MOD_LIVEPATCH3': starting unpatching transition
-livepatch: '$MOD_LIVEPATCH3': completing unpatching transition
-livepatch: '$MOD_LIVEPATCH3': unpatching complete
-% rmmod $MOD_LIVEPATCH3
-% echo 0 > $SYSFS_KLP_DIR/$MOD_LIVEPATCH/enabled
-livepatch: '$MOD_LIVEPATCH': initializing unpatching transition
-livepatch: '$MOD_LIVEPATCH': starting unpatching transition
-livepatch: '$MOD_LIVEPATCH': completing unpatching transition
-livepatch: '$MOD_LIVEPATCH': unpatching complete
-% rmmod $MOD_LIVEPATCH"
-
exit 0
--
2.52.0
^ permalink raw reply related
* [PATCH v3 3/7] livepatch: Support scoped atomic replace using replace_set
From: Yafang Shao @ 2026-06-07 13:16 UTC (permalink / raw)
To: jpoimboe, jikos, mbenes, pmladek, joe.lawrence, song
Cc: live-patching, Yafang Shao
In-Reply-To: <20260607131659.29281-1-laoar.shao@gmail.com>
Convert the replace attribute from a boolean to a u32 to function as a
"replace set." A newly loaded livepatch will now atomically replace any
existing patch belonging to the same set. There can only ever be one active
livepatch for a given replace_set number.
This change currently supports function replacement only. Livepatches that
belong to different replace sets cannot modify the same function. If a new
livepatch attempts to modify a function already modified by an older
livepatch from a different replace_set, the loading of the new livepatch
will be refused.
Similarly, for the KLP state, livepatches belonging to different replace
sets cannot use the same state ID. The system will refuse to load a new
livepatch if it uses a state ID already in use by an older livepatch from
a different replace_set.
For the KLP shadow variable mechanism, developers must assign unique shadow
IDs to livepatches that belong to different replace sets.
Support for replace_set compatibility with KLP state and shadow variables
will be implemented after Petr's KLP state transfer work is completed [0].
Other user-visible changes include:
- The non-replace model is now deprecated
- /sys/kernel/livepatch/livepatch_XXX/replace attribute is replaced by
/sys/kernel/livepatch/livepatch_XXX/replace_set
Link: https://github.com/pmladek/linux/tree/klp-state-transfer-v1-iter12 [0]
Suggested-by: Song Liu <song@kernel.org>
Suggested-by: Petr Mladek <pmladek@suse.com>
Signed-off-by: Yafang Shao <laoar.shao@gmail.com>
---
.../ABI/testing/sysfs-kernel-livepatch | 5 +-
.../livepatch/cumulative-patches.rst | 23 ++++++---
Documentation/livepatch/livepatch.rst | 21 ++++----
include/linux/livepatch.h | 5 +-
kernel/livepatch/core.c | 24 +++++----
kernel/livepatch/state.c | 51 +++++++++++++++----
kernel/livepatch/transition.c | 11 ++--
scripts/livepatch/init.c | 6 +--
scripts/livepatch/klp-build | 16 +++---
9 files changed, 104 insertions(+), 58 deletions(-)
diff --git a/Documentation/ABI/testing/sysfs-kernel-livepatch b/Documentation/ABI/testing/sysfs-kernel-livepatch
index 3c3f36b32b57..6d75235a6a2e 100644
--- a/Documentation/ABI/testing/sysfs-kernel-livepatch
+++ b/Documentation/ABI/testing/sysfs-kernel-livepatch
@@ -47,13 +47,12 @@ Description:
disabled when the feature is used. See
Documentation/livepatch/livepatch.rst for more information.
-What: /sys/kernel/livepatch/<patch>/replace
+What: /sys/kernel/livepatch/<patch>/replace_set
Date: Jun 2024
KernelVersion: 6.11.0
Contact: live-patching@vger.kernel.org
Description:
- An attribute which indicates whether the patch supports
- atomic-replace.
+ An attribute to show the replace_set of this livepatch.
What: /sys/kernel/livepatch/<patch>/stack_order
Date: Jan 2025
diff --git a/Documentation/livepatch/cumulative-patches.rst b/Documentation/livepatch/cumulative-patches.rst
index 1931f318976a..0361adb12f6d 100644
--- a/Documentation/livepatch/cumulative-patches.rst
+++ b/Documentation/livepatch/cumulative-patches.rst
@@ -17,18 +17,20 @@ from all older livepatches and completely replace them in one transition.
Usage
-----
-The atomic replace can be enabled by setting "replace" flag in struct klp_patch,
-for example::
+The "replace_set" attribute in ``struct klp_patch`` acts as a **replace_set**,
+defining the scope of the replacement. By default, the replace_set is 0.
+
+For example::
static struct klp_patch patch = {
.mod = THIS_MODULE,
.objs = objs,
- .replace = true,
+ .replace_set = 0,
};
All processes are then migrated to use the code only from the new patch.
-Once the transition is finished, all older patches are automatically
-disabled.
+Once the transition is finished, all older patches with the same replace
+set are automatically disabled. Patches with different tags remain active.
Ftrace handlers are transparently removed from functions that are no
longer modified by the new cumulative patch.
@@ -62,9 +64,14 @@ Limitations:
------------
- Once the operation finishes, there is no straightforward way
- to reverse it and restore the replaced patches atomically.
-
- A good practice is to set .replace flag in any released livepatch.
+ to reverse it and restore the replaced patches (with the same set)
+ atomically.
+
+ A good practice is to use only one (default) "replace_set". It
+ makes sure that there always will be only one enabled livepatch
+ on the system. The consistency model will ensure a safe update
+ between two versions. It prevents potential problems with installing
+ two livepatches doing incompatible functional changes.
Then re-adding an older livepatch is equivalent to downgrading
to that patch. This is safe as long as the livepatches do _not_ do
extra modifications in (un)patching callbacks or in the module_init()
diff --git a/Documentation/livepatch/livepatch.rst b/Documentation/livepatch/livepatch.rst
index acb90164929e..221d8f1e91c4 100644
--- a/Documentation/livepatch/livepatch.rst
+++ b/Documentation/livepatch/livepatch.rst
@@ -347,15 +347,18 @@ to '0'.
5.3. Replacing
--------------
-All enabled patches might get replaced by a cumulative patch that
-has the .replace flag set.
-
-Once the new patch is enabled and the 'transition' finishes then
-all the functions (struct klp_func) associated with the replaced
-patches are removed from the corresponding struct klp_ops. Also
-the ftrace handler is unregistered and the struct klp_ops is
-freed when the related function is not modified by the new patch
-and func_stack list becomes empty.
+There always can be only one livepatch with a given "replace_set" number.
+They always replace each other.
+
+Once the transition is complete, all functions (``struct klp_func``)
+associated with the matching replaced patches are removed from the
+corresponding ``struct klp_ops``. If a function is no longer modified by
+the new patch and its ``func_stack`` list becomes empty, the ftrace
+handler is unregistered and the ``struct klp_ops`` is freed.
+
+Patches with a different replace set are not affected by this process
+and remain active. This allows for the independent management and
+stacking of multiple, non-conflicting livepatch sets.
See Documentation/livepatch/cumulative-patches.rst for more details.
diff --git a/include/linux/livepatch.h b/include/linux/livepatch.h
index ba9e3988c07c..171c08328299 100644
--- a/include/linux/livepatch.h
+++ b/include/linux/livepatch.h
@@ -123,7 +123,8 @@ struct klp_state {
* @mod: reference to the live patch module
* @objs: object entries for kernel objects to be patched
* @states: system states that can get modified
- * @replace: replace all actively used patches
+ * @replace_set:Livepatch using the same @replace_set will get atomically
+ * replaced.
* @list: list node for global list of actively used patches
* @kobj: kobject for sysfs resources
* @obj_list: dynamic list of the object entries
@@ -137,7 +138,7 @@ struct klp_patch {
struct module *mod;
struct klp_object *objs;
struct klp_state *states;
- bool replace;
+ unsigned int replace_set;
/* internal */
struct list_head list;
diff --git a/kernel/livepatch/core.c b/kernel/livepatch/core.c
index 6e15ae96a0a7..361999e5ce3d 100644
--- a/kernel/livepatch/core.c
+++ b/kernel/livepatch/core.c
@@ -329,7 +329,7 @@ int klp_apply_section_relocs(struct module *pmod, Elf_Shdr *sechdrs,
* /sys/kernel/livepatch/<patch>/enabled
* /sys/kernel/livepatch/<patch>/transition
* /sys/kernel/livepatch/<patch>/force
- * /sys/kernel/livepatch/<patch>/replace
+ * /sys/kernel/livepatch/<patch>/replace_set
* /sys/kernel/livepatch/<patch>/stack_order
* /sys/kernel/livepatch/<patch>/<object>
* /sys/kernel/livepatch/<patch>/<object>/patched
@@ -427,13 +427,13 @@ static ssize_t force_store(struct kobject *kobj, struct kobj_attribute *attr,
return count;
}
-static ssize_t replace_show(struct kobject *kobj,
+static ssize_t replace_set_show(struct kobject *kobj,
struct kobj_attribute *attr, char *buf)
{
struct klp_patch *patch;
patch = container_of(kobj, struct klp_patch, kobj);
- return sysfs_emit(buf, "%d\n", patch->replace);
+ return sysfs_emit(buf, "%u\n", patch->replace_set);
}
static ssize_t stack_order_show(struct kobject *kobj,
@@ -460,13 +460,13 @@ static ssize_t stack_order_show(struct kobject *kobj,
static struct kobj_attribute enabled_kobj_attr = __ATTR_RW(enabled);
static struct kobj_attribute transition_kobj_attr = __ATTR_RO(transition);
static struct kobj_attribute force_kobj_attr = __ATTR_WO(force);
-static struct kobj_attribute replace_kobj_attr = __ATTR_RO(replace);
+static struct kobj_attribute replace_set_kobj_attr = __ATTR_RO(replace_set);
static struct kobj_attribute stack_order_kobj_attr = __ATTR_RO(stack_order);
static struct attribute *klp_patch_attrs[] = {
&enabled_kobj_attr.attr,
&transition_kobj_attr.attr,
&force_kobj_attr.attr,
- &replace_kobj_attr.attr,
+ &replace_set_kobj_attr.attr,
&stack_order_kobj_attr.attr,
NULL
};
@@ -600,6 +600,8 @@ static int klp_add_nops(struct klp_patch *patch)
klp_for_each_object(old_patch, old_obj) {
int err;
+ if (patch->replace_set != old_patch->replace_set)
+ continue;
err = klp_add_object_nops(patch, old_obj);
if (err)
return err;
@@ -772,6 +774,8 @@ void klp_free_replaced_patches_async(struct klp_patch *new_patch)
klp_for_each_patch_safe(old_patch, tmp_patch) {
if (old_patch == new_patch)
return;
+ if (old_patch->replace_set != new_patch->replace_set)
+ continue;
klp_free_patch_async(old_patch);
}
}
@@ -967,11 +971,9 @@ static int klp_init_patch(struct klp_patch *patch)
if (ret)
return ret;
- if (patch->replace) {
- ret = klp_add_nops(patch);
- if (ret)
- return ret;
- }
+ ret = klp_add_nops(patch);
+ if (ret)
+ return ret;
klp_for_each_object(patch, obj) {
ret = klp_init_object(patch, obj);
@@ -1174,6 +1176,8 @@ void klp_unpatch_replaced_patches(struct klp_patch *new_patch)
if (old_patch == new_patch)
return;
+ if (old_patch->replace_set != new_patch->replace_set)
+ continue;
old_patch->enabled = false;
klp_unpatch_objects(old_patch);
}
diff --git a/kernel/livepatch/state.c b/kernel/livepatch/state.c
index 2565d039ade0..a1ac46637336 100644
--- a/kernel/livepatch/state.c
+++ b/kernel/livepatch/state.c
@@ -85,34 +85,65 @@ EXPORT_SYMBOL_GPL(klp_get_prev_state);
/* Check if the patch is able to deal with the existing system state. */
static bool klp_is_state_compatible(struct klp_patch *patch,
+ struct klp_patch *old_patch,
struct klp_state *old_state)
{
struct klp_state *state;
state = klp_get_state(patch, old_state->id);
+ if (patch->replace_set == old_patch->replace_set) {
+ /*
+ * If the new livepatch shares a state set with an existing
+ * one, it must maintain compatibility with all states
+ * modified by the old patch.
+ */
+ if (!state)
+ return false;
+ return state->version >= old_state->version;
- /* A cumulative livepatch must handle all already modified states. */
- if (!state)
- return !patch->replace;
+ }
- return state->version >= old_state->version;
+ /*
+ * Two livepatches with a different "replace_set" must _not_ use
+ * the same "state->id.
+ */
+ return !state;
}
-/*
- * Check that the new livepatch will not break the existing system states.
- * Cumulative patches must handle all already modified states.
- * Non-cumulative patches can touch already modified states.
- */
+/* Check that the new livepatch will not break the existing system states. */
bool klp_is_patch_compatible(struct klp_patch *patch)
{
+ struct klp_object *obj, *old_obj;
struct klp_patch *old_patch;
struct klp_state *old_state;
+ struct klp_func *func;
klp_for_each_patch(old_patch) {
klp_for_each_state(old_patch, old_state) {
- if (!klp_is_state_compatible(patch, old_state))
+ if (!klp_is_state_compatible(patch, old_patch, old_state))
return false;
}
+
+ if (old_patch->replace_set == patch->replace_set)
+ continue;
+
+ /*
+ * Refuse loading a livepatch which would want to modify a
+ * function which is already livepatched with the livepatch
+ * with another "replace_set".
+ */
+ klp_for_each_object_static(patch, obj) {
+ klp_for_each_object(old_patch, old_obj) {
+ if (!!obj->name != !!old_obj->name)
+ continue;
+ if (obj->name && strcmp(obj->name, old_obj->name))
+ continue;
+ klp_for_each_func_static(obj, func) {
+ if (klp_find_func(old_obj, func))
+ return false;
+ }
+ }
+ }
}
return true;
diff --git a/kernel/livepatch/transition.c b/kernel/livepatch/transition.c
index 2351a19ac2a9..8b756dbaa57e 100644
--- a/kernel/livepatch/transition.c
+++ b/kernel/livepatch/transition.c
@@ -89,7 +89,7 @@ static void klp_complete_transition(void)
klp_transition_patch->mod->name,
klp_target_state == KLP_TRANSITION_PATCHED ? "patching" : "unpatching");
- if (klp_transition_patch->replace && klp_target_state == KLP_TRANSITION_PATCHED) {
+ if (klp_target_state == KLP_TRANSITION_PATCHED) {
klp_unpatch_replaced_patches(klp_transition_patch);
klp_discard_nops(klp_transition_patch);
}
@@ -498,7 +498,7 @@ void klp_try_complete_transition(void)
*/
if (!patch->enabled)
klp_free_patch_async(patch);
- else if (patch->replace)
+ else
klp_free_replaced_patches_async(patch);
}
@@ -720,11 +720,12 @@ void klp_force_transition(void)
klp_update_patch_state(idle_task(cpu));
/* Set forced flag for patches being removed. */
- if (klp_target_state == KLP_TRANSITION_UNPATCHED)
+ if (klp_target_state == KLP_TRANSITION_UNPATCHED) {
klp_transition_patch->forced = true;
- else if (klp_transition_patch->replace) {
+ } else {
klp_for_each_patch(patch) {
- if (patch != klp_transition_patch)
+ if (patch != klp_transition_patch &&
+ patch->replace_set == klp_transition_patch->replace_set)
patch->forced = true;
}
}
diff --git a/scripts/livepatch/init.c b/scripts/livepatch/init.c
index f14d8c8fb35f..df4fbcaf4c12 100644
--- a/scripts/livepatch/init.c
+++ b/scripts/livepatch/init.c
@@ -72,10 +72,10 @@ static int __init livepatch_mod_init(void)
/* TODO patch->states */
-#ifdef KLP_NO_REPLACE
- patch->replace = false;
+#ifdef KLP_REPLACE_SET
+ patch->replace_set = KLP_REPLACE_SET;
#else
- patch->replace = true;
+ patch->replace_set = 0;
#endif
return klp_enable_patch(patch);
diff --git a/scripts/livepatch/klp-build b/scripts/livepatch/klp-build
index 0ad7e6631314..1c6c1c4f3190 100755
--- a/scripts/livepatch/klp-build
+++ b/scripts/livepatch/klp-build
@@ -22,7 +22,7 @@ shopt -s lastpipe
unset DEBUG_CLONE DIFF_CHECKSUM SKIP_CLEANUP XTRACE
-REPLACE=1
+REPLACE_SET=0
SHORT_CIRCUIT=0
JOBS="$(getconf _NPROCESSORS_ONLN)"
VERBOSE="-s"
@@ -134,7 +134,7 @@ Options:
-f, --show-first-changed Show address of first changed instruction
-j, --jobs=<jobs> Build jobs to run simultaneously [default: $JOBS]
-o, --output=<file.ko> Output file [default: livepatch-<patch-name>.ko]
- --no-replace Disable livepatch atomic replace
+ -s, --replace-set=<set> Set the atomic replace set for this livepatch
-v, --verbose Pass V=1 to kernel/module builds
Advanced Options:
@@ -159,8 +159,8 @@ process_args() {
local long
local args
- short="hfj:o:vdS:T"
- long="help,show-first-changed,jobs:,output:,no-replace,verbose,debug,short-circuit:,keep-tmp"
+ short="hfj:o:s:vdS:T"
+ long="help,show-first-changed,jobs:,output:,replace-set:,verbose,debug,short-circuit:,keep-tmp"
args=$(getopt --options "$short" --longoptions "$long" -- "$@") || {
echo; usage; exit
@@ -189,9 +189,9 @@ process_args() {
NAME="$(module_name_string "$NAME")"
shift 2
;;
- --no-replace)
- REPLACE=0
- shift
+ -s | --replace-set)
+ REPLACE_SET="$2"
+ shift 2
;;
-v | --verbose)
VERBOSE="V=1"
@@ -777,7 +777,7 @@ build_patch_module() {
cflags=("-ffunction-sections")
cflags+=("-fdata-sections")
- [[ $REPLACE -eq 0 ]] && cflags+=("-DKLP_NO_REPLACE")
+ cflags+=("-DKLP_REPLACE_SET=$REPLACE_SET")
cmd=("make")
cmd+=("$VERBOSE")
--
2.52.0
^ permalink raw reply related
* [PATCH v3 2/7] livepatch: Move klp_find_func() into core.h
From: Yafang Shao @ 2026-06-07 13:16 UTC (permalink / raw)
To: jpoimboe, jikos, mbenes, pmladek, joe.lawrence, song
Cc: live-patching, Yafang Shao
In-Reply-To: <20260607131659.29281-1-laoar.shao@gmail.com>
This function will be used in other source file in the following patch.
Signed-off-by: Yafang Shao <laoar.shao@gmail.com>
---
kernel/livepatch/core.c | 21 ---------------------
kernel/livepatch/core.h | 21 +++++++++++++++++++++
2 files changed, 21 insertions(+), 21 deletions(-)
diff --git a/kernel/livepatch/core.c b/kernel/livepatch/core.c
index 95117f7a5b00..6e15ae96a0a7 100644
--- a/kernel/livepatch/core.c
+++ b/kernel/livepatch/core.c
@@ -82,27 +82,6 @@ static bool klp_initialized(void)
return !!klp_root_kobj;
}
-static struct klp_func *klp_find_func(struct klp_object *obj,
- struct klp_func *old_func)
-{
- struct klp_func *func;
-
- klp_for_each_func(obj, func) {
- /*
- * Besides identical old_sympos, also consider old_sympos
- * of 0 and 1 are identical.
- */
- if ((func->old_name && strcmp(old_func->old_name, func->old_name) == 0) &&
- ((old_func->old_sympos == func->old_sympos) ||
- (old_func->old_sympos == 0 && func->old_sympos == 1) ||
- (old_func->old_sympos == 1 && func->old_sympos == 0))) {
- return func;
- }
- }
-
- return NULL;
-}
-
static struct klp_object *klp_find_object(struct klp_patch *patch,
struct klp_object *old_obj)
{
diff --git a/kernel/livepatch/core.h b/kernel/livepatch/core.h
index 38209c7361b6..8f01eae875eb 100644
--- a/kernel/livepatch/core.h
+++ b/kernel/livepatch/core.h
@@ -56,4 +56,25 @@ static inline void klp_post_unpatch_callback(struct klp_object *obj)
obj->callbacks.post_unpatch_enabled = false;
}
+static inline struct klp_func *klp_find_func(struct klp_object *obj,
+ struct klp_func *old_func)
+{
+ struct klp_func *func;
+
+ klp_for_each_func(obj, func) {
+ /*
+ * Besides identical old_sympos, also consider old_sympos
+ * of 0 and 1 are identical.
+ */
+ if ((func->old_name && strcmp(old_func->old_name, func->old_name) == 0) &&
+ ((old_func->old_sympos == func->old_sympos) ||
+ (old_func->old_sympos == 0 && func->old_sympos == 1) ||
+ (old_func->old_sympos == 1 && func->old_sympos == 0))) {
+ return func;
+ }
+ }
+
+ return NULL;
+}
+
#endif /* _LIVEPATCH_CORE_H */
--
2.52.0
^ permalink raw reply related
* [PATCH v3 1/7] livepatch: Fix NULL pointer dereference in klp_find_func()
From: Yafang Shao @ 2026-06-07 13:16 UTC (permalink / raw)
To: jpoimboe, jikos, mbenes, pmladek, joe.lawrence, song
Cc: live-patching, Yafang Shao, sashiko-bot
In-Reply-To: <20260607131659.29281-1-laoar.shao@gmail.com>
If a newly loaded livepatch provides a function entry with a NULL old_name,
func->old_name will be NULL when evaluated in strcmp():
klp_init_patch()
klp_add_nops()
klp_find_func()
strcmp(old_func->old_name, func->old_name)
Reported-by: sashiko-bot <sashiko-bot@kernel.org>
Closes: https://lore.kernel.org/live-patching/20260529040130.95A9C1F00893@smtp.kernel.org/
Signed-off-by: Yafang Shao <laoar.shao@gmail.com>
---
kernel/livepatch/core.c | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/kernel/livepatch/core.c b/kernel/livepatch/core.c
index 28d15ba58a26..95117f7a5b00 100644
--- a/kernel/livepatch/core.c
+++ b/kernel/livepatch/core.c
@@ -92,7 +92,7 @@ static struct klp_func *klp_find_func(struct klp_object *obj,
* Besides identical old_sympos, also consider old_sympos
* of 0 and 1 are identical.
*/
- if ((strcmp(old_func->old_name, func->old_name) == 0) &&
+ if ((func->old_name && strcmp(old_func->old_name, func->old_name) == 0) &&
((old_func->old_sympos == func->old_sympos) ||
(old_func->old_sympos == 0 && func->old_sympos == 1) ||
(old_func->old_sympos == 1 && func->old_sympos == 0))) {
--
2.52.0
^ permalink raw reply related
* [PATCH v3 0/7] livepatch: Introduce replace set support
From: Yafang Shao @ 2026-06-07 13:16 UTC (permalink / raw)
To: jpoimboe, jikos, mbenes, pmladek, joe.lawrence, song
Cc: live-patching, Yafang Shao
We previously proposed a BPF+livepatch method to enable rapid
experimentation with new kernel features without interrupting production
workloads:
https://lore.kernel.org/live-patching/20260402092607.96430-1-laoar.shao@gmail.com/
In the resulting discussion, Song and Petr suggested adding a "replace set"
to support scenarios where specific livepatches can be selectively replaced
or skipped.
This patch introduces 'replace_set' to provide finer-grained control over
livepatch management. The core rules and behaviors of a replace_set are
defined as follows:
- Livepatches sharing the same replace_set can mutually replace each
other.
- Only one livepatch within a given replace_set can be active at a time.
- Livepatches belonging to different replace_sets can coexist on the
system.
- Livepatches in different replace_sets are prohibited from modifying the
same function.
- Livepatches in different replace_sets cannot use the same state ID.
Additionally, this design deprecates the traditional non-atomic-replace
model. Previously, setting 'replace' to 0 was the only way to keep
certain livepatches persistent on the system, forcing developers to
disable atomic replacement entirely. With the introduction of replace_set,
developers now have a selective option to keep specific livepatches
persistent while maintaining atomic replacement capabilities elsewhere.
At present, KLP state, shadow variables, and callbacks are not integrated
with the new replace_set mechanism in this patchset. Support for these
features is deferred until Petr's klp-state-transfer infrastructure is
completed and merged:
https://github.com/pmladek/linux/tree/klp-state-transfer-v1-iter12
v2->v3:
- Address the feedback from Sachiko AI
- Fix the pre-existing NULL pointer dereference issue
- Move klp_find_func into core.h
- Don't deprecate stack_order completely
v2: https://lore.kernel.org/live-patching/20260529034542.68766-1-laoar.shao@gmail.com/
v1->v2:
- Incorporate feedback from Petr:
- Initialize replace_set to 0 by default
- Improve documentation
- Enforce that livepatches in different replace_sets cannot use the same
state->id.
- Enforce that livepatches in different replace_sets cannot modify the
same function.
- Ensure consistent capitalization and naming usage of KLP_REPLACE_SET.
- Incorporate feedback from Sachiko AI:
- Skip the klp_transition patch during klp_force_transition().
v1 (RFC): https://lore.kernel.org/live-patching/20260513143321.26185-1-laoar.shao@gmail.com/
Yafang Shao (7):
livepatch: Fix NULL pointer dereference in klp_find_func()
livepatch: Move klp_find_func() into core.h
livepatch: Support scoped atomic replace using replace_set
livepatch: Deprecate stack_order
selftests/livepatch: Update tests for replace_set
selftests/livepatch: Add test for state ID conflict across
replace_sets
selftests/livepatch: Add test for function conflict across
replace_sets
.../ABI/removed/sysfs-kernel-livepatch | 9 +
.../ABI/testing/sysfs-kernel-livepatch | 14 +-
.../livepatch/cumulative-patches.rst | 23 ++-
Documentation/livepatch/livepatch.rst | 21 ++-
include/linux/livepatch.h | 5 +-
kernel/livepatch/core.c | 65 ++-----
kernel/livepatch/core.h | 21 +++
kernel/livepatch/state.c | 51 ++++--
kernel/livepatch/transition.c | 11 +-
scripts/livepatch/init.c | 6 +-
scripts/livepatch/klp-build | 16 +-
.../selftests/livepatch/test-callbacks.sh | 33 ++--
.../selftests/livepatch/test-livepatch.sh | 159 +++++++----------
.../testing/selftests/livepatch/test-state.sh | 34 ++++
.../testing/selftests/livepatch/test-sysfs.sh | 91 ++--------
.../selftests/livepatch/test_modules/Makefile | 2 +
.../test_modules/test_klp_atomic_replace.c | 10 +-
.../test_modules/test_klp_atomic_replace2.c | 55 ++++++
.../test_modules/test_klp_callbacks_demo.c | 6 +
.../test_modules/test_klp_callbacks_demo2.c | 10 +-
.../test_modules/test_klp_livepatch.c | 6 +
.../livepatch/test_modules/test_klp_state.c | 4 +-
.../livepatch/test_modules/test_klp_state2.c | 4 +-
.../livepatch/test_modules/test_klp_state4.c | 163 ++++++++++++++++++
24 files changed, 507 insertions(+), 312 deletions(-)
create mode 100644 Documentation/ABI/removed/sysfs-kernel-livepatch
create mode 100644 tools/testing/selftests/livepatch/test_modules/test_klp_atomic_replace2.c
create mode 100644 tools/testing/selftests/livepatch/test_modules/test_klp_state4.c
--
2.52.0
^ permalink raw reply
* Re: [PATCH 4/4] selftests: livepatch: Add information about minimum kernel support
From: Petr Mladek @ 2026-06-05 15:14 UTC (permalink / raw)
To: Marcos Paulo de Souza
Cc: Josh Poimboeuf, Jiri Kosina, Miroslav Benes, Joe Lawrence,
Shuah Khan, live-patching, linux-kselftest, linux-kernel, marcos
In-Reply-To: <20260524-livepatch-unload-on-fail-v1-4-7465de7f741d@suse.com>
On Sun 2026-05-24 20:50:33, Marcos Paulo de Souza wrote:
> The current livepatch selftests are compatible with kernel 4.12, so add
> a note about it for future developers willing to contribute with new
> tests.
>
> Signed-off-by: Marcos Paulo de Souza <mpdesouza@suse.com>
> ---
> tools/testing/selftests/livepatch/README | 3 +++
> 1 file changed, 3 insertions(+)
>
> diff --git a/tools/testing/selftests/livepatch/README b/tools/testing/selftests/livepatch/README
> index d2035dd64a2b..293f4730b927 100644
> --- a/tools/testing/selftests/livepatch/README
> +++ b/tools/testing/selftests/livepatch/README
> @@ -54,3 +54,6 @@ check_result(). The latter function greps the kernel's ring buffer for
> those strings for result comparison. Other utility functions include
> general module loading and livepatch loading helpers (waiting for patch
> transitions, sysfs entries, etc.)
> +
> +All new tests and test modules MUST be compatible with kernel version 4.12 and
> +later, including current upstream kernels.
I do not have a good feeling about this. One thing is to accept fixes/workarounds
for the backward compatibility. But forcing contributors to keep the
compatibility might be too demanding.
I might be wrong. But I think that you would need a pretty old
userspace to run 4.12 kernel. And most people could not get it easily.
Honestly, I though more about this effort and I am not sure how
complicated it would be in the long term.
I know about two pending patchsets which are breaking the backward
compatibility:
+ Better integration of callbacks and shadow variables with the states API [1]
+ Introduce replace set support [2]
Especially the 1st one is a pain. It is 19th patches and most of them
are fixing/reworking the selftests. I think that it was the reason
why nobody reviewed it yet. And it might be even worse if we wanted
to make it backward compatible.
[1] https://lore.kernel.org/all/20250115082431.5550-1-pmladek@suse.com/
[2] https://lore.kernel.org/all/20260529034542.68766-1-laoar.shao@gmail.com/
Best Regards,
Petr
^ permalink raw reply
* Re: [PATCH 3/4] selftests: livepatch: Adapt mod_target module to pass on 4.12 kernels
From: Petr Mladek @ 2026-06-05 14:39 UTC (permalink / raw)
To: Marcos Paulo de Souza
Cc: sashiko-reviews, Miroslav Benes, Shuah Khan, Josh Poimboeuf,
Joe Lawrence, Jiri Kosina, live-patching, linux-kselftest,
linux-kernel
In-Reply-To: <0f269520407c8cfc554059ffcba45ef3220ecfea.camel@suse.com>
On Fri 2026-06-05 11:05:32, Marcos Paulo de Souza wrote:
> On Mon, 2026-05-25 at 00:24 +0000, sashiko-bot@kernel.org wrote:
> > Thank you for your contribution! Sashiko AI review found 1 potential
> > issue(s) to consider:
> > - [Medium] The `test_klp_mod_target_ops` structure omits the
> > logically required `.set` callback, exposing the kernel to a NULL
> > pointer dereference during module load.
> > --
> >
> > commit 532c1b8c7438f534fd4e3440716ede6cf83efe1c
> > Author: Marcos Paulo de Souza <mpdesouza@suse.com>
> >
> > selftests: livepatch: Adapt mod_target module to pass on 4.12 kernels
> >
> > Use the stable module_param_cb API instead of proc_fs for exposing
> > module
> > state. This approach is compatible with kernel 4.12 and later.
> >
> > > diff --git
> > > a/tools/testing/selftests/livepatch/test_modules/test_klp_mod_targe
> > > t.c
> > > b/tools/testing/selftests/livepatch/test_modules/test_klp_mod_targe
> > > t.c
> > > index 9643984d24022..1382a266825fb 100644
> > > ---
> > > a/tools/testing/selftests/livepatch/test_modules/test_klp_mod_targe
> > > t.c
> > > +++
> > > b/tools/testing/selftests/livepatch/test_modules/test_klp_mod_targe
> > > t.c
> > > @@ -5,31 +5,29 @@
> > [ ... ]
> > > +static const struct kernel_param_ops test_klp_mod_target_ops = {
> > > + .get = test_klp_mod_target_get,
> > > +};
> > > +
> > > +module_param_cb(klp_mod_arg, &test_klp_mod_target_ops, NULL,
> > > 0444);
> >
> > [Severity: Medium]
> > Does this code need a .set callback in the kernel_param_ops
> > structure?
> >
> > When a module parameter is passed during module load, the kernel
> > parses the
> > arguments using parse_args(), which invokes parse_one() in
> > kernel/params.c.
> > Because parse_one() unconditionally executes the .set callback
> > without
> > verifying if it is actually defined, omitting it here leaves the
> > callback
> > implicitly initialized to NULL.
> >
> > Could this lead to a NULL pointer dereference and a kernel panic if
> > someone
> > attempts to set this parameter during module load?
>
> Indeed! I'll send a v2 addressing the missing argument. Thanks Sachiko
> for spotting it :)
It should be perfectly fine to use NULL for the .set() callback.
It can't be called because of the given permissions 0444.
We should teach Sashiko about this ;-)
Best Regards,
Petr
^ permalink raw reply
page: next (older)
- recent:[subjects (threaded)|topics (new)|topics (active)]
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox