* [PATCH v4 00/11] liveupdate: Fix module unloading and unregister API
@ 2026-04-13 18:51 Pasha Tatashin
2026-04-13 18:51 ` [PATCH v4 01/11] liveupdate: Safely print untrusted strings Pasha Tatashin
` (10 more replies)
0 siblings, 11 replies; 13+ messages in thread
From: Pasha Tatashin @ 2026-04-13 18:51 UTC (permalink / raw)
To: rppt, akpm, linux-mm, linux-kernel, pasha.tatashin, dmatlack,
pratyush, skhawaja
This patch series addresses an issue with how LUO handles module
reference counting and unregistration during a module unload (e.g.,
via rmmod).
Currently, modules that register live update file handlers are pinned
for the entire duration they are registered. This prevents the modules
from being unloaded gracefully, even when no live update session is in
progress.
Furthermore, if a module is forcefully unloaded, the unregistration
functions return an error (e.g. -EBUSY) if a session is active, which
is ignored by the kernel's module unload path, leaving dangling
pointers in the LUO global lists.
To resolve these issues, this series introduces the following changes:
1. Adds a global read-write semaphore (luo_register_rwlock) to protect
the registration lists for both file handlers and FLBs.
2. Reduces the scope of module reference counting for file handlers and
FLBs. Instead of pinning modules indefinitely upon registration,
references are now taken only when they are actively used in a live
update session (e.g., during preservation, retrieval, or
deserialization).
3. Removes the global luo_session_quiesce() mechanism since module
unload behavior now handles active sessions implicitly.
4. Introduces auto-unregistration of FLBs during file handler
unregistration to prevent leaving dangling resources.
5. Changes the unregistration functions to return void instead of
an error code.
6. Fixes a data race in luo_flb_get_private() by introducing a spinlock
for thread-safe lazy initialization.
7. Strengthens security by using %.*s when printing untrusted deserialized
compatible strings and session names to prevent out-of-bounds reads.
8. Fixes a return value issue in session deserialization.
Changelog since v3:
- Collected Reviewed-by tags from Pratyush Yadav and Samiullah Khawaja.
- Documented the assumption that a handler's lifecycle is bound to its
implementing module's lifecycle in the LUO File Descriptors DOC comment,
as requested by Sami.
- Added a lockdep_assert_held_write() in luo_flb_unregister_all() as
reqeuested by Pratyush.
- Added "fix return value on session allocation failure" patch per
discussion in v3.
Pasha Tatashin (11):
liveupdate: Safely print untrusted strings
liveupdate: Synchronize lazy initialization of FLB private state
liveupdate: Protect file handler list with rwsem
liveupdate: Protect FLB lists with luo_register_rwlock
liveupdate: Defer FLB module refcounting to active sessions
liveupdate: Remove luo_session_quiesce()
liveupdate: Auto unregister FLBs on file handler unregistration
liveupdate: Remove liveupdate_test_unregister()
liveupdate: Make unregister functions return void
liveupdate: Defer file handler module refcounting to active sessions
liveupdate: fix return value on session allocation failure
include/linux/liveupdate.h | 15 ++-
kernel/liveupdate/luo_core.c | 6 +
kernel/liveupdate/luo_file.c | 91 ++++++---------
kernel/liveupdate/luo_flb.c | 183 +++++++++++++++++--------------
kernel/liveupdate/luo_internal.h | 7 +-
kernel/liveupdate/luo_session.c | 49 +--------
lib/tests/liveupdate.c | 18 ---
7 files changed, 151 insertions(+), 218 deletions(-)
--
2.43.0
^ permalink raw reply [flat|nested] 13+ messages in thread
* [PATCH v4 01/11] liveupdate: Safely print untrusted strings
2026-04-13 18:51 [PATCH v4 00/11] liveupdate: Fix module unloading and unregister API Pasha Tatashin
@ 2026-04-13 18:51 ` Pasha Tatashin
2026-04-13 18:51 ` [PATCH v4 02/11] liveupdate: Synchronize lazy initialization of FLB private state Pasha Tatashin
` (9 subsequent siblings)
10 siblings, 0 replies; 13+ messages in thread
From: Pasha Tatashin @ 2026-04-13 18:51 UTC (permalink / raw)
To: rppt, akpm, linux-mm, linux-kernel, pasha.tatashin, dmatlack,
pratyush, skhawaja
Deserialized strings from KHO data (such as file handler compatible
strings and session names) are provided by the previous kernel and
might not be null-terminated if the data is corrupted.
When printing these strings in error messages, use the %.*s format
specifier with the maximum buffer size to prevent out-of-bounds reads
into adjacent kernel memory.
Signed-off-by: Pasha Tatashin <pasha.tatashin@soleen.com>
Reviewed-by: Pratyush Yadav (Google) <pratyush@kernel.org>
---
kernel/liveupdate/luo_file.c | 3 ++-
kernel/liveupdate/luo_session.c | 3 ++-
2 files changed, 4 insertions(+), 2 deletions(-)
diff --git a/kernel/liveupdate/luo_file.c b/kernel/liveupdate/luo_file.c
index 09103cf81107..8fcf302c73b6 100644
--- a/kernel/liveupdate/luo_file.c
+++ b/kernel/liveupdate/luo_file.c
@@ -813,7 +813,8 @@ int luo_file_deserialize(struct luo_file_set *file_set,
}
if (!handler_found) {
- pr_warn("No registered handler for compatible '%s'\n",
+ pr_warn("No registered handler for compatible '%.*s'\n",
+ (int)sizeof(file_ser[i].compatible),
file_ser[i].compatible);
return -ENOENT;
}
diff --git a/kernel/liveupdate/luo_session.c b/kernel/liveupdate/luo_session.c
index 25ae704d7787..8c76dece679b 100644
--- a/kernel/liveupdate/luo_session.c
+++ b/kernel/liveupdate/luo_session.c
@@ -544,7 +544,8 @@ int luo_session_deserialize(void)
session = luo_session_alloc(sh->ser[i].name);
if (IS_ERR(session)) {
- pr_warn("Failed to allocate session [%s] during deserialization %pe\n",
+ pr_warn("Failed to allocate session [%.*s] during deserialization %pe\n",
+ (int)sizeof(sh->ser[i].name),
sh->ser[i].name, session);
return PTR_ERR(session);
}
--
2.43.0
^ permalink raw reply [flat|nested] 13+ messages in thread
* [PATCH v4 02/11] liveupdate: Synchronize lazy initialization of FLB private state
2026-04-13 18:51 [PATCH v4 00/11] liveupdate: Fix module unloading and unregister API Pasha Tatashin
2026-04-13 18:51 ` [PATCH v4 01/11] liveupdate: Safely print untrusted strings Pasha Tatashin
@ 2026-04-13 18:51 ` Pasha Tatashin
2026-04-13 18:51 ` [PATCH v4 03/11] liveupdate: Protect file handler list with rwsem Pasha Tatashin
` (8 subsequent siblings)
10 siblings, 0 replies; 13+ messages in thread
From: Pasha Tatashin @ 2026-04-13 18:51 UTC (permalink / raw)
To: rppt, akpm, linux-mm, linux-kernel, pasha.tatashin, dmatlack,
pratyush, skhawaja
The luo_flb_get_private() function, which is responsible for lazily
initializing the private state of FLB objects, can be called
concurrently from multiple threads. This creates a data
race on the 'initialized' flag and can lead to multiple executions of
mutex_init() and INIT_LIST_HEAD() on the same memory.
Introduce a static spinlock (luo_flb_init_lock) local to the function
to synchronize the initialization path. Use smp_load_acquire() and
smp_store_release() for memory ordering between the fast path and the
slow path.
Signed-off-by: Pasha Tatashin <pasha.tatashin@soleen.com>
Reviewed-by: Pratyush Yadav (Google) <pratyush@kernel.org>
---
kernel/liveupdate/luo_flb.c | 7 ++++++-
1 file changed, 6 insertions(+), 1 deletion(-)
diff --git a/kernel/liveupdate/luo_flb.c b/kernel/liveupdate/luo_flb.c
index f52e8114837e..cf4a8f854c83 100644
--- a/kernel/liveupdate/luo_flb.c
+++ b/kernel/liveupdate/luo_flb.c
@@ -89,13 +89,18 @@ struct luo_flb_link {
static struct luo_flb_private *luo_flb_get_private(struct liveupdate_flb *flb)
{
struct luo_flb_private *private = &ACCESS_PRIVATE(flb, private);
+ static DEFINE_SPINLOCK(luo_flb_init_lock);
+ if (smp_load_acquire(&private->initialized))
+ return private;
+
+ guard(spinlock)(&luo_flb_init_lock);
if (!private->initialized) {
mutex_init(&private->incoming.lock);
mutex_init(&private->outgoing.lock);
INIT_LIST_HEAD(&private->list);
private->users = 0;
- private->initialized = true;
+ smp_store_release(&private->initialized, true);
}
return private;
--
2.43.0
^ permalink raw reply [flat|nested] 13+ messages in thread
* [PATCH v4 03/11] liveupdate: Protect file handler list with rwsem
2026-04-13 18:51 [PATCH v4 00/11] liveupdate: Fix module unloading and unregister API Pasha Tatashin
2026-04-13 18:51 ` [PATCH v4 01/11] liveupdate: Safely print untrusted strings Pasha Tatashin
2026-04-13 18:51 ` [PATCH v4 02/11] liveupdate: Synchronize lazy initialization of FLB private state Pasha Tatashin
@ 2026-04-13 18:51 ` Pasha Tatashin
2026-04-13 18:51 ` [PATCH v4 04/11] liveupdate: Protect FLB lists with luo_register_rwlock Pasha Tatashin
` (7 subsequent siblings)
10 siblings, 0 replies; 13+ messages in thread
From: Pasha Tatashin @ 2026-04-13 18:51 UTC (permalink / raw)
To: rppt, akpm, linux-mm, linux-kernel, pasha.tatashin, dmatlack,
pratyush, skhawaja
Because liveupdate file handlers will no longer hold a module reference
when registered, we must ensure that the access to the handler list is
protected against concurrent module unloading.
Utilize the global luo_register_rwlock to protect the global registry of
file handlers. Read locks are taken during list traversals in
luo_preserve_file() and luo_file_deserialize(). Write locks are taken
during registration and unregistration.
Signed-off-by: Pasha Tatashin <pasha.tatashin@soleen.com>
Reviewed-by: Pratyush Yadav (Google) <pratyush@kernel.org>
---
kernel/liveupdate/luo_core.c | 6 ++++++
kernel/liveupdate/luo_file.c | 31 +++++++++++++++++++++++--------
kernel/liveupdate/luo_internal.h | 2 ++
3 files changed, 31 insertions(+), 8 deletions(-)
diff --git a/kernel/liveupdate/luo_core.c b/kernel/liveupdate/luo_core.c
index 48b25c9abeda..803f51c84275 100644
--- a/kernel/liveupdate/luo_core.c
+++ b/kernel/liveupdate/luo_core.c
@@ -54,6 +54,7 @@
#include <linux/liveupdate.h>
#include <linux/miscdevice.h>
#include <linux/mm.h>
+#include <linux/rwsem.h>
#include <linux/sizes.h>
#include <linux/string.h>
#include <linux/unaligned.h>
@@ -68,6 +69,11 @@ static struct {
u64 liveupdate_num;
} luo_global;
+/*
+ * luo_register_rwlock - Protects registration of file handlers and FLBs.
+ */
+DECLARE_RWSEM(luo_register_rwlock);
+
static int __init early_liveupdate_param(char *buf)
{
return kstrtobool(buf, &luo_global.enabled);
diff --git a/kernel/liveupdate/luo_file.c b/kernel/liveupdate/luo_file.c
index 8fcf302c73b6..ecf780d74d80 100644
--- a/kernel/liveupdate/luo_file.c
+++ b/kernel/liveupdate/luo_file.c
@@ -18,9 +18,10 @@
*
* Handler Registration:
* Kernel modules responsible for a specific file type (e.g., memfd, vfio)
- * register a &struct liveupdate_file_handler. This handler provides a set of
- * callbacks that LUO invokes at different stages of the update process, most
- * notably:
+ * register a &struct liveupdate_file_handler. The lifecycle of the registered
+ * handler is expected to be bound to the lifecycle of the kernel module that
+ * implements it. This handler provides a set of callbacks that LUO invokes at
+ * different stages of the update process, most notably:
*
* - can_preserve(): A lightweight check to determine if the handler is
* compatible with a given 'struct file'.
@@ -288,17 +289,20 @@ int luo_preserve_file(struct luo_file_set *file_set, u64 token, int fd)
goto err_fput;
err = -ENOENT;
+ down_read(&luo_register_rwlock);
list_private_for_each_entry(fh, &luo_file_handler_list, list) {
if (fh->ops->can_preserve(fh, file)) {
err = 0;
break;
}
}
+ up_read(&luo_register_rwlock);
/* err is still -ENOENT if no handler was found */
if (err)
goto err_free_files_mem;
+ /* safe to use fh because its module is pinned */
err = xa_insert(&luo_preserved_files, luo_get_id(fh, file),
file, GFP_KERNEL);
if (err)
@@ -805,12 +809,14 @@ int luo_file_deserialize(struct luo_file_set *file_set,
bool handler_found = false;
struct luo_file *luo_file;
+ down_read(&luo_register_rwlock);
list_private_for_each_entry(fh, &luo_file_handler_list, list) {
if (!strcmp(fh->compatible, file_ser[i].compatible)) {
handler_found = true;
break;
}
}
+ up_read(&luo_register_rwlock);
if (!handler_found) {
pr_warn("No registered handler for compatible '%.*s'\n",
@@ -823,6 +829,7 @@ int luo_file_deserialize(struct luo_file_set *file_set,
if (!luo_file)
return -ENOMEM;
+ /* safe to use fh because its module is pinned */
luo_file->fh = fh;
luo_file->file = NULL;
luo_file->serialized_data = file_ser[i].data;
@@ -879,32 +886,36 @@ int liveupdate_register_file_handler(struct liveupdate_file_handler *fh)
if (!luo_session_quiesce())
return -EBUSY;
+ down_write(&luo_register_rwlock);
/* Check for duplicate compatible strings */
list_private_for_each_entry(fh_iter, &luo_file_handler_list, list) {
if (!strcmp(fh_iter->compatible, fh->compatible)) {
pr_err("File handler registration failed: Compatible string '%s' already registered.\n",
fh->compatible);
err = -EEXIST;
- goto err_resume;
+ goto err_unlock;
}
}
/* Pin the module implementing the handler */
if (!try_module_get(fh->ops->owner)) {
err = -EAGAIN;
- goto err_resume;
+ goto err_unlock;
}
INIT_LIST_HEAD(&ACCESS_PRIVATE(fh, flb_list));
INIT_LIST_HEAD(&ACCESS_PRIVATE(fh, list));
list_add_tail(&ACCESS_PRIVATE(fh, list), &luo_file_handler_list);
+ up_write(&luo_register_rwlock);
+
luo_session_resume();
liveupdate_test_register(fh);
return 0;
-err_resume:
+err_unlock:
+ up_write(&luo_register_rwlock);
luo_session_resume();
return err;
}
@@ -938,16 +949,20 @@ int liveupdate_unregister_file_handler(struct liveupdate_file_handler *fh)
if (!luo_session_quiesce())
goto err_register;
+ down_write(&luo_register_rwlock);
if (!list_empty(&ACCESS_PRIVATE(fh, flb_list)))
- goto err_resume;
+ goto err_unlock;
list_del(&ACCESS_PRIVATE(fh, list));
+ up_write(&luo_register_rwlock);
+
module_put(fh->ops->owner);
luo_session_resume();
return 0;
-err_resume:
+err_unlock:
+ up_write(&luo_register_rwlock);
luo_session_resume();
err_register:
liveupdate_test_register(fh);
diff --git a/kernel/liveupdate/luo_internal.h b/kernel/liveupdate/luo_internal.h
index 8083d8739b09..4bfe00ac8866 100644
--- a/kernel/liveupdate/luo_internal.h
+++ b/kernel/liveupdate/luo_internal.h
@@ -77,6 +77,8 @@ struct luo_session {
struct mutex mutex;
};
+extern struct rw_semaphore luo_register_rwlock;
+
int luo_session_create(const char *name, struct file **filep);
int luo_session_retrieve(const char *name, struct file **filep);
int __init luo_session_setup_outgoing(void *fdt);
--
2.43.0
^ permalink raw reply [flat|nested] 13+ messages in thread
* [PATCH v4 04/11] liveupdate: Protect FLB lists with luo_register_rwlock
2026-04-13 18:51 [PATCH v4 00/11] liveupdate: Fix module unloading and unregister API Pasha Tatashin
` (2 preceding siblings ...)
2026-04-13 18:51 ` [PATCH v4 03/11] liveupdate: Protect file handler list with rwsem Pasha Tatashin
@ 2026-04-13 18:51 ` Pasha Tatashin
2026-04-13 18:51 ` [PATCH v4 05/11] liveupdate: Defer FLB module refcounting to active sessions Pasha Tatashin
` (6 subsequent siblings)
10 siblings, 0 replies; 13+ messages in thread
From: Pasha Tatashin @ 2026-04-13 18:51 UTC (permalink / raw)
To: rppt, akpm, linux-mm, linux-kernel, pasha.tatashin, dmatlack,
pratyush, skhawaja
Because liveupdate FLB objects will soon drop their persistent module
references when registered, list traversals must be protected against
concurrent module unloading.
To provide this protection, utilize the global luo_register_rwlock.
It protects the global registry of FLBs and the handler's specific
list of FLB dependencies.
Read locks are used during concurrent list traversals (e.g., during
preservation and serialization). Write locks are taken during registration
and unregistration.
Signed-off-by: Pasha Tatashin <pasha.tatashin@soleen.com>
Reviewed-by: Pratyush Yadav (Google) <pratyush@kernel.org>
---
include/linux/liveupdate.h | 1 +
kernel/liveupdate/luo_flb.c | 14 ++++++++++++++
2 files changed, 15 insertions(+)
diff --git a/include/linux/liveupdate.h b/include/linux/liveupdate.h
index 61325ad26526..9c761d9bacf8 100644
--- a/include/linux/liveupdate.h
+++ b/include/linux/liveupdate.h
@@ -12,6 +12,7 @@
#include <linux/kho/abi/luo.h>
#include <linux/list.h>
#include <linux/mutex.h>
+#include <linux/rwsem.h>
#include <linux/types.h>
#include <uapi/linux/liveupdate.h>
diff --git a/kernel/liveupdate/luo_flb.c b/kernel/liveupdate/luo_flb.c
index cf4a8f854c83..fdb274410e8f 100644
--- a/kernel/liveupdate/luo_flb.c
+++ b/kernel/liveupdate/luo_flb.c
@@ -245,17 +245,20 @@ int luo_flb_file_preserve(struct liveupdate_file_handler *fh)
struct luo_flb_link *iter;
int err = 0;
+ down_read(&luo_register_rwlock);
list_for_each_entry(iter, flb_list, list) {
err = luo_flb_file_preserve_one(iter->flb);
if (err)
goto exit_err;
}
+ up_read(&luo_register_rwlock);
return 0;
exit_err:
list_for_each_entry_continue_reverse(iter, flb_list, list)
luo_flb_file_unpreserve_one(iter->flb);
+ up_read(&luo_register_rwlock);
return err;
}
@@ -277,6 +280,7 @@ void luo_flb_file_unpreserve(struct liveupdate_file_handler *fh)
struct list_head *flb_list = &ACCESS_PRIVATE(fh, flb_list);
struct luo_flb_link *iter;
+ guard(rwsem_read)(&luo_register_rwlock);
list_for_each_entry_reverse(iter, flb_list, list)
luo_flb_file_unpreserve_one(iter->flb);
}
@@ -297,6 +301,7 @@ void luo_flb_file_finish(struct liveupdate_file_handler *fh)
struct list_head *flb_list = &ACCESS_PRIVATE(fh, flb_list);
struct luo_flb_link *iter;
+ guard(rwsem_read)(&luo_register_rwlock);
list_for_each_entry_reverse(iter, flb_list, list)
luo_flb_file_finish_one(iter->flb);
}
@@ -360,6 +365,8 @@ int liveupdate_register_flb(struct liveupdate_file_handler *fh,
if (!luo_session_quiesce())
return -EBUSY;
+ down_write(&luo_register_rwlock);
+
/* Check that this FLB is not already linked to this file handler */
err = -EEXIST;
list_for_each_entry(iter, flb_list, list) {
@@ -401,11 +408,13 @@ int liveupdate_register_flb(struct liveupdate_file_handler *fh,
private->users++;
link->flb = flb;
list_add_tail(&no_free_ptr(link)->list, flb_list);
+ up_write(&luo_register_rwlock);
luo_session_resume();
return 0;
err_resume:
+ up_write(&luo_register_rwlock);
luo_session_resume();
return err;
}
@@ -449,6 +458,8 @@ int liveupdate_unregister_flb(struct liveupdate_file_handler *fh,
if (!luo_session_quiesce())
return -EBUSY;
+ down_write(&luo_register_rwlock);
+
/* Find and remove the link from the file handler's list */
list_for_each_entry(iter, flb_list, list) {
if (iter->flb == flb) {
@@ -473,11 +484,13 @@ int liveupdate_unregister_flb(struct liveupdate_file_handler *fh,
module_put(flb->ops->owner);
}
+ up_write(&luo_register_rwlock);
luo_session_resume();
return 0;
err_resume:
+ up_write(&luo_register_rwlock);
luo_session_resume();
return err;
}
@@ -643,6 +656,7 @@ void luo_flb_serialize(void)
struct liveupdate_flb *gflb;
int i = 0;
+ guard(rwsem_read)(&luo_register_rwlock);
list_private_for_each_entry(gflb, &luo_flb_global.list, private.list) {
struct luo_flb_private *private = luo_flb_get_private(gflb);
--
2.43.0
^ permalink raw reply [flat|nested] 13+ messages in thread
* [PATCH v4 05/11] liveupdate: Defer FLB module refcounting to active sessions
2026-04-13 18:51 [PATCH v4 00/11] liveupdate: Fix module unloading and unregister API Pasha Tatashin
` (3 preceding siblings ...)
2026-04-13 18:51 ` [PATCH v4 04/11] liveupdate: Protect FLB lists with luo_register_rwlock Pasha Tatashin
@ 2026-04-13 18:51 ` Pasha Tatashin
2026-04-13 18:51 ` [PATCH v4 06/11] liveupdate: Remove luo_session_quiesce() Pasha Tatashin
` (5 subsequent siblings)
10 siblings, 0 replies; 13+ messages in thread
From: Pasha Tatashin @ 2026-04-13 18:51 UTC (permalink / raw)
To: rppt, akpm, linux-mm, linux-kernel, pasha.tatashin, dmatlack,
pratyush, skhawaja
Stop pinning modules indefinitely upon FLB registration.
Instead, dynamically take a module reference when the FLB is actively
used in a session (e.g., during preserve and retrieve) and release it
when the session concludes.
This allows modules providing FLB operations to be cleanly unloaded
when not in active use by the live update orchestrator.
Signed-off-by: Pasha Tatashin <pasha.tatashin@soleen.com>
Reviewed-by: Samiullah Khawaja <skhawaja@google.com>
Reviewed-by: Pratyush Yadav (Google) <pratyush@kernel.org>
---
kernel/liveupdate/luo_flb.c | 27 +++++++++++++++++----------
1 file changed, 17 insertions(+), 10 deletions(-)
diff --git a/kernel/liveupdate/luo_flb.c b/kernel/liveupdate/luo_flb.c
index fdb274410e8f..3d439d1c8ff1 100644
--- a/kernel/liveupdate/luo_flb.c
+++ b/kernel/liveupdate/luo_flb.c
@@ -115,10 +115,15 @@ static int luo_flb_file_preserve_one(struct liveupdate_flb *flb)
struct liveupdate_flb_op_args args = {0};
int err;
+ if (!try_module_get(flb->ops->owner))
+ return -ENODEV;
+
args.flb = flb;
err = flb->ops->preserve(&args);
- if (err)
+ if (err) {
+ module_put(flb->ops->owner);
return err;
+ }
private->outgoing.data = args.data;
private->outgoing.obj = args.obj;
}
@@ -146,6 +151,7 @@ static void luo_flb_file_unpreserve_one(struct liveupdate_flb *flb)
private->outgoing.data = 0;
private->outgoing.obj = NULL;
+ module_put(flb->ops->owner);
}
}
}
@@ -181,12 +187,17 @@ static int luo_flb_retrieve_one(struct liveupdate_flb *flb)
if (!found)
return -ENOENT;
+ if (!try_module_get(flb->ops->owner))
+ return -ENODEV;
+
args.flb = flb;
args.data = private->incoming.data;
err = flb->ops->retrieve(&args);
- if (err)
+ if (err) {
+ module_put(flb->ops->owner);
return err;
+ }
private->incoming.obj = args.obj;
private->incoming.retrieved = true;
@@ -220,6 +231,7 @@ static void luo_flb_file_finish_one(struct liveupdate_flb *flb)
private->incoming.data = 0;
private->incoming.obj = NULL;
private->incoming.finished = true;
+ module_put(flb->ops->owner);
}
}
}
@@ -395,11 +407,6 @@ int liveupdate_register_flb(struct liveupdate_file_handler *fh,
goto err_resume;
}
- if (!try_module_get(flb->ops->owner)) {
- err = -EAGAIN;
- goto err_resume;
- }
-
list_add_tail(&private->list, &luo_flb_global.list);
luo_flb_global.count++;
}
@@ -476,12 +483,11 @@ int liveupdate_unregister_flb(struct liveupdate_file_handler *fh,
private->users--;
/*
* If this is the last file-handler with which we are registred, remove
- * from the global list, and relese module reference.
+ * from the global list.
*/
if (!private->users) {
list_del_init(&private->list);
luo_flb_global.count--;
- module_put(flb->ops->owner);
}
up_write(&luo_register_rwlock);
@@ -510,7 +516,8 @@ int liveupdate_unregister_flb(struct liveupdate_file_handler *fh,
*
* Return: 0 on success, or a negative errno on failure. -ENODATA means no
* incoming FLB data, -ENOENT means specific flb not found in the incoming
- * data, and -EOPNOTSUPP when live update is disabled or not configured.
+ * data, -ENODEV if the FLB's module is unloading, and -EOPNOTSUPP when
+ * live update is disabled or not configured.
*/
int liveupdate_flb_get_incoming(struct liveupdate_flb *flb, void **objp)
{
--
2.43.0
^ permalink raw reply [flat|nested] 13+ messages in thread
* [PATCH v4 06/11] liveupdate: Remove luo_session_quiesce()
2026-04-13 18:51 [PATCH v4 00/11] liveupdate: Fix module unloading and unregister API Pasha Tatashin
` (4 preceding siblings ...)
2026-04-13 18:51 ` [PATCH v4 05/11] liveupdate: Defer FLB module refcounting to active sessions Pasha Tatashin
@ 2026-04-13 18:51 ` Pasha Tatashin
2026-04-13 18:51 ` [PATCH v4 07/11] liveupdate: Auto unregister FLBs on file handler unregistration Pasha Tatashin
` (4 subsequent siblings)
10 siblings, 0 replies; 13+ messages in thread
From: Pasha Tatashin @ 2026-04-13 18:51 UTC (permalink / raw)
To: rppt, akpm, linux-mm, linux-kernel, pasha.tatashin, dmatlack,
pratyush, skhawaja
Now that FLB module references are handled dynamically during active
sessions, we can safely remove the luo_session_quiesce() and
luo_session_resume() mechanism.
Signed-off-by: Pasha Tatashin <pasha.tatashin@soleen.com>
Reviewed-by: Pratyush Yadav (Google) <pratyush@kernel.org>
---
kernel/liveupdate/luo_file.c | 21 +-----------
kernel/liveupdate/luo_flb.c | 59 ++++++--------------------------
kernel/liveupdate/luo_internal.h | 2 --
kernel/liveupdate/luo_session.c | 43 -----------------------
4 files changed, 11 insertions(+), 114 deletions(-)
diff --git a/kernel/liveupdate/luo_file.c b/kernel/liveupdate/luo_file.c
index ecf780d74d80..63ddcf6dcddb 100644
--- a/kernel/liveupdate/luo_file.c
+++ b/kernel/liveupdate/luo_file.c
@@ -878,14 +878,6 @@ int liveupdate_register_file_handler(struct liveupdate_file_handler *fh)
return -EINVAL;
}
- /*
- * Ensure the system is quiescent (no active sessions).
- * This prevents registering new handlers while sessions are active or
- * while deserialization is in progress.
- */
- if (!luo_session_quiesce())
- return -EBUSY;
-
down_write(&luo_register_rwlock);
/* Check for duplicate compatible strings */
list_private_for_each_entry(fh_iter, &luo_file_handler_list, list) {
@@ -908,15 +900,12 @@ int liveupdate_register_file_handler(struct liveupdate_file_handler *fh)
list_add_tail(&ACCESS_PRIVATE(fh, list), &luo_file_handler_list);
up_write(&luo_register_rwlock);
- luo_session_resume();
-
liveupdate_test_register(fh);
return 0;
err_unlock:
up_write(&luo_register_rwlock);
- luo_session_resume();
return err;
}
@@ -928,14 +917,12 @@ int liveupdate_register_file_handler(struct liveupdate_file_handler *fh)
* reverses the operations of liveupdate_register_file_handler().
*
* It ensures safe removal by checking that:
- * No live update session is currently in progress.
* No FLB registered with this file handler.
*
* If the unregistration fails, the internal test state is reverted.
*
* Return: 0 Success. -EOPNOTSUPP when live update is not enabled. -EBUSY A live
- * update is in progress, can't quiesce live update or FLB is registred with
- * this file handler.
+ * update is in progress, FLB is registred with this file handler.
*/
int liveupdate_unregister_file_handler(struct liveupdate_file_handler *fh)
{
@@ -946,9 +933,6 @@ int liveupdate_unregister_file_handler(struct liveupdate_file_handler *fh)
liveupdate_test_unregister(fh);
- if (!luo_session_quiesce())
- goto err_register;
-
down_write(&luo_register_rwlock);
if (!list_empty(&ACCESS_PRIVATE(fh, flb_list)))
goto err_unlock;
@@ -957,14 +941,11 @@ int liveupdate_unregister_file_handler(struct liveupdate_file_handler *fh)
up_write(&luo_register_rwlock);
module_put(fh->ops->owner);
- luo_session_resume();
return 0;
err_unlock:
up_write(&luo_register_rwlock);
- luo_session_resume();
-err_register:
liveupdate_test_register(fh);
return err;
}
diff --git a/kernel/liveupdate/luo_flb.c b/kernel/liveupdate/luo_flb.c
index 3d439d1c8ff1..13f96d11ecc9 100644
--- a/kernel/liveupdate/luo_flb.c
+++ b/kernel/liveupdate/luo_flb.c
@@ -348,7 +348,6 @@ int liveupdate_register_flb(struct liveupdate_file_handler *fh,
struct luo_flb_link *link __free(kfree) = NULL;
struct liveupdate_flb *gflb;
struct luo_flb_link *iter;
- int err;
if (!liveupdate_enabled())
return -EOPNOTSUPP;
@@ -369,21 +368,12 @@ int liveupdate_register_flb(struct liveupdate_file_handler *fh,
if (!link)
return -ENOMEM;
- /*
- * Ensure the system is quiescent (no active sessions).
- * This acts as a global lock for registration: no other thread can
- * be in this section, and no sessions can be creating/using FDs.
- */
- if (!luo_session_quiesce())
- return -EBUSY;
-
- down_write(&luo_register_rwlock);
+ guard(rwsem_write)(&luo_register_rwlock);
/* Check that this FLB is not already linked to this file handler */
- err = -EEXIST;
list_for_each_entry(iter, flb_list, list) {
if (iter->flb == flb)
- goto err_resume;
+ return -EEXIST;
}
/*
@@ -391,20 +381,16 @@ int liveupdate_register_flb(struct liveupdate_file_handler *fh,
* is registered
*/
if (!private->users) {
- if (WARN_ON(!list_empty(&private->list))) {
- err = -EINVAL;
- goto err_resume;
- }
+ if (WARN_ON(!list_empty(&private->list)))
+ return -EINVAL;
- if (luo_flb_global.count == LUO_FLB_MAX) {
- err = -ENOSPC;
- goto err_resume;
- }
+ if (luo_flb_global.count == LUO_FLB_MAX)
+ return -ENOSPC;
/* Check that compatible string is unique in global list */
list_private_for_each_entry(gflb, &luo_flb_global.list, private.list) {
if (!strcmp(gflb->compatible, flb->compatible))
- goto err_resume;
+ return -EEXIST;
}
list_add_tail(&private->list, &luo_flb_global.list);
@@ -415,15 +401,8 @@ int liveupdate_register_flb(struct liveupdate_file_handler *fh,
private->users++;
link->flb = flb;
list_add_tail(&no_free_ptr(link)->list, flb_list);
- up_write(&luo_register_rwlock);
- luo_session_resume();
return 0;
-
-err_resume:
- up_write(&luo_register_rwlock);
- luo_session_resume();
- return err;
}
/**
@@ -439,12 +418,9 @@ int liveupdate_register_flb(struct liveupdate_file_handler *fh,
* the FLB is removed from the global registry and the reference to its
* owner module (acquired during registration) is released.
*
- * Context: This function ensures the session is quiesced (no active FDs
- * being created) during the update. It is typically called from a
- * subsystem's module exit function.
+ * Context: It is typically called from a subsystem's module exit function.
* Return: 0 on success.
* -EOPNOTSUPP if live update is disabled.
- * -EBUSY if the live update session is active and cannot be quiesced.
* -ENOENT if the FLB was not found in the file handler's list.
*/
int liveupdate_unregister_flb(struct liveupdate_file_handler *fh,
@@ -458,14 +434,7 @@ int liveupdate_unregister_flb(struct liveupdate_file_handler *fh,
if (!liveupdate_enabled())
return -EOPNOTSUPP;
- /*
- * Ensure the system is quiescent (no active sessions).
- * This acts as a global lock for unregistration.
- */
- if (!luo_session_quiesce())
- return -EBUSY;
-
- down_write(&luo_register_rwlock);
+ guard(rwsem_write)(&luo_register_rwlock);
/* Find and remove the link from the file handler's list */
list_for_each_entry(iter, flb_list, list) {
@@ -478,7 +447,7 @@ int liveupdate_unregister_flb(struct liveupdate_file_handler *fh,
}
if (err)
- goto err_resume;
+ return err;
private->users--;
/*
@@ -490,15 +459,7 @@ int liveupdate_unregister_flb(struct liveupdate_file_handler *fh,
luo_flb_global.count--;
}
- up_write(&luo_register_rwlock);
- luo_session_resume();
-
return 0;
-
-err_resume:
- up_write(&luo_register_rwlock);
- luo_session_resume();
- return err;
}
/**
diff --git a/kernel/liveupdate/luo_internal.h b/kernel/liveupdate/luo_internal.h
index 4bfe00ac8866..40a011bdfa55 100644
--- a/kernel/liveupdate/luo_internal.h
+++ b/kernel/liveupdate/luo_internal.h
@@ -85,8 +85,6 @@ int __init luo_session_setup_outgoing(void *fdt);
int __init luo_session_setup_incoming(void *fdt);
int luo_session_serialize(void);
int luo_session_deserialize(void);
-bool luo_session_quiesce(void);
-void luo_session_resume(void);
int luo_preserve_file(struct luo_file_set *file_set, u64 token, int fd);
void luo_file_unpreserve_files(struct luo_file_set *file_set);
diff --git a/kernel/liveupdate/luo_session.c b/kernel/liveupdate/luo_session.c
index 8c76dece679b..a3327a28fc1f 100644
--- a/kernel/liveupdate/luo_session.c
+++ b/kernel/liveupdate/luo_session.c
@@ -607,46 +607,3 @@ int luo_session_serialize(void)
return err;
}
-/**
- * luo_session_quiesce - Ensure no active sessions exist and lock session lists.
- *
- * Acquires exclusive write locks on both incoming and outgoing session lists.
- * It then validates no sessions exist in either list.
- *
- * This mechanism is used during file handler un/registration to ensure that no
- * sessions are currently using the handler, and no new sessions can be created
- * while un/registration is in progress.
- *
- * This prevents registering new handlers while sessions are active or
- * while deserialization is in progress.
- *
- * Return:
- * true - System is quiescent (0 sessions) and locked.
- * false - Active sessions exist. The locks are released internally.
- */
-bool luo_session_quiesce(void)
-{
- down_write(&luo_session_global.incoming.rwsem);
- down_write(&luo_session_global.outgoing.rwsem);
-
- if (luo_session_global.incoming.count ||
- luo_session_global.outgoing.count) {
- up_write(&luo_session_global.outgoing.rwsem);
- up_write(&luo_session_global.incoming.rwsem);
- return false;
- }
-
- return true;
-}
-
-/**
- * luo_session_resume - Unlock session lists and resume normal activity.
- *
- * Releases the exclusive locks acquired by a successful call to
- * luo_session_quiesce().
- */
-void luo_session_resume(void)
-{
- up_write(&luo_session_global.outgoing.rwsem);
- up_write(&luo_session_global.incoming.rwsem);
-}
--
2.43.0
^ permalink raw reply [flat|nested] 13+ messages in thread
* [PATCH v4 07/11] liveupdate: Auto unregister FLBs on file handler unregistration
2026-04-13 18:51 [PATCH v4 00/11] liveupdate: Fix module unloading and unregister API Pasha Tatashin
` (5 preceding siblings ...)
2026-04-13 18:51 ` [PATCH v4 06/11] liveupdate: Remove luo_session_quiesce() Pasha Tatashin
@ 2026-04-13 18:51 ` Pasha Tatashin
2026-04-13 18:51 ` [PATCH v4 08/11] liveupdate: Remove liveupdate_test_unregister() Pasha Tatashin
` (3 subsequent siblings)
10 siblings, 0 replies; 13+ messages in thread
From: Pasha Tatashin @ 2026-04-13 18:51 UTC (permalink / raw)
To: rppt, akpm, linux-mm, linux-kernel, pasha.tatashin, dmatlack,
pratyush, skhawaja
To ensure that unregistration is always successful and doesn't leave
dangling resources, introduce auto-unregistration of FLBs: when a file
handler is unregistered, all FLBs associated with it are automatically
unregistered.
Introduce a new helper luo_flb_unregister_all() which unregisters all
FLBs linked to the given file handler.
Signed-off-by: Pasha Tatashin <pasha.tatashin@soleen.com>
Reviewed-by: Pratyush Yadav (Google) <pratyush@kernel.org>
---
kernel/liveupdate/luo_file.c | 14 +-----
kernel/liveupdate/luo_flb.c | 85 ++++++++++++++++++++++----------
kernel/liveupdate/luo_internal.h | 1 +
3 files changed, 61 insertions(+), 39 deletions(-)
diff --git a/kernel/liveupdate/luo_file.c b/kernel/liveupdate/luo_file.c
index 63ddcf6dcddb..b37bd79974ce 100644
--- a/kernel/liveupdate/luo_file.c
+++ b/kernel/liveupdate/luo_file.c
@@ -926,26 +926,16 @@ int liveupdate_register_file_handler(struct liveupdate_file_handler *fh)
*/
int liveupdate_unregister_file_handler(struct liveupdate_file_handler *fh)
{
- int err = -EBUSY;
-
if (!liveupdate_enabled())
return -EOPNOTSUPP;
liveupdate_test_unregister(fh);
- down_write(&luo_register_rwlock);
- if (!list_empty(&ACCESS_PRIVATE(fh, flb_list)))
- goto err_unlock;
-
+ guard(rwsem_write)(&luo_register_rwlock);
+ luo_flb_unregister_all(fh);
list_del(&ACCESS_PRIVATE(fh, list));
- up_write(&luo_register_rwlock);
module_put(fh->ops->owner);
return 0;
-
-err_unlock:
- up_write(&luo_register_rwlock);
- liveupdate_test_register(fh);
- return err;
}
diff --git a/kernel/liveupdate/luo_flb.c b/kernel/liveupdate/luo_flb.c
index 13f96d11ecc9..b9f5d452a998 100644
--- a/kernel/liveupdate/luo_flb.c
+++ b/kernel/liveupdate/luo_flb.c
@@ -318,6 +318,63 @@ void luo_flb_file_finish(struct liveupdate_file_handler *fh)
luo_flb_file_finish_one(iter->flb);
}
+static void luo_flb_unregister_one(struct liveupdate_file_handler *fh,
+ struct liveupdate_flb *flb)
+{
+ struct luo_flb_private *private = luo_flb_get_private(flb);
+ struct list_head *flb_list = &ACCESS_PRIVATE(fh, flb_list);
+ struct luo_flb_link *iter;
+ bool found = false;
+
+ /* Find and remove the link from the file handler's list */
+ lockdep_assert_held_write(&luo_register_rwlock);
+ list_for_each_entry(iter, flb_list, list) {
+ if (iter->flb == flb) {
+ list_del(&iter->list);
+ kfree(iter);
+ found = true;
+ break;
+ }
+ }
+
+ if (!found) {
+ pr_warn("Failed to unregister FLB '%s': not found in file handler '%s'\n",
+ flb->compatible, fh->compatible);
+ return;
+ }
+
+ private->users--;
+
+ /*
+ * If this is the last file-handler with which we are registred, remove
+ * from the global list.
+ */
+ if (!private->users) {
+ list_del_init(&private->list);
+ luo_flb_global.count--;
+ }
+}
+
+/**
+ * luo_flb_unregister_all - Unregister all FLBs associated with a file handler.
+ * @fh: The file handler whose FLBs should be unregistered.
+ *
+ * This function iterates through the list of FLBs associated with the given
+ * file handler and unregisters them all one by one.
+ */
+void luo_flb_unregister_all(struct liveupdate_file_handler *fh)
+{
+ struct list_head *flb_list = &ACCESS_PRIVATE(fh, flb_list);
+ struct luo_flb_link *iter, *tmp;
+
+ if (!liveupdate_enabled())
+ return;
+
+ lockdep_assert_held_write(&luo_register_rwlock);
+ list_for_each_entry_safe(iter, tmp, flb_list, list)
+ luo_flb_unregister_one(fh, iter->flb);
+}
+
/**
* liveupdate_register_flb - Associate an FLB with a file handler and register it globally.
* @fh: The file handler that will now depend on the FLB.
@@ -426,38 +483,12 @@ int liveupdate_register_flb(struct liveupdate_file_handler *fh,
int liveupdate_unregister_flb(struct liveupdate_file_handler *fh,
struct liveupdate_flb *flb)
{
- struct luo_flb_private *private = luo_flb_get_private(flb);
- struct list_head *flb_list = &ACCESS_PRIVATE(fh, flb_list);
- struct luo_flb_link *iter;
- int err = -ENOENT;
-
if (!liveupdate_enabled())
return -EOPNOTSUPP;
guard(rwsem_write)(&luo_register_rwlock);
- /* Find and remove the link from the file handler's list */
- list_for_each_entry(iter, flb_list, list) {
- if (iter->flb == flb) {
- list_del(&iter->list);
- kfree(iter);
- err = 0;
- break;
- }
- }
-
- if (err)
- return err;
-
- private->users--;
- /*
- * If this is the last file-handler with which we are registred, remove
- * from the global list.
- */
- if (!private->users) {
- list_del_init(&private->list);
- luo_flb_global.count--;
- }
+ luo_flb_unregister_one(fh, flb);
return 0;
}
diff --git a/kernel/liveupdate/luo_internal.h b/kernel/liveupdate/luo_internal.h
index 40a011bdfa55..22f6901f89ed 100644
--- a/kernel/liveupdate/luo_internal.h
+++ b/kernel/liveupdate/luo_internal.h
@@ -103,6 +103,7 @@ void luo_file_set_destroy(struct luo_file_set *file_set);
int luo_flb_file_preserve(struct liveupdate_file_handler *fh);
void luo_flb_file_unpreserve(struct liveupdate_file_handler *fh);
void luo_flb_file_finish(struct liveupdate_file_handler *fh);
+void luo_flb_unregister_all(struct liveupdate_file_handler *fh);
int __init luo_flb_setup_outgoing(void *fdt);
int __init luo_flb_setup_incoming(void *fdt);
void luo_flb_serialize(void);
--
2.43.0
^ permalink raw reply [flat|nested] 13+ messages in thread
* [PATCH v4 08/11] liveupdate: Remove liveupdate_test_unregister()
2026-04-13 18:51 [PATCH v4 00/11] liveupdate: Fix module unloading and unregister API Pasha Tatashin
` (6 preceding siblings ...)
2026-04-13 18:51 ` [PATCH v4 07/11] liveupdate: Auto unregister FLBs on file handler unregistration Pasha Tatashin
@ 2026-04-13 18:51 ` Pasha Tatashin
2026-04-13 18:51 ` [PATCH v4 09/11] liveupdate: Make unregister functions return void Pasha Tatashin
` (2 subsequent siblings)
10 siblings, 0 replies; 13+ messages in thread
From: Pasha Tatashin @ 2026-04-13 18:51 UTC (permalink / raw)
To: rppt, akpm, linux-mm, linux-kernel, pasha.tatashin, dmatlack,
pratyush, skhawaja
Now that file handler unregistration automatically unregisters all
associated file handlers (FLBs), the liveupdate_test_unregister()
function is no longer needed. Remove it along with its usages
and declarations.
Signed-off-by: Pasha Tatashin <pasha.tatashin@soleen.com>
Reviewed-by: Pratyush Yadav (Google) <pratyush@kernel.org>
---
kernel/liveupdate/luo_file.c | 2 --
kernel/liveupdate/luo_internal.h | 2 --
lib/tests/liveupdate.c | 18 ------------------
3 files changed, 22 deletions(-)
diff --git a/kernel/liveupdate/luo_file.c b/kernel/liveupdate/luo_file.c
index b37bd79974ce..f3b7bb698d3b 100644
--- a/kernel/liveupdate/luo_file.c
+++ b/kernel/liveupdate/luo_file.c
@@ -929,8 +929,6 @@ int liveupdate_unregister_file_handler(struct liveupdate_file_handler *fh)
if (!liveupdate_enabled())
return -EOPNOTSUPP;
- liveupdate_test_unregister(fh);
-
guard(rwsem_write)(&luo_register_rwlock);
luo_flb_unregister_all(fh);
list_del(&ACCESS_PRIVATE(fh, list));
diff --git a/kernel/liveupdate/luo_internal.h b/kernel/liveupdate/luo_internal.h
index 22f6901f89ed..875844d7a41d 100644
--- a/kernel/liveupdate/luo_internal.h
+++ b/kernel/liveupdate/luo_internal.h
@@ -110,10 +110,8 @@ void luo_flb_serialize(void);
#ifdef CONFIG_LIVEUPDATE_TEST
void liveupdate_test_register(struct liveupdate_file_handler *fh);
-void liveupdate_test_unregister(struct liveupdate_file_handler *fh);
#else
static inline void liveupdate_test_register(struct liveupdate_file_handler *fh) { }
-static inline void liveupdate_test_unregister(struct liveupdate_file_handler *fh) { }
#endif
#endif /* _LINUX_LUO_INTERNAL_H */
diff --git a/lib/tests/liveupdate.c b/lib/tests/liveupdate.c
index 496d6ef91a30..e4b0ecbee32f 100644
--- a/lib/tests/liveupdate.c
+++ b/lib/tests/liveupdate.c
@@ -135,24 +135,6 @@ void liveupdate_test_register(struct liveupdate_file_handler *fh)
TEST_NFLBS, fh->compatible);
}
-void liveupdate_test_unregister(struct liveupdate_file_handler *fh)
-{
- int err, i;
-
- for (i = 0; i < TEST_NFLBS; i++) {
- struct liveupdate_flb *flb = &test_flbs[i];
-
- err = liveupdate_unregister_flb(fh, flb);
- if (err) {
- pr_err("Failed to unregister %s %pe\n",
- flb->compatible, ERR_PTR(err));
- }
- }
-
- pr_info("Unregistered %d FLBs from file handler: [%s]\n",
- TEST_NFLBS, fh->compatible);
-}
-
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Pasha Tatashin <pasha.tatashin@soleen.com>");
MODULE_DESCRIPTION("In-kernel test for LUO mechanism");
--
2.43.0
^ permalink raw reply [flat|nested] 13+ messages in thread
* [PATCH v4 09/11] liveupdate: Make unregister functions return void
2026-04-13 18:51 [PATCH v4 00/11] liveupdate: Fix module unloading and unregister API Pasha Tatashin
` (7 preceding siblings ...)
2026-04-13 18:51 ` [PATCH v4 08/11] liveupdate: Remove liveupdate_test_unregister() Pasha Tatashin
@ 2026-04-13 18:51 ` Pasha Tatashin
2026-04-13 18:51 ` [PATCH v4 10/11] liveupdate: Defer file handler module refcounting to active sessions Pasha Tatashin
2026-04-13 18:51 ` [PATCH v4 11/11] liveupdate: fix return value on session allocation failure Pasha Tatashin
10 siblings, 0 replies; 13+ messages in thread
From: Pasha Tatashin @ 2026-04-13 18:51 UTC (permalink / raw)
To: rppt, akpm, linux-mm, linux-kernel, pasha.tatashin, dmatlack,
pratyush, skhawaja
Change liveupdate_unregister_file_handler and liveupdate_unregister_flb
to return void instead of an error code. This follows the design
principle that unregistration during module unload should not fail,
as the unload cannot be stopped at that point.
Signed-off-by: Pasha Tatashin <pasha.tatashin@soleen.com>
Reviewed-by: Pratyush Yadav (Google) <pratyush@kernel.org>
---
include/linux/liveupdate.h | 14 ++++++--------
kernel/liveupdate/luo_file.c | 14 ++------------
kernel/liveupdate/luo_flb.c | 11 +++--------
3 files changed, 11 insertions(+), 28 deletions(-)
diff --git a/include/linux/liveupdate.h b/include/linux/liveupdate.h
index 9c761d9bacf8..30c5a39ff9e9 100644
--- a/include/linux/liveupdate.h
+++ b/include/linux/liveupdate.h
@@ -231,12 +231,12 @@ bool liveupdate_enabled(void);
int liveupdate_reboot(void);
int liveupdate_register_file_handler(struct liveupdate_file_handler *fh);
-int liveupdate_unregister_file_handler(struct liveupdate_file_handler *fh);
+void liveupdate_unregister_file_handler(struct liveupdate_file_handler *fh);
int liveupdate_register_flb(struct liveupdate_file_handler *fh,
struct liveupdate_flb *flb);
-int liveupdate_unregister_flb(struct liveupdate_file_handler *fh,
- struct liveupdate_flb *flb);
+void liveupdate_unregister_flb(struct liveupdate_file_handler *fh,
+ struct liveupdate_flb *flb);
int liveupdate_flb_get_incoming(struct liveupdate_flb *flb, void **objp);
int liveupdate_flb_get_outgoing(struct liveupdate_flb *flb, void **objp);
@@ -258,9 +258,8 @@ static inline int liveupdate_register_file_handler(struct liveupdate_file_handle
return -EOPNOTSUPP;
}
-static inline int liveupdate_unregister_file_handler(struct liveupdate_file_handler *fh)
+static inline void liveupdate_unregister_file_handler(struct liveupdate_file_handler *fh)
{
- return -EOPNOTSUPP;
}
static inline int liveupdate_register_flb(struct liveupdate_file_handler *fh,
@@ -269,10 +268,9 @@ static inline int liveupdate_register_flb(struct liveupdate_file_handler *fh,
return -EOPNOTSUPP;
}
-static inline int liveupdate_unregister_flb(struct liveupdate_file_handler *fh,
- struct liveupdate_flb *flb)
+static inline void liveupdate_unregister_flb(struct liveupdate_file_handler *fh,
+ struct liveupdate_flb *flb)
{
- return -EOPNOTSUPP;
}
static inline int liveupdate_flb_get_incoming(struct liveupdate_flb *flb,
diff --git a/kernel/liveupdate/luo_file.c b/kernel/liveupdate/luo_file.c
index f3b7bb698d3b..f8e098858c62 100644
--- a/kernel/liveupdate/luo_file.c
+++ b/kernel/liveupdate/luo_file.c
@@ -915,25 +915,15 @@ int liveupdate_register_file_handler(struct liveupdate_file_handler *fh)
*
* Unregisters the file handler from the liveupdate core. This function
* reverses the operations of liveupdate_register_file_handler().
- *
- * It ensures safe removal by checking that:
- * No FLB registered with this file handler.
- *
- * If the unregistration fails, the internal test state is reverted.
- *
- * Return: 0 Success. -EOPNOTSUPP when live update is not enabled. -EBUSY A live
- * update is in progress, FLB is registred with this file handler.
*/
-int liveupdate_unregister_file_handler(struct liveupdate_file_handler *fh)
+void liveupdate_unregister_file_handler(struct liveupdate_file_handler *fh)
{
if (!liveupdate_enabled())
- return -EOPNOTSUPP;
+ return;
guard(rwsem_write)(&luo_register_rwlock);
luo_flb_unregister_all(fh);
list_del(&ACCESS_PRIVATE(fh, list));
module_put(fh->ops->owner);
-
- return 0;
}
diff --git a/kernel/liveupdate/luo_flb.c b/kernel/liveupdate/luo_flb.c
index b9f5d452a998..42c37b3c2101 100644
--- a/kernel/liveupdate/luo_flb.c
+++ b/kernel/liveupdate/luo_flb.c
@@ -476,21 +476,16 @@ int liveupdate_register_flb(struct liveupdate_file_handler *fh,
* owner module (acquired during registration) is released.
*
* Context: It is typically called from a subsystem's module exit function.
- * Return: 0 on success.
- * -EOPNOTSUPP if live update is disabled.
- * -ENOENT if the FLB was not found in the file handler's list.
*/
-int liveupdate_unregister_flb(struct liveupdate_file_handler *fh,
- struct liveupdate_flb *flb)
+void liveupdate_unregister_flb(struct liveupdate_file_handler *fh,
+ struct liveupdate_flb *flb)
{
if (!liveupdate_enabled())
- return -EOPNOTSUPP;
+ return;
guard(rwsem_write)(&luo_register_rwlock);
luo_flb_unregister_one(fh, flb);
-
- return 0;
}
/**
--
2.43.0
^ permalink raw reply [flat|nested] 13+ messages in thread
* [PATCH v4 10/11] liveupdate: Defer file handler module refcounting to active sessions
2026-04-13 18:51 [PATCH v4 00/11] liveupdate: Fix module unloading and unregister API Pasha Tatashin
` (8 preceding siblings ...)
2026-04-13 18:51 ` [PATCH v4 09/11] liveupdate: Make unregister functions return void Pasha Tatashin
@ 2026-04-13 18:51 ` Pasha Tatashin
2026-04-13 18:51 ` [PATCH v4 11/11] liveupdate: fix return value on session allocation failure Pasha Tatashin
10 siblings, 0 replies; 13+ messages in thread
From: Pasha Tatashin @ 2026-04-13 18:51 UTC (permalink / raw)
To: rppt, akpm, linux-mm, linux-kernel, pasha.tatashin, dmatlack,
pratyush, skhawaja
Stop pinning modules indefinitely upon file handler registration.
Instead, dynamically increment the module reference count only when a
live update session actively uses the file handler (e.g., during
preservation or deserialization), and release it when the session ends.
This allows modules providing live update handlers to be gracefully
unloaded when no live update is in progress.
Signed-off-by: Pasha Tatashin <pasha.tatashin@soleen.com>
Reviewed-by: Pratyush Yadav (Google) <pratyush@kernel.org>
---
kernel/liveupdate/luo_file.c | 24 ++++++++++++------------
1 file changed, 12 insertions(+), 12 deletions(-)
diff --git a/kernel/liveupdate/luo_file.c b/kernel/liveupdate/luo_file.c
index f8e098858c62..dedbb8d02e6a 100644
--- a/kernel/liveupdate/luo_file.c
+++ b/kernel/liveupdate/luo_file.c
@@ -292,7 +292,8 @@ int luo_preserve_file(struct luo_file_set *file_set, u64 token, int fd)
down_read(&luo_register_rwlock);
list_private_for_each_entry(fh, &luo_file_handler_list, list) {
if (fh->ops->can_preserve(fh, file)) {
- err = 0;
+ if (try_module_get(fh->ops->owner))
+ err = 0;
break;
}
}
@@ -306,7 +307,7 @@ int luo_preserve_file(struct luo_file_set *file_set, u64 token, int fd)
err = xa_insert(&luo_preserved_files, luo_get_id(fh, file),
file, GFP_KERNEL);
if (err)
- goto err_free_files_mem;
+ goto err_module_put;
err = luo_flb_file_preserve(fh);
if (err)
@@ -342,6 +343,8 @@ int luo_preserve_file(struct luo_file_set *file_set, u64 token, int fd)
luo_flb_file_unpreserve(fh);
err_erase_xa:
xa_erase(&luo_preserved_files, luo_get_id(fh, file));
+err_module_put:
+ module_put(fh->ops->owner);
err_free_files_mem:
luo_free_files_mem(file_set);
err_fput:
@@ -384,6 +387,7 @@ void luo_file_unpreserve_files(struct luo_file_set *file_set)
args.private_data = luo_file->private_data;
luo_file->fh->ops->unpreserve(&args);
luo_flb_file_unpreserve(luo_file->fh);
+ module_put(luo_file->fh->ops->owner);
xa_erase(&luo_preserved_files,
luo_get_id(luo_file->fh, luo_file->file));
@@ -675,6 +679,7 @@ static void luo_file_finish_one(struct luo_file_set *file_set,
luo_file->fh->ops->finish(&args);
luo_flb_file_finish(luo_file->fh);
+ module_put(luo_file->fh->ops->owner);
}
/**
@@ -812,7 +817,8 @@ int luo_file_deserialize(struct luo_file_set *file_set,
down_read(&luo_register_rwlock);
list_private_for_each_entry(fh, &luo_file_handler_list, list) {
if (!strcmp(fh->compatible, file_ser[i].compatible)) {
- handler_found = true;
+ if (try_module_get(fh->ops->owner))
+ handler_found = true;
break;
}
}
@@ -826,8 +832,10 @@ int luo_file_deserialize(struct luo_file_set *file_set,
}
luo_file = kzalloc_obj(*luo_file);
- if (!luo_file)
+ if (!luo_file) {
+ module_put(fh->ops->owner);
return -ENOMEM;
+ }
/* safe to use fh because its module is pinned */
luo_file->fh = fh;
@@ -889,12 +897,6 @@ int liveupdate_register_file_handler(struct liveupdate_file_handler *fh)
}
}
- /* Pin the module implementing the handler */
- if (!try_module_get(fh->ops->owner)) {
- err = -EAGAIN;
- goto err_unlock;
- }
-
INIT_LIST_HEAD(&ACCESS_PRIVATE(fh, flb_list));
INIT_LIST_HEAD(&ACCESS_PRIVATE(fh, list));
list_add_tail(&ACCESS_PRIVATE(fh, list), &luo_file_handler_list);
@@ -924,6 +926,4 @@ void liveupdate_unregister_file_handler(struct liveupdate_file_handler *fh)
guard(rwsem_write)(&luo_register_rwlock);
luo_flb_unregister_all(fh);
list_del(&ACCESS_PRIVATE(fh, list));
-
- module_put(fh->ops->owner);
}
--
2.43.0
^ permalink raw reply [flat|nested] 13+ messages in thread
* [PATCH v4 11/11] liveupdate: fix return value on session allocation failure
2026-04-13 18:51 [PATCH v4 00/11] liveupdate: Fix module unloading and unregister API Pasha Tatashin
` (9 preceding siblings ...)
2026-04-13 18:51 ` [PATCH v4 10/11] liveupdate: Defer file handler module refcounting to active sessions Pasha Tatashin
@ 2026-04-13 18:51 ` Pasha Tatashin
2026-04-14 9:35 ` Pratyush Yadav
10 siblings, 1 reply; 13+ messages in thread
From: Pasha Tatashin @ 2026-04-13 18:51 UTC (permalink / raw)
To: rppt, akpm, linux-mm, linux-kernel, pasha.tatashin, dmatlack,
pratyush, skhawaja
When session allocation fails during deserialization, the global 'err'
variable was not updated before returning. This caused subsequent calls
to luo_session_deserialize() to incorrectly report success.
Ensure 'err' is set to the error code from PTR_ERR(session). This
ensures that an error is correctly returned to userspace when it
attempts to open /dev/liveupdate in the new kernel if deserialization
failed.
Signed-off-by: Pasha Tatashin <pasha.tatashin@soleen.com>
---
kernel/liveupdate/luo_session.c | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)
diff --git a/kernel/liveupdate/luo_session.c b/kernel/liveupdate/luo_session.c
index a3327a28fc1f..92b1af791889 100644
--- a/kernel/liveupdate/luo_session.c
+++ b/kernel/liveupdate/luo_session.c
@@ -547,7 +547,8 @@ int luo_session_deserialize(void)
pr_warn("Failed to allocate session [%.*s] during deserialization %pe\n",
(int)sizeof(sh->ser[i].name),
sh->ser[i].name, session);
- return PTR_ERR(session);
+ err = PTR_ERR(session);
+ return err;
}
err = luo_session_insert(sh, session);
--
2.43.0
^ permalink raw reply [flat|nested] 13+ messages in thread
* Re: [PATCH v4 11/11] liveupdate: fix return value on session allocation failure
2026-04-13 18:51 ` [PATCH v4 11/11] liveupdate: fix return value on session allocation failure Pasha Tatashin
@ 2026-04-14 9:35 ` Pratyush Yadav
0 siblings, 0 replies; 13+ messages in thread
From: Pratyush Yadav @ 2026-04-14 9:35 UTC (permalink / raw)
To: Pasha Tatashin
Cc: rppt, akpm, linux-mm, linux-kernel, dmatlack, pratyush, skhawaja
On Mon, Apr 13 2026, Pasha Tatashin wrote:
> When session allocation fails during deserialization, the global 'err'
> variable was not updated before returning. This caused subsequent calls
> to luo_session_deserialize() to incorrectly report success.
>
> Ensure 'err' is set to the error code from PTR_ERR(session). This
> ensures that an error is correctly returned to userspace when it
> attempts to open /dev/liveupdate in the new kernel if deserialization
> failed.
>
> Signed-off-by: Pasha Tatashin <pasha.tatashin@soleen.com>
Reviewed-by: Pratyush Yadav (Google) <pratyush@kernel.org>
[...]
--
Regards,
Pratyush Yadav
^ permalink raw reply [flat|nested] 13+ messages in thread
end of thread, other threads:[~2026-04-14 9:35 UTC | newest]
Thread overview: 13+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2026-04-13 18:51 [PATCH v4 00/11] liveupdate: Fix module unloading and unregister API Pasha Tatashin
2026-04-13 18:51 ` [PATCH v4 01/11] liveupdate: Safely print untrusted strings Pasha Tatashin
2026-04-13 18:51 ` [PATCH v4 02/11] liveupdate: Synchronize lazy initialization of FLB private state Pasha Tatashin
2026-04-13 18:51 ` [PATCH v4 03/11] liveupdate: Protect file handler list with rwsem Pasha Tatashin
2026-04-13 18:51 ` [PATCH v4 04/11] liveupdate: Protect FLB lists with luo_register_rwlock Pasha Tatashin
2026-04-13 18:51 ` [PATCH v4 05/11] liveupdate: Defer FLB module refcounting to active sessions Pasha Tatashin
2026-04-13 18:51 ` [PATCH v4 06/11] liveupdate: Remove luo_session_quiesce() Pasha Tatashin
2026-04-13 18:51 ` [PATCH v4 07/11] liveupdate: Auto unregister FLBs on file handler unregistration Pasha Tatashin
2026-04-13 18:51 ` [PATCH v4 08/11] liveupdate: Remove liveupdate_test_unregister() Pasha Tatashin
2026-04-13 18:51 ` [PATCH v4 09/11] liveupdate: Make unregister functions return void Pasha Tatashin
2026-04-13 18:51 ` [PATCH v4 10/11] liveupdate: Defer file handler module refcounting to active sessions Pasha Tatashin
2026-04-13 18:51 ` [PATCH v4 11/11] liveupdate: fix return value on session allocation failure Pasha Tatashin
2026-04-14 9:35 ` Pratyush Yadav
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox