Introduce an in-kernel test module to validate the core logic of the
Live Update Orchestrator's File-Lifecycle-Bound feature. This
provides a low-level, controlled environment to test FLB registration
and callback invocation without requiring userspace interaction or
actual kexec reboots.
The test is enabled by the CONFIG_LIVEUPDATE_TEST Kconfig option.
Signed-off-by: Pasha Tatashin <pasha.tatashin@soleen.com>
---
include/linux/liveupdate/abi/luo.h | 5 +
kernel/liveupdate/luo_file.c | 2 +
kernel/liveupdate/luo_internal.h | 6 ++
lib/Kconfig.debug | 23 +++++
lib/tests/Makefile | 1 +
lib/tests/liveupdate.c | 143 +++++++++++++++++++++++++++++
6 files changed, 180 insertions(+)
create mode 100644 lib/tests/liveupdate.c
diff --git a/include/linux/liveupdate/abi/luo.h b/include/linux/liveupdate/abi/luo.h
index 85596ce68c16..cdcace9b48f5 100644
--- a/include/linux/liveupdate/abi/luo.h
+++ b/include/linux/liveupdate/abi/luo.h
@@ -230,4 +230,9 @@ struct luo_flb_ser {
u64 count;
} __packed;
+/* Kernel Live Update Test ABI */
+#ifdef CONFIG_LIVEUPDATE_TEST
+#define LIVEUPDATE_TEST_FLB_COMPATIBLE(i) "liveupdate-test-flb-v" #i
+#endif
+
#endif /* _LINUX_LIVEUPDATE_ABI_LUO_H */
diff --git a/kernel/liveupdate/luo_file.c b/kernel/liveupdate/luo_file.c
index df337c9c4f21..9a531096bdb5 100644
--- a/kernel/liveupdate/luo_file.c
+++ b/kernel/liveupdate/luo_file.c
@@ -834,6 +834,8 @@ int liveupdate_register_file_handler(struct liveupdate_file_handler *fh)
INIT_LIST_HEAD(&fh->flb_list);
list_add_tail(&fh->list, &luo_file_handler_list);
+ liveupdate_test_register(fh);
+
return 0;
}
diff --git a/kernel/liveupdate/luo_internal.h b/kernel/liveupdate/luo_internal.h
index 389fb102775f..c863cb051d49 100644
--- a/kernel/liveupdate/luo_internal.h
+++ b/kernel/liveupdate/luo_internal.h
@@ -86,4 +86,10 @@ int __init luo_flb_setup_outgoing(void *fdt);
int __init luo_flb_setup_incoming(void *fdt);
void luo_flb_serialize(void);
+#ifdef CONFIG_LIVEUPDATE_TEST
+void liveupdate_test_register(struct liveupdate_file_handler *h);
+#else
+static inline void liveupdate_test_register(struct liveupdate_file_handler *h) { }
+#endif
+
#endif /* _LINUX_LUO_INTERNAL_H */
diff --git a/lib/Kconfig.debug b/lib/Kconfig.debug
index 9a087826498a..eaa2af2bd963 100644
--- a/lib/Kconfig.debug
+++ b/lib/Kconfig.debug
@@ -2803,6 +2803,29 @@ config LINEAR_RANGES_TEST
If unsure, say N.
+config LIVEUPDATE_TEST
+ bool "Live Update Kernel Test"
+ default n
+ depends on LIVEUPDATE
+ help
+ Enable a built-in kernel test module for the Live Update
+ Orchestrator.
+
+ This module validates the File-Lifecycle-Bound subsystem by
+ registering a set of mock FLB objects with any real file handlers
+ that support live update (such as the memfd handler).
+
+ When live update operations are performed, this test module will
+ output messages to the kernel log (dmesg), confirming that its
+ registration and various callback functions (preserve, retrieve,
+ finish, etc.) are being invoked correctly.
+
+ This is a debugging and regression testing tool for developers
+ working on the Live Update subsystem. It should not be enabled in
+ production kernels.
+
+ If unsure, say N
+
config CMDLINE_KUNIT_TEST
tristate "KUnit test for cmdline API" if !KUNIT_ALL_TESTS
depends on KUNIT
diff --git a/lib/tests/Makefile b/lib/tests/Makefile
index f7460831cfdd..8e5c527a94ac 100644
--- a/lib/tests/Makefile
+++ b/lib/tests/Makefile
@@ -27,6 +27,7 @@ obj-$(CONFIG_LIST_KUNIT_TEST) += list-test.o
obj-$(CONFIG_KFIFO_KUNIT_TEST) += kfifo_kunit.o
obj-$(CONFIG_TEST_LIST_SORT) += test_list_sort.o
obj-$(CONFIG_LINEAR_RANGES_TEST) += test_linear_ranges.o
+obj-$(CONFIG_LIVEUPDATE_TEST) += liveupdate.o
CFLAGS_longest_symbol_kunit.o += $(call cc-disable-warning, missing-prototypes)
obj-$(CONFIG_LONGEST_SYM_KUNIT_TEST) += longest_symbol_kunit.o
diff --git a/lib/tests/liveupdate.c b/lib/tests/liveupdate.c
new file mode 100644
index 000000000000..05c05b8c1c22
--- /dev/null
+++ b/lib/tests/liveupdate.c
@@ -0,0 +1,143 @@
+// SPDX-License-Identifier: GPL-2.0
+
+/*
+ * Copyright (c) 2025, Google LLC.
+ * Pasha Tatashin <pasha.tatashin@soleen.com>
+ */
+
+#define pr_fmt(fmt) KBUILD_MODNAME " test: " fmt
+
+#include <linux/cleanup.h>
+#include <linux/errno.h>
+#include <linux/init.h>
+#include <linux/liveupdate.h>
+#include <linux/module.h>
+#include "../../kernel/liveupdate/luo_internal.h"
+
+static const struct liveupdate_flb_ops test_flb_ops;
+#define DEFINE_TEST_FLB(i) { \
+ .ops = &test_flb_ops, \
+ .compatible = LIVEUPDATE_TEST_FLB_COMPATIBLE(i), \
+}
+
+/* Number of Test FLBs to register with every file handler */
+#define TEST_NFLBS 3
+static struct liveupdate_flb test_flbs[TEST_NFLBS] = {
+ DEFINE_TEST_FLB(0),
+ DEFINE_TEST_FLB(1),
+ DEFINE_TEST_FLB(2),
+};
+
+#define TEST_FLB_MAGIC_BASE 0xFEEDF00DCAFEBEE0ULL
+
+static int test_flb_preserve(struct liveupdate_flb_op_args *argp)
+{
+ ptrdiff_t index = argp->flb - test_flbs;
+
+ pr_info("%s: preserve was triggered\n", argp->flb->compatible);
+ argp->data = TEST_FLB_MAGIC_BASE + index;
+
+ return 0;
+}
+
+static void test_flb_unpreserve(struct liveupdate_flb_op_args *argp)
+{
+ pr_info("%s: unpreserve was triggered\n", argp->flb->compatible);
+}
+
+static int test_flb_retrieve(struct liveupdate_flb_op_args *argp)
+{
+ ptrdiff_t index = argp->flb - test_flbs;
+ u64 expected_data = TEST_FLB_MAGIC_BASE + index;
+
+ if (argp->data == expected_data) {
+ pr_info("%s: found flb data from the previous boot\n",
+ argp->flb->compatible);
+ argp->obj = (void *)argp->data;
+ } else {
+ pr_err("%s: ERROR - incorrect data handle: %llx, expected %llx\n",
+ argp->flb->compatible, argp->data, expected_data);
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static void test_flb_finish(struct liveupdate_flb_op_args *argp)
+{
+ ptrdiff_t index = argp->flb - test_flbs;
+ void *expected_obj = (void *)(TEST_FLB_MAGIC_BASE + index);
+
+ if (argp->obj == expected_obj) {
+ pr_info("%s: finish was triggered\n", argp->flb->compatible);
+ } else {
+ pr_err("%s: ERROR - finish called with invalid object\n",
+ argp->flb->compatible);
+ }
+}
+
+static const struct liveupdate_flb_ops test_flb_ops = {
+ .preserve = test_flb_preserve,
+ .unpreserve = test_flb_unpreserve,
+ .retrieve = test_flb_retrieve,
+ .finish = test_flb_finish,
+ .owner = THIS_MODULE,
+};
+
+static void liveupdate_test_init(void)
+{
+ static DEFINE_MUTEX(init_lock);
+ static bool initialized;
+ int i;
+
+ guard(mutex)(&init_lock);
+
+ if (initialized)
+ return;
+
+ for (i = 0; i < TEST_NFLBS; i++) {
+ struct liveupdate_flb *flb = &test_flbs[i];
+ void *obj;
+ int err;
+
+ liveupdate_init_flb(flb);
+
+ err = liveupdate_flb_incoming_locked(flb, &obj);
+ if (!err) {
+ liveupdate_flb_incoming_unlock(flb, obj);
+ } else if (err != -ENODATA && err != -ENOENT) {
+ pr_err("liveupdate_flb_incoming_locked for %s failed: %pe\n",
+ flb->compatible, ERR_PTR(err));
+ }
+ }
+ initialized = true;
+}
+
+void liveupdate_test_register(struct liveupdate_file_handler *h)
+{
+ int err, i;
+
+ liveupdate_test_init();
+
+ for (i = 0; i < TEST_NFLBS; i++) {
+ struct liveupdate_flb *flb = &test_flbs[i];
+
+ err = liveupdate_register_flb(h, flb);
+ if (err)
+ pr_err("Failed to register %s %pe\n",
+ flb->compatible, ERR_PTR(err));
+ }
+
+ err = liveupdate_register_flb(h, &test_flbs[0]);
+ if (!err || err != -EEXIST) {
+ pr_err("Failed: %s should be already registered, but got err: %pe\n",
+ test_flbs[0].compatible, ERR_PTR(err));
+ }
+
+ pr_info("Registered %d FLBs with file handler: [%s]\n",
+ TEST_NFLBS, h->compatible);
+}
+
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("Pasha Tatashin <pasha.tatashin@soleen.com>");
+MODULE_DESCRIPTION("In-kernel test for LUO mechanism");
--
2.52.0.rc1.455.g30608eb744-goog
On Sat, Nov 15, 2025 at 06:34:06PM -0500, Pasha Tatashin wrote:
> Introduce an in-kernel test module to validate the core logic of the
> Live Update Orchestrator's File-Lifecycle-Bound feature. This
> provides a low-level, controlled environment to test FLB registration
> and callback invocation without requiring userspace interaction or
> actual kexec reboots.
>
> The test is enabled by the CONFIG_LIVEUPDATE_TEST Kconfig option.
>
> Signed-off-by: Pasha Tatashin <pasha.tatashin@soleen.com>
> ---
> include/linux/liveupdate/abi/luo.h | 5 +
> kernel/liveupdate/luo_file.c | 2 +
> kernel/liveupdate/luo_internal.h | 6 ++
> lib/Kconfig.debug | 23 +++++
> lib/tests/Makefile | 1 +
> lib/tests/liveupdate.c | 143 +++++++++++++++++++++++++++++
> 6 files changed, 180 insertions(+)
> create mode 100644 lib/tests/liveupdate.c
>
> diff --git a/include/linux/liveupdate/abi/luo.h b/include/linux/liveupdate/abi/luo.h
> index 85596ce68c16..cdcace9b48f5 100644
> --- a/include/linux/liveupdate/abi/luo.h
> +++ b/include/linux/liveupdate/abi/luo.h
> @@ -230,4 +230,9 @@ struct luo_flb_ser {
> u64 count;
> } __packed;
>
> +/* Kernel Live Update Test ABI */
> +#ifdef CONFIG_LIVEUPDATE_TEST
> +#define LIVEUPDATE_TEST_FLB_COMPATIBLE(i) "liveupdate-test-flb-v" #i
> +#endif
> +
> #endif /* _LINUX_LIVEUPDATE_ABI_LUO_H */
> diff --git a/kernel/liveupdate/luo_file.c b/kernel/liveupdate/luo_file.c
> index df337c9c4f21..9a531096bdb5 100644
> --- a/kernel/liveupdate/luo_file.c
> +++ b/kernel/liveupdate/luo_file.c
> @@ -834,6 +834,8 @@ int liveupdate_register_file_handler(struct liveupdate_file_handler *fh)
> INIT_LIST_HEAD(&fh->flb_list);
> list_add_tail(&fh->list, &luo_file_handler_list);
>
> + liveupdate_test_register(fh);
> +
Why this cannot be called from the test?
> return 0;
> }
>
--
Sincerely yours,
Mike.
> > #endif /* _LINUX_LIVEUPDATE_ABI_LUO_H */ > > diff --git a/kernel/liveupdate/luo_file.c b/kernel/liveupdate/luo_file.c > > index df337c9c4f21..9a531096bdb5 100644 > > --- a/kernel/liveupdate/luo_file.c > > +++ b/kernel/liveupdate/luo_file.c > > @@ -834,6 +834,8 @@ int liveupdate_register_file_handler(struct liveupdate_file_handler *fh) > > INIT_LIST_HEAD(&fh->flb_list); > > list_add_tail(&fh->list, &luo_file_handler_list); > > > > + liveupdate_test_register(fh); > > + > > Why this cannot be called from the test? Because test does not have access to all file_handlers that are being registered with LUO. Pasha
On Mon, Nov 17, 2025 at 02:00:15PM -0500, Pasha Tatashin wrote: > > > #endif /* _LINUX_LIVEUPDATE_ABI_LUO_H */ > > > diff --git a/kernel/liveupdate/luo_file.c b/kernel/liveupdate/luo_file.c > > > index df337c9c4f21..9a531096bdb5 100644 > > > --- a/kernel/liveupdate/luo_file.c > > > +++ b/kernel/liveupdate/luo_file.c > > > @@ -834,6 +834,8 @@ int liveupdate_register_file_handler(struct liveupdate_file_handler *fh) > > > INIT_LIST_HEAD(&fh->flb_list); > > > list_add_tail(&fh->list, &luo_file_handler_list); > > > > > > + liveupdate_test_register(fh); > > > + > > > > Why this cannot be called from the test? > > Because test does not have access to all file_handlers that are being > registered with LUO. Unless I'm missing something, an FLB users registers a file handlers and let's LUO know that it will need FLB. Why the test can't do the same? > Pasha -- Sincerely yours, Mike.
On Tue, Nov 18, 2025 at 6:31 AM Mike Rapoport <rppt@kernel.org> wrote: > > On Mon, Nov 17, 2025 at 02:00:15PM -0500, Pasha Tatashin wrote: > > > > #endif /* _LINUX_LIVEUPDATE_ABI_LUO_H */ > > > > diff --git a/kernel/liveupdate/luo_file.c b/kernel/liveupdate/luo_file.c > > > > index df337c9c4f21..9a531096bdb5 100644 > > > > --- a/kernel/liveupdate/luo_file.c > > > > +++ b/kernel/liveupdate/luo_file.c > > > > @@ -834,6 +834,8 @@ int liveupdate_register_file_handler(struct liveupdate_file_handler *fh) > > > > INIT_LIST_HEAD(&fh->flb_list); > > > > list_add_tail(&fh->list, &luo_file_handler_list); > > > > > > > > + liveupdate_test_register(fh); > > > > + > > > > > > Why this cannot be called from the test? > > > > Because test does not have access to all file_handlers that are being > > registered with LUO. > > Unless I'm missing something, an FLB users registers a file handlers and > let's LUO know that it will need FLB. Why the test can't do the same? The test needs to attach to every registered file handler because we want to ensure that FLB scales and works correctly with any file handler. For this in-kernel test, there is no need to create our own file type or to drive it from userspace (where a user would create a file of that type, preserve it with LUO, so FLB can be allocated and checked. This in-kernel test is self-sufficient. > > Pasha > > -- > Sincerely yours, > Mike.
© 2016 - 2026 Red Hat, Inc.