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: 20+ 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-11 0:23 ` [PATCH v4 02/19] liveupdate: Pass user argument to file preserve and retrieve David Matlack
2026-11-11 0:23 ` [PATCH v4 03/19] selftests/liveupdate: Add tests for preserve and retrieve user argument David Matlack
2026-11-11 0:23 ` [PATCH v4 04/19] liveupdate: Allow file handlers to mark retrieve failures as retryable David Matlack
2026-11-11 0:23 ` [PATCH v4 05/19] mm: Rename memfd_luo to shmem_luo David Matlack
2026-11-11 0:23 ` [PATCH v4 06/19] liveupdate: Don't WARN if tracking a retrieved file fails David Matlack
2026-11-11 0:23 ` [PATCH v4 07/19] liveupdate: Add helpers to track the files that LUO manages David Matlack
2026-11-11 0:23 ` [PATCH v4 08/19] liveupdate: Let file handlers retrieve into an existing file David Matlack
2026-11-11 0:23 ` [PATCH v4 09/19] mm/shmem_luo: Leave freeing the preserved state to finish() David Matlack
2026-11-11 0:23 ` [PATCH v4 10/19] mm/shmem_luo: Don't make early retrieve failures permanent David Matlack
2026-11-11 0:23 ` [PATCH v4 11/19] mm/shmem_luo: Require permission to preserve a file David Matlack
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-11 0:23 ` [PATCH v4 13/19] mm/shmem_luo: Allow preserving tmpfs files David Matlack
2026-11-11 0:23 ` [PATCH v4 14/19] selftests/liveupdate: Test which tmpfs files can be preserved David Matlack
2026-11-11 0:23 ` [PATCH v4 15/19] selftests/liveupdate: Add a test for retrieving tmpfs files David Matlack
2026-11-11 0:23 ` David Matlack [this message]
2026-11-11 0:23 ` [PATCH v4 17/19] selftests/liveupdate: Test the targets of retrieved " David Matlack
2026-11-11 0:23 ` [PATCH v4 18/19] selftests/liveupdate: Test retrieving tmpfs files preserved read-only David Matlack
2026-11-11 0:23 ` [PATCH v4 19/19] selftests/liveupdate: Test block accounting of retrieved tmpfs files David Matlack
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 a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox
all inboxes | Powered by JetHome®