* [PATCH v3 1/7] livepatch: Fix NULL pointer dereference in klp_find_func()
2026-06-07 13:16 [PATCH v3 0/7] livepatch: Introduce replace set support Yafang Shao
@ 2026-06-07 13:16 ` Yafang Shao
2026-06-07 13:16 ` [PATCH v3 2/7] livepatch: Move klp_find_func() into core.h Yafang Shao
` (5 subsequent siblings)
6 siblings, 0 replies; 12+ messages in thread
From: Yafang Shao @ 2026-06-07 13:16 UTC (permalink / raw)
To: jpoimboe, jikos, mbenes, pmladek, joe.lawrence, song
Cc: live-patching, Yafang Shao, sashiko-bot
If a newly loaded livepatch provides a function entry with a NULL old_name,
func->old_name will be NULL when evaluated in strcmp():
klp_init_patch()
klp_add_nops()
klp_find_func()
strcmp(old_func->old_name, func->old_name)
Reported-by: sashiko-bot <sashiko-bot@kernel.org>
Closes: https://lore.kernel.org/live-patching/20260529040130.95A9C1F00893@smtp.kernel.org/
Signed-off-by: Yafang Shao <laoar.shao@gmail.com>
---
kernel/livepatch/core.c | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/kernel/livepatch/core.c b/kernel/livepatch/core.c
index 28d15ba58a26..95117f7a5b00 100644
--- a/kernel/livepatch/core.c
+++ b/kernel/livepatch/core.c
@@ -92,7 +92,7 @@ static struct klp_func *klp_find_func(struct klp_object *obj,
* Besides identical old_sympos, also consider old_sympos
* of 0 and 1 are identical.
*/
- if ((strcmp(old_func->old_name, func->old_name) == 0) &&
+ if ((func->old_name && strcmp(old_func->old_name, func->old_name) == 0) &&
((old_func->old_sympos == func->old_sympos) ||
(old_func->old_sympos == 0 && func->old_sympos == 1) ||
(old_func->old_sympos == 1 && func->old_sympos == 0))) {
--
2.52.0
^ permalink raw reply related [flat|nested] 12+ messages in thread* [PATCH v3 2/7] livepatch: Move klp_find_func() into core.h
2026-06-07 13:16 [PATCH v3 0/7] livepatch: Introduce replace set support Yafang Shao
2026-06-07 13:16 ` [PATCH v3 1/7] livepatch: Fix NULL pointer dereference in klp_find_func() Yafang Shao
@ 2026-06-07 13:16 ` Yafang Shao
2026-06-07 13:16 ` [PATCH v3 3/7] livepatch: Support scoped atomic replace using replace_set Yafang Shao
` (4 subsequent siblings)
6 siblings, 0 replies; 12+ messages in thread
From: Yafang Shao @ 2026-06-07 13:16 UTC (permalink / raw)
To: jpoimboe, jikos, mbenes, pmladek, joe.lawrence, song
Cc: live-patching, Yafang Shao
This function will be used in other source file in the following patch.
Signed-off-by: Yafang Shao <laoar.shao@gmail.com>
---
kernel/livepatch/core.c | 21 ---------------------
kernel/livepatch/core.h | 21 +++++++++++++++++++++
2 files changed, 21 insertions(+), 21 deletions(-)
diff --git a/kernel/livepatch/core.c b/kernel/livepatch/core.c
index 95117f7a5b00..6e15ae96a0a7 100644
--- a/kernel/livepatch/core.c
+++ b/kernel/livepatch/core.c
@@ -82,27 +82,6 @@ static bool klp_initialized(void)
return !!klp_root_kobj;
}
-static struct klp_func *klp_find_func(struct klp_object *obj,
- struct klp_func *old_func)
-{
- struct klp_func *func;
-
- klp_for_each_func(obj, func) {
- /*
- * Besides identical old_sympos, also consider old_sympos
- * of 0 and 1 are identical.
- */
- if ((func->old_name && strcmp(old_func->old_name, func->old_name) == 0) &&
- ((old_func->old_sympos == func->old_sympos) ||
- (old_func->old_sympos == 0 && func->old_sympos == 1) ||
- (old_func->old_sympos == 1 && func->old_sympos == 0))) {
- return func;
- }
- }
-
- return NULL;
-}
-
static struct klp_object *klp_find_object(struct klp_patch *patch,
struct klp_object *old_obj)
{
diff --git a/kernel/livepatch/core.h b/kernel/livepatch/core.h
index 38209c7361b6..8f01eae875eb 100644
--- a/kernel/livepatch/core.h
+++ b/kernel/livepatch/core.h
@@ -56,4 +56,25 @@ static inline void klp_post_unpatch_callback(struct klp_object *obj)
obj->callbacks.post_unpatch_enabled = false;
}
+static inline struct klp_func *klp_find_func(struct klp_object *obj,
+ struct klp_func *old_func)
+{
+ struct klp_func *func;
+
+ klp_for_each_func(obj, func) {
+ /*
+ * Besides identical old_sympos, also consider old_sympos
+ * of 0 and 1 are identical.
+ */
+ if ((func->old_name && strcmp(old_func->old_name, func->old_name) == 0) &&
+ ((old_func->old_sympos == func->old_sympos) ||
+ (old_func->old_sympos == 0 && func->old_sympos == 1) ||
+ (old_func->old_sympos == 1 && func->old_sympos == 0))) {
+ return func;
+ }
+ }
+
+ return NULL;
+}
+
#endif /* _LIVEPATCH_CORE_H */
--
2.52.0
^ permalink raw reply related [flat|nested] 12+ messages in thread* [PATCH v3 3/7] livepatch: Support scoped atomic replace using replace_set
2026-06-07 13:16 [PATCH v3 0/7] livepatch: Introduce replace set support Yafang Shao
2026-06-07 13:16 ` [PATCH v3 1/7] livepatch: Fix NULL pointer dereference in klp_find_func() Yafang Shao
2026-06-07 13:16 ` [PATCH v3 2/7] livepatch: Move klp_find_func() into core.h Yafang Shao
@ 2026-06-07 13:16 ` Yafang Shao
2026-06-07 13:33 ` sashiko-bot
2026-06-07 13:16 ` [PATCH v3 4/7] livepatch: Deprecate stack_order Yafang Shao
` (3 subsequent siblings)
6 siblings, 1 reply; 12+ messages in thread
From: Yafang Shao @ 2026-06-07 13:16 UTC (permalink / raw)
To: jpoimboe, jikos, mbenes, pmladek, joe.lawrence, song
Cc: live-patching, Yafang Shao
Convert the replace attribute from a boolean to a u32 to function as a
"replace set." A newly loaded livepatch will now atomically replace any
existing patch belonging to the same set. There can only ever be one active
livepatch for a given replace_set number.
This change currently supports function replacement only. Livepatches that
belong to different replace sets cannot modify the same function. If a new
livepatch attempts to modify a function already modified by an older
livepatch from a different replace_set, the loading of the new livepatch
will be refused.
Similarly, for the KLP state, livepatches belonging to different replace
sets cannot use the same state ID. The system will refuse to load a new
livepatch if it uses a state ID already in use by an older livepatch from
a different replace_set.
For the KLP shadow variable mechanism, developers must assign unique shadow
IDs to livepatches that belong to different replace sets.
Support for replace_set compatibility with KLP state and shadow variables
will be implemented after Petr's KLP state transfer work is completed [0].
Other user-visible changes include:
- The non-replace model is now deprecated
- /sys/kernel/livepatch/livepatch_XXX/replace attribute is replaced by
/sys/kernel/livepatch/livepatch_XXX/replace_set
Link: https://github.com/pmladek/linux/tree/klp-state-transfer-v1-iter12 [0]
Suggested-by: Song Liu <song@kernel.org>
Suggested-by: Petr Mladek <pmladek@suse.com>
Signed-off-by: Yafang Shao <laoar.shao@gmail.com>
---
.../ABI/testing/sysfs-kernel-livepatch | 5 +-
.../livepatch/cumulative-patches.rst | 23 ++++++---
Documentation/livepatch/livepatch.rst | 21 ++++----
include/linux/livepatch.h | 5 +-
kernel/livepatch/core.c | 24 +++++----
kernel/livepatch/state.c | 51 +++++++++++++++----
kernel/livepatch/transition.c | 11 ++--
scripts/livepatch/init.c | 6 +--
scripts/livepatch/klp-build | 16 +++---
9 files changed, 104 insertions(+), 58 deletions(-)
diff --git a/Documentation/ABI/testing/sysfs-kernel-livepatch b/Documentation/ABI/testing/sysfs-kernel-livepatch
index 3c3f36b32b57..6d75235a6a2e 100644
--- a/Documentation/ABI/testing/sysfs-kernel-livepatch
+++ b/Documentation/ABI/testing/sysfs-kernel-livepatch
@@ -47,13 +47,12 @@ Description:
disabled when the feature is used. See
Documentation/livepatch/livepatch.rst for more information.
-What: /sys/kernel/livepatch/<patch>/replace
+What: /sys/kernel/livepatch/<patch>/replace_set
Date: Jun 2024
KernelVersion: 6.11.0
Contact: live-patching@vger.kernel.org
Description:
- An attribute which indicates whether the patch supports
- atomic-replace.
+ An attribute to show the replace_set of this livepatch.
What: /sys/kernel/livepatch/<patch>/stack_order
Date: Jan 2025
diff --git a/Documentation/livepatch/cumulative-patches.rst b/Documentation/livepatch/cumulative-patches.rst
index 1931f318976a..0361adb12f6d 100644
--- a/Documentation/livepatch/cumulative-patches.rst
+++ b/Documentation/livepatch/cumulative-patches.rst
@@ -17,18 +17,20 @@ from all older livepatches and completely replace them in one transition.
Usage
-----
-The atomic replace can be enabled by setting "replace" flag in struct klp_patch,
-for example::
+The "replace_set" attribute in ``struct klp_patch`` acts as a **replace_set**,
+defining the scope of the replacement. By default, the replace_set is 0.
+
+For example::
static struct klp_patch patch = {
.mod = THIS_MODULE,
.objs = objs,
- .replace = true,
+ .replace_set = 0,
};
All processes are then migrated to use the code only from the new patch.
-Once the transition is finished, all older patches are automatically
-disabled.
+Once the transition is finished, all older patches with the same replace
+set are automatically disabled. Patches with different tags remain active.
Ftrace handlers are transparently removed from functions that are no
longer modified by the new cumulative patch.
@@ -62,9 +64,14 @@ Limitations:
------------
- Once the operation finishes, there is no straightforward way
- to reverse it and restore the replaced patches atomically.
-
- A good practice is to set .replace flag in any released livepatch.
+ to reverse it and restore the replaced patches (with the same set)
+ atomically.
+
+ A good practice is to use only one (default) "replace_set". It
+ makes sure that there always will be only one enabled livepatch
+ on the system. The consistency model will ensure a safe update
+ between two versions. It prevents potential problems with installing
+ two livepatches doing incompatible functional changes.
Then re-adding an older livepatch is equivalent to downgrading
to that patch. This is safe as long as the livepatches do _not_ do
extra modifications in (un)patching callbacks or in the module_init()
diff --git a/Documentation/livepatch/livepatch.rst b/Documentation/livepatch/livepatch.rst
index acb90164929e..221d8f1e91c4 100644
--- a/Documentation/livepatch/livepatch.rst
+++ b/Documentation/livepatch/livepatch.rst
@@ -347,15 +347,18 @@ to '0'.
5.3. Replacing
--------------
-All enabled patches might get replaced by a cumulative patch that
-has the .replace flag set.
-
-Once the new patch is enabled and the 'transition' finishes then
-all the functions (struct klp_func) associated with the replaced
-patches are removed from the corresponding struct klp_ops. Also
-the ftrace handler is unregistered and the struct klp_ops is
-freed when the related function is not modified by the new patch
-and func_stack list becomes empty.
+There always can be only one livepatch with a given "replace_set" number.
+They always replace each other.
+
+Once the transition is complete, all functions (``struct klp_func``)
+associated with the matching replaced patches are removed from the
+corresponding ``struct klp_ops``. If a function is no longer modified by
+the new patch and its ``func_stack`` list becomes empty, the ftrace
+handler is unregistered and the ``struct klp_ops`` is freed.
+
+Patches with a different replace set are not affected by this process
+and remain active. This allows for the independent management and
+stacking of multiple, non-conflicting livepatch sets.
See Documentation/livepatch/cumulative-patches.rst for more details.
diff --git a/include/linux/livepatch.h b/include/linux/livepatch.h
index ba9e3988c07c..171c08328299 100644
--- a/include/linux/livepatch.h
+++ b/include/linux/livepatch.h
@@ -123,7 +123,8 @@ struct klp_state {
* @mod: reference to the live patch module
* @objs: object entries for kernel objects to be patched
* @states: system states that can get modified
- * @replace: replace all actively used patches
+ * @replace_set:Livepatch using the same @replace_set will get atomically
+ * replaced.
* @list: list node for global list of actively used patches
* @kobj: kobject for sysfs resources
* @obj_list: dynamic list of the object entries
@@ -137,7 +138,7 @@ struct klp_patch {
struct module *mod;
struct klp_object *objs;
struct klp_state *states;
- bool replace;
+ unsigned int replace_set;
/* internal */
struct list_head list;
diff --git a/kernel/livepatch/core.c b/kernel/livepatch/core.c
index 6e15ae96a0a7..361999e5ce3d 100644
--- a/kernel/livepatch/core.c
+++ b/kernel/livepatch/core.c
@@ -329,7 +329,7 @@ int klp_apply_section_relocs(struct module *pmod, Elf_Shdr *sechdrs,
* /sys/kernel/livepatch/<patch>/enabled
* /sys/kernel/livepatch/<patch>/transition
* /sys/kernel/livepatch/<patch>/force
- * /sys/kernel/livepatch/<patch>/replace
+ * /sys/kernel/livepatch/<patch>/replace_set
* /sys/kernel/livepatch/<patch>/stack_order
* /sys/kernel/livepatch/<patch>/<object>
* /sys/kernel/livepatch/<patch>/<object>/patched
@@ -427,13 +427,13 @@ static ssize_t force_store(struct kobject *kobj, struct kobj_attribute *attr,
return count;
}
-static ssize_t replace_show(struct kobject *kobj,
+static ssize_t replace_set_show(struct kobject *kobj,
struct kobj_attribute *attr, char *buf)
{
struct klp_patch *patch;
patch = container_of(kobj, struct klp_patch, kobj);
- return sysfs_emit(buf, "%d\n", patch->replace);
+ return sysfs_emit(buf, "%u\n", patch->replace_set);
}
static ssize_t stack_order_show(struct kobject *kobj,
@@ -460,13 +460,13 @@ static ssize_t stack_order_show(struct kobject *kobj,
static struct kobj_attribute enabled_kobj_attr = __ATTR_RW(enabled);
static struct kobj_attribute transition_kobj_attr = __ATTR_RO(transition);
static struct kobj_attribute force_kobj_attr = __ATTR_WO(force);
-static struct kobj_attribute replace_kobj_attr = __ATTR_RO(replace);
+static struct kobj_attribute replace_set_kobj_attr = __ATTR_RO(replace_set);
static struct kobj_attribute stack_order_kobj_attr = __ATTR_RO(stack_order);
static struct attribute *klp_patch_attrs[] = {
&enabled_kobj_attr.attr,
&transition_kobj_attr.attr,
&force_kobj_attr.attr,
- &replace_kobj_attr.attr,
+ &replace_set_kobj_attr.attr,
&stack_order_kobj_attr.attr,
NULL
};
@@ -600,6 +600,8 @@ static int klp_add_nops(struct klp_patch *patch)
klp_for_each_object(old_patch, old_obj) {
int err;
+ if (patch->replace_set != old_patch->replace_set)
+ continue;
err = klp_add_object_nops(patch, old_obj);
if (err)
return err;
@@ -772,6 +774,8 @@ void klp_free_replaced_patches_async(struct klp_patch *new_patch)
klp_for_each_patch_safe(old_patch, tmp_patch) {
if (old_patch == new_patch)
return;
+ if (old_patch->replace_set != new_patch->replace_set)
+ continue;
klp_free_patch_async(old_patch);
}
}
@@ -967,11 +971,9 @@ static int klp_init_patch(struct klp_patch *patch)
if (ret)
return ret;
- if (patch->replace) {
- ret = klp_add_nops(patch);
- if (ret)
- return ret;
- }
+ ret = klp_add_nops(patch);
+ if (ret)
+ return ret;
klp_for_each_object(patch, obj) {
ret = klp_init_object(patch, obj);
@@ -1174,6 +1176,8 @@ void klp_unpatch_replaced_patches(struct klp_patch *new_patch)
if (old_patch == new_patch)
return;
+ if (old_patch->replace_set != new_patch->replace_set)
+ continue;
old_patch->enabled = false;
klp_unpatch_objects(old_patch);
}
diff --git a/kernel/livepatch/state.c b/kernel/livepatch/state.c
index 2565d039ade0..a1ac46637336 100644
--- a/kernel/livepatch/state.c
+++ b/kernel/livepatch/state.c
@@ -85,34 +85,65 @@ EXPORT_SYMBOL_GPL(klp_get_prev_state);
/* Check if the patch is able to deal with the existing system state. */
static bool klp_is_state_compatible(struct klp_patch *patch,
+ struct klp_patch *old_patch,
struct klp_state *old_state)
{
struct klp_state *state;
state = klp_get_state(patch, old_state->id);
+ if (patch->replace_set == old_patch->replace_set) {
+ /*
+ * If the new livepatch shares a state set with an existing
+ * one, it must maintain compatibility with all states
+ * modified by the old patch.
+ */
+ if (!state)
+ return false;
+ return state->version >= old_state->version;
- /* A cumulative livepatch must handle all already modified states. */
- if (!state)
- return !patch->replace;
+ }
- return state->version >= old_state->version;
+ /*
+ * Two livepatches with a different "replace_set" must _not_ use
+ * the same "state->id.
+ */
+ return !state;
}
-/*
- * Check that the new livepatch will not break the existing system states.
- * Cumulative patches must handle all already modified states.
- * Non-cumulative patches can touch already modified states.
- */
+/* Check that the new livepatch will not break the existing system states. */
bool klp_is_patch_compatible(struct klp_patch *patch)
{
+ struct klp_object *obj, *old_obj;
struct klp_patch *old_patch;
struct klp_state *old_state;
+ struct klp_func *func;
klp_for_each_patch(old_patch) {
klp_for_each_state(old_patch, old_state) {
- if (!klp_is_state_compatible(patch, old_state))
+ if (!klp_is_state_compatible(patch, old_patch, old_state))
return false;
}
+
+ if (old_patch->replace_set == patch->replace_set)
+ continue;
+
+ /*
+ * Refuse loading a livepatch which would want to modify a
+ * function which is already livepatched with the livepatch
+ * with another "replace_set".
+ */
+ klp_for_each_object_static(patch, obj) {
+ klp_for_each_object(old_patch, old_obj) {
+ if (!!obj->name != !!old_obj->name)
+ continue;
+ if (obj->name && strcmp(obj->name, old_obj->name))
+ continue;
+ klp_for_each_func_static(obj, func) {
+ if (klp_find_func(old_obj, func))
+ return false;
+ }
+ }
+ }
}
return true;
diff --git a/kernel/livepatch/transition.c b/kernel/livepatch/transition.c
index 2351a19ac2a9..8b756dbaa57e 100644
--- a/kernel/livepatch/transition.c
+++ b/kernel/livepatch/transition.c
@@ -89,7 +89,7 @@ static void klp_complete_transition(void)
klp_transition_patch->mod->name,
klp_target_state == KLP_TRANSITION_PATCHED ? "patching" : "unpatching");
- if (klp_transition_patch->replace && klp_target_state == KLP_TRANSITION_PATCHED) {
+ if (klp_target_state == KLP_TRANSITION_PATCHED) {
klp_unpatch_replaced_patches(klp_transition_patch);
klp_discard_nops(klp_transition_patch);
}
@@ -498,7 +498,7 @@ void klp_try_complete_transition(void)
*/
if (!patch->enabled)
klp_free_patch_async(patch);
- else if (patch->replace)
+ else
klp_free_replaced_patches_async(patch);
}
@@ -720,11 +720,12 @@ void klp_force_transition(void)
klp_update_patch_state(idle_task(cpu));
/* Set forced flag for patches being removed. */
- if (klp_target_state == KLP_TRANSITION_UNPATCHED)
+ if (klp_target_state == KLP_TRANSITION_UNPATCHED) {
klp_transition_patch->forced = true;
- else if (klp_transition_patch->replace) {
+ } else {
klp_for_each_patch(patch) {
- if (patch != klp_transition_patch)
+ if (patch != klp_transition_patch &&
+ patch->replace_set == klp_transition_patch->replace_set)
patch->forced = true;
}
}
diff --git a/scripts/livepatch/init.c b/scripts/livepatch/init.c
index f14d8c8fb35f..df4fbcaf4c12 100644
--- a/scripts/livepatch/init.c
+++ b/scripts/livepatch/init.c
@@ -72,10 +72,10 @@ static int __init livepatch_mod_init(void)
/* TODO patch->states */
-#ifdef KLP_NO_REPLACE
- patch->replace = false;
+#ifdef KLP_REPLACE_SET
+ patch->replace_set = KLP_REPLACE_SET;
#else
- patch->replace = true;
+ patch->replace_set = 0;
#endif
return klp_enable_patch(patch);
diff --git a/scripts/livepatch/klp-build b/scripts/livepatch/klp-build
index 0ad7e6631314..1c6c1c4f3190 100755
--- a/scripts/livepatch/klp-build
+++ b/scripts/livepatch/klp-build
@@ -22,7 +22,7 @@ shopt -s lastpipe
unset DEBUG_CLONE DIFF_CHECKSUM SKIP_CLEANUP XTRACE
-REPLACE=1
+REPLACE_SET=0
SHORT_CIRCUIT=0
JOBS="$(getconf _NPROCESSORS_ONLN)"
VERBOSE="-s"
@@ -134,7 +134,7 @@ Options:
-f, --show-first-changed Show address of first changed instruction
-j, --jobs=<jobs> Build jobs to run simultaneously [default: $JOBS]
-o, --output=<file.ko> Output file [default: livepatch-<patch-name>.ko]
- --no-replace Disable livepatch atomic replace
+ -s, --replace-set=<set> Set the atomic replace set for this livepatch
-v, --verbose Pass V=1 to kernel/module builds
Advanced Options:
@@ -159,8 +159,8 @@ process_args() {
local long
local args
- short="hfj:o:vdS:T"
- long="help,show-first-changed,jobs:,output:,no-replace,verbose,debug,short-circuit:,keep-tmp"
+ short="hfj:o:s:vdS:T"
+ long="help,show-first-changed,jobs:,output:,replace-set:,verbose,debug,short-circuit:,keep-tmp"
args=$(getopt --options "$short" --longoptions "$long" -- "$@") || {
echo; usage; exit
@@ -189,9 +189,9 @@ process_args() {
NAME="$(module_name_string "$NAME")"
shift 2
;;
- --no-replace)
- REPLACE=0
- shift
+ -s | --replace-set)
+ REPLACE_SET="$2"
+ shift 2
;;
-v | --verbose)
VERBOSE="V=1"
@@ -777,7 +777,7 @@ build_patch_module() {
cflags=("-ffunction-sections")
cflags+=("-fdata-sections")
- [[ $REPLACE -eq 0 ]] && cflags+=("-DKLP_NO_REPLACE")
+ cflags+=("-DKLP_REPLACE_SET=$REPLACE_SET")
cmd=("make")
cmd+=("$VERBOSE")
--
2.52.0
^ permalink raw reply related [flat|nested] 12+ messages in thread* Re: [PATCH v3 3/7] livepatch: Support scoped atomic replace using replace_set
2026-06-07 13:16 ` [PATCH v3 3/7] livepatch: Support scoped atomic replace using replace_set Yafang Shao
@ 2026-06-07 13:33 ` sashiko-bot
2026-06-07 14:00 ` Yafang Shao
0 siblings, 1 reply; 12+ messages in thread
From: sashiko-bot @ 2026-06-07 13:33 UTC (permalink / raw)
To: joe.lawrence, mbenes, jikos, song, jpoimboe, pmladek
Cc: live-patching, Yafang Shao
Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
- [High] The newly introduced check in `klp_is_patch_compatible` causes a kernel panic if a livepatch module contains a `klp_func` entry with `old_name == NULL`.
--
commit ed678e88294b650a730cda1a47351fce99677c1e
Author: Yafang Shao <laoar.shao@gmail.com>
livepatch: Support scoped atomic replace using replace_set
Convert the replace attribute from a boolean to a u32 to function as a
"replace set." A newly loaded livepatch will now atomically replace any
existing patch belonging to the same set.
Link: https://github.com/pmladek/linux/tree/klp-state-transfer-v1-iter12 [0]
[ ... ]
> --- a/kernel/livepatch/state.c
> +++ b/kernel/livepatch/state.c
[ ... ]
> + /*
> + * Refuse loading a livepatch which would want to modify a
> + * function which is already livepatched with the livepatch
> + * with another "replace_set".
> + */
> + klp_for_each_object_static(patch, obj) {
> + klp_for_each_object(old_patch, old_obj) {
> + if (!!obj->name != !!old_obj->name)
> + continue;
> + if (obj->name && strcmp(obj->name, old_obj->name))
> + continue;
> + klp_for_each_func_static(obj, func) {
> + if (klp_find_func(old_obj, func))
> + return false;
> + }
> + }
> + }
> }
[Severity: High]
Can passing an unvalidated function to klp_find_func() cause a NULL pointer
dereference here?
If a new livepatch module is loaded with an improperly initialized klp_func
array (e.g., accidentally missing old_name but providing new_func),
klp_for_each_func_static() will still process it because structural
validation in klp_init_patch() hasn't occurred yet at this point in the
loading process.
When this unvalidated func is passed as the second argument to
klp_find_func(old_obj, func), the roles inside klp_find_func() are reversed
from its usual context. The old_func parameter represents the new patch's
unvalidated function, while the local func iterator represents the
already-installed patch.
Inside klp_find_func(), the check evaluates:
if ((func->old_name && strcmp(old_func->old_name, func->old_name) == 0)
Since the installed patch's func->old_name is guaranteed to be non-NULL,
strcmp() will be called with old_func->old_name. If old_func->old_name is
NULL, does this turn what would normally be a graceful -EINVAL module
rejection into a system crash?
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260607131659.29281-1-laoar.shao@gmail.com?part=3
^ permalink raw reply [flat|nested] 12+ messages in thread* Re: [PATCH v3 3/7] livepatch: Support scoped atomic replace using replace_set
2026-06-07 13:33 ` sashiko-bot
@ 2026-06-07 14:00 ` Yafang Shao
0 siblings, 0 replies; 12+ messages in thread
From: Yafang Shao @ 2026-06-07 14:00 UTC (permalink / raw)
To: sashiko-reviews
Cc: joe.lawrence, mbenes, jikos, song, jpoimboe, pmladek,
live-patching
On Sun, Jun 7, 2026 at 9:33 PM <sashiko-bot@kernel.org> wrote:
>
> Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
> - [High] The newly introduced check in `klp_is_patch_compatible` causes a kernel panic if a livepatch module contains a `klp_func` entry with `old_name == NULL`.
> --
>
> commit ed678e88294b650a730cda1a47351fce99677c1e
> Author: Yafang Shao <laoar.shao@gmail.com>
>
> livepatch: Support scoped atomic replace using replace_set
>
> Convert the replace attribute from a boolean to a u32 to function as a
> "replace set." A newly loaded livepatch will now atomically replace any
> existing patch belonging to the same set.
>
> Link: https://github.com/pmladek/linux/tree/klp-state-transfer-v1-iter12 [0]
>
> [ ... ]
> > --- a/kernel/livepatch/state.c
> > +++ b/kernel/livepatch/state.c
> [ ... ]
> > + /*
> > + * Refuse loading a livepatch which would want to modify a
> > + * function which is already livepatched with the livepatch
> > + * with another "replace_set".
> > + */
> > + klp_for_each_object_static(patch, obj) {
> > + klp_for_each_object(old_patch, old_obj) {
> > + if (!!obj->name != !!old_obj->name)
> > + continue;
> > + if (obj->name && strcmp(obj->name, old_obj->name))
> > + continue;
> > + klp_for_each_func_static(obj, func) {
> > + if (klp_find_func(old_obj, func))
> > + return false;
> > + }
> > + }
> > + }
> > }
>
> [Severity: High]
> Can passing an unvalidated function to klp_find_func() cause a NULL pointer
> dereference here?
>
> If a new livepatch module is loaded with an improperly initialized klp_func
> array (e.g., accidentally missing old_name but providing new_func),
> klp_for_each_func_static() will still process it because structural
> validation in klp_init_patch() hasn't occurred yet at this point in the
> loading process.
>
> When this unvalidated func is passed as the second argument to
> klp_find_func(old_obj, func), the roles inside klp_find_func() are reversed
> from its usual context. The old_func parameter represents the new patch's
> unvalidated function, while the local func iterator represents the
> already-installed patch.
>
> Inside klp_find_func(), the check evaluates:
>
> if ((func->old_name && strcmp(old_func->old_name, func->old_name) == 0)
>
> Since the installed patch's func->old_name is guaranteed to be non-NULL,
> strcmp() will be called with old_func->old_name. If old_func->old_name is
> NULL, does this turn what would normally be a graceful -EINVAL module
> rejection into a system crash?
Oops, I missed that. It seems we should better check both func->name
and old_func->name in patch #1.
--- a/kernel/livepatch/core.c
+++ b/kernel/livepatch/core.c
@@ -82,20 +82,22 @@ static bool klp_initialized(void)
return !!klp_root_kobj;
}
+/* Check if @func is present in @obj */
static struct klp_func *klp_find_func(struct klp_object *obj,
- struct klp_func *old_func)
+ struct klp_func *func)
{
- struct klp_func *func;
+ struct klp_func *obj_func;
- klp_for_each_func(obj, func) {
+ klp_for_each_func(obj, obj_func) {
/*
* Besides identical old_sympos, also consider old_sympos
* of 0 and 1 are identical.
*/
- if ((strcmp(old_func->old_name, func->old_name) == 0) &&
- ((old_func->old_sympos == func->old_sympos) ||
- (old_func->old_sympos == 0 && func->old_sympos == 1) ||
- (old_func->old_sympos == 1 && func->old_sympos == 0))) {
+ if ((obj_func->old_name && func->old_name &&
+ strcmp(obj_func->old_name, func->old_name) == 0) &&
+ ((obj_func->old_sympos == func->old_sympos) ||
+ (obj_func->old_sympos == 0 && func->old_sympos == 1) ||
+ (obj_func->old_sympos == 1 && func->old_sympos == 0))) {
return func;
}
}
--
Regards
Yafang
^ permalink raw reply [flat|nested] 12+ messages in thread
* [PATCH v3 4/7] livepatch: Deprecate stack_order
2026-06-07 13:16 [PATCH v3 0/7] livepatch: Introduce replace set support Yafang Shao
` (2 preceding siblings ...)
2026-06-07 13:16 ` [PATCH v3 3/7] livepatch: Support scoped atomic replace using replace_set Yafang Shao
@ 2026-06-07 13:16 ` Yafang Shao
2026-06-07 13:31 ` sashiko-bot
2026-06-07 13:16 ` [PATCH v3 5/7] selftests/livepatch: Update tests for replace_set Yafang Shao
` (2 subsequent siblings)
6 siblings, 1 reply; 12+ messages in thread
From: Yafang Shao @ 2026-06-07 13:16 UTC (permalink / raw)
To: jpoimboe, jikos, mbenes, pmladek, joe.lawrence, song
Cc: live-patching, Yafang Shao, Wardenjohn
stack_order is no longer needed for atomic-replace livepatches, as a
single function can only be modified by a unique replace_set.
To maintain backward compatibility, print a dummy value, as suggested by
sashiko-bot.
Signed-off-by: Yafang Shao <laoar.shao@gmail.com>
Cc: Wardenjohn <zhangwarden@gmail.com>
---
.../ABI/removed/sysfs-kernel-livepatch | 9 +++
.../ABI/testing/sysfs-kernel-livepatch | 9 ---
kernel/livepatch/core.c | 20 +-----
.../testing/selftests/livepatch/test-sysfs.sh | 69 -------------------
4 files changed, 12 insertions(+), 95 deletions(-)
create mode 100644 Documentation/ABI/removed/sysfs-kernel-livepatch
diff --git a/Documentation/ABI/removed/sysfs-kernel-livepatch b/Documentation/ABI/removed/sysfs-kernel-livepatch
new file mode 100644
index 000000000000..72c1ce0ea864
--- /dev/null
+++ b/Documentation/ABI/removed/sysfs-kernel-livepatch
@@ -0,0 +1,9 @@
+What: /sys/kernel/livepatch/<patch>/stack_order
+Date: Jan 2025
+KernelVersion: 6.14.0
+Description:
+ This attribute specifies the sequence in which live patch modules
+ are applied to the system. If multiple live patches modify the same
+ function, the implementation with the biggest 'stack_order' number
+ is used, unless a transition is currently in progress.
+
diff --git a/Documentation/ABI/testing/sysfs-kernel-livepatch b/Documentation/ABI/testing/sysfs-kernel-livepatch
index 6d75235a6a2e..9d356d8b8c75 100644
--- a/Documentation/ABI/testing/sysfs-kernel-livepatch
+++ b/Documentation/ABI/testing/sysfs-kernel-livepatch
@@ -54,15 +54,6 @@ Contact: live-patching@vger.kernel.org
Description:
An attribute to show the replace_set of this livepatch.
-What: /sys/kernel/livepatch/<patch>/stack_order
-Date: Jan 2025
-KernelVersion: 6.14.0
-Description:
- This attribute specifies the sequence in which live patch modules
- are applied to the system. If multiple live patches modify the same
- function, the implementation with the biggest 'stack_order' number
- is used, unless a transition is currently in progress.
-
What: /sys/kernel/livepatch/<patch>/<object>
Date: Nov 2014
KernelVersion: 3.19.0
diff --git a/kernel/livepatch/core.c b/kernel/livepatch/core.c
index 361999e5ce3d..c73eccd2cd13 100644
--- a/kernel/livepatch/core.c
+++ b/kernel/livepatch/core.c
@@ -330,7 +330,7 @@ int klp_apply_section_relocs(struct module *pmod, Elf_Shdr *sechdrs,
* /sys/kernel/livepatch/<patch>/transition
* /sys/kernel/livepatch/<patch>/force
* /sys/kernel/livepatch/<patch>/replace_set
- * /sys/kernel/livepatch/<patch>/stack_order
+ * /sys/kernel/livepatch/<patch>/stack_order <deprecated>
* /sys/kernel/livepatch/<patch>/<object>
* /sys/kernel/livepatch/<patch>/<object>/patched
* /sys/kernel/livepatch/<patch>/<object>/<function,sympos>
@@ -436,25 +436,11 @@ static ssize_t replace_set_show(struct kobject *kobj,
return sysfs_emit(buf, "%u\n", patch->replace_set);
}
+/* This interface is deprecated and will be removed in the next release */
static ssize_t stack_order_show(struct kobject *kobj,
struct kobj_attribute *attr, char *buf)
{
- struct klp_patch *patch, *this_patch;
- int stack_order = 0;
-
- this_patch = container_of(kobj, struct klp_patch, kobj);
-
- mutex_lock(&klp_mutex);
-
- klp_for_each_patch(patch) {
- stack_order++;
- if (patch == this_patch)
- break;
- }
-
- mutex_unlock(&klp_mutex);
-
- return sysfs_emit(buf, "%d\n", stack_order);
+ return sysfs_emit(buf, "-1\n");
}
static struct kobj_attribute enabled_kobj_attr = __ATTR_RW(enabled);
diff --git a/tools/testing/selftests/livepatch/test-sysfs.sh b/tools/testing/selftests/livepatch/test-sysfs.sh
index 58fe1d96997c..0c31759f34f6 100755
--- a/tools/testing/selftests/livepatch/test-sysfs.sh
+++ b/tools/testing/selftests/livepatch/test-sysfs.sh
@@ -21,8 +21,6 @@ check_sysfs_rights "$MOD_LIVEPATCH" "enabled" "-rw-r--r--"
check_sysfs_value "$MOD_LIVEPATCH" "enabled" "1"
check_sysfs_rights "$MOD_LIVEPATCH" "force" "--w-------"
check_sysfs_rights "$MOD_LIVEPATCH" "replace" "-r--r--r--"
-check_sysfs_rights "$MOD_LIVEPATCH" "stack_order" "-r--r--r--"
-check_sysfs_value "$MOD_LIVEPATCH" "stack_order" "1"
check_sysfs_rights "$MOD_LIVEPATCH" "transition" "-r--r--r--"
check_sysfs_value "$MOD_LIVEPATCH" "transition" "0"
check_sysfs_rights "$MOD_LIVEPATCH" "vmlinux/patched" "-r--r--r--"
@@ -135,71 +133,4 @@ livepatch: '$MOD_LIVEPATCH': completing unpatching transition
livepatch: '$MOD_LIVEPATCH': unpatching complete
% rmmod $MOD_LIVEPATCH"
-start_test "sysfs test stack_order value"
-
-load_lp $MOD_LIVEPATCH
-
-check_sysfs_value "$MOD_LIVEPATCH" "stack_order" "1"
-
-load_lp $MOD_LIVEPATCH2
-
-check_sysfs_value "$MOD_LIVEPATCH2" "stack_order" "2"
-
-load_lp $MOD_LIVEPATCH3
-
-check_sysfs_value "$MOD_LIVEPATCH3" "stack_order" "3"
-
-disable_lp $MOD_LIVEPATCH2
-unload_lp $MOD_LIVEPATCH2
-
-check_sysfs_value "$MOD_LIVEPATCH" "stack_order" "1"
-check_sysfs_value "$MOD_LIVEPATCH3" "stack_order" "2"
-
-disable_lp $MOD_LIVEPATCH3
-unload_lp $MOD_LIVEPATCH3
-
-disable_lp $MOD_LIVEPATCH
-unload_lp $MOD_LIVEPATCH
-
-check_result "% insmod test_modules/$MOD_LIVEPATCH.ko
-livepatch: enabling patch '$MOD_LIVEPATCH'
-livepatch: '$MOD_LIVEPATCH': initializing patching transition
-livepatch: '$MOD_LIVEPATCH': starting patching transition
-livepatch: '$MOD_LIVEPATCH': completing patching transition
-livepatch: '$MOD_LIVEPATCH': patching complete
-% insmod test_modules/$MOD_LIVEPATCH2.ko
-livepatch: enabling patch '$MOD_LIVEPATCH2'
-livepatch: '$MOD_LIVEPATCH2': initializing patching transition
-$MOD_LIVEPATCH2: pre_patch_callback: vmlinux
-livepatch: '$MOD_LIVEPATCH2': starting patching transition
-livepatch: '$MOD_LIVEPATCH2': completing patching transition
-$MOD_LIVEPATCH2: post_patch_callback: vmlinux
-livepatch: '$MOD_LIVEPATCH2': patching complete
-% insmod test_modules/$MOD_LIVEPATCH3.ko
-livepatch: enabling patch '$MOD_LIVEPATCH3'
-livepatch: '$MOD_LIVEPATCH3': initializing patching transition
-livepatch: '$MOD_LIVEPATCH3': starting patching transition
-livepatch: '$MOD_LIVEPATCH3': completing patching transition
-livepatch: '$MOD_LIVEPATCH3': patching complete
-% echo 0 > $SYSFS_KLP_DIR/$MOD_LIVEPATCH2/enabled
-livepatch: '$MOD_LIVEPATCH2': initializing unpatching transition
-$MOD_LIVEPATCH2: pre_unpatch_callback: vmlinux
-livepatch: '$MOD_LIVEPATCH2': starting unpatching transition
-livepatch: '$MOD_LIVEPATCH2': completing unpatching transition
-$MOD_LIVEPATCH2: post_unpatch_callback: vmlinux
-livepatch: '$MOD_LIVEPATCH2': unpatching complete
-% rmmod $MOD_LIVEPATCH2
-% echo 0 > $SYSFS_KLP_DIR/$MOD_LIVEPATCH3/enabled
-livepatch: '$MOD_LIVEPATCH3': initializing unpatching transition
-livepatch: '$MOD_LIVEPATCH3': starting unpatching transition
-livepatch: '$MOD_LIVEPATCH3': completing unpatching transition
-livepatch: '$MOD_LIVEPATCH3': unpatching complete
-% rmmod $MOD_LIVEPATCH3
-% echo 0 > $SYSFS_KLP_DIR/$MOD_LIVEPATCH/enabled
-livepatch: '$MOD_LIVEPATCH': initializing unpatching transition
-livepatch: '$MOD_LIVEPATCH': starting unpatching transition
-livepatch: '$MOD_LIVEPATCH': completing unpatching transition
-livepatch: '$MOD_LIVEPATCH': unpatching complete
-% rmmod $MOD_LIVEPATCH"
-
exit 0
--
2.52.0
^ permalink raw reply related [flat|nested] 12+ messages in thread* Re: [PATCH v3 4/7] livepatch: Deprecate stack_order
2026-06-07 13:16 ` [PATCH v3 4/7] livepatch: Deprecate stack_order Yafang Shao
@ 2026-06-07 13:31 ` sashiko-bot
0 siblings, 0 replies; 12+ messages in thread
From: sashiko-bot @ 2026-06-07 13:31 UTC (permalink / raw)
To: pmladek, mbenes, jpoimboe, song, joe.lawrence, jikos
Cc: live-patching, Yafang Shao, Wardenjohn
Thank you for your contribution! Sashiko AI review found 4 potential issue(s) to consider:
New issues:
- [Medium] Returning `-1` as the dummy value for the deprecated `stack_order` sysfs attribute risks breaking backward compatibility.
- [Low] Test coverage for the deprecated `stack_order` sysfs attribute was completely removed instead of being updated.
- [Low] The deprecated `stack_order` ABI documentation is placed in the wrong directory and contains an outdated description.
Pre-existing issues:
- [High] A NULL pointer dereference occurs in `klp_find_func()` when verifying patch compatibility with a malformed livepatch that omits `.old_name`.
--
commit ea264255177cd1b15e55c6d6aa2b21dbb4abd6c2
Author: Yafang Shao <laoar.shao@gmail.com>
livepatch: Deprecate stack_order
stack_order is no longer needed for atomic-replace livepatches, as a
single function can only be modified by a unique replace_set.
To maintain backward compatibility, print a dummy value, as suggested by
sashiko-bot.
> diff --git a/Documentation/ABI/removed/sysfs-kernel-livepatch b/Documentation/ABI/removed/sysfs-kernel-livepatch
> new file mode 100644
> index 0000000000000..72c1ce0ea8647
> --- /dev/null
> +++ b/Documentation/ABI/removed/sysfs-kernel-livepatch
> @@ -0,0 +1,9 @@
> +What: /sys/kernel/livepatch/<patch>/stack_order
> +Date: Jan 2025
> +KernelVersion: 6.14.0
> +Description:
> + This attribute specifies the sequence in which live patch modules
> + are applied to the system. If multiple live patches modify the same
> + function, the implementation with the biggest 'stack_order' number
> + is used, unless a transition is currently in progress.
[Severity: Low]
Is Documentation/ABI/removed/ the right place for this file since the
attribute is still present in the kernel?
According to Documentation/ABI/README, the removed/ directory is strictly
for interfaces that have been completely deleted. Since the attribute still
exists and returns a dummy value, should this be placed in obsolete/ instead?
Also, should the description be updated to explain that the attribute is now
obsolete and just returns a dummy value, rather than claiming it specifies
the sequence in which live patch modules are applied?
> diff --git a/kernel/livepatch/core.c b/kernel/livepatch/core.c
> index 361999e5ce3da..c73eccd2cd139 100644
> --- a/kernel/livepatch/core.c
> +++ b/kernel/livepatch/core.c
[ ... ]
> @@ -436,25 +436,11 @@ static ssize_t replace_set_show(struct kobject *kobj,
> return sysfs_emit(buf, "%u\n", patch->replace_set);
> }
>
> +/* This interface is deprecated and will be removed in the next release */
> static ssize_t stack_order_show(struct kobject *kobj,
> struct kobj_attribute *attr, char *buf)
> {
> - struct klp_patch *patch, *this_patch;
> - int stack_order = 0;
> -
> - this_patch = container_of(kobj, struct klp_patch, kobj);
> -
> - mutex_lock(&klp_mutex);
> -
> - klp_for_each_patch(patch) {
> - stack_order++;
> - if (patch == this_patch)
> - break;
> - }
> -
> - mutex_unlock(&klp_mutex);
> -
> - return sysfs_emit(buf, "%d\n", stack_order);
> + return sysfs_emit(buf, "-1\n");
[Severity: Medium]
Will returning -1 break userspace tools that expect a non-negative integer
or parse this as unsigned?
The commit message mentions printing a dummy value to maintain backward
compatibility. Since the previous implementation returned a strictly positive
sequence (1, 2, ...), could we use a previously valid integer like 1 to
ensure older scripts don't fail when encountering a negative sign?
[Severity: High]
This isn't a bug introduced by this patch, but an earlier commit in this
series ("livepatch: Support scoped atomic replace using replace_set")
appears to introduce a NULL pointer dereference when a malformed patch
omits .old_name.
When klp_enable_patch() is called, it invokes klp_is_patch_compatible()
before klp_init_patch() validates that func->old_name is non-NULL.
The klp_is_patch_compatible() function passes unvalidated functions to
klp_find_func() as the old_func argument:
kernel/livepatch/state.c:klp_is_patch_compatible() {
...
if (klp_find_func(old_obj, func))
return false;
...
}
Inside klp_find_func(), old_func->old_name is unconditionally dereferenced
in the first argument to strcmp():
kernel/livepatch/core.h:klp_find_func() {
...
if ((func->old_name && strcmp(old_func->old_name, func->old_name) == 0) &&
...
}
Could this trigger a kernel panic during patch load?
> diff --git a/tools/testing/selftests/livepatch/test-sysfs.sh b/tools/testing/selftests/livepatch/test-sysfs.sh
> index 58fe1d96997cd..0c31759f34f63 100755
> --- a/tools/testing/selftests/livepatch/test-sysfs.sh
> +++ b/tools/testing/selftests/livepatch/test-sysfs.sh
> @@ -21,8 +21,6 @@ check_sysfs_rights "$MOD_LIVEPATCH" "enabled" "-rw-r--r--"
> check_sysfs_value "$MOD_LIVEPATCH" "enabled" "1"
> check_sysfs_rights "$MOD_LIVEPATCH" "force" "--w-------"
> check_sysfs_rights "$MOD_LIVEPATCH" "replace" "-r--r--r--"
> -check_sysfs_rights "$MOD_LIVEPATCH" "stack_order" "-r--r--r--"
> -check_sysfs_value "$MOD_LIVEPATCH" "stack_order" "1"
[Severity: Low]
Does removing these checks completely leave the deprecated attribute untested?
Since stack_order is intentionally kept to maintain backward compatibility,
should the test suite still verify its presence, correct permissions, and
the new dummy output? This would ensure future changes don't accidentally
remove the sysfs node before the deprecation period ends.
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260607131659.29281-1-laoar.shao@gmail.com?part=4
^ permalink raw reply [flat|nested] 12+ messages in thread
* [PATCH v3 5/7] selftests/livepatch: Update tests for replace_set
2026-06-07 13:16 [PATCH v3 0/7] livepatch: Introduce replace set support Yafang Shao
` (3 preceding siblings ...)
2026-06-07 13:16 ` [PATCH v3 4/7] livepatch: Deprecate stack_order Yafang Shao
@ 2026-06-07 13:16 ` Yafang Shao
2026-06-07 13:29 ` sashiko-bot
2026-06-07 13:16 ` [PATCH v3 6/7] selftests/livepatch: Add test for state ID conflict across replace_sets Yafang Shao
2026-06-07 13:16 ` [PATCH v3 7/7] selftests/livepatch: Add test for function " Yafang Shao
6 siblings, 1 reply; 12+ messages in thread
From: Yafang Shao @ 2026-06-07 13:16 UTC (permalink / raw)
To: jpoimboe, jikos, mbenes, pmladek, joe.lawrence, song
Cc: live-patching, Yafang Shao
The changes are as follows:
- Replace "replace" with "replace_set"
- For atomic-replace test cases, we configure two different livepatches
that share the same replace_set.
- For non-atomic-replace test cases, we configure multiple livepatches,
each with a different replace_set.
- Remove the test case that an atomic replace livepatch disables
multiple existing livepatches
The result are as follows,
$ ./test-callbacks.sh
TEST: target module before livepatch ... ok
TEST: module_coming notifier ... ok
TEST: module_going notifier ... ok
TEST: module_coming and module_going notifiers ... ok
TEST: target module not present ... ok
TEST: pre-patch callback -ENODEV ... ok
TEST: module_coming + pre-patch callback -ENODEV ... ok
TEST: multiple target modules ... ok
TEST: busy target module ... ok
TEST: multiple livepatches ... ok
TEST: atomic replace ... ok
$ ./test-ftrace.sh
TEST: livepatch interaction with ftrace_enabled sysctl ... ok
TEST: trace livepatched function and check that the live patch remains in effect ... ok
TEST: livepatch a traced function and check that the live patch remains in effect ... ok
$ ./test-kprobe.sh
TEST: livepatch interaction with kprobed function with post_handler ... ok
TEST: livepatch interaction with kprobed function without post_handler ... ok
$ ./test-livepatch.sh
TEST: basic function patching ... ok
TEST: multiple livepatches ... ok
TEST: module function patching ... ok
TEST: module function patching (livepatch first) ... ok
$ ./test-shadow-vars.sh
TEST: basic shadow variable API ... ok
$ ./test-state.sh
TEST: system state modification ... ok
TEST: taking over system state modification ... ok
TEST: compatible cumulative livepatches ... ok
TEST: incompatible cumulative livepatches ... ok
$ ./test-syscall.sh
TEST: patch getpid syscall while being heavily hammered ... ok
Signed-off-by: Yafang Shao <laoar.shao@gmail.com>
---
.../selftests/livepatch/test-callbacks.sh | 33 +++---
.../selftests/livepatch/test-livepatch.sh | 100 +-----------------
.../testing/selftests/livepatch/test-sysfs.sh | 22 ++--
.../test_modules/test_klp_atomic_replace.c | 10 +-
.../test_modules/test_klp_callbacks_demo.c | 6 ++
.../test_modules/test_klp_callbacks_demo2.c | 10 +-
.../test_modules/test_klp_livepatch.c | 6 ++
.../livepatch/test_modules/test_klp_state.c | 2 +-
.../livepatch/test_modules/test_klp_state2.c | 2 +-
9 files changed, 55 insertions(+), 136 deletions(-)
diff --git a/tools/testing/selftests/livepatch/test-callbacks.sh b/tools/testing/selftests/livepatch/test-callbacks.sh
index 2a03deb26a12..692da8ea4c25 100755
--- a/tools/testing/selftests/livepatch/test-callbacks.sh
+++ b/tools/testing/selftests/livepatch/test-callbacks.sh
@@ -451,8 +451,8 @@ $MOD_TARGET_BUSY: busymod_work_func exit
$MOD_TARGET_BUSY: ${MOD_TARGET_BUSY}_exit"
-# Test loading multiple livepatches. This test-case is mainly for comparing
-# with the next test-case.
+# Test loading multiple livepatches sharing different replace_set.
+# This test-case is mainly for comparing with the next test-case.
#
# - Load and unload two livepatches, pre and post (un)patch callbacks
# execute as each patch progresses through its (un)patching
@@ -460,14 +460,14 @@ $MOD_TARGET_BUSY: ${MOD_TARGET_BUSY}_exit"
start_test "multiple livepatches"
-load_lp $MOD_LIVEPATCH
-load_lp $MOD_LIVEPATCH2
+load_lp $MOD_LIVEPATCH replace_set=0
+load_lp $MOD_LIVEPATCH2 replace_set=1
disable_lp $MOD_LIVEPATCH2
disable_lp $MOD_LIVEPATCH
unload_lp $MOD_LIVEPATCH2
unload_lp $MOD_LIVEPATCH
-check_result "% insmod test_modules/$MOD_LIVEPATCH.ko
+check_result "% insmod test_modules/$MOD_LIVEPATCH.ko replace_set=0
livepatch: enabling patch '$MOD_LIVEPATCH'
livepatch: '$MOD_LIVEPATCH': initializing patching transition
$MOD_LIVEPATCH: pre_patch_callback: vmlinux
@@ -475,7 +475,7 @@ livepatch: '$MOD_LIVEPATCH': starting patching transition
livepatch: '$MOD_LIVEPATCH': completing patching transition
$MOD_LIVEPATCH: post_patch_callback: vmlinux
livepatch: '$MOD_LIVEPATCH': patching complete
-% insmod test_modules/$MOD_LIVEPATCH2.ko
+% insmod test_modules/$MOD_LIVEPATCH2.ko replace_set=1
livepatch: enabling patch '$MOD_LIVEPATCH2'
livepatch: '$MOD_LIVEPATCH2': initializing patching transition
$MOD_LIVEPATCH2: pre_patch_callback: vmlinux
@@ -501,14 +501,13 @@ livepatch: '$MOD_LIVEPATCH': unpatching complete
% rmmod $MOD_LIVEPATCH"
-# Load multiple livepatches, but the second as an 'atomic-replace'
-# patch. When the latter loads, the original livepatch should be
-# disabled and *none* of its pre/post-unpatch callbacks executed. On
-# the other hand, when the atomic-replace livepatch is disabled, its
-# pre/post-unpatch callbacks *should* be executed.
+# Load multiple livepatches sharing the same replace_set.
+# When the latter loads, the original livepatch should be disabled and
+# *none* of its pre/post-unpatch callbacks executed. On the other hand,
+# when the atomic-replace livepatch is disabled, its pre/post-unpatch
+# callbacks *should* be executed.
#
-# - Load and unload two livepatches, the second of which has its
-# .replace flag set true.
+# - Load and unload two livepatches sharing the same replace_set
#
# - Pre and post patch callbacks are executed for both livepatches.
#
@@ -517,13 +516,13 @@ livepatch: '$MOD_LIVEPATCH': unpatching complete
start_test "atomic replace"
-load_lp $MOD_LIVEPATCH
-load_lp $MOD_LIVEPATCH2 replace=1
+load_lp $MOD_LIVEPATCH replace_set=0
+load_lp $MOD_LIVEPATCH2 replace_set=0
disable_lp $MOD_LIVEPATCH2
unload_lp $MOD_LIVEPATCH2
unload_lp $MOD_LIVEPATCH
-check_result "% insmod test_modules/$MOD_LIVEPATCH.ko
+check_result "% insmod test_modules/$MOD_LIVEPATCH.ko replace_set=0
livepatch: enabling patch '$MOD_LIVEPATCH'
livepatch: '$MOD_LIVEPATCH': initializing patching transition
$MOD_LIVEPATCH: pre_patch_callback: vmlinux
@@ -531,7 +530,7 @@ livepatch: '$MOD_LIVEPATCH': starting patching transition
livepatch: '$MOD_LIVEPATCH': completing patching transition
$MOD_LIVEPATCH: post_patch_callback: vmlinux
livepatch: '$MOD_LIVEPATCH': patching complete
-% insmod test_modules/$MOD_LIVEPATCH2.ko replace=1
+% insmod test_modules/$MOD_LIVEPATCH2.ko replace_set=0
livepatch: enabling patch '$MOD_LIVEPATCH2'
livepatch: '$MOD_LIVEPATCH2': initializing patching transition
$MOD_LIVEPATCH2: pre_patch_callback: vmlinux
diff --git a/tools/testing/selftests/livepatch/test-livepatch.sh b/tools/testing/selftests/livepatch/test-livepatch.sh
index c44c5341a2f1..4f8fb5620c6e 100755
--- a/tools/testing/selftests/livepatch/test-livepatch.sh
+++ b/tools/testing/selftests/livepatch/test-livepatch.sh
@@ -5,8 +5,6 @@
. $(dirname $0)/functions.sh
MOD_LIVEPATCH1=test_klp_livepatch
-MOD_LIVEPATCH2=test_klp_syscall
-MOD_LIVEPATCH3=test_klp_callbacks_demo
MOD_REPLACE=test_klp_atomic_replace
MOD_TARGET=test_klp_mod_target
MOD_TARGET_PATCH=test_klp_mod_patch
@@ -57,12 +55,12 @@ livepatch: '$MOD_LIVEPATCH1': unpatching complete
start_test "multiple livepatches"
-load_lp $MOD_LIVEPATCH1
+load_lp $MOD_LIVEPATCH1 replace_set=0
grep 'live patched' /proc/cmdline > /dev/kmsg
grep 'live patched' /proc/meminfo > /dev/kmsg
-load_lp $MOD_REPLACE replace=0
+load_lp $MOD_REPLACE replace_set=1
grep 'live patched' /proc/cmdline > /dev/kmsg
grep 'live patched' /proc/meminfo > /dev/kmsg
@@ -79,14 +77,14 @@ unload_lp $MOD_LIVEPATCH1
grep 'live patched' /proc/cmdline > /dev/kmsg
grep 'live patched' /proc/meminfo > /dev/kmsg
-check_result "% insmod test_modules/$MOD_LIVEPATCH1.ko
+check_result "% insmod test_modules/$MOD_LIVEPATCH1.ko replace_set=0
livepatch: enabling patch '$MOD_LIVEPATCH1'
livepatch: '$MOD_LIVEPATCH1': initializing patching transition
livepatch: '$MOD_LIVEPATCH1': starting patching transition
livepatch: '$MOD_LIVEPATCH1': completing patching transition
livepatch: '$MOD_LIVEPATCH1': patching complete
$MOD_LIVEPATCH1: this has been live patched
-% insmod test_modules/$MOD_REPLACE.ko replace=0
+% insmod test_modules/$MOD_REPLACE.ko replace_set=1
livepatch: enabling patch '$MOD_REPLACE'
livepatch: '$MOD_REPLACE': initializing patching transition
livepatch: '$MOD_REPLACE': starting patching transition
@@ -108,96 +106,6 @@ livepatch: '$MOD_LIVEPATCH1': completing unpatching transition
livepatch: '$MOD_LIVEPATCH1': unpatching complete
% rmmod $MOD_LIVEPATCH1"
-
-# - load a livepatch that modifies the output from /proc/cmdline and
-# verify correct behavior
-# - load two additional livepatches and check the number of livepatch modules
-# applied
-# - load an atomic replace livepatch and check that the other three modules were
-# disabled
-# - remove all livepatches besides the atomic replace one and verify that the
-# atomic replace livepatch is still active
-# - remove the atomic replace livepatch and verify that none are active
-
-start_test "atomic replace livepatch"
-
-load_lp $MOD_LIVEPATCH1
-
-grep 'live patched' /proc/cmdline > /dev/kmsg
-grep 'live patched' /proc/meminfo > /dev/kmsg
-
-for mod in $MOD_LIVEPATCH2 $MOD_LIVEPATCH3; do
- load_lp "$mod"
-done
-
-mods=($SYSFS_KLP_DIR/*)
-nmods=${#mods[@]}
-if [ "$nmods" -ne 3 ]; then
- die "Expecting three modules listed, found $nmods"
-fi
-
-load_lp $MOD_REPLACE replace=1
-
-grep 'live patched' /proc/cmdline > /dev/kmsg
-grep 'live patched' /proc/meminfo > /dev/kmsg
-
-loop_until 'mods=($SYSFS_KLP_DIR/*); nmods=${#mods[@]}; [[ "$nmods" -eq 1 ]]' ||
- die "Expecting only one moduled listed, found $nmods"
-
-# These modules were disabled by the atomic replace
-for mod in $MOD_LIVEPATCH3 $MOD_LIVEPATCH2 $MOD_LIVEPATCH1; do
- unload_lp "$mod"
-done
-
-grep 'live patched' /proc/cmdline > /dev/kmsg
-grep 'live patched' /proc/meminfo > /dev/kmsg
-
-disable_lp $MOD_REPLACE
-unload_lp $MOD_REPLACE
-
-grep 'live patched' /proc/cmdline > /dev/kmsg
-grep 'live patched' /proc/meminfo > /dev/kmsg
-
-check_result "% insmod test_modules/$MOD_LIVEPATCH1.ko
-livepatch: enabling patch '$MOD_LIVEPATCH1'
-livepatch: '$MOD_LIVEPATCH1': initializing patching transition
-livepatch: '$MOD_LIVEPATCH1': starting patching transition
-livepatch: '$MOD_LIVEPATCH1': completing patching transition
-livepatch: '$MOD_LIVEPATCH1': patching complete
-$MOD_LIVEPATCH1: this has been live patched
-% insmod test_modules/$MOD_LIVEPATCH2.ko
-livepatch: enabling patch '$MOD_LIVEPATCH2'
-livepatch: '$MOD_LIVEPATCH2': initializing patching transition
-livepatch: '$MOD_LIVEPATCH2': starting patching transition
-livepatch: '$MOD_LIVEPATCH2': completing patching transition
-livepatch: '$MOD_LIVEPATCH2': patching complete
-% insmod test_modules/$MOD_LIVEPATCH3.ko
-livepatch: enabling patch '$MOD_LIVEPATCH3'
-livepatch: '$MOD_LIVEPATCH3': initializing patching transition
-$MOD_LIVEPATCH3: pre_patch_callback: vmlinux
-livepatch: '$MOD_LIVEPATCH3': starting patching transition
-livepatch: '$MOD_LIVEPATCH3': completing patching transition
-$MOD_LIVEPATCH3: post_patch_callback: vmlinux
-livepatch: '$MOD_LIVEPATCH3': patching complete
-% insmod test_modules/$MOD_REPLACE.ko replace=1
-livepatch: enabling patch '$MOD_REPLACE'
-livepatch: '$MOD_REPLACE': initializing patching transition
-livepatch: '$MOD_REPLACE': starting patching transition
-livepatch: '$MOD_REPLACE': completing patching transition
-livepatch: '$MOD_REPLACE': patching complete
-$MOD_REPLACE: this has been live patched
-% rmmod $MOD_LIVEPATCH3
-% rmmod $MOD_LIVEPATCH2
-% rmmod $MOD_LIVEPATCH1
-$MOD_REPLACE: this has been live patched
-% echo 0 > $SYSFS_KLP_DIR/$MOD_REPLACE/enabled
-livepatch: '$MOD_REPLACE': initializing unpatching transition
-livepatch: '$MOD_REPLACE': starting unpatching transition
-livepatch: '$MOD_REPLACE': completing unpatching transition
-livepatch: '$MOD_REPLACE': unpatching complete
-% rmmod $MOD_REPLACE"
-
-
# - load a target module that provides /proc/test_klp_mod_target with
# original output
# - load a livepatch that patches the target module's show function
diff --git a/tools/testing/selftests/livepatch/test-sysfs.sh b/tools/testing/selftests/livepatch/test-sysfs.sh
index 0c31759f34f6..37425ad89f58 100755
--- a/tools/testing/selftests/livepatch/test-sysfs.sh
+++ b/tools/testing/selftests/livepatch/test-sysfs.sh
@@ -20,7 +20,7 @@ check_sysfs_rights "$MOD_LIVEPATCH" "" "drwxr-xr-x"
check_sysfs_rights "$MOD_LIVEPATCH" "enabled" "-rw-r--r--"
check_sysfs_value "$MOD_LIVEPATCH" "enabled" "1"
check_sysfs_rights "$MOD_LIVEPATCH" "force" "--w-------"
-check_sysfs_rights "$MOD_LIVEPATCH" "replace" "-r--r--r--"
+check_sysfs_rights "$MOD_LIVEPATCH" "replace_set" "-r--r--r--"
check_sysfs_rights "$MOD_LIVEPATCH" "transition" "-r--r--r--"
check_sysfs_value "$MOD_LIVEPATCH" "transition" "0"
check_sysfs_rights "$MOD_LIVEPATCH" "vmlinux/patched" "-r--r--r--"
@@ -86,18 +86,18 @@ test_klp_callbacks_demo: post_unpatch_callback: vmlinux
livepatch: 'test_klp_callbacks_demo': unpatching complete
% rmmod test_klp_callbacks_demo"
-start_test "sysfs test replace enabled"
+start_test "sysfs test replace_set 0"
MOD_LIVEPATCH=test_klp_atomic_replace
-load_lp $MOD_LIVEPATCH replace=1
+load_lp $MOD_LIVEPATCH replace_set=0
-check_sysfs_rights "$MOD_LIVEPATCH" "replace" "-r--r--r--"
-check_sysfs_value "$MOD_LIVEPATCH" "replace" "1"
+check_sysfs_rights "$MOD_LIVEPATCH" "replace_set" "-r--r--r--"
+check_sysfs_value "$MOD_LIVEPATCH" "replace_set" "0"
disable_lp $MOD_LIVEPATCH
unload_lp $MOD_LIVEPATCH
-check_result "% insmod test_modules/$MOD_LIVEPATCH.ko replace=1
+check_result "% insmod test_modules/$MOD_LIVEPATCH.ko replace_set=0
livepatch: enabling patch '$MOD_LIVEPATCH'
livepatch: '$MOD_LIVEPATCH': initializing patching transition
livepatch: '$MOD_LIVEPATCH': starting patching transition
@@ -110,17 +110,17 @@ livepatch: '$MOD_LIVEPATCH': completing unpatching transition
livepatch: '$MOD_LIVEPATCH': unpatching complete
% rmmod $MOD_LIVEPATCH"
-start_test "sysfs test replace disabled"
+start_test "sysfs test replace_set 1234"
-load_lp $MOD_LIVEPATCH replace=0
+load_lp $MOD_LIVEPATCH replace_set=1234
-check_sysfs_rights "$MOD_LIVEPATCH" "replace" "-r--r--r--"
-check_sysfs_value "$MOD_LIVEPATCH" "replace" "0"
+check_sysfs_rights "$MOD_LIVEPATCH" "replace_set" "-r--r--r--"
+check_sysfs_value "$MOD_LIVEPATCH" "replace_set" "1234"
disable_lp $MOD_LIVEPATCH
unload_lp $MOD_LIVEPATCH
-check_result "% insmod test_modules/$MOD_LIVEPATCH.ko replace=0
+check_result "% insmod test_modules/$MOD_LIVEPATCH.ko replace_set=1234
livepatch: enabling patch '$MOD_LIVEPATCH'
livepatch: '$MOD_LIVEPATCH': initializing patching transition
livepatch: '$MOD_LIVEPATCH': starting patching transition
diff --git a/tools/testing/selftests/livepatch/test_modules/test_klp_atomic_replace.c b/tools/testing/selftests/livepatch/test_modules/test_klp_atomic_replace.c
index 5af7093ca00c..5333503f193a 100644
--- a/tools/testing/selftests/livepatch/test_modules/test_klp_atomic_replace.c
+++ b/tools/testing/selftests/livepatch/test_modules/test_klp_atomic_replace.c
@@ -7,9 +7,9 @@
#include <linux/kernel.h>
#include <linux/livepatch.h>
-static int replace;
-module_param(replace, int, 0644);
-MODULE_PARM_DESC(replace, "replace (default=0)");
+static int replace_set;
+module_param(replace_set, int, 0644);
+MODULE_PARM_DESC(replace_set, "replace_set (default=0)");
#include <linux/seq_file.h>
static int livepatch_meminfo_proc_show(struct seq_file *m, void *v)
@@ -36,12 +36,12 @@ static struct klp_object objs[] = {
static struct klp_patch patch = {
.mod = THIS_MODULE,
.objs = objs,
- /* set .replace in the init function below for demo purposes */
+ /* set .replace_set in the init function below for demo purposes */
};
static int test_klp_atomic_replace_init(void)
{
- patch.replace = replace;
+ patch.replace_set = replace_set;
return klp_enable_patch(&patch);
}
diff --git a/tools/testing/selftests/livepatch/test_modules/test_klp_callbacks_demo.c b/tools/testing/selftests/livepatch/test_modules/test_klp_callbacks_demo.c
index 3fd8fe1cd1cc..5c3324aa4d75 100644
--- a/tools/testing/selftests/livepatch/test_modules/test_klp_callbacks_demo.c
+++ b/tools/testing/selftests/livepatch/test_modules/test_klp_callbacks_demo.c
@@ -7,6 +7,10 @@
#include <linux/kernel.h>
#include <linux/livepatch.h>
+static int replace_set;
+module_param(replace_set, int, 0644);
+MODULE_PARM_DESC(replace_set, "replace_set (default=0)");
+
static int pre_patch_ret;
module_param(pre_patch_ret, int, 0644);
MODULE_PARM_DESC(pre_patch_ret, "pre_patch_ret (default=0)");
@@ -102,10 +106,12 @@ static struct klp_object objs[] = {
static struct klp_patch patch = {
.mod = THIS_MODULE,
.objs = objs,
+ /* set .replace_set in the init function below for demo purposes */
};
static int test_klp_callbacks_demo_init(void)
{
+ patch.replace_set = replace_set;
return klp_enable_patch(&patch);
}
diff --git a/tools/testing/selftests/livepatch/test_modules/test_klp_callbacks_demo2.c b/tools/testing/selftests/livepatch/test_modules/test_klp_callbacks_demo2.c
index 5417573e80af..31347e2131a7 100644
--- a/tools/testing/selftests/livepatch/test_modules/test_klp_callbacks_demo2.c
+++ b/tools/testing/selftests/livepatch/test_modules/test_klp_callbacks_demo2.c
@@ -7,9 +7,9 @@
#include <linux/kernel.h>
#include <linux/livepatch.h>
-static int replace;
-module_param(replace, int, 0644);
-MODULE_PARM_DESC(replace, "replace (default=0)");
+static int replace_set;
+module_param(replace_set, int, 0644);
+MODULE_PARM_DESC(replace_set, "replace_set (default=0)");
static const char *const module_state[] = {
[MODULE_STATE_LIVE] = "[MODULE_STATE_LIVE] Normal state",
@@ -72,12 +72,12 @@ static struct klp_object objs[] = {
static struct klp_patch patch = {
.mod = THIS_MODULE,
.objs = objs,
- /* set .replace in the init function below for demo purposes */
+ /* set .replace_set in the init function below for demo purposes */
};
static int test_klp_callbacks_demo2_init(void)
{
- patch.replace = replace;
+ patch.replace_set = replace_set;
return klp_enable_patch(&patch);
}
diff --git a/tools/testing/selftests/livepatch/test_modules/test_klp_livepatch.c b/tools/testing/selftests/livepatch/test_modules/test_klp_livepatch.c
index aff08199de71..fedd2494d187 100644
--- a/tools/testing/selftests/livepatch/test_modules/test_klp_livepatch.c
+++ b/tools/testing/selftests/livepatch/test_modules/test_klp_livepatch.c
@@ -15,6 +15,10 @@ static int livepatch_cmdline_proc_show(struct seq_file *m, void *v)
return 0;
}
+static int replace_set;
+module_param(replace_set, int, 0644);
+MODULE_PARM_DESC(replace_set, "replace_set (default=0)");
+
static struct klp_func funcs[] = {
{
.old_name = "cmdline_proc_show",
@@ -32,10 +36,12 @@ static struct klp_object objs[] = {
static struct klp_patch patch = {
.mod = THIS_MODULE,
.objs = objs,
+ /* set .replace_set in the init function below for demo purposes */
};
static int test_klp_livepatch_init(void)
{
+ patch.replace_set = replace_set;
return klp_enable_patch(&patch);
}
diff --git a/tools/testing/selftests/livepatch/test_modules/test_klp_state.c b/tools/testing/selftests/livepatch/test_modules/test_klp_state.c
index 57a4253acb01..8c8829c3ec43 100644
--- a/tools/testing/selftests/livepatch/test_modules/test_klp_state.c
+++ b/tools/testing/selftests/livepatch/test_modules/test_klp_state.c
@@ -142,7 +142,7 @@ static struct klp_patch patch = {
.mod = THIS_MODULE,
.objs = objs,
.states = states,
- .replace = true,
+ .replace_set = 0,
};
static int test_klp_callbacks_demo_init(void)
diff --git a/tools/testing/selftests/livepatch/test_modules/test_klp_state2.c b/tools/testing/selftests/livepatch/test_modules/test_klp_state2.c
index c978ea4d5e67..8a79d7dcce33 100644
--- a/tools/testing/selftests/livepatch/test_modules/test_klp_state2.c
+++ b/tools/testing/selftests/livepatch/test_modules/test_klp_state2.c
@@ -171,7 +171,7 @@ static struct klp_patch patch = {
.mod = THIS_MODULE,
.objs = objs,
.states = states,
- .replace = true,
+ .replace_set = 0,
};
static int test_klp_callbacks_demo_init(void)
--
2.52.0
^ permalink raw reply related [flat|nested] 12+ messages in thread* Re: [PATCH v3 5/7] selftests/livepatch: Update tests for replace_set
2026-06-07 13:16 ` [PATCH v3 5/7] selftests/livepatch: Update tests for replace_set Yafang Shao
@ 2026-06-07 13:29 ` sashiko-bot
0 siblings, 0 replies; 12+ messages in thread
From: sashiko-bot @ 2026-06-07 13:29 UTC (permalink / raw)
To: joe.lawrence, jikos, mbenes, jpoimboe, song, pmladek
Cc: live-patching, Yafang Shao
Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
- [Low] The `replace_set` module parameter is defined as `int` with `0644` permissions, but the `replace_set` field in `struct klp_patch` is an `unsigned int` and cannot be dynamically modified post-initialization.
--
commit 4cfbab500868bad0b10a33bdcbb852806612d546
Author: Yafang Shao <laoar.shao@gmail.com>
selftests/livepatch: Update tests for replace_set
This commit updates the livepatch selftests to replace the replace flag
with replace_set. It adjusts test cases to configure multiple livepatches
that share either the same or different replace_set values, and removes the
test case where an atomic replace disables multiple existing livepatches.
[ ... ]
> diff --git a/tools/testing/selftests/livepatch/test_modules/test_klp_atomic_replace.c b/tools/testing/selftests/livepatch/test_modules/test_klp_atomic_replace.c
> index 5af7093ca00c4..5333503f193a3 100644
> --- a/tools/testing/selftests/livepatch/test_modules/test_klp_atomic_replace.c
> +++ b/tools/testing/selftests/livepatch/test_modules/test_klp_atomic_replace.c
> @@ -7,9 +7,9 @@
> #include <linux/kernel.h>
> #include <linux/livepatch.h>
>
> -static int replace;
> -module_param(replace, int, 0644);
> -MODULE_PARM_DESC(replace, "replace (default=0)");
> +static int replace_set;
> +module_param(replace_set, int, 0644);
[Severity: Low]
The replace_set field in struct klp_patch is an unsigned int, and it cannot
be dynamically modified after initialization.
Should this module parameter be defined as uint with read-only permissions
(0444) instead of int and 0644?
Using int and 0644 allows negative values which could cause improper implicit
casts, and runtime modifications have no actual effect on the livepatch.
[ ... ]
> diff --git a/tools/testing/selftests/livepatch/test_modules/test_klp_livepatch.c b/tools/testing/selftests/livepatch/test_modules/test_klp_livepatch.c
> index aff08199de712..fedd2494d187f 100644
> --- a/tools/testing/selftests/livepatch/test_modules/test_klp_livepatch.c
> +++ b/tools/testing/selftests/livepatch/test_modules/test_klp_livepatch.c
> @@ -15,6 +15,10 @@ static int livepatch_cmdline_proc_show(struct seq_file *m, void *v)
> return 0;
> }
>
> +static int replace_set;
> +module_param(replace_set, int, 0644);
[Severity: Low]
Does this parameter also need to be updated to uint and 0444?
As noted above, the underlying struct klp_patch replace_set field is an
unsigned int and is unaffected by modifications after initialization.
[ ... ]
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260607131659.29281-1-laoar.shao@gmail.com?part=5
^ permalink raw reply [flat|nested] 12+ messages in thread
* [PATCH v3 6/7] selftests/livepatch: Add test for state ID conflict across replace_sets
2026-06-07 13:16 [PATCH v3 0/7] livepatch: Introduce replace set support Yafang Shao
` (4 preceding siblings ...)
2026-06-07 13:16 ` [PATCH v3 5/7] selftests/livepatch: Update tests for replace_set Yafang Shao
@ 2026-06-07 13:16 ` Yafang Shao
2026-06-07 13:16 ` [PATCH v3 7/7] selftests/livepatch: Add test for function " Yafang Shao
6 siblings, 0 replies; 12+ messages in thread
From: Yafang Shao @ 2026-06-07 13:16 UTC (permalink / raw)
To: jpoimboe, jikos, mbenes, pmladek, joe.lawrence, song
Cc: live-patching, Yafang Shao
Livepatches with different replace_sets must not share the same state
ID. If a second livepatch attempts to reuse a state ID already
registered by a livepatch with a different replace_set, the loading
will fail.
Add a test case to verify this behavior. The test result is as follows:
$ ./test-state.sh
TEST: system state modification ... ok
TEST: taking over system state modification ... ok
TEST: compatible cumulative livepatches ... ok
TEST: incompatible cumulative livepatches ... ok
TEST: livepatches state compatibility ... ok <<<< This case
Signed-off-by: Yafang Shao <laoar.shao@gmail.com>
---
.../testing/selftests/livepatch/test-state.sh | 34 ++++
.../selftests/livepatch/test_modules/Makefile | 1 +
.../livepatch/test_modules/test_klp_state.c | 2 +-
.../livepatch/test_modules/test_klp_state2.c | 2 +-
.../livepatch/test_modules/test_klp_state4.c | 163 ++++++++++++++++++
5 files changed, 200 insertions(+), 2 deletions(-)
create mode 100644 tools/testing/selftests/livepatch/test_modules/test_klp_state4.c
diff --git a/tools/testing/selftests/livepatch/test-state.sh b/tools/testing/selftests/livepatch/test-state.sh
index 04b66380f8a0..934a2aa655bb 100755
--- a/tools/testing/selftests/livepatch/test-state.sh
+++ b/tools/testing/selftests/livepatch/test-state.sh
@@ -7,6 +7,7 @@
MOD_LIVEPATCH=test_klp_state
MOD_LIVEPATCH2=test_klp_state2
MOD_LIVEPATCH3=test_klp_state3
+MOD_LIVEPATCH4=test_klp_state4
setup_config
@@ -173,4 +174,37 @@ $MOD_LIVEPATCH2: free_loglevel_state: freeing space for the stored console_logle
livepatch: '$MOD_LIVEPATCH2': unpatching complete
% rmmod $MOD_LIVEPATCH2"
+# livepatches with different replace_set sharing the same state ID
+
+start_test "livepatches state compatibility "
+
+load_lp $MOD_LIVEPATCH
+load_failing_mod $MOD_LIVEPATCH4
+disable_lp $MOD_LIVEPATCH
+unload_lp $MOD_LIVEPATCH
+
+check_result "% insmod test_modules/$MOD_LIVEPATCH.ko
+livepatch: enabling patch '$MOD_LIVEPATCH'
+livepatch: '$MOD_LIVEPATCH': initializing patching transition
+$MOD_LIVEPATCH: pre_patch_callback: vmlinux
+$MOD_LIVEPATCH: allocate_loglevel_state: allocating space to store console_loglevel
+livepatch: '$MOD_LIVEPATCH': starting patching transition
+livepatch: '$MOD_LIVEPATCH': completing patching transition
+$MOD_LIVEPATCH: post_patch_callback: vmlinux
+$MOD_LIVEPATCH: fix_console_loglevel: fixing console_loglevel
+livepatch: '$MOD_LIVEPATCH': patching complete
+% insmod test_modules/$MOD_LIVEPATCH4.ko
+livepatch: Livepatch patch ($MOD_LIVEPATCH4) is not compatible with the already installed livepatches.
+insmod: ERROR: could not insert module test_modules/$MOD_LIVEPATCH4.ko: Invalid parameters
+% echo 0 > $SYSFS_KLP_DIR/$MOD_LIVEPATCH/enabled
+livepatch: '$MOD_LIVEPATCH': initializing unpatching transition
+$MOD_LIVEPATCH: pre_unpatch_callback: vmlinux
+$MOD_LIVEPATCH: restore_console_loglevel: restoring console_loglevel
+livepatch: '$MOD_LIVEPATCH': starting unpatching transition
+livepatch: '$MOD_LIVEPATCH': completing unpatching transition
+$MOD_LIVEPATCH: post_unpatch_callback: vmlinux
+$MOD_LIVEPATCH: free_loglevel_state: freeing space for the stored console_loglevel
+livepatch: '$MOD_LIVEPATCH': unpatching complete
+% rmmod $MOD_LIVEPATCH"
+
exit 0
diff --git a/tools/testing/selftests/livepatch/test_modules/Makefile b/tools/testing/selftests/livepatch/test_modules/Makefile
index a13d398585dc..bdc5ae37311e 100644
--- a/tools/testing/selftests/livepatch/test_modules/Makefile
+++ b/tools/testing/selftests/livepatch/test_modules/Makefile
@@ -14,6 +14,7 @@ obj-m += test_klp_atomic_replace.o \
test_klp_state.o \
test_klp_state2.o \
test_klp_state3.o \
+ test_klp_state4.o \
test_klp_syscall.o
# Ensure that KDIR exists, otherwise skip the compilation
diff --git a/tools/testing/selftests/livepatch/test_modules/test_klp_state.c b/tools/testing/selftests/livepatch/test_modules/test_klp_state.c
index 8c8829c3ec43..6ef3059483de 100644
--- a/tools/testing/selftests/livepatch/test_modules/test_klp_state.c
+++ b/tools/testing/selftests/livepatch/test_modules/test_klp_state.c
@@ -39,7 +39,7 @@ static int allocate_loglevel_state(void)
if (!loglevel_state)
return -EINVAL;
- loglevel_state->data = kzalloc(sizeof(console_loglevel), GFP_KERNEL);
+ loglevel_state->data = kzalloc_obj(console_loglevel, GFP_KERNEL);
if (!loglevel_state->data)
return -ENOMEM;
diff --git a/tools/testing/selftests/livepatch/test_modules/test_klp_state2.c b/tools/testing/selftests/livepatch/test_modules/test_klp_state2.c
index 8a79d7dcce33..908d79bc7540 100644
--- a/tools/testing/selftests/livepatch/test_modules/test_klp_state2.c
+++ b/tools/testing/selftests/livepatch/test_modules/test_klp_state2.c
@@ -46,7 +46,7 @@ static int allocate_loglevel_state(void)
if (!loglevel_state)
return -EINVAL;
- loglevel_state->data = kzalloc(sizeof(console_loglevel), GFP_KERNEL);
+ loglevel_state->data = kzalloc_obj(console_loglevel, GFP_KERNEL);
if (!loglevel_state->data)
return -ENOMEM;
diff --git a/tools/testing/selftests/livepatch/test_modules/test_klp_state4.c b/tools/testing/selftests/livepatch/test_modules/test_klp_state4.c
new file mode 100644
index 000000000000..010d1e13f6ec
--- /dev/null
+++ b/tools/testing/selftests/livepatch/test_modules/test_klp_state4.c
@@ -0,0 +1,163 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * This test is identical to test_klp_state4.c, except that replace_set is
+ * set to 4 instead of 0.
+ */
+
+#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
+
+#include <linux/slab.h>
+#include <linux/module.h>
+#include <linux/kernel.h>
+#include <linux/printk.h>
+#include <linux/livepatch.h>
+
+#define CONSOLE_LOGLEVEL_STATE 1
+#define CONSOLE_LOGLEVEL_STATE_VERSION 4
+
+static const char *const module_state[] = {
+ [MODULE_STATE_LIVE] = "[MODULE_STATE_LIVE] Normal state",
+ [MODULE_STATE_COMING] = "[MODULE_STATE_COMING] Full formed, running module_init",
+ [MODULE_STATE_GOING] = "[MODULE_STATE_GOING] Going away",
+ [MODULE_STATE_UNFORMED] = "[MODULE_STATE_UNFORMED] Still setting it up",
+};
+
+static void callback_info(const char *callback, struct klp_object *obj)
+{
+ if (obj->mod)
+ pr_info("%s: %s -> %s\n", callback, obj->mod->name,
+ module_state[obj->mod->state]);
+ else
+ pr_info("%s: vmlinux\n", callback);
+}
+
+static struct klp_patch patch;
+
+static int allocate_loglevel_state(void)
+{
+ struct klp_state *loglevel_state;
+
+ loglevel_state = klp_get_state(&patch, CONSOLE_LOGLEVEL_STATE);
+ if (!loglevel_state)
+ return -EINVAL;
+
+ loglevel_state->data = kzalloc_obj(console_loglevel, GFP_KERNEL);
+ if (!loglevel_state->data)
+ return -ENOMEM;
+
+ pr_info("%s: allocating space to store console_loglevel\n",
+ __func__);
+ return 0;
+}
+
+static void fix_console_loglevel(void)
+{
+ struct klp_state *loglevel_state;
+
+ loglevel_state = klp_get_state(&patch, CONSOLE_LOGLEVEL_STATE);
+ if (!loglevel_state)
+ return;
+
+ pr_info("%s: fixing console_loglevel\n", __func__);
+ *(int *)loglevel_state->data = console_loglevel;
+ console_loglevel = CONSOLE_LOGLEVEL_MOTORMOUTH;
+}
+
+static void restore_console_loglevel(void)
+{
+ struct klp_state *loglevel_state;
+
+ loglevel_state = klp_get_state(&patch, CONSOLE_LOGLEVEL_STATE);
+ if (!loglevel_state)
+ return;
+
+ pr_info("%s: restoring console_loglevel\n", __func__);
+ console_loglevel = *(int *)loglevel_state->data;
+}
+
+static void free_loglevel_state(void)
+{
+ struct klp_state *loglevel_state;
+
+ loglevel_state = klp_get_state(&patch, CONSOLE_LOGLEVEL_STATE);
+ if (!loglevel_state)
+ return;
+
+ pr_info("%s: freeing space for the stored console_loglevel\n",
+ __func__);
+ kfree(loglevel_state->data);
+}
+
+/* Executed on object patching (ie, patch enablement) */
+static int pre_patch_callback(struct klp_object *obj)
+{
+ callback_info(__func__, obj);
+ return allocate_loglevel_state();
+}
+
+/* Executed on object unpatching (ie, patch disablement) */
+static void post_patch_callback(struct klp_object *obj)
+{
+ callback_info(__func__, obj);
+ fix_console_loglevel();
+}
+
+/* Executed on object unpatching (ie, patch disablement) */
+static void pre_unpatch_callback(struct klp_object *obj)
+{
+ callback_info(__func__, obj);
+ restore_console_loglevel();
+}
+
+/* Executed on object unpatching (ie, patch disablement) */
+static void post_unpatch_callback(struct klp_object *obj)
+{
+ callback_info(__func__, obj);
+ free_loglevel_state();
+}
+
+static struct klp_func no_funcs[] = {
+ {}
+};
+
+static struct klp_object objs[] = {
+ {
+ .name = NULL, /* vmlinux */
+ .funcs = no_funcs,
+ .callbacks = {
+ .pre_patch = pre_patch_callback,
+ .post_patch = post_patch_callback,
+ .pre_unpatch = pre_unpatch_callback,
+ .post_unpatch = post_unpatch_callback,
+ },
+ }, { }
+};
+
+static struct klp_state states[] = {
+ {
+ .id = CONSOLE_LOGLEVEL_STATE,
+ .version = CONSOLE_LOGLEVEL_STATE_VERSION,
+ }, { }
+};
+
+static struct klp_patch patch = {
+ .mod = THIS_MODULE,
+ .objs = objs,
+ .states = states,
+ .replace_set = 4,
+};
+
+static int test_klp_callbacks_demo_init(void)
+{
+ return klp_enable_patch(&patch);
+}
+
+static void test_klp_callbacks_demo_exit(void)
+{
+}
+
+module_init(test_klp_callbacks_demo_init);
+module_exit(test_klp_callbacks_demo_exit);
+MODULE_LICENSE("GPL");
+MODULE_INFO(livepatch, "Y");
+MODULE_DESCRIPTION("Livepatch test: system state compatibility verification");
--
2.52.0
^ permalink raw reply related [flat|nested] 12+ messages in thread* [PATCH v3 7/7] selftests/livepatch: Add test for function conflict across replace_sets
2026-06-07 13:16 [PATCH v3 0/7] livepatch: Introduce replace set support Yafang Shao
` (5 preceding siblings ...)
2026-06-07 13:16 ` [PATCH v3 6/7] selftests/livepatch: Add test for state ID conflict across replace_sets Yafang Shao
@ 2026-06-07 13:16 ` Yafang Shao
6 siblings, 0 replies; 12+ messages in thread
From: Yafang Shao @ 2026-06-07 13:16 UTC (permalink / raw)
To: jpoimboe, jikos, mbenes, pmladek, joe.lawrence, song
Cc: live-patching, Yafang Shao
Livepatches with different replace_sets must not modify the same
function. If a second livepatch attempts to modify a function that
has already been modified by a loaded livepatch with a different
replace_set, the loading will fail. if the second livepatch
shares the same replace_set, it will load successfully.
Add a test case to verify this behavior. The test result is as follows:
$ ./test-livepatch.sh
TEST: basic function patching ... ok
TEST: multiple livepatches ... ok
TEST: module function patching ... ok
TEST: module function patching (livepatch first) ... ok
TEST: function conflict: different replace_sets ... ok <<<<
TEST: function conflict: the same replace_set ... ok <<<<
Signed-off-by: Yafang Shao <laoar.shao@gmail.com>
---
.../selftests/livepatch/test-livepatch.sh | 59 +++++++++++++++++++
.../selftests/livepatch/test_modules/Makefile | 1 +
.../test_modules/test_klp_atomic_replace2.c | 55 +++++++++++++++++
3 files changed, 115 insertions(+)
create mode 100644 tools/testing/selftests/livepatch/test_modules/test_klp_atomic_replace2.c
diff --git a/tools/testing/selftests/livepatch/test-livepatch.sh b/tools/testing/selftests/livepatch/test-livepatch.sh
index 4f8fb5620c6e..9cdfc048d71c 100755
--- a/tools/testing/selftests/livepatch/test-livepatch.sh
+++ b/tools/testing/selftests/livepatch/test-livepatch.sh
@@ -6,6 +6,7 @@
MOD_LIVEPATCH1=test_klp_livepatch
MOD_REPLACE=test_klp_atomic_replace
+MOD_REPLACE2=test_klp_atomic_replace2
MOD_TARGET=test_klp_mod_target
MOD_TARGET_PATCH=test_klp_mod_patch
@@ -203,5 +204,63 @@ livepatch: '$MOD_TARGET_PATCH': unpatching complete
% rmmod $MOD_TARGET
$MOD_TARGET: test_klp_mod_target_exit"
+# - load a livepatch that modifies function A
+# - loading another livepatch with a different replace_set that modifies
+# the same function will fail
+
+start_test "function conflict: different replace_sets"
+
+load_lp $MOD_REPLACE replace_set=0
+load_failing_mod $MOD_REPLACE2 replace_set=1
+disable_lp $MOD_REPLACE
+unload_lp $MOD_REPLACE
+
+check_result "% insmod test_modules/$MOD_REPLACE.ko replace_set=0
+livepatch: enabling patch '$MOD_REPLACE'
+livepatch: '$MOD_REPLACE': initializing patching transition
+livepatch: '$MOD_REPLACE': starting patching transition
+livepatch: '$MOD_REPLACE': completing patching transition
+livepatch: '$MOD_REPLACE': patching complete
+% insmod test_modules/$MOD_REPLACE2.ko replace_set=1
+livepatch: Livepatch patch ($MOD_REPLACE2) is not compatible with the already installed livepatches.
+insmod: ERROR: could not insert module test_modules/$MOD_REPLACE2.ko: Invalid parameters
+% echo 0 > /sys/kernel/livepatch/$MOD_REPLACE/enabled
+livepatch: '$MOD_REPLACE': initializing unpatching transition
+livepatch: '$MOD_REPLACE': starting unpatching transition
+livepatch: '$MOD_REPLACE': completing unpatching transition
+livepatch: '$MOD_REPLACE': unpatching complete
+% rmmod $MOD_REPLACE"
+
+# - load a livepatch that modifies function A
+# - loading another livepatch with the same replace_set that modifies
+# the same function will succeed
+
+start_test "function conflict: the same replace_set"
+
+load_lp $MOD_REPLACE replace_set=0
+load_lp $MOD_REPLACE2 replace_set=0
+disable_lp $MOD_REPLACE2
+unload_lp $MOD_REPLACE2
+unload_lp $MOD_REPLACE
+
+check_result "% insmod test_modules/$MOD_REPLACE.ko replace_set=0
+livepatch: enabling patch '$MOD_REPLACE'
+livepatch: '$MOD_REPLACE': initializing patching transition
+livepatch: '$MOD_REPLACE': starting patching transition
+livepatch: '$MOD_REPLACE': completing patching transition
+livepatch: '$MOD_REPLACE': patching complete
+% insmod test_modules/$MOD_REPLACE2.ko replace_set=0
+livepatch: enabling patch '$MOD_REPLACE2'
+livepatch: '$MOD_REPLACE2': initializing patching transition
+livepatch: '$MOD_REPLACE2': starting patching transition
+livepatch: '$MOD_REPLACE2': completing patching transition
+livepatch: '$MOD_REPLACE2': patching complete
+% echo 0 > /sys/kernel/livepatch/$MOD_REPLACE2/enabled
+livepatch: '$MOD_REPLACE2': initializing unpatching transition
+livepatch: '$MOD_REPLACE2': starting unpatching transition
+livepatch: '$MOD_REPLACE2': completing unpatching transition
+livepatch: '$MOD_REPLACE2': unpatching complete
+% rmmod $MOD_REPLACE2
+% rmmod $MOD_REPLACE"
exit 0
diff --git a/tools/testing/selftests/livepatch/test_modules/Makefile b/tools/testing/selftests/livepatch/test_modules/Makefile
index bdc5ae37311e..f7111d500cab 100644
--- a/tools/testing/selftests/livepatch/test_modules/Makefile
+++ b/tools/testing/selftests/livepatch/test_modules/Makefile
@@ -2,6 +2,7 @@ TESTMODS_DIR := $(realpath $(dir $(abspath $(lastword $(MAKEFILE_LIST)))))
KDIR ?= /lib/modules/$(shell uname -r)/build
obj-m += test_klp_atomic_replace.o \
+ test_klp_atomic_replace2.o \
test_klp_callbacks_busy.o \
test_klp_callbacks_demo.o \
test_klp_callbacks_demo2.o \
diff --git a/tools/testing/selftests/livepatch/test_modules/test_klp_atomic_replace2.c b/tools/testing/selftests/livepatch/test_modules/test_klp_atomic_replace2.c
new file mode 100644
index 000000000000..1dff8b6d45ab
--- /dev/null
+++ b/tools/testing/selftests/livepatch/test_modules/test_klp_atomic_replace2.c
@@ -0,0 +1,55 @@
+// SPDX-License-Identifier: GPL-2.0
+
+#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
+
+#include <linux/module.h>
+#include <linux/kernel.h>
+#include <linux/livepatch.h>
+
+static int replace_set;
+module_param(replace_set, int, 0644);
+MODULE_PARM_DESC(replace_set, "replace_set (default=0)");
+
+#include <linux/seq_file.h>
+static int livepatch_meminfo_proc_show(struct seq_file *m, void *v)
+{
+ seq_printf(m, "%s: %s\n", THIS_MODULE->name,
+ "this has been live patched");
+ return 0;
+}
+
+static struct klp_func funcs[] = {
+ {
+ .old_name = "meminfo_proc_show",
+ .new_func = livepatch_meminfo_proc_show,
+ }, {}
+};
+
+static struct klp_object objs[] = {
+ {
+ /* name being NULL means vmlinux */
+ .funcs = funcs,
+ }, {}
+};
+
+static struct klp_patch patch = {
+ .mod = THIS_MODULE,
+ .objs = objs,
+ /* set .replace_set in the init function below for demo purposes */
+};
+
+static int test_klp_atomic_replace_init(void)
+{
+ patch.replace_set = replace_set;
+ return klp_enable_patch(&patch);
+}
+
+static void test_klp_atomic_replace_exit(void)
+{
+}
+
+module_init(test_klp_atomic_replace_init);
+module_exit(test_klp_atomic_replace_exit);
+MODULE_LICENSE("GPL");
+MODULE_INFO(livepatch, "Y");
+MODULE_DESCRIPTION("Livepatch test: modified function compatibility verification");
--
2.52.0
^ permalink raw reply related [flat|nested] 12+ messages in thread