Live Patching
 help / color / mirror / Atom feed
* [RFC PATCH v4 0/4] livepatch: Introduce replace set support
@ 2026-08-04  6:50 Yafang Shao
  2026-08-04  6:50 ` [RFC PATCH v4 1/4] livepatch: Make klp_find_func() non static Yafang Shao
                   ` (3 more replies)
  0 siblings, 4 replies; 13+ messages in thread
From: Yafang Shao @ 2026-08-04  6:50 UTC (permalink / raw)
  To: jpoimboe, jikos, mbenes, pmladek, joe.lawrence, song
  Cc: live-patching, Yafang Shao

We previously proposed a BPF+livepatch method to enable rapid
experimentation with new kernel features without interrupting production
workloads:

  https://lore.kernel.org/live-patching/20260402092607.96430-1-laoar.shao@gmail.com/

In the resulting discussion, Song and Petr suggested adding a "replace set"
to support scenarios where specific livepatches can be selectively replaced
or skipped.

This patchset introduces 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

Additionally, this design deprecates the traditional non-atomic-replace
model. Previously, setting 'replace' to 0 was the only way to keep
certain livepatches persistent on the system, forcing developers to
disable atomic replacement entirely. With the introduction of replace set,
developers now have a selective option to keep specific livepatches
persistent while maintaining atomic replacement capabilities elsewhere.

At present, KLP state, shadow variables, and callbacks are not integrated
with the new replace_set mechanism in this patchset. Support for these
features is deferred until Petr's klp-state-transfer infrastructure is
completed and merged:

  https://github.com/pmladek/linux/tree/klp-state-transfer-v1-iter12

Future Work
----------
- Allow `provides` and `obsoletes` to be configured dynamically at
  module load time, rather than being fixed at build time.

v3->v4 (RFC):
- Allow a livepatch to replace livepatches with different provides IDs.
  Replace the single `replace_set` field with two separate fields,
  `provides` and `obsoletes`, for more flexible replacement semantics.
  (Petr, Joe)

v3: https://lore.kernel.org/live-patching/20260607131659.29281-1-laoar.shao@gmail.com/

v2->v3:
- Address the feedback from Sachiko AI
 - Fix the pre-existing NULL pointer dereference issue
 - Move klp_find_func into core.h
 - Don't deprecate stack_order completely

v2: https://lore.kernel.org/live-patching/20260529034542.68766-1-laoar.shao@gmail.com/

v1->v2:
- Incorporate feedback from Petr:
  - Initialize replace_set to 0 by default
  - Improve documentation
  - Enforce that livepatches in different replace_sets cannot use the same
    state->id.
  - Enforce that livepatches in different replace_sets cannot modify the
    same function.
  - Ensure consistent capitalization and naming usage of KLP_REPLACE_SET.
- Incorporate feedback from Sachiko AI:
  - Skip the klp_transition patch during klp_force_transition().

v1 (RFC): https://lore.kernel.org/live-patching/20260513143321.26185-1-laoar.shao@gmail.com/

Yafang Shao (4):
  livepatch: Make klp_find_func() non static
  livepatch: Call klp_init_patch_early() earlier
  livepatch: Implement replace set for scoped atomic replace
  livepatch: Deprecate stack_order

 .../ABI/removed/sysfs-kernel-livepatch        |  16 +++
 .../ABI/testing/sysfs-kernel-livepatch        |  27 ++---
 .../livepatch/cumulative-patches.rst          |  93 +++++++++++-----
 Documentation/livepatch/livepatch.rst         |  23 ++--
 include/linux/livepatch.h                     |   7 +-
 kernel/livepatch/core.c                       | 101 +++++++++++-------
 kernel/livepatch/core.h                       |   2 +
 kernel/livepatch/state.c                      |  56 ++++++++--
 kernel/livepatch/transition.c                 |  11 +-
 scripts/livepatch/init.c                      |  71 +++++++++++-
 scripts/livepatch/klp-build                   |  87 +++++++++++++--
 11 files changed, 380 insertions(+), 114 deletions(-)
 create mode 100644 Documentation/ABI/removed/sysfs-kernel-livepatch

-- 
2.52.0


^ permalink raw reply	[flat|nested] 13+ messages in thread

* [RFC PATCH v4 1/4] livepatch: Make klp_find_func() non static
  2026-08-04  6:50 [RFC PATCH v4 0/4] livepatch: Introduce replace set support Yafang Shao
@ 2026-08-04  6:50 ` Yafang Shao
  2026-08-04  6:56   ` sashiko-bot
  2026-08-04  6:50 ` [RFC PATCH v4 2/4] livepatch: Call klp_init_patch_early() earlier Yafang Shao
                   ` (2 subsequent siblings)
  3 siblings, 1 reply; 13+ messages in thread
From: Yafang Shao @ 2026-08-04  6:50 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>
---
 kernel/livepatch/core.c | 16 ++++++++--------
 kernel/livepatch/core.h |  1 +
 2 files changed, 9 insertions(+), 8 deletions(-)

diff --git a/kernel/livepatch/core.c b/kernel/livepatch/core.c
index a240d1144e89..0ad778d1006f 100644
--- a/kernel/livepatch/core.c
+++ b/kernel/livepatch/core.c
@@ -82,20 +82,20 @@ 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))) {
+		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 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] 13+ messages in thread

* [RFC PATCH v4 2/4] livepatch: Call klp_init_patch_early() earlier
  2026-08-04  6:50 [RFC PATCH v4 0/4] livepatch: Introduce replace set support Yafang Shao
  2026-08-04  6:50 ` [RFC PATCH v4 1/4] livepatch: Make klp_find_func() non static Yafang Shao
@ 2026-08-04  6:50 ` Yafang Shao
  2026-08-04  7:03   ` sashiko-bot
  2026-08-04  6:50 ` [RFC PATCH v4 3/4] livepatch: Implement replace set for scoped atomic replace Yafang Shao
  2026-08-04  6:50 ` [RFC PATCH v4 4/4] livepatch: Deprecate stack_order Yafang Shao
  3 siblings, 1 reply; 13+ messages in thread
From: Yafang Shao @ 2026-08-04  6:50 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>
---
 kernel/livepatch/core.c | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/kernel/livepatch/core.c b/kernel/livepatch/core.c
index 0ad778d1006f..5ec30909d382 100644
--- a/kernel/livepatch/core.c
+++ b/kernel/livepatch/core.c
@@ -1145,6 +1145,8 @@ int klp_enable_patch(struct klp_patch *patch)
 
 	mutex_lock(&klp_mutex);
 
+	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);
@@ -1157,8 +1159,6 @@ int klp_enable_patch(struct klp_patch *patch)
 		return -ENODEV;
 	}
 
-	klp_init_patch_early(patch);
-
 	ret = klp_init_patch(patch);
 	if (ret)
 		goto err;
-- 
2.52.0


^ permalink raw reply related	[flat|nested] 13+ messages in thread

* [RFC PATCH v4 3/4] livepatch: Implement replace set for scoped atomic replace
  2026-08-04  6:50 [RFC PATCH v4 0/4] livepatch: Introduce replace set support Yafang Shao
  2026-08-04  6:50 ` [RFC PATCH v4 1/4] livepatch: Make klp_find_func() non static Yafang Shao
  2026-08-04  6:50 ` [RFC PATCH v4 2/4] livepatch: Call klp_init_patch_early() earlier Yafang Shao
@ 2026-08-04  6:50 ` Yafang Shao
  2026-08-04  7:03   ` sashiko-bot
  2026-08-04 23:05   ` Song Liu
  2026-08-04  6:50 ` [RFC PATCH v4 4/4] livepatch: Deprecate stack_order Yafang Shao
  3 siblings, 2 replies; 13+ messages in thread
From: Yafang Shao @ 2026-08-04  6:50 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 old patch should be replaced
  by a new 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>
---
 .../ABI/removed/sysfs-kernel-livepatch        |  7 ++
 .../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                      | 56 +++++++++--
 kernel/livepatch/transition.c                 | 11 ++-
 scripts/livepatch/init.c                      | 71 +++++++++++++-
 scripts/livepatch/klp-build                   | 87 +++++++++++++++--
 11 files changed, 368 insertions(+), 79 deletions(-)
 create mode 100644 Documentation/ABI/removed/sysfs-kernel-livepatch

diff --git a/Documentation/ABI/removed/sysfs-kernel-livepatch b/Documentation/ABI/removed/sysfs-kernel-livepatch
new file mode 100644
index 000000000000..87d57ba27326
--- /dev/null
+++ b/Documentation/ABI/removed/sysfs-kernel-livepatch
@@ -0,0 +1,7 @@
+What:		/sys/kernel/livepatch/<patch>/replace
+Date:		Jun 2024
+KernelVersion:	6.11.0
+Contact:	live-patching@vger.kernel.org
+Description:
+		An attribute which indicates whether the patch supports
+		atomic-replace.
diff --git a/Documentation/ABI/testing/sysfs-kernel-livepatch b/Documentation/ABI/testing/sysfs-kernel-livepatch
index 3c3f36b32b57..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 5ec30909d382..bd37ed99814e 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 old_patch should be replaced by new_patch:
+ * 1. Same provides ID
+ * 2. Old provides ID is in new patch's obsoletes list
+ */
+bool klp_patch_replaceable(struct klp_patch *old_patch, struct klp_patch *new_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_replaceable(old_patch, 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_replaceable(old_patch, new_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);
@@ -1204,6 +1251,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_replaceable(old_patch, new_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..558ce7b70754 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_replaceable(struct klp_patch *old_patch, struct klp_patch *new_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..f388c9ba20f7 100644
--- a/kernel/livepatch/state.c
+++ b/kernel/livepatch/state.c
@@ -85,24 +85,58 @@ 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_replaceable(old_patch, 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 with the livepatch
+ * with another "provides".
  */
+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;
+
+	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 +144,15 @@ 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_patch_replaceable(old_patch, patch))
+			continue;
+
+		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..15dbc13d0341 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_replaceable(patch, klp_transition_patch))
 				patch->forced = true;
 		}
 	}
diff --git a/scripts/livepatch/init.c b/scripts/livepatch/init.c
index f14d8c8fb35f..a875dd228cea 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;
 
@@ -72,10 +73,73 @@ 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: "0,1,2" or "0" or empty)
+	 * and convert to unsigned int array for patch->obsoletes
+	 *
+	 * Note: KLP_OBSOLETES is always defined and includes at least the
+	 * provides ID, ensuring the patch replaces old patches with same 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
+	/*
+	 * This should never happen as obsoletes always includes provides ID.
+	 * But keep this as a safety fallback.
+	 */
+	patch->obsoletes = NULL;
+	patch->nr_obsoletes = 0;
 #endif
 
 	return klp_enable_patch(patch);
@@ -96,6 +160,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..974578328733 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,26 @@ process_args() {
 				NAME="$(module_name_string "$NAME")"
 				shift 2
 				;;
-			--no-replace)
-				REPLACE=0
-				shift
+			-p | --provides)
+				PROVIDES="$2"
+				shift 2
+				;;
+			-r | --obsoletes)
+				OBSOLETES="$2"
+				local obs_val="$2"
+				[[ "$obs_val" != \[*\] ]] && die "obsoletes must be enclosed in brackets, e.g., '[0,1,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 +253,47 @@ process_args() {
 		exit 1
 	fi
 
+	# Always add provides ID to obsoletes (even if provides=0)
+	# This ensures the new patch replaces old patches with the same provides ID
+	if [[ -n "$OBSOLETES" && "$OBSOLETES" != "[]" ]]; then
+		local obsoletes_clean="${OBSOLETES//[\[\] ]/}"
+		local IFS=','
+		local -a obs_array=()
+		local obs_id
+		local provides_added=0
+		local already_exists
+
+		# Parse existing obsoletes values and remove duplicates
+		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")
+					if [[ "$obs_id" -eq "$PROVIDES" ]]; then
+						provides_added=1
+					fi
+				fi
+			fi
+		done
+
+		if [[ "$provides_added" -eq 0 ]]; then
+			obs_array+=("$PROVIDES")
+		fi
+
+		local IFS=','
+		OBSOLETES="[${obs_array[*]}]"
+		unset IFS
+	elif [[ -z "$OBSOLETES" || "$OBSOLETES" == "[]" ]]; then
+		OBSOLETES="[$PROVIDES]"
+	fi
+
 	KEEP_TMP="$keep_tmp"
 	PATCHES=("$@")
 
@@ -847,7 +906,17 @@ build_patch_module() {
 
 	cflags=("-ffunction-sections")
 	cflags+=("-fdata-sections")
-	[[ $REPLACE -eq 0 ]] && cflags+=("-DKLP_NO_REPLACE")
+	cflags+=("-DKLP_PROVIDES=$PROVIDES")
+
+	# Process OBSOLETES: remove brackets and spaces, convert to comma-separated string
+	# Input: "[0, 1, 2]" or "[]" or "" -> Output: "0,1,2" or empty
+	if [[ -n "$OBSOLETES" && "$OBSOLETES" != "[]" ]]; then
+		# Remove '[', ']', and 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] 13+ messages in thread

* [RFC PATCH v4 4/4] livepatch: Deprecate stack_order
  2026-08-04  6:50 [RFC PATCH v4 0/4] livepatch: Introduce replace set support Yafang Shao
                   ` (2 preceding siblings ...)
  2026-08-04  6:50 ` [RFC PATCH v4 3/4] livepatch: Implement replace set for scoped atomic replace Yafang Shao
@ 2026-08-04  6:50 ` Yafang Shao
  3 siblings, 0 replies; 13+ messages in thread
From: Yafang Shao @ 2026-08-04  6:50 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>
---
 .../ABI/removed/sysfs-kernel-livepatch        |  9 +++++++
 .../ABI/testing/sysfs-kernel-livepatch        |  9 -------
 kernel/livepatch/core.c                       | 24 -------------------
 3 files changed, 9 insertions(+), 33 deletions(-)

diff --git a/Documentation/ABI/removed/sysfs-kernel-livepatch b/Documentation/ABI/removed/sysfs-kernel-livepatch
index 87d57ba27326..fb6ff9c3b43e 100644
--- a/Documentation/ABI/removed/sysfs-kernel-livepatch
+++ b/Documentation/ABI/removed/sysfs-kernel-livepatch
@@ -5,3 +5,12 @@ Contact:	live-patching@vger.kernel.org
 Description:
 		An attribute which indicates whether the patch supports
 		atomic-replace.
+
+What:          /sys/kernel/livepatch/<patch>/stack_order
+Date:          Jan 2025
+KernelVersion: 6.14.0
+Description:
+		This attribute specifies the sequence in which live patch modules
+		are applied to the system. If multiple live patches modify the same
+		function, the implementation with the biggest 'stack_order' number
+		is used, unless a transition is currently in progress.
diff --git a/Documentation/ABI/testing/sysfs-kernel-livepatch b/Documentation/ABI/testing/sysfs-kernel-livepatch
index 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 bd37ed99814e..5cadced61d2d 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] 13+ messages in thread

* Re: [RFC PATCH v4 1/4] livepatch: Make klp_find_func() non static
  2026-08-04  6:50 ` [RFC PATCH v4 1/4] livepatch: Make klp_find_func() non static Yafang Shao
@ 2026-08-04  6:56   ` sashiko-bot
  2026-08-04  8:04     ` Yafang Shao
  0 siblings, 1 reply; 13+ messages in thread
From: sashiko-bot @ 2026-08-04  6:56 UTC (permalink / raw)
  To: jpoimboe, joe.lawrence, song, mbenes, jikos, pmladek
  Cc: Yafang Shao, live-patching

Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
- [Medium] klp_find_func() incorrectly returns the search parameter instead of the matched object from the list.
--

--- Patch [1]: [RFC PATCH v4 1/4] livepatch: Make klp_find_func() non static ---
commit 59916304ff12f7685cb556f7a7c1f4a1c5a25b00
Author: Yafang Shao <laoar.shao@gmail.com>

livepatch: Make klp_find_func() non static

This commit makes klp_find_func() non static so it can be used in other
source files by an upcoming patch. It also renames the old_func parameter
to func to better reflect its generic purpose.

> diff --git a/kernel/livepatch/core.c b/kernel/livepatch/core.c
> index 28d15ba58a26d..e07c41a0f3ac8 100644
> --- a/kernel/livepatch/core.c
> +++ b/kernel/livepatch/core.c
[ ... ]
> -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))) {
> +		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 func;

[Severity: Medium]
Will this return the correct object? It looks like this now returns the
search parameter (func) instead of the newly introduced list iterator
variable (obj_func).

If a caller relies on the returned pointer to access the matched object
from the list, they will receive the search parameter instead. This could
lead to incorrect state manipulation or memory corruption for new users
of this exported function.

Should this return obj_func instead?

>  		}
>  	}

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20260804065010.44922-1-laoar.shao@gmail.com?part=1

^ permalink raw reply	[flat|nested] 13+ messages in thread

* Re: [RFC PATCH v4 3/4] livepatch: Implement replace set for scoped atomic replace
  2026-08-04  6:50 ` [RFC PATCH v4 3/4] livepatch: Implement replace set for scoped atomic replace Yafang Shao
@ 2026-08-04  7:03   ` sashiko-bot
  2026-08-04  7:15     ` Yafang Shao
  2026-08-04 23:05   ` Song Liu
  1 sibling, 1 reply; 13+ messages in thread
From: sashiko-bot @ 2026-08-04  7:03 UTC (permalink / raw)
  To: pmladek, jpoimboe, joe.lawrence, jikos, mbenes, song
  Cc: live-patching, Yafang Shao

Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
- [Medium] Dynamically allocated `funcs` arrays are leaked on KLP_OBSOLETES parsing failure.
--

--- Patch [3]: [RFC PATCH v4 3/4] livepatch: Implement replace set for scoped atomic replace ---
commit d186609c1a3dbe210857d0040f657fa918504faf
Author: Yafang Shao <laoar.shao@gmail.com>
Subject: livepatch: Implement replace set for scoped atomic replace

This commit introduces a more flexible atomic replace model for livepatches
using provides and obsoletes fields. A new patch will replace any existing
patch with a matching provides ID or any ID listed in its obsoletes array.
The klp-build script and init code are updated to parse and validate these
new fields.

> diff --git a/scripts/livepatch/init.c b/scripts/livepatch/init.c
> --- a/scripts/livepatch/init.c
> +++ b/scripts/livepatch/init.c
[ ... ]
> @@ -72,10 +73,73 @@ static int __init livepatch_mod_init(void)
[ ... ]
> +#ifdef KLP_OBSOLETES
> +	/*
> +	 * Parse KLP_OBSOLETES string (format: "0,1,2" or "0" or empty)
> +	 * and convert to unsigned int array for patch->obsoletes
> +	 *
> +	 * Note: KLP_OBSOLETES is always defined and includes at least the
> +	 * provides ID, ensuring the patch replaces old patches with same 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;
> +		}

[Severity: Medium]
Does this code leak the funcs arrays on error?

Earlier in livepatch_mod_init(), the funcs arrays are dynamically allocated 
for each object in a loop:

	funcs = kzalloc(sizeof(struct klp_func) * (nr_funcs + 1), GFP_KERNEL);
	...
	obj->funcs = funcs;

If KLP_OBSOLETES parsing fails during string duplication, array allocation, 
or integer conversion, the error paths jump to err_free_objs. It appears 
that the cleanup label only frees the objs array itself but leaves the 
individual obj->funcs arrays allocated:

err_free_objs:
	kfree(objs);
err_free_patch:
	kfree(patch);

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20260804065010.44922-1-laoar.shao@gmail.com?part=3

^ permalink raw reply	[flat|nested] 13+ messages in thread

* Re: [RFC PATCH v4 2/4] livepatch: Call klp_init_patch_early() earlier
  2026-08-04  6:50 ` [RFC PATCH v4 2/4] livepatch: Call klp_init_patch_early() earlier Yafang Shao
@ 2026-08-04  7:03   ` sashiko-bot
  2026-08-04  8:00     ` Yafang Shao
  0 siblings, 1 reply; 13+ messages in thread
From: sashiko-bot @ 2026-08-04  7:03 UTC (permalink / raw)
  To: jpoimboe, jikos, mbenes, pmladek, joe.lawrence, song
  Cc: live-patching, Yafang Shao

Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
- [Medium] Moving klp_init_patch_early() before early failure checks violates the kobject API lifecycle and introduces missing cleanup on error paths.
--

--- Patch [2]: [RFC PATCH v4 2/4] livepatch: Call klp_init_patch_early() earlier ---
commit 6b8df20b82104d4c61053ebea3721f1d77891456
Author: Yafang Shao <laoar.shao@gmail.com>

livepatch: Call klp_init_patch_early() earlier

This commit moves the invocation of klp_init_patch_early() earlier in the
livepatch initialization process. This prepares the code for an upcoming
patch by avoiding mixing iteration variants later in the sequence.

> diff --git a/kernel/livepatch/core.c b/kernel/livepatch/core.c
> index e07c41a0f3ac8..c98cbcf59566d 100644
> --- a/kernel/livepatch/core.c
> +++ b/kernel/livepatch/core.c
> @@ -1135,6 +1135,8 @@ int klp_enable_patch(struct klp_patch *patch)
>  
>  	mutex_lock(&klp_mutex);
>  
> +	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);
> @@ -1147,8 +1149,6 @@ int klp_enable_patch(struct klp_patch *patch)
>  		return -ENODEV;
>  	}

[Severity: Medium]
Since klp_init_patch_early() initializes kobjects for the patch, does
returning directly on compatibility or try_module_get() failures leak
these resources?

The kobject API requires that once kobject_init() is called, kobject_put()
must be used to release the object. Because klp_init_patch_early() is now
called before these checks, if we hit the early return paths in
klp_enable_patch(), we bypass cleaning up the initialized kobjects.

Also, would simply changing these early returns to jump to the err label
cause issues with klp_free_patch_finish()?

kernel/livepatch/core.c:klp_enable_patch() {
    ...
err:
	klp_free_patch_start(patch);
	mutex_unlock(&klp_mutex);
	klp_free_patch_finish(patch);
	return ret;
}

It looks like klp_free_patch_finish() calls module_put(patch->mod)
unconditionally. If try_module_get() failed, jumping to the err label
might underflow the module reference count.

[ ... ]

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20260804065010.44922-1-laoar.shao@gmail.com?part=2

^ permalink raw reply	[flat|nested] 13+ messages in thread

* Re: [RFC PATCH v4 3/4] livepatch: Implement replace set for scoped atomic replace
  2026-08-04  7:03   ` sashiko-bot
@ 2026-08-04  7:15     ` Yafang Shao
  0 siblings, 0 replies; 13+ messages in thread
From: Yafang Shao @ 2026-08-04  7:15 UTC (permalink / raw)
  To: sashiko-reviews
  Cc: pmladek, jpoimboe, joe.lawrence, jikos, mbenes, song,
	live-patching

On Tue, Aug 4, 2026 at 3:03 PM <sashiko-bot@kernel.org> wrote:
>
> Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
> - [Medium] Dynamically allocated `funcs` arrays are leaked on KLP_OBSOLETES parsing failure.
> --
>
> --- Patch [3]: [RFC PATCH v4 3/4] livepatch: Implement replace set for scoped atomic replace ---
> commit d186609c1a3dbe210857d0040f657fa918504faf
> Author: Yafang Shao <laoar.shao@gmail.com>
> Subject: livepatch: Implement replace set for scoped atomic replace
>
> This commit introduces a more flexible atomic replace model for livepatches
> using provides and obsoletes fields. A new patch will replace any existing
> patch with a matching provides ID or any ID listed in its obsoletes array.
> The klp-build script and init code are updated to parse and validate these
> new fields.
>
> > diff --git a/scripts/livepatch/init.c b/scripts/livepatch/init.c
> > --- a/scripts/livepatch/init.c
> > +++ b/scripts/livepatch/init.c
> [ ... ]
> > @@ -72,10 +73,73 @@ static int __init livepatch_mod_init(void)
> [ ... ]
> > +#ifdef KLP_OBSOLETES
> > +     /*
> > +      * Parse KLP_OBSOLETES string (format: "0,1,2" or "0" or empty)
> > +      * and convert to unsigned int array for patch->obsoletes
> > +      *
> > +      * Note: KLP_OBSOLETES is always defined and includes at least the
> > +      * provides ID, ensuring the patch replaces old patches with same 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;
> > +             }
>
> [Severity: Medium]
> Does this code leak the funcs arrays on error?
>
> Earlier in livepatch_mod_init(), the funcs arrays are dynamically allocated
> for each object in a loop:
>
>         funcs = kzalloc(sizeof(struct klp_func) * (nr_funcs + 1), GFP_KERNEL);
>         ...
>         obj->funcs = funcs;
>
> If KLP_OBSOLETES parsing fails during string duplication, array allocation,
> or integer conversion, the error paths jump to err_free_objs. It appears
> that the cleanup label only frees the objs array itself but leaves the
> individual obj->funcs arrays allocated:
>
> err_free_objs:
>         kfree(objs);
> err_free_patch:
>         kfree(patch);

You are right.
Furthermore, there is a pre-existing bug in the error path that should
be fixed as well:

--- a/scripts/livepatch/init.c
+++ b/scripts/livepatch/init.c
@@ -52,7 +52,7 @@ static int __init livepatch_mod_init(void)
                if (!funcs) {
                        ret = -ENOMEM;
                        for (int j = 0; j < i; j++)
-                               kfree(objs[i].funcs);
+                               kfree(objs[j].funcs);
                        goto err_free_objs;
                }

-- 
Regards
Yafang

^ permalink raw reply	[flat|nested] 13+ messages in thread

* Re: [RFC PATCH v4 2/4] livepatch: Call klp_init_patch_early() earlier
  2026-08-04  7:03   ` sashiko-bot
@ 2026-08-04  8:00     ` Yafang Shao
  0 siblings, 0 replies; 13+ messages in thread
From: Yafang Shao @ 2026-08-04  8:00 UTC (permalink / raw)
  To: sashiko-reviews
  Cc: jpoimboe, jikos, mbenes, pmladek, joe.lawrence, song,
	live-patching

On Tue, Aug 4, 2026 at 3:03 PM <sashiko-bot@kernel.org> wrote:
>
> Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
> - [Medium] Moving klp_init_patch_early() before early failure checks violates the kobject API lifecycle and introduces missing cleanup on error paths.
> --
>
> --- Patch [2]: [RFC PATCH v4 2/4] livepatch: Call klp_init_patch_early() earlier ---
> commit 6b8df20b82104d4c61053ebea3721f1d77891456
> Author: Yafang Shao <laoar.shao@gmail.com>
>
> livepatch: Call klp_init_patch_early() earlier
>
> This commit moves the invocation of klp_init_patch_early() earlier in the
> livepatch initialization process. This prepares the code for an upcoming
> patch by avoiding mixing iteration variants later in the sequence.
>
> > diff --git a/kernel/livepatch/core.c b/kernel/livepatch/core.c
> > index e07c41a0f3ac8..c98cbcf59566d 100644
> > --- a/kernel/livepatch/core.c
> > +++ b/kernel/livepatch/core.c
> > @@ -1135,6 +1135,8 @@ int klp_enable_patch(struct klp_patch *patch)
> >
> >       mutex_lock(&klp_mutex);
> >
> > +     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);
> > @@ -1147,8 +1149,6 @@ int klp_enable_patch(struct klp_patch *patch)
> >               return -ENODEV;
> >       }
>
> [Severity: Medium]
> Since klp_init_patch_early() initializes kobjects for the patch, does
> returning directly on compatibility or try_module_get() failures leak
> these resources?
>
> The kobject API requires that once kobject_init() is called, kobject_put()
> must be used to release the object. Because klp_init_patch_early() is now
> called before these checks, if we hit the early return paths in
> klp_enable_patch(), we bypass cleaning up the initialized kobjects.
>
> Also, would simply changing these early returns to jump to the err label
> cause issues with klp_free_patch_finish()?
>
> kernel/livepatch/core.c:klp_enable_patch() {
>     ...
> err:
>         klp_free_patch_start(patch);
>         mutex_unlock(&klp_mutex);
>         klp_free_patch_finish(patch);
>         return ret;
> }
>
> It looks like klp_free_patch_finish() calls module_put(patch->mod)
> unconditionally. If try_module_get() failed, jumping to the err label
> might underflow the module reference count.

Good point. It seems we need the following additional change:

--- a/kernel/livepatch/core.c
+++ b/kernel/livepatch/core.c
@@ -1168,19 +1168,20 @@ int klp_enable_patch(struct klp_patch *patch)

        mutex_lock(&klp_mutex);

+       if (!try_module_get(patch->mod)) {
+               mutex_unlock(&klp_mutex);
+               return -ENODEV;
+       }
+
        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);
-               mutex_unlock(&klp_mutex);
-               return -EINVAL;
+               ret = -EINVAL;
+               goto err;
        }

-       if (!try_module_get(patch->mod)) {
-               mutex_unlock(&klp_mutex);
-               return -ENODEV;
-       }

        ret = klp_init_patch(patch);
        if (ret)

-- 
Regards
Yafang

^ permalink raw reply	[flat|nested] 13+ messages in thread

* Re: [RFC PATCH v4 1/4] livepatch: Make klp_find_func() non static
  2026-08-04  6:56   ` sashiko-bot
@ 2026-08-04  8:04     ` Yafang Shao
  0 siblings, 0 replies; 13+ messages in thread
From: Yafang Shao @ 2026-08-04  8:04 UTC (permalink / raw)
  To: sashiko-reviews
  Cc: jpoimboe, joe.lawrence, song, mbenes, jikos, pmladek,
	live-patching

On Tue, Aug 4, 2026 at 2:56 PM <sashiko-bot@kernel.org> wrote:
>
> Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
> - [Medium] klp_find_func() incorrectly returns the search parameter instead of the matched object from the list.
> --
>
> --- Patch [1]: [RFC PATCH v4 1/4] livepatch: Make klp_find_func() non static ---
> commit 59916304ff12f7685cb556f7a7c1f4a1c5a25b00
> Author: Yafang Shao <laoar.shao@gmail.com>
>
> livepatch: Make klp_find_func() non static
>
> This commit makes klp_find_func() non static so it can be used in other
> source files by an upcoming patch. It also renames the old_func parameter
> to func to better reflect its generic purpose.
>
> > diff --git a/kernel/livepatch/core.c b/kernel/livepatch/core.c
> > index 28d15ba58a26d..e07c41a0f3ac8 100644
> > --- a/kernel/livepatch/core.c
> > +++ b/kernel/livepatch/core.c
> [ ... ]
> > -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))) {
> > +             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 func;
>
> [Severity: Medium]
> Will this return the correct object? It looks like this now returns the
> search parameter (func) instead of the newly introduced list iterator
> variable (obj_func).
>
> If a caller relies on the returned pointer to access the matched object
> from the list, they will receive the search parameter instead. This could
> lead to incorrect state manipulation or memory corruption for new users
> of this exported function.
>
> Should this return obj_func instead?

Good catch. It should return obj_func instead.

-- 
Regards
Yafang

^ permalink raw reply	[flat|nested] 13+ messages in thread

* Re: [RFC PATCH v4 3/4] livepatch: Implement replace set for scoped atomic replace
  2026-08-04  6:50 ` [RFC PATCH v4 3/4] livepatch: Implement replace set for scoped atomic replace Yafang Shao
  2026-08-04  7:03   ` sashiko-bot
@ 2026-08-04 23:05   ` Song Liu
  2026-08-05  2:54     ` Yafang Shao
  1 sibling, 1 reply; 13+ messages in thread
From: Song Liu @ 2026-08-04 23:05 UTC (permalink / raw)
  To: Yafang Shao; +Cc: jpoimboe, jikos, mbenes, pmladek, joe.lawrence, live-patching

On Mon, Aug 3, 2026 at 11:50 PM Yafang Shao <laoar.shao@gmail.com> wrote:
[...]
>  /*
> - * 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 with the livepatch
> + * with another "provides".
>   */

The comment above is not really accurate. This function doesn't
really check "provides" or "obsoletes". Also, as long as the old
patch's "provides" is in the new patches "obsoletes" or "provides",
this replace is allowed (the comment doesn't mention "obsoletes". )

> +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;
[...]
>
> +       # Always add provides ID to obsoletes (even if provides=0)
> +       # This ensures the new patch replaces old patches with the same provides ID

Do we really need th toolchain to enforce this? AFAICT, the kernel already
guranteed "provides" means "obsoletes".

Also, it will be great if we can add some basic tests for this. We are
working on some new test framework for klp-build toolchain. But we
don't have to wait for that. It is always good to add some tests for
a major feature change.

Thanks,
Song

> +       if [[ -n "$OBSOLETES" && "$OBSOLETES" != "[]" ]]; then
> +               local obsoletes_clean="${OBSOLETES//[\[\] ]/}"
> +               local IFS=','
> +               local -a obs_array=()
> +               local obs_id
> +               local provides_added=0
> +               local already_exists

[...]

^ permalink raw reply	[flat|nested] 13+ messages in thread

* Re: [RFC PATCH v4 3/4] livepatch: Implement replace set for scoped atomic replace
  2026-08-04 23:05   ` Song Liu
@ 2026-08-05  2:54     ` Yafang Shao
  0 siblings, 0 replies; 13+ messages in thread
From: Yafang Shao @ 2026-08-05  2:54 UTC (permalink / raw)
  To: Song Liu; +Cc: jpoimboe, jikos, mbenes, pmladek, joe.lawrence, live-patching

On Wed, Aug 5, 2026 at 7:06 AM Song Liu <song@kernel.org> wrote:
>
> On Mon, Aug 3, 2026 at 11:50 PM Yafang Shao <laoar.shao@gmail.com> wrote:
> [...]
> >  /*
> > - * 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 with the livepatch
> > + * with another "provides".
> >   */
>
> The comment above is not really accurate. This function doesn't
> really check "provides" or "obsoletes". Also, as long as the old
> patch's "provides" is in the new patches "obsoletes" or "provides",
> this replace is allowed (the comment doesn't mention "obsoletes". )

This is already handled by klp_patch_replaceable() in
klp_is_patch_compatible(). However, it would be clearer to move the
klp_patch_replaceable() check into klp_has_function_conflict() and
update the comments accordingly.

>
> > +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;
> [...]
> >
> > +       # Always add provides ID to obsoletes (even if provides=0)
> > +       # This ensures the new patch replaces old patches with the same provides ID
>
> Do we really need th toolchain to enforce this? AFAICT, the kernel already
> guranteed "provides" means "obsoletes".

Dropping this enforcement simplifies the code logic. I will remove it
in the next version.

>
> Also, it will be great if we can add some basic tests for this. We are
> working on some new test framework for klp-build toolchain. But we
> don't have to wait for that. It is always good to add some tests for
> a major feature change.

Sure. I will add self tests in the next version.

-- 
Regards
Yafang

^ permalink raw reply	[flat|nested] 13+ messages in thread

end of thread, other threads:[~2026-08-05  2:55 UTC | newest]

Thread overview: 13+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-08-04  6:50 [RFC PATCH v4 0/4] livepatch: Introduce replace set support Yafang Shao
2026-08-04  6:50 ` [RFC PATCH v4 1/4] livepatch: Make klp_find_func() non static Yafang Shao
2026-08-04  6:56   ` sashiko-bot
2026-08-04  8:04     ` Yafang Shao
2026-08-04  6:50 ` [RFC PATCH v4 2/4] livepatch: Call klp_init_patch_early() earlier Yafang Shao
2026-08-04  7:03   ` sashiko-bot
2026-08-04  8:00     ` Yafang Shao
2026-08-04  6:50 ` [RFC PATCH v4 3/4] livepatch: Implement replace set for scoped atomic replace Yafang Shao
2026-08-04  7:03   ` sashiko-bot
2026-08-04  7:15     ` Yafang Shao
2026-08-04 23:05   ` Song Liu
2026-08-05  2:54     ` Yafang Shao
2026-08-04  6:50 ` [RFC PATCH v4 4/4] livepatch: Deprecate stack_order Yafang Shao

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox