[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