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
luo_session_alloc(const char * name)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
luo_session_free(struct luo_session * session)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
luo_session_insert(struct luo_session_header * sh,struct luo_session * session)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
luo_session_remove(struct luo_session_header * sh,struct luo_session * session)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
luo_session_finish_one(struct luo_session * session)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
luo_session_unfreeze_one(struct luo_session * session,struct luo_session_ser * ser)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
luo_session_freeze_one(struct luo_session * session,struct luo_session_ser * ser)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
luo_session_release(struct inode * inodep,struct file * filep)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
luo_session_preserve_fd(struct luo_session * session,struct luo_ucmd * ucmd)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
luo_session_retrieve_fd(struct luo_session * session,struct luo_ucmd * ucmd)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
luo_session_finish(struct luo_session * session,struct luo_ucmd * ucmd)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
luo_session_get_name(struct luo_session * session,struct luo_ucmd * ucmd)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
luo_ioctl_type_valid(struct luo_session * session,const struct luo_ioctl_op * op)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
luo_session_ioctl(struct file * filep,unsigned int cmd,unsigned long arg)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 */
luo_session_getfile(struct luo_session * session,struct file ** filep)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
luo_session_create(const char * name,struct file ** filep)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
luo_session_retrieve(const char * name,struct file ** filep)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
luo_session_setup_outgoing(u64 * sessions_pa)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
luo_session_setup_incoming(u64 sessions_pa)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
luo_session_deserialize_one(struct luo_session_header * sh,struct luo_session_ser * ser)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
luo_session_deserialize(void)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
luo_session_serialize(void)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