[PATCH v1 3/3] liveupdate: pin sessions and handle inactive ones during serialization

Pasha Tatashin pasha.tatashin at soleen.com
Tue May 5 21:32:04 PDT 2026


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 <oskar at gerlicz.space>
Signed-off-by: Pasha Tatashin <pasha.tatashin at soleen.com>
---
 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




More information about the kexec mailing list