Live Patching
 help / color / mirror / Atom feed
From: Petr Mladek <pmladek@suse.com>
To: Yafang Shao <laoar.shao@gmail.com>
Cc: jpoimboe@kernel.org, jikos@kernel.org, mbenes@suse.cz,
	joe.lawrence@redhat.com, song@kernel.org,
	live-patching@vger.kernel.org
Subject: Re: [RFC PATCH 1/6] livepatch: Support scoped atomic replace using replace set
Date: Tue, 26 May 2026 14:52:12 +0200	[thread overview]
Message-ID: <ahWXfHRFpvQBWgsa@pathway.suse.cz> (raw)
In-Reply-To: <20260513143321.26185-2-laoar.shao@gmail.com>

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.

> +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."

>      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.

> +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()...

>  	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".

>  }
>  
> -/*
> - * 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".

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.

>  				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.

Is KLP_REPLACE_SET always defined, please?

>  	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.

> +				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?


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


In general, I am fine with this change. Well, it would require also
adding/fixing selftests.

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

Best Regards,
Petr

  parent reply	other threads:[~2026-05-26 12:52 UTC|newest]

Thread overview: 24+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-05-13 14:33 [RFC PATCH 0/6] livepatch: Introduce replace set support Yafang Shao
2026-05-13 14:33 ` [RFC PATCH 1/6] livepatch: Support scoped atomic replace using replace set Yafang Shao
2026-05-14 20:54   ` sashiko-bot
2026-05-17 13:32     ` Yafang Shao
2026-05-18 21:25   ` Song Liu
2026-05-26 10:35     ` Petr Mladek
2026-05-26 18:27       ` Song Liu
2026-05-27  2:17         ` Yafang Shao
2026-05-26 12:52   ` Petr Mladek [this message]
2026-05-27  2:37     ` Yafang Shao
2026-05-13 14:33 ` [RFC PATCH 2/6] livepatch: Add callbacks for introducing and removing states Yafang Shao
2026-05-13 14:33 ` [RFC PATCH 3/6] livepatch: Allow to handle lifetime of shadow variables using the livepatch state Yafang Shao
2026-05-14 22:07   ` sashiko-bot
2026-05-18  3:24     ` [PATCH] " Yafang Shao
2026-05-13 14:33 ` [RFC PATCH 4/6] livepatch: Remove "data" from struct klp_state Yafang Shao
2026-05-14 22:22   ` sashiko-bot
2026-05-13 14:33 ` [RFC PATCH 5/6] livepatch: Remove obsolete per-object callbacks Yafang Shao
2026-05-14 22:40   ` sashiko-bot
2026-05-19  6:46     ` Yafang Shao
2026-05-13 14:33 ` [RFC PATCH 6/6] livepatch: Support replace_set in shadow variable API Yafang Shao
2026-05-14 23:01   ` sashiko-bot
2026-05-19  6:56     ` Yafang Shao
2026-05-26 13:04   ` Petr Mladek
2026-05-26 13:13 ` [RFC PATCH 0/6] livepatch: Introduce replace set support Petr Mladek

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=ahWXfHRFpvQBWgsa@pathway.suse.cz \
    --to=pmladek@suse.com \
    --cc=jikos@kernel.org \
    --cc=joe.lawrence@redhat.com \
    --cc=jpoimboe@kernel.org \
    --cc=laoar.shao@gmail.com \
    --cc=live-patching@vger.kernel.org \
    --cc=mbenes@suse.cz \
    --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