From: Yafang Shao <laoar.shao@gmail.com>
To: jpoimboe@kernel.org, jikos@kernel.org, mbenes@suse.cz,
pmladek@suse.com, joe.lawrence@redhat.com, song@kernel.org
Cc: live-patching@vger.kernel.org, Yafang Shao <laoar.shao@gmail.com>
Subject: [RFC PATCH v4 3/4] livepatch: Implement replace set for scoped atomic replace
Date: Tue, 4 Aug 2026 14:50:09 +0800 [thread overview]
Message-ID: <20260804065010.44922-4-laoar.shao@gmail.com> (raw)
In-Reply-To: <20260804065010.44922-1-laoar.shao@gmail.com>
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
next prev parent reply other threads:[~2026-08-04 6:50 UTC|newest]
Thread overview: 13+ messages / expand[flat|nested] mbox.gz Atom feed top
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 ` Yafang Shao [this message]
2026-08-04 7:03 ` [RFC PATCH v4 3/4] livepatch: Implement replace set for scoped atomic replace 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
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=20260804065010.44922-4-laoar.shao@gmail.com \
--to=laoar.shao@gmail.com \
--cc=jikos@kernel.org \
--cc=joe.lawrence@redhat.com \
--cc=jpoimboe@kernel.org \
--cc=live-patching@vger.kernel.org \
--cc=mbenes@suse.cz \
--cc=pmladek@suse.com \
--cc=song@kernel.org \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox