xref: /linux/kernel/liveupdate/luo_session.c (revision 85cdaca6970028bf6f544c355c90035586836ddf)
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 	luo_file_set_init(&session->file_set);
158 	INIT_LIST_HEAD(&session->list);
159 	mutex_init(&session->mutex);
160 
161 	return session;
162 }
163 
164 static void luo_session_free(struct luo_session *session)
165 {
166 	luo_file_set_destroy(&session->file_set);
167 	mutex_destroy(&session->mutex);
168 	kfree(session);
169 }
170 
171 static int luo_session_insert(struct luo_session_header *sh,
172 			      struct luo_session *session)
173 {
174 	struct luo_session *it;
175 	int err;
176 
177 	guard(rwsem_write)(&sh->rwsem);
178 
179 	/*
180 	 * For outgoing we should make sure there is room in serialization array
181 	 * for new session.
182 	 */
183 	if (sh == &luo_session_global.outgoing) {
184 		err = kho_block_set_grow(&sh->block_set, sh->count + 1);
185 		if (err)
186 			return err;
187 	}
188 
189 	/*
190 	 * For small number of sessions this loop won't hurt performance
191 	 * but if we ever start using a lot of sessions, this might
192 	 * become a bottle neck during deserialization time, as it would
193 	 * cause O(n*n) complexity.
194 	 */
195 	list_for_each_entry(it, &sh->list, list) {
196 		if (!strncmp(it->name, session->name, sizeof(it->name)))
197 			return -EEXIST;
198 	}
199 	list_add_tail(&session->list, &sh->list);
200 	sh->count++;
201 
202 	return 0;
203 }
204 
205 static void luo_session_remove(struct luo_session_header *sh,
206 			       struct luo_session *session)
207 {
208 	guard(rwsem_write)(&sh->rwsem);
209 	list_del(&session->list);
210 	sh->count--;
211 	if (sh == &luo_session_global.outgoing)
212 		kho_block_set_shrink(&sh->block_set, sh->count);
213 }
214 
215 static int luo_session_finish_one(struct luo_session *session)
216 {
217 	guard(mutex)(&session->mutex);
218 	return luo_file_finish(&session->file_set);
219 }
220 
221 static void luo_session_unfreeze_one(struct luo_session *session,
222 				     struct luo_session_ser *ser)
223 {
224 	guard(mutex)(&session->mutex);
225 	luo_file_unfreeze(&session->file_set, &ser->file_set_ser);
226 }
227 
228 static int luo_session_freeze_one(struct luo_session *session,
229 				  struct luo_session_ser *ser)
230 {
231 	guard(mutex)(&session->mutex);
232 	return luo_file_freeze(&session->file_set, &ser->file_set_ser);
233 }
234 
235 static int luo_session_release(struct inode *inodep, struct file *filep)
236 {
237 	struct luo_session *session = filep->private_data;
238 	struct luo_session_header *sh;
239 
240 	guard(rwsem_read)(&luo_session_serialize_rwsem);
241 	/* If retrieved is set, it means this session is from incoming list */
242 	if (session->retrieved) {
243 		int err = luo_session_finish_one(session);
244 
245 		if (err) {
246 			pr_warn("Unable to finish session [%s] on release\n",
247 				session->name);
248 			return err;
249 		}
250 		sh = &luo_session_global.incoming;
251 	} else {
252 		scoped_guard(mutex, &session->mutex)
253 			luo_file_unpreserve_files(&session->file_set);
254 		sh = &luo_session_global.outgoing;
255 	}
256 
257 	luo_session_remove(sh, session);
258 	luo_session_free(session);
259 
260 	return 0;
261 }
262 
263 static int luo_session_preserve_fd(struct luo_session *session,
264 				   struct luo_ucmd *ucmd)
265 {
266 	struct liveupdate_session_preserve_fd *argp = ucmd->cmd;
267 	int err;
268 
269 	guard(mutex)(&session->mutex);
270 	err = luo_preserve_file(&session->file_set, argp->token, argp->fd);
271 	if (err)
272 		return err;
273 
274 	err = luo_ucmd_respond(ucmd, sizeof(*argp));
275 	if (err)
276 		pr_warn("The file was successfully preserved, but response to user failed\n");
277 
278 	return err;
279 }
280 
281 static int luo_session_retrieve_fd(struct luo_session *session,
282 				   struct luo_ucmd *ucmd)
283 {
284 	struct liveupdate_session_retrieve_fd *argp = ucmd->cmd;
285 	struct file *file;
286 	int err;
287 
288 	argp->fd = get_unused_fd_flags(O_CLOEXEC);
289 	if (argp->fd < 0)
290 		return argp->fd;
291 
292 	mutex_lock(&session->mutex);
293 	err = luo_retrieve_file(&session->file_set, argp->token, &file);
294 	mutex_unlock(&session->mutex);
295 	if (err < 0)
296 		goto err_put_fd;
297 
298 	err = luo_ucmd_respond(ucmd, sizeof(*argp));
299 	if (err)
300 		goto err_put_file;
301 
302 	fd_install(argp->fd, file);
303 
304 	return 0;
305 
306 err_put_file:
307 	fput(file);
308 err_put_fd:
309 	put_unused_fd(argp->fd);
310 
311 	return err;
312 }
313 
314 static int luo_session_finish(struct luo_session *session,
315 			      struct luo_ucmd *ucmd)
316 {
317 	struct liveupdate_session_finish *argp = ucmd->cmd;
318 	int err;
319 
320 	if (argp->reserved)
321 		return -EINVAL;
322 
323 	err = luo_session_finish_one(session);
324 	if (err)
325 		return err;
326 
327 	return luo_ucmd_respond(ucmd, sizeof(*argp));
328 }
329 
330 static int luo_session_get_name(struct luo_session *session,
331 				struct luo_ucmd *ucmd)
332 {
333 	struct liveupdate_session_get_name *argp = ucmd->cmd;
334 
335 	if (argp->reserved != 0)
336 		return -EINVAL;
337 
338 	strscpy((char *)argp->name, session->name, sizeof(argp->name));
339 
340 	return luo_ucmd_respond(ucmd, sizeof(*argp));
341 }
342 
343 union ucmd_buffer {
344 	struct liveupdate_session_finish finish;
345 	struct liveupdate_session_preserve_fd preserve;
346 	struct liveupdate_session_retrieve_fd retrieve;
347 	struct liveupdate_session_get_name get_name;
348 };
349 
350 /* Type of sessions the ioctl applies to. */
351 enum luo_ioctl_type {
352 	LUO_IOCTL_INCOMING,
353 	LUO_IOCTL_OUTGOING,
354 	LUO_IOCTL_ALL,
355 };
356 
357 struct luo_ioctl_op {
358 	unsigned int size;
359 	unsigned int min_size;
360 	unsigned int ioctl_num;
361 	enum luo_ioctl_type type;
362 	int (*execute)(struct luo_session *session, struct luo_ucmd *ucmd);
363 };
364 
365 #define IOCTL_OP(_ioctl, _fn, _struct, _last, _type)                           \
366 	[_IOC_NR(_ioctl) - LIVEUPDATE_CMD_SESSION_BASE] = {                    \
367 		.size = sizeof(_struct) +                                      \
368 			BUILD_BUG_ON_ZERO(sizeof(union ucmd_buffer) <          \
369 					  sizeof(_struct)),                    \
370 		.min_size = offsetofend(_struct, _last),                       \
371 		.ioctl_num = _ioctl,                                           \
372 		.type = _type,                                                 \
373 		.execute = _fn,                                                \
374 	}
375 
376 static const struct luo_ioctl_op luo_session_ioctl_ops[] = {
377 	IOCTL_OP(LIVEUPDATE_SESSION_FINISH, luo_session_finish,
378 		 struct liveupdate_session_finish, reserved, LUO_IOCTL_INCOMING),
379 	IOCTL_OP(LIVEUPDATE_SESSION_PRESERVE_FD, luo_session_preserve_fd,
380 		 struct liveupdate_session_preserve_fd, token, LUO_IOCTL_OUTGOING),
381 	IOCTL_OP(LIVEUPDATE_SESSION_RETRIEVE_FD, luo_session_retrieve_fd,
382 		 struct liveupdate_session_retrieve_fd, token, LUO_IOCTL_INCOMING),
383 	IOCTL_OP(LIVEUPDATE_SESSION_GET_NAME, luo_session_get_name,
384 		 struct liveupdate_session_get_name, name, LUO_IOCTL_ALL),
385 };
386 
387 static bool luo_ioctl_type_valid(struct luo_session *session,
388 				 const struct luo_ioctl_op *op)
389 {
390 	switch (op->type) {
391 	case LUO_IOCTL_INCOMING:
392 		/* Retrieved is only set on incoming sessions */
393 		return session->retrieved;
394 	case LUO_IOCTL_OUTGOING:
395 		return !session->retrieved;
396 	case LUO_IOCTL_ALL:
397 		return true;
398 	}
399 
400 	/* Catch-all. */
401 	return false;
402 }
403 
404 static long luo_session_ioctl(struct file *filep, unsigned int cmd,
405 			      unsigned long arg)
406 {
407 	struct luo_session *session = filep->private_data;
408 	const struct luo_ioctl_op *op;
409 	struct luo_ucmd ucmd = {};
410 	union ucmd_buffer buf;
411 	unsigned int nr;
412 	int ret;
413 
414 	nr = _IOC_NR(cmd);
415 	if (nr < LIVEUPDATE_CMD_SESSION_BASE || (nr - LIVEUPDATE_CMD_SESSION_BASE) >=
416 	    ARRAY_SIZE(luo_session_ioctl_ops)) {
417 		return -EINVAL;
418 	}
419 
420 	ucmd.ubuffer = (void __user *)arg;
421 	ret = get_user(ucmd.user_size, (u32 __user *)ucmd.ubuffer);
422 	if (ret)
423 		return ret;
424 
425 	op = &luo_session_ioctl_ops[nr - LIVEUPDATE_CMD_SESSION_BASE];
426 	if (op->ioctl_num != cmd)
427 		return -ENOIOCTLCMD;
428 	if (!luo_ioctl_type_valid(session, op))
429 		return -EINVAL;
430 	if (ucmd.user_size < op->min_size)
431 		return -EINVAL;
432 
433 	ucmd.cmd = &buf;
434 	ret = copy_struct_from_user(ucmd.cmd, op->size, ucmd.ubuffer,
435 				    ucmd.user_size);
436 	if (ret)
437 		return ret;
438 
439 	guard(rwsem_read)(&luo_session_serialize_rwsem);
440 	return op->execute(session, &ucmd);
441 }
442 
443 static const struct file_operations luo_session_fops = {
444 	.owner = THIS_MODULE,
445 	.release = luo_session_release,
446 	.unlocked_ioctl = luo_session_ioctl,
447 };
448 
449 /* Create a "struct file" for session */
450 static int luo_session_getfile(struct luo_session *session, struct file **filep)
451 {
452 	char name_buf[128];
453 	struct file *file;
454 
455 	lockdep_assert_held(&session->mutex);
456 	snprintf(name_buf, sizeof(name_buf), "[luo_session] %s", session->name);
457 	file = anon_inode_getfile(name_buf, &luo_session_fops, session, O_RDWR);
458 	if (IS_ERR(file))
459 		return PTR_ERR(file);
460 
461 	*filep = file;
462 
463 	return 0;
464 }
465 
466 int luo_session_create(const char *name, struct file **filep)
467 {
468 	size_t len = strnlen(name, LIVEUPDATE_SESSION_NAME_LENGTH);
469 	struct luo_session *session;
470 	int err;
471 
472 	if (len == 0 || len > LIVEUPDATE_SESSION_NAME_LENGTH - 1)
473 		return -EINVAL;
474 
475 	session = luo_session_alloc(name);
476 	if (IS_ERR(session))
477 		return PTR_ERR(session);
478 
479 	down_read(&luo_session_serialize_rwsem);
480 	err = luo_session_insert(&luo_session_global.outgoing, session);
481 	if (err)
482 		goto err_free;
483 
484 	mutex_lock(&session->mutex);
485 	err = luo_session_getfile(session, filep);
486 	mutex_unlock(&session->mutex);
487 	if (err)
488 		goto err_remove;
489 	up_read(&luo_session_serialize_rwsem);
490 
491 	return 0;
492 
493 err_remove:
494 	luo_session_remove(&luo_session_global.outgoing, session);
495 err_free:
496 	luo_session_free(session);
497 	up_read(&luo_session_serialize_rwsem);
498 
499 	return err;
500 }
501 
502 int luo_session_retrieve(const char *name, struct file **filep)
503 {
504 	struct luo_session_header *sh = &luo_session_global.incoming;
505 	struct luo_session *session = NULL;
506 	struct luo_session *it;
507 	int err;
508 
509 	guard(rwsem_read)(&luo_session_serialize_rwsem);
510 	guard(rwsem_read)(&sh->rwsem);
511 	list_for_each_entry(it, &sh->list, list) {
512 		if (!strncmp(it->name, name, sizeof(it->name))) {
513 			session = it;
514 			break;
515 		}
516 	}
517 
518 	if (!session)
519 		return -ENOENT;
520 
521 	guard(mutex)(&session->mutex);
522 	if (session->retrieved)
523 		return -EINVAL;
524 
525 	err = luo_session_getfile(session, filep);
526 	if (!err)
527 		session->retrieved = true;
528 
529 	return err;
530 }
531 
532 void __init luo_session_setup_outgoing(u64 *sessions_pa)
533 {
534 	luo_session_global.outgoing.sessions_pa = sessions_pa;
535 	luo_session_global.outgoing.active = true;
536 }
537 
538 int __init luo_session_setup_incoming(u64 sessions_pa)
539 {
540 	struct luo_session_header *sh = &luo_session_global.incoming;
541 	int err;
542 
543 	if (!sessions_pa)
544 		return 0;
545 
546 	err = kho_block_set_restore(&sh->block_set, sessions_pa);
547 	if (err)
548 		return err;
549 
550 	sh->active = true;
551 	return 0;
552 }
553 
554 static int luo_session_deserialize_one(struct luo_session_header *sh,
555 				       struct luo_session_ser *ser)
556 {
557 	struct luo_session *session;
558 	int err;
559 
560 	session = luo_session_alloc(ser->name);
561 	if (IS_ERR(session)) {
562 		pr_warn("Failed to allocate session [%.*s] during deserialization %pe\n",
563 			(int)sizeof(ser->name), ser->name, session);
564 		return PTR_ERR(session);
565 	}
566 
567 	err = luo_session_insert(sh, session);
568 	if (err) {
569 		pr_warn("Failed to insert session [%s] %pe\n",
570 			session->name, ERR_PTR(err));
571 		luo_session_free(session);
572 		return err;
573 	}
574 
575 	scoped_guard(mutex, &session->mutex) {
576 		err = luo_file_deserialize(&session->file_set,
577 					   &ser->file_set_ser);
578 	}
579 	if (err) {
580 		pr_warn("Failed to deserialize files for session [%s] %pe\n",
581 			session->name, ERR_PTR(err));
582 		return err;
583 	}
584 
585 	return 0;
586 }
587 
588 int luo_session_deserialize(void)
589 {
590 	struct luo_session_header *sh = &luo_session_global.incoming;
591 	static bool is_deserialized;
592 	struct luo_session_ser *ser;
593 	struct kho_block_set_it it;
594 	static int saved_err;
595 	int err;
596 
597 	/* If has been deserialized, always return the same error code */
598 	if (is_deserialized)
599 		return saved_err;
600 
601 	is_deserialized = true;
602 	if (!sh->active)
603 		return 0;
604 
605 	/*
606 	 * Note on error handling:
607 	 *
608 	 * If deserialization fails (e.g., allocation failure or corrupt data),
609 	 * we intentionally skip cleanup of sessions that were already restored.
610 	 *
611 	 * A partial failure leaves the preserved state inconsistent.
612 	 * Implementing a safe "undo" to unwind complex dependencies (sessions,
613 	 * files, hardware state) is error-prone and provides little value, as
614 	 * the system is effectively in a broken state.
615 	 *
616 	 * We treat these resources as leaked. The expected recovery path is for
617 	 * userspace to detect the failure and trigger a reboot, which will
618 	 * reliably reset devices and reclaim memory.
619 	 */
620 	kho_block_set_it_init(&it, &sh->block_set);
621 	while ((ser = kho_block_set_it_read_entry(&it))) {
622 		err = luo_session_deserialize_one(sh, ser);
623 		if (err)
624 			goto save_err;
625 	}
626 
627 	kho_block_set_destroy(&sh->block_set);
628 
629 	return 0;
630 
631 save_err:
632 	kho_block_set_destroy(&sh->block_set);
633 	saved_err = err;
634 	return err;
635 }
636 
637 int luo_session_serialize(void)
638 {
639 	struct luo_session_header *sh = &luo_session_global.outgoing;
640 	struct luo_session *session;
641 	struct kho_block_set_it it;
642 	int err;
643 
644 	down_write(&luo_session_serialize_rwsem);
645 	down_write(&sh->rwsem);
646 	*sh->sessions_pa = 0;
647 
648 	kho_block_set_it_init(&it, &sh->block_set);
649 
650 	list_for_each_entry(session, &sh->list, list) {
651 		struct luo_session_ser *ser = kho_block_set_it_reserve_entry(&it);
652 
653 		/* This should not fail normally as blocks were pre-allocated */
654 		if (WARN_ON_ONCE(!ser)) {
655 			err = -ENOSPC;
656 			goto err_undo;
657 		}
658 
659 		err = luo_session_freeze_one(session, ser);
660 		if (err) {
661 			kho_block_set_it_prev(&it);
662 			goto err_undo;
663 		}
664 
665 		strscpy(ser->name, session->name, sizeof(ser->name));
666 	}
667 
668 	if (sh->count > 0)
669 		*sh->sessions_pa = kho_block_set_head_pa(&sh->block_set);
670 	up_write(&sh->rwsem);
671 
672 	return 0;
673 
674 err_undo:
675 	list_for_each_entry_continue_reverse(session, &sh->list, list) {
676 		struct luo_session_ser *ser = kho_block_set_it_prev(&it);
677 
678 		luo_session_unfreeze_one(session, ser);
679 		memset(ser->name, 0, sizeof(ser->name));
680 	}
681 	up_write(&sh->rwsem);
682 	up_write(&luo_session_serialize_rwsem);
683 
684 	return err;
685 }
686