Re: [PATCH v3 2/5] kexec: abort liveupdate handover on kernel_kexec() unwind

Pasha Tatashin
2026-03-21T23:05:48Z

On Sat, Mar 21, 2026 at 10:39 AM Oskar Gerlicz Kowalczuk
<oskar@gerlicz.space> wrote:
>
> liveupdate_reboot() can freeze outgoing session state before the kexec
> transition completes. v2 only unwound that state when kernel_kexec()
> returned a non-zero error, which misses the preserve_context path where
> machine_kexec() can return to the old kernel with error == 0.
>
> If that return path skips the abort, outgoing sessions remain stuck in
> the reboot state and later user space operations cannot make progress.
>
> Treat every return to the original kernel after a prepared liveupdate
> handover as an unwind point and call liveupdate_reboot_abort() there as
> well.
>
> Fixes: 8b32d3ced269 ("kexec: abort liveupdate handover on kernel_kexec() unwind")
> Signed-off-by: Oskar Gerlicz Kowalczuk <oskar@gerlicz.space>

AFAIK, preserve-context reboot is no longer used; in fact, it is
currently broken. I tried using it six years ago and hit an old
spinlock bug in the x86 code that prevented it, which means this path
touches dead code.

It was originally developed for machines lacking hardware support for
hibernation, and I can't imagine anyone using it today. (I know Amazon
is using it for a non intended reason and that is a very specific
scenario in a costumized kernel that internally fixes the upstream
bugs, and would not conlict with live update).

During live update, we cannot return to userspace after a successful
liveupdate_reboot(). We specifically place liveupdate_reboot() in the
kexec path as the last possible point of failure. Perhap, to guarantee
this behavior, we could add !KEXEC_JUMP as a dependency to KHO and
LUO or crash the kernel.

Pasha

> ---
> include/linux/liveupdate.h | 5 +++++
> kernel/kexec_core.c | 6 +++++-
> kernel/liveupdate/luo_core.c | 11 ++++++++++-
> kernel/liveupdate/luo_internal.h | 1 +
> kernel/liveupdate/luo_session.c | 23 +++++++++++++++++++++++
> 5 files changed, 44 insertions(+), 2 deletions(-)
>
> diff --git a/include/linux/liveupdate.h b/include/linux/liveupdate.h
> index dd11fdc76a5f..d93b043a0421 100644
> --- a/include/linux/liveupdate.h
> +++ b/include/linux/liveupdate.h
> @@ -226,6 +226,7 @@ bool liveupdate_enabled(void);
>
> /* Called during kexec to tell LUO that entered into reboot */
> int liveupdate_reboot(void);
> +void liveupdate_reboot_abort(void);
>
> int liveupdate_register_file_handler(struct liveupdate_file_handler *fh);
> int liveupdate_unregister_file_handler(struct liveupdate_file_handler *fh);
> @@ -250,6 +251,10 @@ static inline int liveupdate_reboot(void)
> return 0;
> }
>
> +static inline void liveupdate_reboot_abort(void)
> +{
> +}
> +
> static inline int liveupdate_register_file_handler(struct liveupdate_file_handler *fh)
> {
> return -EOPNOTSUPP;
> diff --git a/kernel/kexec_core.c b/kernel/kexec_core.c
> index 2fea396d29b9..95710a7d5e56 100644
> --- a/kernel/kexec_core.c
> +++ b/kernel/kexec_core.c
> @@ -1139,6 +1139,7 @@ bool kexec_load_permitted(int kexec_image_type)
> int kernel_kexec(void)
> {
> int error = 0;
> + bool liveupdate_prepared = false;
>
> if (!kexec_trylock())
> return -EBUSY;
> @@ -1147,6 +1148,7 @@ int kernel_kexec(void)
> goto Unlock;
> }
>
> + liveupdate_prepared = true;
> error = liveupdate_reboot();
> if (error)
> goto Unlock;
> @@ -1230,7 +1232,9 @@ int kernel_kexec(void)
> }
> #endif
>
> - Unlock:
> +Unlock:
> + if (liveupdate_prepared)
> + liveupdate_reboot_abort();
> kexec_unlock();
> return error;
> }
> diff --git a/kernel/liveupdate/luo_core.c b/kernel/liveupdate/luo_core.c
> index dda7bb57d421..95a0b81ce60d 100644
> --- a/kernel/liveupdate/luo_core.c
> +++ b/kernel/liveupdate/luo_core.c
> @@ -233,8 +233,9 @@ int liveupdate_reboot(void)
> err = kho_finalize();
> if (err) {
> pr_err("kho_finalize failed %d\n", err);
> + liveupdate_reboot_abort();
> /*
> - * kho_finalize() may return libfdt errors, to aboid passing to
> + * kho_finalize() may return libfdt errors, to avoid passing to
> * userspace unknown errors, change this to EAGAIN.
> */
> err = -EAGAIN;
> @@ -243,6 +244,14 @@ int liveupdate_reboot(void)
> return err;
> }
>
> +void liveupdate_reboot_abort(void)
> +{
> + if (!liveupdate_enabled())
> + return;
> +
> + luo_session_abort_reboot();
> +}
> +
> /**
> * liveupdate_enabled - Check if the live update feature is enabled.
> *
> diff --git a/kernel/liveupdate/luo_internal.h b/kernel/liveupdate/luo_internal.h
> index 0cfc0269d746..8a6b1f6c9b4f 100644
> --- a/kernel/liveupdate/luo_internal.h
> +++ b/kernel/liveupdate/luo_internal.h
> @@ -88,6 +88,7 @@ int luo_session_retrieve(const char *name, struct file **filep);
> int __init luo_session_setup_outgoing(void *fdt);
> int __init luo_session_setup_incoming(void *fdt);
> int luo_session_serialize(void);
> +void luo_session_abort_reboot(void);
> int luo_session_deserialize(void);
> bool luo_session_quiesce(void);
> void luo_session_resume(void);
> diff --git a/kernel/liveupdate/luo_session.c b/kernel/liveupdate/luo_session.c
> index d97ec91e1118..200dd3b8229c 100644
> --- a/kernel/liveupdate/luo_session.c
> +++ b/kernel/liveupdate/luo_session.c
> @@ -788,6 +788,29 @@ int luo_session_serialize(void)
> return err;
> }
>
> +void luo_session_abort_reboot(void)
> +{
> + struct luo_session_header *sh = &luo_session_global.outgoing;
> + struct luo_session *session;
> + int i = 0;
> +
> + guard(rwsem_write)(&sh->rwsem);
> + if (!READ_ONCE(sh->rebooting))
> + return;
> +
> + list_for_each_entry(session, &sh->list, list) {
> + if (i >= sh->header_ser->count)
> + break;
> +
> + luo_session_unfreeze_one(session, &sh->ser[i]);
> + memset(&sh->ser[i], 0, sizeof(sh->ser[i]));
> + i++;
> + }
> +
> + sh->header_ser->count = 0;
> + luo_session_reboot_done(sh);
> +}
> +
> /**
> * luo_session_quiesce - Ensure no active sessions exist and lock session lists.
> *
> --
> 2.53.0
>
>