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