Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux
1
fork

Configure Feed

Select the types of activity you want to include in your feed.

selftests/liveupdate: add userspace API selftests

Introduce a selftest suite for LUO. These tests validate the core
userspace-facing API provided by the /dev/liveupdate device and its
associated ioctls.

The suite covers fundamental device behavior, session management, and the
file preservation mechanism using memfd as a test case. This provides
regression testing for the LUO uAPI.

The following functionality is verified:

Device Access:
Basic open and close operations on /dev/liveupdate.
Enforcement of exclusive device access (verifying EBUSY on a
second open).

Session Management:
Successful creation of sessions with unique names.
Failure to create sessions with duplicate names.

File Preservation:
Preserving a single memfd and verifying its content remains
intact post-preservation.
Preserving multiple memfds within a single session, each with
unique data.
A complex scenario involving multiple sessions, each containing
a mix of empty and data-filled memfds.

Note: This test suite is limited to verifying the pre-kexec functionality
of LUO (e.g., session creation, file preservation). The post-kexec
restoration of resources is not covered, as the kselftest framework does
not currently support orchestrating a reboot and continuing execution in
the new kernel.

Link: https://lkml.kernel.org/r/20251125165850.3389713-17-pasha.tatashin@soleen.com
Signed-off-by: Pasha Tatashin <pasha.tatashin@soleen.com>
Reviewed-by: Pratyush Yadav <pratyush@kernel.org>
Reviewed-by: Mike Rapoport (Microsoft) <rppt@kernel.org>
Tested-by: David Matlack <dmatlack@google.com>
Cc: Aleksander Lobakin <aleksander.lobakin@intel.com>
Cc: Alexander Graf <graf@amazon.com>
Cc: Alice Ryhl <aliceryhl@google.com>
Cc: Andriy Shevchenko <andriy.shevchenko@linux.intel.com>
Cc: anish kumar <yesanishhere@gmail.com>
Cc: Anna Schumaker <anna.schumaker@oracle.com>
Cc: Bartosz Golaszewski <bartosz.golaszewski@linaro.org>
Cc: Bjorn Helgaas <bhelgaas@google.com>
Cc: Borislav Betkov <bp@alien8.de>
Cc: Chanwoo Choi <cw00.choi@samsung.com>
Cc: Chen Ridong <chenridong@huawei.com>
Cc: Chris Li <chrisl@kernel.org>
Cc: Christian Brauner <brauner@kernel.org>
Cc: Daniel Wagner <wagi@kernel.org>
Cc: Danilo Krummrich <dakr@kernel.org>
Cc: Dan Williams <dan.j.williams@intel.com>
Cc: David Hildenbrand <david@redhat.com>
Cc: David Jeffery <djeffery@redhat.com>
Cc: David Rientjes <rientjes@google.com>
Cc: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
Cc: Guixin Liu <kanie@linux.alibaba.com>
Cc: "H. Peter Anvin" <hpa@zytor.com>
Cc: Hugh Dickins <hughd@google.com>
Cc: Ilpo Järvinen <ilpo.jarvinen@linux.intel.com>
Cc: Ingo Molnar <mingo@redhat.com>
Cc: Ira Weiny <ira.weiny@intel.com>
Cc: Jann Horn <jannh@google.com>
Cc: Jason Gunthorpe <jgg@nvidia.com>
Cc: Jens Axboe <axboe@kernel.dk>
Cc: Joanthan Cameron <Jonathan.Cameron@huawei.com>
Cc: Joel Granados <joel.granados@kernel.org>
Cc: Johannes Weiner <hannes@cmpxchg.org>
Cc: Jonathan Corbet <corbet@lwn.net>
Cc: Lennart Poettering <lennart@poettering.net>
Cc: Leon Romanovsky <leon@kernel.org>
Cc: Leon Romanovsky <leonro@nvidia.com>
Cc: Lukas Wunner <lukas@wunner.de>
Cc: Marc Rutland <mark.rutland@arm.com>
Cc: Masahiro Yamada <masahiroy@kernel.org>
Cc: Matthew Maurer <mmaurer@google.com>
Cc: Miguel Ojeda <ojeda@kernel.org>
Cc: Myugnjoo Ham <myungjoo.ham@samsung.com>
Cc: Parav Pandit <parav@nvidia.com>
Cc: Pratyush Yadav <ptyadav@amazon.de>
Cc: Randy Dunlap <rdunlap@infradead.org>
Cc: Roman Gushchin <roman.gushchin@linux.dev>
Cc: Saeed Mahameed <saeedm@nvidia.com>
Cc: Samiullah Khawaja <skhawaja@google.com>
Cc: Song Liu <song@kernel.org>
Cc: Steven Rostedt <rostedt@goodmis.org>
Cc: Stuart Hayes <stuart.w.hayes@gmail.com>
Cc: Tejun Heo <tj@kernel.org>
Cc: Thomas Gleinxer <tglx@linutronix.de>
Cc: Thomas Weißschuh <linux@weissschuh.net>
Cc: Vincent Guittot <vincent.guittot@linaro.org>
Cc: William Tu <witu@nvidia.com>
Cc: Yoann Congal <yoann.congal@smile.fr>
Cc: Zhu Yanjun <yanjun.zhu@linux.dev>
Cc: Zijun Hu <quic_zijuhu@quicinc.com>
Signed-off-by: Andrew Morton <akpm@linux-foundation.org>

authored by

Pasha Tatashin and committed by
Andrew Morton
80bab43f 15fc11bb

+397
+1
MAINTAINERS
··· 14480 14480 F: include/uapi/linux/liveupdate.h 14481 14481 F: kernel/liveupdate/ 14482 14482 F: mm/memfd_luo.c 14483 + F: tools/testing/selftests/liveupdate/ 14483 14484 14484 14485 LLC (802.2) 14485 14486 L: netdev@vger.kernel.org
+1
tools/testing/selftests/Makefile
··· 54 54 TARGETS += landlock 55 55 TARGETS += lib 56 56 TARGETS += livepatch 57 + TARGETS += liveupdate 57 58 TARGETS += lkdtm 58 59 TARGETS += lsm 59 60 TARGETS += membarrier
+9
tools/testing/selftests/liveupdate/.gitignore
··· 1 + # SPDX-License-Identifier: GPL-2.0-only 2 + * 3 + !/**/ 4 + !*.c 5 + !*.h 6 + !*.sh 7 + !.gitignore 8 + !config 9 + !Makefile
+27
tools/testing/selftests/liveupdate/Makefile
··· 1 + # SPDX-License-Identifier: GPL-2.0-only 2 + 3 + TEST_GEN_PROGS += liveupdate 4 + 5 + include ../lib.mk 6 + 7 + CFLAGS += $(KHDR_INCLUDES) 8 + CFLAGS += -Wall -O2 -Wno-unused-function 9 + CFLAGS += -MD 10 + 11 + LIB_O := $(patsubst %.c, $(OUTPUT)/%.o, $(LIB_C)) 12 + TEST_O := $(patsubst %, %.o, $(TEST_GEN_PROGS)) 13 + TEST_O += $(patsubst %, %.o, $(TEST_GEN_PROGS_EXTENDED)) 14 + 15 + TEST_DEP_FILES := $(patsubst %.o, %.d, $(LIB_O)) 16 + TEST_DEP_FILES += $(patsubst %.o, %.d, $(TEST_O)) 17 + -include $(TEST_DEP_FILES) 18 + 19 + $(LIB_O): $(OUTPUT)/%.o: %.c 20 + $(CC) $(CFLAGS) $(CPPFLAGS) $(TARGET_ARCH) -c $< -o $@ 21 + 22 + $(TEST_GEN_PROGS) $(TEST_GEN_PROGS_EXTENDED): $(OUTPUT)/%: %.o $(LIB_O) 23 + $(CC) $(CFLAGS) $(CPPFLAGS) $(LDFLAGS) $(TARGET_ARCH) $< $(LIB_O) $(LDLIBS) -o $@ 24 + 25 + EXTRA_CLEAN += $(LIB_O) 26 + EXTRA_CLEAN += $(TEST_O) 27 + EXTRA_CLEAN += $(TEST_DEP_FILES)
+11
tools/testing/selftests/liveupdate/config
··· 1 + CONFIG_BLK_DEV_INITRD=y 2 + CONFIG_KEXEC_FILE=y 3 + CONFIG_KEXEC_HANDOVER=y 4 + CONFIG_KEXEC_HANDOVER_ENABLE_DEFAULT=y 5 + CONFIG_KEXEC_HANDOVER_DEBUGFS=y 6 + CONFIG_KEXEC_HANDOVER_DEBUG=y 7 + CONFIG_LIVEUPDATE=y 8 + CONFIG_LIVEUPDATE_TEST=y 9 + CONFIG_MEMFD_CREATE=y 10 + CONFIG_TMPFS=y 11 + CONFIG_SHMEM=y
+348
tools/testing/selftests/liveupdate/liveupdate.c
··· 1 + // SPDX-License-Identifier: GPL-2.0 2 + 3 + /* 4 + * Copyright (c) 2025, Google LLC. 5 + * Pasha Tatashin <pasha.tatashin@soleen.com> 6 + */ 7 + 8 + /* 9 + * Selftests for the Live Update Orchestrator. 10 + * This test suite verifies the functionality and behavior of the 11 + * /dev/liveupdate character device and its session management capabilities. 12 + * 13 + * Tests include: 14 + * - Device access: basic open/close, and enforcement of exclusive access. 15 + * - Session management: creation of unique sessions, and duplicate name detection. 16 + * - Resource preservation: successfully preserving individual and multiple memfds, 17 + * verifying contents remain accessible. 18 + * - Complex multi-session scenarios involving mixed empty and populated files. 19 + */ 20 + 21 + #include <errno.h> 22 + #include <fcntl.h> 23 + #include <string.h> 24 + #include <sys/ioctl.h> 25 + #include <unistd.h> 26 + 27 + #include <linux/liveupdate.h> 28 + 29 + #include "../kselftest.h" 30 + #include "../kselftest_harness.h" 31 + 32 + #define LIVEUPDATE_DEV "/dev/liveupdate" 33 + 34 + FIXTURE(liveupdate_device) { 35 + int fd1; 36 + int fd2; 37 + }; 38 + 39 + FIXTURE_SETUP(liveupdate_device) 40 + { 41 + self->fd1 = -1; 42 + self->fd2 = -1; 43 + } 44 + 45 + FIXTURE_TEARDOWN(liveupdate_device) 46 + { 47 + if (self->fd1 >= 0) 48 + close(self->fd1); 49 + if (self->fd2 >= 0) 50 + close(self->fd2); 51 + } 52 + 53 + /* 54 + * Test Case: Basic Open and Close 55 + * 56 + * Verifies that the /dev/liveupdate device can be opened and subsequently 57 + * closed without errors. Skips if the device does not exist. 58 + */ 59 + TEST_F(liveupdate_device, basic_open_close) 60 + { 61 + self->fd1 = open(LIVEUPDATE_DEV, O_RDWR); 62 + 63 + if (self->fd1 < 0 && errno == ENOENT) 64 + SKIP(return, "%s does not exist.", LIVEUPDATE_DEV); 65 + 66 + ASSERT_GE(self->fd1, 0); 67 + ASSERT_EQ(close(self->fd1), 0); 68 + self->fd1 = -1; 69 + } 70 + 71 + /* 72 + * Test Case: Exclusive Open Enforcement 73 + * 74 + * Verifies that the /dev/liveupdate device can only be opened by one process 75 + * at a time. It checks that a second attempt to open the device fails with 76 + * the EBUSY error code. 77 + */ 78 + TEST_F(liveupdate_device, exclusive_open) 79 + { 80 + self->fd1 = open(LIVEUPDATE_DEV, O_RDWR); 81 + 82 + if (self->fd1 < 0 && errno == ENOENT) 83 + SKIP(return, "%s does not exist.", LIVEUPDATE_DEV); 84 + 85 + ASSERT_GE(self->fd1, 0); 86 + self->fd2 = open(LIVEUPDATE_DEV, O_RDWR); 87 + EXPECT_LT(self->fd2, 0); 88 + EXPECT_EQ(errno, EBUSY); 89 + } 90 + 91 + /* Helper function to create a LUO session via ioctl. */ 92 + static int create_session(int lu_fd, const char *name) 93 + { 94 + struct liveupdate_ioctl_create_session args = {}; 95 + 96 + args.size = sizeof(args); 97 + strncpy((char *)args.name, name, sizeof(args.name) - 1); 98 + 99 + if (ioctl(lu_fd, LIVEUPDATE_IOCTL_CREATE_SESSION, &args)) 100 + return -errno; 101 + 102 + return args.fd; 103 + } 104 + 105 + /* 106 + * Test Case: Create Duplicate Session 107 + * 108 + * Verifies that attempting to create two sessions with the same name fails 109 + * on the second attempt with EEXIST. 110 + */ 111 + TEST_F(liveupdate_device, create_duplicate_session) 112 + { 113 + int session_fd1, session_fd2; 114 + 115 + self->fd1 = open(LIVEUPDATE_DEV, O_RDWR); 116 + if (self->fd1 < 0 && errno == ENOENT) 117 + SKIP(return, "%s does not exist", LIVEUPDATE_DEV); 118 + 119 + ASSERT_GE(self->fd1, 0); 120 + 121 + session_fd1 = create_session(self->fd1, "duplicate-session-test"); 122 + ASSERT_GE(session_fd1, 0); 123 + 124 + session_fd2 = create_session(self->fd1, "duplicate-session-test"); 125 + EXPECT_LT(session_fd2, 0); 126 + EXPECT_EQ(-session_fd2, EEXIST); 127 + 128 + ASSERT_EQ(close(session_fd1), 0); 129 + } 130 + 131 + /* 132 + * Test Case: Create Distinct Sessions 133 + * 134 + * Verifies that creating two sessions with different names succeeds. 135 + */ 136 + TEST_F(liveupdate_device, create_distinct_sessions) 137 + { 138 + int session_fd1, session_fd2; 139 + 140 + self->fd1 = open(LIVEUPDATE_DEV, O_RDWR); 141 + if (self->fd1 < 0 && errno == ENOENT) 142 + SKIP(return, "%s does not exist", LIVEUPDATE_DEV); 143 + 144 + ASSERT_GE(self->fd1, 0); 145 + 146 + session_fd1 = create_session(self->fd1, "distinct-session-1"); 147 + ASSERT_GE(session_fd1, 0); 148 + 149 + session_fd2 = create_session(self->fd1, "distinct-session-2"); 150 + ASSERT_GE(session_fd2, 0); 151 + 152 + ASSERT_EQ(close(session_fd1), 0); 153 + ASSERT_EQ(close(session_fd2), 0); 154 + } 155 + 156 + static int preserve_fd(int session_fd, int fd_to_preserve, __u64 token) 157 + { 158 + struct liveupdate_session_preserve_fd args = {}; 159 + 160 + args.size = sizeof(args); 161 + args.fd = fd_to_preserve; 162 + args.token = token; 163 + 164 + if (ioctl(session_fd, LIVEUPDATE_SESSION_PRESERVE_FD, &args)) 165 + return -errno; 166 + 167 + return 0; 168 + } 169 + 170 + /* 171 + * Test Case: Preserve MemFD 172 + * 173 + * Verifies that a valid memfd can be successfully preserved in a session and 174 + * that its contents remain intact after the preservation call. 175 + */ 176 + TEST_F(liveupdate_device, preserve_memfd) 177 + { 178 + const char *test_str = "hello liveupdate"; 179 + char read_buf[64] = {}; 180 + int session_fd, mem_fd; 181 + 182 + self->fd1 = open(LIVEUPDATE_DEV, O_RDWR); 183 + if (self->fd1 < 0 && errno == ENOENT) 184 + SKIP(return, "%s does not exist", LIVEUPDATE_DEV); 185 + ASSERT_GE(self->fd1, 0); 186 + 187 + session_fd = create_session(self->fd1, "preserve-memfd-test"); 188 + ASSERT_GE(session_fd, 0); 189 + 190 + mem_fd = memfd_create("test-memfd", 0); 191 + ASSERT_GE(mem_fd, 0); 192 + 193 + ASSERT_EQ(write(mem_fd, test_str, strlen(test_str)), strlen(test_str)); 194 + ASSERT_EQ(preserve_fd(session_fd, mem_fd, 0x1234), 0); 195 + ASSERT_EQ(close(session_fd), 0); 196 + 197 + ASSERT_EQ(lseek(mem_fd, 0, SEEK_SET), 0); 198 + ASSERT_EQ(read(mem_fd, read_buf, sizeof(read_buf)), strlen(test_str)); 199 + ASSERT_STREQ(read_buf, test_str); 200 + ASSERT_EQ(close(mem_fd), 0); 201 + } 202 + 203 + /* 204 + * Test Case: Preserve Multiple MemFDs 205 + * 206 + * Verifies that multiple memfds can be preserved in a single session, 207 + * each with a unique token, and that their contents remain distinct and 208 + * correct after preservation. 209 + */ 210 + TEST_F(liveupdate_device, preserve_multiple_memfds) 211 + { 212 + const char *test_str1 = "data for memfd one"; 213 + const char *test_str2 = "data for memfd two"; 214 + char read_buf[64] = {}; 215 + int session_fd, mem_fd1, mem_fd2; 216 + 217 + self->fd1 = open(LIVEUPDATE_DEV, O_RDWR); 218 + if (self->fd1 < 0 && errno == ENOENT) 219 + SKIP(return, "%s does not exist", LIVEUPDATE_DEV); 220 + ASSERT_GE(self->fd1, 0); 221 + 222 + session_fd = create_session(self->fd1, "preserve-multi-memfd-test"); 223 + ASSERT_GE(session_fd, 0); 224 + 225 + mem_fd1 = memfd_create("test-memfd-1", 0); 226 + ASSERT_GE(mem_fd1, 0); 227 + mem_fd2 = memfd_create("test-memfd-2", 0); 228 + ASSERT_GE(mem_fd2, 0); 229 + 230 + ASSERT_EQ(write(mem_fd1, test_str1, strlen(test_str1)), strlen(test_str1)); 231 + ASSERT_EQ(write(mem_fd2, test_str2, strlen(test_str2)), strlen(test_str2)); 232 + 233 + ASSERT_EQ(preserve_fd(session_fd, mem_fd1, 0xAAAA), 0); 234 + ASSERT_EQ(preserve_fd(session_fd, mem_fd2, 0xBBBB), 0); 235 + 236 + memset(read_buf, 0, sizeof(read_buf)); 237 + ASSERT_EQ(lseek(mem_fd1, 0, SEEK_SET), 0); 238 + ASSERT_EQ(read(mem_fd1, read_buf, sizeof(read_buf)), strlen(test_str1)); 239 + ASSERT_STREQ(read_buf, test_str1); 240 + 241 + memset(read_buf, 0, sizeof(read_buf)); 242 + ASSERT_EQ(lseek(mem_fd2, 0, SEEK_SET), 0); 243 + ASSERT_EQ(read(mem_fd2, read_buf, sizeof(read_buf)), strlen(test_str2)); 244 + ASSERT_STREQ(read_buf, test_str2); 245 + 246 + ASSERT_EQ(close(mem_fd1), 0); 247 + ASSERT_EQ(close(mem_fd2), 0); 248 + ASSERT_EQ(close(session_fd), 0); 249 + } 250 + 251 + /* 252 + * Test Case: Preserve Complex Scenario 253 + * 254 + * Verifies a more complex scenario with multiple sessions and a mix of empty 255 + * and non-empty memfds distributed across them. 256 + */ 257 + TEST_F(liveupdate_device, preserve_complex_scenario) 258 + { 259 + const char *data1 = "data for session 1"; 260 + const char *data2 = "data for session 2"; 261 + char read_buf[64] = {}; 262 + int session_fd1, session_fd2; 263 + int mem_fd_data1, mem_fd_empty1, mem_fd_data2, mem_fd_empty2; 264 + 265 + self->fd1 = open(LIVEUPDATE_DEV, O_RDWR); 266 + if (self->fd1 < 0 && errno == ENOENT) 267 + SKIP(return, "%s does not exist", LIVEUPDATE_DEV); 268 + ASSERT_GE(self->fd1, 0); 269 + 270 + session_fd1 = create_session(self->fd1, "complex-session-1"); 271 + ASSERT_GE(session_fd1, 0); 272 + session_fd2 = create_session(self->fd1, "complex-session-2"); 273 + ASSERT_GE(session_fd2, 0); 274 + 275 + mem_fd_data1 = memfd_create("data1", 0); 276 + ASSERT_GE(mem_fd_data1, 0); 277 + ASSERT_EQ(write(mem_fd_data1, data1, strlen(data1)), strlen(data1)); 278 + 279 + mem_fd_empty1 = memfd_create("empty1", 0); 280 + ASSERT_GE(mem_fd_empty1, 0); 281 + 282 + mem_fd_data2 = memfd_create("data2", 0); 283 + ASSERT_GE(mem_fd_data2, 0); 284 + ASSERT_EQ(write(mem_fd_data2, data2, strlen(data2)), strlen(data2)); 285 + 286 + mem_fd_empty2 = memfd_create("empty2", 0); 287 + ASSERT_GE(mem_fd_empty2, 0); 288 + 289 + ASSERT_EQ(preserve_fd(session_fd1, mem_fd_data1, 0x1111), 0); 290 + ASSERT_EQ(preserve_fd(session_fd1, mem_fd_empty1, 0x2222), 0); 291 + ASSERT_EQ(preserve_fd(session_fd2, mem_fd_data2, 0x3333), 0); 292 + ASSERT_EQ(preserve_fd(session_fd2, mem_fd_empty2, 0x4444), 0); 293 + 294 + ASSERT_EQ(lseek(mem_fd_data1, 0, SEEK_SET), 0); 295 + ASSERT_EQ(read(mem_fd_data1, read_buf, sizeof(read_buf)), strlen(data1)); 296 + ASSERT_STREQ(read_buf, data1); 297 + 298 + memset(read_buf, 0, sizeof(read_buf)); 299 + ASSERT_EQ(lseek(mem_fd_data2, 0, SEEK_SET), 0); 300 + ASSERT_EQ(read(mem_fd_data2, read_buf, sizeof(read_buf)), strlen(data2)); 301 + ASSERT_STREQ(read_buf, data2); 302 + 303 + ASSERT_EQ(lseek(mem_fd_empty1, 0, SEEK_SET), 0); 304 + ASSERT_EQ(read(mem_fd_empty1, read_buf, sizeof(read_buf)), 0); 305 + 306 + ASSERT_EQ(lseek(mem_fd_empty2, 0, SEEK_SET), 0); 307 + ASSERT_EQ(read(mem_fd_empty2, read_buf, sizeof(read_buf)), 0); 308 + 309 + ASSERT_EQ(close(mem_fd_data1), 0); 310 + ASSERT_EQ(close(mem_fd_empty1), 0); 311 + ASSERT_EQ(close(mem_fd_data2), 0); 312 + ASSERT_EQ(close(mem_fd_empty2), 0); 313 + ASSERT_EQ(close(session_fd1), 0); 314 + ASSERT_EQ(close(session_fd2), 0); 315 + } 316 + 317 + /* 318 + * Test Case: Preserve Unsupported File Descriptor 319 + * 320 + * Verifies that attempting to preserve a file descriptor that does not have 321 + * a registered Live Update handler fails gracefully. 322 + * Uses /dev/null as a representative of a file type (character device) 323 + * that is not supported by the orchestrator. 324 + */ 325 + TEST_F(liveupdate_device, preserve_unsupported_fd) 326 + { 327 + int session_fd, unsupported_fd; 328 + int ret; 329 + 330 + self->fd1 = open(LIVEUPDATE_DEV, O_RDWR); 331 + if (self->fd1 < 0 && errno == ENOENT) 332 + SKIP(return, "%s does not exist", LIVEUPDATE_DEV); 333 + ASSERT_GE(self->fd1, 0); 334 + 335 + session_fd = create_session(self->fd1, "unsupported-fd-test"); 336 + ASSERT_GE(session_fd, 0); 337 + 338 + unsupported_fd = open("/dev/null", O_RDWR); 339 + ASSERT_GE(unsupported_fd, 0); 340 + 341 + ret = preserve_fd(session_fd, unsupported_fd, 0xDEAD); 342 + EXPECT_EQ(ret, -ENOENT); 343 + 344 + ASSERT_EQ(close(unsupported_fd), 0); 345 + ASSERT_EQ(close(session_fd), 0); 346 + } 347 + 348 + TEST_HARNESS_MAIN