Convert the replace attribute from a boolean to a u32 to function as a
"replace set." A newly loaded livepatch will now atomically replace
existing patches that belong to the same set.

This change currently supports function replacement only; support for
state and shadow variables will be introduced in subsequent patches.

Suggested-by: Song Liu <[email protected]>
Signed-off-by: Yafang Shao <[email protected]>
---
 .../livepatch/cumulative-patches.rst          | 17 ++++++++------
 Documentation/livepatch/livepatch.rst         | 23 +++++++++++--------
 include/linux/livepatch.h                     |  5 ++--
 kernel/livepatch/core.c                       | 16 ++++++++-----
 kernel/livepatch/state.c                      | 17 +++++++-------
 kernel/livepatch/transition.c                 | 10 ++++----
 scripts/livepatch/init.c                      |  7 +-----
 scripts/livepatch/klp-build                   | 14 +++++------
 8 files changed, 59 insertions(+), 50 deletions(-)

diff --git a/Documentation/livepatch/cumulative-patches.rst 
b/Documentation/livepatch/cumulative-patches.rst
index 1931f318976a..6ef49748110e 100644
--- a/Documentation/livepatch/cumulative-patches.rst
+++ b/Documentation/livepatch/cumulative-patches.rst
@@ -17,18 +17,20 @@ from all older livepatches and completely replace them in 
one transition.
 Usage
 -----
 
-The atomic replace can be enabled by setting "replace" flag in struct 
klp_patch,
-for example::
+The "replace_set" attribute in ``struct klp_patch`` acts as a **replace set**,
+defining the scope of the replacement. By default, the replace set is 1.
+
+For example::
 
        static struct klp_patch patch = {
                .mod = THIS_MODULE,
                .objs = objs,
-               .replace = true,
+               .replace_set = 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
+set are automatically disabled. Patches with different tags remain active.
 
 Ftrace handlers are transparently removed from functions that are no
 longer modified by the new cumulative patch.
@@ -62,9 +64,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 set)
+    atomically.
 
-    A good practice is to set .replace flag in any released livepatch.
+    A good practice is to set a consistent .replace set 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..07c8d5a13003 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.
+All currently enabled patches may be superseded by a cumulative patch that
+has the same ``.replace_set`` attribute. Once the new patch is enabled and
+the transition finishes, the livepatching core identifies all existing
+patches that share the same replace set.
+
+Once the transition is complete, all functions (``struct klp_func``)
+associated with the matching replaced patches are removed from the
+corresponding ``struct klp_ops``. If a function is no longer modified by
+the new patch and its ``func_stack`` list becomes empty, the ftrace
+handler is unregistered and the ``struct klp_ops`` is freed.
+
+Patches with a different replace set are not affected by this process
+and remain active. This allows for the independent management and
+stacking of multiple, non-conflicting livepatch sets.
 
 See Documentation/livepatch/cumulative-patches.rst for more details.
 
diff --git a/include/linux/livepatch.h b/include/linux/livepatch.h
index ba9e3988c07c..171c08328299 100644
--- a/include/linux/livepatch.h
+++ b/include/linux/livepatch.h
@@ -123,7 +123,8 @@ struct klp_state {
  * @mod:       reference to the live patch module
  * @objs:      object entries for kernel objects to be patched
  * @states:    system states that can get modified
- * @replace:   replace all actively used patches
+ * @replace_set:Livepatch using the same @replace_set will get atomically
+ *             replaced.
  * @list:      list node for global list of actively used patches
  * @kobj:      kobject for sysfs resources
  * @obj_list:  dynamic list of the object entries
@@ -137,7 +138,7 @@ struct klp_patch {
        struct module *mod;
        struct klp_object *objs;
        struct klp_state *states;
-       bool replace;
+       unsigned int replace_set;
 
        /* internal */
        struct list_head list;
diff --git a/kernel/livepatch/core.c b/kernel/livepatch/core.c
index 28d15ba58a26..9eeded1f9cf0 100644
--- a/kernel/livepatch/core.c
+++ b/kernel/livepatch/core.c
@@ -454,7 +454,7 @@ static ssize_t replace_show(struct kobject *kobj,
        struct klp_patch *patch;
 
        patch = container_of(kobj, struct klp_patch, kobj);
-       return sysfs_emit(buf, "%d\n", patch->replace);
+       return sysfs_emit(buf, "%d\n", patch->replace_set);
 }
 
 static ssize_t stack_order_show(struct kobject *kobj,
@@ -621,6 +621,8 @@ static int klp_add_nops(struct klp_patch *patch)
                klp_for_each_object(old_patch, old_obj) {
                        int err;
 
+                       if (patch->replace_set != old_patch->replace_set)
+                               continue;
                        err = klp_add_object_nops(patch, old_obj);
                        if (err)
                                return err;
@@ -793,6 +795,8 @@ void klp_free_replaced_patches_async(struct klp_patch 
*new_patch)
        klp_for_each_patch_safe(old_patch, tmp_patch) {
                if (old_patch == new_patch)
                        return;
+               if (old_patch->replace_set != new_patch->replace_set)
+                       continue;
                klp_free_patch_async(old_patch);
        }
 }
@@ -988,11 +992,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);
@@ -1195,6 +1197,8 @@ void klp_unpatch_replaced_patches(struct klp_patch 
*new_patch)
                if (old_patch == new_patch)
                        return;
 
+               if (old_patch->replace_set != new_patch->replace_set)
+                       continue;
                old_patch->enabled = false;
                klp_unpatch_objects(old_patch);
        }
diff --git a/kernel/livepatch/state.c b/kernel/livepatch/state.c
index 2565d039ade0..a2d223f2bbc0 100644
--- a/kernel/livepatch/state.c
+++ b/kernel/livepatch/state.c
@@ -85,24 +85,25 @@ 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);
 
-       /* A cumulative livepatch must handle all already modified states. */
+       /*
+        * If the new livepatch shares a state set with an existing one, it
+        * must maintain compatibility with all states modified by the old
+        * patch.
+        */
        if (!state)
-               return !patch->replace;
+               return patch->replace_set != old_patch->replace_set;
 
        return state->version >= old_state->version;
 }
 
-/*
- * Check that the new livepatch will not break the existing system states.
- * Cumulative patches must handle all already modified states.
- * Non-cumulative patches can touch already modified states.
- */
+/* Check that the new livepatch will not break the existing system states. */
 bool klp_is_patch_compatible(struct klp_patch *patch)
 {
        struct klp_patch *old_patch;
@@ -110,7 +111,7 @@ 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;
                }
        }
diff --git a/kernel/livepatch/transition.c b/kernel/livepatch/transition.c
index 2351a19ac2a9..d9f5968fecdc 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,11 @@ 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->replace_set == 
klp_transition_patch->replace_set)
                                patch->forced = true;
                }
        }
diff --git a/scripts/livepatch/init.c b/scripts/livepatch/init.c
index f14d8c8fb35f..659db21a5b53 100644
--- a/scripts/livepatch/init.c
+++ b/scripts/livepatch/init.c
@@ -72,12 +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_set = KLP_REPLACE_TAG;
        return klp_enable_patch(patch);
 
 err_free_objs:
diff --git a/scripts/livepatch/klp-build b/scripts/livepatch/klp-build
index 7b82c7503c2b..66d4a0631f1b 100755
--- a/scripts/livepatch/klp-build
+++ b/scripts/livepatch/klp-build
@@ -117,7 +117,7 @@ Options:
    -f, --show-first-changed    Show address of first changed instruction
    -j, --jobs=<jobs>           Build jobs to run simultaneously [default: 
$JOBS]
    -o, --output=<file.ko>      Output file [default: livepatch-<patch-name>.ko]
-       --no-replace            Disable livepatch atomic replace
+   -s, --replace-set=<set>     Set the atomic replace set for this livepatch
    -v, --verbose               Pass V=1 to kernel/module builds
 
 Advanced Options:
@@ -142,8 +142,8 @@ process_args() {
        local long
        local args
 
-       short="hfj:o:vdS:T"
-       
long="help,show-first-changed,jobs:,output:,no-replace,verbose,debug,short-circuit:,keep-tmp"
+       short="hfj:o:s:vdS:T"
+       
long="help,show-first-changed,jobs:,output:,replace-set:,verbose,debug,short-circuit:,keep-tmp"
 
        args=$(getopt --options "$short" --longoptions "$long" -- "$@") || {
                echo; usage; exit
@@ -172,9 +172,9 @@ process_args() {
                                NAME="$(module_name_string "$NAME")"
                                shift 2
                                ;;
-                       --no-replace)
-                               REPLACE=0
-                               shift
+                       -s | --replace-set)
+                               REPLACE="$2"
+                               shift 2
                                ;;
                        -v | --verbose)
                                VERBOSE="V=1"
@@ -759,7 +759,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")
-- 
2.47.3


Reply via email to