]> git.ipfire.org Git - thirdparty/linux.git/commitdiff
liveupdate: block session mutations during reboot
authorPasha Tatashin <pasha.tatashin@soleen.com>
Wed, 27 May 2026 20:27:35 +0000 (20:27 +0000)
committerMike Rapoport (Microsoft) <rppt@kernel.org>
Mon, 1 Jun 2026 06:19:38 +0000 (09:19 +0300)
During the reboot() syscall, user processes may still be running
concurrently and attempting to mutate sessions (e.g., creating,
retrieving, or releasing sessions). To prevent this, introduce
luo_session_serialize_rwsem to synchronize mutations with the
serialization process.

All session mutation operations (create, retrieve, release, ioctl) take
the read lock. The serialization process (luo_session_serialize) takes
the write lock and holds it indefinitely on success. This effectively
freezes the LUO session subsystem during the transition to the new
kernel. If serialization fails, the lock is released to allow recovery.

Fixes: 0153094d03df ("liveupdate: luo_session: add sessions support")
Reported-by: Oskar Gerlicz Kowalczuk <oskar@gerlicz.space>
Acked-by: Mike Rapoport (Microsoft) <rppt@kernel.org>
Signed-off-by: Pasha Tatashin <pasha.tatashin@soleen.com>
Reviewed-by: Pratyush Yadav (Google) <pratyush@kernel.org>
Link: https://patch.msgid.link/20260527202737.1345192-4-pasha.tatashin@soleen.com
Signed-off-by: Mike Rapoport (Microsoft) <rppt@kernel.org>
kernel/liveupdate/luo_session.c

index a1c742eeb444e04c50ad6b0b0596bbf9618b1929..5c6cebc6e32661aba14adaa492be82bd18059ee4 100644 (file)
  * 4.  Retrieval: A userspace agent in the new kernel can then call
  *     `luo_session_retrieve()` with a session name to get a new file
  *     descriptor and access the preserved state.
+ *
+ * Locking:
+ *
+ * The LUO session subsystem uses a three-tier locking hierarchy to ensure thread
+ * safety and prevent deadlocks during concurrent session mutations and kexec
+ * serialization:
+ *
+ * 1. `luo_session_serialize_rwsem` (global rwsem):
+ *    Protects session mutations (creation, retrieval, release, and ioctls)
+ *    against the serialization process during reboot.
+ *
+ *    - Readers: Taken by any path modifying or accessing session state (e.g.,
+ *      `luo_session_create()`, `luo_session_retrieve()`, `luo_session_release()`,
+ *      and `luo_session_ioctl()`).
+ *    - Writer: Taken by the serialization process (`luo_session_serialize()`)
+ *      during reboot. On success, the write lock is held indefinitely to freeze
+ *      the subsystem. On failure, it is released to allow recovery.
+ *
+ * 2. `luo_session_header->rwsem` (per-list rwsem):
+ *    Synchronizes list-level operations for the incoming and outgoing session headers.
+ *
+ *    - Writer: Taken during list mutation operations (inserting or removing a
+ *      session from the list).
+ *    - Reader: Taken when traversing the list (e.g., retrieving a session by name).
+ *
+ * 3. `luo_session->mutex` (per-session mutex):
+ *    Protects the internal state and file sets of an individual session. It is
+ *    acquired during per-session operations such as preserving, retrieving,
+ *    or freezing files.
+ *
+ * Lock Hierarchy:
+ *   `luo_session_serialize_rwsem` -> `luo_session_header->rwsem` -> `luo_session->mutex`
  */
 
 #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
                sizeof(struct luo_session_header_ser)) /                \
                sizeof(struct luo_session_ser))
 
+static DECLARE_RWSEM(luo_session_serialize_rwsem);
+
 /**
  * struct luo_session_header - Header struct for managing LUO sessions.
  * @count:      The number of sessions currently tracked in the @list.
@@ -205,6 +239,7 @@ static int luo_session_release(struct inode *inodep, struct file *filep)
        struct luo_session *session = filep->private_data;
        struct luo_session_header *sh;
 
+       guard(rwsem_read)(&luo_session_serialize_rwsem);
        /* If retrieved is set, it means this session is from incoming list */
        if (session->retrieved) {
                int err = luo_session_finish_one(session);
@@ -398,6 +433,7 @@ static long luo_session_ioctl(struct file *filep, unsigned int cmd,
        if (ret)
                return ret;
 
+       guard(rwsem_read)(&luo_session_serialize_rwsem);
        return op->execute(session, &ucmd);
 }
 
@@ -437,14 +473,17 @@ int luo_session_create(const char *name, struct file **filep)
        if (IS_ERR(session))
                return PTR_ERR(session);
 
+       down_read(&luo_session_serialize_rwsem);
        err = luo_session_insert(&luo_session_global.outgoing, session);
        if (err)
                goto err_free;
 
-       scoped_guard(mutex, &session->mutex)
-               err = luo_session_getfile(session, filep);
+       mutex_lock(&session->mutex);
+       err = luo_session_getfile(session, filep);
+       mutex_unlock(&session->mutex);
        if (err)
                goto err_remove;
+       up_read(&luo_session_serialize_rwsem);
 
        return 0;
 
@@ -452,6 +491,7 @@ err_remove:
        luo_session_remove(&luo_session_global.outgoing, session);
 err_free:
        luo_session_free(session);
+       up_read(&luo_session_serialize_rwsem);
 
        return err;
 }
@@ -463,6 +503,7 @@ int luo_session_retrieve(const char *name, struct file **filep)
        struct luo_session *it;
        int err;
 
+       guard(rwsem_read)(&luo_session_serialize_rwsem);
        guard(rwsem_read)(&sh->rwsem);
        list_for_each_entry(it, &sh->list, list) {
                if (!strncmp(it->name, name, sizeof(it->name))) {
@@ -635,7 +676,8 @@ int luo_session_serialize(void)
        int i = 0;
        int err;
 
-       guard(rwsem_write)(&sh->rwsem);
+       down_write(&luo_session_serialize_rwsem);
+       down_write(&sh->rwsem);
        list_for_each_entry(session, &sh->list, list) {
                err = luo_session_freeze_one(session, &sh->ser[i]);
                if (err)
@@ -646,6 +688,7 @@ int luo_session_serialize(void)
                i++;
        }
        sh->header_ser->count = sh->count;
+       up_write(&sh->rwsem);
 
        return 0;
 
@@ -655,6 +698,8 @@ err_undo:
                luo_session_unfreeze_one(session, &sh->ser[i]);
                memset(sh->ser[i].name, 0, sizeof(sh->ser[i].name));
        }
+       up_write(&sh->rwsem);
+       up_write(&luo_session_serialize_rwsem);
 
        return err;
 }