On Tue, Apr 7, 2026 at 11:16 AM Yafang Shao <[email protected]> wrote:
>
> On Tue, Apr 7, 2026 at 10:54 AM Song Liu <[email protected]> wrote:
> >
> > On Mon, Apr 6, 2026 at 2:12 PM Joe Lawrence <[email protected]> wrote:
> > [...]
> > > > > > - The regular livepatches are cumulative, have the replace flag; and
> > > > > >   are replaceable.
> > > > > > - The occasional "off-band" livepatches do not have the replace 
> > > > > > flag,
> > > > > >   and are not replaceable.
> > > > > >
> > > > > > With this setup, for systems with off-band livepatches loaded, we 
> > > > > > can
> > > > > > still release a cumulative livepatch to replace the previous 
> > > > > > cumulative
> > > > > > livepatch. Is this the expected use case?
> > > > >
> > > > > That matches our expected use case.
> > > >
> > > > If we really want to serve use cases like this, I think we can introduce
> > > > some replace tag concept: Each livepatch will have a tag, u32 number.
> > > > Newly loaded livepatch will only replace existing livepatch with the
> > > > same tag. We can even reuse the existing "bool replace" in klp_patch,
> > > > and make it u32: replace=0 means no replace; replace > 0 are the
> > > > replace tag.
> > > >
> > > > For current users of cumulative patches, all the livepatch will have the
> > > > same tag, say 1. For your use case, you can assign each user a
> > > > unique tag. Then all these users can do atomic upgrades of their
> > > > own livepatches.
> > > >
> > > > We may also need to check whether two livepatches of different tags
> > > > touch the same kernel function. When that happens, the later
> > > > livepatch should fail to load.
>
> That sounds like a viable solution. I'll look into it and see how we
> can implement it.

Does the following change look good to you ?

Subject: [PATCH] livepatch: Support scoped atomic replace using replace tags

Extend the replace attribute from a boolean to a u32 to act as a replace
tag. This introduces the following semantics:

  replace = 0: Atomic replace is disabled. However, this patch remains
               eligible to be superseded by others.
  replace > 0: Enables tagged replace (default is 1). A newly loaded
               livepatch will only replace existing patches that share the
               same tag.

To maintain backward compatibility, a patch with replace == 0 does not
trigger an outgoing atomic replace, but remains eligible to be superseded
by any incoming patch with a valid replace tag.

Suggested-by: Song Liu <[email protected]>
Signed-off-by: Yafang Shao <[email protected]>
---
 .../livepatch/cumulative-patches.rst          | 20 +++++++-----
 Documentation/livepatch/livepatch.rst         | 31 +++++++++++++------
 include/linux/livepatch.h                     |  8 +++--
 kernel/livepatch/core.c                       |  4 +++
 scripts/livepatch/init.c                      |  6 +---
 scripts/livepatch/klp-build                   | 11 +++++--
 6 files changed, 53 insertions(+), 27 deletions(-)

diff --git a/Documentation/livepatch/cumulative-patches.rst
b/Documentation/livepatch/cumulative-patches.rst
index 1931f318976a..06e90dc5967c 100644
--- a/Documentation/livepatch/cumulative-patches.rst
+++ b/Documentation/livepatch/cumulative-patches.rst
@@ -12,23 +12,26 @@ 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.
+from older livepatches with a matching tag and replace them in one transition.

 Usage
 -----

-The atomic replace can be enabled by setting "replace" flag in struct
klp_patch,
-for example::
+he atomic replace can be enabled by setting a non-zero value to the "replace"
+attribute in ``struct klp_patch``. This value acts as a **replace tag**,
+defining the scope of the replacement.
+
+For example::

        static struct klp_patch patch = {
                .mod = THIS_MODULE,
                .objs = objs,
-               .replace = true,
+               .replace = 1,
        };

 All processes are then migrated to use the code only from the new patch.
-Once the transition is finished, all older patches are automatically
-disabled.
+Once the transition is finished, all older patches with the same replace tag
+are automatically disabled. Patches with different tags remain active.

 Ftrace handlers are transparently removed from functions that are no
 longer modified by the new cumulative patch.
@@ -62,9 +65,10 @@ Limitations:
 ------------

   - Once the operation finishes, there is no straightforward way
-    to reverse it and restore the replaced patches atomically.
+    to reverse it and restore the replaced patches (with the same tag)
+    atomically.

-    A good practice is to set .replace flag in any released livepatch.
+    A good practice is to set a consistent .replace tag in related livepatches.
     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..1fc1543a22c3 100644
--- a/Documentation/livepatch/livepatch.rst
+++ b/Documentation/livepatch/livepatch.rst
@@ -347,15 +347,28 @@ 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.
+All currently enabled patches may be superseded by a cumulative patch that
+has the ``.replace`` attribute enabled. The behavior of the replacement
+depends on the value assigned to the replace tag:
+
+replace = 0
+    Atomic replace is disabled. However, this patch remains eligible to be
+    superseded by others.
+
+replace > 0
+    Enables tagged atomic replace. Once the new patch is enabled and the
+    transition finishes, the livepatching core identifies all existing
+    patches that share the same replace tag.
+
+Once the transition is complete, all functions (``struct klp_func``)
+associated with the matching replaced patches are removed from the
+corresponding ``struct klp_ops``. If a function is no longer modified by
+the new patch and its ``func_stack`` list becomes empty, the ftrace
+handler is unregistered and the ``struct klp_ops`` is freed.
+
+Patches with a different replace tag 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..417c67a17b99 100644
--- a/include/linux/livepatch.h
+++ b/include/linux/livepatch.h
@@ -123,7 +123,11 @@ struct klp_state {
  * @mod:       reference to the live patch module
  * @objs:      object entries for kernel objects to be patched
  * @states:    system states that can get modified
- * @replace:   replace all actively used patches
+ * @replace:   replace tag:
+ *             = 0: Atomic replace is disabled; however, this patch remains
+ *                  eligible to be superseded by others.
+ *             > 0: Atomic replace is enabled. Only existing patches with a
+ *                  matching replace tag will be superseded.
  * @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 +141,7 @@ struct klp_patch {
        struct module *mod;
        struct klp_object *objs;
        struct klp_state *states;
-       bool replace;
+       unsigned int replace;

        /* internal */
        struct list_head list;
diff --git a/kernel/livepatch/core.c b/kernel/livepatch/core.c
index 28d15ba58a26..e4e5c03b0724 100644
--- a/kernel/livepatch/core.c
+++ b/kernel/livepatch/core.c
@@ -793,6 +793,8 @@ void klp_free_replaced_patches_async(struct
klp_patch *new_patch)
        klp_for_each_patch_safe(old_patch, tmp_patch) {
                if (old_patch == new_patch)
                        return;
+               if (old_patch->replace && old_patch->replace !=
new_patch->replace)
+                       continue;
                klp_free_patch_async(old_patch);
        }
 }
@@ -1194,6 +1196,8 @@ void klp_unpatch_replaced_patches(struct
klp_patch *new_patch)
        klp_for_each_patch(old_patch) {
                if (old_patch == new_patch)
                        return;
+               if (old_patch->replace && old_patch->replace !=
new_patch->replace)
+                       continue;

                old_patch->enabled = false;
                klp_unpatch_objects(old_patch);
diff --git a/scripts/livepatch/init.c b/scripts/livepatch/init.c
index f14d8c8fb35f..cd00e278a1d2 100644
--- a/scripts/livepatch/init.c
+++ b/scripts/livepatch/init.c
@@ -72,11 +72,7 @@ static int __init livepatch_mod_init(void)

        /* TODO patch->states */

-#ifdef KLP_NO_REPLACE
-       patch->replace = false;
-#else
-       patch->replace = true;
-#endif
+       patch->replace = KLP_REPLACE_TAG;

        return klp_enable_patch(patch);

diff --git a/scripts/livepatch/klp-build b/scripts/livepatch/klp-build
index 7b82c7503c2b..9f6a7673304f 100755
--- a/scripts/livepatch/klp-build
+++ b/scripts/livepatch/klp-build
@@ -118,6 +118,7 @@ Options:
    -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
+   -t, --replace-tag=<tag>     Set the atomic replace tag for this livepatch
    -v, --verbose               Pass V=1 to kernel/module builds

 Advanced Options:
@@ -142,8 +143,8 @@ process_args() {
        local long
        local args

-       short="hfj:o:vdS:T"
-       
long="help,show-first-changed,jobs:,output:,no-replace,verbose,debug,short-circuit:,keep-tmp"
+       short="hfj:o:t:vdS:T"
+       
long="help,show-first-changed,jobs:,output:,no-replace,replace-tag:,verbose,debug,short-circuit:,keep-tmp"

        args=$(getopt --options "$short" --longoptions "$long" -- "$@") || {
                echo; usage; exit
@@ -176,6 +177,10 @@ process_args() {
                                REPLACE=0
                                shift
                                ;;
+                       -t | --replace-tag)
+                               REPLACE="$2"
+                               shift 2
+                               ;;
                        -v | --verbose)
                                VERBOSE="V=1"
                                shift
@@ -759,7 +764,7 @@ build_patch_module() {

        cflags=("-ffunction-sections")
        cflags+=("-fdata-sections")
-       [[ $REPLACE -eq 0 ]] && cflags+=("-DKLP_NO_REPLACE")
+       cflags+=("-DKLP_REPLACE_TAG=$REPLACE")

        cmd=("make")
        cmd+=("$VERBOSE")


--
Regards
Yafang

Reply via email to