From: Oskar Gerlicz Kowalczuk <oskar@gerlicz.space>
To: Pasha Tatashin <pasha.tatashin@soleen.com>,
Mike Rapoport <rppt@kernel.org>, Baoquan He <bhe@redhat.com>
Cc: Pratyush Yadav <pratyush@kernel.org>,
Andrew Morton <akpm@linux-foundation.org>,
linux-kernel@vger.kernel.org, kexec@lists.infradead.org,
linux-mm@kvack.org, Oskar Gerlicz Kowalczuk <oskar@gerlicz.space>
Subject: [PATCH v4 1/5] liveupdate: block outgoing session updates during reboot
Date: Tue, 24 Mar 2026 22:27:26 +0100 [thread overview]
Message-ID: <20260324212730.65290-2-oskar@gerlicz.space> (raw)
In-Reply-To: <20260324212730.65290-1-oskar@gerlicz.space>
Block new outgoing session mutations with the rebooting gate and serialize
in-flight operations with session->mutex. During serialization, pin the
outgoing sessions with temporary references instead of relying on an
auxiliary in-flight counter.
Also make incoming session origin immutable and use retrieved only as the
"checked out by userspace" bit. This keeps FINISH, close(), and
retrieve-by-name on a single lifetime model without reintroducing a larger
session state machine.
Signed-off-by: Oskar Gerlicz Kowalczuk <oskar@gerlicz.space>
---
kernel/liveupdate/luo_internal.h | 9 +-
kernel/liveupdate/luo_session.c | 308 +++++++++++++++++++++++++------
2 files changed, 259 insertions(+), 58 deletions(-)
diff --git a/kernel/liveupdate/luo_internal.h b/kernel/liveupdate/luo_internal.h
index 8083d8739b09..e29e1a46b491 100644
--- a/kernel/liveupdate/luo_internal.h
+++ b/kernel/liveupdate/luo_internal.h
@@ -9,6 +9,7 @@
#define _LINUX_LUO_INTERNAL_H
#include <linux/liveupdate.h>
+#include <linux/refcount.h>
#include <linux/uaccess.h>
struct luo_ucmd {
@@ -63,8 +64,10 @@ struct luo_file_set {
* @list: A list_head member used to link this session into a global list
* of either outgoing (to be preserved) or incoming (restored from
* previous kernel) sessions.
- * @retrieved: A boolean flag indicating whether this session has been
- * retrieved by a consumer in the new kernel.
+ * @incoming: True for sessions restored from the previous kernel. This is
+ * immutable after publication and distinguishes incoming sessions
+ * from outgoing ones.
+ * @retrieved: True while an incoming session is checked out by userspace.
* @file_set: A set of files that belong to this session.
* @mutex: protects fields in the luo_session.
*/
@@ -72,8 +75,10 @@ struct luo_session {
char name[LIVEUPDATE_SESSION_NAME_LENGTH];
struct luo_session_ser *ser;
struct list_head list;
+ bool incoming;
bool retrieved;
struct luo_file_set file_set;
+ refcount_t refs;
struct mutex mutex;
};
diff --git a/kernel/liveupdate/luo_session.c b/kernel/liveupdate/luo_session.c
index 783677295640..63aee9cc881d 100644
--- a/kernel/liveupdate/luo_session.c
+++ b/kernel/liveupdate/luo_session.c
@@ -66,6 +66,7 @@
#include <linux/rwsem.h>
#include <linux/slab.h>
#include <linux/unaligned.h>
+#include <linux/wait.h>
#include <uapi/linux/liveupdate.h>
#include "luo_internal.h"
@@ -81,9 +82,12 @@
* @list: The head of the linked list of `struct luo_session` instances.
* @rwsem: A read-write semaphore providing synchronized access to the
* session list and other fields in this structure.
+ * @reboot_waitq: Wait queue used by close() and serialize() to wait for
+ * rebooting transitions.
* @header_ser: The header data of serialization array.
* @ser: The serialized session data (an array of
* `struct luo_session_ser`).
+ * @rebooting: True while outgoing sessions are frozen for kexec handover.
* @active: Set to true when first initialized. If previous kernel did not
* send session data, active stays false for incoming.
*/
@@ -91,8 +95,10 @@ struct luo_session_header {
long count;
struct list_head list;
struct rw_semaphore rwsem;
+ wait_queue_head_t reboot_waitq;
struct luo_session_header_ser *header_ser;
struct luo_session_ser *ser;
+ bool rebooting;
bool active;
};
@@ -110,13 +116,23 @@ static struct luo_session_global luo_session_global = {
.incoming = {
.list = LIST_HEAD_INIT(luo_session_global.incoming.list),
.rwsem = __RWSEM_INITIALIZER(luo_session_global.incoming.rwsem),
+ .reboot_waitq =
+ __WAIT_QUEUE_HEAD_INITIALIZER(luo_session_global.incoming.reboot_waitq),
},
.outgoing = {
.list = LIST_HEAD_INIT(luo_session_global.outgoing.list),
.rwsem = __RWSEM_INITIALIZER(luo_session_global.outgoing.rwsem),
+ .reboot_waitq =
+ __WAIT_QUEUE_HEAD_INITIALIZER(luo_session_global.outgoing.reboot_waitq),
},
};
+static void luo_session_reboot_done(struct luo_session_header *sh)
+{
+ WRITE_ONCE(sh->rebooting, false);
+ wake_up_all(&sh->reboot_waitq);
+}
+
static struct luo_session *luo_session_alloc(const char *name)
{
struct luo_session *session = kzalloc_obj(*session);
@@ -128,6 +144,7 @@ static struct luo_session *luo_session_alloc(const char *name)
INIT_LIST_HEAD(&session->file_set.files_list);
luo_file_set_init(&session->file_set);
INIT_LIST_HEAD(&session->list);
+ refcount_set(&session->refs, 1);
mutex_init(&session->mutex);
return session;
@@ -140,6 +157,17 @@ static void luo_session_free(struct luo_session *session)
kfree(session);
}
+static void luo_session_get(struct luo_session *session)
+{
+ refcount_inc(&session->refs);
+}
+
+static void luo_session_put(struct luo_session *session)
+{
+ if (refcount_dec_and_test(&session->refs))
+ luo_session_free(session);
+}
+
static int luo_session_insert(struct luo_session_header *sh,
struct luo_session *session)
{
@@ -152,6 +180,9 @@ static int luo_session_insert(struct luo_session_header *sh,
* for new session.
*/
if (sh == &luo_session_global.outgoing) {
+ if (READ_ONCE(sh->rebooting))
+ return -EBUSY;
+
if (sh->count == LUO_SESSION_MAX)
return -ENOMEM;
}
@@ -172,58 +203,157 @@ 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)
+{
+ list_del_init(&session->list);
+ sh->count--;
+}
+
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--;
+ __luo_session_remove(sh, session);
}
-static int luo_session_finish_one(struct luo_session *session)
+static int luo_session_outgoing_begin(struct luo_session *session,
+ struct luo_session_header **shp)
{
- guard(mutex)(&session->mutex);
- return luo_file_finish(&session->file_set);
+ struct luo_session_header *sh;
+
+ if (session->incoming)
+ return -EINVAL;
+
+ sh = &luo_session_global.outgoing;
+ down_read(&sh->rwsem);
+ if (READ_ONCE(sh->rebooting)) {
+ up_read(&sh->rwsem);
+ return -EBUSY;
+ }
+
+ *shp = sh;
+ return 0;
}
-static void luo_session_unfreeze_one(struct luo_session *session,
- struct luo_session_ser *ser)
+static void luo_session_outgoing_end(struct luo_session_header *sh)
{
- guard(mutex)(&session->mutex);
- luo_file_unfreeze(&session->file_set, &ser->file_set_ser);
+ if (sh)
+ up_read(&sh->rwsem);
}
-static int luo_session_freeze_one(struct luo_session *session,
- struct luo_session_ser *ser)
+static int luo_session_get_all_outgoing(struct luo_session **sessions)
{
- guard(mutex)(&session->mutex);
- return luo_file_freeze(&session->file_set, &ser->file_set_ser);
+ struct luo_session_header *sh = &luo_session_global.outgoing;
+ struct luo_session *session;
+ u64 nr = 0;
+
+ down_write(&sh->rwsem);
+ if (READ_ONCE(sh->rebooting)) {
+ up_write(&sh->rwsem);
+ return -EBUSY;
+ }
+
+ WRITE_ONCE(sh->rebooting, true);
+ list_for_each_entry(session, &sh->list, list) {
+ luo_session_get(session);
+ sessions[nr++] = session;
+ }
+ up_write(&sh->rwsem);
+
+ return nr;
+}
+
+static void luo_session_put_all(struct luo_session **sessions, u64 nr)
+{
+ while (nr)
+ luo_session_put(sessions[--nr]);
+}
+
+static void luo_session_wait_reboot(struct luo_session_header *sh)
+{
+ DEFINE_WAIT(wait);
+
+ for (;;) {
+ prepare_to_wait(&sh->reboot_waitq, &wait,
+ TASK_UNINTERRUPTIBLE | TASK_FREEZABLE);
+ if (!READ_ONCE(sh->rebooting))
+ break;
+ schedule();
+ }
+
+ finish_wait(&sh->reboot_waitq, &wait);
+}
+
+static int luo_session_finish_retrieved(struct luo_session *session)
+{
+ lockdep_assert_held(&session->mutex);
+ if (!session->retrieved)
+ return -EINVAL;
+
+ return luo_file_finish(&session->file_set);
}
static int luo_session_release(struct inode *inodep, struct file *filep)
{
struct luo_session *session = filep->private_data;
- struct luo_session_header *sh;
+ struct luo_session_header *sh = &luo_session_global.incoming;
+ int ret = 0;
+ bool removed = false;
- /* If retrieved is set, it means this session is from incoming list */
- if (session->retrieved) {
- int err = luo_session_finish_one(session);
+ if (session->incoming) {
+ scoped_guard(mutex, &session->mutex) {
+ if (session->retrieved) {
+ ret = luo_session_finish_retrieved(session);
+ if (!ret) {
+ down_write(&sh->rwsem);
+ if (!list_empty(&session->list)) {
+ __luo_session_remove(sh, session);
+ removed = true;
+ }
+ up_write(&sh->rwsem);
+ }
+ WRITE_ONCE(session->retrieved, false);
+ }
+ }
- if (err) {
+ if (ret) {
pr_warn("Unable to finish session [%s] on release\n",
session->name);
- return err;
+ luo_session_put(session);
+ return ret;
+ }
+
+ if (removed)
+ luo_session_put(session);
+ luo_session_put(session);
+ return 0;
+ }
+
+ sh = &luo_session_global.outgoing;
+
+ for (;;) {
+ down_read(&sh->rwsem);
+ if (READ_ONCE(sh->rebooting)) {
+ up_read(&sh->rwsem);
+ luo_session_wait_reboot(sh);
+ continue;
}
- sh = &luo_session_global.incoming;
- } else {
- scoped_guard(mutex, &session->mutex)
- luo_file_unpreserve_files(&session->file_set);
- sh = &luo_session_global.outgoing;
+
+ mutex_lock(&session->mutex);
+ up_read(&sh->rwsem);
+ break;
}
- luo_session_remove(sh, session);
- luo_session_free(session);
+ luo_file_unpreserve_files(&session->file_set);
+ down_write(&sh->rwsem);
+ __luo_session_remove(sh, session);
+ up_write(&sh->rwsem);
+ mutex_unlock(&session->mutex);
+
+ luo_session_put(session);
+ luo_session_put(session);
return 0;
}
@@ -231,10 +361,16 @@ static int luo_session_preserve_fd(struct luo_session *session,
struct luo_ucmd *ucmd)
{
struct liveupdate_session_preserve_fd *argp = ucmd->cmd;
+ struct luo_session_header *sh = NULL;
int err;
- guard(mutex)(&session->mutex);
- err = luo_preserve_file(&session->file_set, argp->token, argp->fd);
+ err = luo_session_outgoing_begin(session, &sh);
+ if (err)
+ return err;
+
+ scoped_guard(mutex, &session->mutex)
+ err = luo_preserve_file(&session->file_set, argp->token, argp->fd);
+ luo_session_outgoing_end(sh);
if (err)
return err;
@@ -256,8 +392,13 @@ static int luo_session_retrieve_fd(struct luo_session *session,
if (argp->fd < 0)
return argp->fd;
- guard(mutex)(&session->mutex);
- err = luo_retrieve_file(&session->file_set, argp->token, &file);
+ scoped_guard(mutex, &session->mutex) {
+ if (!session->retrieved)
+ err = -EINVAL;
+ else
+ err = luo_retrieve_file(&session->file_set,
+ argp->token, &file);
+ }
if (err < 0)
goto err_put_fd;
@@ -281,11 +422,28 @@ static int luo_session_finish(struct luo_session *session,
struct luo_ucmd *ucmd)
{
struct liveupdate_session_finish *argp = ucmd->cmd;
- int err = luo_session_finish_one(session);
+ struct luo_session_header *sh = &luo_session_global.incoming;
+ bool removed = false;
+ int err;
+ scoped_guard(mutex, &session->mutex) {
+ err = luo_session_finish_retrieved(session);
+ if (!err) {
+ down_write(&sh->rwsem);
+ if (!list_empty(&session->list)) {
+ __luo_session_remove(sh, session);
+ removed = true;
+ }
+ up_write(&sh->rwsem);
+ WRITE_ONCE(session->retrieved, false);
+ }
+ }
if (err)
return err;
+ if (removed)
+ luo_session_put(session);
+
return luo_ucmd_respond(ucmd, sizeof(*argp));
}
@@ -371,9 +529,12 @@ static int luo_session_getfile(struct luo_session *session, struct file **filep)
lockdep_assert_held(&session->mutex);
snprintf(name_buf, sizeof(name_buf), "[luo_session] %s", session->name);
+ luo_session_get(session);
file = anon_inode_getfile(name_buf, &luo_session_fops, session, O_RDWR);
- if (IS_ERR(file))
+ if (IS_ERR(file)) {
+ luo_session_put(session);
return PTR_ERR(file);
+ }
*filep = file;
@@ -401,9 +562,10 @@ int luo_session_create(const char *name, struct file **filep)
return 0;
err_remove:
- luo_session_remove(&luo_session_global.outgoing, session);
+ scoped_guard(mutex, &session->mutex)
+ luo_session_remove(&luo_session_global.outgoing, session);
err_free:
- luo_session_free(session);
+ luo_session_put(session);
return err;
}
@@ -418,6 +580,8 @@ int luo_session_retrieve(const char *name, struct file **filep)
scoped_guard(rwsem_read, &sh->rwsem) {
list_for_each_entry(it, &sh->list, list) {
if (!strncmp(it->name, name, sizeof(it->name))) {
+ /* Keep the session alive after dropping rwsem. */
+ luo_session_get(it);
session = it;
break;
}
@@ -428,12 +592,14 @@ int luo_session_retrieve(const char *name, struct file **filep)
return -ENOENT;
guard(mutex)(&session->mutex);
- if (session->retrieved)
- return -EINVAL;
+ if (session->retrieved || list_empty(&session->list))
+ err = -EINVAL;
+ else
+ err = luo_session_getfile(session, filep);
- err = luo_session_getfile(session, filep);
if (!err)
- session->retrieved = true;
+ WRITE_ONCE(session->retrieved, true);
+ luo_session_put(session);
return err;
}
@@ -548,6 +714,7 @@ int luo_session_deserialize(void)
sh->ser[i].name, session);
return PTR_ERR(session);
}
+ session->incoming = true;
err = luo_session_insert(sh, session);
if (err) {
@@ -573,32 +740,61 @@ int luo_session_deserialize(void)
int luo_session_serialize(void)
{
struct luo_session_header *sh = &luo_session_global.outgoing;
- struct luo_session *session;
- int i = 0;
+ struct luo_session **sessions;
+ u64 nr;
+ u64 i;
+ u64 frozen_nr = 0;
int err;
- guard(rwsem_write)(&sh->rwsem);
- list_for_each_entry(session, &sh->list, list) {
- err = luo_session_freeze_one(session, &sh->ser[i]);
- if (err)
- goto err_undo;
+ sessions = kcalloc(LUO_SESSION_MAX, sizeof(*sessions), GFP_KERNEL);
+ if (!sessions)
+ return -ENOMEM;
- strscpy(sh->ser[i].name, session->name,
- sizeof(sh->ser[i].name));
- i++;
- }
- sh->header_ser->count = sh->count;
+ err = luo_session_get_all_outgoing(sessions);
+ if (err < 0)
+ goto out_free_sessions;
- return 0;
+ nr = err;
+ for (i = 0; i < nr; i++) {
+ struct luo_session *session = sessions[i];
-err_undo:
- list_for_each_entry_continue_reverse(session, &sh->list, list) {
- i--;
- luo_session_unfreeze_one(session, &sh->ser[i]);
- memset(sh->ser[i].name, 0, sizeof(sh->ser[i].name));
+ scoped_guard(mutex, &session->mutex) {
+ if (list_empty(&session->list))
+ continue;
+
+ err = luo_file_freeze(&session->file_set,
+ &sh->ser[frozen_nr].file_set_ser);
+ if (err)
+ goto err_undo;
+
+ strscpy(sh->ser[frozen_nr].name, session->name,
+ sizeof(sh->ser[frozen_nr].name));
+ swap(sessions[frozen_nr], sessions[i]);
+ frozen_nr++;
+ }
}
+ sh->header_ser->count = frozen_nr;
+ err = 0;
+out_put_sessions:
+ luo_session_put_all(sessions, nr);
+out_free_sessions:
+ kfree(sessions);
return err;
+
+err_undo:
+ while (frozen_nr) {
+ struct luo_session *session = sessions[--frozen_nr];
+
+ scoped_guard(mutex, &session->mutex) {
+ luo_file_unfreeze(&session->file_set,
+ &sh->ser[frozen_nr].file_set_ser);
+ memset(&sh->ser[frozen_nr], 0, sizeof(sh->ser[frozen_nr]));
+ }
+ }
+ sh->header_ser->count = 0;
+ luo_session_reboot_done(sh);
+ goto out_put_sessions;
}
/**
--
2.53.0
next prev parent reply other threads:[~2026-03-24 21:31 UTC|newest]
Thread overview: 11+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-03-24 21:27 [PATCH v4 0/5] liveupdate: tighten handover cleanup and session lifetime Oskar Gerlicz Kowalczuk
2026-03-24 21:27 ` Oskar Gerlicz Kowalczuk [this message]
2026-03-24 21:27 ` [PATCH v4 2/5] kexec: abort liveupdate handover on kernel_kexec() unwind Oskar Gerlicz Kowalczuk
2026-03-24 21:39 ` [PATCH v4 3/5] liveupdate: fail session restore on file deserialization errors Oskar Gerlicz Kowalczuk
2026-03-24 21:39 ` [PATCH v4 4/5] liveupdate: validate handover metadata before using it Oskar Gerlicz Kowalczuk
2026-03-24 21:39 ` [PATCH v4 5/5] liveupdate: harden FLB lifetime and remaining teardown paths Oskar Gerlicz Kowalczuk
2026-03-25 13:38 ` [PATCH v4 0/5] liveupdate: tighten handover cleanup and session lifetime Pasha Tatashin
2026-03-26 6:43 ` oskar
2026-03-26 6:47 ` oskar
2026-03-26 9:18 ` Mike Rapoport
2026-03-26 14:49 ` Pasha Tatashin
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=20260324212730.65290-2-oskar@gerlicz.space \
--to=oskar@gerlicz.space \
--cc=akpm@linux-foundation.org \
--cc=bhe@redhat.com \
--cc=kexec@lists.infradead.org \
--cc=linux-kernel@vger.kernel.org \
--cc=linux-mm@kvack.org \
--cc=pasha.tatashin@soleen.com \
--cc=pratyush@kernel.org \
--cc=rppt@kernel.org \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox