xref: /linux/kernel/liveupdate/luo_session.c (revision 2a441a14c2c03b39d1c89438dd28cef9d8fa57d5)
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  * DOC: LUO Sessions
10  *
11  * LUO Sessions provide the core mechanism for grouping and managing `struct
12  * file *` instances that need to be preserved across a kexec-based live
13  * update. Each session acts as a named container for a set of file objects,
14  * allowing a userspace agent to manage the lifecycle of resources critical to a
15  * workload.
16  *
17  * Core Concepts:
18  *
19  * - Named Containers: Sessions are identified by a unique, user-provided name,
20  *   which is used for both creation in the current kernel and retrieval in the
21  *   next kernel.
22  *
23  * - Userspace Interface: Session management is driven from userspace via
24  *   ioctls on /dev/liveupdate.
25  *
26  * - Serialization: Session metadata is preserved using the KHO framework. When
27  *   a live update is triggered via kexec, session metadata is serialized into
28  *   a chain of linked-blocks and placed in a preserved memory region. The
29  *   physical address of the first block header is stored in the centralized
30  *   `struct luo_ser` structure.
31  *
32  * Session Lifecycle:
33  *
34  * 1.  Creation: A userspace agent calls `luo_session_create()` to create a
35  *     new, empty session and receives a file descriptor for it.
36  *
37  * 2.  Serialization: When the `reboot(LINUX_REBOOT_CMD_KEXEC)` syscall is
38  *     made, `luo_session_serialize()` is called. It iterates through all
39  *     active sessions and writes their metadata into a memory area preserved
40  *     by KHO.
41  *
42  * 3.  Deserialization (in new kernel): After kexec, `luo_session_deserialize()`
43  *     runs, reading the serialized data and creating a list of `struct
44  *     luo_session` objects representing the preserved sessions.
45  *
46  * 4.  Retrieval: A userspace agent in the new kernel can then call
47  *     `luo_session_retrieve()` with a session name to get a new file
48  *     descriptor and access the preserved state.
49  *
50  * Locking:
51  *
52  * The LUO session subsystem uses a three-tier locking hierarchy to ensure thread
53  * safety and prevent deadlocks during concurrent session mutations and kexec
54  * serialization:
55  *
56  * 1. `luo_session_serialize_rwsem` (global rwsem):
57  *    Protects session mutations (creation, retrieval, release, and ioctls)
58  *    against the serialization process during reboot.
59  *
60  *    - Readers: Taken by any path modifying or accessing session state (e.g.,
61  *      `luo_session_create()`, `luo_session_retrieve()`, `luo_session_release()`,
62  *      and `luo_session_ioctl()`).
63  *    - Writer: Taken by the serialization process (`luo_session_serialize()`)
64  *      during reboot. On success, the write lock is held indefinitely to freeze
65  *      the subsystem. On failure, it is released to allow recovery.
66  *
67  * 2. `luo_session_header->rwsem` (per-list rwsem):
68  *    Synchronizes list-level operations for the incoming and outgoing session headers.
69  *
70  *    - Writer: Taken during list mutation operations (inserting or removing a
71  *      session from the list).
72  *    - Reader: Taken when traversing the list (e.g., retrieving a session by name).
73  *
74  * 3. `luo_session->mutex` (per-session mutex):
75  *    Protects the internal state and file sets of an individual session. It is
76  *    acquired during per-session operations such as preserving, retrieving,
77  *    or freezing files.
78  *
79  * Lock Hierarchy:
80  *   `luo_session_serialize_rwsem` -> `luo_session_header->rwsem` -> `luo_session->mutex`
81  */
82 
83 #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
84 
85 #include <linux/anon_inodes.h>
86 #include <linux/cleanup.h>
87 #include <linux/err.h>
88 #include <linux/errno.h>
89 #include <linux/file.h>
90 #include <linux/fs.h>
91 #include <linux/io.h>
92 #include <linux/kexec_handover.h>
93 #include <linux/kho_block.h>
94 #include <linux/kho/abi/luo.h>
95 #include <linux/list.h>
96 #include <linux/liveupdate.h>
97 #include <linux/mutex.h>
98 #include <linux/rwsem.h>
99 #include <linux/slab.h>
100 #include <uapi/linux/liveupdate.h>
101 #include "luo_internal.h"
102 
103 static DECLARE_RWSEM(luo_session_serialize_rwsem);
104 /**
105  * struct luo_session_header - Header struct for managing LUO sessions.
106  * @count:       The number of sessions currently tracked in the @list.
107  * @list:        The head of the linked list of `struct luo_session` instances.
108  * @rwsem:       A read-write semaphore providing synchronized access to the
109  *               session list and other fields in this structure.
110  * @block_set:   The set of serialization blocks.
111  * @sessions_pa: Points to the location of sessions_pa within struct luo_ser.
112  * @active:      Set to true when first initialized. If previous kernel did not
113  *               send session data, active stays false for incoming.
114  */
115 struct luo_session_header {
116 	long count;
117 	struct list_head list;
118 	struct rw_semaphore rwsem;
119 	struct kho_block_set block_set;
120 	u64 *sessions_pa;
121 	bool active;
122 };
123 
124 /**
125  * struct luo_session_global - Global container for managing LUO sessions.
126  * @incoming:     The sessions passed from the previous kernel.
127  * @outgoing:     The sessions that are going to be passed to the next kernel.
128  */
129 struct luo_session_global {
130 	struct luo_session_header incoming;
131 	struct luo_session_header outgoing;
132 };
133 
134 static struct luo_session_global luo_session_global = {
135 	.incoming = {
136 		.list = LIST_HEAD_INIT(luo_session_global.incoming.list),
137 		.rwsem = __RWSEM_INITIALIZER(luo_session_global.incoming.rwsem),
138 		.block_set = KHO_BLOCK_SET_INIT(luo_session_global.incoming.block_set,
139 						sizeof(struct luo_session_ser)),
140 	},
141 	.outgoing = {
142 		.list = LIST_HEAD_INIT(luo_session_global.outgoing.list),
143 		.rwsem = __RWSEM_INITIALIZER(luo_session_global.outgoing.rwsem),
144 		.block_set = KHO_BLOCK_SET_INIT(luo_session_global.outgoing.block_set,
145 						sizeof(struct luo_session_ser)),
146 	},
147 };
148 
149 static struct luo_session *luo_session_alloc(const char *name)
150 {
151 	struct luo_session *session = kzalloc_obj(*session);
152 
153 	if (!session)
154 		return ERR_PTR(-ENOMEM);
155 
156 	strscpy(session->name, name, sizeof(session->name));
157 	INIT_LIST_HEAD(&session->file_set.files_list);
158 	luo_file_set_init(&session->file_set);
159 	INIT_LIST_HEAD(&session->list);
160 	mutex_init(&session->mutex);
161 
162 	return session;
163 }
164 
165 static void luo_session_free(struct luo_session *session)
166 {
167 	luo_file_set_destroy(&session->file_set);
168 	mutex_destroy(&session->mutex);
169 	kfree(session);
170 }
171 
172 static int luo_session_insert(struct luo_session_header *sh,
173 			      struct luo_session *session)
174 {
175 	struct luo_session *it;
176 	int err;
177 
178 	guard(rwsem_write)(&sh->rwsem);
179 
180 	/*
181 	 * For outgoing we should make sure there is room in serialization array
182 	 * for new session.
183 	 */
184 	if (sh == &luo_session_global.outgoing) {
185 		err = kho_block_set_grow(&sh->block_set, sh->count + 1);
186 		if (err)
187 			return err;
188 	}
189 
190 	/*
191 	 * For small number of sessions this loop won't hurt performance
192 	 * but if we ever start using a lot of sessions, this might
193 	 * become a bottle neck during deserialization time, as it would
194 	 * cause O(n*n) complexity.
195 	 */
196 	list_for_each_entry(it, &sh->list, list) {
197 		if (!strncmp(it->name, session->name, sizeof(it->name)))
198 			return -EEXIST;
199 	}
200 	list_add_tail(&session->list, &sh->list);
201 	sh->count++;
202 
203 	return 0;
204 }
205 
206 static void luo_session_remove(struct luo_session_header *sh,
207 			       struct luo_session *session)
208 {
209 	guard(rwsem_write)(&sh->rwsem);
210 	list_del(&session->list);
211 	sh->count--;
212 	if (sh == &luo_session_global.outgoing)
213 		kho_block_set_shrink(&sh->block_set, sh->count);
214 }
215 
216 static int luo_session_finish_one(struct luo_session *session)
217 {
218 	guard(mutex)(&session->mutex);
219 	return luo_file_finish(&session->file_set);
220 }
221 
222 static void luo_session_unfreeze_one(struct luo_session *session,
223 				     struct luo_session_ser *ser)
224 {
225 	guard(mutex)(&session->mutex);
226 	luo_file_unfreeze(&session->file_set, &ser->file_set_ser);
227 }
228 
229 static int luo_session_freeze_one(struct luo_session *session,
230 				  struct luo_session_ser *ser)
231 {
232 	guard(mutex)(&session->mutex);
233 	return luo_file_freeze(&session->file_set, &ser->file_set_ser);
234 }
235 
236 static int luo_session_release(struct inode *inodep, struct file *filep)
237 {
238 	struct luo_session *session = filep->private_data;
239 	struct luo_session_header *sh;
240 
241 	guard(rwsem_read)(&luo_session_serialize_rwsem);
242 	/* If retrieved is set, it means this session is from incoming list */
243 	if (session->retrieved) {
244 		int err = luo_session_finish_one(session);
245 
246 		if (err) {
247 			pr_warn("Unable to finish session [%s] on release\n",
248 				session->name);
249 			return err;
250 		}
251 		sh = &luo_session_global.incoming;
252 	} else {
253 		scoped_guard(mutex, &session->mutex)
254 			luo_file_unpreserve_files(&session->file_set);
255 		sh = &luo_session_global.outgoing;
256 	}
257 
258 	luo_session_remove(sh, session);
259 	luo_session_free(session);
260 
261 	return 0;
262 }
263 
264 static int luo_session_preserve_fd(struct luo_session *session,
265 				   struct luo_ucmd *ucmd)
266 {
267 	struct liveupdate_session_preserve_fd *argp = ucmd->cmd;
268 	int err;
269 
270 	guard(mutex)(&session->mutex);
271 	err = luo_preserve_file(&session->file_set, argp->token, argp->fd);
272 	if (err)
273 		return err;
274 
275 	err = luo_ucmd_respond(ucmd, sizeof(*argp));
276 	if (err)
277 		pr_warn("The file was successfully preserved, but response to user failed\n");
278 
279 	return err;
280 }
281 
282 static int luo_session_retrieve_fd(struct luo_session *session,
283 				   struct luo_ucmd *ucmd)
284 {
285 	struct liveupdate_session_retrieve_fd *argp = ucmd->cmd;
286 	struct file *file;
287 	int err;
288 
289 	argp->fd = get_unused_fd_flags(O_CLOEXEC);
290 	if (argp->fd < 0)
291 		return argp->fd;
292 
293 	mutex_lock(&session->mutex);
294 	err = luo_retrieve_file(&session->file_set, argp->token, &file);
295 	mutex_unlock(&session->mutex);
296 	if (err < 0)
297 		goto err_put_fd;
298 
299 	err = luo_ucmd_respond(ucmd, sizeof(*argp));
300 	if (err)
301 		goto err_put_file;
302 
303 	fd_install(argp->fd, file);
304 
305 	return 0;
306 
307 err_put_file:
308 	fput(file);
309 err_put_fd:
310 	put_unused_fd(argp->fd);
311 
312 	return err;
313 }
314 
315 static int luo_session_finish(struct luo_session *session,
316 			      struct luo_ucmd *ucmd)
317 {
318 	struct liveupdate_session_finish *argp = ucmd->cmd;
319 	int err = luo_session_finish_one(session);
320 
321 	if (err)
322 		return err;
323 
324 	return luo_ucmd_respond(ucmd, sizeof(*argp));
325 }
326 
327 static int luo_session_get_name(struct luo_session *session,
328 				struct luo_ucmd *ucmd)
329 {
330 	struct liveupdate_session_get_name *argp = ucmd->cmd;
331 
332 	if (argp->reserved != 0)
333 		return -EINVAL;
334 
335 	strscpy((char *)argp->name, session->name, sizeof(argp->name));
336 
337 	return luo_ucmd_respond(ucmd, sizeof(*argp));
338 }
339 
340 union ucmd_buffer {
341 	struct liveupdate_session_finish finish;
342 	struct liveupdate_session_preserve_fd preserve;
343 	struct liveupdate_session_retrieve_fd retrieve;
344 	struct liveupdate_session_get_name get_name;
345 };
346 
347 /* Type of sessions the ioctl applies to. */
348 enum luo_ioctl_type {
349 	LUO_IOCTL_INCOMING,
350 	LUO_IOCTL_OUTGOING,
351 	LUO_IOCTL_ALL,
352 };
353 
354 struct luo_ioctl_op {
355 	unsigned int size;
356 	unsigned int min_size;
357 	unsigned int ioctl_num;
358 	enum luo_ioctl_type type;
359 	int (*execute)(struct luo_session *session, struct luo_ucmd *ucmd);
360 };
361 
362 #define IOCTL_OP(_ioctl, _fn, _struct, _last, _type)                           \
363 	[_IOC_NR(_ioctl) - LIVEUPDATE_CMD_SESSION_BASE] = {                    \
364 		.size = sizeof(_struct) +                                      \
365 			BUILD_BUG_ON_ZERO(sizeof(union ucmd_buffer) <          \
366 					  sizeof(_struct)),                    \
367 		.min_size = offsetofend(_struct, _last),                       \
368 		.ioctl_num = _ioctl,                                           \
369 		.type = _type,                                                 \
370 		.execute = _fn,                                                \
371 	}
372 
373 static const struct luo_ioctl_op luo_session_ioctl_ops[] = {
374 	IOCTL_OP(LIVEUPDATE_SESSION_FINISH, luo_session_finish,
375 		 struct liveupdate_session_finish, reserved, LUO_IOCTL_INCOMING),
376 	IOCTL_OP(LIVEUPDATE_SESSION_PRESERVE_FD, luo_session_preserve_fd,
377 		 struct liveupdate_session_preserve_fd, token, LUO_IOCTL_OUTGOING),
378 	IOCTL_OP(LIVEUPDATE_SESSION_RETRIEVE_FD, luo_session_retrieve_fd,
379 		 struct liveupdate_session_retrieve_fd, token, LUO_IOCTL_INCOMING),
380 	IOCTL_OP(LIVEUPDATE_SESSION_GET_NAME, luo_session_get_name,
381 		 struct liveupdate_session_retrieve_fd, token, LUO_IOCTL_ALL),
382 };
383 
384 static bool luo_ioctl_type_valid(struct luo_session *session,
385 				 const struct luo_ioctl_op *op)
386 {
387 	switch (op->type) {
388 	case LUO_IOCTL_INCOMING:
389 		/* Retrieved is only set on incoming sessions */
390 		return session->retrieved;
391 	case LUO_IOCTL_OUTGOING:
392 		return !session->retrieved;
393 	case LUO_IOCTL_ALL:
394 		return true;
395 	}
396 
397 	/* Catch-all. */
398 	return false;
399 }
400 
401 static long luo_session_ioctl(struct file *filep, unsigned int cmd,
402 			      unsigned long arg)
403 {
404 	struct luo_session *session = filep->private_data;
405 	const struct luo_ioctl_op *op;
406 	struct luo_ucmd ucmd = {};
407 	union ucmd_buffer buf;
408 	unsigned int nr;
409 	int ret;
410 
411 	nr = _IOC_NR(cmd);
412 	if (nr < LIVEUPDATE_CMD_SESSION_BASE || (nr - LIVEUPDATE_CMD_SESSION_BASE) >=
413 	    ARRAY_SIZE(luo_session_ioctl_ops)) {
414 		return -EINVAL;
415 	}
416 
417 	ucmd.ubuffer = (void __user *)arg;
418 	ret = get_user(ucmd.user_size, (u32 __user *)ucmd.ubuffer);
419 	if (ret)
420 		return ret;
421 
422 	op = &luo_session_ioctl_ops[nr - LIVEUPDATE_CMD_SESSION_BASE];
423 	if (op->ioctl_num != cmd)
424 		return -ENOIOCTLCMD;
425 	if (!luo_ioctl_type_valid(session, op))
426 		return -EINVAL;
427 	if (ucmd.user_size < op->min_size)
428 		return -EINVAL;
429 
430 	ucmd.cmd = &buf;
431 	ret = copy_struct_from_user(ucmd.cmd, op->size, ucmd.ubuffer,
432 				    ucmd.user_size);
433 	if (ret)
434 		return ret;
435 
436 	guard(rwsem_read)(&luo_session_serialize_rwsem);
437 	return op->execute(session, &ucmd);
438 }
439 
440 static const struct file_operations luo_session_fops = {
441 	.owner = THIS_MODULE,
442 	.release = luo_session_release,
443 	.unlocked_ioctl = luo_session_ioctl,
444 };
445 
446 /* Create a "struct file" for session */
447 static int luo_session_getfile(struct luo_session *session, struct file **filep)
448 {
449 	char name_buf[128];
450 	struct file *file;
451 
452 	lockdep_assert_held(&session->mutex);
453 	snprintf(name_buf, sizeof(name_buf), "[luo_session] %s", session->name);
454 	file = anon_inode_getfile(name_buf, &luo_session_fops, session, O_RDWR);
455 	if (IS_ERR(file))
456 		return PTR_ERR(file);
457 
458 	*filep = file;
459 
460 	return 0;
461 }
462 
463 int luo_session_create(const char *name, struct file **filep)
464 {
465 	size_t len = strnlen(name, LIVEUPDATE_SESSION_NAME_LENGTH);
466 	struct luo_session *session;
467 	int err;
468 
469 	if (len == 0 || len > LIVEUPDATE_SESSION_NAME_LENGTH - 1)
470 		return -EINVAL;
471 
472 	session = luo_session_alloc(name);
473 	if (IS_ERR(session))
474 		return PTR_ERR(session);
475 
476 	down_read(&luo_session_serialize_rwsem);
477 	err = luo_session_insert(&luo_session_global.outgoing, session);
478 	if (err)
479 		goto err_free;
480 
481 	mutex_lock(&session->mutex);
482 	err = luo_session_getfile(session, filep);
483 	mutex_unlock(&session->mutex);
484 	if (err)
485 		goto err_remove;
486 	up_read(&luo_session_serialize_rwsem);
487 
488 	return 0;
489 
490 err_remove:
491 	luo_session_remove(&luo_session_global.outgoing, session);
492 err_free:
493 	luo_session_free(session);
494 	up_read(&luo_session_serialize_rwsem);
495 
496 	return err;
497 }
498 
499 int luo_session_retrieve(const char *name, struct file **filep)
500 {
501 	struct luo_session_header *sh = &luo_session_global.incoming;
502 	struct luo_session *session = NULL;
503 	struct luo_session *it;
504 	int err;
505 
506 	guard(rwsem_read)(&luo_session_serialize_rwsem);
507 	guard(rwsem_read)(&sh->rwsem);
508 	list_for_each_entry(it, &sh->list, list) {
509 		if (!strncmp(it->name, name, sizeof(it->name))) {
510 			session = it;
511 			break;
512 		}
513 	}
514 
515 	if (!session)
516 		return -ENOENT;
517 
518 	guard(mutex)(&session->mutex);
519 	if (session->retrieved)
520 		return -EINVAL;
521 
522 	err = luo_session_getfile(session, filep);
523 	if (!err)
524 		session->retrieved = true;
525 
526 	return err;
527 }
528 
529 void __init luo_session_setup_outgoing(u64 *sessions_pa)
530 {
531 	luo_session_global.outgoing.sessions_pa = sessions_pa;
532 	luo_session_global.outgoing.active = true;
533 }
534 
535 int __init luo_session_setup_incoming(u64 sessions_pa)
536 {
537 	struct luo_session_header *sh = &luo_session_global.incoming;
538 	int err;
539 
540 	if (!sessions_pa)
541 		return 0;
542 
543 	err = kho_block_set_restore(&sh->block_set, sessions_pa);
544 	if (err)
545 		return err;
546 
547 	sh->active = true;
548 	return 0;
549 }
550 
551 static int luo_session_deserialize_one(struct luo_session_header *sh,
552 				       struct luo_session_ser *ser)
553 {
554 	struct luo_session *session;
555 	int err;
556 
557 	session = luo_session_alloc(ser->name);
558 	if (IS_ERR(session)) {
559 		pr_warn("Failed to allocate session [%.*s] during deserialization %pe\n",
560 			(int)sizeof(ser->name), ser->name, session);
561 		return PTR_ERR(session);
562 	}
563 
564 	err = luo_session_insert(sh, session);
565 	if (err) {
566 		pr_warn("Failed to insert session [%s] %pe\n",
567 			session->name, ERR_PTR(err));
568 		luo_session_free(session);
569 		return err;
570 	}
571 
572 	scoped_guard(mutex, &session->mutex) {
573 		err = luo_file_deserialize(&session->file_set,
574 					   &ser->file_set_ser);
575 	}
576 	if (err) {
577 		pr_warn("Failed to deserialize files for session [%s] %pe\n",
578 			session->name, ERR_PTR(err));
579 		return err;
580 	}
581 
582 	return 0;
583 }
584 
585 int luo_session_deserialize(void)
586 {
587 	struct luo_session_header *sh = &luo_session_global.incoming;
588 	static bool is_deserialized;
589 	struct luo_session_ser *ser;
590 	struct kho_block_set_it it;
591 	static int saved_err;
592 	int err;
593 
594 	/* If has been deserialized, always return the same error code */
595 	if (is_deserialized)
596 		return saved_err;
597 
598 	is_deserialized = true;
599 	if (!sh->active)
600 		return 0;
601 
602 	/*
603 	 * Note on error handling:
604 	 *
605 	 * If deserialization fails (e.g., allocation failure or corrupt data),
606 	 * we intentionally skip cleanup of sessions that were already restored.
607 	 *
608 	 * A partial failure leaves the preserved state inconsistent.
609 	 * Implementing a safe "undo" to unwind complex dependencies (sessions,
610 	 * files, hardware state) is error-prone and provides little value, as
611 	 * the system is effectively in a broken state.
612 	 *
613 	 * We treat these resources as leaked. The expected recovery path is for
614 	 * userspace to detect the failure and trigger a reboot, which will
615 	 * reliably reset devices and reclaim memory.
616 	 */
617 	kho_block_set_it_init(&it, &sh->block_set);
618 	while ((ser = kho_block_set_it_read_entry(&it))) {
619 		err = luo_session_deserialize_one(sh, ser);
620 		if (err)
621 			goto save_err;
622 	}
623 
624 	kho_block_set_destroy(&sh->block_set);
625 
626 	return 0;
627 
628 save_err:
629 	kho_block_set_destroy(&sh->block_set);
630 	saved_err = err;
631 	return err;
632 }
633 
634 int luo_session_serialize(void)
635 {
636 	struct luo_session_header *sh = &luo_session_global.outgoing;
637 	struct luo_session *session;
638 	struct kho_block_set_it it;
639 	int err;
640 
641 	down_write(&luo_session_serialize_rwsem);
642 	down_write(&sh->rwsem);
643 	*sh->sessions_pa = 0;
644 
645 	kho_block_set_it_init(&it, &sh->block_set);
646 
647 	list_for_each_entry(session, &sh->list, list) {
648 		struct luo_session_ser *ser = kho_block_set_it_reserve_entry(&it);
649 
650 		/* This should not fail normally as blocks were pre-allocated */
651 		if (WARN_ON_ONCE(!ser)) {
652 			err = -ENOSPC;
653 			goto err_undo;
654 		}
655 
656 		err = luo_session_freeze_one(session, ser);
657 		if (err) {
658 			kho_block_set_it_prev(&it);
659 			goto err_undo;
660 		}
661 
662 		strscpy(ser->name, session->name, sizeof(ser->name));
663 	}
664 
665 	if (sh->count > 0)
666 		*sh->sessions_pa = kho_block_set_head_pa(&sh->block_set);
667 	up_write(&sh->rwsem);
668 
669 	return 0;
670 
671 err_undo:
672 	list_for_each_entry_continue_reverse(session, &sh->list, list) {
673 		struct luo_session_ser *ser = kho_block_set_it_prev(&it);
674 
675 		luo_session_unfreeze_one(session, ser);
676 		memset(ser->name, 0, sizeof(ser->name));
677 	}
678 	up_write(&sh->rwsem);
679 	up_write(&luo_session_serialize_rwsem);
680 
681 	return err;
682 }
683