* [PATCH v7 for-next 1/8] livepatch: Make klp_find_func() non static
2026-08-25 11:46 [PATCH v7 for-next 0/8] livepatch: Introduce replace set support Yafang Shao
@ 2026-08-25 11:46 ` Yafang Shao
2026-08-25 11:46 ` [PATCH v7 for-next 2/8] livepatch: Call klp_init_patch_early() earlier Yafang Shao
` (6 subsequent siblings)
7 siblings, 0 replies; 23+ messages in thread
From: Yafang Shao @ 2026-08-25 11:46 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>
---
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 a240d1144e89..c34306ecfb0b 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] 23+ messages in thread* [PATCH v7 for-next 2/8] livepatch: Call klp_init_patch_early() earlier
2026-08-25 11:46 [PATCH v7 for-next 0/8] livepatch: Introduce replace set support Yafang Shao
2026-08-25 11:46 ` [PATCH v7 for-next 1/8] livepatch: Make klp_find_func() non static Yafang Shao
@ 2026-08-25 11:46 ` Yafang Shao
2026-08-25 12:06 ` sashiko-bot
2026-08-27 23:57 ` Josh Poimboeuf
2026-08-25 11:46 ` [PATCH v7 for-next 3/8] livepatch: Implement replace set for scoped atomic replace Yafang Shao
` (5 subsequent siblings)
7 siblings, 2 replies; 23+ messages in thread
From: Yafang Shao @ 2026-08-25 11:46 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>
---
kernel/livepatch/core.c | 15 ++++++++-------
1 file changed, 8 insertions(+), 7 deletions(-)
diff --git a/kernel/livepatch/core.c b/kernel/livepatch/core.c
index c34306ecfb0b..3de3940d34b2 100644
--- a/kernel/livepatch/core.c
+++ b/kernel/livepatch/core.c
@@ -1145,13 +1145,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;
@@ -1159,6 +1152,14 @@ 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] 23+ messages in thread* Re: [PATCH v7 for-next 2/8] livepatch: Call klp_init_patch_early() earlier
2026-08-25 11:46 ` [PATCH v7 for-next 2/8] livepatch: Call klp_init_patch_early() earlier Yafang Shao
@ 2026-08-25 12:06 ` sashiko-bot
2026-08-25 12:11 ` Yafang Shao
2026-08-27 23:57 ` Josh Poimboeuf
1 sibling, 1 reply; 23+ messages in thread
From: sashiko-bot @ 2026-08-25 12:06 UTC (permalink / raw)
To: pmladek, jpoimboe, song, jikos, mbenes, joe.lawrence, Yafang Shao
Cc: live-patching
Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
Pre-existing issues:
- [High] Potential use-after-free of kobject delayed work structs during error cleanup if CONFIG_DEBUG_KOBJECT_RELEASE is enabled.
--
--- Patch [2]: [PATCH v7 for-next 2/8] livepatch: Call klp_init_patch_early() earlier ---
commit b5f9135d749ffc4d6ea6c6a68a0fc6489e7a1cbf
Author: Yafang Shao <laoar.shao@gmail.com>
livepatch: Call klp_init_patch_early() earlier
Invoke klp_init_patch_early() during early initialization to avoid mixing
for_each_() and for_each_*_static() variants later in the code.
> diff --git a/kernel/livepatch/core.c b/kernel/livepatch/core.c
> --- a/kernel/livepatch/core.c
> +++ b/kernel/livepatch/core.c
[ ... ]
> @@ -1152,6 +1152,14 @@ 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;
> + }
[Severity: High]
This is a pre-existing issue, but does this error path expose a use-after-free
of kobject delayed work structs when CONFIG_DEBUG_KOBJECT_RELEASE is enabled?
Because kobject_add() has not yet run (it runs later in klp_init_patch()), the
child kobjects (obj and func) do not have their parent pointers set to
patch->kobj during this cleanup path.
When the error path jumps to err and calls klp_free_patch_start(), it drops
references to the child kobjects. Since their parent pointers aren't set,
putting the child kobjects does not elevate or pin the parent patch->kobj.
Consequently, the wait_for_completion() in klp_free_patch_finish() only waits
for the patch kobject to finish releasing, but not the child kobjects:
kernel/livepatch/core.c:klp_free_patch_finish() {
...
kobject_put(&patch->kobj);
wait_for_completion(&patch->finish);
/* Put the module after the last access to struct klp_patch. */
if (!patch->forced)
module_put(patch->mod);
}
This allows the module memory to be completely freed while the delayed work
for the child obj and func kobjects (which reside in the now-freed module
memory) might still be queued. When the workqueue executes them, it results in
a use-after-free.
While this issue already existed for klp_init_patch() failures, moving
klp_is_patch_compatible() after klp_init_patch_early() makes it trivially
reachable by simply providing an incompatible livepatch.
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260825114641.80452-1-laoar.shao@gmail.com?part=2
^ permalink raw reply [flat|nested] 23+ messages in thread* Re: [PATCH v7 for-next 2/8] livepatch: Call klp_init_patch_early() earlier
2026-08-25 12:06 ` sashiko-bot
@ 2026-08-25 12:11 ` Yafang Shao
0 siblings, 0 replies; 23+ messages in thread
From: Yafang Shao @ 2026-08-25 12:11 UTC (permalink / raw)
To: sashiko-reviews
Cc: pmladek, jpoimboe, song, jikos, mbenes, joe.lawrence,
live-patching
On Tue, Aug 25, 2026 at 8:06 PM <sashiko-bot@kernel.org> wrote:
>
> Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
>
> Pre-existing issues:
> - [High] Potential use-after-free of kobject delayed work structs during error cleanup if CONFIG_DEBUG_KOBJECT_RELEASE is enabled.
Seems you didn't read the "Notes for sashiko-bot" carefully ;)
--
Regards
Yafang
^ permalink raw reply [flat|nested] 23+ messages in thread
* Re: [PATCH v7 for-next 2/8] livepatch: Call klp_init_patch_early() earlier
2026-08-25 11:46 ` [PATCH v7 for-next 2/8] livepatch: Call klp_init_patch_early() earlier Yafang Shao
2026-08-25 12:06 ` sashiko-bot
@ 2026-08-27 23:57 ` Josh Poimboeuf
2026-08-28 2:24 ` Yafang Shao
1 sibling, 1 reply; 23+ messages in thread
From: Josh Poimboeuf @ 2026-08-27 23:57 UTC (permalink / raw)
To: Yafang Shao; +Cc: jikos, mbenes, pmladek, joe.lawrence, song, live-patching
On Tue, Aug 25, 2026 at 07:46:35PM +0800, 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>
> ---
> kernel/livepatch/core.c | 15 ++++++++-------
> 1 file changed, 8 insertions(+), 7 deletions(-)
>
> diff --git a/kernel/livepatch/core.c b/kernel/livepatch/core.c
> index c34306ecfb0b..3de3940d34b2 100644
> --- a/kernel/livepatch/core.c
> +++ b/kernel/livepatch/core.c
> @@ -1145,13 +1145,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;
> @@ -1159,6 +1152,14 @@ 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;
Stray newline.
--
Josh
^ permalink raw reply [flat|nested] 23+ messages in thread* Re: [PATCH v7 for-next 2/8] livepatch: Call klp_init_patch_early() earlier
2026-08-27 23:57 ` Josh Poimboeuf
@ 2026-08-28 2:24 ` Yafang Shao
0 siblings, 0 replies; 23+ messages in thread
From: Yafang Shao @ 2026-08-28 2:24 UTC (permalink / raw)
To: Josh Poimboeuf; +Cc: jikos, mbenes, pmladek, joe.lawrence, song, live-patching
On Fri, Aug 28, 2026 at 7:57 AM Josh Poimboeuf <jpoimboe@kernel.org> wrote:
>
> On Tue, Aug 25, 2026 at 07:46:35PM +0800, 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>
> > ---
> > kernel/livepatch/core.c | 15 ++++++++-------
> > 1 file changed, 8 insertions(+), 7 deletions(-)
> >
> > diff --git a/kernel/livepatch/core.c b/kernel/livepatch/core.c
> > index c34306ecfb0b..3de3940d34b2 100644
> > --- a/kernel/livepatch/core.c
> > +++ b/kernel/livepatch/core.c
> > @@ -1145,13 +1145,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;
> > @@ -1159,6 +1152,14 @@ 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;
>
> Stray newline.
I will update it.
--
Regards
Yafang
^ permalink raw reply [flat|nested] 23+ messages in thread
* [PATCH v7 for-next 3/8] livepatch: Implement replace set for scoped atomic replace
2026-08-25 11:46 [PATCH v7 for-next 0/8] livepatch: Introduce replace set support Yafang Shao
2026-08-25 11:46 ` [PATCH v7 for-next 1/8] livepatch: Make klp_find_func() non static Yafang Shao
2026-08-25 11:46 ` [PATCH v7 for-next 2/8] livepatch: Call klp_init_patch_early() earlier Yafang Shao
@ 2026-08-25 11:46 ` Yafang Shao
2026-08-25 11:59 ` sashiko-bot
2026-08-28 0:26 ` Josh Poimboeuf
2026-08-25 11:46 ` [PATCH v7 for-next 4/8] livepatch: Deprecate stack_order Yafang Shao
` (4 subsequent siblings)
7 siblings, 2 replies; 23+ messages in thread
From: Yafang Shao @ 2026-08-25 11:46 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 patch replace set.
By default (provides=0), any livepatch replaces any other livepatch.
- 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 replace sets.
A new livepatch atomically replaces any existing livepatch whose
provides id matches either:
1. The new patch provides id (same replace set), or
2. Any id in the new patch obsoletes list
The klp-build script is updated with -p/--provides and -r/--obsoletes
options. The obsoletes list automatically includes the provides id
and deduplicates entries. Input validation rejects malformed values
at build time.
Two helper functions are introduced:
- klp_patch_replaceable(): checks if an new patch will replace the old
patch
- klp_has_function_conflict(): rejects loading a livepatch that would
modify a function already patched by a livepatch with a different
provides id.
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/testing/sysfs-kernel-livepatch | 22 ++++-
.../livepatch/cumulative-patches.rst | 93 +++++++++++++------
Documentation/livepatch/livepatch.rst | 23 +++--
include/linux/livepatch.h | 7 +-
kernel/livepatch/core.c | 69 ++++++++++++--
kernel/livepatch/core.h | 1 +
kernel/livepatch/state.c | 57 ++++++++++--
kernel/livepatch/transition.c | 11 ++-
scripts/livepatch/init.c | 71 +++++++++++++-
scripts/livepatch/klp-build | 78 ++++++++++++++--
10 files changed, 351 insertions(+), 81 deletions(-)
diff --git a/Documentation/ABI/testing/sysfs-kernel-livepatch b/Documentation/ABI/testing/sysfs-kernel-livepatch
index 3c3f36b32b57..2588f676deb1 100644
--- a/Documentation/ABI/testing/sysfs-kernel-livepatch
+++ b/Documentation/ABI/testing/sysfs-kernel-livepatch
@@ -47,13 +47,25 @@ 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.
+ Only one active livepatch per provides id is allowed.
+
+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.
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..04352ae3f0d0 100644
--- a/Documentation/livepatch/cumulative-patches.rst
+++ b/Documentation/livepatch/cumulative-patches.rst
@@ -2,33 +2,66 @@
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 is the newly introduced ``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``. In this mode, 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's replace set. 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. By default, it includes the patch's own ``provides`` id, ensuring
+that a new patch always replaces any existing patch with the same
+``provides`` id.
+
+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 +97,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..73635f9ddd91 100644
--- a/Documentation/livepatch/livepatch.rst
+++ b/Documentation/livepatch/livepatch.rst
@@ -347,15 +347,20 @@ 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.
+
+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 ``provides`` id 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..854ed6230b96 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
+ * @provides: only one active livepatch per id
+ * @obsoletes: replace given livepatch id(s)
* @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,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/core.c b/kernel/livepatch/core.c
index 3de3940d34b2..81e9d7aa1de7 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, "%u%s", patch->obsoletes[i],
+ i < patch->nr_obsoletes - 1 ? "," : "");
+
+ 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,28 @@ 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
+ */
+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)
+ return false;
+
+ for (i = 0; i < new_patch->nr_obsoletes; i++) {
+ if (new_patch->obsoletes[i] == old_patch->provides)
+ return true;
+ }
+ return false;
+}
+
static struct klp_object *klp_alloc_object_dynamic(const char *name,
struct klp_patch *patch)
{
@@ -618,6 +662,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;
@@ -793,6 +840,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);
}
}
@@ -985,11 +1034,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);
@@ -1205,6 +1252,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..d76697309df5 100644
--- a/kernel/livepatch/state.c
+++ b/kernel/livepatch/state.c
@@ -85,24 +85,62 @@ 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.
*/
+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 +148,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..4405ff5f52f6 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 &&
+ klp_patch_replaces(klp_transition_patch, patch))
patch->forced = true;
}
}
diff --git a/scripts/livepatch/init.c b/scripts/livepatch/init.c
index f14d8c8fb35f..044263191652 100644
--- a/scripts/livepatch/init.c
+++ b/scripts/livepatch/init.c
@@ -8,6 +8,7 @@
#include <linux/kernel.h>
#include <linux/slab.h>
#include <linux/livepatch.h>
+#include <linux/string.h>
static struct klp_patch *patch;
@@ -50,8 +51,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 +71,76 @@ static int __init livepatch_mod_init(void)
/* TODO patch->states */
-#ifdef KLP_NO_REPLACE
- patch->replace = false;
+#ifdef KLP_PROVIDES
+ patch->provides = KLP_PROVIDES;
#else
- patch->replace = true;
+ patch->provides = 0;
+#endif
+
+#ifdef KLP_OBSOLETES
+ /*
+ * Parse KLP_OBSOLETES string (format: "1,2,3") and convert to
+ * unsigned int array for patch->obsoletes.
+ *
+ * Note: The provides ID is not included here; the kernel will
+ * replace livepatch with the same provides ID.
+ */
+ {
+ unsigned int *obs_array;
+ unsigned int count = 1;
+ char *obsoletes_str;
+ char *token, *str;
+ int i = 0;
+
+ for (str = (char *)KLP_OBSOLETES; *str; str++) {
+ if (*str == ',')
+ count++;
+ }
+
+ obsoletes_str = kstrdup(KLP_OBSOLETES, GFP_KERNEL);
+ if (!obsoletes_str) {
+ ret = -ENOMEM;
+ goto err_free_objs;
+ }
+
+ obs_array = kmalloc_array(count, sizeof(unsigned int), GFP_KERNEL);
+ if (!obs_array) {
+ kfree(obsoletes_str);
+ ret = -ENOMEM;
+ goto err_free_objs;
+ }
+
+ str = obsoletes_str;
+ while ((token = strsep(&str, ",")) != NULL) {
+ unsigned int val;
+
+ ret = kstrtouint(token, 10, &val);
+ if (ret) {
+ kfree(obsoletes_str);
+ kfree(obs_array);
+ goto err_free_objs;
+ }
+ obs_array[i++] = val;
+ }
+
+ patch->obsoletes = obs_array;
+ patch->nr_obsoletes = i;
+
+ if (i > 0)
+ pr_info("obsoletes patch ids: %s\n", KLP_OBSOLETES);
+
+ kfree(obsoletes_str);
+ }
+#else
+ patch->obsoletes = NULL;
+ patch->nr_obsoletes = 0;
#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);
@@ -96,6 +156,7 @@ static void __exit livepatch_mod_exit(void)
kfree(obj->funcs);
kfree(patch->objs);
+ kfree(patch->obsoletes);
kfree(patch);
}
diff --git a/scripts/livepatch/klp-build b/scripts/livepatch/klp-build
index c4a7acf8edc3..19b0997c35b8 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:
@@ -147,7 +149,6 @@ Advanced Options:
EOF
}
-
usage() {
__usage >&2
}
@@ -159,8 +160,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 +190,27 @@ process_args() {
NAME="$(module_name_string "$NAME")"
shift 2
;;
- --no-replace)
- REPLACE=0
- shift
+ -p | --provides)
+ PROVIDES="$2"
+ [[ ! "$PROVIDES" =~ ^[0-9]+$ ]] && \
+ die "provides contains invalid value '$PROVIDES': only non-negative integers allowed"
+ shift 2
+ ;;
+ -r | --obsoletes)
+ OBSOLETES="$2"
+ local obs_val="$2"
+ local obs_check="${obs_val//[ ]/}"
+ [[ -z "$obs_check" ]] && shift 2 && continue
+ [[ "$obs_check" == ,* || "$obs_check" == *, || "$obs_check" == *,,* ]] && \
+ die "obsoletes has invalid comma usage: '$obs_val'"
+ local IFS=','
+ local obs_elem
+ for obs_elem in $obs_check; do
+ [[ ! "$obs_elem" =~ ^[0-9]+$ ]] && \
+ die "obsoletes contains invalid value '$obs_elem': only non-negative integers allowed"
+ done
+ unset IFS
+ shift 2
;;
-v | --verbose)
VERBOSE=1
@@ -235,6 +254,37 @@ process_args() {
exit 1
fi
+ # Remove duplicates from obsoletes (if specified)
+ # Note: provides ID is not added here; the kernel will replace
+ # livepatch with the same provides ID.
+ if [[ -n "$OBSOLETES" ]]; then
+ local obsoletes_clean="${OBSOLETES//[ ]/}"
+ local IFS=','
+ local -a obs_array=()
+ local obs_id
+ local already_exists
+
+ for obs_id in $obsoletes_clean; do
+ if [[ -n "$obs_id" ]]; then
+ already_exists=0
+ for existing in "${obs_array[@]}"; do
+ if [[ "$existing" -eq "$obs_id" ]]; then
+ already_exists=1
+ break
+ fi
+ done
+
+ if [[ "$already_exists" -eq 0 ]]; then
+ obs_array+=("$obs_id")
+ fi
+ fi
+ done
+
+ local IFS=','
+ OBSOLETES="${obs_array[*]}"
+ unset IFS
+ fi
+
KEEP_TMP="$keep_tmp"
PATCHES=("$@")
@@ -847,7 +897,17 @@ build_patch_module() {
cflags=("-ffunction-sections")
cflags+=("-fdata-sections")
- [[ $REPLACE -eq 0 ]] && cflags+=("-DKLP_NO_REPLACE")
+ cflags+=("-DKLP_PROVIDES=$PROVIDES")
+
+ # Process OBSOLETES: remove spaces, convert to comma-separated string
+ # Input: "0, 1, 2" or "" -> Output: "0,1,2" or empty
+ if [[ -n "$OBSOLETES" ]]; then
+ # Remove spaces, keep commas
+ local obsoletes_clean="${OBSOLETES//[ ]/}"
+ # Escape quotes properly for C string macro
+ cflags+=("-DKLP_OBSOLETES=\\\"$obsoletes_clean\\\"")
+ [[ -v VERBOSE ]] && echo " KLP_OBSOLETES=$obsoletes_clean (from OBSOLETES=$OBSOLETES, PROVIDES=$PROVIDES)" >&2
+ fi
cmd=("make")
if [[ -v VERBOSE ]]; then
--
2.52.0
^ permalink raw reply related [flat|nested] 23+ messages in thread* Re: [PATCH v7 for-next 3/8] livepatch: Implement replace set for scoped atomic replace
2026-08-25 11:46 ` [PATCH v7 for-next 3/8] livepatch: Implement replace set for scoped atomic replace Yafang Shao
@ 2026-08-25 11:59 ` sashiko-bot
2026-08-25 12:10 ` Yafang Shao
2026-08-28 0:26 ` Josh Poimboeuf
1 sibling, 1 reply; 23+ messages in thread
From: sashiko-bot @ 2026-08-25 11:59 UTC (permalink / raw)
To: Yafang Shao, mbenes, jpoimboe, joe.lawrence, pmladek, song, jikos
Cc: live-patching
Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
- [Medium] Contradiction between documentation/commit message and actual code regarding the 'obsoletes' list.
--
--- Patch [3]: [PATCH v7 for-next 3/8] livepatch: Implement replace set for scoped atomic replace ---
commit 32e1bcc9ed17f1580951816746e0cfa7cadbca0b
Author: Yafang Shao <laoar.shao@gmail.com>
livepatch: Implement replace set for scoped atomic replace
This commit replaces the boolean replace flag with a more flexible model
using provides and obsoletes fields to allow multiple independent livepatch
sets to coexist on the same system.
> The klp-build script is updated with -p/--provides and -r/--obsoletes
> options. The obsoletes list automatically includes the provides id
> and deduplicates entries. Input validation rejects malformed values
> at build time.
[Severity: Medium]
Is this description accurate? The code and inline comments in the build
script and init.c explicitly state that the provides ID is not included in
the obsoletes list, and the kernel instead relies on explicitly checking the
provides ID directly.
> diff --git a/Documentation/livepatch/cumulative-patches.rst b/Documentation/livepatch/cumulative-patches.rst
> index 1931f318976a5..04352ae3f0d06 100644
> --- a/Documentation/livepatch/cumulative-patches.rst
> +++ b/Documentation/livepatch/cumulative-patches.rst
[ ... ]
> +Provides and Obsoletes
> +-----------------------
> +
> +The ``provides`` field in ``struct klp_patch`` is an unsigned integer that
> +identifies the livepatch's replace set. 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. By default, it includes the patch's own ``provides`` id, ensuring
> +that a new patch always replaces any existing patch with the same
> +``provides`` id.
[Severity: Medium]
This isn't a code bug, but does this documentation match the implementation?
The text here states that the obsoletes field automatically includes the
patch's own provides id by default.
> diff --git a/scripts/livepatch/klp-build b/scripts/livepatch/klp-build
> index b52a8489d9f67..64bf3fce7fbc1 100755
> --- a/scripts/livepatch/klp-build
> +++ b/scripts/livepatch/klp-build
[ ... ]
> @@ -235,6 +254,37 @@ process_args() {
> exit 1
> fi
>
> + # Remove duplicates from obsoletes (if specified)
> + # Note: provides ID is not added here; the kernel will replace
> + # livepatch with the same provides ID.
[Severity: Medium]
Here the build script explicitly notes that the provides ID is not added to
the obsoletes list. Could the commit message and the documentation in
cumulative-patches.rst be updated to match this behavior to avoid confusion?
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260825114641.80452-1-laoar.shao@gmail.com?part=3
^ permalink raw reply [flat|nested] 23+ messages in thread* Re: [PATCH v7 for-next 3/8] livepatch: Implement replace set for scoped atomic replace
2026-08-25 11:59 ` sashiko-bot
@ 2026-08-25 12:10 ` Yafang Shao
0 siblings, 0 replies; 23+ messages in thread
From: Yafang Shao @ 2026-08-25 12:10 UTC (permalink / raw)
To: sashiko-reviews
Cc: mbenes, jpoimboe, joe.lawrence, pmladek, song, jikos,
live-patching
On Tue, Aug 25, 2026 at 7:59 PM <sashiko-bot@kernel.org> wrote:
>
> Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
> - [Medium] Contradiction between documentation/commit message and actual code regarding the 'obsoletes' list.
Ah, the provides ID is not included in the obsoletes list.
I will update it.
--
Regards
Yafang
^ permalink raw reply [flat|nested] 23+ messages in thread
* Re: [PATCH v7 for-next 3/8] livepatch: Implement replace set for scoped atomic replace
2026-08-25 11:46 ` [PATCH v7 for-next 3/8] livepatch: Implement replace set for scoped atomic replace Yafang Shao
2026-08-25 11:59 ` sashiko-bot
@ 2026-08-28 0:26 ` Josh Poimboeuf
2026-08-28 3:03 ` Yafang Shao
1 sibling, 1 reply; 23+ messages in thread
From: Josh Poimboeuf @ 2026-08-28 0:26 UTC (permalink / raw)
To: Yafang Shao; +Cc: jikos, mbenes, pmladek, joe.lawrence, song, live-patching
On Tue, Aug 25, 2026 at 07:46:36PM +0800, 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 patch replace set.
> By default (provides=0), any livepatch replaces any other livepatch.
>
> - 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 replace sets.
>
> A new livepatch atomically replaces any existing livepatch whose
> provides id matches either:
> 1. The new patch provides id (same replace set), or
> 2. Any id in the new patch obsoletes list
>
> The klp-build script is updated with -p/--provides and -r/--obsoletes
> options. The obsoletes list automatically includes the provides id
> and deduplicates entries. Input validation rejects malformed values
> at build time.
>
> Two helper functions are introduced:
> - klp_patch_replaceable(): checks if an new patch will replace the old
> patch
Should be klp_patch_replaces()?
> - klp_has_function_conflict(): rejects loading a livepatch that would
> modify a function already patched by a livepatch with a different
> provides id.
I'm not sure the commit log really needs to list some functions it adds,
that's already evident in the patch.
> diff --git a/include/linux/livepatch.h b/include/linux/livepatch.h
> index ba9e3988c07c..854ed6230b96 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
> + * @provides: only one active livepatch per id
> + * @obsoletes: replace given livepatch id(s)
> * @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,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;
nr_obsoletes is missing from the kernel-doc above.
> @@ -72,15 +71,76 @@ static int __init livepatch_mod_init(void)
>
> /* TODO patch->states */
>
> -#ifdef KLP_NO_REPLACE
> - patch->replace = false;
> +#ifdef KLP_PROVIDES
> + patch->provides = KLP_PROVIDES;
> #else
> - patch->replace = true;
> + patch->provides = 0;
> +#endif
'patch' is already zeroed, so the #else isn't needed.
> +#ifdef KLP_OBSOLETES
> + /*
> + * Parse KLP_OBSOLETES string (format: "1,2,3") and convert to
> + * unsigned int array for patch->obsoletes.
> + *
> + * Note: The provides ID is not included here; the kernel will
> + * replace livepatch with the same provides ID.
> + */
> + {
> + unsigned int *obs_array;
> + unsigned int count = 1;
> + char *obsoletes_str;
> + char *token, *str;
> + int i = 0;
> +
> + for (str = (char *)KLP_OBSOLETES; *str; str++) {
> + if (*str == ',')
> + count++;
> + }
> +
> + obsoletes_str = kstrdup(KLP_OBSOLETES, GFP_KERNEL);
> + if (!obsoletes_str) {
> + ret = -ENOMEM;
> + goto err_free_objs;
> + }
> +
> + obs_array = kmalloc_array(count, sizeof(unsigned int), GFP_KERNEL);
> + if (!obs_array) {
> + kfree(obsoletes_str);
> + ret = -ENOMEM;
> + goto err_free_objs;
> + }
> +
> + str = obsoletes_str;
> + while ((token = strsep(&str, ",")) != NULL) {
> + unsigned int val;
> +
> + ret = kstrtouint(token, 10, &val);
> + if (ret) {
> + kfree(obsoletes_str);
> + kfree(obs_array);
> + goto err_free_objs;
> + }
> + obs_array[i++] = val;
> + }
> +
> + patch->obsoletes = obs_array;
> + patch->nr_obsoletes = i;
> +
> + if (i > 0)
> + pr_info("obsoletes patch ids: %s\n", KLP_OBSOLETES);
> +
> + kfree(obsoletes_str);
> + }
Can we do something simpler like this?
#ifdef KLP_OBSOLETES
static unsigned int klp_obsoletes[] = { KLP_OBSOLETES };
#endif
And in livepatch_mod_init():
#ifdef KLP_PROVIDES
patch->provides = KLP_PROVIDES;
#endif
#ifdef KLP_OBSOLETES
patch->obsoletes = klp_obsoletes;
patch->nr_obsoletes = ARRAY_SIZE(klp_obsoletes);
#endif
> +++ 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:
> @@ -147,7 +149,6 @@ Advanced Options:
>
> EOF
> }
> -
> usage() {
Unnecessary whitespace change
> @@ -235,6 +254,37 @@ process_args() {
> exit 1
> fi
>
> + # Remove duplicates from obsoletes (if specified)
> + # Note: provides ID is not added here; the kernel will replace
> + # livepatch with the same provides ID.
> + if [[ -n "$OBSOLETES" ]]; then
> + local obsoletes_clean="${OBSOLETES//[ ]/}"
> + local IFS=','
> + local -a obs_array=()
> + local obs_id
> + local already_exists
> +
> + for obs_id in $obsoletes_clean; do
> + if [[ -n "$obs_id" ]]; then
> + already_exists=0
> + for existing in "${obs_array[@]}"; do
> + if [[ "$existing" -eq "$obs_id" ]]; then
> + already_exists=1
> + break
> + fi
> + done
> +
> + if [[ "$already_exists" -eq 0 ]]; then
> + obs_array+=("$obs_id")
> + fi
> + fi
> + done
> +
> + local IFS=','
> + OBSOLETES="${obs_array[*]}"
> + unset IFS
> + fi
> +
What's the point of this? Do we expect duplicates, and if so, wouldn't
it be the kernel's job to handle that?
--
Josh
^ permalink raw reply [flat|nested] 23+ messages in thread* Re: [PATCH v7 for-next 3/8] livepatch: Implement replace set for scoped atomic replace
2026-08-28 0:26 ` Josh Poimboeuf
@ 2026-08-28 3:03 ` Yafang Shao
2026-08-28 3:39 ` Josh Poimboeuf
0 siblings, 1 reply; 23+ messages in thread
From: Yafang Shao @ 2026-08-28 3:03 UTC (permalink / raw)
To: Josh Poimboeuf; +Cc: jikos, mbenes, pmladek, joe.lawrence, song, live-patching
On Fri, Aug 28, 2026 at 8:26 AM Josh Poimboeuf <jpoimboe@kernel.org> wrote:
>
> On Tue, Aug 25, 2026 at 07:46:36PM +0800, 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 patch replace set.
> > By default (provides=0), any livepatch replaces any other livepatch.
> >
> > - 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 replace sets.
> >
> > A new livepatch atomically replaces any existing livepatch whose
> > provides id matches either:
> > 1. The new patch provides id (same replace set), or
> > 2. Any id in the new patch obsoletes list
> >
> > The klp-build script is updated with -p/--provides and -r/--obsoletes
> > options. The obsoletes list automatically includes the provides id
> > and deduplicates entries. Input validation rejects malformed values
> > at build time.
> >
> > Two helper functions are introduced:
> > - klp_patch_replaceable(): checks if an new patch will replace the old
> > patch
>
> Should be klp_patch_replaces()?
right.
>
> > - klp_has_function_conflict(): rejects loading a livepatch that would
> > modify a function already patched by a livepatch with a different
> > provides id.
>
> I'm not sure the commit log really needs to list some functions it adds,
> that's already evident in the patch.
I will remove them from the commit log.
>
> > diff --git a/include/linux/livepatch.h b/include/linux/livepatch.h
> > index ba9e3988c07c..854ed6230b96 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
> > + * @provides: only one active livepatch per id
> > + * @obsoletes: replace given livepatch id(s)
> > * @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,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;
>
> nr_obsoletes is missing from the kernel-doc above.
will add it
>
> > @@ -72,15 +71,76 @@ static int __init livepatch_mod_init(void)
> >
> > /* TODO patch->states */
> >
> > -#ifdef KLP_NO_REPLACE
> > - patch->replace = false;
> > +#ifdef KLP_PROVIDES
> > + patch->provides = KLP_PROVIDES;
> > #else
> > - patch->replace = true;
> > + patch->provides = 0;
> > +#endif
>
> 'patch' is already zeroed, so the #else isn't needed.
will remove it.
>
> > +#ifdef KLP_OBSOLETES
> > + /*
> > + * Parse KLP_OBSOLETES string (format: "1,2,3") and convert to
> > + * unsigned int array for patch->obsoletes.
> > + *
> > + * Note: The provides ID is not included here; the kernel will
> > + * replace livepatch with the same provides ID.
> > + */
> > + {
> > + unsigned int *obs_array;
> > + unsigned int count = 1;
> > + char *obsoletes_str;
> > + char *token, *str;
> > + int i = 0;
> > +
> > + for (str = (char *)KLP_OBSOLETES; *str; str++) {
> > + if (*str == ',')
> > + count++;
> > + }
> > +
> > + obsoletes_str = kstrdup(KLP_OBSOLETES, GFP_KERNEL);
> > + if (!obsoletes_str) {
> > + ret = -ENOMEM;
> > + goto err_free_objs;
> > + }
> > +
> > + obs_array = kmalloc_array(count, sizeof(unsigned int), GFP_KERNEL);
> > + if (!obs_array) {
> > + kfree(obsoletes_str);
> > + ret = -ENOMEM;
> > + goto err_free_objs;
> > + }
> > +
> > + str = obsoletes_str;
> > + while ((token = strsep(&str, ",")) != NULL) {
> > + unsigned int val;
> > +
> > + ret = kstrtouint(token, 10, &val);
> > + if (ret) {
> > + kfree(obsoletes_str);
> > + kfree(obs_array);
> > + goto err_free_objs;
> > + }
> > + obs_array[i++] = val;
> > + }
> > +
> > + patch->obsoletes = obs_array;
> > + patch->nr_obsoletes = i;
> > +
> > + if (i > 0)
> > + pr_info("obsoletes patch ids: %s\n", KLP_OBSOLETES);
> > +
> > + kfree(obsoletes_str);
> > + }
>
> Can we do something simpler like this?
>
> #ifdef KLP_OBSOLETES
> static unsigned int klp_obsoletes[] = { KLP_OBSOLETES };
> #endif
>
> And in livepatch_mod_init():
>
> #ifdef KLP_PROVIDES
> patch->provides = KLP_PROVIDES;
> #endif
> #ifdef KLP_OBSOLETES
> patch->obsoletes = klp_obsoletes;
> patch->nr_obsoletes = ARRAY_SIZE(klp_obsoletes);
> #endif
good point.
I will update it.
>
>
> > +++ 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:
> > @@ -147,7 +149,6 @@ Advanced Options:
> >
> > EOF
> > }
> > -
> > usage() {
>
> Unnecessary whitespace change
will update it.
>
> > @@ -235,6 +254,37 @@ process_args() {
> > exit 1
> > fi
> >
> > + # Remove duplicates from obsoletes (if specified)
> > + # Note: provides ID is not added here; the kernel will replace
> > + # livepatch with the same provides ID.
> > + if [[ -n "$OBSOLETES" ]]; then
> > + local obsoletes_clean="${OBSOLETES//[ ]/}"
> > + local IFS=','
> > + local -a obs_array=()
> > + local obs_id
> > + local already_exists
> > +
> > + for obs_id in $obsoletes_clean; do
> > + if [[ -n "$obs_id" ]]; then
> > + already_exists=0
> > + for existing in "${obs_array[@]}"; do
> > + if [[ "$existing" -eq "$obs_id" ]]; then
> > + already_exists=1
> > + break
> > + fi
> > + done
> > +
> > + if [[ "$already_exists" -eq 0 ]]; then
> > + obs_array+=("$obs_id")
> > + fi
> > + fi
> > + done
> > +
> > + local IFS=','
> > + OBSOLETES="${obs_array[*]}"
> > + unset IFS
> > + fi
> > +
>
> What's the point of this? Do we expect duplicates,
Duplicated ids are indeed a user input error (e.g. -r "1,1,2,3") and
should not be expected.
> and if so, wouldn't
> it be the kernel's job to handle that?
On the kernel side the duplicates are safe:
klp_patch_replaces() iterates over the obsoletes array and compares
each entry against the other patches' provides, so a duplicated id is
simply idempotent - the only side effect is duplicated ids shown in
the /sys/kernel/livepatch/<patch>/obsoletes attribute.
Silently deduplicating in klp-build was masking the input mistake.
Agreed on the fail-fast approach: klp-build will now error out with a
clear message when duplicate obsoletes ids are detected. The list
passed to the kernel is then guaranteed clean, and the sysfs side
effect cannot happen.
--
Regards
Yafang
^ permalink raw reply [flat|nested] 23+ messages in thread* Re: [PATCH v7 for-next 3/8] livepatch: Implement replace set for scoped atomic replace
2026-08-28 3:03 ` Yafang Shao
@ 2026-08-28 3:39 ` Josh Poimboeuf
2026-08-28 5:42 ` Yafang Shao
0 siblings, 1 reply; 23+ messages in thread
From: Josh Poimboeuf @ 2026-08-28 3:39 UTC (permalink / raw)
To: Yafang Shao; +Cc: jikos, mbenes, pmladek, joe.lawrence, song, live-patching
On Fri, Aug 28, 2026 at 11:03:02AM +0800, Yafang Shao wrote:
> On Fri, Aug 28, 2026 at 8:26 AM Josh Poimboeuf <jpoimboe@kernel.org> wrote:
> > > + # Remove duplicates from obsoletes (if specified)
> > > + # Note: provides ID is not added here; the kernel will replace
> > > + # livepatch with the same provides ID.
> > > + if [[ -n "$OBSOLETES" ]]; then
> > > + local obsoletes_clean="${OBSOLETES//[ ]/}"
> > > + local IFS=','
> > > + local -a obs_array=()
> > > + local obs_id
> > > + local already_exists
> > > +
> > > + for obs_id in $obsoletes_clean; do
> > > + if [[ -n "$obs_id" ]]; then
> > > + already_exists=0
> > > + for existing in "${obs_array[@]}"; do
> > > + if [[ "$existing" -eq "$obs_id" ]]; then
> > > + already_exists=1
> > > + break
> > > + fi
> > > + done
> > > +
> > > + if [[ "$already_exists" -eq 0 ]]; then
> > > + obs_array+=("$obs_id")
> > > + fi
> > > + fi
> > > + done
> > > +
> > > + local IFS=','
> > > + OBSOLETES="${obs_array[*]}"
> > > + unset IFS
> > > + fi
> > > +
> >
> > What's the point of this? Do we expect duplicates,
>
> Duplicated ids are indeed a user input error (e.g. -r "1,1,2,3") and
> should not be expected.
>
> > and if so, wouldn't
> > it be the kernel's job to handle that?
>
> On the kernel side the duplicates are safe:
> klp_patch_replaces() iterates over the obsoletes array and compares
> each entry against the other patches' provides, so a duplicated id is
> simply idempotent - the only side effect is duplicated ids shown in
> the /sys/kernel/livepatch/<patch>/obsoletes attribute.
>
> Silently deduplicating in klp-build was masking the input mistake.
> Agreed on the fail-fast approach: klp-build will now error out with a
> clear message when duplicate obsoletes ids are detected. The list
> passed to the kernel is then guaranteed clean, and the sysfs side
> effect cannot happen.
There are other ways of building livepatches beyond klp-build, and this
wouldn't prevent those other patch generation methods from making the
same mistake.
But since it doesn't actually break anything, and the user is not likely
to be setting multiple obsoletes, much less introducing duplicates, I
would say just remove the check altogether. It's not really a bug and I
doubt that somebody who is careless enough to have duplicate obsoletes
would notice or care that sysfs also has duplicates... and if they do
care, they should fix their patch :-)
For similar reasons I think some of the other error checking isn't
really needed (invalid comma usage, negative number check) as those will
either fail the build or will give the user what they asked for, not
dissimilar from providing a buggy patch.
Then I think the majority of those klp-build changes aren't needed, and
it can be a simple passthrough of the user cmdline:
cflags+=("-DKLP_OBSOLETES=$OBSOLETES")
--
Josh
^ permalink raw reply [flat|nested] 23+ messages in thread* Re: [PATCH v7 for-next 3/8] livepatch: Implement replace set for scoped atomic replace
2026-08-28 3:39 ` Josh Poimboeuf
@ 2026-08-28 5:42 ` Yafang Shao
0 siblings, 0 replies; 23+ messages in thread
From: Yafang Shao @ 2026-08-28 5:42 UTC (permalink / raw)
To: Josh Poimboeuf; +Cc: jikos, mbenes, pmladek, joe.lawrence, song, live-patching
On Fri, Aug 28, 2026 at 11:39 AM Josh Poimboeuf <jpoimboe@kernel.org> wrote:
>
> On Fri, Aug 28, 2026 at 11:03:02AM +0800, Yafang Shao wrote:
> > On Fri, Aug 28, 2026 at 8:26 AM Josh Poimboeuf <jpoimboe@kernel.org> wrote:
> > > > + # Remove duplicates from obsoletes (if specified)
> > > > + # Note: provides ID is not added here; the kernel will replace
> > > > + # livepatch with the same provides ID.
> > > > + if [[ -n "$OBSOLETES" ]]; then
> > > > + local obsoletes_clean="${OBSOLETES//[ ]/}"
> > > > + local IFS=','
> > > > + local -a obs_array=()
> > > > + local obs_id
> > > > + local already_exists
> > > > +
> > > > + for obs_id in $obsoletes_clean; do
> > > > + if [[ -n "$obs_id" ]]; then
> > > > + already_exists=0
> > > > + for existing in "${obs_array[@]}"; do
> > > > + if [[ "$existing" -eq "$obs_id" ]]; then
> > > > + already_exists=1
> > > > + break
> > > > + fi
> > > > + done
> > > > +
> > > > + if [[ "$already_exists" -eq 0 ]]; then
> > > > + obs_array+=("$obs_id")
> > > > + fi
> > > > + fi
> > > > + done
> > > > +
> > > > + local IFS=','
> > > > + OBSOLETES="${obs_array[*]}"
> > > > + unset IFS
> > > > + fi
> > > > +
> > >
> > > What's the point of this? Do we expect duplicates,
> >
> > Duplicated ids are indeed a user input error (e.g. -r "1,1,2,3") and
> > should not be expected.
> >
> > > and if so, wouldn't
> > > it be the kernel's job to handle that?
> >
> > On the kernel side the duplicates are safe:
> > klp_patch_replaces() iterates over the obsoletes array and compares
> > each entry against the other patches' provides, so a duplicated id is
> > simply idempotent - the only side effect is duplicated ids shown in
> > the /sys/kernel/livepatch/<patch>/obsoletes attribute.
> >
> > Silently deduplicating in klp-build was masking the input mistake.
> > Agreed on the fail-fast approach: klp-build will now error out with a
> > clear message when duplicate obsoletes ids are detected. The list
> > passed to the kernel is then guaranteed clean, and the sysfs side
> > effect cannot happen.
>
> There are other ways of building livepatches beyond klp-build, and this
> wouldn't prevent those other patch generation methods from making the
> same mistake.
>
> But since it doesn't actually break anything, and the user is not likely
> to be setting multiple obsoletes, much less introducing duplicates, I
> would say just remove the check altogether. It's not really a bug and I
> doubt that somebody who is careless enough to have duplicate obsoletes
> would notice or care that sysfs also has duplicates... and if they do
> care, they should fix their patch :-)
>
> For similar reasons I think some of the other error checking isn't
> really needed (invalid comma usage, negative number check) as those will
> either fail the build or will give the user what they asked for, not
> dissimilar from providing a buggy patch.
>
> Then I think the majority of those klp-build changes aren't needed, and
> it can be a simple passthrough of the user cmdline:
>
> cflags+=("-DKLP_OBSOLETES=$OBSOLETES")
Thanks for your suggestion.
I will update it.
--
Regards
Yafang
^ permalink raw reply [flat|nested] 23+ messages in thread
* [PATCH v7 for-next 4/8] livepatch: Deprecate stack_order
2026-08-25 11:46 [PATCH v7 for-next 0/8] livepatch: Introduce replace set support Yafang Shao
` (2 preceding siblings ...)
2026-08-25 11:46 ` [PATCH v7 for-next 3/8] livepatch: Implement replace set for scoped atomic replace Yafang Shao
@ 2026-08-25 11:46 ` Yafang Shao
2026-08-28 0:29 ` Josh Poimboeuf
2026-08-25 11:46 ` [PATCH v7 for-next 5/8] selftests/livepatch: Adapt atomic replace tests to provides/obsoletes Yafang Shao
` (3 subsequent siblings)
7 siblings, 1 reply; 23+ messages in thread
From: Yafang Shao @ 2026-08-25 11:46 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.
Since this livepatch sysfs interface is mainly intended for kernel
developers, backward compatibility does not need to be maintained.
Signed-off-by: Yafang Shao <laoar.shao@gmail.com>
Cc: Wardenjohn <zhangwarden@gmail.com>
Acked-by: Song Liu <song@kernel.org>
---
.../ABI/removed/sysfs-kernel-livepatch | 16 +++++++++++++
.../ABI/testing/sysfs-kernel-livepatch | 9 -------
kernel/livepatch/core.c | 24 -------------------
3 files changed, 16 insertions(+), 33 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..fb6ff9c3b43e
--- /dev/null
+++ b/Documentation/ABI/removed/sysfs-kernel-livepatch
@@ -0,0 +1,16 @@
+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.
+
+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 2588f676deb1..3564b3e4385f 100644
--- a/Documentation/ABI/testing/sysfs-kernel-livepatch
+++ b/Documentation/ABI/testing/sysfs-kernel-livepatch
@@ -67,15 +67,6 @@ Description:
either this patch's provides id or any id in the obsoletes
list will be atomically replaced.
-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 81e9d7aa1de7..27f3891176dd 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] 23+ messages in thread* Re: [PATCH v7 for-next 4/8] livepatch: Deprecate stack_order
2026-08-25 11:46 ` [PATCH v7 for-next 4/8] livepatch: Deprecate stack_order Yafang Shao
@ 2026-08-28 0:29 ` Josh Poimboeuf
2026-08-28 3:14 ` Yafang Shao
0 siblings, 1 reply; 23+ messages in thread
From: Josh Poimboeuf @ 2026-08-28 0:29 UTC (permalink / raw)
To: Yafang Shao
Cc: jikos, mbenes, pmladek, joe.lawrence, song, live-patching,
Wardenjohn
On Tue, Aug 25, 2026 at 07:46:37PM +0800, Yafang Shao wrote:
> stack_order is no longer needed for atomic-replace livepatches, as a
> single function can only be modified by a unique replace_set.
> Since this livepatch sysfs interface is mainly intended for kernel
> developers, backward compatibility does not need to be maintained.
>
> Signed-off-by: Yafang Shao <laoar.shao@gmail.com>
> Cc: Wardenjohn <zhangwarden@gmail.com>
> Acked-by: Song Liu <song@kernel.org>
> ---
> .../ABI/removed/sysfs-kernel-livepatch | 16 +++++++++++++
> .../ABI/testing/sysfs-kernel-livepatch | 9 -------
> kernel/livepatch/core.c | 24 -------------------
> 3 files changed, 16 insertions(+), 33 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..fb6ff9c3b43e
> --- /dev/null
> +++ b/Documentation/ABI/removed/sysfs-kernel-livepatch
> @@ -0,0 +1,16 @@
> +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.
This part belongs in that other patch which removed it?
--
Josh
^ permalink raw reply [flat|nested] 23+ messages in thread
* Re: [PATCH v7 for-next 4/8] livepatch: Deprecate stack_order
2026-08-28 0:29 ` Josh Poimboeuf
@ 2026-08-28 3:14 ` Yafang Shao
0 siblings, 0 replies; 23+ messages in thread
From: Yafang Shao @ 2026-08-28 3:14 UTC (permalink / raw)
To: Josh Poimboeuf
Cc: jikos, mbenes, pmladek, joe.lawrence, song, live-patching,
Wardenjohn
On Fri, Aug 28, 2026 at 8:29 AM Josh Poimboeuf <jpoimboe@kernel.org> wrote:
>
> On Tue, Aug 25, 2026 at 07:46:37PM +0800, Yafang Shao wrote:
> > stack_order is no longer needed for atomic-replace livepatches, as a
> > single function can only be modified by a unique replace_set.
> > Since this livepatch sysfs interface is mainly intended for kernel
> > developers, backward compatibility does not need to be maintained.
> >
> > Signed-off-by: Yafang Shao <laoar.shao@gmail.com>
> > Cc: Wardenjohn <zhangwarden@gmail.com>
> > Acked-by: Song Liu <song@kernel.org>
> > ---
> > .../ABI/removed/sysfs-kernel-livepatch | 16 +++++++++++++
> > .../ABI/testing/sysfs-kernel-livepatch | 9 -------
> > kernel/livepatch/core.c | 24 -------------------
> > 3 files changed, 16 insertions(+), 33 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..fb6ff9c3b43e
> > --- /dev/null
> > +++ b/Documentation/ABI/removed/sysfs-kernel-livepatch
> > @@ -0,0 +1,16 @@
> > +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.
>
> This part belongs in that other patch which removed it?
My bad.
I will update it.
--
Regards
Yafang
^ permalink raw reply [flat|nested] 23+ messages in thread
* [PATCH v7 for-next 5/8] selftests/livepatch: Adapt atomic replace tests to provides/obsoletes
2026-08-25 11:46 [PATCH v7 for-next 0/8] livepatch: Introduce replace set support Yafang Shao
` (3 preceding siblings ...)
2026-08-25 11:46 ` [PATCH v7 for-next 4/8] livepatch: Deprecate stack_order Yafang Shao
@ 2026-08-25 11:46 ` Yafang Shao
2026-08-28 0:31 ` Josh Poimboeuf
2026-08-25 11:46 ` [PATCH v7 for-next 6/8] selftests/livepatch: Add provides/obsoletes test scenarios Yafang Shao
` (2 subsequent siblings)
7 siblings, 1 reply; 23+ messages in thread
From: Yafang Shao @ 2026-08-25 11:46 UTC (permalink / raw)
To: jpoimboe, jikos, mbenes, pmladek, joe.lawrence, song
Cc: live-patching, Yafang Shao
The legacy "replace" field in struct klp_patch was 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:
- test_modules/Makefile detects whether the kernel header still
declares the "replace" field. If so, it passes KLP_HAS_REPLACE to
the modules via ccflags-y.
- test_klp_atomic_replace.c and other source files using .replace guard
their replace module parameter and .replace assignment with
#ifdef KLP_HAS_REPLACE. The #else branches are left as TODO
placeholders for the upcoming provides/obsoletes based tests.
- functions.sh adds detect_replace_attr(), which loads a temporary
livepatch and checks for the /sys/kernel/livepatch/<patch>/replace
attribute, setting HAS_REPLACE_ATTR accordingly.
- test-livepatch.sh and test-callbacks.sh run the tests that depend
on multiple livepatches coexisting or on the atomic replace
behavior ("multiple livepatches" and "atomic replace") only when
HAS_REPLACE_ATTR is set. These scenarios do not apply to the new
kernel, where provides=0 replaces any other livepatch by default.
The provides/obsoletes based selftests will be added later.
Assisted-by: Comagic:DeepSeek-V4-Flash
Signed-off-by: Yafang Shao <laoar.shao@gmail.com>
---
tools/testing/selftests/livepatch/functions.sh | 15 +++++++++++++++
.../selftests/livepatch/test-callbacks.sh | 6 ++++++
.../selftests/livepatch/test-livepatch.sh | 6 ++++++
.../selftests/livepatch/test_modules/Makefile | 14 ++++++++++++++
.../test_modules/test_klp_atomic_replace.c | 16 ++++++++++++++++
.../test_modules/test_klp_callbacks_demo2.c | 16 ++++++++++++++++
.../livepatch/test_modules/test_klp_state.c | 7 +++++++
.../livepatch/test_modules/test_klp_state2.c | 7 +++++++
8 files changed, 87 insertions(+)
diff --git a/tools/testing/selftests/livepatch/functions.sh b/tools/testing/selftests/livepatch/functions.sh
index 30dc677b2f45..17810521d069 100644
--- a/tools/testing/selftests/livepatch/functions.sh
+++ b/tools/testing/selftests/livepatch/functions.sh
@@ -351,6 +351,21 @@ function does_sysfs_exist() {
[[ -f "$SYSFS_KLP_DIR/$mod/$attr" ]]
}
+# detect_replace_attr() - detect whether the running kernel still exposes
+# the legacy "replace" sysfs attribute and set HAS_REPLACE_ATTR
+# accordingly. The atomic replace tests are only run when the attribute
+# is present. The provides/obsoletes based tests will be added later.
+function detect_replace_attr() {
+ HAS_REPLACE_ATTR=0
+
+ load_lp test_klp_livepatch
+ if does_sysfs_exist test_klp_livepatch "replace"; then
+ HAS_REPLACE_ATTR=1
+ fi
+ disable_lp test_klp_livepatch
+ unload_lp test_klp_livepatch
+}
+
# 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..e9b2dc6ea099 100755
--- a/tools/testing/selftests/livepatch/test-callbacks.sh
+++ b/tools/testing/selftests/livepatch/test-callbacks.sh
@@ -11,6 +11,8 @@ MOD_TARGET_BUSY=test_klp_callbacks_busy
setup_config
+detect_replace_attr
+
# Test a combination of loading a kernel module and a livepatch that
# patches a function in the first module. Load the target module
@@ -451,6 +453,7 @@ $MOD_TARGET_BUSY: busymod_work_func exit
$MOD_TARGET_BUSY: ${MOD_TARGET_BUSY}_exit"
+if [[ "$HAS_REPLACE_ATTR" == "1" ]]; then
# Test loading multiple livepatches. This test-case is mainly for comparing
# with the next test-case.
#
@@ -499,8 +502,10 @@ $MOD_LIVEPATCH: post_unpatch_callback: vmlinux
livepatch: '$MOD_LIVEPATCH': unpatching complete
% rmmod $MOD_LIVEPATCH2
% rmmod $MOD_LIVEPATCH"
+fi
+if [[ "$HAS_REPLACE_ATTR" == "1" ]]; then
# 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
@@ -548,6 +553,7 @@ $MOD_LIVEPATCH2: post_unpatch_callback: vmlinux
livepatch: '$MOD_LIVEPATCH2': unpatching complete
% rmmod $MOD_LIVEPATCH2
% rmmod $MOD_LIVEPATCH"
+fi
exit 0
diff --git a/tools/testing/selftests/livepatch/test-livepatch.sh b/tools/testing/selftests/livepatch/test-livepatch.sh
index c44c5341a2f1..b3b7cd81e3d0 100755
--- a/tools/testing/selftests/livepatch/test-livepatch.sh
+++ b/tools/testing/selftests/livepatch/test-livepatch.sh
@@ -13,6 +13,8 @@ MOD_TARGET_PATCH=test_klp_mod_patch
setup_config
+detect_replace_attr
+
# - load a livepatch that modifies the output from /proc/cmdline and
# verify correct behavior
@@ -49,6 +51,7 @@ livepatch: '$MOD_LIVEPATCH1': unpatching complete
% rmmod $MOD_LIVEPATCH1"
+if [[ "$HAS_REPLACE_ATTR" == "1" ]]; then
# - load a livepatch that modifies the output from /proc/cmdline and
# verify correct behavior
# - load another livepatch and verify that both livepatches are active
@@ -107,8 +110,10 @@ livepatch: '$MOD_LIVEPATCH1': starting unpatching transition
livepatch: '$MOD_LIVEPATCH1': completing unpatching transition
livepatch: '$MOD_LIVEPATCH1': unpatching complete
% rmmod $MOD_LIVEPATCH1"
+fi
+if [[ "$HAS_REPLACE_ATTR" == "1" ]]; then
# - 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
@@ -196,6 +201,7 @@ livepatch: '$MOD_REPLACE': starting unpatching transition
livepatch: '$MOD_REPLACE': completing unpatching transition
livepatch: '$MOD_REPLACE': unpatching complete
% rmmod $MOD_REPLACE"
+fi
# - load a target module that provides /proc/test_klp_mod_target with
diff --git a/tools/testing/selftests/livepatch/test_modules/Makefile b/tools/testing/selftests/livepatch/test_modules/Makefile
index a13d398585dc..a4fe469a6501 100644
--- a/tools/testing/selftests/livepatch/test_modules/Makefile
+++ b/tools/testing/selftests/livepatch/test_modules/Makefile
@@ -16,6 +16,20 @@ obj-m += test_klp_atomic_replace.o \
test_klp_state3.o \
test_klp_syscall.o
+# The legacy "replace" field was replaced by provides/obsoletes. When the
+# kernel (KDIR) still supports "replace", pass KLP_HAS_REPLACE to the
+# modules so that they can #ifdef the legacy "replace" code. Otherwise
+# the modules are built with the provides/obsoletes based code (to be
+# added later).
+KLP_SRC := $(if $(wildcard $(srctree)/include/linux/livepatch.h), \
+ $(srctree), $(KDIR))
+KLP_HAS_REPLACE := $(shell grep -q 'bool replace' \
+ $(KLP_SRC)/include/linux/livepatch.h 2>/dev/null \
+ && echo y)
+ifeq ($(KLP_HAS_REPLACE),y)
+ccflags-y += -DKLP_HAS_REPLACE
+endif
+
# Ensure that KDIR exists, otherwise skip the compilation
modules:
ifneq ("$(wildcard $(KDIR))", "")
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..4b3ed17886e7 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,16 @@
#include <linux/kernel.h>
#include <linux/livepatch.h>
+#ifdef KLP_HAS_REPLACE
static int replace;
module_param(replace, int, 0644);
MODULE_PARM_DESC(replace, "replace (default=0)");
+#else
+/*
+ * TODO: Add provides/obsoletes module parameters for the
+ * provides/obsoletes based tests (to be added later).
+ */
+#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,
+#ifdef KLP_HAS_REPLACE
/* set .replace in the init function below for demo purposes */
+#endif
};
static int test_klp_atomic_replace_init(void)
{
+#ifdef KLP_HAS_REPLACE
patch.replace = replace;
+#else
+ /*
+ * TODO: Set provides/obsoletes from the module parameters
+ * for the provides/obsoletes based tests (to be added later).
+ */
+#endif
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..135da6d4d882 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 KLP_HAS_REPLACE
static int replace;
module_param(replace, int, 0644);
MODULE_PARM_DESC(replace, "replace (default=0)");
+#else
+/*
+ * TODO: Add provides/obsoletes module parameters for the
+ * provides/obsoletes based tests (to be added later).
+ */
+#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,
+#ifdef KLP_HAS_REPLACE
/* set .replace in the init function below for demo purposes */
+#endif
};
static int test_klp_callbacks_demo2_init(void)
{
+#ifdef KLP_HAS_REPLACE
patch.replace = replace;
+#else
+ /*
+ * TODO: Set provides/obsoletes from the module parameters
+ * for the provides/obsoletes based tests (to be added later).
+ */
+#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..3492472e5d07 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 KLP_HAS_REPLACE
.replace = true,
+#else
+/*
+ * TODO: Add provides/obsoletes module parameters for the
+ * provides/obsoletes based tests (to be added later).
+ */
+#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..8160b565a659 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 KLP_HAS_REPLACE
.replace = true,
+#else
+/*
+ * TODO: Add provides/obsoletes module parameters for the
+ * provides/obsoletes based tests (to be added later).
+ */
+#endif
};
static int test_klp_callbacks_demo_init(void)
--
2.52.0
^ permalink raw reply related [flat|nested] 23+ messages in thread* Re: [PATCH v7 for-next 5/8] selftests/livepatch: Adapt atomic replace tests to provides/obsoletes
2026-08-25 11:46 ` [PATCH v7 for-next 5/8] selftests/livepatch: Adapt atomic replace tests to provides/obsoletes Yafang Shao
@ 2026-08-28 0:31 ` Josh Poimboeuf
2026-08-28 3:56 ` Yafang Shao
0 siblings, 1 reply; 23+ messages in thread
From: Josh Poimboeuf @ 2026-08-28 0:31 UTC (permalink / raw)
To: Yafang Shao; +Cc: jikos, mbenes, pmladek, joe.lawrence, song, live-patching
On Tue, Aug 25, 2026 at 07:46:38PM +0800, Yafang Shao wrote:
> The legacy "replace" field in struct klp_patch was replaced by the
> provides/obsoletes mechanism. As a result, the atomic replace
> selftests fail to build against kernels that only support
> provides/obsoletes.
Then wouldn't the test failure be working as designed?
--
Josh
^ permalink raw reply [flat|nested] 23+ messages in thread
* Re: [PATCH v7 for-next 5/8] selftests/livepatch: Adapt atomic replace tests to provides/obsoletes
2026-08-28 0:31 ` Josh Poimboeuf
@ 2026-08-28 3:56 ` Yafang Shao
0 siblings, 0 replies; 23+ messages in thread
From: Yafang Shao @ 2026-08-28 3:56 UTC (permalink / raw)
To: Josh Poimboeuf; +Cc: jikos, mbenes, pmladek, joe.lawrence, song, live-patching
On Fri, Aug 28, 2026 at 8:31 AM Josh Poimboeuf <jpoimboe@kernel.org> wrote:
>
> On Tue, Aug 25, 2026 at 07:46:38PM +0800, Yafang Shao wrote:
> > The legacy "replace" field in struct klp_patch was replaced by the
> > provides/obsoletes mechanism. As a result, the atomic replace
> > selftests fail to build against kernels that only support
> > provides/obsoletes.
>
> Then wouldn't the test failure be working as designed?
Agreed that dropping the legacy field makes the old tests fail to
build - that is the expected consequence of the API change, not a bug.
The point of this patch is not to hide that failure. The suite still
runs on older kernels that keep the "replace" field, and we do not
want a new kernel to take down the *entire* livepatch selftest set
just because one module no longer compiles. The #ifdef keeps one test
tree working on both kernels.
However, the current patch skips the replace scenarios silently, which
is a real problem: a green run would hide the missing coverage.
I'll make the skip explicit: print a SKIP line in the log when
HAS_REPLACE_ATTR is not set, so the output shows exactly what is not
covered, such as:
if [[ "$HAS_REPLACE_ATTR" == "1" ]]; then
# ... atomic replace scenario ...
else
echo "SKIP: legacy replace attribute not present; replaced by
provides/obsoletes (tests to be added later in this series)"
fi
I'll fold this into the next revision. If you'd prefer the legacy
scenarios dropped entirely (and only provides/obsoletes based tests
kept), I can do that instead.
--
Regards
Yafang
^ permalink raw reply [flat|nested] 23+ messages in thread
* [PATCH v7 for-next 6/8] selftests/livepatch: Add provides/obsoletes test scenarios
2026-08-25 11:46 [PATCH v7 for-next 0/8] livepatch: Introduce replace set support Yafang Shao
` (4 preceding siblings ...)
2026-08-25 11:46 ` [PATCH v7 for-next 5/8] selftests/livepatch: Adapt atomic replace tests to provides/obsoletes Yafang Shao
@ 2026-08-25 11:46 ` Yafang Shao
2026-08-25 11:46 ` [PATCH v7 for-next 7/8] selftests/livepatch: Add test for state ID conflict across provides Yafang Shao
2026-08-25 11:46 ` [PATCH v7 for-next 8/8] selftests/livepatch: Add test for function " Yafang Shao
7 siblings, 0 replies; 23+ messages in thread
From: Yafang Shao @ 2026-08-25 11:46 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.
Fill in the #else branches of the test modules that were left as
TODO placeholders in the previous patch:
- test_klp_atomic_replace.c gains provides and obsoletes module
parameters for the provides/obsoletes based tests.
- test_klp_callbacks_demo2.c gains a provides module parameter.
- test_klp_livepatch.c gains a provides module parameter so that it
can be loaded with a specific provides id for coexistence tests.
- test_klp_state.c and test_klp_state2.c add a comment in the #else
branch noting that provides=0 (the default) is equivalent to the
previous .replace=true behavior.
Add test-provides.sh with four test scenarios:
1. provides=0 replaces provides=0: two livepatches with provides=0
that modify different functions; the second replaces the first.
2. Same provides replaces: two livepatches with provides=1 that
modify different functions; the second replaces the first.
3. Obsoletes replaces: a livepatch with provides=2 and obsoletes=[1]
replaces a livepatch with provides=1. The provides/obsoletes sysfs
values are verified.
4. provides=0 coexists with provides=1: two livepatches with
different provides ids that modify different functions coexist,
verifying that provides=0 does not replace a patch with a
different provides id.
The provides/obsoletes based tests only run on kernels that no longer
support the legacy "replace" attribute.
Assisted-by: Comagic:DeepSeek-V4-Flash
Signed-off-by: Yafang Shao <laoar.shao@gmail.com>
---
tools/testing/selftests/livepatch/Makefile | 3 +-
.../livepatch/test-provides-obsoletes.sh | 174 ++++++++++++++++++
.../test_modules/test_klp_atomic_replace.c | 22 ++-
.../test_modules/test_klp_callbacks_demo2.c | 12 +-
.../test_modules/test_klp_livepatch.c | 9 +
.../livepatch/test_modules/test_klp_state.c | 5 +-
.../livepatch/test_modules/test_klp_state2.c | 5 +-
7 files changed, 205 insertions(+), 25 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..2707457b9133
--- /dev/null
+++ b/tools/testing/selftests/livepatch/test-provides-obsoletes.sh
@@ -0,0 +1,174 @@
+#!/bin/bash
+# SPDX-License-Identifier: GPL-2.0
+# Copyright (C) 2026 Yafang Shao <laoar.shao@gmail.com>
+
+. $(dirname $0)/functions.sh
+
+MOD_ATOMIC=test_klp_atomic_replace
+MOD_LIVEPATCH=test_klp_livepatch
+
+setup_config
+
+detect_replace_attr
+
+# The provides/obsoletes based tests only run on kernels that no longer
+# support the legacy "replace" attribute.
+if [[ "$HAS_REPLACE_ATTR" == "1" ]]; then
+ echo "SKIP: kernel still supports the legacy replace attribute"
+ exit $ksft_skip
+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_LIVEPATCH provides=0
+load_lp $MOD_ATOMIC provides=0
+
+check_sysfs_value "$MOD_ATOMIC" "enabled" "1"
+
+disable_lp $MOD_ATOMIC
+unload_lp $MOD_ATOMIC
+unload_lp $MOD_LIVEPATCH
+
+check_result "% insmod test_modules/$MOD_LIVEPATCH.ko provides=0
+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_ATOMIC.ko provides=0
+livepatch: enabling patch '$MOD_ATOMIC'
+livepatch: '$MOD_ATOMIC': initializing patching transition
+livepatch: '$MOD_ATOMIC': starting patching transition
+livepatch: '$MOD_ATOMIC': completing patching transition
+livepatch: '$MOD_ATOMIC': patching complete
+% echo 0 > /sys/kernel/livepatch/$MOD_ATOMIC/enabled
+livepatch: '$MOD_ATOMIC': initializing unpatching transition
+livepatch: '$MOD_ATOMIC': starting unpatching transition
+livepatch: '$MOD_ATOMIC': completing unpatching transition
+livepatch: '$MOD_ATOMIC': unpatching complete
+% rmmod $MOD_ATOMIC
+% rmmod $MOD_LIVEPATCH"
+
+
+# - 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_LIVEPATCH provides=1
+load_lp $MOD_ATOMIC provides=1
+
+check_sysfs_value "$MOD_ATOMIC" "enabled" "1"
+
+disable_lp $MOD_ATOMIC
+unload_lp $MOD_ATOMIC
+unload_lp $MOD_LIVEPATCH
+
+check_result "% insmod test_modules/$MOD_LIVEPATCH.ko provides=1
+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_ATOMIC.ko provides=1
+livepatch: enabling patch '$MOD_ATOMIC'
+livepatch: '$MOD_ATOMIC': initializing patching transition
+livepatch: '$MOD_ATOMIC': starting patching transition
+livepatch: '$MOD_ATOMIC': completing patching transition
+livepatch: '$MOD_ATOMIC': patching complete
+% echo 0 > /sys/kernel/livepatch/$MOD_ATOMIC/enabled
+livepatch: '$MOD_ATOMIC': initializing unpatching transition
+livepatch: '$MOD_ATOMIC': starting unpatching transition
+livepatch: '$MOD_ATOMIC': completing unpatching transition
+livepatch: '$MOD_ATOMIC': unpatching complete
+% rmmod $MOD_ATOMIC
+% rmmod $MOD_LIVEPATCH"
+
+
+# - 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_LIVEPATCH provides=1
+load_lp $MOD_ATOMIC provides=2 obsoletes=1
+
+check_sysfs_value "$MOD_ATOMIC" "provides" "2"
+check_sysfs_value "$MOD_ATOMIC" "obsoletes" "1"
+
+disable_lp $MOD_ATOMIC
+unload_lp $MOD_ATOMIC
+unload_lp $MOD_LIVEPATCH
+
+check_result "% insmod test_modules/$MOD_LIVEPATCH.ko provides=1
+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_ATOMIC.ko provides=2 obsoletes=1
+livepatch: enabling patch '$MOD_ATOMIC'
+livepatch: '$MOD_ATOMIC': initializing patching transition
+livepatch: '$MOD_ATOMIC': starting patching transition
+livepatch: '$MOD_ATOMIC': completing patching transition
+livepatch: '$MOD_ATOMIC': patching complete
+% echo 0 > /sys/kernel/livepatch/$MOD_ATOMIC/enabled
+livepatch: '$MOD_ATOMIC': initializing unpatching transition
+livepatch: '$MOD_ATOMIC': starting unpatching transition
+livepatch: '$MOD_ATOMIC': completing unpatching transition
+livepatch: '$MOD_ATOMIC': unpatching complete
+% rmmod $MOD_ATOMIC
+% rmmod $MOD_LIVEPATCH"
+
+
+# - 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_LIVEPATCH provides=0
+load_lp $MOD_ATOMIC provides=1
+
+disable_lp $MOD_LIVEPATCH
+unload_lp $MOD_LIVEPATCH
+disable_lp $MOD_ATOMIC
+unload_lp $MOD_ATOMIC
+
+check_result "% insmod test_modules/$MOD_LIVEPATCH.ko provides=0
+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_ATOMIC.ko provides=1
+livepatch: enabling patch '$MOD_ATOMIC'
+livepatch: '$MOD_ATOMIC': initializing patching transition
+livepatch: '$MOD_ATOMIC': starting patching transition
+livepatch: '$MOD_ATOMIC': completing patching transition
+livepatch: '$MOD_ATOMIC': patching complete
+% echo 0 > /sys/kernel/livepatch/$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
+% echo 0 > /sys/kernel/livepatch/$MOD_ATOMIC/enabled
+livepatch: '$MOD_ATOMIC': initializing unpatching transition
+livepatch: '$MOD_ATOMIC': starting unpatching transition
+livepatch: '$MOD_ATOMIC': completing unpatching transition
+livepatch: '$MOD_ATOMIC': unpatching complete
+% rmmod $MOD_ATOMIC"
+
+exit 0
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 4b3ed17886e7..b0ed924eeeed 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
@@ -12,10 +12,15 @@ static int replace;
module_param(replace, int, 0644);
MODULE_PARM_DESC(replace, "replace (default=0)");
#else
-/*
- * TODO: Add provides/obsoletes module parameters for the
- * provides/obsoletes based tests (to be added later).
- */
+static unsigned int provides;
+module_param(provides, uint, 0644);
+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, 0644);
+MODULE_PARM_DESC(obsoletes, "obsoletes provides ids");
#endif
#include <linux/seq_file.h>
@@ -53,10 +58,11 @@ static int test_klp_atomic_replace_init(void)
#ifdef KLP_HAS_REPLACE
patch.replace = replace;
#else
- /*
- * 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;
+ }
#endif
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 135da6d4d882..a0e7b7d1e198 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
@@ -12,10 +12,9 @@ static int replace;
module_param(replace, int, 0644);
MODULE_PARM_DESC(replace, "replace (default=0)");
#else
-/*
- * TODO: Add provides/obsoletes module parameters for the
- * provides/obsoletes based tests (to be added later).
- */
+static unsigned int provides;
+module_param(provides, uint, 0644);
+MODULE_PARM_DESC(provides, "provides id (default=0)");
#endif
static const char *const module_state[] = {
@@ -89,10 +88,7 @@ static int test_klp_callbacks_demo2_init(void)
#ifdef KLP_HAS_REPLACE
patch.replace = replace;
#else
- /*
- * TODO: Set provides/obsoletes from the module parameters
- * for the provides/obsoletes based tests (to be added later).
- */
+ patch.provides = provides;
#endif
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..fa2bc6ec921d 100644
--- a/tools/testing/selftests/livepatch/test_modules/test_klp_livepatch.c
+++ b/tools/testing/selftests/livepatch/test_modules/test_klp_livepatch.c
@@ -7,6 +7,12 @@
#include <linux/kernel.h>
#include <linux/livepatch.h>
+#ifndef KLP_HAS_REPLACE
+static unsigned int provides;
+module_param(provides, uint, 0644);
+MODULE_PARM_DESC(provides, "provides id (default=0)");
+#endif
+
#include <linux/seq_file.h>
static int livepatch_cmdline_proc_show(struct seq_file *m, void *v)
{
@@ -36,6 +42,9 @@ static struct klp_patch patch = {
static int test_klp_livepatch_init(void)
{
+#ifndef KLP_HAS_REPLACE
+ patch.provides = provides;
+#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 3492472e5d07..313401a5506e 100644
--- a/tools/testing/selftests/livepatch/test_modules/test_klp_state.c
+++ b/tools/testing/selftests/livepatch/test_modules/test_klp_state.c
@@ -145,10 +145,7 @@ static struct klp_patch patch = {
#ifdef KLP_HAS_REPLACE
.replace = true,
#else
-/*
- * 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 */
#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 8160b565a659..1afc2cabc39d 100644
--- a/tools/testing/selftests/livepatch/test_modules/test_klp_state2.c
+++ b/tools/testing/selftests/livepatch/test_modules/test_klp_state2.c
@@ -174,10 +174,7 @@ static struct klp_patch patch = {
#ifdef KLP_HAS_REPLACE
.replace = true,
#else
-/*
- * 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 */
#endif
};
--
2.52.0
^ permalink raw reply related [flat|nested] 23+ messages in thread* [PATCH v7 for-next 7/8] selftests/livepatch: Add test for state ID conflict across provides
2026-08-25 11:46 [PATCH v7 for-next 0/8] livepatch: Introduce replace set support Yafang Shao
` (5 preceding siblings ...)
2026-08-25 11:46 ` [PATCH v7 for-next 6/8] selftests/livepatch: Add provides/obsoletes test scenarios Yafang Shao
@ 2026-08-25 11:46 ` Yafang Shao
2026-08-25 11:46 ` [PATCH v7 for-next 8/8] selftests/livepatch: Add test for function " Yafang Shao
7 siblings, 0 replies; 23+ messages in thread
From: Yafang Shao @ 2026-08-25 11:46 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. If a second livepatch attempts to reuse a state id already
registered by a livepatch with a different provides id, the loading
will fail. However, if the second livepatch's obsoletes list includes
the first livepatch's provides id, the second livepatch replaces the
first one and may reuse the same state id.
Add provides and obsoletes module parameters to test_klp_state.c and
test_klp_state2.c (guarded by #ifndef KLP_HAS_REPLACE) so that they
can be loaded with different provides ids and obsoletes ids.
Add a "state id conflict across provides" test scenario to
test-provides.sh:
- Load test_klp_state with provides=1, which registers state ID 1.
- Attempt to load test_klp_state2 with provides=2, which reuses the
same state ID 1. The second livepatch is rejected because
livepatches with different provides ids must not share the same
state id.
- Disable and unload the remaining livepatch.
Add a "taking over system state via obsoletes" test scenario to
test-state.sh:
- Load test_klp_state with provides=10, which registers state ID 1.
- Load test_klp_state2 with provides=20 obsoletes=10, which reuses the
same state ID 1. Although the provides ids differ, the second livepatch
replaces the first one because its obsoletes list includes the first
livepatch's provides id (10). The state is taken over successfully.
- Unload the replaced livepatch, then disable and unload the second
livepatch.
Assisted-by: Comagic:DeepSeek-V4-Flash
Signed-off-by: Yafang Shao <laoar.shao@gmail.com>
---
.../livepatch/test-provides-obsoletes.sh | 86 +++++++++++++++++++
.../livepatch/test_modules/test_klp_state.c | 9 ++
.../livepatch/test_modules/test_klp_state2.c | 19 ++++
3 files changed, 114 insertions(+)
diff --git a/tools/testing/selftests/livepatch/test-provides-obsoletes.sh b/tools/testing/selftests/livepatch/test-provides-obsoletes.sh
index 2707457b9133..f9a9f9b28490 100755
--- a/tools/testing/selftests/livepatch/test-provides-obsoletes.sh
+++ b/tools/testing/selftests/livepatch/test-provides-obsoletes.sh
@@ -6,6 +6,8 @@
MOD_ATOMIC=test_klp_atomic_replace
MOD_LIVEPATCH=test_klp_livepatch
+MOD_STATE=test_klp_state
+MOD_STATE2=test_klp_state2
setup_config
@@ -171,4 +173,88 @@ livepatch: '$MOD_ATOMIC': completing unpatching transition
livepatch: '$MOD_ATOMIC': unpatching complete
% rmmod $MOD_ATOMIC"
+# - 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 "state id conflict across provides"
+
+load_lp $MOD_STATE provides=1
+load_failing_mod $MOD_STATE2 provides=2
+
+disable_lp $MOD_STATE
+unload_lp $MOD_STATE
+
+check_result "% insmod test_modules/$MOD_STATE.ko 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 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 "taking over system state via obsoletes"
+
+load_lp $MOD_STATE provides=10
+load_lp $MOD_STATE2 provides=20 obsoletes=10
+unload_lp $MOD_STATE
+disable_lp $MOD_STATE2
+unload_lp $MOD_STATE2
+
+check_result "% insmod test_modules/$MOD_STATE.ko provides=10
+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 provides=20 obsoletes=10
+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"
+
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 313401a5506e..77a5511d9d69 100644
--- a/tools/testing/selftests/livepatch/test_modules/test_klp_state.c
+++ b/tools/testing/selftests/livepatch/test_modules/test_klp_state.c
@@ -9,6 +9,12 @@
#include <linux/printk.h>
#include <linux/livepatch.h>
+#ifndef KLP_HAS_REPLACE
+static unsigned int provides;
+module_param(provides, uint, 0644);
+MODULE_PARM_DESC(provides, "provides id (default=0)");
+#endif
+
#define CONSOLE_LOGLEVEL_STATE 1
/* Version 1 does not support migration. */
#define CONSOLE_LOGLEVEL_STATE_VERSION 1
@@ -151,6 +157,9 @@ static struct klp_patch patch = {
static int test_klp_callbacks_demo_init(void)
{
+#ifndef KLP_HAS_REPLACE
+ patch.provides = provides;
+#endif
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 1afc2cabc39d..53ff6e793952 100644
--- a/tools/testing/selftests/livepatch/test_modules/test_klp_state2.c
+++ b/tools/testing/selftests/livepatch/test_modules/test_klp_state2.c
@@ -9,6 +9,18 @@
#include <linux/printk.h>
#include <linux/livepatch.h>
+#ifndef KLP_HAS_REPLACE
+static unsigned int provides;
+module_param(provides, uint, 0644);
+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, 0644);
+MODULE_PARM_DESC(obsoletes, "obsoletes provides ids");
+#endif
+
#define CONSOLE_LOGLEVEL_STATE 1
/* Version 2 supports migration. */
#define CONSOLE_LOGLEVEL_STATE_VERSION 2
@@ -180,6 +192,13 @@ static struct klp_patch patch = {
static int test_klp_callbacks_demo_init(void)
{
+#ifndef KLP_HAS_REPLACE
+ patch.provides = provides;
+ if (nr_obsoletes > 0) {
+ patch.obsoletes = obsoletes;
+ patch.nr_obsoletes = nr_obsoletes;
+ }
+#endif
return klp_enable_patch(&patch);
}
--
2.52.0
^ permalink raw reply related [flat|nested] 23+ messages in thread* [PATCH v7 for-next 8/8] selftests/livepatch: Add test for function conflict across provides
2026-08-25 11:46 [PATCH v7 for-next 0/8] livepatch: Introduce replace set support Yafang Shao
` (6 preceding siblings ...)
2026-08-25 11:46 ` [PATCH v7 for-next 7/8] selftests/livepatch: Add test for state ID conflict across provides Yafang Shao
@ 2026-08-25 11:46 ` Yafang Shao
7 siblings, 0 replies; 23+ messages in thread
From: Yafang Shao @ 2026-08-25 11:46 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. If a second livepatch attempts to modify a function that
has already been modified by a loaded livepatch with a different
provides id, the loading will fail. If the second livepatch shares
the same provides id, or if its obsoletes list contains the first
livepatch's provides id, it will load successfully and replace the
first one.
Add a new test module test_klp_provides.c that patches meminfo_proc_show
(the same function as test_klp_atomic_replace) so that the two modules
can be loaded with different provides ids to test the function conflict
detection.
Add three test scenarios to test-provides.sh:
1. Function conflict across provides: load test_klp_atomic_replace with
provides=1, then attempt to load test_klp_provides with provides=2.
The second livepatch is rejected because livepatches with different
provides ids and no obsoletes must not modify the same function.
2. Function replace within same provides: load test_klp_atomic_replace
with provides=1, then load test_klp_provides with provides=1. The
second livepatch loads successfully and replaces the first one
because they share the same provides id.
3. Function replace across provides with obsoletes: load
test_klp_atomic_replace with provides=1, then load test_klp_provides
with provides=2 and obsoletes=[1]. The second livepatch loads
successfully and replaces the first one because the obsoletes list
allows it to replace a livepatch with a different provides id.
Assisted-by: Comagic:DeepSeek-V4-Flash
Signed-off-by: Yafang Shao <laoar.shao@gmail.com>
---
.../livepatch/test-provides-obsoletes.sh | 110 ++++++++++++++++++
.../selftests/livepatch/test_modules/Makefile | 1 +
.../test_modules/test_klp_provides.c | 72 ++++++++++++
3 files changed, 183 insertions(+)
create mode 100644 tools/testing/selftests/livepatch/test_modules/test_klp_provides.c
diff --git a/tools/testing/selftests/livepatch/test-provides-obsoletes.sh b/tools/testing/selftests/livepatch/test-provides-obsoletes.sh
index f9a9f9b28490..be54e8d2c889 100755
--- a/tools/testing/selftests/livepatch/test-provides-obsoletes.sh
+++ b/tools/testing/selftests/livepatch/test-provides-obsoletes.sh
@@ -8,6 +8,7 @@ MOD_ATOMIC=test_klp_atomic_replace
MOD_LIVEPATCH=test_klp_livepatch
MOD_STATE=test_klp_state
MOD_STATE2=test_klp_state2
+MOD_PROVIDES=test_klp_provides
setup_config
@@ -257,4 +258,113 @@ $MOD_STATE2: free_loglevel_state: freeing space for the stored console_loglevel
livepatch: '$MOD_STATE2': unpatching complete
% rmmod $MOD_STATE2"
+
+# - load a livepatch with provides=1 that modifies meminfo_proc_show
+# - try to load another livepatch with provides=2 that modifies the
+# same function. The second one must be rejected because livepatches
+# with different provides ids and no obsoletes must not modify
+# the same function.
+# - disable and unload the remaining livepatch
+
+start_test "function conflict across provides"
+
+load_lp $MOD_ATOMIC provides=1
+load_failing_mod $MOD_PROVIDES provides=2
+
+disable_lp $MOD_ATOMIC
+unload_lp $MOD_ATOMIC
+
+check_result "% insmod test_modules/$MOD_ATOMIC.ko provides=1
+livepatch: enabling patch '$MOD_ATOMIC'
+livepatch: '$MOD_ATOMIC': initializing patching transition
+livepatch: '$MOD_ATOMIC': starting patching transition
+livepatch: '$MOD_ATOMIC': completing patching transition
+livepatch: '$MOD_ATOMIC': patching complete
+% insmod test_modules/$MOD_PROVIDES.ko provides=2
+livepatch: Livepatch patch ($MOD_PROVIDES) is not compatible with the already installed livepatches.
+insmod: ERROR: could not insert module test_modules/$MOD_PROVIDES.ko: Invalid parameters
+% echo 0 > $SYSFS_KLP_DIR/$MOD_ATOMIC/enabled
+livepatch: '$MOD_ATOMIC': initializing unpatching transition
+livepatch: '$MOD_ATOMIC': starting unpatching transition
+livepatch: '$MOD_ATOMIC': completing unpatching transition
+livepatch: '$MOD_ATOMIC': unpatching complete
+% rmmod $MOD_ATOMIC"
+
+
+# - load a livepatch with provides=1 that modifies meminfo_proc_show
+# - load another livepatch with provides=1 that modifies the same
+# function. The second one loads successfully because livepatches
+# with the same provides id replace each other.
+# - disable and unload the remaining livepatch
+
+start_test "function replace within same provides"
+
+load_lp $MOD_ATOMIC provides=1
+load_lp $MOD_PROVIDES provides=1
+
+check_sysfs_value "$MOD_PROVIDES" "enabled" "1"
+
+disable_lp $MOD_PROVIDES
+unload_lp $MOD_PROVIDES
+unload_lp $MOD_ATOMIC
+
+check_result "% insmod test_modules/$MOD_ATOMIC.ko provides=1
+livepatch: enabling patch '$MOD_ATOMIC'
+livepatch: '$MOD_ATOMIC': initializing patching transition
+livepatch: '$MOD_ATOMIC': starting patching transition
+livepatch: '$MOD_ATOMIC': completing patching transition
+livepatch: '$MOD_ATOMIC': patching complete
+% insmod test_modules/$MOD_PROVIDES.ko provides=1
+livepatch: enabling patch '$MOD_PROVIDES'
+livepatch: '$MOD_PROVIDES': initializing patching transition
+livepatch: '$MOD_PROVIDES': starting patching transition
+livepatch: '$MOD_PROVIDES': completing patching transition
+livepatch: '$MOD_PROVIDES': patching complete
+% echo 0 > $SYSFS_KLP_DIR/$MOD_PROVIDES/enabled
+livepatch: '$MOD_PROVIDES': initializing unpatching transition
+livepatch: '$MOD_PROVIDES': starting unpatching transition
+livepatch: '$MOD_PROVIDES': completing unpatching transition
+livepatch: '$MOD_PROVIDES': unpatching complete
+% rmmod $MOD_PROVIDES
+% rmmod $MOD_ATOMIC"
+
+
+# - load a livepatch with provides=1 that modifies meminfo_proc_show
+# - load another livepatch with provides=2 and obsoletes=[1] that
+# modifies the same function. The second one loads successfully
+# because the obsoletes list allows it to replace the first one
+# even though they have different provides ids.
+# - disable and unload the remaining livepatch
+
+start_test "function replace across provides with obsoletes"
+
+load_lp $MOD_ATOMIC provides=1
+load_lp $MOD_PROVIDES provides=2 obsoletes=1
+
+check_sysfs_value "$MOD_PROVIDES" "enabled" "1"
+
+disable_lp $MOD_PROVIDES
+unload_lp $MOD_PROVIDES
+unload_lp $MOD_ATOMIC
+
+check_result "% insmod test_modules/$MOD_ATOMIC.ko provides=1
+livepatch: enabling patch '$MOD_ATOMIC'
+livepatch: '$MOD_ATOMIC': initializing patching transition
+livepatch: '$MOD_ATOMIC': starting patching transition
+livepatch: '$MOD_ATOMIC': completing patching transition
+livepatch: '$MOD_ATOMIC': patching complete
+% insmod test_modules/$MOD_PROVIDES.ko provides=2 obsoletes=1
+livepatch: enabling patch '$MOD_PROVIDES'
+livepatch: '$MOD_PROVIDES': initializing patching transition
+livepatch: '$MOD_PROVIDES': starting patching transition
+livepatch: '$MOD_PROVIDES': completing patching transition
+livepatch: '$MOD_PROVIDES': patching complete
+% echo 0 > $SYSFS_KLP_DIR/$MOD_PROVIDES/enabled
+livepatch: '$MOD_PROVIDES': initializing unpatching transition
+livepatch: '$MOD_PROVIDES': starting unpatching transition
+livepatch: '$MOD_PROVIDES': completing unpatching transition
+livepatch: '$MOD_PROVIDES': unpatching complete
+% rmmod $MOD_PROVIDES
+% rmmod $MOD_ATOMIC"
+
exit 0
diff --git a/tools/testing/selftests/livepatch/test_modules/Makefile b/tools/testing/selftests/livepatch/test_modules/Makefile
index a4fe469a6501..13b1344b8e89 100644
--- a/tools/testing/selftests/livepatch/test_modules/Makefile
+++ b/tools/testing/selftests/livepatch/test_modules/Makefile
@@ -10,6 +10,7 @@ obj-m += test_klp_atomic_replace.o \
test_klp_livepatch.o \
test_klp_mod_patch.o \
test_klp_mod_target.o \
+ test_klp_provides.o \
test_klp_shadow_vars.o \
test_klp_state.o \
test_klp_state2.o \
diff --git a/tools/testing/selftests/livepatch/test_modules/test_klp_provides.c b/tools/testing/selftests/livepatch/test_modules/test_klp_provides.c
new file mode 100644
index 000000000000..9751a6f6c851
--- /dev/null
+++ b/tools/testing/selftests/livepatch/test_modules/test_klp_provides.c
@@ -0,0 +1,72 @@
+// SPDX-License-Identifier: GPL-2.0
+
+#include <linux/module.h>
+#include <linux/kernel.h>
+#include <linux/livepatch.h>
+
+#ifdef KLP_HAS_REPLACE
+static int replace;
+module_param(replace, int, 0644);
+MODULE_PARM_DESC(replace, "replace (default=0)");
+#else
+static unsigned int provides;
+module_param(provides, uint, 0644);
+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, 0644);
+MODULE_PARM_DESC(obsoletes, "obsoletes provides ids");
+#endif
+
+#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,
+};
+
+static int test_klp_provides_init(void)
+{
+#ifdef KLP_HAS_REPLACE
+ patch.replace = replace;
+#else
+ patch.provides = provides;
+ if (nr_obsoletes > 0) {
+ patch.obsoletes = obsoletes;
+ patch.nr_obsoletes = nr_obsoletes;
+ }
+#endif
+ return klp_enable_patch(&patch);
+}
+
+static void test_klp_provides_exit(void)
+{
+}
+
+module_init(test_klp_provides_init);
+module_exit(test_klp_provides_exit);
+MODULE_LICENSE("GPL");
+MODULE_INFO(livepatch, "Y");
+MODULE_DESCRIPTION("Livepatch test: provides conflict");
--
2.52.0
^ permalink raw reply related [flat|nested] 23+ messages in thread