During the reboot() syscall, user processes are still running
concurrently. Even though the system is actively serializing LUO
sessions and will not return to userspace unless liveupdate_reboot()
fails, it is still possible for a user process to close a preserved LUO
session. This creates a race condition where a session could be
destroyed while it is being serialized.

To fix this, we must ensure that we only preserve sessions that are not
closed at the time of serialization. Take a reference to the session's
struct file for all outgoing sessions to pin them during this process.

Handle inactive sessions (where get_file_active() fails) by cleaning
them up and removing them from the outgoing list during the pinning
phase. This ensures serialization can safely proceed with the remaining
active sessions.

Fixes: 0153094d03df ("liveupdate: luo_session: add sessions support")
Reported-by: Oskar Gerlicz Kowalczuk <[email protected]>
Signed-off-by: Pasha Tatashin <[email protected]>
---
 kernel/liveupdate/luo_internal.h |  1 +
 kernel/liveupdate/luo_session.c  | 77 +++++++++++++++++++++++++++++---
 2 files changed, 73 insertions(+), 5 deletions(-)

diff --git a/kernel/liveupdate/luo_internal.h b/kernel/liveupdate/luo_internal.h
index 875844d7a41d..1124bab6e0de 100644
--- a/kernel/liveupdate/luo_internal.h
+++ b/kernel/liveupdate/luo_internal.h
@@ -73,6 +73,7 @@ struct luo_session {
        struct luo_session_ser *ser;
        struct list_head list;
        bool retrieved;
+       struct file *file;
        struct luo_file_set file_set;
        struct mutex mutex;
 };
diff --git a/kernel/liveupdate/luo_session.c b/kernel/liveupdate/luo_session.c
index 996adc995514..b344b64bbced 100644
--- a/kernel/liveupdate/luo_session.c
+++ b/kernel/liveupdate/luo_session.c
@@ -177,12 +177,27 @@ static int luo_session_insert(struct luo_session_header 
*sh,
        return 0;
 }
 
+static void __luo_session_remove(struct luo_session_header *sh,
+                                struct luo_session *session)
+{
+       lockdep_assert_held_write(&sh->rwsem);
+       if (!list_empty(&session->list)) {
+               list_del_init(&session->list);
+               sh->count--;
+       }
+}
+
+static void luo_session_unpreserve_files(struct luo_session *session)
+{
+       scoped_guard(mutex, &session->mutex)
+               luo_file_unpreserve_files(&session->file_set);
+}
+
 static void luo_session_remove(struct luo_session_header *sh,
                               struct luo_session *session)
 {
-       guard(rwsem_write)(&sh->rwsem);
-       list_del(&session->list);
-       sh->count--;
+       scoped_guard(rwsem_write, &sh->rwsem)
+               __luo_session_remove(sh, session);
 }
 
 static int luo_session_finish_one(struct luo_session *session)
@@ -217,12 +232,13 @@ static int luo_session_release(struct inode *inodep, 
struct file *filep)
                if (err) {
                        pr_warn("Unable to finish session [%s] on release\n",
                                session->name);
+                       scoped_guard(mutex, &session->mutex)
+                               session->file = NULL;
                        return err;
                }
                sh = &luo_session_global.incoming;
        } else {
-               scoped_guard(mutex, &session->mutex)
-                       luo_file_unpreserve_files(&session->file_set);
+               luo_session_unpreserve_files(session);
                sh = &luo_session_global.outgoing;
        }
 
@@ -380,16 +396,65 @@ static int luo_session_getfile(struct luo_session 
*session, struct file **filep)
        struct file *file;
 
        lockdep_assert_held(&session->mutex);
+
+       /* serialization may evict partially initialized session */
+       if (list_empty(&session->list))
+               return -EBUSY;
+
        snprintf(name_buf, sizeof(name_buf), "[luo_session] %s", session->name);
        file = anon_inode_getfile(name_buf, &luo_session_fops, session, O_RDWR);
        if (IS_ERR(file))
                return PTR_ERR(file);
 
+       session->file = file;
        *filep = file;
 
        return 0;
 }
 
+/**
+ * luo_session_get_all_outgoing - Pins all active outgoing sessions.
+ *
+ * This function iterates through all sessions in the outgoing list and
+ * attempts to pin their associated file descriptors. If a session's file
+ * is no longer active (refcount 0), the session is cleaned up and removed.
+ *
+ * Note: Successfully pinned files have their reference count increased.
+ * These references are intentionally leaked in the current kernel upon
+ * successful live update, as the system will transition to a new kernel
+ * image which will reclaim all memory. In case of failure, they are
+ * released in luo_session_put_all_outgoing().
+ */
+static void luo_session_get_all_outgoing(void)
+{
+       struct luo_session_header *sh = &luo_session_global.outgoing;
+       struct luo_session *session, *tmp;
+
+       lockdep_assert_held_write(&sh->rwsem);
+       list_for_each_entry_safe(session, tmp, &sh->list, list) {
+               guard(mutex)(&session->mutex);
+               if (!session->file) {
+                       __luo_session_remove(sh, session);
+               } else if (!get_file_active(&session->file)) {
+                       luo_session_unpreserve_files(session);
+                       __luo_session_remove(sh, session);
+               }
+       }
+}
+
+static void luo_session_put_all_outgoing(void)
+{
+       struct luo_session_header *sh = &luo_session_global.outgoing;
+       struct luo_session *session;
+
+       lockdep_assert_held_write(&sh->rwsem);
+       list_for_each_entry(session, &sh->list, list) {
+               guard(mutex)(&session->mutex);
+               if (session->file)
+                       fput(session->file);
+       }
+}
+
 int luo_session_create(const char *name, struct file **filep)
 {
        struct luo_session *session;
@@ -596,6 +661,7 @@ int luo_session_serialize(void)
        guard(rwsem_write)(&sh->rwsem);
 
        sh->rebooting = true;
+       luo_session_get_all_outgoing();
        list_for_each_entry(session, &sh->list, list) {
                err = luo_session_freeze_one(session, &sh->ser[i]);
                if (err)
@@ -616,6 +682,7 @@ int luo_session_serialize(void)
                luo_session_unfreeze_one(session, &sh->ser[i]);
                memset(sh->ser[i].name, 0, sizeof(sh->ser[i].name));
        }
+       luo_session_put_all_outgoing();
 
        return err;
 }
-- 
2.54.0.545.g6539524ca2-goog


Reply via email to