Re: [RFC PATCH 1/6] livepatch: Support scoped atomic replace using replace set

Yafang Shao <[email protected]>
Newsgroups org.kernel.vger.live-patching,dev.linux.lists.sashiko-reviews
Message-ID <CALOAHbCpRO9-EpFURKPw7NOj0sXhkEMwNp_orVjXhWfhP9dtEw@mail.gmail.com>
On Fri, May 15, 2026 at 4:54 AM <[email protected]> wrote:
>
> Thank you for your contribution! Sashiko AI review found 4 potential issue(s) to consider:
> - [High] Module reference leak during forced transition.
> - [High] Silent semantic reversion for zero-initialized out-of-tree livepatches.
> - [High] Sysfs ABI breakage and incorrect type formatting for the `replace` attribute.
> - [Medium] Build failure in livepatch selftests.
> --
>
> commit 8a05e8a3ebd9241541926ac6aba19452a820c899
> Author: Yafang Shao <[email protected]>
>
> livepatch: Support scoped atomic replace using replace set
>
> This commit converts the boolean replace attribute to a u32 replace_set
> to allow livepatches to atomically replace only existing patches within
> the same set. Patches with different replace sets can now remain active
> simultaneously.
>
> > diff --git a/include/linux/livepatch.h b/include/linux/livepatch.h
> > --- a/include/linux/livepatch.h
> > +++ b/include/linux/livepatch.h
> [ ... ]
> > @@ -137,7 +138,7 @@ struct klp_patch {
> >       struct module *mod;
> >       struct klp_object *objs;
> >       struct klp_state *states;
> > -     bool replace;
> > +     unsigned int replace_set;
>
> By removing the boolean replace flag, out-of-tree livepatches that
> zero-initialize their patch structures will now default to a replace_set of 0.
> Will this cause them to unintentionally replace all other zero-initialized
> patches instead of stacking safely?

As previously discussed, backward compatibility is not a concern.

>
> Additionally, does this rename break the livepatch selftests in
> tools/testing/selftests/livepatch/test_modules/ (such as test_klp_state2.c),
> which still explicitly set patch.replace = true?

Selftests are currently excluded from this RFC

>
> [ ... ]
> > diff --git a/kernel/livepatch/core.c b/kernel/livepatch/core.c
> > --- 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);
>
> Does changing the sysfs output from a boolean to a replace set ID break
> compatibility for userspace tools expecting strict 0 or 1 values from the
> replace attribute?
>
> Also, since replace_set is declared as an unsigned int, will using the
> signed %d format specifier cause large replace set IDs to overflow and
> display as negative numbers?

Should use "%u" instead.

>
> [ ... ]
> > diff --git a/kernel/livepatch/transition.c b/kernel/livepatch/transition.c
> > --- 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)
> >                               patch->forced = true;
> >               }
> >       }
>
> Could this inadvertently set forced = true on the klp_transition_patch
> itself?
>
> Since klp_transition_patch was already added to the global list during
> klp_init_patch(), it will match its own replace_set here. If it is marked
> as forced during a patching transition, wouldn't we leak its module reference
> in klp_free_patch_finish() when it is cleanly disabled later?

Right. it should be :

                klp_for_each_patch(patch) {
-                       if (patch != klp_transition_patch)
+                       if (patch != klp_transition_patch &&
+                           patch->replace_set ==
klp_transition_patch->replace_set)
                                patch->forced = true;
                }

-- 
Regards
Yafang
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.