From: David Matlack <dmatlack@google.com>
To: kexec@lists.infradead.org, linux-doc@vger.kernel.org,
linux-kernel@vger.kernel.org, linux-kselftest@vger.kernel.org,
linux-mm@kvack.org
Cc: Alexander Graf <graf@amazon.com>,
Andrew Morton <akpm@linux-foundation.org>,
Baolin Wang <baolin.wang@linux.alibaba.com>,
Christian Brauner <brauner@kernel.org>,
David Hildenbrand <david@kernel.org>,
Hugh Dickins <hughd@google.com>, Jonathan Corbet <corbet@lwn.net>,
"Liam R. Howlett" <liam@infradead.org>,
Lorenzo Stoakes <ljs@kernel.org>, Michal Hocko <mhocko@suse.com>,
Mike Rapoport <rppt@kernel.org>,
Pasha Tatashin <pasha.tatashin@soleen.com>,
Pratyush Yadav <pratyush@kernel.org>,
Randy Dunlap <rdunlap@infradead.org>,
Samiullah Khawaja <skhawaja@google.com>,
Shuah Khan <skhan@linuxfoundation.org>,
Suren Baghdasaryan <surenb@google.com>,
Vlastimil Babka <vbabka@kernel.org>,
David Matlack <dmatlack@google.com>
Subject: [PATCH v4 16/19] selftests/liveupdate: Test memfds and LUO interactions in luo_tmpfs
Date: Wed, 11 Nov 2026 00:23:54 +0000 [thread overview]
Message-ID: <20261111002358.1842967-17-dmatlack@google.com> (raw)
In-Reply-To: <20261111002358.1842967-1-dmatlack@google.com>
Add test cases to luo_tmpfs that check that memfds are retrieved as
memfds, with their seals and file position, and only without a retrieve
argument, while tmpfs files can only be retrieved with one.
Also check that invalid retrieve arguments fail and can be retried after
fixing them. Retrieving a token again fails with -EBUSY with an
argument, and returns the same open file without one. Files can't be
retrieved from outgoing sessions. A file that LUO already manages can't
be the target, whether it is preserved in an outgoing session or was
already retrieved.
Signed-off-by: David Matlack <dmatlack@google.com>
---
.../testing/selftests/liveupdate/luo_tmpfs.c | 310 ++++++++++++++++++
1 file changed, 310 insertions(+)
diff --git a/tools/testing/selftests/liveupdate/luo_tmpfs.c b/tools/testing/selftests/liveupdate/luo_tmpfs.c
index 4e6e035e8433..91a607752ee2 100644
--- a/tools/testing/selftests/liveupdate/luo_tmpfs.c
+++ b/tools/testing/selftests/liveupdate/luo_tmpfs.c
@@ -96,6 +96,19 @@
} \
} while (0)
+/* Check that a system call fails with @err. */
+#define CHECK_ERRNO(expr, err) do { \
+ long __ret = (expr); \
+ int __err = errno, __exp = (err); \
+ \
+ if (__ret != -1 || __err != __exp) { \
+ ksft_print_msg("%s:%d: %s: %ld, errno %d, expected %d\n", \
+ __func__, __LINE__, #expr, \
+ __ret, __err, __exp); \
+ return KSFT_FAIL; \
+ } \
+} while (0)
+
/* Call a function that returns a KSFT_* code, and return if it fails. */
#define TRY(expr) do { \
int __ret = (expr); \
@@ -121,6 +134,7 @@ struct test_case {
};
static unsigned long page_size;
+static int dev_fd;
static int session_fd;
/* nolibc doesn't have these, so use syscall() where needed. */
@@ -271,6 +285,15 @@ static int link_fd(int fd, const char *path)
AT_SYMLINK_FOLLOW);
}
+/* Open the file @fd again with @flags. */
+static int reopen_fd(int fd, int flags)
+{
+ char fd_path[64];
+
+ snprintf(fd_path, sizeof(fd_path), "/proc/self/fd/%d", fd);
+ return open(fd_path, flags);
+}
+
/* The contents of the files depend on their token. */
static unsigned char pattern(__u64 token, size_t offset)
{
@@ -397,6 +420,23 @@ static int preserve_file(struct test_case *tc)
return preserve_source(path, tc->size, tc->pos, tc->token);
}
+/* Preserve a memfd with the contents of the source of @tc. */
+static int preserve_memfd(struct test_case *tc, unsigned int flags,
+ unsigned int seals)
+{
+ int fd, ret;
+
+ fd = memfd_create(tc->name, flags);
+ CHECK(fd >= 0);
+ CHECK_EQ(write_pattern(fd, 0, tc->size, tc->token), 0);
+ if (seals)
+ CHECK_EQ(do_fcntl(fd, F_ADD_SEALS, seals), 0);
+ ret = preserve_fd(fd, tc->pos, tc->token);
+ close(fd);
+
+ return ret;
+}
+
/* Retrieve the file of @tc into a new file on a new mount at dst. */
static int restore_verify(struct test_case *tc)
{
@@ -560,6 +600,234 @@ static int linked_target_verify(struct test_case *tc)
return KSFT_PASS;
}
+static int memfd_preserve(struct test_case *tc)
+{
+ return preserve_memfd(tc, 0, 0);
+}
+
+static int memfd_verify(struct test_case *tc)
+{
+ struct stat st;
+ int fd;
+
+ /* memfds are only retrieved as memfds. */
+ CHECK_EQ(mount_tmpfs("dst", 16, NULL), 0);
+ CHECK_EQ(retrieve_new(tc->token, "dst"), -EINVAL);
+ CHECK_EQ(mnt_used("dst"), 0);
+
+ fd = luo_session_retrieve_fd(session_fd, tc->token);
+ CHECK(fd >= 0);
+ TRY(check_restored(tc, fd));
+ CHECK(!fstat(fd, &st));
+ CHECK_EQ(st.st_nlink, 0);
+ CHECK_EQ(do_fcntl(fd, F_GETFL, 0) & O_ACCMODE, O_RDWR);
+
+ /* Unlike for tmpfs files, the file position is restored. */
+ CHECK_EQ(lseek(fd, 0, SEEK_CUR), tc->pos);
+
+ close(fd);
+ return KSFT_PASS;
+}
+
+#define MEMFD_SEALS (F_SEAL_SHRINK | F_SEAL_GROW | F_SEAL_WRITE)
+
+static int sealed_memfd_preserve(struct test_case *tc)
+{
+ return preserve_memfd(tc, MFD_ALLOW_SEALING, MEMFD_SEALS);
+}
+
+static int sealed_memfd_verify(struct test_case *tc)
+{
+ void *p;
+ int fd;
+
+ fd = luo_session_retrieve_fd(session_fd, tc->token);
+ CHECK(fd >= 0);
+ TRY(check_restored(tc, fd));
+ CHECK_EQ(do_fcntl(fd, F_GET_SEALS, 0), MEMFD_SEALS);
+ CHECK_ERRNO(write(fd, "x", 1), EPERM);
+ CHECK_ERRNO(ftruncate(fd, 0), EPERM);
+ p = mmap(NULL, page_size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
+ CHECK(p == MAP_FAILED && errno == EPERM);
+
+ close(fd);
+ return KSFT_PASS;
+}
+
+static int bad_args_verify(struct test_case *tc)
+{
+ struct {
+ struct liveupdate_shmem_retrieve_arg arg;
+ __u64 extra;
+ } big = {};
+ struct liveupdate_shmem_retrieve_arg arg;
+ int target, path_fd;
+
+ CHECK_EQ(mount_tmpfs("dst", 16, NULL), 0);
+ target = new_target("dst");
+ CHECK(target >= 0);
+
+ /* Files from tmpfs mounts are only retrieved into files on tmpfs. */
+ CHECK_EQ(luo_session_retrieve_fd(session_fd, tc->token), -EINVAL);
+
+ memset(&arg, 0, sizeof(arg));
+ arg.size = sizeof(arg);
+ arg.fd = target;
+
+ /* Problems with the argument itself. */
+ arg.size = offsetof(struct liveupdate_shmem_retrieve_arg, fd);
+ CHECK_EQ(retrieve_arg(tc->token, &arg), -EINVAL);
+ arg.size = 2 * page_size;
+ CHECK_EQ(retrieve_arg(tc->token, &arg), -E2BIG);
+ arg.size = sizeof(arg);
+
+ big.arg = arg;
+ big.arg.size = sizeof(big);
+ big.extra = 1;
+ CHECK_EQ(retrieve_arg(tc->token, &big.arg), -E2BIG);
+
+ /* Problems with the file descriptor. */
+ arg.fd = -1;
+ CHECK_EQ(retrieve_arg(tc->token, &arg), -EBADF);
+ path_fd = reopen_fd(target, O_PATH);
+ CHECK(path_fd >= 0);
+ arg.fd = path_fd;
+ CHECK_EQ(retrieve_arg(tc->token, &arg), -EBADF);
+ close(path_fd);
+ arg.fd = target;
+
+ CHECK_EQ(retrieve_arg(tc->token + 1, &arg), -ENOENT);
+ CHECK_EQ(file_size(target), 0);
+ CHECK_EQ(mnt_used("dst"), 0);
+
+ /* The failures didn't consume the preserved file. */
+ CHECK_EQ(retrieve_arg(tc->token, &arg), target);
+ TRY(check_restored(tc, target));
+
+ close(target);
+ return KSFT_PASS;
+}
+
+static int retrieve_twice_verify(struct test_case *tc)
+{
+ struct stat st, new_st;
+ int target, fd, new_fd;
+
+ CHECK_EQ(mount_tmpfs("dst", 16, NULL), 0);
+ fd = retrieve_new(tc->token, "dst");
+ CHECK(fd >= 0);
+ TRY(check_restored(tc, fd));
+
+ /* The argument only applies to the retrieve that restores the file. */
+ target = new_target("dst");
+ CHECK(target >= 0);
+ CHECK_EQ(retrieve_into(tc->token, target), -EBUSY);
+ CHECK_EQ(file_size(target), 0);
+ close(target);
+
+ /* A plain retrieve returns the same open file. */
+ new_fd = luo_session_retrieve_fd(session_fd, tc->token);
+ CHECK(new_fd >= 0);
+ CHECK(!fstat(fd, &st));
+ CHECK(!fstat(new_fd, &new_st));
+ CHECK_EQ(new_st.st_dev, st.st_dev);
+ CHECK_EQ(new_st.st_ino, st.st_ino);
+ CHECK_EQ(lseek(new_fd, 77, SEEK_SET), 77);
+ CHECK_EQ(lseek(fd, 0, SEEK_CUR), 77);
+ CHECK_EQ(mnt_used("dst"), nr_pages(tc->size));
+
+ close(new_fd);
+ close(fd);
+ return KSFT_PASS;
+}
+
+static int preserve_nothing(struct test_case *tc)
+{
+ return KSFT_PASS;
+}
+
+static int outgoing_session_verify(struct test_case *tc)
+{
+ struct liveupdate_shmem_retrieve_arg arg = {
+ .size = sizeof(arg),
+ };
+ int out_fd, source_fd;
+
+ out_fd = luo_create_session(dev_fd, SESSION_NAME "_outgoing_session");
+ CHECK(out_fd >= 0);
+ CHECK_EQ(mount_tmpfs("src", 16, NULL), 0);
+ source_fd = new_target("src");
+ CHECK(source_fd >= 0);
+ CHECK_EQ(luo_session_preserve_fd(out_fd, source_fd, tc->token), 0);
+
+ /* Files can only be retrieved from incoming sessions. */
+ arg.fd = new_target("src");
+ CHECK(arg.fd >= 0);
+ CHECK_EQ(luo_session_retrieve_fd_arg(out_fd, tc->token,
+ (__u64)(uintptr_t)&arg), -EINVAL);
+
+ close(arg.fd);
+ close(source_fd);
+ close(out_fd);
+ return KSFT_PASS;
+}
+
+static int target_preserved_verify(struct test_case *tc)
+{
+ int out_fd, target;
+
+ /* A file preserved in an outgoing session can't be the target. */
+ out_fd = luo_create_session(dev_fd, SESSION_NAME "_target_preserved");
+ CHECK(out_fd >= 0);
+ CHECK_EQ(mount_tmpfs("dst", 16, NULL), 0);
+ target = new_target("dst");
+ CHECK(target >= 0);
+ CHECK_EQ(luo_session_preserve_fd(out_fd, target, tc->token), 0);
+ CHECK_EQ(retrieve_into(tc->token, target), -EBUSY);
+ CHECK_EQ(file_size(target), 0);
+
+ /* Once the session is closed, the file is no longer preserved. */
+ close(out_fd);
+ CHECK_EQ(retrieve_into(tc->token, target), target);
+ TRY(check_restored(tc, target));
+
+ close(target);
+ return KSFT_PASS;
+}
+
+static int target_retrieved_preserve(struct test_case *tc)
+{
+ TRY(preserve_source("src/target_retrieved_empty", 0, 0, tc->token));
+ return preserve_source("src/target_retrieved", tc->size, 0,
+ tc->token + 1);
+}
+
+static int target_retrieved_verify(struct test_case *tc)
+{
+ int target, other_fd;
+
+ /* Retrieving an empty file leaves the target empty... */
+ CHECK_EQ(mount_tmpfs("dst", 16, NULL), 0);
+ target = new_target("dst");
+ CHECK(target >= 0);
+ CHECK_EQ(retrieve_into(tc->token, target), target);
+ CHECK_EQ(file_size(target), 0);
+
+ /* ... but LUO manages the file now, so it can't be a target again. */
+ CHECK_EQ(retrieve_into(tc->token + 1, target), -EBUSY);
+ CHECK_EQ(file_size(target), 0);
+
+ /* The failure didn't consume the preserved file. */
+ other_fd = retrieve_new(tc->token + 1, "dst");
+ CHECK(other_fd >= 0);
+ CHECK_EQ(file_size(other_fd), tc->size);
+ TRY(check_contents(other_fd, tc->size, tc->token + 1));
+
+ close(other_fd);
+ close(target);
+ return KSFT_PASS;
+}
+
static struct test_case test_cases[] = {
/* The restored file. */
{
@@ -588,6 +856,47 @@ static struct test_case test_cases[] = {
.size = SMALL_SIZE,
.verify = linked_target_verify,
},
+ /* memfds. */
+ {
+ .name = "memfd",
+ .size = SMALL_SIZE,
+ .pos = 700,
+ .preserve = memfd_preserve,
+ .verify = memfd_verify,
+ },
+ {
+ .name = "sealed_memfd",
+ .size = SMALL_SIZE,
+ .preserve = sealed_memfd_preserve,
+ .verify = sealed_memfd_verify,
+ },
+ /* Interactions with LUO. */
+ {
+ .name = "bad_args",
+ .size = SMALL_SIZE,
+ .verify = bad_args_verify,
+ },
+ {
+ .name = "retrieve_twice",
+ .size = SMALL_SIZE,
+ .verify = retrieve_twice_verify,
+ },
+ {
+ .name = "outgoing_session",
+ .preserve = preserve_nothing,
+ .verify = outgoing_session_verify,
+ },
+ {
+ .name = "target_preserved",
+ .size = SMALL_SIZE,
+ .verify = target_preserved_verify,
+ },
+ {
+ .name = "target_retrieved",
+ .size = SMALL_SIZE,
+ .preserve = target_retrieved_preserve,
+ .verify = target_retrieved_verify,
+ },
};
static long read_taint(void)
@@ -617,6 +926,7 @@ static void setup(int luo_fd)
{
unsigned int i;
+ dev_fd = luo_fd;
page_size = getpagesize();
umask(TEST_UMASK);
--
2.56.0.542.g3ef89b7020-goog
next prev parent reply other threads:[~2026-11-11 0:24 UTC|newest]
Thread overview: 39+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-11-11 0:23 [PATCH v4 00/19] liveupdate: Preserve tmpfs files across Live Update David Matlack
2026-11-11 0:23 ` [PATCH v4 01/19] liveupdate: Remove the list of steps from luo_preserve_file()'s comment David Matlack
2026-11-12 0:31 ` sashiko-bot
2026-11-11 0:23 ` [PATCH v4 02/19] liveupdate: Pass user argument to file preserve and retrieve David Matlack
2026-11-12 0:31 ` sashiko-bot
2026-11-11 0:23 ` [PATCH v4 03/19] selftests/liveupdate: Add tests for preserve and retrieve user argument David Matlack
2026-11-12 0:31 ` sashiko-bot
2026-11-11 0:23 ` [PATCH v4 04/19] liveupdate: Allow file handlers to mark retrieve failures as retryable David Matlack
2026-11-12 0:31 ` sashiko-bot
2026-11-11 0:23 ` [PATCH v4 05/19] mm: Rename memfd_luo to shmem_luo David Matlack
2026-11-12 0:31 ` sashiko-bot
2026-11-11 0:23 ` [PATCH v4 06/19] liveupdate: Don't WARN if tracking a retrieved file fails David Matlack
2026-11-12 0:31 ` sashiko-bot
2026-11-11 0:23 ` [PATCH v4 07/19] liveupdate: Add helpers to track the files that LUO manages David Matlack
2026-11-12 0:31 ` sashiko-bot
2026-11-11 0:23 ` [PATCH v4 08/19] liveupdate: Let file handlers retrieve into an existing file David Matlack
2026-11-12 0:31 ` sashiko-bot
2026-11-11 0:23 ` [PATCH v4 09/19] mm/shmem_luo: Leave freeing the preserved state to finish() David Matlack
2026-11-12 0:31 ` sashiko-bot
2026-11-11 0:23 ` [PATCH v4 10/19] mm/shmem_luo: Don't make early retrieve failures permanent David Matlack
2026-11-12 0:31 ` sashiko-bot
2026-11-11 0:23 ` [PATCH v4 11/19] mm/shmem_luo: Require permission to preserve a file David Matlack
2026-11-12 0:31 ` sashiko-bot
2026-11-11 0:23 ` [PATCH v4 12/19] mm/shmem_luo: Retrieve tmpfs files into a file created by userspace David Matlack
2026-11-12 0:31 ` sashiko-bot
2026-11-11 0:23 ` [PATCH v4 13/19] mm/shmem_luo: Allow preserving tmpfs files David Matlack
2026-11-12 0:31 ` sashiko-bot
2026-11-11 0:23 ` [PATCH v4 14/19] selftests/liveupdate: Test which tmpfs files can be preserved David Matlack
2026-11-12 0:31 ` sashiko-bot
2026-11-11 0:23 ` [PATCH v4 15/19] selftests/liveupdate: Add a test for retrieving tmpfs files David Matlack
2026-11-12 0:31 ` sashiko-bot
2026-11-11 0:23 ` David Matlack [this message]
2026-11-12 0:31 ` [PATCH v4 16/19] selftests/liveupdate: Test memfds and LUO interactions in luo_tmpfs sashiko-bot
2026-11-11 0:23 ` [PATCH v4 17/19] selftests/liveupdate: Test the targets of retrieved tmpfs files David Matlack
2026-11-12 0:31 ` sashiko-bot
2026-11-11 0:23 ` [PATCH v4 18/19] selftests/liveupdate: Test retrieving tmpfs files preserved read-only David Matlack
2026-11-12 0:31 ` sashiko-bot
2026-11-11 0:23 ` [PATCH v4 19/19] selftests/liveupdate: Test block accounting of retrieved tmpfs files David Matlack
2026-11-12 0:31 ` sashiko-bot
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=20261111002358.1842967-17-dmatlack@google.com \
--to=dmatlack@google.com \
--cc=akpm@linux-foundation.org \
--cc=baolin.wang@linux.alibaba.com \
--cc=brauner@kernel.org \
--cc=corbet@lwn.net \
--cc=david@kernel.org \
--cc=graf@amazon.com \
--cc=hughd@google.com \
--cc=kexec@lists.infradead.org \
--cc=liam@infradead.org \
--cc=linux-doc@vger.kernel.org \
--cc=linux-kernel@vger.kernel.org \
--cc=linux-kselftest@vger.kernel.org \
--cc=linux-mm@kvack.org \
--cc=ljs@kernel.org \
--cc=mhocko@suse.com \
--cc=pasha.tatashin@soleen.com \
--cc=pratyush@kernel.org \
--cc=rdunlap@infradead.org \
--cc=rppt@kernel.org \
--cc=skhan@linuxfoundation.org \
--cc=skhawaja@google.com \
--cc=surenb@google.com \
--cc=vbabka@kernel.org \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.