[PATCH v2 6/6] selftests/liveupdate: add fallocate test for memfd

From: Pratyush Yadav

Date: Fri Apr 03 2026 - 15:42:28 EST


From: "Pratyush Yadav (Google)" <pratyush@xxxxxxxxxx>

When memory is added to a memfd via fallocate(), it does not get zeroed
immediately. This is tracked by the absence of the uptodate folio flag.
Initially, memfd preservation simply saved the folio flags at preserve
time. This led to a bug, where all writes to un-initialized fallocated
memory after preserve were lost after live update. This is fixed by
commit 50d7b4332f27 ("mm: memfd_luo: always make all folios uptodate").

Add a test that fallocates some memory in a memfd, preserves it, writes
to it. Then in stage 2 it verifies the written content is still present.

Reviewed-by: Mike Rapoport (Microsoft) <rppt@xxxxxxxxxx>
Signed-off-by: Pratyush Yadav (Google) <pratyush@xxxxxxxxxx>
---
.../testing/selftests/liveupdate/luo_memfd.c | 83 +++++++++++++++++++
1 file changed, 83 insertions(+)

diff --git a/tools/testing/selftests/liveupdate/luo_memfd.c b/tools/testing/selftests/liveupdate/luo_memfd.c
index a1e9f34da006..bc6ab063363f 100644
--- a/tools/testing/selftests/liveupdate/luo_memfd.c
+++ b/tools/testing/selftests/liveupdate/luo_memfd.c
@@ -43,6 +43,11 @@
#define PRESERVED_MEMFD_TOKEN 1
#define PRESERVED_BUFFER_SIZE SZ_1M

+#define FALLOCATE_SESSION_NAME "fallocate_session"
+#define FALLOCATE_MEMFD_TOKEN 1
+#define FALLOCATE_BUFFER_SIZE SZ_1M
+#define FALLOCATE_DATA_FS_COPY "fallocate_data_fs_copy.bin"
+
static int luo_fd = -1;
static int stage;

@@ -222,6 +227,84 @@ TEST(preserved_ops)
ASSERT_EQ(lseek(fd, 0, SEEK_END), PRESERVED_BUFFER_SIZE);
}

+static void fallocate_memfd_stage_1(struct __test_metadata *_metadata)
+{
+ int fd, session;
+ char *buffer;
+ struct liveupdate_session_preserve_fd preserve_arg = { .size = sizeof(preserve_arg) };
+
+ buffer = malloc(FALLOCATE_BUFFER_SIZE);
+ ASSERT_NE(buffer, NULL);
+
+ session = luo_create_session(luo_fd, FALLOCATE_SESSION_NAME);
+ ASSERT_GE(session, 0);
+
+ fd = memfd_create("fallocate_memfd", 0);
+ ASSERT_GE(fd, 0);
+
+ /* Fallocate memory but do not write to it yet */
+ ASSERT_EQ(fallocate(fd, 0, 0, FALLOCATE_BUFFER_SIZE), 0);
+
+ preserve_arg.fd = fd;
+ preserve_arg.token = FALLOCATE_MEMFD_TOKEN;
+ ASSERT_GE(ioctl(session, LIVEUPDATE_SESSION_PRESERVE_FD, &preserve_arg), 0);
+
+ /* Now write to it after preserving */
+ ASSERT_GE(generate_random_data(buffer, FALLOCATE_BUFFER_SIZE), 0);
+ ASSERT_EQ(save_test_data(FALLOCATE_DATA_FS_COPY, buffer, FALLOCATE_BUFFER_SIZE), 0);
+
+ ASSERT_GE(lseek(fd, 0, SEEK_SET), 0);
+ ASSERT_EQ(write_size(fd, buffer, FALLOCATE_BUFFER_SIZE), 0);
+
+ daemonize_and_wait();
+}
+
+static void fallocate_memfd_stage_2(struct __test_metadata *_metadata)
+{
+ int fd, session;
+ char *buffer;
+ struct liveupdate_session_retrieve_fd retrieve_arg = { .size = sizeof(retrieve_arg) };
+
+ buffer = malloc(FALLOCATE_BUFFER_SIZE);
+ ASSERT_NE(buffer, NULL);
+
+ session = luo_retrieve_session(luo_fd, FALLOCATE_SESSION_NAME);
+ ASSERT_GE(session, 0);
+
+ ASSERT_EQ(load_test_data(FALLOCATE_DATA_FS_COPY, buffer, FALLOCATE_BUFFER_SIZE), 0);
+
+ retrieve_arg.token = FALLOCATE_MEMFD_TOKEN;
+ ASSERT_GE(ioctl(session, LIVEUPDATE_SESSION_RETRIEVE_FD, &retrieve_arg), 0);
+ fd = retrieve_arg.fd;
+ ASSERT_GE(fd, 0);
+
+ ASSERT_EQ(verify_fd_content_read(fd, buffer, FALLOCATE_BUFFER_SIZE), 0);
+
+ ASSERT_EQ(luo_session_finish(session), 0);
+}
+
+/*
+ * Test that an fallocated memfd is preserved across live update and can be
+ * written to after being preserved.
+ */
+TEST(fallocate_memfd)
+{
+ if (cwd_is_tmpfs())
+ SKIP(return, "test saves data to rootfs, cannot run on tmpfs");
+
+ switch (stage) {
+ case 1:
+ fallocate_memfd_stage_1(_metadata);
+ break;
+ case 2:
+ fallocate_memfd_stage_2(_metadata);
+ break;
+ default:
+ TH_LOG("Unknown stage %d\n", stage);
+ ASSERT_FALSE(true);
+ }
+}
+
int main(int argc, char *argv[])
{
int expected_stage = 0;
--
2.53.0.1213.gd9a14994de-goog