lists.openwall.net   lists  /  announce  owl-users  owl-dev  john-users  john-dev  passwdqc-users  yescrypt  popa3d-users  /  oss-security  kernel-hardening  musl  sabotage  tlsify  passwords  /  crypt-dev  xvendor  /  Bugtraq  Full-Disclosure  linux-kernel  linux-netdev  linux-ext4  linux-hardening  linux-cve-announce  PHC 
Open Source and information security mailing list archives
 
Hash Suite: Windows password security audit tool. GUI, reports in PDF.
[<prev] [next>] [<thread-prev] [thread-next>] [day] [month] [year] [list]
Message-ID: <c8b46600-d40f-41b4-a5a3-99300ef1a2eb@linux.dev>
Date: Sun, 16 Nov 2025 10:53:34 -0800
From: Zhu Yanjun <yanjun.zhu@...ux.dev>
To: Pasha Tatashin <pasha.tatashin@...een.com>, pratyush@...nel.org,
 jasonmiu@...gle.com, graf@...zon.com, rppt@...nel.org, dmatlack@...gle.com,
 rientjes@...gle.com, corbet@....net, rdunlap@...radead.org,
 ilpo.jarvinen@...ux.intel.com, kanie@...ux.alibaba.com, ojeda@...nel.org,
 aliceryhl@...gle.com, masahiroy@...nel.org, akpm@...ux-foundation.org,
 tj@...nel.org, yoann.congal@...le.fr, mmaurer@...gle.com,
 roman.gushchin@...ux.dev, chenridong@...wei.com, axboe@...nel.dk,
 mark.rutland@....com, jannh@...gle.com, vincent.guittot@...aro.org,
 hannes@...xchg.org, dan.j.williams@...el.com, david@...hat.com,
 joel.granados@...nel.org, rostedt@...dmis.org, anna.schumaker@...cle.com,
 song@...nel.org, linux@...ssschuh.net, linux-kernel@...r.kernel.org,
 linux-doc@...r.kernel.org, linux-mm@...ck.org, gregkh@...uxfoundation.org,
 tglx@...utronix.de, mingo@...hat.com, bp@...en8.de,
 dave.hansen@...ux.intel.com, x86@...nel.org, hpa@...or.com,
 rafael@...nel.org, dakr@...nel.org, bartosz.golaszewski@...aro.org,
 cw00.choi@...sung.com, myungjoo.ham@...sung.com, yesanishhere@...il.com,
 Jonathan.Cameron@...wei.com, quic_zijuhu@...cinc.com,
 aleksander.lobakin@...el.com, ira.weiny@...el.com,
 andriy.shevchenko@...ux.intel.com, leon@...nel.org, lukas@...ner.de,
 bhelgaas@...gle.com, wagi@...nel.org, djeffery@...hat.com,
 stuart.w.hayes@...il.com, ptyadav@...zon.de, lennart@...ttering.net,
 brauner@...nel.org, linux-api@...r.kernel.org,
 linux-fsdevel@...r.kernel.org, saeedm@...dia.com, ajayachandra@...dia.com,
 jgg@...dia.com, parav@...dia.com, leonro@...dia.com, witu@...dia.com,
 hughd@...gle.com, skhawaja@...gle.com, chrisl@...nel.org
Subject: Re: [PATCH v6 18/20] selftests/liveupdate: Add kexec-based selftest
 for session lifecycle

在 2025/11/15 15:34, Pasha Tatashin 写道:
> Introduce a kexec-based selftest, luo_kexec_simple, to validate the
> end-to-end lifecycle of a Live Update Orchestrator (LUO) session across
> a reboot.
> 
> While existing tests verify the uAPI in a pre-reboot context, this test
> ensures that the core functionality—preserving state via Kexec Handover
> and restoring it in a new kernel—works as expected.
> 
> The test operates in two stages, managing its state across the reboot by
> preserving a dedicated "state session" containing a memfd. This
> mechanism dogfoods the LUO feature itself for state tracking, making the
> test self-contained.
> 
> The test validates the following sequence:
> 
> Stage 1 (Pre-kexec):
>   - Creates a test session (test-session).
>   - Creates and preserves a memfd with a known data pattern into the test
>     session.
>   - Creates the state-tracking session to signal progression to Stage 2.
>   - Executes a kexec reboot via a helper script.
> 
> Stage 2 (Post-kexec):
>   - Retrieves the state-tracking session to confirm it is in the
>     post-reboot stage.
>   - Retrieves the preserved test session.
>   - Restores the memfd from the test session and verifies its contents
>     match the original data pattern written in Stage 1.
>   - Finalizes both the test and state sessions to ensure a clean
>     teardown.
> 
> The test relies on a helper script (do_kexec.sh) to perform the reboot
> and a shared utility library (luo_test_utils.c) for common LUO
> operations, keeping the main test logic clean and focused.
> 
> Signed-off-by: Pasha Tatashin <pasha.tatashin@...een.com>
> ---
>   tools/testing/selftests/liveupdate/.gitignore |   1 +
>   tools/testing/selftests/liveupdate/Makefile   |  32 ++++
>   .../testing/selftests/liveupdate/do_kexec.sh  |  16 ++
>   .../selftests/liveupdate/luo_kexec_simple.c   | 114 ++++++++++++
>   .../selftests/liveupdate/luo_test_utils.c     | 168 ++++++++++++++++++
>   .../selftests/liveupdate/luo_test_utils.h     |  39 ++++
>   6 files changed, 370 insertions(+)
>   create mode 100755 tools/testing/selftests/liveupdate/do_kexec.sh
>   create mode 100644 tools/testing/selftests/liveupdate/luo_kexec_simple.c
>   create mode 100644 tools/testing/selftests/liveupdate/luo_test_utils.c
>   create mode 100644 tools/testing/selftests/liveupdate/luo_test_utils.h
> 
> diff --git a/tools/testing/selftests/liveupdate/.gitignore b/tools/testing/selftests/liveupdate/.gitignore
> index af6e773cf98f..daeef116174d 100644
> --- a/tools/testing/selftests/liveupdate/.gitignore
> +++ b/tools/testing/selftests/liveupdate/.gitignore
> @@ -1 +1,2 @@
>   /liveupdate
> +/luo_kexec_simple
> diff --git a/tools/testing/selftests/liveupdate/Makefile b/tools/testing/selftests/liveupdate/Makefile
> index 2a573c36016e..1563ac84006a 100644
> --- a/tools/testing/selftests/liveupdate/Makefile
> +++ b/tools/testing/selftests/liveupdate/Makefile
> @@ -1,7 +1,39 @@
>   # SPDX-License-Identifier: GPL-2.0-only
> +
> +KHDR_INCLUDES ?= -I../../../../usr/include
>   CFLAGS += -Wall -O2 -Wno-unused-function
>   CFLAGS += $(KHDR_INCLUDES)
> +LDFLAGS += -static
> +OUTPUT ?= .
> +
> +# --- Test Configuration (Edit this section when adding new tests) ---
> +LUO_SHARED_SRCS := luo_test_utils.c
> +LUO_SHARED_HDRS += luo_test_utils.h
> +
> +LUO_MANUAL_TESTS += luo_kexec_simple
> +
> +TEST_FILES += do_kexec.sh
>   
>   TEST_GEN_PROGS += liveupdate
>   
> +# --- Automatic Rule Generation (Do not edit below) ---
> +
> +TEST_GEN_PROGS_EXTENDED += $(LUO_MANUAL_TESTS)
> +
> +# Define the full list of sources for each manual test.
> +$(foreach test,$(LUO_MANUAL_TESTS), \
> +	$(eval $(test)_SOURCES := $(test).c $(LUO_SHARED_SRCS)))
> +
> +# This loop automatically generates an explicit build rule for each manual test.
> +# It includes dependencies on the shared headers and makes the output
> +# executable.
> +# Note the use of '$$' to escape automatic variables for the 'eval' command.
> +$(foreach test,$(LUO_MANUAL_TESTS), \
> +	$(eval $(OUTPUT)/$(test): $($(test)_SOURCES) $(LUO_SHARED_HDRS) \
> +		$(call msg,LINK,,$$@) ; \
> +		$(Q)$(LINK.c) $$^ $(LDLIBS) -o $$@ ; \
> +		$(Q)chmod +x $$@ \
> +	) \
> +)
> +
>   include ../lib.mk
> diff --git a/tools/testing/selftests/liveupdate/do_kexec.sh b/tools/testing/selftests/liveupdate/do_kexec.sh
> new file mode 100755
> index 000000000000..3c7c6cafbef8
> --- /dev/null
> +++ b/tools/testing/selftests/liveupdate/do_kexec.sh
> @@ -0,0 +1,16 @@
> +#!/bin/sh
> +# SPDX-License-Identifier: GPL-2.0
> +set -e
> +
> +# Use $KERNEL and $INITRAMFS to pass custom Kernel and optional initramfs
> +
> +KERNEL="${KERNEL:-/boot/bzImage}"
> +set -- -l -s --reuse-cmdline "$KERNEL"
> +
> +INITRAMFS="${INITRAMFS:-/boot/initramfs}"
> +if [ -f "$INITRAMFS" ]; then
> +    set -- "$@" --initrd="$INITRAMFS"
> +fi
> +
> +kexec "$@"
> +kexec -e

Thanks a lot. Just with kernel image, it is not enough to boot the host. 
Adding initramfs will avoid the crash when the host boots.
I have made tests to verify this.

Reviewed-by: Zhu Yanjun <yanjun.zhu@...ux.dev>

Zhu Yanjun

> diff --git a/tools/testing/selftests/liveupdate/luo_kexec_simple.c b/tools/testing/selftests/liveupdate/luo_kexec_simple.c
> new file mode 100644
> index 000000000000..67ab6ebf9eec
> --- /dev/null
> +++ b/tools/testing/selftests/liveupdate/luo_kexec_simple.c
> @@ -0,0 +1,114 @@
> +// SPDX-License-Identifier: GPL-2.0-only
> +
> +/*
> + * Copyright (c) 2025, Google LLC.
> + * Pasha Tatashin <pasha.tatashin@...een.com>
> + *
> + * A simple selftest to validate the end-to-end lifecycle of a LUO session
> + * across a single kexec reboot.
> + */
> +
> +#include "luo_test_utils.h"
> +
> +/* Test-specific constants are now defined locally */
> +#define KEXEC_SCRIPT "./do_kexec.sh"
> +#define TEST_SESSION_NAME "test-session"
> +#define TEST_MEMFD_TOKEN 0x1A
> +#define TEST_MEMFD_DATA "hello kexec world"
> +
> +/* Constants for the state-tracking mechanism, specific to this test file. */
> +#define STATE_SESSION_NAME "kexec_simple_state"
> +#define STATE_MEMFD_TOKEN 999
> +
> +/* Stage 1: Executed before the kexec reboot. */
> +static void run_stage_1(int luo_fd)
> +{
> +	int session_fd;
> +
> +	ksft_print_msg("[STAGE 1] Starting pre-kexec setup...\n");
> +
> +	ksft_print_msg("[STAGE 1] Creating state file for next stage (2)...\n");
> +	create_state_file(luo_fd, STATE_SESSION_NAME, STATE_MEMFD_TOKEN, 2);
> +
> +	ksft_print_msg("[STAGE 1] Creating session '%s' and preserving memfd...\n",
> +		       TEST_SESSION_NAME);
> +	session_fd = luo_create_session(luo_fd, TEST_SESSION_NAME);
> +	if (session_fd < 0)
> +		fail_exit("luo_create_session for '%s'", TEST_SESSION_NAME);
> +
> +	if (create_and_preserve_memfd(session_fd, TEST_MEMFD_TOKEN,
> +				      TEST_MEMFD_DATA) < 0) {
> +		fail_exit("create_and_preserve_memfd for token %#x",
> +			  TEST_MEMFD_TOKEN);
> +	}
> +
> +	ksft_print_msg("[STAGE 1] Executing kexec...\n");
> +	if (system(KEXEC_SCRIPT) != 0)
> +		fail_exit("kexec script failed");
> +	exit(EXIT_FAILURE);
> +}
> +
> +/* Stage 2: Executed after the kexec reboot. */
> +static void run_stage_2(int luo_fd, int state_session_fd)
> +{
> +	int session_fd, mfd, stage;
> +
> +	ksft_print_msg("[STAGE 2] Starting post-kexec verification...\n");
> +
> +	restore_and_read_stage(state_session_fd, STATE_MEMFD_TOKEN, &stage);
> +	if (stage != 2)
> +		fail_exit("Expected stage 2, but state file contains %d", stage);
> +
> +	ksft_print_msg("[STAGE 2] Retrieving session '%s'...\n", TEST_SESSION_NAME);
> +	session_fd = luo_retrieve_session(luo_fd, TEST_SESSION_NAME);
> +	if (session_fd < 0)
> +		fail_exit("luo_retrieve_session for '%s'", TEST_SESSION_NAME);
> +
> +	ksft_print_msg("[STAGE 2] Restoring and verifying memfd (token %#x)...\n",
> +		       TEST_MEMFD_TOKEN);
> +	mfd = restore_and_verify_memfd(session_fd, TEST_MEMFD_TOKEN,
> +				       TEST_MEMFD_DATA);
> +	if (mfd < 0)
> +		fail_exit("restore_and_verify_memfd for token %#x", TEST_MEMFD_TOKEN);
> +	close(mfd);
> +
> +	ksft_print_msg("[STAGE 2] Test data verified successfully.\n");
> +	ksft_print_msg("[STAGE 2] Finalizing test session...\n");
> +	if (luo_session_finish(session_fd) < 0)
> +		fail_exit("luo_session_finish for test session");
> +	close(session_fd);
> +
> +	ksft_print_msg("[STAGE 2] Finalizing state session...\n");
> +	if (luo_session_finish(state_session_fd) < 0)
> +		fail_exit("luo_session_finish for state session");
> +	close(state_session_fd);
> +
> +	ksft_print_msg("\n--- SIMPLE KEXEC TEST PASSED ---\n");
> +}
> +
> +int main(int argc, char *argv[])
> +{
> +	int luo_fd;
> +	int state_session_fd;
> +
> +	luo_fd = luo_open_device();
> +	if (luo_fd < 0)
> +		ksft_exit_skip("Failed to open %s. Is the luo module loaded?\n",
> +			       LUO_DEVICE);
> +
> +	/*
> +	 * Determine the stage by attempting to retrieve the state session.
> +	 * If it doesn't exist (ENOENT), we are in Stage 1 (pre-kexec).
> +	 */
> +	state_session_fd = luo_retrieve_session(luo_fd, STATE_SESSION_NAME);
> +	if (state_session_fd == -ENOENT) {
> +		run_stage_1(luo_fd);
> +	} else if (state_session_fd >= 0) {
> +		/* We got a valid handle, pass it directly to stage 2 */
> +		run_stage_2(luo_fd, state_session_fd);
> +	} else {
> +		fail_exit("Failed to check for state session");
> +	}
> +
> +	close(luo_fd);
> +}
> diff --git a/tools/testing/selftests/liveupdate/luo_test_utils.c b/tools/testing/selftests/liveupdate/luo_test_utils.c
> new file mode 100644
> index 000000000000..0a24105cbc54
> --- /dev/null
> +++ b/tools/testing/selftests/liveupdate/luo_test_utils.c
> @@ -0,0 +1,168 @@
> +// SPDX-License-Identifier: GPL-2.0-only
> +
> +/*
> + * Copyright (c) 2025, Google LLC.
> + * Pasha Tatashin <pasha.tatashin@...een.com>
> + */
> +
> +#define _GNU_SOURCE
> +
> +#include <stdio.h>
> +#include <stdlib.h>
> +#include <string.h>
> +#include <fcntl.h>
> +#include <unistd.h>
> +#include <sys/ioctl.h>
> +#include <sys/syscall.h>
> +#include <sys/mman.h>
> +#include <errno.h>
> +#include <stdarg.h>
> +
> +#include "luo_test_utils.h"
> +
> +int luo_open_device(void)
> +{
> +	return open(LUO_DEVICE, O_RDWR);
> +}
> +
> +int luo_create_session(int luo_fd, const char *name)
> +{
> +	struct liveupdate_ioctl_create_session arg = { .size = sizeof(arg) };
> +
> +	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)
> +		return -errno;
> +
> +	return arg.fd;
> +}
> +
> +int luo_retrieve_session(int luo_fd, const char *name)
> +{
> +	struct liveupdate_ioctl_retrieve_session arg = { .size = sizeof(arg) };
> +
> +	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)
> +		return -errno;
> +
> +	return arg.fd;
> +}
> +
> +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);
> +	void *map = MAP_FAILED;
> +	int mfd = -1, ret = -1;
> +
> +	mfd = memfd_create("test_mfd", 0);
> +	if (mfd < 0)
> +		return -errno;
> +
> +	if (ftruncate(mfd, page_size) != 0)
> +		goto out;
> +
> +	map = mmap(NULL, page_size, PROT_WRITE, MAP_SHARED, mfd, 0);
> +	if (map == MAP_FAILED)
> +		goto out;
> +
> +	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)
> +		goto out;
> +
> +	ret = 0;
> +out:
> +	if (ret != 0 && errno != 0)
> +		ret = -errno;
> +	if (mfd >= 0)
> +		close(mfd);
> +	return ret;
> +}
> +
> +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);
> +	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;
> +
> +	map = mmap(NULL, page_size, PROT_READ, MAP_SHARED, mfd, 0);
> +	if (map == MAP_FAILED)
> +		goto out;
> +
> +	if (expected_data && strcmp(expected_data, map) != 0) {
> +		ksft_print_msg("Data mismatch! Expected '%s', Got '%s'\n",
> +			       expected_data, (char *)map);
> +		ret = -EINVAL;
> +		goto out_munmap;
> +	}
> +
> +	ret = mfd;
> +out_munmap:
> +	munmap(map, page_size);
> +out:
> +	if (ret < 0 && errno != 0)
> +		ret = -errno;
> +	if (ret < 0 && mfd >= 0)
> +		close(mfd);
> +	return ret;
> +}
> +
> +int luo_session_finish(int session_fd)
> +{
> +	struct liveupdate_session_finish arg = { .size = sizeof(arg) };
> +
> +	if (ioctl(session_fd, LIVEUPDATE_SESSION_FINISH, &arg) < 0)
> +		return -errno;
> +
> +	return 0;
> +}
> +
> +void create_state_file(int luo_fd, const char *session_name, int token,
> +		       int next_stage)
> +{
> +	char buf[32];
> +	int state_session_fd;
> +
> +	state_session_fd = luo_create_session(luo_fd, session_name);
> +	if (state_session_fd < 0)
> +		fail_exit("luo_create_session for state tracking");
> +
> +	snprintf(buf, sizeof(buf), "%d", next_stage);
> +	if (create_and_preserve_memfd(state_session_fd, token, buf) < 0)
> +		fail_exit("create_and_preserve_memfd for state tracking");
> +
> +	/*
> +	 * DO NOT close session FD, otherwise it is going to be unpreserved
> +	 */
> +}
> +
> +void restore_and_read_stage(int state_session_fd, int token, int *stage)
> +{
> +	char buf[32] = {0};
> +	int mfd;
> +
> +	mfd = restore_and_verify_memfd(state_session_fd, token, NULL);
> +	if (mfd < 0)
> +		fail_exit("failed to restore state memfd");
> +
> +	if (read(mfd, buf, sizeof(buf) - 1) < 0)
> +		fail_exit("failed to read state mfd");
> +
> +	*stage = atoi(buf);
> +
> +	close(mfd);
> +}
> diff --git a/tools/testing/selftests/liveupdate/luo_test_utils.h b/tools/testing/selftests/liveupdate/luo_test_utils.h
> new file mode 100644
> index 000000000000..093e787b9f4b
> --- /dev/null
> +++ b/tools/testing/selftests/liveupdate/luo_test_utils.h
> @@ -0,0 +1,39 @@
> +/* SPDX-License-Identifier: GPL-2.0 */
> +
> +/*
> + * Copyright (c) 2025, Google LLC.
> + * Pasha Tatashin <pasha.tatashin@...een.com>
> + *
> + * Utility functions for LUO kselftests.
> + */
> +
> +#ifndef LUO_TEST_UTILS_H
> +#define LUO_TEST_UTILS_H
> +
> +#include <errno.h>
> +#include <string.h>
> +#include <linux/liveupdate.h>
> +#include "../kselftest.h"
> +
> +#define LUO_DEVICE "/dev/liveupdate"
> +
> +#define fail_exit(fmt, ...)						\
> +	ksft_exit_fail_msg("[%s:%d] " fmt " (errno: %s)\n",	\
> +			   __func__, __LINE__, ##__VA_ARGS__, strerror(errno))
> +
> +/* Generic LUO and session management helpers */
> +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);
> +
> +/* Generic file preservation and restoration helpers */
> +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);
> +
> +/* Kexec state-tracking helpers */
> +void create_state_file(int luo_fd, const char *session_name, int token,
> +		       int next_stage);
> +void restore_and_read_stage(int state_session_fd, int token, int *stage);
> +
> +#endif /* LUO_TEST_UTILS_H */


Powered by blists - more mailing lists

Powered by Openwall GNU/*/Linux Powered by OpenVZ