[PATCH 1/5] liveupdate: block outgoing session updates during reboot

Oskar Gerlicz Kowalczuk oskar at gerlicz.space
Fri Mar 20 09:37:16 PDT 2026


When kernel_kexec() starts a live update handover, LUO serializes
outgoing sessions before the reboot path freezes tasks or shuts
devices down. That leaves a window where close() and
LIVEUPDATE_SESSION_PRESERVE_FD can still mutate an existing outgoing
session after luo_session_serialize() has already captured it.

The race is dangerous because the next kernel may inherit stale file
metadata or references to memory that userspace has already
unpreserved. That breaks handover consistency and can later trigger
restore failures on already torn down state.

Mark the outgoing session set as rebooting while serialization is in
progress, reject new mutations with -EBUSY, and make release wait
until rebooting finishes before unpreserving files. Reset the flag and
wake waiters when serialization rolls back, and use READ_ONCE() and
WRITE_ONCE() so the state is visible across CPUs.

Signed-off-by: Oskar Gerlicz Kowalczuk <oskar at gerlicz.space>
---
 kernel/liveupdate/luo_session.c | 58 ++++++++++++++++++++++++++++++---
 1 file changed, 53 insertions(+), 5 deletions(-)

diff --git a/kernel/liveupdate/luo_session.c b/kernel/liveupdate/luo_session.c
index 783677295640..ee5ea2a8ed3f 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"
 
@@ -91,8 +92,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 +113,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);
@@ -152,6 +165,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;
 	}
@@ -216,9 +232,22 @@ static int luo_session_release(struct inode *inodep, struct file *filep)
 		}
 		sh = &luo_session_global.incoming;
 	} else {
-		scoped_guard(mutex, &session->mutex)
-			luo_file_unpreserve_files(&session->file_set);
 		sh = &luo_session_global.outgoing;
+
+		for (;;) {
+			down_read(&sh->rwsem);
+			if (READ_ONCE(sh->rebooting)) {
+				up_read(&sh->rwsem);
+				wait_event(sh->reboot_waitq,
+					   !READ_ONCE(sh->rebooting));
+				continue;
+			}
+
+			scoped_guard(mutex, &session->mutex)
+				luo_file_unpreserve_files(&session->file_set);
+			up_read(&sh->rwsem);
+			break;
+		}
 	}
 
 	luo_session_remove(sh, session);
@@ -231,10 +260,22 @@ 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);
+	if (!session->retrieved) {
+		sh = &luo_session_global.outgoing;
+		down_read(&sh->rwsem);
+		if (READ_ONCE(sh->rebooting)) {
+			up_read(&sh->rwsem);
+			return -EBUSY;
+		}
+	}
+
+	scoped_guard(mutex, &session->mutex)
+		err = luo_preserve_file(&session->file_set, argp->token, argp->fd);
+	if (sh)
+		up_read(&sh->rwsem);
 	if (err)
 		return err;
 
@@ -578,6 +619,10 @@ int luo_session_serialize(void)
 	int err;
 
 	guard(rwsem_write)(&sh->rwsem);
+	if (READ_ONCE(sh->rebooting))
+		return -EBUSY;
+
+	WRITE_ONCE(sh->rebooting, true);
 	list_for_each_entry(session, &sh->list, list) {
 		err = luo_session_freeze_one(session, &sh->ser[i]);
 		if (err)
@@ -595,8 +640,11 @@ int luo_session_serialize(void)
 	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));
+		memset(&sh->ser[i], 0, sizeof(sh->ser[i]));
 	}
+	sh->header_ser->count = 0;
+	/* Reset rebooting flag on serialization failure. */
+	luo_session_reboot_done(sh);
 
 	return err;
 }
-- 
2.53.0




More information about the kexec mailing list