From: David Matlack <[email protected]> Extend the multi-session LUO test to verify preserving a path-backed tmpfs file and retrieving its contents to a newly created empty tmpfs file post-kexec using struct liveupdate_memfd_retrieve_arg with LIVEUPDATE_SESSION_RETRIEVE_FD. Also verify that retrieve argument validation errors (passing an argument when retrieving a memfd, omitting the argument when retrieving a tmpfs file, or passing a read-only target tmpfs FD) are retryable and allow a subsequent valid retrieve call to succeed.
Signed-off-by: David Matlack <[email protected]> Co-developed-by: Pasha Tatashin <[email protected]> Signed-off-by: Pasha Tatashin <[email protected]> --- .../liveupdate/lib/include/libliveupdate.h | 2 + .../selftests/liveupdate/lib/lu_utils.c | 44 +++++++ .../selftests/liveupdate/luo_multi_session.c | 110 ++++++++++++++++-- 3 files changed, 147 insertions(+), 9 deletions(-) diff --git a/tools/testing/selftests/liveupdate/lib/include/libliveupdate.h b/tools/testing/selftests/liveupdate/lib/include/libliveupdate.h index 8a38268e89dee..dc5c01408462d 100644 --- a/tools/testing/selftests/liveupdate/lib/include/libliveupdate.h +++ b/tools/testing/selftests/liveupdate/lib/include/libliveupdate.h @@ -37,6 +37,8 @@ int luo_session_retrieve_fd_arg(int session_fd, __u64 token, __u64 user_arg); 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); +int restore_and_verify_tmpfs(int session_fd, int token, int fd, + const char *expected_data); void create_state_file(int luo_fd, const char *session_name, int token, int next_stage); diff --git a/tools/testing/selftests/liveupdate/lib/lu_utils.c b/tools/testing/selftests/liveupdate/lib/lu_utils.c index 5c6f8bf80b297..1b1a556287d3a 100644 --- a/tools/testing/selftests/liveupdate/lib/lu_utils.c +++ b/tools/testing/selftests/liveupdate/lib/lu_utils.c @@ -347,3 +347,47 @@ int luo_test(int argc, char *argv[], return 0; } + +int restore_and_verify_tmpfs(int session_fd, int token, int fd, + const char *expected_data) +{ + struct liveupdate_memfd_retrieve_arg rarg = { + .size = sizeof(rarg), + .fd = fd, + }; + long page_size = getpagesize(); + void *map = MAP_FAILED; + struct stat st; + int ret_fd; + + ret_fd = luo_session_retrieve_fd_arg(session_fd, token, + (unsigned long)&rarg); + if (ret_fd < 0) { + ksft_print_msg("luo_session_retrieve_fd_arg failed: %d\n", + ret_fd); + return ret_fd; + } + close(ret_fd); + + if (fstat(fd, &st) < 0) + return -errno; + if (st.st_size != page_size) { + ksft_print_msg("Size mismatch! Expected %ld, Got %lld\n", + page_size, (long long)st.st_size); + return -EINVAL; + } + + map = mmap(NULL, page_size, PROT_READ, MAP_SHARED, fd, 0); + if (map == MAP_FAILED) + return -errno; + + if (expected_data && strcmp(expected_data, map) != 0) { + ksft_print_msg("Data mismatch! Expected '%s', Got '%s'\n", + expected_data, (char *)map); + munmap(map, page_size); + return -EINVAL; + } + + munmap(map, page_size); + return 0; +} diff --git a/tools/testing/selftests/liveupdate/luo_multi_session.c b/tools/testing/selftests/liveupdate/luo_multi_session.c index aac24a5f5ce3c..46889676d9f52 100644 --- a/tools/testing/selftests/liveupdate/luo_multi_session.c +++ b/tools/testing/selftests/liveupdate/luo_multi_session.c @@ -9,7 +9,15 @@ * files. */ +#include <errno.h> +#include <fcntl.h> +#include <sys/mman.h> +#include <sys/mount.h> +#include <sys/stat.h> +#include <unistd.h> + #include <libliveupdate.h> +#include <linux/liveupdate.h> #define SESSION_EMPTY_1 "multi-test-empty-1" #define SESSION_EMPTY_2 "multi-test-empty-2" @@ -23,6 +31,7 @@ #define MFD1_DATA "Data for session files 1" #define MFD2_DATA "First file for session files 2" #define MFD3_DATA "Second file for session files 2" +#define MFD3_POS 42 #define STATE_SESSION_NAME "kexec_multi_state" #define STATE_MEMFD_TOKEN 998 @@ -31,6 +40,8 @@ static void run_stage_1(int luo_fd) { int s_empty1_fd, s_empty2_fd, s_files1_fd, s_files2_fd; + int tmpfs_fd; + void *map; ksft_print_msg("[STAGE 1] Starting pre-kexec setup for multi-session test...\n"); @@ -53,25 +64,50 @@ static void run_stage_1(int luo_fd) s_files1_fd = luo_create_session(luo_fd, SESSION_FILES_1); if (s_files1_fd < 0) fail_exit("luo_create_session for '%s'", SESSION_FILES_1); + ksft_print_msg(" -> Preserving memfd (token %#x, content: \"%s\")\n", + MFD1_TOKEN, MFD1_DATA); if (create_and_preserve_memfd(s_files1_fd, MFD1_TOKEN, MFD1_DATA) < 0) { fail_exit("create_and_preserve_memfd for token %#x", MFD1_TOKEN); } - ksft_print_msg("[STAGE 1] Creating session '%s' with two memfds...\n", + ksft_print_msg("[STAGE 1] Creating session '%s' with memfd and tmpfs file...\n", SESSION_FILES_2); s_files2_fd = luo_create_session(luo_fd, SESSION_FILES_2); if (s_files2_fd < 0) fail_exit("luo_create_session for '%s'", SESSION_FILES_2); + ksft_print_msg(" -> Preserving memfd (token %#x, content: \"%s\")\n", + MFD2_TOKEN, MFD2_DATA); if (create_and_preserve_memfd(s_files2_fd, MFD2_TOKEN, MFD2_DATA) < 0) { fail_exit("create_and_preserve_memfd for token %#x", MFD2_TOKEN); } - if (create_and_preserve_memfd(s_files2_fd, MFD3_TOKEN, MFD3_DATA) < 0) { - fail_exit("create_and_preserve_memfd for token %#x", - MFD3_TOKEN); - } + + ksft_print_msg(" -> Populating tmpfs file at /mnt/mfd3...\n"); + if (mkdir("/mnt", 0777) < 0 && errno != EEXIST) + fail_exit("mkdir /mnt"); + if (mount("tmpfs", "/mnt", "tmpfs", 0, NULL) < 0) + fail_exit("mount tmpfs"); + tmpfs_fd = open("/mnt/mfd3", O_CREAT | O_RDWR, 0666); + if (tmpfs_fd < 0) + fail_exit("failed to open tmpfs_fd"); + ksft_print_msg(" -> Preserving generic tmpfs file (token %#x, content: \"%s\")\n", + MFD3_TOKEN, MFD3_DATA); + if (ftruncate(tmpfs_fd, getpagesize()) < 0) + fail_exit("ftruncate tmpfs_fd"); + map = mmap(NULL, getpagesize(), PROT_WRITE, MAP_SHARED, tmpfs_fd, 0); + if (map == MAP_FAILED) + fail_exit("mmap tmpfs_fd"); + strcpy(map, MFD3_DATA); + munmap(map, getpagesize()); + if (lseek(tmpfs_fd, MFD3_POS, SEEK_SET) != MFD3_POS) + fail_exit("lseek tmpfs_fd"); + + if (luo_session_preserve_fd(s_files2_fd, tmpfs_fd, MFD3_TOKEN) < 0) + fail_exit("preserve tmpfs fd"); + + close(tmpfs_fd); close(luo_fd); daemonize_and_wait(); @@ -81,7 +117,10 @@ static void run_stage_1(int luo_fd) static void run_stage_2(int luo_fd, int state_session_fd) { int s_empty1_fd, s_empty2_fd, s_files1_fd, s_files2_fd; - int mfd1, mfd2, mfd3, stage; + struct liveupdate_memfd_retrieve_arg rarg = { + .size = sizeof(rarg), + }; + int mfd1, mfd2, mfd3, ro_fd, ret, stage; ksft_print_msg("[STAGE 2] Starting post-kexec verification...\n"); @@ -110,6 +149,8 @@ static void run_stage_2(int luo_fd, int state_session_fd) ksft_print_msg("[STAGE 2] Verifying contents of session '%s'...\n", SESSION_FILES_1); + ksft_print_msg(" -> Retrieving memfd (token %#x) from session...\n", + MFD1_TOKEN); mfd1 = restore_and_verify_memfd(s_files1_fd, MFD1_TOKEN, MFD1_DATA); if (mfd1 < 0) fail_exit("restore_and_verify_memfd for token %#x", MFD1_TOKEN); @@ -118,14 +159,65 @@ static void run_stage_2(int luo_fd, int state_session_fd) ksft_print_msg("[STAGE 2] Verifying contents of session '%s'...\n", SESSION_FILES_2); + ksft_print_msg(" -> Creating empty tmpfs file at /mnt/mfd3...\n"); + if (mkdir("/mnt", 0777) < 0 && errno != EEXIST) + fail_exit("mkdir /mnt"); + if (mount("tmpfs", "/mnt", "tmpfs", 0, NULL) < 0) + fail_exit("mount tmpfs"); + mfd3 = open("/mnt/mfd3", O_CREAT | O_RDWR, 0666); + if (mfd3 < 0) + fail_exit("open tmpfs file"); + ro_fd = open("/mnt/mfd3", O_RDONLY); + if (ro_fd < 0) + fail_exit("open read-only tmpfs file"); + + ksft_print_msg(" -> Verifying retryable retrieve errors for memfd and tmpfs...\n"); + rarg.fd = mfd3; + ret = luo_session_retrieve_fd_arg(s_files2_fd, MFD2_TOKEN, + (uintptr_t)&rarg); + if (ret != -EINVAL) + fail_exit("expected -EINVAL when passing arg to memfd retrieve, got %d", + ret); + + ksft_print_msg(" -> Retrieving memfd (token %#x) from session...\n", + MFD2_TOKEN); mfd2 = restore_and_verify_memfd(s_files2_fd, MFD2_TOKEN, MFD2_DATA); if (mfd2 < 0) fail_exit("restore_and_verify_memfd for token %#x", MFD2_TOKEN); close(mfd2); - mfd3 = restore_and_verify_memfd(s_files2_fd, MFD3_TOKEN, MFD3_DATA); - if (mfd3 < 0) - fail_exit("restore_and_verify_memfd for token %#x", MFD3_TOKEN); + ret = luo_session_retrieve_fd(s_files2_fd, MFD3_TOKEN); + if (ret != -EINVAL) + fail_exit("expected -EINVAL when retrieving tmpfs without arg, got %d", + ret); + + rarg.fd = ro_fd; + ret = luo_session_retrieve_fd_arg(s_files2_fd, MFD3_TOKEN, + (uintptr_t)&rarg); + if (ret != -EACCES) + fail_exit("expected -EACCES when retrieving tmpfs into read-only fd, got %d", + ret); + close(ro_fd); + + if (ftruncate(mfd3, 1) < 0) + fail_exit("ftruncate non-empty tmpfs file"); + rarg.fd = mfd3; + ret = luo_session_retrieve_fd_arg(s_files2_fd, MFD3_TOKEN, + (uintptr_t)&rarg); + if (ret != -EBUSY) + fail_exit("expected -EBUSY when retrieving tmpfs into non-empty fd, got %d", + ret); + if (ftruncate(mfd3, 0) < 0) + fail_exit("ftruncate empty tmpfs file"); + + ksft_print_msg(" -> Retrieving tmpfs file (token %#x) from session...\n", + MFD3_TOKEN); + if (restore_and_verify_tmpfs(s_files2_fd, MFD3_TOKEN, mfd3, + MFD3_DATA) < 0) + fail_exit("restore_and_verify_tmpfs for token %#x", + MFD3_TOKEN); + if (lseek(mfd3, 0, SEEK_CUR) != MFD3_POS) + fail_exit("expected f_pos %d on restored tmpfs file", MFD3_POS); close(mfd3); ksft_print_msg("[STAGE 2] Test data verified successfully.\n"); -- 2.56.0.385.gd3acb90ef8-goog

