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:
parent
cab056f2aa
commit
f653ff7af9
|
|
@ -14652,6 +14652,7 @@ F: include/linux/liveupdate.h
|
||||||
F: include/linux/liveupdate/
|
F: include/linux/liveupdate/
|
||||||
F: include/uapi/linux/liveupdate.h
|
F: include/uapi/linux/liveupdate.h
|
||||||
F: kernel/liveupdate/
|
F: kernel/liveupdate/
|
||||||
|
F: lib/tests/liveupdate.c
|
||||||
F: mm/memfd_luo.c
|
F: mm/memfd_luo.c
|
||||||
F: tools/testing/selftests/liveupdate/
|
F: tools/testing/selftests/liveupdate/
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -239,4 +239,9 @@ struct luo_flb_ser {
|
||||||
u64 count;
|
u64 count;
|
||||||
} __packed;
|
} __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 */
|
#endif /* _LINUX_KHO_ABI_LUO_H */
|
||||||
|
|
|
||||||
|
|
@ -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);
|
list_add_tail(&ACCESS_PRIVATE(fh, list), &luo_file_handler_list);
|
||||||
luo_session_resume();
|
luo_session_resume();
|
||||||
|
|
||||||
|
liveupdate_test_register(fh);
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
|
|
||||||
err_resume:
|
err_resume:
|
||||||
|
|
@ -895,8 +897,10 @@ int liveupdate_unregister_file_handler(struct liveupdate_file_handler *fh)
|
||||||
if (!liveupdate_enabled())
|
if (!liveupdate_enabled())
|
||||||
return -EOPNOTSUPP;
|
return -EOPNOTSUPP;
|
||||||
|
|
||||||
|
liveupdate_test_unregister(fh);
|
||||||
|
|
||||||
if (!luo_session_quiesce())
|
if (!luo_session_quiesce())
|
||||||
return -EBUSY;
|
goto err_register;
|
||||||
|
|
||||||
if (!list_empty(&ACCESS_PRIVATE(fh, flb_list)))
|
if (!list_empty(&ACCESS_PRIVATE(fh, flb_list)))
|
||||||
goto err_resume;
|
goto err_resume;
|
||||||
|
|
@ -909,5 +913,7 @@ int liveupdate_unregister_file_handler(struct liveupdate_file_handler *fh)
|
||||||
|
|
||||||
err_resume:
|
err_resume:
|
||||||
luo_session_resume();
|
luo_session_resume();
|
||||||
|
err_register:
|
||||||
|
liveupdate_test_register(fh);
|
||||||
return err;
|
return err;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -107,4 +107,12 @@ int __init luo_flb_setup_outgoing(void *fdt);
|
||||||
int __init luo_flb_setup_incoming(void *fdt);
|
int __init luo_flb_setup_incoming(void *fdt);
|
||||||
void luo_flb_serialize(void);
|
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 */
|
#endif /* _LINUX_LUO_INTERNAL_H */
|
||||||
|
|
|
||||||
|
|
@ -2825,6 +2825,29 @@ config LINEAR_RANGES_TEST
|
||||||
|
|
||||||
If unsure, say N.
|
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
|
config CMDLINE_KUNIT_TEST
|
||||||
tristate "KUnit test for cmdline API" if !KUNIT_ALL_TESTS
|
tristate "KUnit test for cmdline API" if !KUNIT_ALL_TESTS
|
||||||
depends on KUNIT
|
depends on KUNIT
|
||||||
|
|
|
||||||
|
|
@ -30,6 +30,7 @@ obj-$(CONFIG_LIST_PRIVATE_KUNIT_TEST) += list-private-test.o
|
||||||
obj-$(CONFIG_KFIFO_KUNIT_TEST) += kfifo_kunit.o
|
obj-$(CONFIG_KFIFO_KUNIT_TEST) += kfifo_kunit.o
|
||||||
obj-$(CONFIG_TEST_LIST_SORT) += test_list_sort.o
|
obj-$(CONFIG_TEST_LIST_SORT) += test_list_sort.o
|
||||||
obj-$(CONFIG_LINEAR_RANGES_TEST) += test_linear_ranges.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)
|
CFLAGS_longest_symbol_kunit.o += $(call cc-disable-warning, missing-prototypes)
|
||||||
obj-$(CONFIG_LONGEST_SYM_KUNIT_TEST) += longest_symbol_kunit.o
|
obj-$(CONFIG_LONGEST_SYM_KUNIT_TEST) += longest_symbol_kunit.o
|
||||||
|
|
|
||||||
|
|
@ -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");
|
||||||
Loading…
Reference in New Issue