* [PATCH v10 1/9] selftests/livepatch: Clarify test module file names
2026-09-30 3:03 [PATCH v10 0/9] livepatch: Add support for scoped atomic replace Yafang Shao
@ 2026-09-30 3:03 ` Yafang Shao
2026-10-01 15:27 ` Petr Mladek
2026-09-30 3:03 ` [PATCH v10 2/9] selftests/livepatch: Adapt atomic replace tests to provides/obsoletes Yafang Shao
` (7 subsequent siblings)
8 siblings, 1 reply; 24+ messages in thread
From: Yafang Shao @ 2026-09-30 3:03 UTC (permalink / raw)
To: jpoimboe, jikos, mbenes, pmladek, joe.lawrence, song
Cc: live-patching, Yafang Shao
Rename the test module files to be more descriptive of what they
actually test, as suggested by Petr.
Suggested-by: Petr Mladek <pmladek@suse.com>
Signed-off-by: Yafang Shao <laoar.shao@gmail.com>
Acked-by: Song Liu <song@kernel.org>
Acked-by: Miroslav Benes <mbenes@suse.cz>
---
tools/testing/selftests/livepatch/test-ftrace.sh | 2 +-
tools/testing/selftests/livepatch/test-kprobe.sh | 12 ++++++------
tools/testing/selftests/livepatch/test-livepatch.sh | 6 +++---
tools/testing/selftests/livepatch/test-syscall.sh | 2 +-
tools/testing/selftests/livepatch/test-sysfs.sh | 6 +++---
.../selftests/livepatch/test_modules/Makefile | 8 ++++----
.../{test_klp_kprobe.c => test_klp_cmdline_kp.c} | 10 +++++-----
.../{test_klp_livepatch.c => test_klp_cmdline_lp.c} | 10 +++++-----
...st_klp_atomic_replace.c => test_klp_meminfo_lp.c} | 10 +++++-----
...{test_klp_syscall.c => test_klp_syscall_getpid.c} | 4 ++--
10 files changed, 35 insertions(+), 35 deletions(-)
rename tools/testing/selftests/livepatch/test_modules/{test_klp_kprobe.c => test_klp_cmdline_kp.c} (78%)
rename tools/testing/selftests/livepatch/test_modules/{test_klp_livepatch.c => test_klp_cmdline_lp.c} (79%)
rename tools/testing/selftests/livepatch/test_modules/{test_klp_atomic_replace.c => test_klp_meminfo_lp.c} (82%)
rename tools/testing/selftests/livepatch/test_modules/{test_klp_syscall.c => test_klp_syscall_getpid.c} (95%)
diff --git a/tools/testing/selftests/livepatch/test-ftrace.sh b/tools/testing/selftests/livepatch/test-ftrace.sh
index d2c3dea63104..90e8fcc4d065 100755
--- a/tools/testing/selftests/livepatch/test-ftrace.sh
+++ b/tools/testing/selftests/livepatch/test-ftrace.sh
@@ -4,7 +4,7 @@
. $(dirname $0)/functions.sh
-MOD_LIVEPATCH=test_klp_livepatch
+MOD_LIVEPATCH=test_klp_cmdline_lp
setup_config
diff --git a/tools/testing/selftests/livepatch/test-kprobe.sh b/tools/testing/selftests/livepatch/test-kprobe.sh
index 7ced4082cff3..81ab6d4760aa 100755
--- a/tools/testing/selftests/livepatch/test-kprobe.sh
+++ b/tools/testing/selftests/livepatch/test-kprobe.sh
@@ -7,8 +7,8 @@
grep -q kprobe_ftrace_ops /proc/kallsyms || skip "test-kprobe requires CONFIG_KPROBES_ON_FTRACE"
-MOD_LIVEPATCH=test_klp_livepatch
-MOD_KPROBE=test_klp_kprobe
+MOD_LIVEPATCH=test_klp_cmdline_lp
+MOD_KPROBE=test_klp_cmdline_kp
setup_config
@@ -24,7 +24,7 @@ load_mod $MOD_KPROBE has_post_handler=y
load_failing_mod $MOD_LIVEPATCH
unload_mod $MOD_KPROBE
-check_result "% insmod test_modules/test_klp_kprobe.ko has_post_handler=y
+check_result "% insmod test_modules/$MOD_KPROBE.ko has_post_handler=y
% insmod test_modules/$MOD_LIVEPATCH.ko
livepatch: enabling patch '$MOD_LIVEPATCH'
livepatch: '$MOD_LIVEPATCH': initializing patching transition
@@ -35,7 +35,7 @@ livepatch: '$MOD_LIVEPATCH': canceling patching transition, going to unpatch
livepatch: '$MOD_LIVEPATCH': completing unpatching transition
livepatch: '$MOD_LIVEPATCH': unpatching complete
insmod: ERROR: could not insert module test_modules/$MOD_LIVEPATCH.ko: Device or resource busy
-% rmmod test_klp_kprobe"
+% rmmod $MOD_KPROBE"
start_test "livepatch interaction with kprobed function without post_handler"
@@ -46,14 +46,14 @@ unload_mod $MOD_KPROBE
disable_lp $MOD_LIVEPATCH
unload_lp $MOD_LIVEPATCH
-check_result "% insmod test_modules/test_klp_kprobe.ko has_post_handler=n
+check_result "% insmod test_modules/$MOD_KPROBE.ko has_post_handler=n
% 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
-% rmmod test_klp_kprobe
+% rmmod $MOD_KPROBE
% echo 0 > /sys/kernel/livepatch/$MOD_LIVEPATCH/enabled
livepatch: '$MOD_LIVEPATCH': initializing unpatching transition
livepatch: '$MOD_LIVEPATCH': starting unpatching transition
diff --git a/tools/testing/selftests/livepatch/test-livepatch.sh b/tools/testing/selftests/livepatch/test-livepatch.sh
index c44c5341a2f1..5380b5f174d4 100755
--- a/tools/testing/selftests/livepatch/test-livepatch.sh
+++ b/tools/testing/selftests/livepatch/test-livepatch.sh
@@ -4,10 +4,10 @@
. $(dirname $0)/functions.sh
-MOD_LIVEPATCH1=test_klp_livepatch
-MOD_LIVEPATCH2=test_klp_syscall
+MOD_LIVEPATCH1=test_klp_cmdline_lp
+MOD_LIVEPATCH2=test_klp_syscall_getpid
MOD_LIVEPATCH3=test_klp_callbacks_demo
-MOD_REPLACE=test_klp_atomic_replace
+MOD_REPLACE=test_klp_meminfo_lp
MOD_TARGET=test_klp_mod_target
MOD_TARGET_PATCH=test_klp_mod_patch
diff --git a/tools/testing/selftests/livepatch/test-syscall.sh b/tools/testing/selftests/livepatch/test-syscall.sh
index 5f9344277b62..488207f25c50 100755
--- a/tools/testing/selftests/livepatch/test-syscall.sh
+++ b/tools/testing/selftests/livepatch/test-syscall.sh
@@ -5,7 +5,7 @@
. $(dirname $0)/functions.sh
-MOD_SYSCALL=test_klp_syscall
+MOD_SYSCALL=test_klp_syscall_getpid
setup_config
diff --git a/tools/testing/selftests/livepatch/test-sysfs.sh b/tools/testing/selftests/livepatch/test-sysfs.sh
index 3b16285c6e67..32d3af0097ac 100755
--- a/tools/testing/selftests/livepatch/test-sysfs.sh
+++ b/tools/testing/selftests/livepatch/test-sysfs.sh
@@ -4,9 +4,9 @@
. $(dirname $0)/functions.sh
-MOD_LIVEPATCH=test_klp_livepatch
+MOD_LIVEPATCH=test_klp_cmdline_lp
MOD_LIVEPATCH2=test_klp_callbacks_demo
-MOD_LIVEPATCH3=test_klp_syscall
+MOD_LIVEPATCH3=test_klp_syscall_getpid
HAS_PATCH_ATTR=0
HAS_REPLACE_ATTR=0
@@ -108,7 +108,7 @@ fi
if [[ "$HAS_REPLACE_ATTR" == "1" ]]; then
start_test "sysfs test replace enabled"
- MOD_ATOMIC_REPLACE=test_klp_atomic_replace
+ MOD_ATOMIC_REPLACE=test_klp_meminfo_lp
load_lp $MOD_ATOMIC_REPLACE replace=1
check_sysfs_rights "$MOD_ATOMIC_REPLACE" "replace" "-r--r--r--"
diff --git a/tools/testing/selftests/livepatch/test_modules/Makefile b/tools/testing/selftests/livepatch/test_modules/Makefile
index a13d398585dc..732e0003930b 100644
--- a/tools/testing/selftests/livepatch/test_modules/Makefile
+++ b/tools/testing/selftests/livepatch/test_modules/Makefile
@@ -1,20 +1,20 @@
TESTMODS_DIR := $(realpath $(dir $(abspath $(lastword $(MAKEFILE_LIST)))))
KDIR ?= /lib/modules/$(shell uname -r)/build
-obj-m += test_klp_atomic_replace.o \
+obj-m += test_klp_meminfo_lp.o \
test_klp_callbacks_busy.o \
test_klp_callbacks_demo.o \
test_klp_callbacks_demo2.o \
test_klp_callbacks_mod.o \
- test_klp_kprobe.o \
- test_klp_livepatch.o \
+ test_klp_cmdline_kp.o \
+ test_klp_cmdline_lp.o \
test_klp_mod_patch.o \
test_klp_mod_target.o \
test_klp_shadow_vars.o \
test_klp_state.o \
test_klp_state2.o \
test_klp_state3.o \
- test_klp_syscall.o
+ test_klp_syscall_getpid.o
# Ensure that KDIR exists, otherwise skip the compilation
modules:
diff --git a/tools/testing/selftests/livepatch/test_modules/test_klp_kprobe.c b/tools/testing/selftests/livepatch/test_modules/test_klp_cmdline_kp.c
similarity index 78%
rename from tools/testing/selftests/livepatch/test_modules/test_klp_kprobe.c
rename to tools/testing/selftests/livepatch/test_modules/test_klp_cmdline_kp.c
index 67a8d29012f6..28ef7ea33fc7 100644
--- a/tools/testing/selftests/livepatch/test_modules/test_klp_kprobe.c
+++ b/tools/testing/selftests/livepatch/test_modules/test_klp_cmdline_kp.c
@@ -18,7 +18,7 @@ static struct kprobe kp = {
.symbol_name = "cmdline_proc_show",
};
-static int __init kprobe_init(void)
+static int __init cmdline_kp_init(void)
{
if (has_post_handler)
kp.post_handler = post_handler;
@@ -26,13 +26,13 @@ static int __init kprobe_init(void)
return register_kprobe(&kp);
}
-static void __exit kprobe_exit(void)
+static void __exit cmdline_kp_exit(void)
{
unregister_kprobe(&kp);
}
-module_init(kprobe_init)
-module_exit(kprobe_exit)
+module_init(cmdline_kp_init)
+module_exit(cmdline_kp_exit)
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Michael Vetter <mvetter@suse.com>");
-MODULE_DESCRIPTION("Livepatch test: kprobe function");
+MODULE_DESCRIPTION("Livepatch test: cmdline kprobe");
diff --git a/tools/testing/selftests/livepatch/test_modules/test_klp_livepatch.c b/tools/testing/selftests/livepatch/test_modules/test_klp_cmdline_lp.c
similarity index 79%
rename from tools/testing/selftests/livepatch/test_modules/test_klp_livepatch.c
rename to tools/testing/selftests/livepatch/test_modules/test_klp_cmdline_lp.c
index aff08199de71..88c6aed925fb 100644
--- a/tools/testing/selftests/livepatch/test_modules/test_klp_livepatch.c
+++ b/tools/testing/selftests/livepatch/test_modules/test_klp_cmdline_lp.c
@@ -34,18 +34,18 @@ static struct klp_patch patch = {
.objs = objs,
};
-static int test_klp_livepatch_init(void)
+static int test_klp_cmdline_lp_init(void)
{
return klp_enable_patch(&patch);
}
-static void test_klp_livepatch_exit(void)
+static void test_klp_cmdline_lp_exit(void)
{
}
-module_init(test_klp_livepatch_init);
-module_exit(test_klp_livepatch_exit);
+module_init(test_klp_cmdline_lp_init);
+module_exit(test_klp_cmdline_lp_exit);
MODULE_LICENSE("GPL");
MODULE_INFO(livepatch, "Y");
MODULE_AUTHOR("Seth Jennings <sjenning@redhat.com>");
-MODULE_DESCRIPTION("Livepatch test: livepatch module");
+MODULE_DESCRIPTION("Livepatch test: cmdline livepatch");
diff --git a/tools/testing/selftests/livepatch/test_modules/test_klp_atomic_replace.c b/tools/testing/selftests/livepatch/test_modules/test_klp_meminfo_lp.c
similarity index 82%
rename from tools/testing/selftests/livepatch/test_modules/test_klp_atomic_replace.c
rename to tools/testing/selftests/livepatch/test_modules/test_klp_meminfo_lp.c
index 5af7093ca00c..f2477b217aab 100644
--- a/tools/testing/selftests/livepatch/test_modules/test_klp_atomic_replace.c
+++ b/tools/testing/selftests/livepatch/test_modules/test_klp_meminfo_lp.c
@@ -39,19 +39,19 @@ static struct klp_patch patch = {
/* set .replace in the init function below for demo purposes */
};
-static int test_klp_atomic_replace_init(void)
+static int test_klp_meminfo_lp_init(void)
{
patch.replace = replace;
return klp_enable_patch(&patch);
}
-static void test_klp_atomic_replace_exit(void)
+static void test_klp_meminfo_lp_exit(void)
{
}
-module_init(test_klp_atomic_replace_init);
-module_exit(test_klp_atomic_replace_exit);
+module_init(test_klp_meminfo_lp_init);
+module_exit(test_klp_meminfo_lp_exit);
MODULE_LICENSE("GPL");
MODULE_INFO(livepatch, "Y");
MODULE_AUTHOR("Joe Lawrence <joe.lawrence@redhat.com>");
-MODULE_DESCRIPTION("Livepatch test: atomic replace");
+MODULE_DESCRIPTION("Livepatch test: meminfo livepatch");
diff --git a/tools/testing/selftests/livepatch/test_modules/test_klp_syscall.c b/tools/testing/selftests/livepatch/test_modules/test_klp_syscall_getpid.c
similarity index 95%
rename from tools/testing/selftests/livepatch/test_modules/test_klp_syscall.c
rename to tools/testing/selftests/livepatch/test_modules/test_klp_syscall_getpid.c
index 08aacc0e14de..5da2bc7920cc 100644
--- a/tools/testing/selftests/livepatch/test_modules/test_klp_syscall.c
+++ b/tools/testing/selftests/livepatch/test_modules/test_klp_syscall_getpid.c
@@ -93,7 +93,7 @@ static int livepatch_init(void)
{
int ret;
- klp_kobj = kobject_create_and_add("test_klp_syscall", kernel_kobj);
+ klp_kobj = kobject_create_and_add("test_klp_syscall_getpid", kernel_kobj);
if (!klp_kobj)
return -ENOMEM;
@@ -128,4 +128,4 @@ MODULE_INFO(livepatch, "Y");
MODULE_AUTHOR("Libor Pechacek <lpechacek@suse.cz>");
MODULE_AUTHOR("Nicolai Stange <nstange@suse.de>");
MODULE_AUTHOR("Marcos Paulo de Souza <mpdesouza@suse.com>");
-MODULE_DESCRIPTION("Livepatch test: syscall transition");
+MODULE_DESCRIPTION("Livepatch test: syscall getpid transition");
--
2.52.0
^ permalink raw reply related [flat|nested] 24+ messages in thread* [PATCH v10 2/9] selftests/livepatch: Adapt atomic replace tests to provides/obsoletes
2026-09-30 3:03 [PATCH v10 0/9] livepatch: Add support for scoped atomic replace Yafang Shao
2026-09-30 3:03 ` [PATCH v10 1/9] selftests/livepatch: Clarify test module file names Yafang Shao
@ 2026-09-30 3:03 ` Yafang Shao
2026-10-01 15:57 ` Petr Mladek
2026-10-02 12:01 ` Miroslav Benes
2026-09-30 3:03 ` [PATCH v10 3/9] livepatch: Make klp_find_func() non static Yafang Shao
` (6 subsequent siblings)
8 siblings, 2 replies; 24+ messages in thread
From: Yafang Shao @ 2026-09-30 3:03 UTC (permalink / raw)
To: jpoimboe, jikos, mbenes, pmladek, joe.lawrence, song
Cc: live-patching, Yafang Shao
The legacy "replace" field in struct klp_patch will be replaced by the
provides/obsoletes mechanism. As a result, the atomic replace
selftests fail to build against kernels that only support
provides/obsoletes.
Adapt the selftests so that they build and run on both old and new
kernels. On kernels without the legacy "replace" support, the
replace-related test cases are skipped with a SKIP message instead
of being run.
Also introduce the CONFIG_KLP_HAS_PROVIDES compile-time marker in
kernel/livepatch/Kconfig. It defaults to n and will be set to y once
the provides/obsoletes support is implemented later in this series.
The provides/obsoletes-based selftests will be added later in this
series, after the legacy "replace" field has been substituted by the
new mechanism.
Suggested-by: Petr Mladek <pmladek@suse.com>
Signed-off-by: Yafang Shao <laoar.shao@gmail.com>
Acked-by: Song Liu <song@kernel.org>
---
kernel/livepatch/Kconfig | 14 +++
.../testing/selftests/livepatch/functions.sh | 42 +++++++-
.../selftests/livepatch/test-callbacks.sh | 43 +++++---
.../selftests/livepatch/test-kprobe.sh | 2 +-
.../selftests/livepatch/test-livepatch.sh | 102 ++++++++++--------
.../test_modules/test_klp_callbacks_demo2.c | 16 +++
.../test_modules/test_klp_meminfo_lp.c | 16 +++
.../livepatch/test_modules/test_klp_state.c | 7 ++
.../livepatch/test_modules/test_klp_state2.c | 7 ++
9 files changed, 186 insertions(+), 63 deletions(-)
diff --git a/kernel/livepatch/Kconfig b/kernel/livepatch/Kconfig
index 4c0a9c18d0b2..d1eb971abb1a 100644
--- a/kernel/livepatch/Kconfig
+++ b/kernel/livepatch/Kconfig
@@ -30,3 +30,17 @@ config KLP_BUILD
select OBJTOOL
help
Enable klp-build support
+
+config KLP_HAS_PROVIDES
+ def_bool n
+ help
+ Compile-time marker for the livepatch provides/obsoletes support.
+ It is always set when the kernel is built with the scoped atomic
+ replace model, i.e. struct klp_patch provides the "provides" and
+ "obsoletes" fields instead of the legacy "replace" flag.
+
+ The option itself does not enable or add any functionality; it
+ only serves as a compile-time marker so that out-of-tree modules
+ and tools (e.g. the livepatch kselftests) can detect kernels that
+ speak the provides/obsoletes interface and adapt to it with
+ #ifdef CONFIG_KLP_HAS_PROVIDES.
diff --git a/tools/testing/selftests/livepatch/functions.sh b/tools/testing/selftests/livepatch/functions.sh
index 8352c8d509a5..2458db8faa64 100644
--- a/tools/testing/selftests/livepatch/functions.sh
+++ b/tools/testing/selftests/livepatch/functions.sh
@@ -27,14 +27,26 @@ function log() {
echo "$1" > /dev/kmsg
}
-# skip(msg) - testing can't proceed
+# skip_exit(msg) - testing can't proceed
# msg - explanation
-function skip() {
+function skip_exit() {
log "SKIP: $1"
echo "SKIP: $1" >&2
exit $ksft_skip
}
+# skip(test, reason) - skip the test for a specific reason
+# test - a test case
+# reason - reason to skip it
+function skip() {
+ local test="$1"
+ local reason="$2"
+
+ echo -n "TEST: $test ... "
+ echo "SKIP ($reason)"
+ log "===== TEST: $test SKIPPED: $reason ====="
+}
+
# root test
function is_root() {
uid=$(id -u)
@@ -120,7 +132,7 @@ function set_ftrace_enabled() {
return
fi
- skip "failed to set kernel.ftrace_enabled = $1"
+ skip_exit "failed to set kernel.ftrace_enabled = $1"
fi
echo "livepatch: kernel.ftrace_enabled = $result" > /dev/kmsg
@@ -357,6 +369,30 @@ function does_sysfs_exist() {
[[ -f "$SYSFS_KLP_DIR/$mod/$attr" ]]
}
+# detect_provides_attr() - detect whether the running kernel supports the
+# livepatch "provides" attribute and set HAS_PROVIDES_ATTR accordingly.
+# The provides/obsoletes based tests are only run when HAS_PROVIDES_ATTR
+# is set.
+function detect_provides_attr() {
+ HAS_PROVIDES_ATTR=0
+
+ if [[ -r /proc/config.gz ]] &&
+ zgrep -q "CONFIG_KLP_HAS_PROVIDES=y" /proc/config.gz 2>/dev/null; then
+ HAS_PROVIDES_ATTR=1
+ return 0
+ fi
+
+ # /proc/config.gz is available only when CONFIG_IKCONFIG_PROC is
+ # enabled. Fall back to probing the "provides" sysfs attribute,
+ # which requires loading and unloading a livepatch.
+ load_lp test_klp_cmdline_lp
+ if does_sysfs_exist test_klp_cmdline_lp "provides"; then
+ HAS_PROVIDES_ATTR=1
+ fi
+ disable_lp test_klp_cmdline_lp
+ unload_lp test_klp_cmdline_lp
+}
+
# check_sysfs_rights(modname, rel_path, expected_rights) - check sysfs
# path permissions
# modname - livepatch module creating the sysfs interface
diff --git a/tools/testing/selftests/livepatch/test-callbacks.sh b/tools/testing/selftests/livepatch/test-callbacks.sh
index 2a03deb26a12..c6583647d2f1 100755
--- a/tools/testing/selftests/livepatch/test-callbacks.sh
+++ b/tools/testing/selftests/livepatch/test-callbacks.sh
@@ -10,6 +10,7 @@ MOD_TARGET=test_klp_callbacks_mod
MOD_TARGET_BUSY=test_klp_callbacks_busy
setup_config
+detect_provides_attr
# Test a combination of loading a kernel module and a livepatch that
@@ -458,16 +459,17 @@ $MOD_TARGET_BUSY: ${MOD_TARGET_BUSY}_exit"
# execute as each patch progresses through its (un)patching
# transition.
-start_test "multiple livepatches"
+function test_multiple_livepatches() {
+ start_test "multiple livepatches"
-load_lp $MOD_LIVEPATCH
-load_lp $MOD_LIVEPATCH2
-disable_lp $MOD_LIVEPATCH2
-disable_lp $MOD_LIVEPATCH
-unload_lp $MOD_LIVEPATCH2
-unload_lp $MOD_LIVEPATCH
+ load_lp $MOD_LIVEPATCH
+ load_lp $MOD_LIVEPATCH2
+ 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
livepatch: enabling patch '$MOD_LIVEPATCH'
livepatch: '$MOD_LIVEPATCH': initializing patching transition
$MOD_LIVEPATCH: pre_patch_callback: vmlinux
@@ -499,6 +501,7 @@ $MOD_LIVEPATCH: post_unpatch_callback: vmlinux
livepatch: '$MOD_LIVEPATCH': unpatching complete
% rmmod $MOD_LIVEPATCH2
% rmmod $MOD_LIVEPATCH"
+}
# Load multiple livepatches, but the second as an 'atomic-replace'
@@ -515,15 +518,16 @@ livepatch: '$MOD_LIVEPATCH': unpatching complete
# - Once the atomic replace module is loaded, only its pre and post
# unpatch callbacks are executed.
-start_test "atomic replace"
+function test_atomic_replace() {
+ start_test "atomic replace"
-load_lp $MOD_LIVEPATCH
-load_lp $MOD_LIVEPATCH2 replace=1
-disable_lp $MOD_LIVEPATCH2
-unload_lp $MOD_LIVEPATCH2
-unload_lp $MOD_LIVEPATCH
+ load_lp $MOD_LIVEPATCH
+ load_lp $MOD_LIVEPATCH2 replace=1
+ 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
livepatch: enabling patch '$MOD_LIVEPATCH'
livepatch: '$MOD_LIVEPATCH': initializing patching transition
$MOD_LIVEPATCH: pre_patch_callback: vmlinux
@@ -548,6 +552,15 @@ $MOD_LIVEPATCH2: post_unpatch_callback: vmlinux
livepatch: '$MOD_LIVEPATCH2': unpatching complete
% rmmod $MOD_LIVEPATCH2
% rmmod $MOD_LIVEPATCH"
+}
+
+if [[ "$HAS_PROVIDES_ATTR" != "1" ]]; then
+ test_multiple_livepatches
+ test_atomic_replace
+else
+ skip "multiple livepatches" "legacy replace attribute not present"
+ skip "atomic replace" "legacy replace attribute not present"
+fi
exit 0
diff --git a/tools/testing/selftests/livepatch/test-kprobe.sh b/tools/testing/selftests/livepatch/test-kprobe.sh
index 81ab6d4760aa..75d9093c1742 100755
--- a/tools/testing/selftests/livepatch/test-kprobe.sh
+++ b/tools/testing/selftests/livepatch/test-kprobe.sh
@@ -5,7 +5,7 @@
. $(dirname $0)/functions.sh
-grep -q kprobe_ftrace_ops /proc/kallsyms || skip "test-kprobe requires CONFIG_KPROBES_ON_FTRACE"
+grep -q kprobe_ftrace_ops /proc/kallsyms || skip_exit "test-kprobe requires CONFIG_KPROBES_ON_FTRACE"
MOD_LIVEPATCH=test_klp_cmdline_lp
MOD_KPROBE=test_klp_cmdline_kp
diff --git a/tools/testing/selftests/livepatch/test-livepatch.sh b/tools/testing/selftests/livepatch/test-livepatch.sh
index 5380b5f174d4..2b89cba0547e 100755
--- a/tools/testing/selftests/livepatch/test-livepatch.sh
+++ b/tools/testing/selftests/livepatch/test-livepatch.sh
@@ -12,6 +12,7 @@ MOD_TARGET=test_klp_mod_target
MOD_TARGET_PATCH=test_klp_mod_patch
setup_config
+detect_provides_attr
# - load a livepatch that modifies the output from /proc/cmdline and
@@ -55,31 +56,32 @@ livepatch: '$MOD_LIVEPATCH1': unpatching complete
# - unload the second livepatch and verify that the first is still active
# - unload the first livepatch and verify none are active
-start_test "multiple livepatches"
+function test_multiple_livepatches() {
+ start_test "multiple livepatches"
-load_lp $MOD_LIVEPATCH1
+ load_lp $MOD_LIVEPATCH1
-grep 'live patched' /proc/cmdline > /dev/kmsg
-grep 'live patched' /proc/meminfo > /dev/kmsg
+ 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=0
-grep 'live patched' /proc/cmdline > /dev/kmsg
-grep 'live patched' /proc/meminfo > /dev/kmsg
+ grep 'live patched' /proc/cmdline > /dev/kmsg
+ grep 'live patched' /proc/meminfo > /dev/kmsg
-disable_lp $MOD_REPLACE
-unload_lp $MOD_REPLACE
+ disable_lp $MOD_REPLACE
+ unload_lp $MOD_REPLACE
-grep 'live patched' /proc/cmdline > /dev/kmsg
-grep 'live patched' /proc/meminfo > /dev/kmsg
+ grep 'live patched' /proc/cmdline > /dev/kmsg
+ grep 'live patched' /proc/meminfo > /dev/kmsg
-disable_lp $MOD_LIVEPATCH1
-unload_lp $MOD_LIVEPATCH1
+ disable_lp $MOD_LIVEPATCH1
+ unload_lp $MOD_LIVEPATCH1
-grep 'live patched' /proc/cmdline > /dev/kmsg
-grep 'live patched' /proc/meminfo > /dev/kmsg
+ 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
livepatch: enabling patch '$MOD_LIVEPATCH1'
livepatch: '$MOD_LIVEPATCH1': initializing patching transition
livepatch: '$MOD_LIVEPATCH1': starting patching transition
@@ -107,6 +109,7 @@ livepatch: '$MOD_LIVEPATCH1': starting unpatching transition
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
@@ -119,46 +122,47 @@ livepatch: '$MOD_LIVEPATCH1': unpatching complete
# atomic replace livepatch is still active
# - remove the atomic replace livepatch and verify that none are active
-start_test "atomic replace livepatch"
+function test_atomic_replace_livepatch() {
+ start_test "atomic replace livepatch"
-load_lp $MOD_LIVEPATCH1
+ load_lp $MOD_LIVEPATCH1
-grep 'live patched' /proc/cmdline > /dev/kmsg
-grep 'live patched' /proc/meminfo > /dev/kmsg
+ 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
+ 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
+ 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
+ load_lp $MOD_REPLACE replace=1
-grep 'live patched' /proc/cmdline > /dev/kmsg
-grep 'live patched' /proc/meminfo > /dev/kmsg
+ 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"
+ 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
+ # 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
+ grep 'live patched' /proc/cmdline > /dev/kmsg
+ grep 'live patched' /proc/meminfo > /dev/kmsg
-disable_lp $MOD_REPLACE
-unload_lp $MOD_REPLACE
+ disable_lp $MOD_REPLACE
+ unload_lp $MOD_REPLACE
-grep 'live patched' /proc/cmdline > /dev/kmsg
-grep 'live patched' /proc/meminfo > /dev/kmsg
+ 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
livepatch: enabling patch '$MOD_LIVEPATCH1'
livepatch: '$MOD_LIVEPATCH1': initializing patching transition
livepatch: '$MOD_LIVEPATCH1': starting patching transition
@@ -196,6 +200,16 @@ livepatch: '$MOD_REPLACE': starting unpatching transition
livepatch: '$MOD_REPLACE': completing unpatching transition
livepatch: '$MOD_REPLACE': unpatching complete
% rmmod $MOD_REPLACE"
+}
+
+
+if [[ "$HAS_PROVIDES_ATTR" != "1" ]]; then
+ test_multiple_livepatches
+ test_atomic_replace_livepatch
+else
+ skip "multiple livepatches" "legacy replace attribute not present"
+ skip "atomic replace livepatch" "legacy replace attribute not present"
+fi
# - load a target module that provides /proc/test_klp_mod_target with
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..6c46ce575f5c 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,16 @@
#include <linux/kernel.h>
#include <linux/livepatch.h>
+#ifdef CONFIG_KLP_HAS_PROVIDES
+/*
+ * TODO: Add provides/obsoletes module parameters for the
+ * provides/obsoletes based tests (to be added later).
+ */
+#else
static int replace;
module_param(replace, int, 0644);
MODULE_PARM_DESC(replace, "replace (default=0)");
+#endif
static const char *const module_state[] = {
[MODULE_STATE_LIVE] = "[MODULE_STATE_LIVE] Normal state",
@@ -72,12 +79,21 @@ static struct klp_object objs[] = {
static struct klp_patch patch = {
.mod = THIS_MODULE,
.objs = objs,
+#ifndef CONFIG_KLP_HAS_PROVIDES
/* set .replace in the init function below for demo purposes */
+#endif
};
static int test_klp_callbacks_demo2_init(void)
{
+#ifdef CONFIG_KLP_HAS_PROVIDES
+ /*
+ * TODO: Set provides/obsoletes from the module parameters
+ * for the provides/obsoletes based tests (to be added later).
+ */
+#else
patch.replace = replace;
+#endif
return klp_enable_patch(&patch);
}
diff --git a/tools/testing/selftests/livepatch/test_modules/test_klp_meminfo_lp.c b/tools/testing/selftests/livepatch/test_modules/test_klp_meminfo_lp.c
index f2477b217aab..0c434a27672d 100644
--- a/tools/testing/selftests/livepatch/test_modules/test_klp_meminfo_lp.c
+++ b/tools/testing/selftests/livepatch/test_modules/test_klp_meminfo_lp.c
@@ -7,9 +7,16 @@
#include <linux/kernel.h>
#include <linux/livepatch.h>
+#ifdef CONFIG_KLP_HAS_PROVIDES
+/*
+ * TODO: Add provides/obsoletes module parameters for the
+ * provides/obsoletes based tests (to be added later).
+ */
+#else
static int replace;
module_param(replace, int, 0644);
MODULE_PARM_DESC(replace, "replace (default=0)");
+#endif
#include <linux/seq_file.h>
static int livepatch_meminfo_proc_show(struct seq_file *m, void *v)
@@ -36,12 +43,21 @@ static struct klp_object objs[] = {
static struct klp_patch patch = {
.mod = THIS_MODULE,
.objs = objs,
+#ifndef CONFIG_KLP_HAS_PROVIDES
/* set .replace in the init function below for demo purposes */
+#endif
};
static int test_klp_meminfo_lp_init(void)
{
+#ifdef CONFIG_KLP_HAS_PROVIDES
+ /*
+ * TODO: Set provides/obsoletes from the module parameters
+ * for the provides/obsoletes based tests (to be added later).
+ */
+#else
patch.replace = replace;
+#endif
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..4d419a58cf65 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,14 @@ static struct klp_patch patch = {
.mod = THIS_MODULE,
.objs = objs,
.states = states,
+#ifdef CONFIG_KLP_HAS_PROVIDES
+ /*
+ * TODO: Add provides/obsoletes module parameters for the
+ * provides/obsoletes based tests (to be added later).
+ */
+#else
.replace = true,
+#endif
};
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..4f70f0619967 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,14 @@ static struct klp_patch patch = {
.mod = THIS_MODULE,
.objs = objs,
.states = states,
+#ifdef CONFIG_KLP_HAS_PROVIDES
+ /*
+ * TODO: Add provides/obsoletes module parameters for the
+ * provides/obsoletes based tests (to be added later).
+ */
+#else
.replace = true,
+#endif
};
static int test_klp_callbacks_demo_init(void)
--
2.52.0
^ permalink raw reply related [flat|nested] 24+ messages in thread* Re: [PATCH v10 2/9] selftests/livepatch: Adapt atomic replace tests to provides/obsoletes
2026-09-30 3:03 ` [PATCH v10 2/9] selftests/livepatch: Adapt atomic replace tests to provides/obsoletes Yafang Shao
@ 2026-10-01 15:57 ` Petr Mladek
2026-10-02 12:01 ` Miroslav Benes
1 sibling, 0 replies; 24+ messages in thread
From: Petr Mladek @ 2026-10-01 15:57 UTC (permalink / raw)
To: Yafang Shao; +Cc: jpoimboe, jikos, mbenes, joe.lawrence, song, live-patching
On Wed 2026-09-30 11:03:16, Yafang Shao wrote:
> The legacy "replace" field in struct klp_patch will be replaced by the
> provides/obsoletes mechanism. As a result, the atomic replace
> selftests fail to build against kernels that only support
> provides/obsoletes.
>
> Adapt the selftests so that they build and run on both old and new
> kernels. On kernels without the legacy "replace" support, the
> replace-related test cases are skipped with a SKIP message instead
> of being run.
>
> Also introduce the CONFIG_KLP_HAS_PROVIDES compile-time marker in
> kernel/livepatch/Kconfig. It defaults to n and will be set to y once
> the provides/obsoletes support is implemented later in this series.
>
> The provides/obsoletes-based selftests will be added later in this
> series, after the legacy "replace" field has been substituted by the
> new mechanism.
>
> Suggested-by: Petr Mladek <pmladek@suse.com>
> Signed-off-by: Yafang Shao <laoar.shao@gmail.com>
> Acked-by: Song Liu <song@kernel.org>
Looks good and passes selftests. Feel free to use:
Reviewed-by: Petr Mladek <pmladek@suse.com>
Tested-by: Petr Mladek <pmladek@suse.com>
Thouhg, see few nits below:
> --- a/tools/testing/selftests/livepatch/test-callbacks.sh
> +++ b/tools/testing/selftests/livepatch/test-callbacks.sh
> @@ -458,16 +459,17 @@ $MOD_TARGET_BUSY: ${MOD_TARGET_BUSY}_exit"
> # execute as each patch progresses through its (un)patching
> # transition.
>
> -start_test "multiple livepatches"
> +function test_multiple_livepatches() {
I really like the approach with bash functions. It separates
the test code from the skip-check.
> + start_test "multiple livepatches"
>
> -load_lp $MOD_LIVEPATCH
> -load_lp $MOD_LIVEPATCH2
> -disable_lp $MOD_LIVEPATCH2
> -disable_lp $MOD_LIVEPATCH
> -unload_lp $MOD_LIVEPATCH2
> -unload_lp $MOD_LIVEPATCH
> + load_lp $MOD_LIVEPATCH
> + load_lp $MOD_LIVEPATCH2
> + 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
> livepatch: enabling patch '$MOD_LIVEPATCH'
> livepatch: '$MOD_LIVEPATCH': initializing patching transition
> $MOD_LIVEPATCH: pre_patch_callback: vmlinux
> @@ -548,6 +552,15 @@ $MOD_LIVEPATCH2: post_unpatch_callback: vmlinux
> livepatch: '$MOD_LIVEPATCH2': unpatching complete
> % rmmod $MOD_LIVEPATCH2
> % rmmod $MOD_LIVEPATCH"
> +}
> +
>
> +if [[ "$HAS_PROVIDES_ATTR" != "1" ]]; then
> + test_multiple_livepatches
> + test_atomic_replace
> +else
> + skip "multiple livepatches" "legacy replace attribute not present"
> + skip "atomic replace" "legacy replace attribute not present"
> +fi
I would revert the logic and ordering:
+ Positive checks are easier to read ;-)
+ It would make sense to have the obsolete checks in the "else" part.
+ It would be the same ordering as in the .c files.
> --- a/tools/testing/selftests/livepatch/test-kprobe.sh
> +++ b/tools/testing/selftests/livepatch/test-kprobe.sh
> @@ -196,6 +200,16 @@ livepatch: '$MOD_REPLACE': starting unpatching transition
> livepatch: '$MOD_REPLACE': completing unpatching transition
> livepatch: '$MOD_REPLACE': unpatching complete
> % rmmod $MOD_REPLACE"
> +}
> +
> +
> +if [[ "$HAS_PROVIDES_ATTR" != "1" ]]; then
> + test_multiple_livepatches
> + test_atomic_replace_livepatch
> +else
> + skip "multiple livepatches" "legacy replace attribute not present"
> + skip "atomic replace livepatch" "legacy replace attribute not present"
> +fi
Same here.
>
> # - load a target module that provides /proc/test_klp_mod_target with
> 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..6c46ce575f5c 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,16 @@
> #include <linux/kernel.h>
> #include <linux/livepatch.h>
>
> +#ifdef CONFIG_KLP_HAS_PROVIDES
JFYI, this is the related check in the .c code.
> +/*
> + * TODO: Add provides/obsoletes module parameters for the
> + * provides/obsoletes based tests (to be added later).
> + */
> +#else
> static int replace;
> module_param(replace, int, 0644);
> MODULE_PARM_DESC(replace, "replace (default=0)");
> +#endif
>
> static const char *const module_state[] = {
> [MODULE_STATE_LIVE] = "[MODULE_STATE_LIVE] Normal state",
Best Regardss,
Petr
^ permalink raw reply [flat|nested] 24+ messages in thread* Re: [PATCH v10 2/9] selftests/livepatch: Adapt atomic replace tests to provides/obsoletes
2026-09-30 3:03 ` [PATCH v10 2/9] selftests/livepatch: Adapt atomic replace tests to provides/obsoletes Yafang Shao
2026-10-01 15:57 ` Petr Mladek
@ 2026-10-02 12:01 ` Miroslav Benes
1 sibling, 0 replies; 24+ messages in thread
From: Miroslav Benes @ 2026-10-02 12:01 UTC (permalink / raw)
To: Yafang Shao
Cc: jpoimboe, jikos, mbenes, pmladek, joe.lawrence, song,
live-patching
On Wed, 30 Sep 2026 11:03:16 +0800, Yafang Shao <laoar.shao@gmail.com> wrote:
> The legacy "replace" field in struct klp_patch will be replaced by the
> provides/obsoletes mechanism. As a result, the atomic replace
> selftests fail to build against kernels that only support
> provides/obsoletes.
>
> Adapt the selftests so that they build and run on both old and new
> kernels. On kernels without the legacy "replace" support, the
> replace-related test cases are skipped with a SKIP message instead
> of being run.
>
> [...]
Acked-by: Miroslav Benes <mbenes@suse.cz>
--
Miroslav
^ permalink raw reply [flat|nested] 24+ messages in thread
* [PATCH v10 3/9] livepatch: Make klp_find_func() non static
2026-09-30 3:03 [PATCH v10 0/9] livepatch: Add support for scoped atomic replace Yafang Shao
2026-09-30 3:03 ` [PATCH v10 1/9] selftests/livepatch: Clarify test module file names Yafang Shao
2026-09-30 3:03 ` [PATCH v10 2/9] selftests/livepatch: Adapt atomic replace tests to provides/obsoletes Yafang Shao
@ 2026-09-30 3:03 ` Yafang Shao
2026-10-02 14:19 ` Petr Mladek
2026-09-30 3:03 ` [PATCH v10 4/9] livepatch: Call klp_init_patch_early() earlier Yafang Shao
` (5 subsequent siblings)
8 siblings, 1 reply; 24+ messages in thread
From: Yafang Shao @ 2026-09-30 3:03 UTC (permalink / raw)
To: jpoimboe, jikos, mbenes, pmladek, joe.lawrence, song
Cc: live-patching, Yafang Shao
Make klp_find_func() non static to allow its use in other source files
by an upcoming patch.
While at it, rename the parameter @old_func to @func to better reflect
its generic purpose.
Signed-off-by: Yafang Shao <laoar.shao@gmail.com>
Acked-by: Song Liu <song@kernel.org>
Acked-by: Miroslav Benes <mbenes@suse.cz>
---
kernel/livepatch/core.c | 18 +++++++++---------
kernel/livepatch/core.h | 1 +
2 files changed, 10 insertions(+), 9 deletions(-)
diff --git a/kernel/livepatch/core.c b/kernel/livepatch/core.c
index 517fe427ff92..89251fafe86e 100644
--- a/kernel/livepatch/core.c
+++ b/kernel/livepatch/core.c
@@ -82,21 +82,21 @@ 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)
+/* Check if @func is present in @obj. */
+struct klp_func *klp_find_func(struct klp_object *obj, 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))) {
- return func;
+ if ((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 obj_func;
}
}
diff --git a/kernel/livepatch/core.h b/kernel/livepatch/core.h
index 38209c7361b6..361a0917a03f 100644
--- a/kernel/livepatch/core.h
+++ b/kernel/livepatch/core.h
@@ -17,6 +17,7 @@ void klp_free_patch_async(struct klp_patch *patch);
void klp_free_replaced_patches_async(struct klp_patch *new_patch);
void klp_unpatch_replaced_patches(struct klp_patch *new_patch);
void klp_discard_nops(struct klp_patch *new_patch);
+struct klp_func *klp_find_func(struct klp_object *obj, struct klp_func *func);
static inline bool klp_is_object_loaded(struct klp_object *obj)
{
--
2.52.0
^ permalink raw reply related [flat|nested] 24+ messages in thread* Re: [PATCH v10 3/9] livepatch: Make klp_find_func() non static
2026-09-30 3:03 ` [PATCH v10 3/9] livepatch: Make klp_find_func() non static Yafang Shao
@ 2026-10-02 14:19 ` Petr Mladek
0 siblings, 0 replies; 24+ messages in thread
From: Petr Mladek @ 2026-10-02 14:19 UTC (permalink / raw)
To: Yafang Shao; +Cc: jpoimboe, jikos, mbenes, joe.lawrence, song, live-patching
On Wed 2026-09-30 11:03:17, Yafang Shao wrote:
> Make klp_find_func() non static to allow its use in other source files
> by an upcoming patch.
>
> While at it, rename the parameter @old_func to @func to better reflect
> its generic purpose.
>
> Signed-off-by: Yafang Shao <laoar.shao@gmail.com>
> Acked-by: Song Liu <song@kernel.org>
> Acked-by: Miroslav Benes <mbenes@suse.cz>
Looks good and passes selftests:
Reviewed-by: Petr Mladek <pmladek@suse.com>
Tested-by: Petr Mladek <pmladek@suse.com>
Best Regards,
Petr
^ permalink raw reply [flat|nested] 24+ messages in thread
* [PATCH v10 4/9] livepatch: Call klp_init_patch_early() earlier
2026-09-30 3:03 [PATCH v10 0/9] livepatch: Add support for scoped atomic replace Yafang Shao
` (2 preceding siblings ...)
2026-09-30 3:03 ` [PATCH v10 3/9] livepatch: Make klp_find_func() non static Yafang Shao
@ 2026-09-30 3:03 ` Yafang Shao
2026-10-02 15:58 ` Petr Mladek
2026-09-30 3:03 ` [PATCH v10 5/9] livepatch: Implement provides and obsoletes for scoped atomic replace Yafang Shao
` (4 subsequent siblings)
8 siblings, 1 reply; 24+ messages in thread
From: Yafang Shao @ 2026-09-30 3:03 UTC (permalink / raw)
To: jpoimboe, jikos, mbenes, pmladek, joe.lawrence, song
Cc: live-patching, Yafang Shao
Invoke klp_init_patch_early() during early initialization to avoid
mixing for_each_() and for_each_*_static() variants later in the code.
No functional change. This prepares the code for an upcoming patch.
Suggested-by: Petr Mladek <pmladek@suse.com>
Signed-off-by: Yafang Shao <laoar.shao@gmail.com>
Acked-by: Song Liu <song@kernel.org>
Acked-by: Miroslav Benes <mbenes@suse.cz>
---
kernel/livepatch/core.c | 14 +++++++-------
1 file changed, 7 insertions(+), 7 deletions(-)
diff --git a/kernel/livepatch/core.c b/kernel/livepatch/core.c
index 89251fafe86e..c0879b5d9dc0 100644
--- a/kernel/livepatch/core.c
+++ b/kernel/livepatch/core.c
@@ -1155,13 +1155,6 @@ int klp_enable_patch(struct klp_patch *patch)
mutex_lock(&klp_mutex);
- if (!klp_is_patch_compatible(patch)) {
- pr_err("Livepatch patch (%s) is not compatible with the already installed livepatches.\n",
- patch->mod->name);
- mutex_unlock(&klp_mutex);
- return -EINVAL;
- }
-
if (!try_module_get(patch->mod)) {
mutex_unlock(&klp_mutex);
return -ENODEV;
@@ -1169,6 +1162,13 @@ int klp_enable_patch(struct klp_patch *patch)
klp_init_patch_early(patch);
+ if (!klp_is_patch_compatible(patch)) {
+ pr_err("Livepatch patch (%s) is not compatible with the already installed livepatches.\n",
+ patch->mod->name);
+ ret = -EINVAL;
+ goto err;
+ }
+
ret = klp_init_patch(patch);
if (ret)
goto err;
--
2.52.0
^ permalink raw reply related [flat|nested] 24+ messages in thread* Re: [PATCH v10 4/9] livepatch: Call klp_init_patch_early() earlier
2026-09-30 3:03 ` [PATCH v10 4/9] livepatch: Call klp_init_patch_early() earlier Yafang Shao
@ 2026-10-02 15:58 ` Petr Mladek
0 siblings, 0 replies; 24+ messages in thread
From: Petr Mladek @ 2026-10-02 15:58 UTC (permalink / raw)
To: Yafang Shao; +Cc: jpoimboe, jikos, mbenes, joe.lawrence, song, live-patching
On Wed 2026-09-30 11:03:18, Yafang Shao wrote:
> Invoke klp_init_patch_early() during early initialization to avoid
> mixing for_each_() and for_each_*_static() variants later in the code.
>
> No functional change. This prepares the code for an upcoming patch.
>
> Suggested-by: Petr Mladek <pmladek@suse.com>
> Signed-off-by: Yafang Shao <laoar.shao@gmail.com>
> Acked-by: Song Liu <song@kernel.org>
> Acked-by: Miroslav Benes <mbenes@suse.cz>
Looks good and passed selftests:
Reviewed-by: Petr Mladek <pmladek@suse.com>
Signed-off-by: Petr Mladek <pmladek@suse.com>
Best Regards,
Petr
^ permalink raw reply [flat|nested] 24+ messages in thread
* [PATCH v10 5/9] livepatch: Implement provides and obsoletes for scoped atomic replace
2026-09-30 3:03 [PATCH v10 0/9] livepatch: Add support for scoped atomic replace Yafang Shao
` (3 preceding siblings ...)
2026-09-30 3:03 ` [PATCH v10 4/9] livepatch: Call klp_init_patch_early() earlier Yafang Shao
@ 2026-09-30 3:03 ` Yafang Shao
2026-10-02 12:01 ` Miroslav Benes
2026-10-02 17:06 ` Petr Mladek
2026-09-30 3:03 ` [PATCH v10 6/9] livepatch: Deprecate stack_order Yafang Shao
` (3 subsequent siblings)
8 siblings, 2 replies; 24+ messages in thread
From: Yafang Shao @ 2026-09-30 3:03 UTC (permalink / raw)
To: jpoimboe, jikos, mbenes, pmladek, joe.lawrence, song
Cc: live-patching, Yafang Shao
The current bool replace flag is too coarse: it is either all or
nothing. A livepatch with .replace=true replaces ALL existing
livepatches, which is safe but inflexible. There is no way to have
multiple independent livepatch sets coexist on the same system.
Replace it with a more flexible model using two new fields in
struct klp_patch:
- provides: an unsigned int id identifying the changes made by
the related livepatch. Where the changes are a set
of modified objects, functions, and used callbacks,
shadow variable ids, and state ids.
- obsoletes: an optional array of unsigned int ids specifying
additional provides ids to be replaced. This allows a new patch
to explicitly obsolete patches from different scopes.
A new livepatch atomically replaces any existing livepatch that
meets any of the following conditions:
1. It has the same provides id as the new patch,
2. Its provides id is listed in the new patch's obsoletes list, or
3. The new patch's provides id is listed in its obsoletes list
(the obsoletes relationship is symmetric).
The obsoletes relationship is symmetric: two livepatches that
obsolete each other are mutually exclusive. Loading either one
atomically removes the other, so they can never be enabled at the
same time. This allows swapping between two independently prepared
livepatches that cover the same scope, in either direction.
klp-build gains -p/--provides and -r/--obsoletes options. The two
parameters are not validated in klp-build, since the compiler and the
kernel handle invalid values.
Enable the CONFIG_KLP_HAS_PROVIDES compile-time marker now that the
provides/obsoletes interface is implemented.
Suggested-by: Song Liu <song@kernel.org>
Suggested-by: Joe Lawrence <joe.lawrence@redhat.com>
Suggested-by: Petr Mladek <pmladek@suse.com>
Co-developed-by: Petr Mladek <pmladek@suse.com>
Signed-off-by: Petr Mladek <pmladek@suse.com>
Signed-off-by: Yafang Shao <laoar.shao@gmail.com>
Acked-by: Song Liu <song@kernel.org>
---
.../ABI/removed/sysfs-kernel-livepatch | 7 ++
.../ABI/testing/sysfs-kernel-livepatch | 25 ++++-
.../livepatch/cumulative-patches.rst | 99 +++++++++++++------
Documentation/livepatch/livepatch.rst | 25 +++--
include/linux/livepatch.h | 8 +-
kernel/livepatch/Kconfig | 2 +-
kernel/livepatch/core.c | 92 ++++++++++++++---
kernel/livepatch/core.h | 1 +
kernel/livepatch/state.c | 58 +++++++++--
kernel/livepatch/transition.c | 15 +--
scripts/livepatch/init.c | 19 ++--
scripts/livepatch/klp-build | 23 +++--
12 files changed, 285 insertions(+), 89 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..87d57ba27326
--- /dev/null
+++ b/Documentation/ABI/removed/sysfs-kernel-livepatch
@@ -0,0 +1,7 @@
+What: /sys/kernel/livepatch/<patch>/replace
+Date: Jun 2024
+KernelVersion: 6.11.0
+Contact: live-patching@vger.kernel.org
+Description:
+ An attribute which indicates whether the patch supports
+ atomic-replace.
diff --git a/Documentation/ABI/testing/sysfs-kernel-livepatch b/Documentation/ABI/testing/sysfs-kernel-livepatch
index 3c3f36b32b57..7ccfe5414ac3 100644
--- a/Documentation/ABI/testing/sysfs-kernel-livepatch
+++ b/Documentation/ABI/testing/sysfs-kernel-livepatch
@@ -47,13 +47,28 @@ Description:
disabled when the feature is used. See
Documentation/livepatch/livepatch.rst for more information.
-What: /sys/kernel/livepatch/<patch>/replace
-Date: Jun 2024
-KernelVersion: 6.11.0
+What: /sys/kernel/livepatch/<patch>/provides
+Date: Jun 2026
+KernelVersion: 7.4.0
Contact: live-patching@vger.kernel.org
Description:
- An attribute which indicates whether the patch supports
- atomic-replace.
+ An attribute to show the provides id of this livepatch.
+ Livepatches with the same provides id replace each other.
+
+What: /sys/kernel/livepatch/<patch>/obsoletes
+Date: Jun 2026
+KernelVersion: 7.4.0
+Contact: live-patching@vger.kernel.org
+Description:
+ An attribute to show the obsoletes ids of this livepatch.
+ The obsoletes ids are a comma-separated list of provides
+ ids that this patch obsoletes. When this livepatch is
+ loaded, any existing livepatch whose provides id matches
+ either this patch's provides id or any id in the obsoletes
+ list will be atomically replaced.
+ The obsoletes relationship is symmetric: an existing
+ livepatch that obsoletes this patch's provides id will
+ also be atomically replaced by this patch.
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..8898a2ee356c 100644
--- a/Documentation/livepatch/cumulative-patches.rst
+++ b/Documentation/livepatch/cumulative-patches.rst
@@ -2,33 +2,72 @@
Atomic Replace & Cumulative Patches
===================================
-There might be dependencies between livepatches. If multiple patches need
-to do different changes to the same function(s) then we need to define
-an order in which the patches will be installed. And function implementations
-from any newer livepatch must be done on top of the older ones.
-
-This might become a maintenance nightmare. Especially when more patches
-modified the same function in different ways.
-
-An elegant solution comes with the feature called "Atomic Replace". It allows
-creation of so called "Cumulative Patches". They include all wanted changes
-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::
-
- static struct klp_patch patch = {
- .mod = THIS_MODULE,
- .objs = objs,
- .replace = true,
- };
-
-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.
+Livepatches are used to fix kernel bugs. New fixes need to be added over time.
+The fixes might be independent, but they might also depend on each other. This
+brings a challenge of how to keep the livepatched system safe and consistent.
+
+Part of the solution is the "Atomic Replace" feature, which allows the kernel to
+atomically replace an existing livepatch with another one. These newer
+livepatches are designed as "Cumulative Patches". They include all wanted
+changes from all older livepatches and completely replace them in one
+transition.
+
+The second part of the solution are ``provides`` and ``obsoletes`` fields in
+``struct klp_patch``, which allow the installation of multiple livepatches in
+parallel. A livepatch will atomically replace any already installed livepatch
+whose ``provides`` id matches either the new patch's ``provides`` id or any id
+in the new patch's ``obsoletes`` list. This might be used to fix independent
+problems separately, for example, the livepatches might be prepared by separate
+teams focusing on particular functionality or a subsystem.
+
+It should be emphasized that the preferred and most secure way is to always use
+the default ``provides = 0``. As long as all livepatches use the same default
+id, any livepatch replaces any other livepatch, preventing any unexpected
+interactions between incompatible livepatches.
+
+Provides and Obsoletes
+-----------------------
+
+The ``provides`` field in ``struct klp_patch`` is an unsigned integer that
+identifies the livepatch. By default, it is 0.
+
+The ``obsoletes`` field is an optional array of unsigned integers that
+specifies additional ``provides`` ids to be replaced when this patch is
+loaded.
+
+The ``obsoletes`` relationship is symmetric. If livepatch A lists the
+``provides`` id of livepatch B in its ``obsoletes`` list, then B also
+atomically replaces A when it is loaded, because A's ``obsoletes`` list
+contains B's ``provides`` id. In other words, two livepatches that
+obsolete each other are mutually exclusive: loading either one always
+removes the other one, so they can never be active at the same time.
+This allows swapping between independently prepared livepatches that
+cover the same functionality, in either direction.
+
+For example::
+
+ static struct klp_patch patch = {
+ .mod = THIS_MODULE,
+ .objs = objs,
+ .provides = 0,
+ };
+
+Any ``provides`` value might be associated with a set of livepatched symbols,
+callbacks, shadow variables, and state IDs. By definition, there can only ever
+be one active livepatch for a given ``provides`` id.
+
+On the contrary, livepatches with a different ``provides`` id must not
+modify the same function, or use the state with the same ID. Any attempt to
+load an incompatible livepatch will be rejected by the kernel.
+
+Atomic Replace
+--------------
+
+A livepatch with a given ``provides`` id is replaced by another livepatch
+with the same ``provides`` id, or whose ``obsoletes`` list includes that id.
+All processes are migrated to use the code only from the new patch. Once
+the transition is finished, the older patch is disabled. Patches with a
+different ``provides`` id are not affected and remain active.
Ftrace handlers are transparently removed from functions that are no
longer modified by the new cumulative patch.
@@ -64,7 +103,11 @@ 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.
+ A good practice is to use only one (default) ``provides`` id. 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..dfdacd347773 100644
--- a/Documentation/livepatch/livepatch.rst
+++ b/Documentation/livepatch/livepatch.rst
@@ -347,15 +347,22 @@ 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 can be only one active livepatch for a given ``provides`` id.
+A new livepatch atomically replaces any existing livepatch whose
+``provides`` id matches either the new patch's ``provides`` id or
+any id in the new patch's ``obsoletes`` list.
+
+The replacement is symmetric: if an existing livepatch's ``obsoletes``
+list contains the new patch's ``provides`` id, the new patch will also
+atomically replace that existing livepatch. Consequently, two
+livepatches that obsolete each other can never be enabled at the same
+time, and either of them can be loaded to take over from the 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.
See Documentation/livepatch/cumulative-patches.rst for more details.
diff --git a/include/linux/livepatch.h b/include/linux/livepatch.h
index 5f74f79c22b4..1e3a09e29a65 100644
--- a/include/linux/livepatch.h
+++ b/include/linux/livepatch.h
@@ -127,7 +127,9 @@ 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
+ * @provides: only one active livepatch per id
+ * @obsoletes: replace given livepatch id(s)
+ * @nr_obsoletes: number of entries in the @obsoletes array
* @list: list node for global list of actively used patches
* @kobj: kobject for sysfs resources
* @obj_list: dynamic list of the object entries
@@ -141,7 +143,9 @@ struct klp_patch {
struct module *mod;
struct klp_object *objs;
struct klp_state *states;
- bool replace;
+ unsigned int provides;
+ unsigned int *obsoletes;
+ unsigned int nr_obsoletes;
/* internal */
struct list_head list;
diff --git a/kernel/livepatch/Kconfig b/kernel/livepatch/Kconfig
index d1eb971abb1a..d92603658f7e 100644
--- a/kernel/livepatch/Kconfig
+++ b/kernel/livepatch/Kconfig
@@ -32,7 +32,7 @@ config KLP_BUILD
Enable klp-build support
config KLP_HAS_PROVIDES
- def_bool n
+ def_bool y
help
Compile-time marker for the livepatch provides/obsoletes support.
It is always set when the kernel is built with the scoped atomic
diff --git a/kernel/livepatch/core.c b/kernel/livepatch/core.c
index c0879b5d9dc0..82f541b16ecc 100644
--- a/kernel/livepatch/core.c
+++ b/kernel/livepatch/core.c
@@ -350,7 +350,8 @@ 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>/provides
+ * /sys/kernel/livepatch/<patch>/obsoletes
* /sys/kernel/livepatch/<patch>/stack_order
* /sys/kernel/livepatch/<patch>/<object>
* /sys/kernel/livepatch/<patch>/<object>/patched
@@ -448,13 +449,32 @@ 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 provides_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->provides);
+}
+
+static ssize_t obsoletes_show(struct kobject *kobj,
+ struct kobj_attribute *attr, char *buf)
+{
+ struct klp_patch *patch;
+ unsigned int i;
+ int len = 0;
+
+ patch = container_of(kobj, struct klp_patch, kobj);
+ if (!patch->obsoletes || !patch->nr_obsoletes)
+ return sysfs_emit(buf, "\n");
+
+ for (i = 0; i < patch->nr_obsoletes; i++)
+ len += sysfs_emit_at(buf, len, "%s%u", i == 0 ? "" : ",",
+ patch->obsoletes[i]);
+
+ len += sysfs_emit_at(buf, len, "\n");
+ return len;
}
static ssize_t stack_order_show(struct kobject *kobj,
@@ -481,13 +501,15 @@ 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 provides_kobj_attr = __ATTR_RO(provides);
+static struct kobj_attribute obsoletes_kobj_attr = __ATTR_RO(obsoletes);
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,
+ &provides_kobj_attr.attr,
+ &obsoletes_kobj_attr.attr,
&stack_order_kobj_attr.attr,
NULL
};
@@ -520,6 +542,39 @@ static void klp_init_func_early(struct klp_object *obj,
static void klp_init_object_early(struct klp_patch *patch,
struct klp_object *obj);
+/*
+ * Check if @new_patch will replace @old_patch:
+ * 1. Same provides ID
+ * 2. Old provides ID is in new patch's obsoletes list
+ * 3. New provides ID is in old patch's obsoletes list (symmetric replace)
+ */
+bool klp_patch_replaces(struct klp_patch *new_patch, struct klp_patch *old_patch)
+{
+ unsigned int i;
+
+ if (old_patch->provides == new_patch->provides)
+ return true;
+
+ if (new_patch->obsoletes) {
+ for (i = 0; i < new_patch->nr_obsoletes; i++) {
+ if (new_patch->obsoletes[i] == old_patch->provides)
+ return true;
+ }
+ }
+
+ /*
+ * The relationship is symmetric: an old patch that obsoletes the
+ * new one is also replaced by it.
+ */
+ if (old_patch->obsoletes) {
+ for (i = 0; i < old_patch->nr_obsoletes; i++) {
+ if (old_patch->obsoletes[i] == new_patch->provides)
+ return true;
+ }
+ }
+ return false;
+}
+
static struct klp_object *klp_alloc_object_dynamic(const char *name,
struct klp_patch *patch)
{
@@ -608,9 +663,10 @@ static int klp_add_object_nops(struct klp_patch *patch,
* Add 'nop' functions which simply return to the caller to run the
* original function.
*
- * They are added only when the atomic replace mode is used and only for
- * functions which are currently livepatched but are no longer included
- * in the new livepatch.
+ * They are added only when the new livepatch replaces an older one,
+ * see klp_patch_replaces(), and only for functions which are
+ * currently livepatched but are no longer included in the new
+ * livepatch.
*/
static int klp_add_nops(struct klp_patch *patch)
{
@@ -618,6 +674,9 @@ static int klp_add_nops(struct klp_patch *patch)
struct klp_object *old_obj;
klp_for_each_patch(old_patch) {
+ if (!klp_patch_replaces(patch, old_patch))
+ continue;
+
klp_for_each_object(old_patch, old_obj) {
int err;
@@ -799,6 +858,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 (!klp_patch_replaces(new_patch, old_patch))
+ continue;
klp_free_patch_async(old_patch);
}
}
@@ -995,11 +1056,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);
@@ -1193,11 +1252,12 @@ int klp_enable_patch(struct klp_patch *patch)
EXPORT_SYMBOL_GPL(klp_enable_patch);
/*
- * This function unpatches objects from the replaced livepatches.
+ * This function unpatches objects from the livepatches that are replaced
+ * by the new one, see klp_patch_replaces().
*
* We could be pretty aggressive here. It is called in the situation where
* these structures are no longer accessed from the ftrace handler.
- * All functions are redirected by the klp_transition_patch. They
+ * All their functions are redirected by the klp_transition_patch. They
* use either a new code or they are in the original code because
* of the special nop function patches.
*
@@ -1214,6 +1274,8 @@ void klp_unpatch_replaced_patches(struct klp_patch *new_patch)
klp_for_each_patch(old_patch) {
if (old_patch == new_patch)
return;
+ if (!klp_patch_replaces(new_patch, old_patch))
+ continue;
old_patch->enabled = false;
klp_unpatch_objects(old_patch);
diff --git a/kernel/livepatch/core.h b/kernel/livepatch/core.h
index 361a0917a03f..b03cfc58b660 100644
--- a/kernel/livepatch/core.h
+++ b/kernel/livepatch/core.h
@@ -18,6 +18,7 @@ void klp_free_replaced_patches_async(struct klp_patch *new_patch);
void klp_unpatch_replaced_patches(struct klp_patch *new_patch);
void klp_discard_nops(struct klp_patch *new_patch);
struct klp_func *klp_find_func(struct klp_object *obj, struct klp_func *func);
+bool klp_patch_replaces(struct klp_patch *new_patch, struct klp_patch *old_patch);
static inline bool klp_is_object_loaded(struct klp_object *obj)
{
diff --git a/kernel/livepatch/state.c b/kernel/livepatch/state.c
index 2565d039ade0..3b262df48c35 100644
--- a/kernel/livepatch/state.c
+++ b/kernel/livepatch/state.c
@@ -85,24 +85,63 @@ 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 (klp_patch_replaces(patch, old_patch)) {
+ /*
+ * If the new livepatch will replace the old one, it must
+ * handle all already modified states (cumulative 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 "provides" 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.
+ * Refuse loading a livepatch which would want to modify a function
+ * which is already livepatched by a patch that will not be replaced.
+ * A patch is replaced if it has the same provides id or if its
+ * provides id is in the new patch's obsoletes list or if the new patch's
+ * provides id is in the old patch's obsoletes list.
*/
+static bool klp_has_function_conflict(struct klp_patch *patch,
+ struct klp_patch *old_patch)
+{
+ struct klp_object *obj, *old_obj;
+ struct klp_func *func;
+
+ if (klp_patch_replaces(patch, old_patch))
+ return false;
+
+ klp_for_each_object(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(obj, func) {
+ if (klp_find_func(old_obj, func))
+ return true;
+ }
+ }
+ }
+ return false;
+}
+
+/* Check that the new livepatch will not break the existing system states. */
bool klp_is_patch_compatible(struct klp_patch *patch)
{
struct klp_patch *old_patch;
@@ -110,9 +149,12 @@ bool klp_is_patch_compatible(struct klp_patch *patch)
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 (klp_has_function_conflict(patch, old_patch))
+ return false;
}
return true;
diff --git a/kernel/livepatch/transition.c b/kernel/livepatch/transition.c
index 2351a19ac2a9..ef70ff582da1 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);
}
@@ -496,10 +496,10 @@ void klp_try_complete_transition(void)
* klp_complete_transition() but it is called also
* from klp_cancel_transition().
*/
- if (!patch->enabled)
- klp_free_patch_async(patch);
- else if (patch->replace)
+ if (patch->enabled)
klp_free_replaced_patches_async(patch);
+ else
+ klp_free_patch_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 &&
+ klp_patch_replaces(klp_transition_patch, patch))
patch->forced = true;
}
}
diff --git a/scripts/livepatch/init.c b/scripts/livepatch/init.c
index f14d8c8fb35f..1eb124191cf2 100644
--- a/scripts/livepatch/init.c
+++ b/scripts/livepatch/init.c
@@ -8,6 +8,11 @@
#include <linux/kernel.h>
#include <linux/slab.h>
#include <linux/livepatch.h>
+#include <linux/string.h>
+
+#ifdef KLP_OBSOLETES
+static unsigned int klp_obsoletes[] = { KLP_OBSOLETES };
+#endif
static struct klp_patch *patch;
@@ -50,8 +55,6 @@ static int __init livepatch_mod_init(void)
funcs = kzalloc(sizeof(struct klp_func) * (nr_funcs + 1), GFP_KERNEL);
if (!funcs) {
ret = -ENOMEM;
- for (int j = 0; j < i; j++)
- kfree(objs[i].funcs);
goto err_free_objs;
}
@@ -72,15 +75,19 @@ static int __init livepatch_mod_init(void)
/* TODO patch->states */
-#ifdef KLP_NO_REPLACE
- patch->replace = false;
-#else
- patch->replace = true;
+#ifdef KLP_PROVIDES
+ patch->provides = KLP_PROVIDES;
+#endif
+#ifdef KLP_OBSOLETES
+ patch->obsoletes = klp_obsoletes;
+ patch->nr_obsoletes = ARRAY_SIZE(klp_obsoletes);
#endif
return klp_enable_patch(patch);
err_free_objs:
+ for (int i = 0; i < nr_objs; i++)
+ kfree(objs[i].funcs);
kfree(objs);
err_free_patch:
kfree(patch);
diff --git a/scripts/livepatch/klp-build b/scripts/livepatch/klp-build
index c4a7acf8edc3..95472224680a 100755
--- a/scripts/livepatch/klp-build
+++ b/scripts/livepatch/klp-build
@@ -21,7 +21,8 @@ shopt -s lastpipe
unset DEBUG_CLONE DIFF_CHECKSUM SKIP_CLEANUP VERBOSE XTRACE
-REPLACE=1
+PROVIDES=0
+OBSOLETES=""
SHORT_CIRCUIT=0
JOBS="$(getconf _NPROCESSORS_ONLN)"
shopt -o xtrace | grep -q 'on' && XTRACE=1
@@ -132,7 +133,8 @@ 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
+ -p, --provides=<id> Set the provides id for this livepatch
+ -r, --obsoletes=<ids> Set the obsoletes ids array (e.g., "0,1,2")
-v, --verbose Pass V=1 to kernel/module builds
Advanced Options:
@@ -159,8 +161,8 @@ process_args() {
local args
local patch
- short="hfj:o:vdS:T"
- long="help,show-first-changed,jobs:,output:,no-replace,verbose,debug,short-circuit:,keep-tmp"
+ short="hfj:o:p:r:vdS:T"
+ long="help,show-first-changed,jobs:,output:,provides:,obsoletes:,verbose,debug,short-circuit:,keep-tmp"
args=$(getopt --options "$short" --longoptions "$long" -- "$@") || {
echo; usage; exit
@@ -189,9 +191,13 @@ process_args() {
NAME="$(module_name_string "$NAME")"
shift 2
;;
- --no-replace)
- REPLACE=0
- shift
+ -p | --provides)
+ PROVIDES="$2"
+ shift 2
+ ;;
+ -r | --obsoletes)
+ OBSOLETES="$2"
+ shift 2
;;
-v | --verbose)
VERBOSE=1
@@ -847,7 +853,8 @@ build_patch_module() {
cflags=("-ffunction-sections")
cflags+=("-fdata-sections")
- [[ $REPLACE -eq 0 ]] && cflags+=("-DKLP_NO_REPLACE")
+ cflags+=("-DKLP_PROVIDES=$PROVIDES")
+ [[ -n "$OBSOLETES" ]] && cflags+=("-DKLP_OBSOLETES=$OBSOLETES")
cmd=("make")
if [[ -v VERBOSE ]]; then
--
2.52.0
^ permalink raw reply related [flat|nested] 24+ messages in thread* Re: [PATCH v10 5/9] livepatch: Implement provides and obsoletes for scoped atomic replace
2026-09-30 3:03 ` [PATCH v10 5/9] livepatch: Implement provides and obsoletes for scoped atomic replace Yafang Shao
@ 2026-10-02 12:01 ` Miroslav Benes
2026-10-02 17:06 ` Petr Mladek
1 sibling, 0 replies; 24+ messages in thread
From: Miroslav Benes @ 2026-10-02 12:01 UTC (permalink / raw)
To: Yafang Shao
Cc: jpoimboe, jikos, mbenes, pmladek, joe.lawrence, song,
live-patching
On Wed, 30 Sep 2026 11:03:19 +0800, Yafang Shao <laoar.shao@gmail.com> wrote:
> The current bool replace flag is too coarse: it is either all or
> nothing. A livepatch with .replace=true replaces ALL existing
> livepatches, which is safe but inflexible. There is no way to have
> multiple independent livepatch sets coexist on the same system.
>
> Replace it with a more flexible model using two new fields in
> struct klp_patch:
>
> [...]
Acked-by: Miroslav Benes <mbenes@suse.cz>
--
Miroslav
^ permalink raw reply [flat|nested] 24+ messages in thread
* Re: [PATCH v10 5/9] livepatch: Implement provides and obsoletes for scoped atomic replace
2026-09-30 3:03 ` [PATCH v10 5/9] livepatch: Implement provides and obsoletes for scoped atomic replace Yafang Shao
2026-10-02 12:01 ` Miroslav Benes
@ 2026-10-02 17:06 ` Petr Mladek
2026-10-04 15:46 ` Yafang Shao
1 sibling, 1 reply; 24+ messages in thread
From: Petr Mladek @ 2026-10-02 17:06 UTC (permalink / raw)
To: Yafang Shao; +Cc: jpoimboe, jikos, mbenes, joe.lawrence, song, live-patching
On Wed 2026-09-30 11:03:19, Yafang Shao wrote:
> The current bool replace flag is too coarse: it is either all or
> nothing. A livepatch with .replace=true replaces ALL existing
> livepatches, which is safe but inflexible. There is no way to have
> multiple independent livepatch sets coexist on the same system.
>
> Replace it with a more flexible model using two new fields in
> struct klp_patch:
>
> - provides: an unsigned int id identifying the changes made by
> the related livepatch. Where the changes are a set
> of modified objects, functions, and used callbacks,
> shadow variable ids, and state ids.
>
> - obsoletes: an optional array of unsigned int ids specifying
> additional provides ids to be replaced. This allows a new patch
> to explicitly obsolete patches from different scopes.
>
> A new livepatch atomically replaces any existing livepatch that
> meets any of the following conditions:
> 1. It has the same provides id as the new patch,
> 2. Its provides id is listed in the new patch's obsoletes list, or
> 3. The new patch's provides id is listed in its obsoletes list
> (the obsoletes relationship is symmetric).
>
> The obsoletes relationship is symmetric: two livepatches that
> obsolete each other are mutually exclusive. Loading either one
> atomically removes the other, so they can never be enabled at the
> same time. This allows swapping between two independently prepared
> livepatches that cover the same scope, in either direction.
Honestly, I am confused by the 3rd rule and by the last paragraph.
It is not compatible with my understanding of the provides/obsoletes
semantic.
In my mental model, the provides and obsoletes define which
way is supported. Let me show an example, let's have:
Livepatch A:
+ provides: 1
+ obsoletes:
Livepatch B:
+ provides: 2
+ obsoletes: 1
Livepatch C:
+ provides: 3
+ obsoletes: 1,2
Livepatch D:
+ provides: 2
+ obsoletes: 1,3
How is this supposed to work, please?
In my mental model:
Livepatch A:
+ can be replaced by B, C, or D because they all obsolete 1
+ can _not_ replace B, C, D because A does not obsolete 2 and/or 3.
Livepatch B:
+ can be replaced by C because C obsoletes both 1 and 2
+ can be replaced by D beause D provides 2 and obsoletes 1
+ can't replace C or D because B neither provides not obsoletes 3.
Livepatch C:
+ can be replaced by D because D provides and obsoletes
all ids which are proviced and obsoleted by D
+ can replace D from the same reasons.
OK, I understand that it might be practical to allow replacing
the patches just because they are "conflicting". As a kind
of revert/downgrade.
It might help to rename "obsoletes" to "conflicts". And just
say that the livepatch replaces livepatch with the same id
in provides and also all conflicting patches.
But then I see another problem. We have three types of conflicts:
1. same id in provides/obsoletes/conflicts
2. same livepatches function
3. same livepatch state id
It is weird that we would silently replace livepatches with 1st
type conflict. But we would refuse to load the livepatch with
the 2nd and/or 3rd type conflict.
Note that the inconsistent handling of conflicts exists in
this patch even when the array is called "obsoletes". My proposal
to rename it to "conflicts" just helped me to see this
inconsistence.
Any option/idea? Yet another naming?
One more thing, the commit message does not describe that
the livepatches might conflict when livepatching the same
function or using the same state id. It is important part
of the semantic and must be documented.
Best Regards,
Petr
PS: I am sorry but I won't be able to finish the review before
the Plumbers conference. But I am happy that I have found
the above problem. We could discuss it at the conference
and come up with some sane naming and semantic.
^ permalink raw reply [flat|nested] 24+ messages in thread* Re: [PATCH v10 5/9] livepatch: Implement provides and obsoletes for scoped atomic replace
2026-10-02 17:06 ` Petr Mladek
@ 2026-10-04 15:46 ` Yafang Shao
2026-10-08 10:30 ` Petr Mladek
0 siblings, 1 reply; 24+ messages in thread
From: Yafang Shao @ 2026-10-04 15:46 UTC (permalink / raw)
To: Petr Mladek; +Cc: jpoimboe, jikos, mbenes, joe.lawrence, song, live-patching
On Sat, Oct 3, 2026 at 1:06 AM Petr Mladek <pmladek@suse.com> wrote:
>
> On Wed 2026-09-30 11:03:19, Yafang Shao wrote:
> > The current bool replace flag is too coarse: it is either all or
> > nothing. A livepatch with .replace=true replaces ALL existing
> > livepatches, which is safe but inflexible. There is no way to have
> > multiple independent livepatch sets coexist on the same system.
> >
> > Replace it with a more flexible model using two new fields in
> > struct klp_patch:
> >
> > - provides: an unsigned int id identifying the changes made by
> > the related livepatch. Where the changes are a set
> > of modified objects, functions, and used callbacks,
> > shadow variable ids, and state ids.
> >
> > - obsoletes: an optional array of unsigned int ids specifying
> > additional provides ids to be replaced. This allows a new patch
> > to explicitly obsolete patches from different scopes.
> >
> > A new livepatch atomically replaces any existing livepatch that
> > meets any of the following conditions:
> > 1. It has the same provides id as the new patch,
> > 2. Its provides id is listed in the new patch's obsoletes list, or
> > 3. The new patch's provides id is listed in its obsoletes list
> > (the obsoletes relationship is symmetric).
> >
> > The obsoletes relationship is symmetric: two livepatches that
> > obsolete each other are mutually exclusive. Loading either one
> > atomically removes the other, so they can never be enabled at the
> > same time. This allows swapping between two independently prepared
> > livepatches that cover the same scope, in either direction.
>
> Honestly, I am confused by the 3rd rule and by the last paragraph.
> It is not compatible with my understanding of the provides/obsoletes
> semantic.
Correct. In package management semantics, Obsoletes is not a symmetric
relationship.
>
> In my mental model, the provides and obsoletes define which
> way is supported. Let me show an example, let's have:
>
> Livepatch A:
> + provides: 1
> + obsoletes:
>
> Livepatch B:
> + provides: 2
> + obsoletes: 1
>
> Livepatch C:
> + provides: 3
> + obsoletes: 1,2
>
> Livepatch D:
> + provides: 2
> + obsoletes: 1,3
>
> How is this supposed to work, please?
>
> In my mental model:
>
> Livepatch A:
> + can be replaced by B, C, or D because they all obsolete 1
> + can _not_ replace B, C, D because A does not obsolete 2 and/or 3.
>
> Livepatch B:
> + can be replaced by C because C obsoletes both 1 and 2
> + can be replaced by D beause D provides 2 and obsoletes 1
> + can't replace C or D because B neither provides not obsoletes 3.
>
> Livepatch C:
> + can be replaced by D because D provides and obsoletes
> all ids which are proviced and obsoleted by D
>
> + can replace D from the same reasons.
>
>
> OK, I understand that it might be practical to allow replacing
> the patches just because they are "conflicting". As a kind
> of revert/downgrade.
Right. This will let us easily roll back the buggy livepatch and
reinstall the old one as part of the rollout.
>
> It might help to rename "obsoletes" to "conflicts". And just
> say that the livepatch replaces livepatch with the same id
> in provides and also all conflicting patches.
>
> But then I see another problem. We have three types of conflicts:
>
> 1. same id in provides/obsoletes/conflicts
> 2. same livepatches function
> 3. same livepatch state id
>
> It is weird that we would silently replace livepatches with 1st
> type conflict. But we would refuse to load the livepatch with
> the 2nd and/or 3rd type conflict.
>
> Note that the inconsistent handling of conflicts exists in
> this patch even when the array is called "obsoletes". My proposal
> to rename it to "conflicts" just helped me to see this
> inconsistence.
>
> Any option/idea? Yet another naming?
Perhaps it could use a better name, but I'm not good at naming things ;)
>
> One more thing, the commit message does not describe that
> the livepatches might conflict when livepatching the same
> function or using the same state id. It is important part
> of the semantic and must be documented.
Sure. I will update it.
>
> Best Regards,
> Petr
>
> PS: I am sorry but I won't be able to finish the review before
> the Plumbers conference. But I am happy that I have found
> the above problem. We could discuss it at the conference
> and come up with some sane naming and semantic.
I can't be there in person, but I believe we'll come up with a
sensible name or semantic. I'll hold off until we reach a final
conclusion.
--
Regards
Yafang
^ permalink raw reply [flat|nested] 24+ messages in thread
* Re: [PATCH v10 5/9] livepatch: Implement provides and obsoletes for scoped atomic replace
2026-10-04 15:46 ` Yafang Shao
@ 2026-10-08 10:30 ` Petr Mladek
2026-10-08 11:04 ` Yafang Shao
0 siblings, 1 reply; 24+ messages in thread
From: Petr Mladek @ 2026-10-08 10:30 UTC (permalink / raw)
To: Yafang Shao; +Cc: jpoimboe, jikos, mbenes, joe.lawrence, song, live-patching
On Sun 2026-10-04 23:46:29, Yafang Shao wrote:
> On Sat, Oct 3, 2026 at 1:06 AM Petr Mladek <pmladek@suse.com> wrote:
> >
> > On Wed 2026-09-30 11:03:19, Yafang Shao wrote:
> > > The current bool replace flag is too coarse: it is either all or
> > > nothing. A livepatch with .replace=true replaces ALL existing
> > > livepatches, which is safe but inflexible. There is no way to have
> > > multiple independent livepatch sets coexist on the same system.
> > >
> > > Replace it with a more flexible model using two new fields in
> > > struct klp_patch:
> > >
> > > - provides: an unsigned int id identifying the changes made by
> > > the related livepatch. Where the changes are a set
> > > of modified objects, functions, and used callbacks,
> > > shadow variable ids, and state ids.
> > >
> > > - obsoletes: an optional array of unsigned int ids specifying
> > > additional provides ids to be replaced. This allows a new patch
> > > to explicitly obsolete patches from different scopes.
> > >
> > > A new livepatch atomically replaces any existing livepatch that
> > > meets any of the following conditions:
> > > 1. It has the same provides id as the new patch,
> > > 2. Its provides id is listed in the new patch's obsoletes list, or
> > > 3. The new patch's provides id is listed in its obsoletes list
> > > (the obsoletes relationship is symmetric).
> > >
> > > The obsoletes relationship is symmetric: two livepatches that
> > > obsolete each other are mutually exclusive. Loading either one
> > > atomically removes the other, so they can never be enabled at the
> > > same time. This allows swapping between two independently prepared
> > > livepatches that cover the same scope, in either direction.
> >
> > Honestly, I am confused by the 3rd rule and by the last paragraph.
> > It is not compatible with my understanding of the provides/obsoletes
> > semantic.
>
> Correct. In package management semantics, Obsoletes is not a symmetric
> relationship.
>
> >
> > In my mental model, the provides and obsoletes define which
> > way is supported. Let me show an example, let's have:
> >
> > Livepatch A:
> > + provides: 1
> > + obsoletes:
> >
> > Livepatch B:
> > + provides: 2
> > + obsoletes: 1
> >
> > Livepatch C:
> > + provides: 3
> > + obsoletes: 1,2
> >
> > Livepatch D:
> > + provides: 2
> > + obsoletes: 1,3
> >
> > How is this supposed to work, please?
> >
> > In my mental model:
> >
> > Livepatch A:
> > + can be replaced by B, C, or D because they all obsolete 1
> > + can _not_ replace B, C, D because A does not obsolete 2 and/or 3.
> >
> > Livepatch B:
> > + can be replaced by C because C obsoletes both 1 and 2
> > + can be replaced by D beause D provides 2 and obsoletes 1
> > + can't replace C or D because B neither provides not obsoletes 3.
> >
> > Livepatch C:
> > + can be replaced by D because D provides and obsoletes
> > all ids which are proviced and obsoleted by D
> >
> > + can replace D from the same reasons.
> >
> >
> > OK, I understand that it might be practical to allow replacing
> > the patches just because they are "conflicting". As a kind
> > of revert/downgrade.
>
> Right. This will let us easily roll back the buggy livepatch and
> reinstall the old one as part of the rollout.
>
> >
> > It might help to rename "obsoletes" to "conflicts". And just
> > say that the livepatch replaces livepatch with the same id
> > in provides and also all conflicting patches.
> >
> > But then I see another problem. We have three types of conflicts:
> >
> > 1. same id in provides/obsoletes/conflicts
> > 2. same livepatches function
> > 3. same livepatch state id
> >
> > It is weird that we would silently replace livepatches with 1st
> > type conflict. But we would refuse to load the livepatch with
> > the 2nd and/or 3rd type conflict.
> >
> > Note that the inconsistent handling of conflicts exists in
> > this patch even when the array is called "obsoletes". My proposal
> > to rename it to "conflicts" just helped me to see this
> > inconsistence.
> >
> > Any option/idea? Yet another naming?
>
> Perhaps it could use a better name, but I'm not good at naming things ;)
>
> >
> > One more thing, the commit message does not describe that
> > the livepatches might conflict when livepatching the same
> > function or using the same state id. It is important part
> > of the semantic and must be documented.
>
> Sure. I will update it.
>
> >
> > Best Regards,
> > Petr
> >
> > PS: I am sorry but I won't be able to finish the review before
> > the Plumbers conference. But I am happy that I have found
> > the above problem. We could discuss it at the conference
> > and come up with some sane naming and semantic.
>
> I can't be there in person, but I believe we'll come up with a
> sensible name or semantic. I'll hold off until we reach a final
> conclusion.
I am not sure if you saw the video from the the conference.
It looked like we were going to nack this feature because
installing more livepatches on the system was considered
too dangerous.
In particular, it allows using livepatches provided by different
teams/vendors. It is almost impossible to prevent incompatible
changes and breaking the system. The check preventing livepatching
the same function or using the same state IDs is just the best
effort but it still allows many hidden incompatibilities.
We should rather discourage people installing more livepatches
in parallel.
Anyway, we had a follow-up discussion and agreed to accept this
feature in the end. We already allow to install more livepatches
in parallel. So this patchset is not adding any new danger.
Instead, it reduces the danger a bit without complicating the
code too much.
Also we understand that there might be some use cases for this
feature. And it might be safe when used carefully.
We also discussed the semantic of the obsolete field and we
would like to it simple. The livepatch with a provides ID should
replace a livepatch with another provides ID _only_ when the other
ID is mentioned in obsoletes.
By other words, please remove the symmetry and "support" for
a rollback. The symmetry causes some confusion as explained
before. I would summarize it by two reasons:
+ In the packaging world, the "obsoletes" behavior is not
symmetric.
+ The symmetric behavior adds a "conflict" semantic. But
this particular conflict is handled another way than
the other conflicts (livepatches functions, state IDs).
I mean replacing an already enabled livepatch vs.
rejecting the new one.
In addition, we do not support the rollback in all situations,
for example, let's have:
Livepatch A:
+ provides: 1
+ obsoletes:
Livepatch B:
+ provides: 2
+ obsoletes:
Livepatch C:
+ provides: 3
+ obsoletes: 1,2
The meaning is:
+ A and B are independent livepatches.
+ C adds some dependency and has to replace both A and B.
Here a proper rollback from C to A+B would require loading both
A and B livepatches synchronously. It would require adding
special support into the module loader and the livepatch core code.
We do not want to add this complexity. Especially, because
the parallel installation of livepatches is considered a bad
practice.
A partial workaround might be to allow changing "obsoletes" via
a livepatch module parameter. It would allow to do the
rollback in 2 steps:
modprobe obsoletes=3 livepatch_A
modprobe livepatch_B
It is still not an atomic rollback. But it is better than
without the parameter which would require 3 steps:
echo 0 > /sys/kernel/livepatch/livepatch_C/enabled
modprobe livepatch_A
modprobe livepatch_B
Summary:
We are open to accept this feature but please:
+ Remove the symmetric handling of obsolete flag.
+ Properly document, in both the commit message and
documentation, when the livepatches will be replaced
and when they will conflict.
+ Document the pitfalls with the rollback.
+ Add a warning that installing more livepatches in parallel
is dangerous, especially when they are provided by different
teams or vendors. The built-in checks of possible conflicts
do not prevent potential incompatibilities and breakages.
Heh, I hope that I got it right.
Best Regards,
Petr
^ permalink raw reply [flat|nested] 24+ messages in thread* Re: [PATCH v10 5/9] livepatch: Implement provides and obsoletes for scoped atomic replace
2026-10-08 10:30 ` Petr Mladek
@ 2026-10-08 11:04 ` Yafang Shao
2026-10-09 8:33 ` Yafang Shao
0 siblings, 1 reply; 24+ messages in thread
From: Yafang Shao @ 2026-10-08 11:04 UTC (permalink / raw)
To: Petr Mladek; +Cc: jpoimboe, jikos, mbenes, joe.lawrence, song, live-patching
On Thu, Oct 8, 2026 at 6:30 PM Petr Mladek <pmladek@suse.com> wrote:
>
> On Sun 2026-10-04 23:46:29, Yafang Shao wrote:
> > On Sat, Oct 3, 2026 at 1:06 AM Petr Mladek <pmladek@suse.com> wrote:
> > >
> > > On Wed 2026-09-30 11:03:19, Yafang Shao wrote:
> > > > The current bool replace flag is too coarse: it is either all or
> > > > nothing. A livepatch with .replace=true replaces ALL existing
> > > > livepatches, which is safe but inflexible. There is no way to have
> > > > multiple independent livepatch sets coexist on the same system.
> > > >
> > > > Replace it with a more flexible model using two new fields in
> > > > struct klp_patch:
> > > >
> > > > - provides: an unsigned int id identifying the changes made by
> > > > the related livepatch. Where the changes are a set
> > > > of modified objects, functions, and used callbacks,
> > > > shadow variable ids, and state ids.
> > > >
> > > > - obsoletes: an optional array of unsigned int ids specifying
> > > > additional provides ids to be replaced. This allows a new patch
> > > > to explicitly obsolete patches from different scopes.
> > > >
> > > > A new livepatch atomically replaces any existing livepatch that
> > > > meets any of the following conditions:
> > > > 1. It has the same provides id as the new patch,
> > > > 2. Its provides id is listed in the new patch's obsoletes list, or
> > > > 3. The new patch's provides id is listed in its obsoletes list
> > > > (the obsoletes relationship is symmetric).
> > > >
> > > > The obsoletes relationship is symmetric: two livepatches that
> > > > obsolete each other are mutually exclusive. Loading either one
> > > > atomically removes the other, so they can never be enabled at the
> > > > same time. This allows swapping between two independently prepared
> > > > livepatches that cover the same scope, in either direction.
> > >
> > > Honestly, I am confused by the 3rd rule and by the last paragraph.
> > > It is not compatible with my understanding of the provides/obsoletes
> > > semantic.
> >
> > Correct. In package management semantics, Obsoletes is not a symmetric
> > relationship.
> >
> > >
> > > In my mental model, the provides and obsoletes define which
> > > way is supported. Let me show an example, let's have:
> > >
> > > Livepatch A:
> > > + provides: 1
> > > + obsoletes:
> > >
> > > Livepatch B:
> > > + provides: 2
> > > + obsoletes: 1
> > >
> > > Livepatch C:
> > > + provides: 3
> > > + obsoletes: 1,2
> > >
> > > Livepatch D:
> > > + provides: 2
> > > + obsoletes: 1,3
> > >
> > > How is this supposed to work, please?
> > >
> > > In my mental model:
> > >
> > > Livepatch A:
> > > + can be replaced by B, C, or D because they all obsolete 1
> > > + can _not_ replace B, C, D because A does not obsolete 2 and/or 3.
> > >
> > > Livepatch B:
> > > + can be replaced by C because C obsoletes both 1 and 2
> > > + can be replaced by D beause D provides 2 and obsoletes 1
> > > + can't replace C or D because B neither provides not obsoletes 3.
> > >
> > > Livepatch C:
> > > + can be replaced by D because D provides and obsoletes
> > > all ids which are proviced and obsoleted by D
> > >
> > > + can replace D from the same reasons.
> > >
> > >
> > > OK, I understand that it might be practical to allow replacing
> > > the patches just because they are "conflicting". As a kind
> > > of revert/downgrade.
> >
> > Right. This will let us easily roll back the buggy livepatch and
> > reinstall the old one as part of the rollout.
> >
> > >
> > > It might help to rename "obsoletes" to "conflicts". And just
> > > say that the livepatch replaces livepatch with the same id
> > > in provides and also all conflicting patches.
> > >
> > > But then I see another problem. We have three types of conflicts:
> > >
> > > 1. same id in provides/obsoletes/conflicts
> > > 2. same livepatches function
> > > 3. same livepatch state id
> > >
> > > It is weird that we would silently replace livepatches with 1st
> > > type conflict. But we would refuse to load the livepatch with
> > > the 2nd and/or 3rd type conflict.
> > >
> > > Note that the inconsistent handling of conflicts exists in
> > > this patch even when the array is called "obsoletes". My proposal
> > > to rename it to "conflicts" just helped me to see this
> > > inconsistence.
> > >
> > > Any option/idea? Yet another naming?
> >
> > Perhaps it could use a better name, but I'm not good at naming things ;)
> >
> > >
> > > One more thing, the commit message does not describe that
> > > the livepatches might conflict when livepatching the same
> > > function or using the same state id. It is important part
> > > of the semantic and must be documented.
> >
> > Sure. I will update it.
> >
> > >
> > > Best Regards,
> > > Petr
> > >
> > > PS: I am sorry but I won't be able to finish the review before
> > > the Plumbers conference. But I am happy that I have found
> > > the above problem. We could discuss it at the conference
> > > and come up with some sane naming and semantic.
> >
> > I can't be there in person, but I believe we'll come up with a
> > sensible name or semantic. I'll hold off until we reach a final
> > conclusion.
>
> I am not sure if you saw the video from the the conference.
Song has shared the results with me offline. Thank you all so much!
> It looked like we were going to nack this feature because
> installing more livepatches on the system was considered
> too dangerous.
>
> In particular, it allows using livepatches provided by different
> teams/vendors. It is almost impossible to prevent incompatible
> changes and breaking the system. The check preventing livepatching
> the same function or using the same state IDs is just the best
> effort but it still allows many hidden incompatibilities.
>
> We should rather discourage people installing more livepatches
> in parallel.
>
> Anyway, we had a follow-up discussion and agreed to accept this
> feature in the end. We already allow to install more livepatches
> in parallel. So this patchset is not adding any new danger.
> Instead, it reduces the danger a bit without complicating the
> code too much.
>
> Also we understand that there might be some use cases for this
> feature. And it might be safe when used carefully.
>
>
> We also discussed the semantic of the obsolete field and we
> would like to it simple. The livepatch with a provides ID should
> replace a livepatch with another provides ID _only_ when the other
> ID is mentioned in obsoletes.
>
> By other words, please remove the symmetry and "support" for
> a rollback. The symmetry causes some confusion as explained
> before. I would summarize it by two reasons:
>
> + In the packaging world, the "obsoletes" behavior is not
> symmetric.
>
> + The symmetric behavior adds a "conflict" semantic. But
> this particular conflict is handled another way than
> the other conflicts (livepatches functions, state IDs).
> I mean replacing an already enabled livepatch vs.
> rejecting the new one.
>
> In addition, we do not support the rollback in all situations,
> for example, let's have:
>
> Livepatch A:
> + provides: 1
> + obsoletes:
>
> Livepatch B:
> + provides: 2
> + obsoletes:
>
> Livepatch C:
> + provides: 3
> + obsoletes: 1,2
>
> The meaning is:
>
> + A and B are independent livepatches.
> + C adds some dependency and has to replace both A and B.
>
> Here a proper rollback from C to A+B would require loading both
> A and B livepatches synchronously. It would require adding
> special support into the module loader and the livepatch core code.
> We do not want to add this complexity. Especially, because
> the parallel installation of livepatches is considered a bad
> practice.
>
> A partial workaround might be to allow changing "obsoletes" via
> a livepatch module parameter. It would allow to do the
> rollback in 2 steps:
>
> modprobe obsoletes=3 livepatch_A
> modprobe livepatch_B
>
> It is still not an atomic rollback. But it is better than
> without the parameter which would require 3 steps:
>
> echo 0 > /sys/kernel/livepatch/livepatch_C/enabled
> modprobe livepatch_A
> modprobe livepatch_B
>
>
Thank you for the review! I had actually sent v11 before your email
arrived — you can find it here:
https://lore.kernel.org/live-patching/20261008095830.26308-1-laoar.shao@gmail.com/
Regarding your summary:
>
> Summary:
>
> We are open to accept this feature but please:
>
> + Remove the symmetric handling of obsolete flag.
Done in v11.
>
> + Properly document, in both the commit message and
> documentation, when the livepatches will be replaced
> and when they will conflict.
Done in v11.
>
> + Document the pitfalls with the rollback.
Not yet addressed — I'll cover this in the next revision.
>
> + Add a warning that installing more livepatches in parallel
> is dangerous, especially when they are provided by different
> teams or vendors. The built-in checks of possible conflicts
> do not prevent potential incompatibilities and breakages.
Not yet addressed — planned for the next revision as well.
>
>
> Heh, I hope that I got it right.
If you have a chance, could you take a look at v11 and let me know if
there's anything else I should address?
Thanks again for your time and guidance!
--
Regards
Yafang
^ permalink raw reply [flat|nested] 24+ messages in thread
* Re: [PATCH v10 5/9] livepatch: Implement provides and obsoletes for scoped atomic replace
2026-10-08 11:04 ` Yafang Shao
@ 2026-10-09 8:33 ` Yafang Shao
0 siblings, 0 replies; 24+ messages in thread
From: Yafang Shao @ 2026-10-09 8:33 UTC (permalink / raw)
To: Petr Mladek; +Cc: jpoimboe, jikos, mbenes, joe.lawrence, song, live-patching
On Thu, Oct 8, 2026 at 7:04 PM Yafang Shao <laoar.shao@gmail.com> wrote:
>
> On Thu, Oct 8, 2026 at 6:30 PM Petr Mladek <pmladek@suse.com> wrote:
> >
> > On Sun 2026-10-04 23:46:29, Yafang Shao wrote:
> > > On Sat, Oct 3, 2026 at 1:06 AM Petr Mladek <pmladek@suse.com> wrote:
> > > >
> > > > On Wed 2026-09-30 11:03:19, Yafang Shao wrote:
> > > > > The current bool replace flag is too coarse: it is either all or
> > > > > nothing. A livepatch with .replace=true replaces ALL existing
> > > > > livepatches, which is safe but inflexible. There is no way to have
> > > > > multiple independent livepatch sets coexist on the same system.
> > > > >
> > > > > Replace it with a more flexible model using two new fields in
> > > > > struct klp_patch:
> > > > >
> > > > > - provides: an unsigned int id identifying the changes made by
> > > > > the related livepatch. Where the changes are a set
> > > > > of modified objects, functions, and used callbacks,
> > > > > shadow variable ids, and state ids.
> > > > >
> > > > > - obsoletes: an optional array of unsigned int ids specifying
> > > > > additional provides ids to be replaced. This allows a new patch
> > > > > to explicitly obsolete patches from different scopes.
> > > > >
> > > > > A new livepatch atomically replaces any existing livepatch that
> > > > > meets any of the following conditions:
> > > > > 1. It has the same provides id as the new patch,
> > > > > 2. Its provides id is listed in the new patch's obsoletes list, or
> > > > > 3. The new patch's provides id is listed in its obsoletes list
> > > > > (the obsoletes relationship is symmetric).
> > > > >
> > > > > The obsoletes relationship is symmetric: two livepatches that
> > > > > obsolete each other are mutually exclusive. Loading either one
> > > > > atomically removes the other, so they can never be enabled at the
> > > > > same time. This allows swapping between two independently prepared
> > > > > livepatches that cover the same scope, in either direction.
> > > >
> > > > Honestly, I am confused by the 3rd rule and by the last paragraph.
> > > > It is not compatible with my understanding of the provides/obsoletes
> > > > semantic.
> > >
> > > Correct. In package management semantics, Obsoletes is not a symmetric
> > > relationship.
> > >
> > > >
> > > > In my mental model, the provides and obsoletes define which
> > > > way is supported. Let me show an example, let's have:
> > > >
> > > > Livepatch A:
> > > > + provides: 1
> > > > + obsoletes:
> > > >
> > > > Livepatch B:
> > > > + provides: 2
> > > > + obsoletes: 1
> > > >
> > > > Livepatch C:
> > > > + provides: 3
> > > > + obsoletes: 1,2
> > > >
> > > > Livepatch D:
> > > > + provides: 2
> > > > + obsoletes: 1,3
> > > >
> > > > How is this supposed to work, please?
> > > >
> > > > In my mental model:
> > > >
> > > > Livepatch A:
> > > > + can be replaced by B, C, or D because they all obsolete 1
> > > > + can _not_ replace B, C, D because A does not obsolete 2 and/or 3.
> > > >
> > > > Livepatch B:
> > > > + can be replaced by C because C obsoletes both 1 and 2
> > > > + can be replaced by D beause D provides 2 and obsoletes 1
> > > > + can't replace C or D because B neither provides not obsoletes 3.
> > > >
> > > > Livepatch C:
> > > > + can be replaced by D because D provides and obsoletes
> > > > all ids which are proviced and obsoleted by D
> > > >
> > > > + can replace D from the same reasons.
> > > >
> > > >
> > > > OK, I understand that it might be practical to allow replacing
> > > > the patches just because they are "conflicting". As a kind
> > > > of revert/downgrade.
> > >
> > > Right. This will let us easily roll back the buggy livepatch and
> > > reinstall the old one as part of the rollout.
> > >
> > > >
> > > > It might help to rename "obsoletes" to "conflicts". And just
> > > > say that the livepatch replaces livepatch with the same id
> > > > in provides and also all conflicting patches.
> > > >
> > > > But then I see another problem. We have three types of conflicts:
> > > >
> > > > 1. same id in provides/obsoletes/conflicts
> > > > 2. same livepatches function
> > > > 3. same livepatch state id
> > > >
> > > > It is weird that we would silently replace livepatches with 1st
> > > > type conflict. But we would refuse to load the livepatch with
> > > > the 2nd and/or 3rd type conflict.
> > > >
> > > > Note that the inconsistent handling of conflicts exists in
> > > > this patch even when the array is called "obsoletes". My proposal
> > > > to rename it to "conflicts" just helped me to see this
> > > > inconsistence.
> > > >
> > > > Any option/idea? Yet another naming?
> > >
> > > Perhaps it could use a better name, but I'm not good at naming things ;)
> > >
> > > >
> > > > One more thing, the commit message does not describe that
> > > > the livepatches might conflict when livepatching the same
> > > > function or using the same state id. It is important part
> > > > of the semantic and must be documented.
> > >
> > > Sure. I will update it.
> > >
> > > >
> > > > Best Regards,
> > > > Petr
> > > >
> > > > PS: I am sorry but I won't be able to finish the review before
> > > > the Plumbers conference. But I am happy that I have found
> > > > the above problem. We could discuss it at the conference
> > > > and come up with some sane naming and semantic.
> > >
> > > I can't be there in person, but I believe we'll come up with a
> > > sensible name or semantic. I'll hold off until we reach a final
> > > conclusion.
> >
> > I am not sure if you saw the video from the the conference.
>
> Song has shared the results with me offline. Thank you all so much!
>
> > It looked like we were going to nack this feature because
> > installing more livepatches on the system was considered
> > too dangerous.
> >
> > In particular, it allows using livepatches provided by different
> > teams/vendors. It is almost impossible to prevent incompatible
> > changes and breaking the system. The check preventing livepatching
> > the same function or using the same state IDs is just the best
> > effort but it still allows many hidden incompatibilities.
> >
> > We should rather discourage people installing more livepatches
> > in parallel.
> >
> > Anyway, we had a follow-up discussion and agreed to accept this
> > feature in the end. We already allow to install more livepatches
> > in parallel. So this patchset is not adding any new danger.
> > Instead, it reduces the danger a bit without complicating the
> > code too much.
> >
> > Also we understand that there might be some use cases for this
> > feature. And it might be safe when used carefully.
> >
> >
> > We also discussed the semantic of the obsolete field and we
> > would like to it simple. The livepatch with a provides ID should
> > replace a livepatch with another provides ID _only_ when the other
> > ID is mentioned in obsoletes.
> >
> > By other words, please remove the symmetry and "support" for
> > a rollback. The symmetry causes some confusion as explained
> > before. I would summarize it by two reasons:
> >
> > + In the packaging world, the "obsoletes" behavior is not
> > symmetric.
> >
> > + The symmetric behavior adds a "conflict" semantic. But
> > this particular conflict is handled another way than
> > the other conflicts (livepatches functions, state IDs).
> > I mean replacing an already enabled livepatch vs.
> > rejecting the new one.
> >
> > In addition, we do not support the rollback in all situations,
> > for example, let's have:
> >
> > Livepatch A:
> > + provides: 1
> > + obsoletes:
> >
> > Livepatch B:
> > + provides: 2
> > + obsoletes:
> >
> > Livepatch C:
> > + provides: 3
> > + obsoletes: 1,2
> >
> > The meaning is:
> >
> > + A and B are independent livepatches.
> > + C adds some dependency and has to replace both A and B.
> >
> > Here a proper rollback from C to A+B would require loading both
> > A and B livepatches synchronously. It would require adding
> > special support into the module loader and the livepatch core code.
> > We do not want to add this complexity. Especially, because
> > the parallel installation of livepatches is considered a bad
> > practice.
> >
> > A partial workaround might be to allow changing "obsoletes" via
> > a livepatch module parameter. It would allow to do the
> > rollback in 2 steps:
> >
> > modprobe obsoletes=3 livepatch_A
> > modprobe livepatch_B
> >
> > It is still not an atomic rollback. But it is better than
> > without the parameter which would require 3 steps:
> >
> > echo 0 > /sys/kernel/livepatch/livepatch_C/enabled
> > modprobe livepatch_A
> > modprobe livepatch_B
> >
> >
>
> Thank you for the review! I had actually sent v11 before your email
> arrived — you can find it here:
>
> https://lore.kernel.org/live-patching/20261008095830.26308-1-laoar.shao@gmail.com/
>
> Regarding your summary:
>
> >
> > Summary:
> >
> > We are open to accept this feature but please:
> >
> > + Remove the symmetric handling of obsolete flag.
>
> Done in v11.
>
> >
> > + Properly document, in both the commit message and
> > documentation, when the livepatches will be replaced
> > and when they will conflict.
>
> Done in v11.
>
> >
> > + Document the pitfalls with the rollback.
>
> Not yet addressed — I'll cover this in the next revision.
>
> >
> > + Add a warning that installing more livepatches in parallel
> > is dangerous, especially when they are provided by different
> > teams or vendors. The built-in checks of possible conflicts
> > do not prevent potential incompatibilities and breakages.
>
> Not yet addressed — planned for the next revision as well.
>
> >
> >
> > Heh, I hope that I got it right.
>
> If you have a chance, could you take a look at v11 and let me know if
> there's anything else I should address?
> Thanks again for your time and guidance!
Hi Petr,
Regarding the last two comments, I've drafted the following additional
changes on top of v11. Does the wording look good to you?
If yes, I'll fold them into the series and send v12.
diff --git a/Documentation/livepatch/cumulative-patches.rst
b/Documentation/livepatch/cumulative-patches.rst
index f456b727318b..5ce04424e09d 100644
--- a/Documentation/livepatch/cumulative-patches.rst
+++ b/Documentation/livepatch/cumulative-patches.rst
@@ -25,6 +25,13 @@ the default ``provides = 0``. As long as all
livepatches use the same default
id, any livepatch replaces any other livepatch, preventing any unexpected
interactions between incompatible livepatches.
+Installing more livepatches in parallel is dangerous, especially
+when they are provided by different teams or vendors. The built-in
+checks for possible conflicts do not prevent potential
+incompatibilities and breakages. For example, two livepatches might
+modify different functions but still affect each other via shared
+data structures or global variables.
+
Provides and Obsoletes
-----------------------
@@ -109,6 +116,35 @@ Limitations:
only when the transition was not forced.
+ - The rollback is even more complicated when the new livepatch
+ obsoleted more livepatches. For example, livepatch A (provides 1)
+ and livepatch B (provides 2) are independent. Livepatch C
+ (provides 3, obsoletes 1 and 2) replaces both A and B. A proper
+ rollback from C would require loading A and B synchronously.
+ It would need special support in the module loader and
+ the livepatch core code. This is not supported.
+
+ Instead, the rollback has to be done in more steps, for example::
+
+ echo 0 > /sys/kernel/livepatch/livepatch_C/enabled
+ modprobe livepatch_A
+ modprobe livepatch_B
+
+ Note that the replaced livepatches stay loaded but disabled.
+ They have to be removed before they can be loaded again.
+ The example above assumes that they were removed in
+ the meantime.
+
+ Also, the older livepatches can be loaded only when they do
+ not conflict with the still enabled ones, see the conflict
+ rules above. The newer livepatch is expected to include the
+ fixes from the obsoleted livepatches. As a result, the
+ rollback usually has to start by disabling the newer
+ livepatch. The rollback is not atomic. The affected
+ functions run the original code until all the older
+ livepatches get loaded again.
+
+
- Only the (un)patching callbacks from the _new_ cumulative livepatch are
executed. Any callbacks from the replaced patches are ignored.
diff --git a/Documentation/livepatch/livepatch.rst
b/Documentation/livepatch/livepatch.rst
index 56c4022654d7..2567e09c74fd 100644
--- a/Documentation/livepatch/livepatch.rst
+++ b/Documentation/livepatch/livepatch.rst
@@ -357,6 +357,11 @@ In this case, the two livepatches must not modify
the same function, or use
the state with the same id. Any attempt to load an incompatible livepatch
will be rejected by the kernel.
+Installing more livepatches in parallel is dangerous, especially
+when they are provided by different teams or vendors. The built-in
+checks for possible conflicts do not prevent potential
+incompatibilities and breakages.
+
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
--
Regards
Yafang
^ permalink raw reply related [flat|nested] 24+ messages in thread
* [PATCH v10 6/9] livepatch: Deprecate stack_order
2026-09-30 3:03 [PATCH v10 0/9] livepatch: Add support for scoped atomic replace Yafang Shao
` (4 preceding siblings ...)
2026-09-30 3:03 ` [PATCH v10 5/9] livepatch: Implement provides and obsoletes for scoped atomic replace Yafang Shao
@ 2026-09-30 3:03 ` Yafang Shao
2026-09-30 3:03 ` [PATCH v10 7/9] selftests/livepatch: Add provides/obsoletes test scenarios Yafang Shao
` (2 subsequent siblings)
8 siblings, 0 replies; 24+ messages in thread
From: Yafang Shao @ 2026-09-30 3:03 UTC (permalink / raw)
To: jpoimboe, jikos, mbenes, pmladek, joe.lawrence, song
Cc: live-patching, Yafang Shao, Wardenjohn
The stack_order sysfs attribute was added to determine which active
livepatch is used for a given function. It is no longer needed because
recent changes refuse loading another livepatch which would modify
an already livepatched function unless the new livepatch would replace
the conflicting one.
[Commit message authored by Petr.]
Signed-off-by: Yafang Shao <laoar.shao@gmail.com>
Acked-by: Song Liu <song@kernel.org>
Acked-by: Miroslav Benes <mbenes@suse.cz>
Cc: Wardenjohn <zhangwarden@gmail.com>
Cc: Petr Mladek <pmladek@suse.com>
---
.../ABI/removed/sysfs-kernel-livepatch | 9 +++++++
.../ABI/testing/sysfs-kernel-livepatch | 9 -------
kernel/livepatch/core.c | 24 -------------------
3 files changed, 9 insertions(+), 33 deletions(-)
diff --git a/Documentation/ABI/removed/sysfs-kernel-livepatch b/Documentation/ABI/removed/sysfs-kernel-livepatch
index 87d57ba27326..f094bd18379f 100644
--- a/Documentation/ABI/removed/sysfs-kernel-livepatch
+++ b/Documentation/ABI/removed/sysfs-kernel-livepatch
@@ -5,3 +5,12 @@ Contact: live-patching@vger.kernel.org
Description:
An attribute which indicates whether the patch supports
atomic-replace.
+
+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 7ccfe5414ac3..8229348e74a3 100644
--- a/Documentation/ABI/testing/sysfs-kernel-livepatch
+++ b/Documentation/ABI/testing/sysfs-kernel-livepatch
@@ -70,15 +70,6 @@ Description:
livepatch that obsoletes this patch's provides id will
also be atomically replaced by this patch.
-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 82f541b16ecc..b00256323317 100644
--- a/kernel/livepatch/core.c
+++ b/kernel/livepatch/core.c
@@ -352,7 +352,6 @@ int klp_apply_section_relocs(struct module *pmod, Elf_Shdr *sechdrs,
* /sys/kernel/livepatch/<patch>/force
* /sys/kernel/livepatch/<patch>/provides
* /sys/kernel/livepatch/<patch>/obsoletes
- * /sys/kernel/livepatch/<patch>/stack_order
* /sys/kernel/livepatch/<patch>/<object>
* /sys/kernel/livepatch/<patch>/<object>/patched
* /sys/kernel/livepatch/<patch>/<object>/<function,sympos>
@@ -477,40 +476,17 @@ static ssize_t obsoletes_show(struct kobject *kobj,
return len;
}
-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);
-}
-
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 provides_kobj_attr = __ATTR_RO(provides);
static struct kobj_attribute obsoletes_kobj_attr = __ATTR_RO(obsoletes);
-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,
&provides_kobj_attr.attr,
&obsoletes_kobj_attr.attr,
- &stack_order_kobj_attr.attr,
NULL
};
ATTRIBUTE_GROUPS(klp_patch);
--
2.52.0
^ permalink raw reply related [flat|nested] 24+ messages in thread* [PATCH v10 7/9] selftests/livepatch: Add provides/obsoletes test scenarios
2026-09-30 3:03 [PATCH v10 0/9] livepatch: Add support for scoped atomic replace Yafang Shao
` (5 preceding siblings ...)
2026-09-30 3:03 ` [PATCH v10 6/9] livepatch: Deprecate stack_order Yafang Shao
@ 2026-09-30 3:03 ` Yafang Shao
2026-09-30 3:13 ` sashiko-bot
2026-10-02 12:27 ` Miroslav Benes
2026-09-30 3:03 ` [PATCH v10 8/9] selftests/livepatch: Add state test for provides/obsoletes Yafang Shao
2026-09-30 3:03 ` [PATCH v10 9/9] selftests/livepatch: Add function " Yafang Shao
8 siblings, 2 replies; 24+ messages in thread
From: Yafang Shao @ 2026-09-30 3:03 UTC (permalink / raw)
To: jpoimboe, jikos, mbenes, pmladek, joe.lawrence, song
Cc: live-patching, Yafang Shao
Now that the legacy "replace" field has been replaced by the
provides/obsoletes mechanism, add test scenarios to verify the new
behavior.
The test modules gain provides/obsoletes module parameters, and
test-provides-obsoletes.sh covers the new replace semantics: provides=0
replacement, same-provides replacement, obsoletes-based replacement
and coexistence of different provides ids.
These tests are skipped on kernels that still support the legacy
"replace" attribute.
Assisted-by: LLM
Signed-off-by: Yafang Shao <laoar.shao@gmail.com>
---
tools/testing/selftests/livepatch/Makefile | 3 +-
.../livepatch/test-provides-obsoletes.sh | 221 ++++++++++++++++++
.../test_modules/test_klp_callbacks_demo2.c | 12 +-
.../test_modules/test_klp_cmdline_lp.c | 11 +-
.../test_modules/test_klp_meminfo_lp.c | 22 +-
.../livepatch/test_modules/test_klp_state.c | 5 +-
.../livepatch/test_modules/test_klp_state2.c | 5 +-
7 files changed, 253 insertions(+), 26 deletions(-)
create mode 100755 tools/testing/selftests/livepatch/test-provides-obsoletes.sh
diff --git a/tools/testing/selftests/livepatch/Makefile b/tools/testing/selftests/livepatch/Makefile
index a080eb54a215..38f98594d883 100644
--- a/tools/testing/selftests/livepatch/Makefile
+++ b/tools/testing/selftests/livepatch/Makefile
@@ -11,7 +11,8 @@ TEST_PROGS := \
test-ftrace.sh \
test-sysfs.sh \
test-syscall.sh \
- test-kprobe.sh
+ test-kprobe.sh \
+ test-provides-obsoletes.sh
TEST_FILES := settings
diff --git a/tools/testing/selftests/livepatch/test-provides-obsoletes.sh b/tools/testing/selftests/livepatch/test-provides-obsoletes.sh
new file mode 100755
index 000000000000..a1c0ebb9ed44
--- /dev/null
+++ b/tools/testing/selftests/livepatch/test-provides-obsoletes.sh
@@ -0,0 +1,221 @@
+#!/bin/bash
+# SPDX-License-Identifier: GPL-2.0
+# Copyright (C) 2026 Yafang Shao <laoar.shao@gmail.com>
+
+. $(dirname $0)/functions.sh
+
+MOD_MEMINFO=test_klp_meminfo_lp
+MOD_CMDLINE=test_klp_cmdline_lp
+
+setup_config
+detect_provides_attr
+
+# The provides/obsoletes based tests only run on kernels that no longer
+# support the legacy "replace" attribute.
+if [[ "$HAS_PROVIDES_ATTR" != "1" ]]; then
+ skip_exit "kernel still supports the legacy replace attribute"
+fi
+
+
+# - load a livepatch with provides=0 (cmdline), then another with
+# provides=0 (meminfo). The second replaces the first (same provides).
+# - unload the remaining livepatch
+
+start_test "provides 0 replaces provides 0"
+
+load_lp $MOD_CMDLINE provides=0
+load_lp $MOD_MEMINFO provides=0
+
+check_sysfs_value "$MOD_MEMINFO" "enabled" "1"
+
+disable_lp $MOD_MEMINFO
+unload_lp $MOD_MEMINFO
+unload_lp $MOD_CMDLINE
+
+check_result "% insmod test_modules/$MOD_CMDLINE.ko provides=0
+livepatch: enabling patch '$MOD_CMDLINE'
+livepatch: '$MOD_CMDLINE': initializing patching transition
+livepatch: '$MOD_CMDLINE': starting patching transition
+livepatch: '$MOD_CMDLINE': completing patching transition
+livepatch: '$MOD_CMDLINE': patching complete
+% insmod test_modules/$MOD_MEMINFO.ko provides=0
+livepatch: enabling patch '$MOD_MEMINFO'
+livepatch: '$MOD_MEMINFO': initializing patching transition
+livepatch: '$MOD_MEMINFO': starting patching transition
+livepatch: '$MOD_MEMINFO': completing patching transition
+livepatch: '$MOD_MEMINFO': patching complete
+% echo 0 > $SYSFS_KLP_DIR/$MOD_MEMINFO/enabled
+livepatch: '$MOD_MEMINFO': initializing unpatching transition
+livepatch: '$MOD_MEMINFO': starting unpatching transition
+livepatch: '$MOD_MEMINFO': completing unpatching transition
+livepatch: '$MOD_MEMINFO': unpatching complete
+% rmmod $MOD_MEMINFO
+% rmmod $MOD_CMDLINE"
+
+
+# - load a livepatch with provides=1 (cmdline), then another with
+# provides=1 (meminfo). The second replaces the first (same provides).
+# - unload the remaining livepatch
+
+start_test "same provides replaces"
+
+load_lp $MOD_CMDLINE provides=1
+load_lp $MOD_MEMINFO provides=1
+
+check_sysfs_value "$MOD_MEMINFO" "enabled" "1"
+
+disable_lp $MOD_MEMINFO
+unload_lp $MOD_MEMINFO
+unload_lp $MOD_CMDLINE
+
+check_result "% insmod test_modules/$MOD_CMDLINE.ko provides=1
+livepatch: enabling patch '$MOD_CMDLINE'
+livepatch: '$MOD_CMDLINE': initializing patching transition
+livepatch: '$MOD_CMDLINE': starting patching transition
+livepatch: '$MOD_CMDLINE': completing patching transition
+livepatch: '$MOD_CMDLINE': patching complete
+% insmod test_modules/$MOD_MEMINFO.ko provides=1
+livepatch: enabling patch '$MOD_MEMINFO'
+livepatch: '$MOD_MEMINFO': initializing patching transition
+livepatch: '$MOD_MEMINFO': starting patching transition
+livepatch: '$MOD_MEMINFO': completing patching transition
+livepatch: '$MOD_MEMINFO': patching complete
+% echo 0 > $SYSFS_KLP_DIR/$MOD_MEMINFO/enabled
+livepatch: '$MOD_MEMINFO': initializing unpatching transition
+livepatch: '$MOD_MEMINFO': starting unpatching transition
+livepatch: '$MOD_MEMINFO': completing unpatching transition
+livepatch: '$MOD_MEMINFO': unpatching complete
+% rmmod $MOD_MEMINFO
+% rmmod $MOD_CMDLINE"
+
+
+# - load a livepatch with provides=1 (cmdline), then another with
+# provides=2 and obsoletes=[1] (meminfo). The second replaces the
+# first (obsoletes contains the first patch's provides id).
+# - verify the provides/obsoletes sysfs values
+# - unload the remaining livepatch
+
+start_test "obsoletes replaces"
+
+load_lp $MOD_CMDLINE provides=1
+load_lp $MOD_MEMINFO provides=2 obsoletes=1
+
+check_sysfs_value "$MOD_MEMINFO" "provides" "2"
+check_sysfs_value "$MOD_MEMINFO" "obsoletes" "1"
+
+disable_lp $MOD_MEMINFO
+unload_lp $MOD_MEMINFO
+unload_lp $MOD_CMDLINE
+
+check_result "% insmod test_modules/$MOD_CMDLINE.ko provides=1
+livepatch: enabling patch '$MOD_CMDLINE'
+livepatch: '$MOD_CMDLINE': initializing patching transition
+livepatch: '$MOD_CMDLINE': starting patching transition
+livepatch: '$MOD_CMDLINE': completing patching transition
+livepatch: '$MOD_CMDLINE': patching complete
+% insmod test_modules/$MOD_MEMINFO.ko provides=2 obsoletes=1
+livepatch: enabling patch '$MOD_MEMINFO'
+livepatch: '$MOD_MEMINFO': initializing patching transition
+livepatch: '$MOD_MEMINFO': starting patching transition
+livepatch: '$MOD_MEMINFO': completing patching transition
+livepatch: '$MOD_MEMINFO': patching complete
+% echo 0 > $SYSFS_KLP_DIR/$MOD_MEMINFO/enabled
+livepatch: '$MOD_MEMINFO': initializing unpatching transition
+livepatch: '$MOD_MEMINFO': starting unpatching transition
+livepatch: '$MOD_MEMINFO': completing unpatching transition
+livepatch: '$MOD_MEMINFO': unpatching complete
+% rmmod $MOD_MEMINFO
+% rmmod $MOD_CMDLINE"
+
+
+# - load two livepatches with different provides ids that modify
+# different functions and verify that they coexist
+# - in particular, verify that provides=0 does NOT replace a patch
+# with a different provides id
+# - unload both livepatches
+
+start_test "provides 0 coexists with provides 1"
+
+load_lp $MOD_CMDLINE provides=0
+load_lp $MOD_MEMINFO provides=1
+
+grep 'live patched' /proc/cmdline > /dev/kmsg
+grep 'live patched' /proc/meminfo > /dev/kmsg
+
+disable_lp $MOD_CMDLINE
+unload_lp $MOD_CMDLINE
+disable_lp $MOD_MEMINFO
+unload_lp $MOD_MEMINFO
+
+check_result "% insmod test_modules/$MOD_CMDLINE.ko provides=0
+livepatch: enabling patch '$MOD_CMDLINE'
+livepatch: '$MOD_CMDLINE': initializing patching transition
+livepatch: '$MOD_CMDLINE': starting patching transition
+livepatch: '$MOD_CMDLINE': completing patching transition
+livepatch: '$MOD_CMDLINE': patching complete
+% insmod test_modules/$MOD_MEMINFO.ko provides=1
+livepatch: enabling patch '$MOD_MEMINFO'
+livepatch: '$MOD_MEMINFO': initializing patching transition
+livepatch: '$MOD_MEMINFO': starting patching transition
+livepatch: '$MOD_MEMINFO': completing patching transition
+livepatch: '$MOD_MEMINFO': patching complete
+$MOD_CMDLINE: this has been live patched
+$MOD_MEMINFO: this has been live patched
+% echo 0 > $SYSFS_KLP_DIR/$MOD_CMDLINE/enabled
+livepatch: '$MOD_CMDLINE': initializing unpatching transition
+livepatch: '$MOD_CMDLINE': starting unpatching transition
+livepatch: '$MOD_CMDLINE': completing unpatching transition
+livepatch: '$MOD_CMDLINE': unpatching complete
+% rmmod $MOD_CMDLINE
+% echo 0 > $SYSFS_KLP_DIR/$MOD_MEMINFO/enabled
+livepatch: '$MOD_MEMINFO': initializing unpatching transition
+livepatch: '$MOD_MEMINFO': starting unpatching transition
+livepatch: '$MOD_MEMINFO': completing unpatching transition
+livepatch: '$MOD_MEMINFO': unpatching complete
+% rmmod $MOD_MEMINFO"
+
+
+# - load a livepatch with provides=2 and obsoletes=[1] (cmdline),
+# then another with provides=1 (meminfo). Although the second patch
+# does not list provides=2 in its own obsoletes list, it still
+# replaces the first one, because the obsoletes relationship is
+# symmetric: the first patch's obsoletes list contains the second
+# patch's provides id.
+# - verify the provides sysfs value of the replacing patch
+# - unload the remaining livepatch
+#
+# MOD_MEMINFO(provides=2, obsoletes=[1]) -- loaded first
+# MOD_CMDLINE(provides=1) -- loaded second, replaces above
+
+start_test "symmetric obsoletes replaces"
+
+load_lp $MOD_MEMINFO provides=2 obsoletes=1
+load_lp $MOD_CMDLINE provides=1
+
+check_sysfs_value "$MOD_CMDLINE" "provides" "1"
+
+disable_lp $MOD_CMDLINE
+unload_lp $MOD_CMDLINE
+unload_lp $MOD_MEMINFO
+
+check_result "% insmod test_modules/$MOD_MEMINFO.ko provides=2 obsoletes=1
+livepatch: enabling patch '$MOD_MEMINFO'
+livepatch: '$MOD_MEMINFO': initializing patching transition
+livepatch: '$MOD_MEMINFO': starting patching transition
+livepatch: '$MOD_MEMINFO': completing patching transition
+livepatch: '$MOD_MEMINFO': patching complete
+% insmod test_modules/$MOD_CMDLINE.ko provides=1
+livepatch: enabling patch '$MOD_CMDLINE'
+livepatch: '$MOD_CMDLINE': initializing patching transition
+livepatch: '$MOD_CMDLINE': starting patching transition
+livepatch: '$MOD_CMDLINE': completing patching transition
+livepatch: '$MOD_CMDLINE': patching complete
+% echo 0 > $SYSFS_KLP_DIR/$MOD_CMDLINE/enabled
+livepatch: '$MOD_CMDLINE': initializing unpatching transition
+livepatch: '$MOD_CMDLINE': starting unpatching transition
+livepatch: '$MOD_CMDLINE': completing unpatching transition
+livepatch: '$MOD_CMDLINE': unpatching complete
+% rmmod $MOD_CMDLINE
+% rmmod $MOD_MEMINFO"
+
+exit 0
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 6c46ce575f5c..93789470fe18 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
@@ -8,10 +8,9 @@
#include <linux/livepatch.h>
#ifdef CONFIG_KLP_HAS_PROVIDES
-/*
- * TODO: Add provides/obsoletes module parameters for the
- * provides/obsoletes based tests (to be added later).
- */
+static unsigned int provides;
+module_param(provides, uint, 0444);
+MODULE_PARM_DESC(provides, "provides id (default=0)");
#else
static int replace;
module_param(replace, int, 0644);
@@ -87,10 +86,7 @@ static struct klp_patch patch = {
static int test_klp_callbacks_demo2_init(void)
{
#ifdef CONFIG_KLP_HAS_PROVIDES
- /*
- * TODO: Set provides/obsoletes from the module parameters
- * for the provides/obsoletes based tests (to be added later).
- */
+ patch.provides = provides;
#else
patch.replace = replace;
#endif
diff --git a/tools/testing/selftests/livepatch/test_modules/test_klp_cmdline_lp.c b/tools/testing/selftests/livepatch/test_modules/test_klp_cmdline_lp.c
index 88c6aed925fb..8a93f005a383 100644
--- a/tools/testing/selftests/livepatch/test_modules/test_klp_cmdline_lp.c
+++ b/tools/testing/selftests/livepatch/test_modules/test_klp_cmdline_lp.c
@@ -6,8 +6,14 @@
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/livepatch.h>
-
#include <linux/seq_file.h>
+
+#ifdef CONFIG_KLP_HAS_PROVIDES
+static unsigned int provides;
+module_param(provides, uint, 0444);
+MODULE_PARM_DESC(provides, "provides id (default=0)");
+#endif
+
static int livepatch_cmdline_proc_show(struct seq_file *m, void *v)
{
seq_printf(m, "%s: %s\n", THIS_MODULE->name,
@@ -36,6 +42,9 @@ static struct klp_patch patch = {
static int test_klp_cmdline_lp_init(void)
{
+#ifdef CONFIG_KLP_HAS_PROVIDES
+ patch.provides = provides;
+#endif
return klp_enable_patch(&patch);
}
diff --git a/tools/testing/selftests/livepatch/test_modules/test_klp_meminfo_lp.c b/tools/testing/selftests/livepatch/test_modules/test_klp_meminfo_lp.c
index 0c434a27672d..a3b433385148 100644
--- a/tools/testing/selftests/livepatch/test_modules/test_klp_meminfo_lp.c
+++ b/tools/testing/selftests/livepatch/test_modules/test_klp_meminfo_lp.c
@@ -8,10 +8,15 @@
#include <linux/livepatch.h>
#ifdef CONFIG_KLP_HAS_PROVIDES
-/*
- * TODO: Add provides/obsoletes module parameters for the
- * provides/obsoletes based tests (to be added later).
- */
+static unsigned int provides;
+module_param(provides, uint, 0444);
+MODULE_PARM_DESC(provides, "provides id (default=0)");
+
+#define KLP_MAX_OBSOLETES 16
+static unsigned int obsoletes[KLP_MAX_OBSOLETES];
+static int nr_obsoletes;
+module_param_array(obsoletes, uint, &nr_obsoletes, 0444);
+MODULE_PARM_DESC(obsoletes, "obsoletes provides ids");
#else
static int replace;
module_param(replace, int, 0644);
@@ -51,10 +56,11 @@ static struct klp_patch patch = {
static int test_klp_meminfo_lp_init(void)
{
#ifdef CONFIG_KLP_HAS_PROVIDES
- /*
- * TODO: Set provides/obsoletes from the module parameters
- * for the provides/obsoletes based tests (to be added later).
- */
+ patch.provides = provides;
+ if (nr_obsoletes > 0) {
+ patch.obsoletes = obsoletes;
+ patch.nr_obsoletes = nr_obsoletes;
+ }
#else
patch.replace = replace;
#endif
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 4d419a58cf65..f53eead9d55b 100644
--- a/tools/testing/selftests/livepatch/test_modules/test_klp_state.c
+++ b/tools/testing/selftests/livepatch/test_modules/test_klp_state.c
@@ -143,10 +143,7 @@ static struct klp_patch patch = {
.objs = objs,
.states = states,
#ifdef CONFIG_KLP_HAS_PROVIDES
- /*
- * TODO: Add provides/obsoletes module parameters for the
- * provides/obsoletes based tests (to be added later).
- */
+ /* provides=0 by default, replaces all provides=0 patches */
#else
.replace = true,
#endif
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 4f70f0619967..5f07fe1be2b9 100644
--- a/tools/testing/selftests/livepatch/test_modules/test_klp_state2.c
+++ b/tools/testing/selftests/livepatch/test_modules/test_klp_state2.c
@@ -172,10 +172,7 @@ static struct klp_patch patch = {
.objs = objs,
.states = states,
#ifdef CONFIG_KLP_HAS_PROVIDES
- /*
- * TODO: Add provides/obsoletes module parameters for the
- * provides/obsoletes based tests (to be added later).
- */
+ /* provides=0 by default, replaces all provides=0 patches */
#else
.replace = true,
#endif
--
2.52.0
^ permalink raw reply related [flat|nested] 24+ messages in thread* Re: [PATCH v10 7/9] selftests/livepatch: Add provides/obsoletes test scenarios
2026-09-30 3:03 ` [PATCH v10 7/9] selftests/livepatch: Add provides/obsoletes test scenarios Yafang Shao
@ 2026-09-30 3:13 ` sashiko-bot
2026-09-30 3:29 ` Yafang Shao
2026-10-02 12:27 ` Miroslav Benes
1 sibling, 1 reply; 24+ messages in thread
From: sashiko-bot @ 2026-09-30 3:13 UTC (permalink / raw)
To: jikos, Yafang Shao, mbenes, pmladek, joe.lawrence, song, jpoimboe
Cc: live-patching
Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
- [Low] selftests: comment mismatch in obsoletes test
--
commit b0b3364b7d14143d4a32c2c1ce9eaf4a8c23c322
Author: Yafang Shao <laoar.shao@gmail.com>
selftests/livepatch: Add provides/obsoletes test scenarios
This commit adds test scenarios for the new provides/obsoletes livepatch
mechanism, verifying various replacement semantics. The tests are skipped
on kernels that only support the legacy "replace" attribute.
> diff --git a/tools/testing/selftests/livepatch/test-provides-obsoletes.sh b/tools/testing/selftests/livepatch/test-provides-obsoletes.sh
> new file mode 100755
> index 0000000000000..a1c0ebb9ed44e
> --- /dev/null
> +++ b/tools/testing/selftests/livepatch/test-provides-obsoletes.sh
[ ... ]
> +# - load a livepatch with provides=2 and obsoletes=[1] (cmdline),
> +# then another with provides=1 (meminfo). Although the second patch
[Severity: Low]
Does this block comment swap the module names?
The text says cmdline is loaded with obsoletes=[1] and meminfo is loaded
second. However, as noted later in this same comment block and in the test
implementation below, MOD_MEMINFO is loaded first with obsoletes=1 and
MOD_CMDLINE is loaded second. This is functionally necessary because
test_klp_cmdline_lp.c does not declare an obsoletes module parameter.
> +# does not list provides=2 in its own obsoletes list, it still
> +# replaces the first one, because the obsoletes relationship is
> +# symmetric: the first patch's obsoletes list contains the second
> +# patch's provides id.
> +# - verify the provides sysfs value of the replacing patch
> +# - unload the remaining livepatch
> +#
> +# MOD_MEMINFO(provides=2, obsoletes=[1]) -- loaded first
> +# MOD_CMDLINE(provides=1) -- loaded second, replaces above
> +
> +start_test "symmetric obsoletes replaces"
> +
> +load_lp $MOD_MEMINFO provides=2 obsoletes=1
> +load_lp $MOD_CMDLINE provides=1
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260930030323.14396-1-laoar.shao@gmail.com?part=7
^ permalink raw reply [flat|nested] 24+ messages in thread
* Re: [PATCH v10 7/9] selftests/livepatch: Add provides/obsoletes test scenarios
2026-09-30 3:13 ` sashiko-bot
@ 2026-09-30 3:29 ` Yafang Shao
0 siblings, 0 replies; 24+ messages in thread
From: Yafang Shao @ 2026-09-30 3:29 UTC (permalink / raw)
To: sashiko-reviews
Cc: jikos, mbenes, pmladek, joe.lawrence, song, jpoimboe,
live-patching
On Wed, Sep 30, 2026 at 11:13 AM <sashiko-bot@kernel.org> wrote:
>
> Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
> - [Low] selftests: comment mismatch in obsoletes test
Nice catch.
I will update it.
--
Regards
Yafang
^ permalink raw reply [flat|nested] 24+ messages in thread
* Re: [PATCH v10 7/9] selftests/livepatch: Add provides/obsoletes test scenarios
2026-09-30 3:03 ` [PATCH v10 7/9] selftests/livepatch: Add provides/obsoletes test scenarios Yafang Shao
2026-09-30 3:13 ` sashiko-bot
@ 2026-10-02 12:27 ` Miroslav Benes
1 sibling, 0 replies; 24+ messages in thread
From: Miroslav Benes @ 2026-10-02 12:27 UTC (permalink / raw)
To: Yafang Shao
Cc: jpoimboe, jikos, mbenes, pmladek, joe.lawrence, song,
live-patching
> Now that the legacy "replace" field has been replaced by the
> provides/obsoletes mechanism, add test scenarios to verify the new
> behavior.
>
> The test modules gain provides/obsoletes module parameters, and
> test-provides-obsoletes.sh covers the new replace semantics: provides=0
> replacement, same-provides replacement, obsoletes-based replacement
> and coexistence of different provides ids.
>
> These tests are skipped on kernels that still support the legacy
> "replace" attribute.
>
> Assisted-by: LLM
> Signed-off-by: Yafang Shao <laoar.shao@gmail.com>
With the comment reported by Sashiko fixed
Acked-by: Miroslav Benes <mbenes@suse.cz>
--
Miroslav
^ permalink raw reply [flat|nested] 24+ messages in thread
* [PATCH v10 8/9] selftests/livepatch: Add state test for provides/obsoletes
2026-09-30 3:03 [PATCH v10 0/9] livepatch: Add support for scoped atomic replace Yafang Shao
` (6 preceding siblings ...)
2026-09-30 3:03 ` [PATCH v10 7/9] selftests/livepatch: Add provides/obsoletes test scenarios Yafang Shao
@ 2026-09-30 3:03 ` Yafang Shao
2026-09-30 3:03 ` [PATCH v10 9/9] selftests/livepatch: Add function " Yafang Shao
8 siblings, 0 replies; 24+ messages in thread
From: Yafang Shao @ 2026-09-30 3:03 UTC (permalink / raw)
To: jpoimboe, jikos, mbenes, pmladek, joe.lawrence, song
Cc: live-patching, Yafang Shao
Livepatches with different provides ids must not share the same state
id, unless the second livepatch replaces the first one via obsoletes.
Add provides/obsoletes module parameters to test_klp_state and
test_klp_state2, and cover both cases: reusing a state id across
different provides ids is rejected, while it is allowed when the new
livepatch obsoletes the old one.
Suggested-by: Petr Mladek <pmladek@suse.com>
Signed-off-by: Yafang Shao <laoar.shao@gmail.com>
---
.../livepatch/test-provides-obsoletes.sh | 239 ++++++++++++++++++
.../livepatch/test_modules/test_klp_state.c | 36 ++-
.../livepatch/test_modules/test_klp_state2.c | 44 +++-
3 files changed, 303 insertions(+), 16 deletions(-)
diff --git a/tools/testing/selftests/livepatch/test-provides-obsoletes.sh b/tools/testing/selftests/livepatch/test-provides-obsoletes.sh
index a1c0ebb9ed44..430c3a9a9d84 100755
--- a/tools/testing/selftests/livepatch/test-provides-obsoletes.sh
+++ b/tools/testing/selftests/livepatch/test-provides-obsoletes.sh
@@ -6,6 +6,8 @@
MOD_MEMINFO=test_klp_meminfo_lp
MOD_CMDLINE=test_klp_cmdline_lp
+MOD_STATE=test_klp_state
+MOD_STATE2=test_klp_state2
setup_config
detect_provides_attr
@@ -218,4 +220,241 @@ livepatch: '$MOD_CMDLINE': unpatching complete
% rmmod $MOD_CMDLINE
% rmmod $MOD_MEMINFO"
+
+# - load a livepatch with provides=1 that registers state ID 1
+# - try to load another livepatch with provides=1 that reuses the same
+# state ID. The second one will replace the first one.
+# - disable and unload the remaining livepatch
+
+start_test "same state, same provides"
+
+load_lp $MOD_STATE state=1 provides=1
+load_lp $MOD_STATE2 state=1 provides=1
+
+disable_lp $MOD_STATE2
+unload_lp $MOD_STATE2
+unload_lp $MOD_STATE
+
+check_result "% insmod test_modules/$MOD_STATE.ko state=1 provides=1
+livepatch: enabling patch '$MOD_STATE'
+livepatch: '$MOD_STATE': initializing patching transition
+$MOD_STATE: pre_patch_callback: vmlinux
+$MOD_STATE: allocate_loglevel_state: allocating space to store console_loglevel
+livepatch: '$MOD_STATE': starting patching transition
+livepatch: '$MOD_STATE': completing patching transition
+$MOD_STATE: post_patch_callback: vmlinux
+$MOD_STATE: fix_console_loglevel: fixing console_loglevel
+livepatch: '$MOD_STATE': patching complete
+% insmod test_modules/$MOD_STATE2.ko state=1 provides=1
+livepatch: enabling patch '$MOD_STATE2'
+livepatch: '$MOD_STATE2': initializing patching transition
+$MOD_STATE2: pre_patch_callback: vmlinux
+$MOD_STATE2: allocate_loglevel_state: space to store console_loglevel already allocated
+livepatch: '$MOD_STATE2': starting patching transition
+livepatch: '$MOD_STATE2': completing patching transition
+$MOD_STATE2: post_patch_callback: vmlinux
+$MOD_STATE2: fix_console_loglevel: taking over the console_loglevel change
+livepatch: '$MOD_STATE2': patching complete
+% echo 0 > $SYSFS_KLP_DIR/$MOD_STATE2/enabled
+livepatch: '$MOD_STATE2': initializing unpatching transition
+$MOD_STATE2: pre_unpatch_callback: vmlinux
+$MOD_STATE2: restore_console_loglevel: restoring console_loglevel
+livepatch: '$MOD_STATE2': starting unpatching transition
+livepatch: '$MOD_STATE2': completing unpatching transition
+$MOD_STATE2: post_unpatch_callback: vmlinux
+$MOD_STATE2: free_loglevel_state: freeing space for the stored console_loglevel
+livepatch: '$MOD_STATE2': unpatching complete
+% rmmod $MOD_STATE2
+% rmmod $MOD_STATE"
+
+
+# - load a livepatch with provides=1 that registers state ID 1
+# - try to load another livepatch with provides=2 that reuses the same
+# state ID. The second one must be rejected because livepatches with
+# different provides ids must not share the same state id.
+# - disable and unload the remaining livepatch
+
+start_test "same state, misc provides"
+
+load_lp $MOD_STATE state=1 provides=1
+load_failing_mod $MOD_STATE2 state=1 provides=2
+
+disable_lp $MOD_STATE
+unload_lp $MOD_STATE
+
+check_result "% insmod test_modules/$MOD_STATE.ko state=1 provides=1
+livepatch: enabling patch '$MOD_STATE'
+livepatch: '$MOD_STATE': initializing patching transition
+$MOD_STATE: pre_patch_callback: vmlinux
+$MOD_STATE: allocate_loglevel_state: allocating space to store console_loglevel
+livepatch: '$MOD_STATE': starting patching transition
+livepatch: '$MOD_STATE': completing patching transition
+$MOD_STATE: post_patch_callback: vmlinux
+$MOD_STATE: fix_console_loglevel: fixing console_loglevel
+livepatch: '$MOD_STATE': patching complete
+% insmod test_modules/$MOD_STATE2.ko state=1 provides=2
+livepatch: Livepatch patch ($MOD_STATE2) is not compatible with the already installed livepatches.
+insmod: ERROR: could not insert module test_modules/$MOD_STATE2.ko: Invalid parameters
+% echo 0 > $SYSFS_KLP_DIR/$MOD_STATE/enabled
+livepatch: '$MOD_STATE': initializing unpatching transition
+$MOD_STATE: pre_unpatch_callback: vmlinux
+$MOD_STATE: restore_console_loglevel: restoring console_loglevel
+livepatch: '$MOD_STATE': starting unpatching transition
+livepatch: '$MOD_STATE': completing unpatching transition
+$MOD_STATE: post_unpatch_callback: vmlinux
+$MOD_STATE: free_loglevel_state: freeing space for the stored console_loglevel
+livepatch: '$MOD_STATE': unpatching complete
+% rmmod $MOD_STATE"
+
+
+# Take over system state change by a patch that obsoletes the old one.
+# Although the provides IDs are different, the second patch's obsoletes
+# list includes the first patch's provides ID, so it can replace the
+# first patch and reuse the same state ID.
+
+start_test "same states, replaced by obsoletes"
+
+load_lp $MOD_STATE state=1 provides=1
+load_lp $MOD_STATE2 state=1 provides=2 obsoletes=1
+unload_lp $MOD_STATE
+disable_lp $MOD_STATE2
+unload_lp $MOD_STATE2
+
+check_result "% insmod test_modules/$MOD_STATE.ko state=1 provides=1
+livepatch: enabling patch '$MOD_STATE'
+livepatch: '$MOD_STATE': initializing patching transition
+$MOD_STATE: pre_patch_callback: vmlinux
+$MOD_STATE: allocate_loglevel_state: allocating space to store console_loglevel
+livepatch: '$MOD_STATE': starting patching transition
+livepatch: '$MOD_STATE': completing patching transition
+$MOD_STATE: post_patch_callback: vmlinux
+$MOD_STATE: fix_console_loglevel: fixing console_loglevel
+livepatch: '$MOD_STATE': patching complete
+% insmod test_modules/$MOD_STATE2.ko state=1 provides=2 obsoletes=1
+livepatch: enabling patch '$MOD_STATE2'
+livepatch: '$MOD_STATE2': initializing patching transition
+$MOD_STATE2: pre_patch_callback: vmlinux
+$MOD_STATE2: allocate_loglevel_state: space to store console_loglevel already allocated
+livepatch: '$MOD_STATE2': starting patching transition
+livepatch: '$MOD_STATE2': completing patching transition
+$MOD_STATE2: post_patch_callback: vmlinux
+$MOD_STATE2: fix_console_loglevel: taking over the console_loglevel change
+livepatch: '$MOD_STATE2': patching complete
+% rmmod $MOD_STATE
+% echo 0 > $SYSFS_KLP_DIR/$MOD_STATE2/enabled
+livepatch: '$MOD_STATE2': initializing unpatching transition
+$MOD_STATE2: pre_unpatch_callback: vmlinux
+$MOD_STATE2: restore_console_loglevel: restoring console_loglevel
+livepatch: '$MOD_STATE2': starting unpatching transition
+livepatch: '$MOD_STATE2': completing unpatching transition
+$MOD_STATE2: post_unpatch_callback: vmlinux
+$MOD_STATE2: free_loglevel_state: freeing space for the stored console_loglevel
+livepatch: '$MOD_STATE2': unpatching complete
+% rmmod $MOD_STATE2"
+
+
+# - load a livepatch that modifies system state (state=1) with
+# provides=1, then another livepatch with the same provides id=1 that declares
+# a different system state (state=2)
+# - the second livepatch would replace the first one due to the same
+# provides id, but it does not handle the system state modified by
+# the first one, therefore it is rejected as incompatible and the
+# insmod must fail.
+# - verify the first livepatch is still alive, then disable and
+# unload it.
+
+start_test "misc states, same provides"
+load_lp $MOD_STATE state=1 provides=1
+load_failing_mod $MOD_STATE2 state=2 provides=1
+
+disable_lp $MOD_STATE
+unload_lp $MOD_STATE
+
+check_result "% insmod test_modules/$MOD_STATE.ko state=1 provides=1
+livepatch: enabling patch '$MOD_STATE'
+livepatch: '$MOD_STATE': initializing patching transition
+$MOD_STATE: pre_patch_callback: vmlinux
+$MOD_STATE: allocate_loglevel_state: allocating space to store console_loglevel
+livepatch: '$MOD_STATE': starting patching transition
+livepatch: '$MOD_STATE': completing patching transition
+$MOD_STATE: post_patch_callback: vmlinux
+$MOD_STATE: fix_console_loglevel: fixing console_loglevel
+livepatch: '$MOD_STATE': patching complete
+% insmod test_modules/$MOD_STATE2.ko state=2 provides=1
+livepatch: Livepatch patch ($MOD_STATE2) is not compatible with the already installed livepatches.
+insmod: ERROR: could not insert module test_modules/$MOD_STATE2.ko: Invalid parameters
+% echo 0 > $SYSFS_KLP_DIR/$MOD_STATE/enabled
+livepatch: '$MOD_STATE': initializing unpatching transition
+$MOD_STATE: pre_unpatch_callback: vmlinux
+$MOD_STATE: restore_console_loglevel: restoring console_loglevel
+livepatch: '$MOD_STATE': starting unpatching transition
+livepatch: '$MOD_STATE': completing unpatching transition
+$MOD_STATE: post_unpatch_callback: vmlinux
+$MOD_STATE: free_loglevel_state: freeing space for the stored console_loglevel
+livepatch: '$MOD_STATE': unpatching complete
+% rmmod $MOD_STATE"
+
+
+# - load two livepatches with different provides ids (provides=1 and
+# provides=2) that modify different system states (state=1 and
+# state=2). Neither of them replaces the other, because their
+# provides ids differ (and the obsoletes lists are empty), so they
+# can coexist.
+# - verify the provides sysfs value of both livepatches
+# - disable and unload both livepatches
+
+start_test "misc states, misc provides"
+
+load_lp $MOD_STATE state=1 provides=1
+load_lp $MOD_STATE2 state=2 provides=2
+
+check_sysfs_value "$MOD_STATE" "provides" "1"
+check_sysfs_value "$MOD_STATE2" "provides" "2"
+
+disable_lp $MOD_STATE2
+unload_lp $MOD_STATE2
+disable_lp $MOD_STATE
+unload_lp $MOD_STATE
+
+check_result "% insmod test_modules/$MOD_STATE.ko state=1 provides=1
+livepatch: enabling patch '$MOD_STATE'
+livepatch: '$MOD_STATE': initializing patching transition
+$MOD_STATE: pre_patch_callback: vmlinux
+$MOD_STATE: allocate_loglevel_state: allocating space to store console_loglevel
+livepatch: '$MOD_STATE': starting patching transition
+livepatch: '$MOD_STATE': completing patching transition
+$MOD_STATE: post_patch_callback: vmlinux
+$MOD_STATE: fix_console_loglevel: fixing console_loglevel
+livepatch: '$MOD_STATE': patching complete
+% insmod test_modules/$MOD_STATE2.ko state=2 provides=2
+livepatch: enabling patch '$MOD_STATE2'
+livepatch: '$MOD_STATE2': initializing patching transition
+$MOD_STATE2: pre_patch_callback: vmlinux
+$MOD_STATE2: allocate_loglevel_state: allocating space to store console_loglevel
+livepatch: '$MOD_STATE2': starting patching transition
+livepatch: '$MOD_STATE2': completing patching transition
+$MOD_STATE2: post_patch_callback: vmlinux
+$MOD_STATE2: fix_console_loglevel: fixing console_loglevel
+livepatch: '$MOD_STATE2': patching complete
+% echo 0 > $SYSFS_KLP_DIR/$MOD_STATE2/enabled
+livepatch: '$MOD_STATE2': initializing unpatching transition
+$MOD_STATE2: pre_unpatch_callback: vmlinux
+$MOD_STATE2: restore_console_loglevel: restoring console_loglevel
+livepatch: '$MOD_STATE2': starting unpatching transition
+livepatch: '$MOD_STATE2': completing unpatching transition
+$MOD_STATE2: post_unpatch_callback: vmlinux
+$MOD_STATE2: free_loglevel_state: freeing space for the stored console_loglevel
+livepatch: '$MOD_STATE2': unpatching complete
+% rmmod $MOD_STATE2
+% echo 0 > $SYSFS_KLP_DIR/$MOD_STATE/enabled
+livepatch: '$MOD_STATE': initializing unpatching transition
+$MOD_STATE: pre_unpatch_callback: vmlinux
+$MOD_STATE: restore_console_loglevel: restoring console_loglevel
+livepatch: '$MOD_STATE': starting unpatching transition
+livepatch: '$MOD_STATE': completing unpatching transition
+$MOD_STATE: post_unpatch_callback: vmlinux
+$MOD_STATE: free_loglevel_state: freeing space for the stored console_loglevel
+livepatch: '$MOD_STATE': unpatching complete
+% rmmod $MOD_STATE"
+
exit 0
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 f53eead9d55b..3511cf081fef 100644
--- a/tools/testing/selftests/livepatch/test_modules/test_klp_state.c
+++ b/tools/testing/selftests/livepatch/test_modules/test_klp_state.c
@@ -9,10 +9,25 @@
#include <linux/printk.h>
#include <linux/livepatch.h>
-#define CONSOLE_LOGLEVEL_STATE 1
/* Version 1 does not support migration. */
#define CONSOLE_LOGLEVEL_STATE_VERSION 1
+static unsigned int state = 1;
+module_param(state, uint, 0444);
+MODULE_PARM_DESC(state, "console loglevel state (default=1)");
+
+#ifdef CONFIG_KLP_HAS_PROVIDES
+static unsigned int provides;
+module_param(provides, uint, 0444);
+MODULE_PARM_DESC(provides, "provides id (default=0)");
+
+#define KLP_MAX_OBSOLETES 16
+static unsigned int obsoletes[KLP_MAX_OBSOLETES];
+static int nr_obsoletes;
+module_param_array(obsoletes, uint, &nr_obsoletes, 0444);
+MODULE_PARM_DESC(obsoletes, "obsoletes provides ids");
+#endif
+
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",
@@ -35,7 +50,7 @@ static int allocate_loglevel_state(void)
{
struct klp_state *loglevel_state;
- loglevel_state = klp_get_state(&patch, CONSOLE_LOGLEVEL_STATE);
+ loglevel_state = klp_get_state(&patch, state);
if (!loglevel_state)
return -EINVAL;
@@ -52,7 +67,7 @@ static void fix_console_loglevel(void)
{
struct klp_state *loglevel_state;
- loglevel_state = klp_get_state(&patch, CONSOLE_LOGLEVEL_STATE);
+ loglevel_state = klp_get_state(&patch, state);
if (!loglevel_state)
return;
@@ -65,7 +80,7 @@ static void restore_console_loglevel(void)
{
struct klp_state *loglevel_state;
- loglevel_state = klp_get_state(&patch, CONSOLE_LOGLEVEL_STATE);
+ loglevel_state = klp_get_state(&patch, state);
if (!loglevel_state)
return;
@@ -77,7 +92,7 @@ static void free_loglevel_state(void)
{
struct klp_state *loglevel_state;
- loglevel_state = klp_get_state(&patch, CONSOLE_LOGLEVEL_STATE);
+ loglevel_state = klp_get_state(&patch, state);
if (!loglevel_state)
return;
@@ -133,7 +148,6 @@ static struct klp_object objs[] = {
static struct klp_state states[] = {
{
- .id = CONSOLE_LOGLEVEL_STATE,
.version = CONSOLE_LOGLEVEL_STATE_VERSION,
}, { }
};
@@ -151,6 +165,16 @@ static struct klp_patch patch = {
static int test_klp_callbacks_demo_init(void)
{
+#ifdef CONFIG_KLP_HAS_PROVIDES
+ patch.provides = provides;
+ if (nr_obsoletes > 0) {
+ patch.obsoletes = obsoletes;
+ patch.nr_obsoletes = nr_obsoletes;
+ }
+#else
+ patch.replace = true;
+#endif
+ states[0].id = state;
return klp_enable_patch(&patch);
}
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 5f07fe1be2b9..aeb911b87d37 100644
--- a/tools/testing/selftests/livepatch/test_modules/test_klp_state2.c
+++ b/tools/testing/selftests/livepatch/test_modules/test_klp_state2.c
@@ -9,10 +9,25 @@
#include <linux/printk.h>
#include <linux/livepatch.h>
-#define CONSOLE_LOGLEVEL_STATE 1
/* Version 2 supports migration. */
#define CONSOLE_LOGLEVEL_STATE_VERSION 2
+static unsigned int state = 1;
+module_param(state, uint, 0444);
+MODULE_PARM_DESC(state, "console loglevel state (default=1)");
+
+#ifdef CONFIG_KLP_HAS_PROVIDES
+static unsigned int provides;
+module_param(provides, uint, 0444);
+MODULE_PARM_DESC(provides, "provides id (default=0)");
+
+#define KLP_MAX_OBSOLETES 16
+static unsigned int obsoletes[KLP_MAX_OBSOLETES];
+static int nr_obsoletes;
+module_param_array(obsoletes, uint, &nr_obsoletes, 0444);
+MODULE_PARM_DESC(obsoletes, "obsoletes provides ids");
+#endif
+
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",
@@ -35,14 +50,14 @@ static int allocate_loglevel_state(void)
{
struct klp_state *loglevel_state, *prev_loglevel_state;
- prev_loglevel_state = klp_get_prev_state(CONSOLE_LOGLEVEL_STATE);
+ prev_loglevel_state = klp_get_prev_state(state);
if (prev_loglevel_state) {
pr_info("%s: space to store console_loglevel already allocated\n",
__func__);
return 0;
}
- loglevel_state = klp_get_state(&patch, CONSOLE_LOGLEVEL_STATE);
+ loglevel_state = klp_get_state(&patch, state);
if (!loglevel_state)
return -EINVAL;
@@ -59,11 +74,11 @@ static void fix_console_loglevel(void)
{
struct klp_state *loglevel_state, *prev_loglevel_state;
- loglevel_state = klp_get_state(&patch, CONSOLE_LOGLEVEL_STATE);
+ loglevel_state = klp_get_state(&patch, state);
if (!loglevel_state)
return;
- prev_loglevel_state = klp_get_prev_state(CONSOLE_LOGLEVEL_STATE);
+ prev_loglevel_state = klp_get_prev_state(state);
if (prev_loglevel_state) {
pr_info("%s: taking over the console_loglevel change\n",
__func__);
@@ -80,14 +95,14 @@ static void restore_console_loglevel(void)
{
struct klp_state *loglevel_state, *prev_loglevel_state;
- prev_loglevel_state = klp_get_prev_state(CONSOLE_LOGLEVEL_STATE);
+ prev_loglevel_state = klp_get_prev_state(state);
if (prev_loglevel_state) {
pr_info("%s: passing the console_loglevel change back to the old livepatch\n",
__func__);
return;
}
- loglevel_state = klp_get_state(&patch, CONSOLE_LOGLEVEL_STATE);
+ loglevel_state = klp_get_state(&patch, state);
if (!loglevel_state)
return;
@@ -99,14 +114,14 @@ static void free_loglevel_state(void)
{
struct klp_state *loglevel_state, *prev_loglevel_state;
- prev_loglevel_state = klp_get_prev_state(CONSOLE_LOGLEVEL_STATE);
+ prev_loglevel_state = klp_get_prev_state(state);
if (prev_loglevel_state) {
pr_info("%s: keeping space to store console_loglevel\n",
__func__);
return;
}
- loglevel_state = klp_get_state(&patch, CONSOLE_LOGLEVEL_STATE);
+ loglevel_state = klp_get_state(&patch, state);
if (!loglevel_state)
return;
@@ -162,7 +177,6 @@ static struct klp_object objs[] = {
static struct klp_state states[] = {
{
- .id = CONSOLE_LOGLEVEL_STATE,
.version = CONSOLE_LOGLEVEL_STATE_VERSION,
}, { }
};
@@ -180,6 +194,16 @@ static struct klp_patch patch = {
static int test_klp_callbacks_demo_init(void)
{
+#ifdef CONFIG_KLP_HAS_PROVIDES
+ patch.provides = provides;
+ if (nr_obsoletes > 0) {
+ patch.obsoletes = obsoletes;
+ patch.nr_obsoletes = nr_obsoletes;
+ }
+#else
+ patch.replace = true;
+#endif
+ states[0].id = state;
return klp_enable_patch(&patch);
}
--
2.52.0
^ permalink raw reply related [flat|nested] 24+ messages in thread* [PATCH v10 9/9] selftests/livepatch: Add function test for provides/obsoletes
2026-09-30 3:03 [PATCH v10 0/9] livepatch: Add support for scoped atomic replace Yafang Shao
` (7 preceding siblings ...)
2026-09-30 3:03 ` [PATCH v10 8/9] selftests/livepatch: Add state test for provides/obsoletes Yafang Shao
@ 2026-09-30 3:03 ` Yafang Shao
8 siblings, 0 replies; 24+ messages in thread
From: Yafang Shao @ 2026-09-30 3:03 UTC (permalink / raw)
To: jpoimboe, jikos, mbenes, pmladek, joe.lawrence, song
Cc: live-patching, Yafang Shao
Livepatches with different provides ids must not modify the same
function, unless they share the same provides id or the second
livepatch obsoletes the first one.
Add test scenarios that verify this rule on the combination of
patched functions and provides ids.
These tests are skipped on kernels that still support the legacy
"replace" attribute.
Suggested-by: Petr Mladek <pmladek@suse.com>
Signed-off-by: Yafang Shao <laoar.shao@gmail.com>
---
.../livepatch/test-provides-obsoletes.sh | 188 ++++++++++++++++++
.../selftests/livepatch/test_modules/Makefile | 1 +
.../test_modules/test_klp_meminfo_lp2.c | 2 +
3 files changed, 191 insertions(+)
create mode 100644 tools/testing/selftests/livepatch/test_modules/test_klp_meminfo_lp2.c
diff --git a/tools/testing/selftests/livepatch/test-provides-obsoletes.sh b/tools/testing/selftests/livepatch/test-provides-obsoletes.sh
index 430c3a9a9d84..69b836ed51de 100755
--- a/tools/testing/selftests/livepatch/test-provides-obsoletes.sh
+++ b/tools/testing/selftests/livepatch/test-provides-obsoletes.sh
@@ -5,6 +5,7 @@
. $(dirname $0)/functions.sh
MOD_MEMINFO=test_klp_meminfo_lp
+MOD_MEMINFO2=test_klp_meminfo_lp2
MOD_CMDLINE=test_klp_cmdline_lp
MOD_STATE=test_klp_state
MOD_STATE2=test_klp_state2
@@ -457,4 +458,191 @@ $MOD_STATE: free_loglevel_state: freeing space for the stored console_loglevel
livepatch: '$MOD_STATE': unpatching complete
% rmmod $MOD_STATE"
+
+# - load two livepatches that modify the same function
+# (meminfo_proc_show), both with provides=1. The second livepatch
+# replaces the first one because of the same provides id, even
+# though they share the patched function.
+# - verify that the first livepatch is gone from sysfs (replaced)
+# and that the second one is enabled
+# - unload the replacing livepatch, then the replaced one
+
+start_test "same function, replaced by provides"
+
+load_lp $MOD_MEMINFO provides=1
+
+grep 'live patched' /proc/meminfo > /dev/kmsg
+
+load_lp $MOD_MEMINFO2 provides=1
+
+loop_until '[[ ! -e "$SYSFS_KLP_DIR/$MOD_MEMINFO" ]]' ||
+ die "failed to replace $MOD_MEMINFO"
+
+grep 'live patched' /proc/meminfo > /dev/kmsg
+
+check_sysfs_value "$MOD_MEMINFO2" "provides" "1"
+
+disable_lp $MOD_MEMINFO2
+unload_lp $MOD_MEMINFO2
+
+grep 'live patched' /proc/meminfo > /dev/kmsg
+
+unload_lp $MOD_MEMINFO
+
+check_result "% insmod test_modules/$MOD_MEMINFO.ko provides=1
+livepatch: enabling patch '$MOD_MEMINFO'
+livepatch: '$MOD_MEMINFO': initializing patching transition
+livepatch: '$MOD_MEMINFO': starting patching transition
+livepatch: '$MOD_MEMINFO': completing patching transition
+livepatch: '$MOD_MEMINFO': patching complete
+$MOD_MEMINFO: this has been live patched
+% insmod test_modules/$MOD_MEMINFO2.ko provides=1
+livepatch: enabling patch '$MOD_MEMINFO2'
+livepatch: '$MOD_MEMINFO2': initializing patching transition
+livepatch: '$MOD_MEMINFO2': starting patching transition
+livepatch: '$MOD_MEMINFO2': completing patching transition
+livepatch: '$MOD_MEMINFO2': patching complete
+$MOD_MEMINFO2: this has been live patched
+% echo 0 > $SYSFS_KLP_DIR/$MOD_MEMINFO2/enabled
+livepatch: '$MOD_MEMINFO2': initializing unpatching transition
+livepatch: '$MOD_MEMINFO2': starting unpatching transition
+livepatch: '$MOD_MEMINFO2': completing unpatching transition
+livepatch: '$MOD_MEMINFO2': unpatching complete
+% rmmod $MOD_MEMINFO2
+% rmmod $MOD_MEMINFO"
+
+
+# - load a livepatch that modifies meminfo_proc_show with provides=1,
+# then try to load another livepatch that modifies the same
+# function with a different provides id (provides=2).
+# - since the provides ids differ, the second livepatch would not
+# replace the first one; the same function can not be handled by
+# two different provides groups in parallel, so the load must fail.
+# - unload the first livepatch
+
+start_test "same function, can't be loaded in parallel"
+
+load_lp $MOD_MEMINFO provides=1
+load_failing_mod $MOD_MEMINFO2 provides=2
+
+disable_lp $MOD_MEMINFO
+unload_lp $MOD_MEMINFO
+
+check_result "% insmod test_modules/$MOD_MEMINFO.ko provides=1
+livepatch: enabling patch '$MOD_MEMINFO'
+livepatch: '$MOD_MEMINFO': initializing patching transition
+livepatch: '$MOD_MEMINFO': starting patching transition
+livepatch: '$MOD_MEMINFO': completing patching transition
+livepatch: '$MOD_MEMINFO': patching complete
+% insmod test_modules/$MOD_MEMINFO2.ko provides=2
+livepatch: Livepatch patch ($MOD_MEMINFO2) is not compatible with the already installed livepatches.
+insmod: ERROR: could not insert module test_modules/$MOD_MEMINFO2.ko: Invalid parameters
+% echo 0 > $SYSFS_KLP_DIR/$MOD_MEMINFO/enabled
+livepatch: '$MOD_MEMINFO': initializing unpatching transition
+livepatch: '$MOD_MEMINFO': starting unpatching transition
+livepatch: '$MOD_MEMINFO': completing unpatching transition
+livepatch: '$MOD_MEMINFO': unpatching complete
+% rmmod $MOD_MEMINFO"
+
+
+# - load a livepatch that modifies meminfo_proc_show with provides=1,
+# then another livepatch with provides=1 that modifies a different
+# function (cmdline_proc_show). The second one replaces the first
+# one because of the same provides id.
+# - since the patched functions differ, the replacement does not need
+# to restore the old function body; the transition only switches
+# the ftrace nops, i.e. the atomic replace (nops) path.
+# - after the replacement /proc/meminfo is no longer patched, while
+# /proc/cmdline is patched
+# - unload the replacing livepatch, then the replaced one
+
+start_test "different functions, atomic replace works (nops)"
+
+load_lp $MOD_MEMINFO provides=1
+
+grep 'live patched' /proc/meminfo > /dev/kmsg
+
+load_lp $MOD_CMDLINE provides=1
+
+grep 'live patched' /proc/meminfo > /dev/kmsg
+grep 'live patched' /proc/cmdline > /dev/kmsg
+
+disable_lp $MOD_CMDLINE
+unload_lp $MOD_CMDLINE
+unload_lp $MOD_MEMINFO
+
+check_result "% insmod test_modules/$MOD_MEMINFO.ko provides=1
+livepatch: enabling patch '$MOD_MEMINFO'
+livepatch: '$MOD_MEMINFO': initializing patching transition
+livepatch: '$MOD_MEMINFO': starting patching transition
+livepatch: '$MOD_MEMINFO': completing patching transition
+livepatch: '$MOD_MEMINFO': patching complete
+$MOD_MEMINFO: this has been live patched
+% insmod test_modules/$MOD_CMDLINE.ko provides=1
+livepatch: enabling patch '$MOD_CMDLINE'
+livepatch: '$MOD_CMDLINE': initializing patching transition
+livepatch: '$MOD_CMDLINE': starting patching transition
+livepatch: '$MOD_CMDLINE': completing patching transition
+livepatch: '$MOD_CMDLINE': patching complete
+$MOD_CMDLINE: this has been live patched
+% echo 0 > $SYSFS_KLP_DIR/$MOD_CMDLINE/enabled
+livepatch: '$MOD_CMDLINE': initializing unpatching transition
+livepatch: '$MOD_CMDLINE': starting unpatching transition
+livepatch: '$MOD_CMDLINE': completing unpatching transition
+livepatch: '$MOD_CMDLINE': unpatching complete
+% rmmod $MOD_CMDLINE
+% rmmod $MOD_MEMINFO"
+
+
+# - load two livepatches with different provides ids (1 and 2) that
+# modify different functions (meminfo_proc_show and
+# cmdline_proc_show). Neither of them replaces the other, so both
+# livepatches must coexist.
+# - verify that both /proc/meminfo and /proc/cmdline are patched
+# - disable and unload both livepatches
+
+start_test "different functions, install in parallel"
+
+load_lp $MOD_MEMINFO provides=1
+
+grep 'live patched' /proc/meminfo > /dev/kmsg
+
+load_lp $MOD_CMDLINE provides=2
+
+grep 'live patched' /proc/meminfo > /dev/kmsg
+grep 'live patched' /proc/cmdline > /dev/kmsg
+
+disable_lp $MOD_MEMINFO
+unload_lp $MOD_MEMINFO
+disable_lp $MOD_CMDLINE
+unload_lp $MOD_CMDLINE
+
+check_result "% insmod test_modules/$MOD_MEMINFO.ko provides=1
+livepatch: enabling patch '$MOD_MEMINFO'
+livepatch: '$MOD_MEMINFO': initializing patching transition
+livepatch: '$MOD_MEMINFO': starting patching transition
+livepatch: '$MOD_MEMINFO': completing patching transition
+livepatch: '$MOD_MEMINFO': patching complete
+$MOD_MEMINFO: this has been live patched
+% insmod test_modules/$MOD_CMDLINE.ko provides=2
+livepatch: enabling patch '$MOD_CMDLINE'
+livepatch: '$MOD_CMDLINE': initializing patching transition
+livepatch: '$MOD_CMDLINE': starting patching transition
+livepatch: '$MOD_CMDLINE': completing patching transition
+livepatch: '$MOD_CMDLINE': patching complete
+$MOD_MEMINFO: this has been live patched
+$MOD_CMDLINE: this has been live patched
+% echo 0 > $SYSFS_KLP_DIR/$MOD_MEMINFO/enabled
+livepatch: '$MOD_MEMINFO': initializing unpatching transition
+livepatch: '$MOD_MEMINFO': starting unpatching transition
+livepatch: '$MOD_MEMINFO': completing unpatching transition
+livepatch: '$MOD_MEMINFO': unpatching complete
+% rmmod $MOD_MEMINFO
+% echo 0 > $SYSFS_KLP_DIR/$MOD_CMDLINE/enabled
+livepatch: '$MOD_CMDLINE': initializing unpatching transition
+livepatch: '$MOD_CMDLINE': starting unpatching transition
+livepatch: '$MOD_CMDLINE': completing unpatching transition
+livepatch: '$MOD_CMDLINE': unpatching complete
+% rmmod $MOD_CMDLINE"
+
exit 0
diff --git a/tools/testing/selftests/livepatch/test_modules/Makefile b/tools/testing/selftests/livepatch/test_modules/Makefile
index 732e0003930b..0c17754997d2 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_meminfo_lp.o \
+ test_klp_meminfo_lp2.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_meminfo_lp2.c b/tools/testing/selftests/livepatch/test_modules/test_klp_meminfo_lp2.c
new file mode 100644
index 000000000000..2b93c969b7ba
--- /dev/null
+++ b/tools/testing/selftests/livepatch/test_modules/test_klp_meminfo_lp2.c
@@ -0,0 +1,2 @@
+// SPDX-License-Identifier: GPL-2.0
+#include "test_klp_meminfo_lp.c"
--
2.52.0
^ permalink raw reply related [flat|nested] 24+ messages in thread