tests/liveupdate: add in-kernel liveupdate test

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.

Link: https://lkml.kernel.org/r/20251218155752.3045808-6-pasha.tatashin@soleen.com
Signed-off-by: Pasha Tatashin <pasha.tatashin@soleen.com>
Cc: Alexander Graf <graf@amazon.com>
Cc: David Gow <davidgow@google.com>
Cc: David Matlack <dmatlack@google.com>
Cc: David Rientjes <rientjes@google.com>
Cc: Jonathan Corbet <corbet@lwn.net>
Cc: Kees Cook <kees@kernel.org>
Cc: Mike Rapoport <rppt@kernel.org>
Cc: Petr Mladek <pmladek@suse.com>
Cc: Pratyush Yadav <pratyush@kernel.org>
Cc: Samiullah Khawaja <skhawaja@google.com>
Cc: Tamir Duberstein <tamird@gmail.com>
Signed-off-by: Andrew Morton <akpm@linux-foundation.org>
This commit is contained in:
Pasha Tatashin 2025-12-18 10:57:52 -05:00 committed by Andrew Morton
parent cab056f2aa
commit f653ff7af9
7 changed files with 203 additions and 1 deletions

View file

@ -14652,6 +14652,7 @@ F: include/linux/liveupdate.h
F: include/linux/liveupdate/
F: include/uapi/linux/liveupdate.h
F: kernel/liveupdate/
F: lib/tests/liveupdate.c
F: mm/memfd_luo.c
F: tools/testing/selftests/liveupdate/

View file

@ -239,4 +239,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_KHO_ABI_LUO_H */

View file

@ -864,6 +864,8 @@ int liveupdate_register_file_handler(struct liveupdate_file_handler *fh)
list_add_tail(&ACCESS_PRIVATE(fh, list), &luo_file_handler_list);
luo_session_resume();
liveupdate_test_register(fh);
return 0;
err_resume:
@ -895,8 +897,10 @@ int liveupdate_unregister_file_handler(struct liveupdate_file_handler *fh)
if (!liveupdate_enabled())
return -EOPNOTSUPP;
liveupdate_test_unregister(fh);
if (!luo_session_quiesce())
return -EBUSY;
goto err_register;
if (!list_empty(&ACCESS_PRIVATE(fh, flb_list)))
goto err_resume;
@ -909,5 +913,7 @@ int liveupdate_unregister_file_handler(struct liveupdate_file_handler *fh)
err_resume:
luo_session_resume();
err_register:
liveupdate_test_register(fh);
return err;
}

View file

@ -107,4 +107,12 @@ 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 *fh);
void liveupdate_test_unregister(struct liveupdate_file_handler *fh);
#else
static inline void liveupdate_test_register(struct liveupdate_file_handler *fh) { }
static inline void liveupdate_test_unregister(struct liveupdate_file_handler *fh) { }
#endif
#endif /* _LINUX_LUO_INTERNAL_H */

View file

@ -2825,6 +2825,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

View file

@ -30,6 +30,7 @@ obj-$(CONFIG_LIST_PRIVATE_KUNIT_TEST) += list-private-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

158
lib/tests/liveupdate.c Normal file
View file

@ -0,0 +1,158 @@
// 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;
err = liveupdate_flb_get_incoming(flb, &obj);
if (err && err != -ENODATA && err != -ENOENT) {
pr_err("liveupdate_flb_get_incoming for %s failed: %pe\n",
flb->compatible, ERR_PTR(err));
}
}
initialized = true;
}
void liveupdate_test_register(struct liveupdate_file_handler *fh)
{
int err, i;
liveupdate_test_init();
for (i = 0; i < TEST_NFLBS; i++) {
struct liveupdate_flb *flb = &test_flbs[i];
err = liveupdate_register_flb(fh, flb);
if (err) {
pr_err("Failed to register %s %pe\n",
flb->compatible, ERR_PTR(err));
}
}
err = liveupdate_register_flb(fh, &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, fh->compatible);
}
void liveupdate_test_unregister(struct liveupdate_file_handler *fh)
{
int err, i;
for (i = 0; i < TEST_NFLBS; i++) {
struct liveupdate_flb *flb = &test_flbs[i];
err = liveupdate_unregister_flb(fh, flb);
if (err) {
pr_err("Failed to unregister %s %pe\n",
flb->compatible, ERR_PTR(err));
}
}
pr_info("Unregistered %d FLBs from file handler: [%s]\n",
TEST_NFLBS, fh->compatible);
}
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Pasha Tatashin <pasha.tatashin@soleen.com>");
MODULE_DESCRIPTION("In-kernel test for LUO mechanism");