public inbox for linux-mm@kvack.org
 help / color / mirror / Atom feed
* [PATCH 1/5] liveupdate: block outgoing session updates during reboot
@ 2026-03-20 16:37 Oskar Gerlicz Kowalczuk
  2026-03-20 16:37 ` [PATCH 2/5] kexec: abort liveupdate handover on kernel_kexec() unwind Oskar Gerlicz Kowalczuk
                   ` (5 more replies)
  0 siblings, 6 replies; 8+ messages in thread
From: Oskar Gerlicz Kowalczuk @ 2026-03-20 16:37 UTC (permalink / raw)
  To: Pasha Tatashin, Mike Rapoport, Baoquan He
  Cc: Pratyush Yadav, Andrew Morton, linux-kernel, kexec, linux-mm,
	Oskar Gerlicz Kowalczuk

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@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



^ permalink raw reply related	[flat|nested] 8+ messages in thread

* [PATCH 2/5] kexec: abort liveupdate handover on kernel_kexec() unwind
  2026-03-20 16:37 [PATCH 1/5] liveupdate: block outgoing session updates during reboot Oskar Gerlicz Kowalczuk
@ 2026-03-20 16:37 ` Oskar Gerlicz Kowalczuk
  2026-03-20 16:37 ` [PATCH 3/5] liveupdate: fail session restore on file deserialization errors Oskar Gerlicz Kowalczuk
                   ` (4 subsequent siblings)
  5 siblings, 0 replies; 8+ messages in thread
From: Oskar Gerlicz Kowalczuk @ 2026-03-20 16:37 UTC (permalink / raw)
  To: Pasha Tatashin, Mike Rapoport, Baoquan He
  Cc: Pratyush Yadav, Andrew Morton, linux-kernel, kexec, linux-mm,
	Oskar Gerlicz Kowalczuk

Once outgoing sessions are blocked during reboot, every failure path
after liveupdate_reboot() must release that state again. Today
kernel_kexec() sets liveupdate_prepared only after
liveupdate_reboot() returns successfully, so a partial failure inside
liveupdate_reboot() skips the abort path entirely. The
kho_finalize() failure path also leaves the LUO session state frozen.

This is dangerous because a failed kexec attempt can leave userspace
with outgoing sessions stuck in reboot state, blocking release and
preserve paths until the next reboot.

Export a liveupdate_reboot_abort() helper, call it from the
kho_finalize() error path, and mark liveupdate as prepared before
entering liveupdate_reboot(). That makes every failed handover attempt
unwind the session state and wake blocked waiters.

Signed-off-by: Oskar Gerlicz Kowalczuk <oskar@gerlicz.space>
---
 include/linux/liveupdate.h       |  5 +++++
 kernel/kexec_core.c              |  4 ++++
 kernel/liveupdate/luo_core.c     | 11 ++++++++++-
 kernel/liveupdate/luo_internal.h |  1 +
 kernel/liveupdate/luo_session.c  | 23 +++++++++++++++++++++++
 5 files changed, 43 insertions(+), 1 deletion(-)

diff --git a/include/linux/liveupdate.h b/include/linux/liveupdate.h
index dd11fdc76a5f..d93b043a0421 100644
--- a/include/linux/liveupdate.h
+++ b/include/linux/liveupdate.h
@@ -226,6 +226,7 @@ bool liveupdate_enabled(void);
 
 /* Called during kexec to tell LUO that entered into reboot */
 int liveupdate_reboot(void);
+void liveupdate_reboot_abort(void);
 
 int liveupdate_register_file_handler(struct liveupdate_file_handler *fh);
 int liveupdate_unregister_file_handler(struct liveupdate_file_handler *fh);
@@ -250,6 +251,10 @@ static inline int liveupdate_reboot(void)
 	return 0;
 }
 
+static inline void liveupdate_reboot_abort(void)
+{
+}
+
 static inline int liveupdate_register_file_handler(struct liveupdate_file_handler *fh)
 {
 	return -EOPNOTSUPP;
diff --git a/kernel/kexec_core.c b/kernel/kexec_core.c
index 2fea396d29b9..492c17f7e96f 100644
--- a/kernel/kexec_core.c
+++ b/kernel/kexec_core.c
@@ -1139,6 +1139,7 @@ bool kexec_load_permitted(int kexec_image_type)
 int kernel_kexec(void)
 {
 	int error = 0;
+	bool liveupdate_prepared = false;
 
 	if (!kexec_trylock())
 		return -EBUSY;
@@ -1147,6 +1148,7 @@ int kernel_kexec(void)
 		goto Unlock;
 	}
 
+	liveupdate_prepared = true;
 	error = liveupdate_reboot();
 	if (error)
 		goto Unlock;
@@ -1231,6 +1233,8 @@ int kernel_kexec(void)
 #endif
 
  Unlock:
+	if (error && liveupdate_prepared)
+		liveupdate_reboot_abort();
 	kexec_unlock();
 	return error;
 }
diff --git a/kernel/liveupdate/luo_core.c b/kernel/liveupdate/luo_core.c
index dda7bb57d421..95a0b81ce60d 100644
--- a/kernel/liveupdate/luo_core.c
+++ b/kernel/liveupdate/luo_core.c
@@ -233,8 +233,9 @@ int liveupdate_reboot(void)
 	err = kho_finalize();
 	if (err) {
 		pr_err("kho_finalize failed %d\n", err);
+		liveupdate_reboot_abort();
 		/*
-		 * kho_finalize() may return libfdt errors, to aboid passing to
+		 * kho_finalize() may return libfdt errors, to avoid passing to
 		 * userspace unknown errors, change this to EAGAIN.
 		 */
 		err = -EAGAIN;
@@ -243,6 +244,14 @@ int liveupdate_reboot(void)
 	return err;
 }
 
+void liveupdate_reboot_abort(void)
+{
+	if (!liveupdate_enabled())
+		return;
+
+	luo_session_abort_reboot();
+}
+
 /**
  * liveupdate_enabled - Check if the live update feature is enabled.
  *
diff --git a/kernel/liveupdate/luo_internal.h b/kernel/liveupdate/luo_internal.h
index 8083d8739b09..ad09ea756156 100644
--- a/kernel/liveupdate/luo_internal.h
+++ b/kernel/liveupdate/luo_internal.h
@@ -82,6 +82,7 @@ int luo_session_retrieve(const char *name, struct file **filep);
 int __init luo_session_setup_outgoing(void *fdt);
 int __init luo_session_setup_incoming(void *fdt);
 int luo_session_serialize(void);
+void luo_session_abort_reboot(void);
 int luo_session_deserialize(void);
 bool luo_session_quiesce(void);
 void luo_session_resume(void);
diff --git a/kernel/liveupdate/luo_session.c b/kernel/liveupdate/luo_session.c
index ee5ea2a8ed3f..39215e5eda7a 100644
--- a/kernel/liveupdate/luo_session.c
+++ b/kernel/liveupdate/luo_session.c
@@ -649,6 +649,29 @@ int luo_session_serialize(void)
 	return err;
 }
 
+void luo_session_abort_reboot(void)
+{
+	struct luo_session_header *sh = &luo_session_global.outgoing;
+	struct luo_session *session;
+	int i = 0;
+
+	guard(rwsem_write)(&sh->rwsem);
+	if (!READ_ONCE(sh->rebooting))
+		return;
+
+	list_for_each_entry(session, &sh->list, list) {
+		if (i >= sh->header_ser->count)
+			break;
+
+		luo_session_unfreeze_one(session, &sh->ser[i]);
+		memset(&sh->ser[i], 0, sizeof(sh->ser[i]));
+		i++;
+	}
+
+	sh->header_ser->count = 0;
+	luo_session_reboot_done(sh);
+}
+
 /**
  * luo_session_quiesce - Ensure no active sessions exist and lock session lists.
  *
-- 
2.53.0



^ permalink raw reply related	[flat|nested] 8+ messages in thread

* [PATCH 3/5] liveupdate: fail session restore on file deserialization errors
  2026-03-20 16:37 [PATCH 1/5] liveupdate: block outgoing session updates during reboot Oskar Gerlicz Kowalczuk
  2026-03-20 16:37 ` [PATCH 2/5] kexec: abort liveupdate handover on kernel_kexec() unwind Oskar Gerlicz Kowalczuk
@ 2026-03-20 16:37 ` Oskar Gerlicz Kowalczuk
  2026-03-20 16:37 ` [PATCH 4/5] liveupdate: validate handover metadata before using it Oskar Gerlicz Kowalczuk
                   ` (3 subsequent siblings)
  5 siblings, 0 replies; 8+ messages in thread
From: Oskar Gerlicz Kowalczuk @ 2026-03-20 16:37 UTC (permalink / raw)
  To: Pasha Tatashin, Mike Rapoport, Baoquan He
  Cc: Pratyush Yadav, Andrew Morton, linux-kernel, kexec, linux-mm,
	Oskar Gerlicz Kowalczuk

luo_session_deserialize() calls luo_file_deserialize() but ignores its
return value. If file restore fails part-way through, the incoming
session still gets inserted and the caller still sees success.

Leaving a partially restored session on the incoming list is dangerous
because later retrieve or finish operations can walk half-built file
state and operate on uninitialized or stale entries.

Propagate file deserialization failures back to session restore,
remove the partially restored session, and free any struct luo_file
objects that were already allocated before returning the error.

Signed-off-by: Oskar Gerlicz Kowalczuk <oskar@gerlicz.space>
---
 kernel/liveupdate/luo_file.c    | 45 ++++++++++++++++++++-------------
 kernel/liveupdate/luo_session.c | 27 +++++++-------------
 2 files changed, 36 insertions(+), 36 deletions(-)

diff --git a/kernel/liveupdate/luo_file.c b/kernel/liveupdate/luo_file.c
index 5acee4174bf0..cc0fd7e9c332 100644
--- a/kernel/liveupdate/luo_file.c
+++ b/kernel/liveupdate/luo_file.c
@@ -717,6 +717,22 @@ int luo_file_finish(struct luo_file_set *file_set)
 	return 0;
 }
 
+static void luo_file_discard_deserialized(struct luo_file_set *file_set)
+{
+	struct luo_file *luo_file;
+
+	while (!list_empty(&file_set->files_list)) {
+		luo_file = list_last_entry(&file_set->files_list,
+					   struct luo_file, list);
+		list_del(&luo_file->list);
+		mutex_destroy(&luo_file->mutex);
+		kfree(luo_file);
+	}
+
+	file_set->count = 0;
+	file_set->files = NULL;
+}
+
 /**
  * luo_file_deserialize - Reconstructs the list of preserved files in the new kernel.
  * @file_set:     The incoming file_set to fill with deserialized data.
@@ -747,6 +763,7 @@ int luo_file_deserialize(struct luo_file_set *file_set,
 {
 	struct luo_file_ser *file_ser;
 	u64 i;
+	int err;
 
 	if (!file_set_ser->files) {
 		WARN_ON(file_set_ser->count);
@@ -756,21 +773,6 @@ int luo_file_deserialize(struct luo_file_set *file_set,
 	file_set->count = file_set_ser->count;
 	file_set->files = phys_to_virt(file_set_ser->files);
 
-	/*
-	 * Note on error handling:
-	 *
-	 * If deserialization fails (e.g., allocation failure or corrupt data),
-	 * we intentionally skip cleanup of files that were already restored.
-	 *
-	 * A partial failure leaves the preserved state inconsistent.
-	 * Implementing a safe "undo" to unwind complex dependencies (sessions,
-	 * files, hardware state) is error-prone and provides little value, as
-	 * the system is effectively in a broken state.
-	 *
-	 * We treat these resources as leaked. The expected recovery path is for
-	 * userspace to detect the failure and trigger a reboot, which will
-	 * reliably reset devices and reclaim memory.
-	 */
 	file_ser = file_set->files;
 	for (i = 0; i < file_set->count; i++) {
 		struct liveupdate_file_handler *fh;
@@ -787,12 +789,15 @@ int luo_file_deserialize(struct luo_file_set *file_set,
 		if (!handler_found) {
 			pr_warn("No registered handler for compatible '%s'\n",
 				file_ser[i].compatible);
-			return -ENOENT;
+			err = -ENOENT;
+			goto err_discard;
 		}
 
 		luo_file = kzalloc_obj(*luo_file);
-		if (!luo_file)
-			return -ENOMEM;
+		if (!luo_file) {
+			err = -ENOMEM;
+			goto err_discard;
+		}
 
 		luo_file->fh = fh;
 		luo_file->file = NULL;
@@ -803,6 +808,10 @@ int luo_file_deserialize(struct luo_file_set *file_set,
 	}
 
 	return 0;
+
+err_discard:
+	luo_file_discard_deserialized(file_set);
+	return err;
 }
 
 void luo_file_set_init(struct luo_file_set *file_set)
diff --git a/kernel/liveupdate/luo_session.c b/kernel/liveupdate/luo_session.c
index 39215e5eda7a..77afa913d6c7 100644
--- a/kernel/liveupdate/luo_session.c
+++ b/kernel/liveupdate/luo_session.c
@@ -565,21 +565,6 @@ int luo_session_deserialize(void)
 	if (!sh->active)
 		return 0;
 
-	/*
-	 * Note on error handling:
-	 *
-	 * If deserialization fails (e.g., allocation failure or corrupt data),
-	 * we intentionally skip cleanup of sessions that were already restored.
-	 *
-	 * A partial failure leaves the preserved state inconsistent.
-	 * Implementing a safe "undo" to unwind complex dependencies (sessions,
-	 * files, hardware state) is error-prone and provides little value, as
-	 * the system is effectively in a broken state.
-	 *
-	 * We treat these resources as leaked. The expected recovery path is for
-	 * userspace to detect the failure and trigger a reboot, which will
-	 * reliably reset devices and reclaim memory.
-	 */
 	for (int i = 0; i < sh->header_ser->count; i++) {
 		struct luo_session *session;
 
@@ -598,9 +583,15 @@ int luo_session_deserialize(void)
 			return err;
 		}
 
-		scoped_guard(mutex, &session->mutex) {
-			luo_file_deserialize(&session->file_set,
-					     &sh->ser[i].file_set_ser);
+		scoped_guard(mutex, &session->mutex)
+			err = luo_file_deserialize(&session->file_set,
+						   &sh->ser[i].file_set_ser);
+		if (err) {
+			pr_warn("Failed to deserialize session [%s] files %pe\n",
+				session->name, ERR_PTR(err));
+			luo_session_remove(sh, session);
+			luo_session_free(session);
+			return err;
 		}
 	}
 
-- 
2.53.0



^ permalink raw reply related	[flat|nested] 8+ messages in thread

* [PATCH 4/5] liveupdate: validate handover metadata before using it
  2026-03-20 16:37 [PATCH 1/5] liveupdate: block outgoing session updates during reboot Oskar Gerlicz Kowalczuk
  2026-03-20 16:37 ` [PATCH 2/5] kexec: abort liveupdate handover on kernel_kexec() unwind Oskar Gerlicz Kowalczuk
  2026-03-20 16:37 ` [PATCH 3/5] liveupdate: fail session restore on file deserialization errors Oskar Gerlicz Kowalczuk
@ 2026-03-20 16:37 ` Oskar Gerlicz Kowalczuk
  2026-03-20 16:37 ` [PATCH 5/5] liveupdate: guard FLB counters against underflow Oskar Gerlicz Kowalczuk
                   ` (2 subsequent siblings)
  5 siblings, 0 replies; 8+ messages in thread
From: Oskar Gerlicz Kowalczuk @ 2026-03-20 16:37 UTC (permalink / raw)
  To: Pasha Tatashin, Mike Rapoport, Baoquan He
  Cc: Pratyush Yadav, Andrew Morton, linux-kernel, kexec, linux-mm,
	Oskar Gerlicz Kowalczuk

The handover restore path currently trusts counts, strings and
addresses copied from the previous kernel. A truncated or corrupted
handover can feed invalid header addresses, oversized counts,
unterminated names or impossible memfd folio layouts straight into
restore code.

That is dangerous because it can turn a bad handover into
out-of-bounds iteration, bogus phys_to_virt() translations or
inconsistent shmem state in the new kernel.

Add cheap sanity checks before consuming the metadata. Validate
session and file counts, require non-zero serialized pointers when
data is present, reject unterminated names, sanity-check FLB headers,
and make memfd_luo reject impossible folio indices and unsupported
flags.

Signed-off-by: Oskar Gerlicz Kowalczuk <oskar@gerlicz.space>
---
 kernel/liveupdate/luo_file.c    | 19 +++++++++++++++----
 kernel/liveupdate/luo_flb.c     | 14 ++++++++++++++
 kernel/liveupdate/luo_session.c | 16 ++++++++++++++++
 mm/memfd_luo.c                  | 26 ++++++++++++++++++++++++++
 4 files changed, 71 insertions(+), 4 deletions(-)

diff --git a/kernel/liveupdate/luo_file.c b/kernel/liveupdate/luo_file.c
index cc0fd7e9c332..94d49255115d 100644
--- a/kernel/liveupdate/luo_file.c
+++ b/kernel/liveupdate/luo_file.c
@@ -765,10 +765,14 @@ int luo_file_deserialize(struct luo_file_set *file_set,
 	u64 i;
 	int err;
 
-	if (!file_set_ser->files) {
-		WARN_ON(file_set_ser->count);
-		return 0;
-	}
+	if (!file_set_ser->count)
+		return file_set_ser->files ? -EINVAL : 0;
+
+	if (!file_set_ser->files)
+		return -EINVAL;
+
+	if (file_set_ser->count > LUO_FILE_MAX)
+		return -EINVAL;
 
 	file_set->count = file_set_ser->count;
 	file_set->files = phys_to_virt(file_set_ser->files);
@@ -779,6 +783,13 @@ int luo_file_deserialize(struct luo_file_set *file_set,
 		bool handler_found = false;
 		struct luo_file *luo_file;
 
+		if (strnlen(file_ser[i].compatible,
+			    sizeof(file_ser[i].compatible)) ==
+		    sizeof(file_ser[i].compatible)) {
+			err = -EINVAL;
+			goto err_discard;
+		}
+
 		list_private_for_each_entry(fh, &luo_file_handler_list, list) {
 			if (!strcmp(fh->compatible, file_ser[i].compatible)) {
 				handler_found = true;
diff --git a/kernel/liveupdate/luo_flb.c b/kernel/liveupdate/luo_flb.c
index f52e8114837e..3672cbf8e075 100644
--- a/kernel/liveupdate/luo_flb.c
+++ b/kernel/liveupdate/luo_flb.c
@@ -165,7 +165,14 @@ static int luo_flb_retrieve_one(struct liveupdate_flb *flb)
 		return -ENODATA;
 
 	for (int i = 0; i < fh->header_ser->count; i++) {
+		if (strnlen(fh->ser[i].name, sizeof(fh->ser[i].name)) ==
+		    sizeof(fh->ser[i].name))
+			return -EINVAL;
+
 		if (!strcmp(fh->ser[i].name, flb->compatible)) {
+			if (!fh->ser[i].count)
+				return -EINVAL;
+
 			private->incoming.data = fh->ser[i].data;
 			private->incoming.count = fh->ser[i].count;
 			found = true;
@@ -609,7 +616,14 @@ int __init luo_flb_setup_incoming(void *fdt_in)
 	}
 
 	header_ser_pa = get_unaligned((u64 *)ptr);
+	if (!header_ser_pa) {
+		pr_err("FLB header address is missing\n");
+		return -EINVAL;
+	}
+
 	header_ser = phys_to_virt(header_ser_pa);
+	if (header_ser->pgcnt != LUO_FLB_PGCNT || header_ser->count > LUO_FLB_MAX)
+		return -EINVAL;
 
 	luo_flb_global.incoming.header_ser = header_ser;
 	luo_flb_global.incoming.ser = (void *)(header_ser + 1);
diff --git a/kernel/liveupdate/luo_session.c b/kernel/liveupdate/luo_session.c
index 77afa913d6c7..9a08e9794062 100644
--- a/kernel/liveupdate/luo_session.c
+++ b/kernel/liveupdate/luo_session.c
@@ -542,6 +542,11 @@ int __init luo_session_setup_incoming(void *fdt_in)
 	}
 
 	header_ser_pa = get_unaligned((u64 *)ptr);
+	if (!header_ser_pa) {
+		pr_err("Session header address is missing\n");
+		return -EINVAL;
+	}
+
 	header_ser = phys_to_virt(header_ser_pa);
 
 	luo_session_global.incoming.header_ser = header_ser;
@@ -565,9 +570,20 @@ int luo_session_deserialize(void)
 	if (!sh->active)
 		return 0;
 
+	if (sh->header_ser->count > LUO_SESSION_MAX) {
+		pr_warn("Invalid session count %llu\n", sh->header_ser->count);
+		return -EINVAL;
+	}
+
 	for (int i = 0; i < sh->header_ser->count; i++) {
 		struct luo_session *session;
 
+		if (strnlen(sh->ser[i].name, sizeof(sh->ser[i].name)) ==
+		    sizeof(sh->ser[i].name)) {
+			pr_warn("Session name is not NUL-terminated\n");
+			return -EINVAL;
+		}
+
 		session = luo_session_alloc(sh->ser[i].name);
 		if (IS_ERR(session)) {
 			pr_warn("Failed to allocate session [%s] during deserialization %pe\n",
diff --git a/mm/memfd_luo.c b/mm/memfd_luo.c
index b8edb9f981d7..75f3f7226e6e 100644
--- a/mm/memfd_luo.c
+++ b/mm/memfd_luo.c
@@ -394,10 +394,15 @@ static int memfd_luo_retrieve_folios(struct file *file,
 {
 	struct inode *inode = file_inode(file);
 	struct address_space *mapping = inode->i_mapping;
+	u64 max_index = i_size_read(inode);
+	u64 prev_index = 0;
 	struct folio *folio;
 	int err = -EIO;
 	long i;
 
+	if (max_index)
+		max_index = (max_index - 1) >> PAGE_SHIFT;
+
 	for (i = 0; i < nr_folios; i++) {
 		const struct memfd_luo_folio_ser *pfolio = &folios_ser[i];
 		phys_addr_t phys;
@@ -407,6 +412,18 @@ static int memfd_luo_retrieve_folios(struct file *file,
 		if (!pfolio->pfn)
 			continue;
 
+		if (pfolio->flags &
+		    ~(MEMFD_LUO_FOLIO_DIRTY | MEMFD_LUO_FOLIO_UPTODATE)) {
+			err = -EINVAL;
+			goto put_folios;
+		}
+
+		if (pfolio->index > max_index ||
+		    (i && pfolio->index <= prev_index)) {
+			err = -EINVAL;
+			goto put_folios;
+		}
+
 		phys = PFN_PHYS(pfolio->pfn);
 		folio = kho_restore_folio(phys);
 		if (!folio) {
@@ -415,6 +432,7 @@ static int memfd_luo_retrieve_folios(struct file *file,
 			goto put_folios;
 		}
 		index = pfolio->index;
+		prev_index = index;
 		flags = pfolio->flags;
 
 		/* Set up the folio for insertion. */
@@ -486,6 +504,14 @@ static int memfd_luo_retrieve(struct liveupdate_file_op_args *args)
 	if (!ser)
 		return -EINVAL;
 
+	if (!!ser->nr_folios != !!ser->folios.first.phys)
+		return -EINVAL;
+
+	if (ser->nr_folios &&
+	    (!ser->size ||
+	     ser->nr_folios > ((ser->size - 1) >> PAGE_SHIFT) + 1))
+		return -EINVAL;
+
 	file = memfd_alloc_file("", 0);
 	if (IS_ERR(file)) {
 		pr_err("failed to setup file: %pe\n", file);
-- 
2.53.0



^ permalink raw reply related	[flat|nested] 8+ messages in thread

* [PATCH 5/5] liveupdate: guard FLB counters against underflow
  2026-03-20 16:37 [PATCH 1/5] liveupdate: block outgoing session updates during reboot Oskar Gerlicz Kowalczuk
                   ` (2 preceding siblings ...)
  2026-03-20 16:37 ` [PATCH 4/5] liveupdate: validate handover metadata before using it Oskar Gerlicz Kowalczuk
@ 2026-03-20 16:37 ` Oskar Gerlicz Kowalczuk
  2026-03-21  1:23 ` [PATCH 1/5] liveupdate: block outgoing session updates during reboot Andrew Morton
  2026-03-22  7:40 ` kernel test robot
  5 siblings, 0 replies; 8+ messages in thread
From: Oskar Gerlicz Kowalczuk @ 2026-03-20 16:37 UTC (permalink / raw)
  To: Pasha Tatashin, Mike Rapoport, Baoquan He
  Cc: Pratyush Yadav, Andrew Morton, linux-kernel, kexec, linux-mm,
	Oskar Gerlicz Kowalczuk

The FLB lifetime counters are decremented unconditionally on
unpreserve and finish. If either path runs with a zero counter, the
value wraps and the FLB state appears to stay live forever.

Once the counter underflows, later handover operations can act on
stale FLB state or skip the real final teardown, which makes restore
and finish lifetime tracking unreliable.

Guard both counters against zero and warn once instead of
decrementing. This keeps the existing lifetime rules intact while
preventing wraparound.

Signed-off-by: Oskar Gerlicz Kowalczuk <oskar@gerlicz.space>
---
 kernel/liveupdate/luo_flb.c | 9 ++++++++-
 1 file changed, 8 insertions(+), 1 deletion(-)

diff --git a/kernel/liveupdate/luo_flb.c b/kernel/liveupdate/luo_flb.c
index 3672cbf8e075..06501360fa3f 100644
--- a/kernel/liveupdate/luo_flb.c
+++ b/kernel/liveupdate/luo_flb.c
@@ -128,6 +128,9 @@ static void luo_flb_file_unpreserve_one(struct liveupdate_flb *flb)
 	struct luo_flb_private *private = luo_flb_get_private(flb);
 
 	scoped_guard(mutex, &private->outgoing.lock) {
+		if (WARN_ON_ONCE(!private->outgoing.count))
+			return;
+
 		private->outgoing.count--;
 		if (!private->outgoing.count) {
 			struct liveupdate_flb_op_args args = {0};
@@ -201,8 +204,12 @@ static void luo_flb_file_finish_one(struct liveupdate_flb *flb)
 	struct luo_flb_private *private = luo_flb_get_private(flb);
 	u64 count;
 
-	scoped_guard(mutex, &private->incoming.lock)
+	scoped_guard(mutex, &private->incoming.lock) {
+		if (WARN_ON_ONCE(!private->incoming.count))
+			return;
+
 		count = --private->incoming.count;
+	}
 
 	if (!count) {
 		struct liveupdate_flb_op_args args = {0};
-- 
2.53.0



^ permalink raw reply related	[flat|nested] 8+ messages in thread

* Re: [PATCH 1/5] liveupdate: block outgoing session updates during reboot
  2026-03-20 16:37 [PATCH 1/5] liveupdate: block outgoing session updates during reboot Oskar Gerlicz Kowalczuk
                   ` (3 preceding siblings ...)
  2026-03-20 16:37 ` [PATCH 5/5] liveupdate: guard FLB counters against underflow Oskar Gerlicz Kowalczuk
@ 2026-03-21  1:23 ` Andrew Morton
  2026-03-21 10:25   ` oskar
  2026-03-22  7:40 ` kernel test robot
  5 siblings, 1 reply; 8+ messages in thread
From: Andrew Morton @ 2026-03-21  1:23 UTC (permalink / raw)
  To: Oskar Gerlicz Kowalczuk
  Cc: Pasha Tatashin, Mike Rapoport, Baoquan He, Pratyush Yadav,
	linux-kernel, kexec, linux-mm

On Fri, 20 Mar 2026 17:37:16 +0100 Oskar Gerlicz Kowalczuk <oskar@gerlicz.space> wrote:

> 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.

Sashiko AI review has quite a few questions:
	https://sashiko.dev/#/patchset/20260320163720.100456-1-oskar@gerlicz.space


^ permalink raw reply	[flat|nested] 8+ messages in thread

* Re: [PATCH 1/5] liveupdate: block outgoing session updates during reboot
  2026-03-21  1:23 ` [PATCH 1/5] liveupdate: block outgoing session updates during reboot Andrew Morton
@ 2026-03-21 10:25   ` oskar
  0 siblings, 0 replies; 8+ messages in thread
From: oskar @ 2026-03-21 10:25 UTC (permalink / raw)
  To: Andrew Morton
  Cc: Pasha Tatashin, Mike Rapoport, Baoquan He, Pratyush Yadav,
	linux-kernel, kexec, linux-mm

On 2026-03-21 02:23, Andrew Morton wrote:
> On Fri, 20 Mar 2026 17:37:16 +0100 Oskar Gerlicz Kowalczuk <oskar@gerlicz.space> wrote:
> 
>> 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.
> 
> Sashiko AI review has quite a few questions:
> 	https://sashiko.dev/#/patchset/20260320163720.100456-1-oskar@gerlicz.space


Thanks for the careful review.

Patch 1

You are right. The incoming .release path can return before
luo_session_remove() / luo_session_free() and leave the session
stranded. This is pre-existing rather than introduced by this patch,
but it is a real bug. I will fix it in v2 by ensuring the release path
always removes and frees the session, while keeping the warning if
finish fails.

You are right. This wait can be hit from userspace while the
preserve_context path is entering the freezer. I will switch this to
wait_event_freezable(), as this path can be reached from userspace
during the freeze phase.

You are right. Dropping the rwsem before luo_session_remove() leaves
a serialize-vs-close window, so v1 still allows an outgoing session to
be serialized after close has started. In v2 I will keep the outgoing
session exclusion across the entire unpreserve/remove path (i.e. prevent
serialization until the session is fully removed), and I will gate both
PRESERVE_FD and FINISH under the same reboot check.

These issues indicate the outgoing session lifecycle still has race
windows, so I will rework this part more substantially in v2.

Patch 2

You are right. On the preserve_context flow, machine_kexec() can
return to the original kernel with error == 0, so the current
abort-on-error logic is insufficient. I will fix this by explicitly
aborting liveupdate state on the preserve_context return path in
kernel_kexec() after machine_kexec() returns.

Agreed. The abort loop only stays synchronized as long as the outgoing
list cannot change after serialization. The remaining release/remove
race in patch 1 breaks that assumption. I will fix that in v2 by
ensuring outgoing removal is excluded once serialization starts, so
abort can unfreeze the same session set that was serialized.

Patch 3

You are right. luo_file_discard_deserialized() currently drops
file_set->files without freeing the preserved KHO block. I will fix
this by routing the discard path through a common cleanup path which
calls kho_restore_free(file_set->files) before clearing the pointer.

You are right. The direct returns in luo_session_deserialize()
bypass cleanup of sh->header_ser, can leave previously restored
sessions behind, and do not cache the final error value. I will fix
this by routing all deserialize failures through a common cleanup path
which removes any sessions restored so far, frees sh->header_ser,
clears sh->ser, and stores the cached error before returning.

I do not think this specific UAF is reachable, because deserialization
happens before the device becomes visible to userspace and the device
enforces single-open semantics. The cleanup and leak issues are real,
though, and the common unwind path above will address them.

Patch 4

Agreed. The file-set validation failures currently share the same
cleanup hole as patch 3. The common deserialize cleanup in v2 will
free the preserved file_set block via kho_restore_free() before
dropping the pointer.

You are right about the direct returns in luo_session_deserialize().
I will convert those to the same common error path so sh->header_ser
is freed and the cached error is set consistently.

You are right about memfd cleanup. I will route early validation
failures through free_ser:, and I will fix the folio unwind so it also
releases the current folio entry rather than only the tail entries
after it.

Agreed. I will also validate nr_folios against the vmalloc backing
metadata (ser->folios.total_pages), not only against ser->size,
before iterating.

Patch 5

I agree this is a bug. It is pre-existing and not introduced by this
patch, so I will fix it in a follow-up by rechecking the incoming state
under the mutex before returning success from
liveupdate_flb_get_incoming().

Overall, I will rework the outgoing session lifecycle, unify the
serialization exclusion rules, and fix the cleanup paths in v2.

Oskar Gerlicz Kowalczuk


^ permalink raw reply	[flat|nested] 8+ messages in thread

* Re: [PATCH 1/5] liveupdate: block outgoing session updates during reboot
  2026-03-20 16:37 [PATCH 1/5] liveupdate: block outgoing session updates during reboot Oskar Gerlicz Kowalczuk
                   ` (4 preceding siblings ...)
  2026-03-21  1:23 ` [PATCH 1/5] liveupdate: block outgoing session updates during reboot Andrew Morton
@ 2026-03-22  7:40 ` kernel test robot
  5 siblings, 0 replies; 8+ messages in thread
From: kernel test robot @ 2026-03-22  7:40 UTC (permalink / raw)
  To: Oskar Gerlicz Kowalczuk, Pasha Tatashin, Mike Rapoport,
	Baoquan He
  Cc: oe-kbuild-all, Pratyush Yadav, Andrew Morton,
	Linux Memory Management List, linux-kernel, kexec,
	Oskar Gerlicz Kowalczuk

Hi Oskar,

kernel test robot noticed the following build warnings:

[auto build test WARNING on linus/master]
[also build test WARNING on v7.0-rc4 next-20260320]
[cannot apply to akpm-mm/mm-everything]
[If your patch is applied to the wrong git tree, kindly drop us a note.
And when submitting patch, we suggest to use '--base' as documented in
https://git-scm.com/docs/git-format-patch#_base_tree_information]

url:    https://github.com/intel-lab-lkp/linux/commits/Oskar-Gerlicz-Kowalczuk/kexec-abort-liveupdate-handover-on-kernel_kexec-unwind/20260322-071016
base:   linus/master
patch link:    https://lore.kernel.org/r/20260320163720.100456-1-oskar%40gerlicz.space
patch subject: [PATCH 1/5] liveupdate: block outgoing session updates during reboot
config: x86_64-randconfig-161-20260322 (https://download.01.org/0day-ci/archive/20260322/202603221537.TfoAS2LD-lkp@intel.com/config)
compiler: gcc-12 (Debian 12.4.0-5) 12.4.0
smatch: v0.5.0-9004-gb810ac53
reproduce (this is a W=1 build): (https://download.01.org/0day-ci/archive/20260322/202603221537.TfoAS2LD-lkp@intel.com/reproduce)

If you fix the issue in a separate patch/commit (i.e. not just a new version of
the same patch/commit), kindly add following tags
| Reported-by: kernel test robot <lkp@intel.com>
| Closes: https://lore.kernel.org/oe-kbuild-all/202603221537.TfoAS2LD-lkp@intel.com/

All warnings (new ones prefixed by >>):

>> Warning: kernel/liveupdate/luo_session.c:99 struct member 'reboot_waitq' not described in 'luo_session_header'
>> Warning: kernel/liveupdate/luo_session.c:99 struct member 'rebooting' not described in 'luo_session_header'
>> Warning: kernel/liveupdate/luo_session.c:99 struct member 'reboot_waitq' not described in 'luo_session_header'
>> Warning: kernel/liveupdate/luo_session.c:99 struct member 'rebooting' not described in 'luo_session_header'

-- 
0-DAY CI Kernel Test Service
https://github.com/intel/lkp-tests/wiki


^ permalink raw reply	[flat|nested] 8+ messages in thread

end of thread, other threads:[~2026-03-22  7:41 UTC | newest]

Thread overview: 8+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-03-20 16:37 [PATCH 1/5] liveupdate: block outgoing session updates during reboot Oskar Gerlicz Kowalczuk
2026-03-20 16:37 ` [PATCH 2/5] kexec: abort liveupdate handover on kernel_kexec() unwind Oskar Gerlicz Kowalczuk
2026-03-20 16:37 ` [PATCH 3/5] liveupdate: fail session restore on file deserialization errors Oskar Gerlicz Kowalczuk
2026-03-20 16:37 ` [PATCH 4/5] liveupdate: validate handover metadata before using it Oskar Gerlicz Kowalczuk
2026-03-20 16:37 ` [PATCH 5/5] liveupdate: guard FLB counters against underflow Oskar Gerlicz Kowalczuk
2026-03-21  1:23 ` [PATCH 1/5] liveupdate: block outgoing session updates during reboot Andrew Morton
2026-03-21 10:25   ` oskar
2026-03-22  7:40 ` kernel test robot

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox