[PATCH v2 4/6] selftests/liveupdate: add test for zero-size memfd preservation

Pratyush Yadav
2026-04-03T19:40:35Z

A zero-size memfd is a special case of memfd preservation. It takes a
different path from normal both during preservation and during restore.
In the serialization structure, the number of folios is zero and the
vmalloc array with folios is empty. The restore logic should check for
this and make sure to not touch the invalid array.

Add a test to make sure this path works as expected. In stage 1, the
test creates and preserves a memfd without any data. In stage 2, the
test retrieves the memfd and makes sure it is still without data.

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

diff --git a/tools/testing/selftests/liveupdate/luo_memfd.c b/tools/testing/selftests/liveupdate/luo_memfd.c
index 70eb96950f04..3ba0709f2c61 100644
--- a/tools/testing/selftests/liveupdate/luo_memfd.c
+++ b/tools/testing/selftests/liveupdate/luo_memfd.c
@@ -36,6 +36,9 @@
#define MEMFD_DATA_BUFFER_SIZE SZ_1M
#define MEMFD_DATA_FS_COPY "memfd_data_fs_copy.bin"

+#define ZERO_SESSION_NAME "zero_session"
+#define ZERO_MEMFD_TOKEN 1
+
static int luo_fd = -1;
static int stage;

@@ -108,6 +111,60 @@ TEST(memfd_data)
}
}

+static void zero_memfd_stage_1(struct __test_metadata *_metadata)
+{
+ int zero_fd, session;
+ struct liveupdate_session_preserve_fd preserve_arg = { .size = sizeof(preserve_arg) };
+
+ session = luo_create_session(luo_fd, ZERO_SESSION_NAME);
+ ASSERT_GE(session, 0);
+
+ zero_fd = memfd_create("zero_memfd", 0);
+ ASSERT_GE(zero_fd, 0);
+
+ preserve_arg.fd = zero_fd;
+ preserve_arg.token = ZERO_MEMFD_TOKEN;
+ ASSERT_GE(ioctl(session, LIVEUPDATE_SESSION_PRESERVE_FD, &preserve_arg), 0);
+
+ daemonize_and_wait();
+}
+
+static void zero_memfd_stage_2(struct __test_metadata *_metadata)
+{
+ int zero_fd, session;
+ struct liveupdate_session_retrieve_fd retrieve_arg = { .size = sizeof(retrieve_arg) };
+
+ session = luo_retrieve_session(luo_fd, ZERO_SESSION_NAME);
+ ASSERT_GE(session, 0);
+
+ retrieve_arg.token = ZERO_MEMFD_TOKEN;
+ ASSERT_GE(ioctl(session, LIVEUPDATE_SESSION_RETRIEVE_FD, &retrieve_arg), 0);
+ zero_fd = retrieve_arg.fd;
+ ASSERT_GE(zero_fd, 0);
+
+ ASSERT_EQ(lseek(zero_fd, 0, SEEK_END), 0);
+
+ ASSERT_EQ(luo_session_finish(session), 0);
+}
+
+/*
+ * Test that a zero-sized memfd is preserved across live update.
+ */
+TEST(zero_memfd)
+{
+ switch (stage) {
+ case 1:
+ zero_memfd_stage_1(_metadata);
+ break;
+ case 2:
+ zero_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