From 89dd1421c554515e0f562ec67d976e6309f2710b Mon Sep 17 00:00:00 2001 From: Pasha Tatashin Date: Fri, 26 Jun 2026 12:15:36 +0300 Subject: [PATCH 1/8] selftests/liveupdate: add end to end test infrastructure and scripts Add the end to end testing infrastructure required to verify the liveupdate feature. This includes a custom init process, a test orchestration script, and a batch runner. The framework consists of: init.c: A lightweight init process that manages the kexec lifecycle. It mounts necessary filesystems, determines the current execution stage (1 or 2) via the kernel command line, and handles the kexec_file_load() sequence to transition between kernels. vmtest.sh: The primary KTAP-compliant test driver. It handles: - Kernel configuration merging and building. - Cross-compilation detection for x86_64 and arm64. - Generation of the initrd containing the test binary and init. - QEMU execution with automatic accelerator detection (KVM, HVF, or TCG). run-vmtests.sh: A wrapper that runs vmtest.sh for each LUO test across supported architectures, providing a summary of pass/fail/skip results. Signed-off-by: Pasha Tatashin Co-developed-by: Jordan Richards Signed-off-by: Jordan Richards Co-developed-by: Mike Rapoport (Microsoft) Acked-by: Pratyush Yadav Link: https://patch.msgid.link/20260626-luo-vmtest-v0-v4-1-e7d3111cd5b3@kernel.org Signed-off-by: Mike Rapoport (Microsoft) --- tools/testing/selftests/liveupdate/.gitignore | 1 + tools/testing/selftests/liveupdate/config | 1 + .../selftests/liveupdate/config.aarch64 | 2 + .../selftests/liveupdate/config.x86_64 | 2 + tools/testing/selftests/liveupdate/init.c | 179 ++++++++++++ .../selftests/liveupdate/luo_test_utils.c | 15 +- .../selftests/liveupdate/run-vmtests.sh | 97 +++++++ tools/testing/selftests/liveupdate/vmtest.sh | 263 ++++++++++++++++++ 8 files changed, 551 insertions(+), 9 deletions(-) create mode 100644 tools/testing/selftests/liveupdate/config.aarch64 create mode 100644 tools/testing/selftests/liveupdate/config.x86_64 create mode 100644 tools/testing/selftests/liveupdate/init.c create mode 100755 tools/testing/selftests/liveupdate/run-vmtests.sh create mode 100755 tools/testing/selftests/liveupdate/vmtest.sh diff --git a/tools/testing/selftests/liveupdate/.gitignore b/tools/testing/selftests/liveupdate/.gitignore index 661827083ab6..cb08ddb0dfee 100644 --- a/tools/testing/selftests/liveupdate/.gitignore +++ b/tools/testing/selftests/liveupdate/.gitignore @@ -6,4 +6,5 @@ !*.sh !.gitignore !config +!config.* !Makefile diff --git a/tools/testing/selftests/liveupdate/config b/tools/testing/selftests/liveupdate/config index 91d03f9a6a39..016d009dba13 100644 --- a/tools/testing/selftests/liveupdate/config +++ b/tools/testing/selftests/liveupdate/config @@ -1,4 +1,5 @@ CONFIG_BLK_DEV_INITRD=y +CONFIG_DEVTMPFS=y CONFIG_KEXEC_FILE=y CONFIG_KEXEC_HANDOVER=y CONFIG_KEXEC_HANDOVER_ENABLE_DEFAULT=y diff --git a/tools/testing/selftests/liveupdate/config.aarch64 b/tools/testing/selftests/liveupdate/config.aarch64 new file mode 100644 index 000000000000..445716403925 --- /dev/null +++ b/tools/testing/selftests/liveupdate/config.aarch64 @@ -0,0 +1,2 @@ +CONFIG_SERIAL_AMBA_PL011=y +CONFIG_SERIAL_AMBA_PL011_CONSOLE=y diff --git a/tools/testing/selftests/liveupdate/config.x86_64 b/tools/testing/selftests/liveupdate/config.x86_64 new file mode 100644 index 000000000000..810d9c9d213e --- /dev/null +++ b/tools/testing/selftests/liveupdate/config.x86_64 @@ -0,0 +1,2 @@ +CONFIG_SERIAL_8250=y +CONFIG_SERIAL_8250_CONSOLE=y diff --git a/tools/testing/selftests/liveupdate/init.c b/tools/testing/selftests/liveupdate/init.c new file mode 100644 index 000000000000..fb08bd58b9b9 --- /dev/null +++ b/tools/testing/selftests/liveupdate/init.c @@ -0,0 +1,179 @@ +// SPDX-License-Identifier: GPL-2.0 + +/* + * Copyright (c) 2025, Google LLC. + * Pasha Tatashin + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define COMMAND_LINE_SIZE 2048 +#define KERNEL_IMAGE "/kernel" +#define INITRD_IMAGE "/initrd.img" +#define TEST_BINARY "/test_binary" + +static int mount_filesystems(void) +{ + if (mount("devtmpfs", "/dev", "devtmpfs", 0, NULL) < 0) { + fprintf(stderr, "INIT: Warning: Failed to mount devtmpfs\n"); + return -1; + } + + if (mount("debugfs", "/debugfs", "debugfs", 0, NULL) < 0) { + fprintf(stderr, "INIT: Failed to mount debugfs\n"); + return -1; + } + + if (mount("proc", "/proc", "proc", 0, NULL) < 0) { + fprintf(stderr, "INIT: Failed to mount proc\n"); + return -1; + } + + return 0; +} + +static long kexec_file_load(int kernel_fd, int initrd_fd, + unsigned long cmdline_len, const char *cmdline, + unsigned long flags) +{ + return syscall(__NR_kexec_file_load, kernel_fd, initrd_fd, cmdline_len, + cmdline, flags); +} + +static int kexec_load(void) +{ + char cmdline[COMMAND_LINE_SIZE]; + int kernel_fd, initrd_fd, err; + ssize_t len; + int fd; + + fd = open("/proc/cmdline", O_RDONLY); + if (fd < 0) { + fprintf(stderr, "INIT: Failed to read /proc/cmdline\n"); + + return -1; + } + + len = read(fd, cmdline, sizeof(cmdline) - 1); + close(fd); + if (len < 0) + return -1; + + cmdline[len] = 0; + if (len > 0 && cmdline[len - 1] == '\n') + cmdline[len - 1] = 0; + + strncat(cmdline, " luo_stage=2", sizeof(cmdline) - strlen(cmdline) - 1); + + kernel_fd = open(KERNEL_IMAGE, O_RDONLY); + if (kernel_fd < 0) { + fprintf(stderr, "INIT: Failed to open kernel image\n"); + return -1; + } + + initrd_fd = open(INITRD_IMAGE, O_RDONLY); + if (initrd_fd < 0) { + fprintf(stderr, "INIT: Failed to open initrd image\n"); + close(kernel_fd); + return -1; + } + + err = kexec_file_load(kernel_fd, initrd_fd, strlen(cmdline) + 1, + cmdline, 0); + + close(initrd_fd); + close(kernel_fd); + + return err; +} + +static int run_test(int stage) +{ + char stage_arg[32]; + int status; + pid_t pid; + + snprintf(stage_arg, sizeof(stage_arg), "%d", stage); + + pid = fork(); + if (pid < 0) + return -1; + + if (!pid) { + char *const argv[] = {TEST_BINARY, "-s", stage_arg, NULL}; + + execve(TEST_BINARY, argv, NULL); + fprintf(stderr, "INIT: execve failed\n"); + _exit(1); + } + + waitpid(pid, &status, 0); + + return (WIFEXITED(status) && WEXITSTATUS(status) == 0) ? 0 : -1; +} + +static int get_current_stage(void) +{ + char cmdline[COMMAND_LINE_SIZE]; + ssize_t len; + int fd; + + fd = open("/proc/cmdline", O_RDONLY); + if (fd < 0) + return -1; + + len = read(fd, cmdline, sizeof(cmdline) - 1); + close(fd); + + if (len < 0) + return -1; + + cmdline[len] = 0; + + return strstr(cmdline, "luo_stage=2") ? 2 : 1; +} + +int main(int argc, char *argv[]) +{ + int current_stage; + int err; + + if (mount_filesystems()) + goto err_reboot; + + current_stage = get_current_stage(); + if (current_stage < 0) { + fprintf(stderr, "INIT: Failed to read cmdline"); + goto err_reboot; + } + + printf("INIT: Starting Stage %d\n", current_stage); + + if (current_stage == 1 && kexec_load()) { + fprintf(stderr, "INIT: Failed to load kexec kernel\n"); + goto err_reboot; + } + + if (run_test(current_stage)) { + fprintf(stderr, "INIT: Test binary returned failure\n"); + goto err_reboot; + } + + printf("INIT: Stage %d completed successfully.\n", current_stage); + reboot(current_stage == 1 ? RB_KEXEC : RB_AUTOBOOT); + + return 0; + +err_reboot: + reboot(RB_AUTOBOOT); + + return -1; +} diff --git a/tools/testing/selftests/liveupdate/luo_test_utils.c b/tools/testing/selftests/liveupdate/luo_test_utils.c index 333a3530051b..1439b2b48ed0 100644 --- a/tools/testing/selftests/liveupdate/luo_test_utils.c +++ b/tools/testing/selftests/liveupdate/luo_test_utils.c @@ -21,6 +21,7 @@ #include #include #include +#include #include "luo_test_utils.h" @@ -81,7 +82,7 @@ int luo_retrieve_session(int luo_fd, const char *name) int create_and_preserve_memfd(int session_fd, int token, const char *data) { struct liveupdate_session_preserve_fd arg = { .size = sizeof(arg) }; - long page_size = sysconf(_SC_PAGE_SIZE); + long page_size = getpagesize(); void *map = MAP_FAILED; int mfd = -1, ret = -1; @@ -117,7 +118,7 @@ int restore_and_verify_memfd(int session_fd, int token, const char *expected_data) { struct liveupdate_session_retrieve_fd arg = { .size = sizeof(arg) }; - long page_size = sysconf(_SC_PAGE_SIZE); + long page_size = getpagesize(); void *map = MAP_FAILED; int mfd = -1, ret = -1; @@ -228,16 +229,11 @@ void daemonize_and_wait(void) static int parse_stage_args(int argc, char *argv[]) { - static struct option long_options[] = { - {"stage", required_argument, 0, 's'}, - {0, 0, 0, 0} - }; - int option_index = 0; int stage = 1; int opt; optind = 1; - while ((opt = getopt_long(argc, argv, "s:", long_options, &option_index)) != -1) { + while ((opt = getopt(argc, argv, "s:")) != -1) { switch (opt) { case 's': stage = atoi(optarg); @@ -248,6 +244,7 @@ static int parse_stage_args(int argc, char *argv[]) fail_exit("Unknown argument"); } } + return stage; } @@ -275,7 +272,7 @@ int luo_test(int argc, char *argv[], fail_exit("Failed to check for state session"); if (target_stage != detected_stage) { - ksft_exit_fail_msg("Stage mismatch Requested --stage %d, but system is in stage %d.\n" + ksft_exit_fail_msg("Stage mismatch Requested stage %d, but system is in stage %d.\n" "(State session %s: %s)\n", target_stage, detected_stage, state_session_name, (detected_stage == 2) ? "EXISTS" : "MISSING"); diff --git a/tools/testing/selftests/liveupdate/run-vmtests.sh b/tools/testing/selftests/liveupdate/run-vmtests.sh new file mode 100755 index 000000000000..d656ce58c5a7 --- /dev/null +++ b/tools/testing/selftests/liveupdate/run-vmtests.sh @@ -0,0 +1,97 @@ +#!/bin/bash +# SPDX-License-Identifier: GPL-2.0 + +SCRIPT_DIR=$(dirname "$(realpath "$0")") +TEST_RUNNER="$SCRIPT_DIR/vmtest.sh" + +TARGETS=("x86_64" "aarch64") + +GREEN='\033[0;32m' +RED='\033[0;31m' +YELLOW='\033[1;33m' +NC='\033[0m' + +passed=0 +failed=0 +skipped=0 + +TEST_NAMES=( + "luo_kexec_simple" + "luo_multi_session" + "luo_stress_files" + "luo_stress_sessions" +) + +function usage() { + cat < %-8s %-24s ... " "$arch" "$test_name" + + "$TEST_RUNNER" -t "$arch" -T "$test_name" &> "$log" + exit_code=$? + + case $exit_code in + 0) pass;; + 4) skip;; + *) fail;; + esac + done + echo "" + done + + echo "SUMMARY: PASS=$passed SKIP=$skipped FAIL=$failed" + if [ -n "$keep_logs" ]; then + echo "Logs: $output_dir" + fi + + exit $((failed != 0)) +} + +main "$@" diff --git a/tools/testing/selftests/liveupdate/vmtest.sh b/tools/testing/selftests/liveupdate/vmtest.sh new file mode 100755 index 000000000000..b0000fae1461 --- /dev/null +++ b/tools/testing/selftests/liveupdate/vmtest.sh @@ -0,0 +1,263 @@ +#!/bin/bash +# SPDX-License-Identifier: GPL-2.0 + +set -ue + +CROSS_COMPILE="${CROSS_COMPILE:-""}" + +test_dir=$(realpath "$(dirname "$0")") +kernel_dir=$(realpath "$test_dir/../../../..") + +workspace_dir="" +headers_dir="" +initrd="" +KEEP_WORKSPACE=0 + +source "$test_dir/../kselftest/ktap_helpers.sh" + +function get_arch_conf() { + local arch=$1 + if [[ "$arch" == "arm64" ]]; then + QEMU_CMD="qemu-system-aarch64 -M virt -cpu max" + KERNEL_IMAGE="Image" + KERNEL_CMDLINE="console=ttyAMA0" + elif [[ "$arch" == "x86" ]]; then + QEMU_CMD="qemu-system-x86_64" + KERNEL_IMAGE="bzImage" + KERNEL_CMDLINE="console=ttyS0" + else + echo "Unsupported architecture: $arch" + exit 1 + fi +} + +function usage() { + cat <) + -j) number of jobs for compilation + -t) run test for target_arch (aarch64, x86_64) + -T) test name to run (default: luo_kexec_simple) + -w) custom workspace directory (default: creates temp dir) + -k) keep workspace directory after successful test + -h) display this help +EOF +} + +function cleanup() { + if [ "$KEEP_WORKSPACE" -eq 1 ]; then + echo "# Workspace preserved at: $workspace_dir" + else + rm -fr "$workspace_dir" + fi + + ktap_finished +} + +function skip() { + local msg=${1:-""} + ktap_test_skip "$msg" + exit "$KSFT_SKIP" +} + +function fail() { + local msg=${1:-""} + ktap_test_fail "$msg" + exit "$KSFT_FAIL" +} + +function detect_cross_compile() { + local target=$1 + local host=$(uname -m) + + [[ "$host" == "arm64" ]] && host="aarch64" + [[ "$target" == "arm64" ]] && target="aarch64" + + if [[ "$host" == "$target" ]]; then + CROSS_COMPILE="" + return + fi + + if [[ -n "$CROSS_COMPILE" ]]; then + return + fi + + local candidate="" + case "$target" in + aarch64) candidate="aarch64-linux-gnu-" ;; + x86_64) candidate="x86_64-linux-gnu-" ;; + *) skip "Auto-detection for target '$target' not supported. Please set CROSS_COMPILE manually." ;; + esac + + if command -v "${candidate}gcc" &> /dev/null; then + CROSS_COMPILE="$candidate" + else + skip "Compiler '${candidate}gcc' not found. Please install it (e.g., 'apt install gcc-aarch64-linux-gnu') or set CROSS_COMPILE." + fi +} + +function build_kernel() { + local build_dir=$1 + local make_cmd=$2 + local kimage=$3 + local target_arch=$4 + + local luo_config="$build_dir/luo.config" + local kconfig="$build_dir/.config" + local common_conf="$test_dir/config" + local arch_conf="$test_dir/config.$target_arch" + + echo "# Building kernel in: $build_dir" + + cat "$arch_conf" "$common_conf" | tee "$kconfig" > "$luo_config" + $make_cmd olddefconfig + + # verify that kernel confiration has all necessary options + while read -r opt ; do + grep "$opt" "$kconfig" &>/dev/null || skip "$opt is missing" + done < "$luo_config" + + $make_cmd "$kimage" + $make_cmd headers_install INSTALL_HDR_PATH="$headers_dir" +} + +function mkinitrd() { + local build_dir=$1 + local kernel_path=$2 + local test_name=$3 + + # Compile the test binary and the init process + "$CROSS_COMPILE"gcc -static -O2 -nostdinc -nostdlib \ + -I "$headers_dir/include" \ + -I "$kernel_dir/tools/include/nolibc" \ + -I "$test_dir" \ + -o "$workspace_dir/test_binary" \ + "$test_dir/$test_name.c" "$test_dir/luo_test_utils.c" + + "$CROSS_COMPILE"gcc -s -static -Os -nostdinc -nostdlib \ + -fno-asynchronous-unwind-tables -fno-ident \ + -fno-stack-protector \ + -I "$headers_dir/include" \ + -I "$kernel_dir/tools/include/nolibc" \ + -o "$workspace_dir/init" "$test_dir/init.c" + + cat > "$workspace_dir/cpio_list_inner" < "$workspace_dir/inner_initrd.cpio" + + cat > "$workspace_dir/cpio_list" < "$initrd" +} + +function run_qemu() { + local qemu_cmd=$1 + local cmdline=$2 + local kernel_path=$3 + local serial="$workspace_dir/qemu.serial" + + cmdline="$cmdline liveupdate=on panic=-1" + + echo "# Serial Log: $serial" + timeout 30s \ + $qemu_cmd -m 1G -smp 2 -no-reboot -nographic -nodefaults \ + -accel tcg -accel hvf -accel kvm \ + -serial file:"$serial" \ + -append "$cmdline" \ + -kernel "$kernel_path" \ + -initrd "$initrd" + + grep "TEST PASSED" "$serial" &> /dev/null || fail "Liveupdate failed" +} + +function target_to_arch() { + local target=$1 + case $target in + aarch64) echo "arm64" ;; + x86_64) echo "x86" ;; + *) skip "architecture $target is not supported" + esac +} + +function main() { + local build_dir="" + local jobs=$(nproc) + local target="$(uname -m)" + local test_name="luo_kexec_simple" + local workspace_arg="" + + set -o errtrace + trap fail ERR + + while getopts 'hd:j:t:T:w:k' opt; do + case $opt in + d) build_dir="$OPTARG" ;; + j) jobs="$OPTARG" ;; + t) target="$OPTARG" ;; + T) test_name="$OPTARG" ;; + w) workspace_arg="$OPTARG" ;; + k) KEEP_WORKSPACE=1 ;; + h) usage; exit 0 ;; + *) echo "Unknown argument $opt"; usage; exit 1 ;; + esac + done + + ktap_print_header + ktap_set_plan 1 + trap cleanup EXIT + + if [ -n "$workspace_arg" ]; then + workspace_dir="$(realpath -m "$workspace_arg")" + mkdir -p "$workspace_dir" + else + workspace_dir=$(mktemp -d /tmp/luo-test.XXXXXXXX) + fi + + echo "# Workspace created at: $workspace_dir" + headers_dir="$workspace_dir/usr" + initrd="$workspace_dir/initrd.cpio" + + detect_cross_compile "$target" + + local arch=$(target_to_arch "$target") + + if [ -z "$build_dir" ]; then + build_dir="$kernel_dir/.luo_test_build.$arch" + fi + + mkdir -p "$build_dir" + build_dir=$(realpath "$build_dir") + get_arch_conf "$arch" + + local make_cmd="make -s ARCH=$arch CROSS_COMPILE=$CROSS_COMPILE -j$jobs" + local make_cmd_build="$make_cmd -C $kernel_dir O=$build_dir" + + build_kernel "$build_dir" "$make_cmd_build" "$KERNEL_IMAGE" "$target" + + local final_kernel="$build_dir/arch/$arch/boot/$KERNEL_IMAGE" + mkinitrd "$build_dir" "$final_kernel" "$test_name" + + run_qemu "$QEMU_CMD" "$KERNEL_CMDLINE" "$final_kernel" + ktap_test_pass "$test_name succeeded" +} + +main "$@" From 4867ab462d8d69e73c239b9a1a98e42a222f17a2 Mon Sep 17 00:00:00 2001 From: Hongfu Li Date: Tue, 14 Jul 2026 15:40:56 +0800 Subject: [PATCH 2/8] liveupdate: Remove redundant INIT_LIST_HEAD in luo_session_alloc luo_session_alloc() calls INIT_LIST_HEAD(&session->file_set.files_list) followed immediately by luo_file_set_init(), which also performs INIT_LIST_HEAD() on the same list head. Remove the duplicate call. Signed-off-by: Hongfu Li Reviewed-by: Pratyush Yadav Link: https://patch.msgid.link/20260714074056.95335-1-hongfu.li@linux.dev Signed-off-by: Mike Rapoport (Microsoft) --- kernel/liveupdate/luo_session.c | 1 - 1 file changed, 1 deletion(-) diff --git a/kernel/liveupdate/luo_session.c b/kernel/liveupdate/luo_session.c index b79b2a488974..90ae3810e6cf 100644 --- a/kernel/liveupdate/luo_session.c +++ b/kernel/liveupdate/luo_session.c @@ -154,7 +154,6 @@ static struct luo_session *luo_session_alloc(const char *name) return ERR_PTR(-ENOMEM); strscpy(session->name, name, sizeof(session->name)); - INIT_LIST_HEAD(&session->file_set.files_list); luo_file_set_init(&session->file_set); INIT_LIST_HEAD(&session->list); mutex_init(&session->mutex); From 05cf3d87a0bf23e328a7f7db488860fe14acc335 Mon Sep 17 00:00:00 2001 From: Jackie Liu Date: Thu, 16 Jul 2026 09:26:07 +0800 Subject: [PATCH 3/8] liveupdate: reject nonzero reserved value for SESSION_FINISH The UAPI documents liveupdate_session_finish::reserved as requiring zero, but luo_session_finish() currently ignores it and finishes the session. Accepting nonzero values prevents the field from being safely repurposed by a future extension. Reject nonzero reserved values before changing session state, matching LIVEUPDATE_SESSION_GET_NAME. Fixes: 16cec0d26521 ("liveupdate: luo_session: add ioctls for file preservation") Assisted-by: Codex:gpt-5.6-sol Reviewed-by: Pratyush Yadav (Google) Signed-off-by: Jackie Liu Link: https://patch.msgid.link/20260716012607.22020-2-liu.yun@linux.dev Signed-off-by: Mike Rapoport (Microsoft) --- kernel/liveupdate/luo_session.c | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/kernel/liveupdate/luo_session.c b/kernel/liveupdate/luo_session.c index f38b5b18f3f8..b4a9f55c4498 100644 --- a/kernel/liveupdate/luo_session.c +++ b/kernel/liveupdate/luo_session.c @@ -316,8 +316,12 @@ static int luo_session_finish(struct luo_session *session, struct luo_ucmd *ucmd) { struct liveupdate_session_finish *argp = ucmd->cmd; - int err = luo_session_finish_one(session); + int err; + if (argp->reserved) + return -EINVAL; + + err = luo_session_finish_one(session); if (err) return err; From 36882f3392395704c8a3fe7fac831fb6f5737e7d Mon Sep 17 00:00:00 2001 From: David Matlack Date: Thu, 28 May 2026 17:41:39 +0000 Subject: [PATCH 4/8] liveupdate: Reference count outgoing FLB data Increment the outgoing FLB refcount in liveupdate_flb_get_outgoing() so that the FLB structure cannot be freed while the caller is actively using it. Add an additional liveupdate_flb_put_outgoing() function so the caller can explicitly indicate when it is done using the outgoing FLB. During a Live Update, the kernel may need to fetch the outgoing FLB outside of the scope of a file handler's preserve() and unpreserve() callbacks. In that situation there is no way for the caller to protect itself against the outgoing FLB from being freed while it is using it. Incrementing the reference count in liveupdate_flb_get_outgoing() ensures it cannot be freed. This change also aligns the outgoing FLB lifecycle management with the incoming FLB, since the latter uses the same get/put semantics. Fixes: cab056f2aae7 ("liveupdate: luo_flb: introduce File-Lifecycle-Bound global state") Assisted-by: Gemini:gemini-3-pro-preview Signed-off-by: David Matlack Reviewed-by: Pasha Tatashin Link: https://patch.msgid.link/20260528174140.1921129-2-dmatlack@google.com Signed-off-by: Pasha Tatashin Signed-off-by: Mike Rapoport (Microsoft) --- include/linux/liveupdate.h | 5 +++++ kernel/liveupdate/luo_flb.c | 10 +++++++--- 2 files changed, 12 insertions(+), 3 deletions(-) diff --git a/include/linux/liveupdate.h b/include/linux/liveupdate.h index 88722e5caf02..c344bf987b63 100644 --- a/include/linux/liveupdate.h +++ b/include/linux/liveupdate.h @@ -243,6 +243,7 @@ int liveupdate_flb_get_incoming(struct liveupdate_flb *flb, void **objp); void liveupdate_flb_put_incoming(struct liveupdate_flb *flb); int liveupdate_flb_get_outgoing(struct liveupdate_flb *flb, void **objp); +void liveupdate_flb_put_outgoing(struct liveupdate_flb *flb); #else /* CONFIG_LIVEUPDATE */ @@ -292,5 +293,9 @@ static inline int liveupdate_flb_get_outgoing(struct liveupdate_flb *flb, return -EOPNOTSUPP; } +static inline void liveupdate_flb_put_outgoing(struct liveupdate_flb *flb) +{ +} + #endif /* CONFIG_LIVEUPDATE */ #endif /* _LINUX_LIVEUPDATE_H */ diff --git a/kernel/liveupdate/luo_flb.c b/kernel/liveupdate/luo_flb.c index 5c27134ce7ba..02b449e1e98b 100644 --- a/kernel/liveupdate/luo_flb.c +++ b/kernel/liveupdate/luo_flb.c @@ -133,7 +133,7 @@ static int luo_flb_file_preserve_one(struct liveupdate_flb *flb) return 0; } -static void luo_flb_file_unpreserve_one(struct liveupdate_flb *flb) +void liveupdate_flb_put_outgoing(struct liveupdate_flb *flb) { struct luo_flb_private *private = luo_flb_get_private(flb); @@ -264,7 +264,7 @@ int luo_flb_file_preserve(struct liveupdate_file_handler *fh) exit_err: list_for_each_entry_continue_reverse(iter, flb_list, list) - luo_flb_file_unpreserve_one(iter->flb); + liveupdate_flb_put_outgoing(iter->flb); up_read(&luo_register_rwlock); return err; @@ -289,7 +289,7 @@ void luo_flb_file_unpreserve(struct liveupdate_file_handler *fh) guard(rwsem_read)(&luo_register_rwlock); list_for_each_entry_reverse(iter, flb_list, list) - luo_flb_file_unpreserve_one(iter->flb); + liveupdate_flb_put_outgoing(iter->flb); } /** @@ -544,6 +544,10 @@ int liveupdate_flb_get_outgoing(struct liveupdate_flb *flb, void **objp) return -EOPNOTSUPP; guard(mutex)(&private->outgoing.lock); + if (!private->outgoing.obj) + return -ENOENT; + + refcount_inc(&private->outgoing.count); *objp = private->outgoing.obj; return 0; From 5c4a03afcb21783987ffc64562b76ddd5a21b12b Mon Sep 17 00:00:00 2001 From: David Matlack Date: Thu, 28 May 2026 17:41:40 +0000 Subject: [PATCH 5/8] liveupdate: Remember FLB retrieve() status LUO keeps track of successful retrieve attempts on an FLB. It does so to avoid multiple retrievals of the same FLB. Multiple retrievals cause problems because once the FLB is retrieved, the serialized data structures are likely freed and the FLB is likely in a very different state from what the code expects. All this works well when retrieve succeeds. When it fails, luo_flb_retrieve_one() returns the error immediately, without ever storing anywhere that a retrieve was attempted or what its error code was. If the user attempts to retrieve another file registered with the same FLB, LUO will attempt to call the FLB's retrieve() callback again. The retry is problematic for much of the same reasons listed above. The FLB is likely in a very different state than what the retrieve logic normally expects (e.g. some KHO pages may have already been restored and freed). There is no sane way of attempting the retrieve again. Remember the error retrieve returned and directly return it on a retry. This is done by changing the retrieved bool to a retrieve_status integer. A value of 0 means retrieve was never attempted, a positive value means it succeeded, and a negative value means it failed and the error code is the value. This is similar to commit f85b1c6af5bc ("liveupdate: luo_file: remember retrieve() status") which did the same for LUO files. Fixes: cab056f2aae7 ("liveupdate: luo_flb: introduce File-Lifecycle-Bound global state") Assisted-by: Gemini:gemini-3-pro-preview Signed-off-by: David Matlack Reviewed-by: Pasha Tatashin Reviewed-by: Pratyush Yadav (Google) Link: https://patch.msgid.link/20260528174140.1921129-3-dmatlack@google.com Signed-off-by: Pasha Tatashin Signed-off-by: Mike Rapoport (Microsoft) --- include/linux/liveupdate.h | 6 ++++-- kernel/liveupdate/luo_flb.c | 10 +++++++--- 2 files changed, 11 insertions(+), 5 deletions(-) diff --git a/include/linux/liveupdate.h b/include/linux/liveupdate.h index c344bf987b63..63ea5417de84 100644 --- a/include/linux/liveupdate.h +++ b/include/linux/liveupdate.h @@ -173,7 +173,9 @@ struct liveupdate_flb_ops { * @lock: A mutex that protects all fields within this structure, providing * the synchronization service for the FLB's ops. * @finished: True once the FLB's finish() callback has run. - * @retrieved: True once the FLB's retrieve() callback has run. + * @retrieve_status: Status code indicating whether retrieve() has been + * attempted. 0 means not attempted, 1 means successful, + * and negative value means it failed with that error code. */ struct luo_flb_private_state { refcount_t count; @@ -181,7 +183,7 @@ struct luo_flb_private_state { void *obj; struct mutex lock; bool finished; - bool retrieved; + int retrieve_status; }; /* diff --git a/kernel/liveupdate/luo_flb.c b/kernel/liveupdate/luo_flb.c index 02b449e1e98b..cd715a7c1d99 100644 --- a/kernel/liveupdate/luo_flb.c +++ b/kernel/liveupdate/luo_flb.c @@ -168,7 +168,10 @@ static int luo_flb_retrieve_one(struct liveupdate_flb *flb) if (private->incoming.finished) return -ENODATA; - if (private->incoming.retrieved) + if (private->incoming.retrieve_status < 0) + return private->incoming.retrieve_status; + + if (private->incoming.retrieve_status > 0) return 0; if (!fh->active) @@ -194,12 +197,13 @@ static int luo_flb_retrieve_one(struct liveupdate_flb *flb) err = flb->ops->retrieve(&args); if (err) { + private->incoming.retrieve_status = err; module_put(flb->ops->owner); return err; } private->incoming.obj = args.obj; - private->incoming.retrieved = true; + private->incoming.retrieve_status = 1; return 0; } @@ -213,7 +217,7 @@ void liveupdate_flb_put_incoming(struct liveupdate_flb *flb) if (!refcount_dec_and_test(&private->incoming.count)) return; - if (!private->incoming.retrieved) { + if (private->incoming.retrieve_status <= 0) { int err = luo_flb_retrieve_one(flb); if (WARN_ON(err)) From 2f3f53a3735a7e67bbe9e580cb49372bf9261967 Mon Sep 17 00:00:00 2001 From: Vipin Sharma Date: Mon, 20 Jul 2026 13:32:01 -0700 Subject: [PATCH 6/8] selftests/liveupdate: Use luo_test_utils.c for liveupdate ioctl APIs Use luo_test_utils.c for all live update ioctl calls. Remove direct ioctl calls in liveupdate.c. This avoids code duplication and use common interface. While at it, make ioctl error check stricter as liveupdate APIs don't return postive numbers as a valid result. Co-developed-by: David Matlack Signed-off-by: David Matlack Reviewed-by: Pasha Tatashin Reviewed-by: Pratyush Yadav (Google) Signed-off-by: Vipin Sharma Link: https://patch.msgid.link/20260720203202.1964557-2-vipinsh@google.com Signed-off-by: Mike Rapoport (Microsoft) --- .../testing/selftests/liveupdate/liveupdate.c | 104 +++++------------- .../selftests/liveupdate/luo_test_utils.c | 61 ++++++++-- .../selftests/liveupdate/luo_test_utils.h | 3 + 3 files changed, 83 insertions(+), 85 deletions(-) diff --git a/tools/testing/selftests/liveupdate/liveupdate.c b/tools/testing/selftests/liveupdate/liveupdate.c index 502fb3567e38..5c7ed2512710 100644 --- a/tools/testing/selftests/liveupdate/liveupdate.c +++ b/tools/testing/selftests/liveupdate/liveupdate.c @@ -89,36 +89,6 @@ TEST_F(liveupdate_device, exclusive_open) EXPECT_EQ(errno, EBUSY); } -/* Helper function to create a LUO session via ioctl. */ -static int create_session(int lu_fd, const char *name) -{ - struct liveupdate_ioctl_create_session args = {}; - - args.size = sizeof(args); - strncpy((char *)args.name, name, sizeof(args.name) - 1); - - if (ioctl(lu_fd, LIVEUPDATE_IOCTL_CREATE_SESSION, &args)) - return -errno; - - return args.fd; -} - -/* Helper function to get a session name via ioctl. */ -static int get_session_name(int session_fd, char *name, size_t name_len) -{ - struct liveupdate_session_get_name args = {}; - - args.size = sizeof(args); - - if (ioctl(session_fd, LIVEUPDATE_SESSION_GET_NAME, &args)) - return -errno; - - strncpy(name, (char *)args.name, name_len - 1); - name[name_len - 1] = '\0'; - - return 0; -} - /* * Test Case: Create Duplicate Session * @@ -135,10 +105,10 @@ TEST_F(liveupdate_device, create_duplicate_session) ASSERT_GE(self->fd1, 0); - session_fd1 = create_session(self->fd1, "duplicate-session-test"); + session_fd1 = luo_create_session(self->fd1, "duplicate-session-test"); ASSERT_GE(session_fd1, 0); - session_fd2 = create_session(self->fd1, "duplicate-session-test"); + session_fd2 = luo_create_session(self->fd1, "duplicate-session-test"); EXPECT_LT(session_fd2, 0); EXPECT_EQ(-session_fd2, EEXIST); @@ -160,30 +130,16 @@ TEST_F(liveupdate_device, create_distinct_sessions) ASSERT_GE(self->fd1, 0); - session_fd1 = create_session(self->fd1, "distinct-session-1"); + session_fd1 = luo_create_session(self->fd1, "distinct-session-1"); ASSERT_GE(session_fd1, 0); - session_fd2 = create_session(self->fd1, "distinct-session-2"); + session_fd2 = luo_create_session(self->fd1, "distinct-session-2"); ASSERT_GE(session_fd2, 0); ASSERT_EQ(close(session_fd1), 0); ASSERT_EQ(close(session_fd2), 0); } -static int preserve_fd(int session_fd, int fd_to_preserve, __u64 token) -{ - struct liveupdate_session_preserve_fd args = {}; - - args.size = sizeof(args); - args.fd = fd_to_preserve; - args.token = token; - - if (ioctl(session_fd, LIVEUPDATE_SESSION_PRESERVE_FD, &args)) - return -errno; - - return 0; -} - /* * Test Case: Preserve MemFD * @@ -201,14 +157,14 @@ TEST_F(liveupdate_device, preserve_memfd) SKIP(return, "%s does not exist", LIVEUPDATE_DEV); ASSERT_GE(self->fd1, 0); - session_fd = create_session(self->fd1, "preserve-memfd-test"); + session_fd = luo_create_session(self->fd1, "preserve-memfd-test"); ASSERT_GE(session_fd, 0); mem_fd = memfd_create("test-memfd", 0); ASSERT_GE(mem_fd, 0); ASSERT_EQ(write(mem_fd, test_str, strlen(test_str)), strlen(test_str)); - ASSERT_EQ(preserve_fd(session_fd, mem_fd, 0x1234), 0); + ASSERT_EQ(luo_session_preserve_fd(session_fd, mem_fd, 0x1234), 0); ASSERT_EQ(close(session_fd), 0); ASSERT_EQ(lseek(mem_fd, 0, SEEK_SET), 0); @@ -236,7 +192,7 @@ TEST_F(liveupdate_device, preserve_multiple_memfds) SKIP(return, "%s does not exist", LIVEUPDATE_DEV); ASSERT_GE(self->fd1, 0); - session_fd = create_session(self->fd1, "preserve-multi-memfd-test"); + session_fd = luo_create_session(self->fd1, "preserve-multi-memfd-test"); ASSERT_GE(session_fd, 0); mem_fd1 = memfd_create("test-memfd-1", 0); @@ -247,8 +203,8 @@ TEST_F(liveupdate_device, preserve_multiple_memfds) ASSERT_EQ(write(mem_fd1, test_str1, strlen(test_str1)), strlen(test_str1)); ASSERT_EQ(write(mem_fd2, test_str2, strlen(test_str2)), strlen(test_str2)); - ASSERT_EQ(preserve_fd(session_fd, mem_fd1, 0xAAAA), 0); - ASSERT_EQ(preserve_fd(session_fd, mem_fd2, 0xBBBB), 0); + ASSERT_EQ(luo_session_preserve_fd(session_fd, mem_fd1, 0xAAAA), 0); + ASSERT_EQ(luo_session_preserve_fd(session_fd, mem_fd2, 0xBBBB), 0); memset(read_buf, 0, sizeof(read_buf)); ASSERT_EQ(lseek(mem_fd1, 0, SEEK_SET), 0); @@ -284,9 +240,9 @@ TEST_F(liveupdate_device, preserve_complex_scenario) SKIP(return, "%s does not exist", LIVEUPDATE_DEV); ASSERT_GE(self->fd1, 0); - session_fd1 = create_session(self->fd1, "complex-session-1"); + session_fd1 = luo_create_session(self->fd1, "complex-session-1"); ASSERT_GE(session_fd1, 0); - session_fd2 = create_session(self->fd1, "complex-session-2"); + session_fd2 = luo_create_session(self->fd1, "complex-session-2"); ASSERT_GE(session_fd2, 0); mem_fd_data1 = memfd_create("data1", 0); @@ -303,10 +259,10 @@ TEST_F(liveupdate_device, preserve_complex_scenario) mem_fd_empty2 = memfd_create("empty2", 0); ASSERT_GE(mem_fd_empty2, 0); - ASSERT_EQ(preserve_fd(session_fd1, mem_fd_data1, 0x1111), 0); - ASSERT_EQ(preserve_fd(session_fd1, mem_fd_empty1, 0x2222), 0); - ASSERT_EQ(preserve_fd(session_fd2, mem_fd_data2, 0x3333), 0); - ASSERT_EQ(preserve_fd(session_fd2, mem_fd_empty2, 0x4444), 0); + ASSERT_EQ(luo_session_preserve_fd(session_fd1, mem_fd_data1, 0x1111), 0); + ASSERT_EQ(luo_session_preserve_fd(session_fd1, mem_fd_empty1, 0x2222), 0); + ASSERT_EQ(luo_session_preserve_fd(session_fd2, mem_fd_data2, 0x3333), 0); + ASSERT_EQ(luo_session_preserve_fd(session_fd2, mem_fd_empty2, 0x4444), 0); ASSERT_EQ(lseek(mem_fd_data1, 0, SEEK_SET), 0); ASSERT_EQ(read(mem_fd_data1, read_buf, sizeof(read_buf)), strlen(data1)); @@ -349,13 +305,13 @@ TEST_F(liveupdate_device, preserve_unsupported_fd) SKIP(return, "%s does not exist", LIVEUPDATE_DEV); ASSERT_GE(self->fd1, 0); - session_fd = create_session(self->fd1, "unsupported-fd-test"); + session_fd = luo_create_session(self->fd1, "unsupported-fd-test"); ASSERT_GE(session_fd, 0); unsupported_fd = open("/dev/null", O_RDWR); ASSERT_GE(unsupported_fd, 0); - ret = preserve_fd(session_fd, unsupported_fd, 0xDEAD); + ret = luo_session_preserve_fd(session_fd, unsupported_fd, 0xDEAD); EXPECT_EQ(ret, -ENOENT); ASSERT_EQ(close(unsupported_fd), 0); @@ -379,23 +335,23 @@ TEST_F(liveupdate_device, prevent_double_preservation) SKIP(return, "%s does not exist", LIVEUPDATE_DEV); ASSERT_GE(self->fd1, 0); - session_fd1 = create_session(self->fd1, "double-preserve-session-1"); + session_fd1 = luo_create_session(self->fd1, "double-preserve-session-1"); ASSERT_GE(session_fd1, 0); - session_fd2 = create_session(self->fd1, "double-preserve-session-2"); + session_fd2 = luo_create_session(self->fd1, "double-preserve-session-2"); ASSERT_GE(session_fd2, 0); mem_fd = memfd_create("test-memfd", 0); ASSERT_GE(mem_fd, 0); /* First preservation should succeed */ - ASSERT_EQ(preserve_fd(session_fd1, mem_fd, 0x1111), 0); + ASSERT_EQ(luo_session_preserve_fd(session_fd1, mem_fd, 0x1111), 0); /* Second preservation in a different session should fail with EBUSY */ - ret = preserve_fd(session_fd2, mem_fd, 0x2222); + ret = luo_session_preserve_fd(session_fd2, mem_fd, 0x2222); EXPECT_EQ(ret, -EBUSY); /* Second preservation in the same session (different token) should fail with EBUSY */ - ret = preserve_fd(session_fd1, mem_fd, 0x3333); + ret = luo_session_preserve_fd(session_fd1, mem_fd, 0x3333); EXPECT_EQ(ret, -EBUSY); ASSERT_EQ(close(mem_fd), 0); @@ -441,7 +397,7 @@ TEST_F(liveupdate_device, create_session_empty_name) SKIP(return, "%s does not exist", LIVEUPDATE_DEV); ASSERT_GE(self->fd1, 0); - session_fd = create_session(self->fd1, ""); + session_fd = luo_create_session(self->fd1, ""); EXPECT_EQ(session_fd, -EINVAL); } @@ -462,10 +418,10 @@ TEST_F(liveupdate_device, get_session_name) SKIP(return, "%s does not exist", LIVEUPDATE_DEV); ASSERT_GE(self->fd1, 0); - session_fd = create_session(self->fd1, session_name); + session_fd = luo_create_session(self->fd1, session_name); ASSERT_GE(session_fd, 0); - ASSERT_EQ(get_session_name(session_fd, name_buf, sizeof(name_buf)), 0); + ASSERT_EQ(luo_get_session_name(session_fd, name_buf, sizeof(name_buf)), 0); ASSERT_STREQ(name_buf, session_name); ASSERT_EQ(close(session_fd), 0); @@ -491,10 +447,10 @@ TEST_F(liveupdate_device, get_session_name_max_length) SKIP(return, "%s does not exist", LIVEUPDATE_DEV); ASSERT_GE(self->fd1, 0); - session_fd = create_session(self->fd1, long_name); + session_fd = luo_create_session(self->fd1, long_name); ASSERT_GE(session_fd, 0); - ASSERT_EQ(get_session_name(session_fd, name_buf, sizeof(name_buf)), 0); + ASSERT_EQ(luo_get_session_name(session_fd, name_buf, sizeof(name_buf)), 0); ASSERT_STREQ(name_buf, long_name); ASSERT_EQ(close(session_fd), 0); @@ -528,7 +484,7 @@ TEST_F(liveupdate_device, preserve_many_sessions) char name[64]; snprintf(name, sizeof(name), "many-session-%d", i); - session_fds[i] = create_session(self->fd1, name); + session_fds[i] = luo_create_session(self->fd1, name); ASSERT_GE(session_fds[i], 0); } @@ -554,7 +510,7 @@ TEST_F(liveupdate_device, preserve_many_files) SKIP(return, "%s does not exist", LIVEUPDATE_DEV); ASSERT_GE(self->fd1, 0); - session_fd = create_session(self->fd1, "many-files-test"); + session_fd = luo_create_session(self->fd1, "many-files-test"); ASSERT_GE(session_fd, 0); ret = luo_ensure_nofile_limit(MANY_FILES + 10); @@ -565,7 +521,7 @@ TEST_F(liveupdate_device, preserve_many_files) for (i = 0; i < MANY_FILES; i++) { mem_fds[i] = memfd_create("test-memfd", 0); ASSERT_GE(mem_fds[i], 0); - ASSERT_EQ(preserve_fd(session_fd, mem_fds[i], i), 0); + ASSERT_EQ(luo_session_preserve_fd(session_fd, mem_fds[i], i), 0); } for (i = 0; i < MANY_FILES; i++) diff --git a/tools/testing/selftests/liveupdate/luo_test_utils.c b/tools/testing/selftests/liveupdate/luo_test_utils.c index 1439b2b48ed0..885712a65075 100644 --- a/tools/testing/selftests/liveupdate/luo_test_utils.c +++ b/tools/testing/selftests/liveupdate/luo_test_utils.c @@ -60,7 +60,7 @@ int luo_create_session(int luo_fd, const char *name) snprintf((char *)arg.name, LIVEUPDATE_SESSION_NAME_LENGTH, "%.*s", LIVEUPDATE_SESSION_NAME_LENGTH - 1, name); - if (ioctl(luo_fd, LIVEUPDATE_IOCTL_CREATE_SESSION, &arg) < 0) + if (ioctl(luo_fd, LIVEUPDATE_IOCTL_CREATE_SESSION, &arg)) return -errno; return arg.fd; @@ -73,15 +73,57 @@ int luo_retrieve_session(int luo_fd, const char *name) snprintf((char *)arg.name, LIVEUPDATE_SESSION_NAME_LENGTH, "%.*s", LIVEUPDATE_SESSION_NAME_LENGTH - 1, name); - if (ioctl(luo_fd, LIVEUPDATE_IOCTL_RETRIEVE_SESSION, &arg) < 0) + if (ioctl(luo_fd, LIVEUPDATE_IOCTL_RETRIEVE_SESSION, &arg)) return -errno; return arg.fd; } +int luo_session_preserve_fd(int session_fd, int fd, __u64 token) +{ + struct liveupdate_session_preserve_fd arg = { + .size = sizeof(arg), + .fd = fd, + .token = token, + }; + + if (ioctl(session_fd, LIVEUPDATE_SESSION_PRESERVE_FD, &arg)) + return -errno; + + return 0; +} + +int luo_session_retrieve_fd(int session_fd, __u64 token) +{ + struct liveupdate_session_retrieve_fd arg = { + .size = sizeof(arg), + .token = token, + }; + + if (ioctl(session_fd, LIVEUPDATE_SESSION_RETRIEVE_FD, &arg)) + return -errno; + + return arg.fd; +} + +/* Helper function to get a session name via ioctl. */ +int luo_get_session_name(int session_fd, char *name, size_t name_len) +{ + struct liveupdate_session_get_name args = {}; + + args.size = sizeof(args); + + if (ioctl(session_fd, LIVEUPDATE_SESSION_GET_NAME, &args)) + return -errno; + + strncpy(name, (char *)args.name, name_len - 1); + name[name_len - 1] = '\0'; + + return 0; +} + int create_and_preserve_memfd(int session_fd, int token, const char *data) { - struct liveupdate_session_preserve_fd arg = { .size = sizeof(arg) }; long page_size = getpagesize(); void *map = MAP_FAILED; int mfd = -1, ret = -1; @@ -100,9 +142,8 @@ int create_and_preserve_memfd(int session_fd, int token, const char *data) snprintf(map, page_size, "%s", data); munmap(map, page_size); - arg.fd = mfd; - arg.token = token; - if (ioctl(session_fd, LIVEUPDATE_SESSION_PRESERVE_FD, &arg) < 0) + ret = luo_session_preserve_fd(session_fd, mfd, token); + if (ret) goto out; ret = 0; @@ -117,15 +158,13 @@ int create_and_preserve_memfd(int session_fd, int token, const char *data) int restore_and_verify_memfd(int session_fd, int token, const char *expected_data) { - struct liveupdate_session_retrieve_fd arg = { .size = sizeof(arg) }; long page_size = getpagesize(); void *map = MAP_FAILED; int mfd = -1, ret = -1; - arg.token = token; - if (ioctl(session_fd, LIVEUPDATE_SESSION_RETRIEVE_FD, &arg) < 0) - return -errno; - mfd = arg.fd; + mfd = luo_session_retrieve_fd(session_fd, token); + if (mfd < 0) + return mfd; map = mmap(NULL, page_size, PROT_READ, MAP_SHARED, mfd, 0); if (map == MAP_FAILED) diff --git a/tools/testing/selftests/liveupdate/luo_test_utils.h b/tools/testing/selftests/liveupdate/luo_test_utils.h index 6a0d85386613..49931ab90593 100644 --- a/tools/testing/selftests/liveupdate/luo_test_utils.h +++ b/tools/testing/selftests/liveupdate/luo_test_utils.h @@ -25,8 +25,11 @@ int luo_open_device(void); int luo_create_session(int luo_fd, const char *name); int luo_retrieve_session(int luo_fd, const char *name); int luo_session_finish(int session_fd); +int luo_get_session_name(int session_fd, char *name, size_t name_len); int luo_ensure_nofile_limit(long min_limit); +int luo_session_preserve_fd(int session_fd, int fd, __u64 token); +int luo_session_retrieve_fd(int session_fd, __u64 token); int create_and_preserve_memfd(int session_fd, int token, const char *data); int restore_and_verify_memfd(int session_fd, int token, const char *expected_data); From df1a0d068d481b7d175c5af3970043206409a062 Mon Sep 17 00:00:00 2001 From: Vipin Sharma Date: Mon, 20 Jul 2026 13:32:02 -0700 Subject: [PATCH 7/8] selftests/liveupdate: Move luo_test_utils.* into a reusable library Move luo_test_utils.[ch] into a lib/ directory and make libliveupdate library. Pull the rules to build them out into a separate libliveupdate.mk script. This will enable these utilities to be also built by and used within other selftests (such as VFIO). Update path in vmtest.sh as that one uses hardcoded path for util. No functional change intended. Co-developed-by: David Matlack Signed-off-by: David Matlack Acked-by: Pratyush Yadav (Google) Reviewed-by: Pasha Tatashin Signed-off-by: Vipin Sharma Link: https://patch.msgid.link/20260720203202.1964557-3-vipinsh@google.com Signed-off-by: Mike Rapoport (Microsoft) --- tools/testing/selftests/liveupdate/.gitignore | 1 + tools/testing/selftests/liveupdate/Makefile | 14 ++++--------- .../include/libliveupdate.h} | 8 ++++---- .../selftests/liveupdate/lib/libliveupdate.mk | 20 +++++++++++++++++++ .../{luo_test_utils.c => lib/lu_utils.c} | 2 +- .../testing/selftests/liveupdate/liveupdate.c | 2 +- .../selftests/liveupdate/luo_kexec_simple.c | 2 +- .../selftests/liveupdate/luo_multi_session.c | 2 +- .../selftests/liveupdate/luo_stress_files.c | 3 ++- .../liveupdate/luo_stress_sessions.c | 3 ++- tools/testing/selftests/liveupdate/vmtest.sh | 4 ++-- 11 files changed, 39 insertions(+), 22 deletions(-) rename tools/testing/selftests/liveupdate/{luo_test_utils.h => lib/include/libliveupdate.h} (89%) create mode 100644 tools/testing/selftests/liveupdate/lib/libliveupdate.mk rename tools/testing/selftests/liveupdate/{luo_test_utils.c => lib/lu_utils.c} (99%) diff --git a/tools/testing/selftests/liveupdate/.gitignore b/tools/testing/selftests/liveupdate/.gitignore index cb08ddb0dfee..47c670bf532b 100644 --- a/tools/testing/selftests/liveupdate/.gitignore +++ b/tools/testing/selftests/liveupdate/.gitignore @@ -3,6 +3,7 @@ !/**/ !*.c !*.h +!*.mk !*.sh !.gitignore !config diff --git a/tools/testing/selftests/liveupdate/Makefile b/tools/testing/selftests/liveupdate/Makefile index 30689d22cb02..634211c66652 100644 --- a/tools/testing/selftests/liveupdate/Makefile +++ b/tools/testing/selftests/liveupdate/Makefile @@ -1,7 +1,5 @@ # SPDX-License-Identifier: GPL-2.0-only -LIB_C += luo_test_utils.c - TEST_GEN_PROGS += liveupdate TEST_GEN_PROGS_EXTENDED += luo_kexec_simple @@ -12,25 +10,21 @@ TEST_GEN_PROGS_EXTENDED += luo_stress_files TEST_FILES += do_kexec.sh include ../lib.mk +include lib/libliveupdate.mk CFLAGS += $(KHDR_INCLUDES) CFLAGS += -Wall -O2 -Wno-unused-function CFLAGS += -MD -LIB_O := $(patsubst %.c, $(OUTPUT)/%.o, $(LIB_C)) TEST_O := $(patsubst %, %.o, $(TEST_GEN_PROGS)) TEST_O += $(patsubst %, %.o, $(TEST_GEN_PROGS_EXTENDED)) -TEST_DEP_FILES := $(patsubst %.o, %.d, $(LIB_O)) +TEST_DEP_FILES := $(patsubst %.o, %.d, $(LIBLIVEUPDATE_O)) TEST_DEP_FILES += $(patsubst %.o, %.d, $(TEST_O)) -include $(TEST_DEP_FILES) -$(LIB_O): $(OUTPUT)/%.o: %.c - $(CC) $(CFLAGS) $(CPPFLAGS) $(TARGET_ARCH) -c $< -o $@ +$(TEST_GEN_PROGS) $(TEST_GEN_PROGS_EXTENDED): $(OUTPUT)/%: %.o $(LIBLIVEUPDATE_O) + $(CC) $(CFLAGS) $(CPPFLAGS) $(LDFLAGS) $(TARGET_ARCH) $< $(LIBLIVEUPDATE_O) $(LDLIBS) -o $@ -$(TEST_GEN_PROGS) $(TEST_GEN_PROGS_EXTENDED): $(OUTPUT)/%: %.o $(LIB_O) - $(CC) $(CFLAGS) $(CPPFLAGS) $(LDFLAGS) $(TARGET_ARCH) $< $(LIB_O) $(LDLIBS) -o $@ - -EXTRA_CLEAN += $(LIB_O) EXTRA_CLEAN += $(TEST_O) EXTRA_CLEAN += $(TEST_DEP_FILES) diff --git a/tools/testing/selftests/liveupdate/luo_test_utils.h b/tools/testing/selftests/liveupdate/lib/include/libliveupdate.h similarity index 89% rename from tools/testing/selftests/liveupdate/luo_test_utils.h rename to tools/testing/selftests/liveupdate/lib/include/libliveupdate.h index 49931ab90593..fa07fed08364 100644 --- a/tools/testing/selftests/liveupdate/luo_test_utils.h +++ b/tools/testing/selftests/liveupdate/lib/include/libliveupdate.h @@ -7,13 +7,13 @@ * Utility functions for LUO kselftests. */ -#ifndef LUO_TEST_UTILS_H -#define LUO_TEST_UTILS_H +#ifndef SELFTESTS_LIVEUPDATE_LIB_LIVEUPDATE_H +#define SELFTESTS_LIVEUPDATE_LIB_LIVEUPDATE_H #include #include #include -#include "../kselftest.h" +#include "../../../kselftest.h" #define LUO_DEVICE "/dev/liveupdate" @@ -46,4 +46,4 @@ typedef void (*luo_test_stage2_fn)(int luo_fd, int state_session_fd); int luo_test(int argc, char *argv[], const char *state_session_name, luo_test_stage1_fn stage1, luo_test_stage2_fn stage2); -#endif /* LUO_TEST_UTILS_H */ +#endif /* SELFTESTS_LIVEUPDATE_LIB_LIVEUPDATE_H */ diff --git a/tools/testing/selftests/liveupdate/lib/libliveupdate.mk b/tools/testing/selftests/liveupdate/lib/libliveupdate.mk new file mode 100644 index 000000000000..634cd4c16c47 --- /dev/null +++ b/tools/testing/selftests/liveupdate/lib/libliveupdate.mk @@ -0,0 +1,20 @@ +include $(top_srcdir)/scripts/subarch.include +ARCH ?= $(SUBARCH) + +LIBLIVEUPDATE_SRCDIR := $(selfdir)/liveupdate/lib + +LIBLIVEUPDATE_C := lu_utils.c + +LIBLIVEUPDATE_OUTPUT := $(OUTPUT)/libliveupdate + +LIBLIVEUPDATE_O := $(patsubst %.c, $(LIBLIVEUPDATE_OUTPUT)/%.o, $(LIBLIVEUPDATE_C)) + +CFLAGS += -I$(LIBLIVEUPDATE_SRCDIR)/include + +$(LIBLIVEUPDATE_OUTPUT): + $(Q)mkdir -p $@ + +$(LIBLIVEUPDATE_O): $(LIBLIVEUPDATE_OUTPUT)/%.o : $(LIBLIVEUPDATE_SRCDIR)/%.c | $(LIBLIVEUPDATE_OUTPUT) + $(CC) $(CFLAGS) $(CPPFLAGS) $(TARGET_ARCH) -c $< -o $@ + +EXTRA_CLEAN += $(LIBLIVEUPDATE_OUTPUT) diff --git a/tools/testing/selftests/liveupdate/luo_test_utils.c b/tools/testing/selftests/liveupdate/lib/lu_utils.c similarity index 99% rename from tools/testing/selftests/liveupdate/luo_test_utils.c rename to tools/testing/selftests/liveupdate/lib/lu_utils.c index 885712a65075..74d41115c281 100644 --- a/tools/testing/selftests/liveupdate/luo_test_utils.c +++ b/tools/testing/selftests/liveupdate/lib/lu_utils.c @@ -23,7 +23,7 @@ #include #include -#include "luo_test_utils.h" +#include int luo_open_device(void) { diff --git a/tools/testing/selftests/liveupdate/liveupdate.c b/tools/testing/selftests/liveupdate/liveupdate.c index 5c7ed2512710..2dedd5fc2534 100644 --- a/tools/testing/selftests/liveupdate/liveupdate.c +++ b/tools/testing/selftests/liveupdate/liveupdate.c @@ -24,9 +24,9 @@ #include #include +#include #include -#include "luo_test_utils.h" #include "../kselftest.h" #include "../kselftest_harness.h" diff --git a/tools/testing/selftests/liveupdate/luo_kexec_simple.c b/tools/testing/selftests/liveupdate/luo_kexec_simple.c index d7ac1f3dc4cb..786ac93b9ae3 100644 --- a/tools/testing/selftests/liveupdate/luo_kexec_simple.c +++ b/tools/testing/selftests/liveupdate/luo_kexec_simple.c @@ -8,7 +8,7 @@ * across a single kexec reboot. */ -#include "luo_test_utils.h" +#include #define TEST_SESSION_NAME "test-session" #define TEST_MEMFD_TOKEN 0x1A diff --git a/tools/testing/selftests/liveupdate/luo_multi_session.c b/tools/testing/selftests/liveupdate/luo_multi_session.c index 0ee2d795beef..aac24a5f5ce3 100644 --- a/tools/testing/selftests/liveupdate/luo_multi_session.c +++ b/tools/testing/selftests/liveupdate/luo_multi_session.c @@ -9,7 +9,7 @@ * files. */ -#include "luo_test_utils.h" +#include #define SESSION_EMPTY_1 "multi-test-empty-1" #define SESSION_EMPTY_2 "multi-test-empty-2" diff --git a/tools/testing/selftests/liveupdate/luo_stress_files.c b/tools/testing/selftests/liveupdate/luo_stress_files.c index 0cdf9cd4bac7..a0d48490f4ed 100644 --- a/tools/testing/selftests/liveupdate/luo_stress_files.c +++ b/tools/testing/selftests/liveupdate/luo_stress_files.c @@ -10,7 +10,8 @@ #include #include -#include "luo_test_utils.h" + +#include #define NUM_FILES 500 #define STATE_SESSION_NAME "kexec_many_files_state" diff --git a/tools/testing/selftests/liveupdate/luo_stress_sessions.c b/tools/testing/selftests/liveupdate/luo_stress_sessions.c index f201b1839d1d..278aebabe0e9 100644 --- a/tools/testing/selftests/liveupdate/luo_stress_sessions.c +++ b/tools/testing/selftests/liveupdate/luo_stress_sessions.c @@ -10,7 +10,8 @@ #include #include -#include "luo_test_utils.h" + +#include #define NUM_SESSIONS 2000 #define STATE_SESSION_NAME "kexec_many_state" diff --git a/tools/testing/selftests/liveupdate/vmtest.sh b/tools/testing/selftests/liveupdate/vmtest.sh index b0000fae1461..64fd2ab55b76 100755 --- a/tools/testing/selftests/liveupdate/vmtest.sh +++ b/tools/testing/selftests/liveupdate/vmtest.sh @@ -131,9 +131,9 @@ function mkinitrd() { "$CROSS_COMPILE"gcc -static -O2 -nostdinc -nostdlib \ -I "$headers_dir/include" \ -I "$kernel_dir/tools/include/nolibc" \ - -I "$test_dir" \ + -I "$test_dir/lib/include" \ -o "$workspace_dir/test_binary" \ - "$test_dir/$test_name.c" "$test_dir/luo_test_utils.c" + "$test_dir/$test_name.c" "$test_dir/lib/lu_utils.c" "$CROSS_COMPILE"gcc -s -static -Os -nostdinc -nostdlib \ -fno-asynchronous-unwind-tables -fno-ident \ From 3a0b8fa2eb36afc88b62a95f33f0c77c71fa5ded Mon Sep 17 00:00:00 2001 From: "Pratyush Yadav (Google)" Date: Mon, 27 Jul 2026 17:02:39 +0200 Subject: [PATCH 8/8] kho: fix size calculation in kho_preserved_memory_reserve() kho_preserved_memory_reserve() calculates the size of a preservation by doing 1 << (order + PAGE_SHIFT). Since the '1' is a 32-bit integer, it can only be shifted by 31. That is, it will only work for preservations up to 2 GiB. Larger preservations will trigger undefined behaviour. While preservations larger than 2 GiB can't be obtained via folios currently, they can be obtained via kho_preserve_pages(). For example, memblock reserve_mem uses kho_preserve_pages(). Reservations larger than 2 GiB are valid and will trigger this bug if properly aligned. Fix it by using 1UL for shifting. Fixes: fc33e4b44b27 ("kexec: enable KHO support for memory preservation") Reported-by: Sashiko Cc: stable@vger.kernel.org Signed-off-by: Pratyush Yadav (Google) Link: https://patch.msgid.link/20260727150240.889555-1-pratyush@kernel.org Signed-off-by: Mike Rapoport (Microsoft) --- kernel/liveupdate/kexec_handover.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/kernel/liveupdate/kexec_handover.c b/kernel/liveupdate/kexec_handover.c index 175c08a6e41e..6fad9152387a 100644 --- a/kernel/liveupdate/kexec_handover.c +++ b/kernel/liveupdate/kexec_handover.c @@ -501,7 +501,7 @@ static int __init kho_preserved_memory_reserve(phys_addr_t phys, struct page *page; u64 sz; - sz = 1 << (order + PAGE_SHIFT); + sz = 1UL << (order + PAGE_SHIFT); page = kho_get_preserved_page(phys, order); /* Reserve the memory preserved in KHO in memblock */