On Tue, May 26, 2026 at 8:52 PM Petr Mladek <[email protected]> wrote:
>
> On Wed 2026-05-13 22:33:16, Yafang Shao wrote:
> > 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.
> >
> > --- 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.
>
> Why "1" by default, please?
>
> I guess that you wanted to make it "compatible" with the original
> "replace" flag. It makes some sense. But it is weird in the long term.
>
> This patchset is changing the whole semantic. Every livepatch is able
> to replace an older one. It is not longer "no replace" vs "replace
> all". Instead, a livepatch with a particular "replace_set" number
> replaces an older livepatch with the same "replace_set" number.
>
> It brings the question whether "replace_set" is a good name. There
> is always only one enabled livepatch with a particular "replace_set"
> number. It would make sense to call it "replace_tag" or "replace_id".
>
> That said, the "set" might mean a set of livepatched functions.
> And we should make sure that each set is separate. We should refuse
> loading a livepatch which would patch a function already patched
> by another livepatch with another another "replace_set".
>
> Summary:
>
> I would keep "replace_set" name. But I would use "0" by default.

I will update it.

>
> > +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.
>
> I would say something like:
>
>      "A good practice is to use only one (default) "replace_set". 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."

Thanks for your suggestion.

>
> >      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
>
> In fact, there always can be only one livepatch with a given
> "replace_set" number. They always replace each other.

Thanks for the clarification.

>
> > +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.
> >
> > --- a/kernel/livepatch/core.c
> > +++ b/kernel/livepatch/core.c
> > @@ -454,7 +454,7 @@ static ssize_t replace_show(struct kobject *kobj,
>
> The function should get renamed to replace_set_show()...

sure.

>
> >       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,
> > --- 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;
>
> Also I would enforce that two livepatches with a different "replace_set"
> must _not_ use the same "state->id".

I will update it.

>
> >  }
> >
> > -/*
> > - * 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;
> >               }
> >       }
>
> In addition, I strictly recommend to compare the set of livepatched
> functions. We should refuse loading a livepatch which would want to modify
> a function which is already livepatched with the livepatch with
> another "replace_set".

I will update it.

>
> Aka, the "set" means a set of livepatched functions. And the sets
> should be independent.
>
> > --- a/kernel/livepatch/transition.c
> > +++ b/kernel/livepatch/transition.c
> > @@ -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)
>
> We still need to skip klp_transition patch as suggested by Sashiko AI.

sure

>
> >                               patch->forced = true;
> >               }
> >       }
> > --- 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;
>
> It should be KLP_REPLACE_SET to keep the naming consistent.

I will fix it.

>
> Is KLP_REPLACE_SET always defined, please?

Right, it is always defined:

  cflags+=("-DKLP_REPLACE_SET=$REPLACE")

>
> >       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
> > @@ -172,9 +172,9 @@ process_args() {
> >                               NAME="$(module_name_string "$NAME")"
> >                               shift 2
> >                               ;;
> > -                     --no-replace)
> > -                             REPLACE=0
> > -                             shift
> > +                     -s | --replace-set)
> > +                             REPLACE="$2"
>
> I would rename it to REPLACE_SET.

sure

>
> > +                             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")
>
> with a consisten naming scheme:
>
>         cflags+=("-DKLP_REPLACE_SET=$REPLACE_SET")
>
> Is there a default value?

The default value is currently 1, but I will update it to 0 as suggested.

  REPLACE=1

>
>
> >       cmd=("make")
> >       cmd+=("$VERBOSE")
>
>
> In general, I am fine with this change. Well, it would require also
> adding/fixing selftests.

I will update it.

>
> That said, I would prefer to rework the klp callbacks, shadow, and
> state API first. But it is not a strict requirement.

I will submit this as a standalone patch, as function replacement
works seamlessly with it.

-- 
Regards
Yafang

Reply via email to