Race condition / concurrent resource management (double preservation) across liveupdate sessions

MEDIUM
torvalds/linux
Commit: 00d0b372374f
Affected: v7.0-rc6 and earlier (versions containing the liveupdate LUO feature prior to this patch)
2026-04-25 13:13 UTC

Description

The patch adds a global tracking mechanism (luo_preserved_files) to prevent the same file/resource from being managed by multiple LUO sessions by recording an identifier for each preserved file. It introduces a get_id callback to allow resources to define what constitutes a unique identifier. However, the implementation has a race-condition/chronic misbehavior: in luo_retrieve_file(), the code unconditionally calls xa_insert(...) and then uses WARN_ON(...) to report if the key already exists, but it does not enforce a failure when the insert fails. As a result, two LUO sessions could concurrently preserve the same resource (especially when get_id() provides a stable cross-process identifier), potentially leading to conflicting access, resource mismanagement, or use-after-free scenarios if the resources are finished/released concurrently. The guard in luo_preserve_file() would otherwise prevent a second preservation by returning -EEXIST, but the retrieve path path can bypass this protection due to its non-fatal handling of duplication.

Proof of Concept

Note: The actual user-space interface for LUO is kernel-ioctl based and depends on the liveupdate subsystem implementation. The following PoC is conceptual and intended for environments where you can drive LUO’s retrieve path via controlled test interfaces. It demonstrates the race/duplication problem as described by the patch, not a ready-to-run exploit against a production system. 1) Build a test kernel with this patch applied and load a dummy LUO test module exposing a stable, cross-process file identifier through the get_id() hook (for example, have the file_ops.get_id(file) return a constant value like 0xdeadbeef for a test resource). 2) Create a test resource whose get_id() returns a constant identifier across processes, so two separate LUO sessions would contend on the same resource (e.g., a dummy hardware resource or a memfd-backed object wired to report the same id). 3) In two separate user-space LUO sessions (or two threads in separate processes), concurrently invoke the LUO retrieve/preserve path for the same resource identifier: - Session A calls LUO retrieve/preserve for resource 0xdeadbeef. - Session B, almost simultaneously, calls LUO retrieve/preserve for the same resource 0xdeadbeef. 4) Observe kernel logs (dmesg) for a WARN_ON triggered by the XA insert in the retrieve path, indicating a duplicate key: the designed protection via -EBUSY is not enforced in the retrieve path due to the WARN_ON usage. Depending on timing, both sessions may end up holding references to the same resource, or at least one path may not properly fail with -EBUSY as intended. 5) Validate behavior by finishing/releases and observing whether both sessions attempt to release the same resource, potentially triggering double-free or use-after-free in the LUO finish path if the global xa entry is not properly synchronized. Notes and caveats: - The exact ioctl/user-space sequence depends on the kernel’s LUO interface; the PoC assumes a test setup where you can trigger retrieve/preserve paths and where get_id() provides a stable cross-process identifier. - In a real test, you would observe a correction-like failure (-EBUSY) if the code path properly enforces uniqueness, but in this patch, the retrieve path uses WARN_ON and proceeds, which is the root of the vulnerability surface. - This PoC is conceptual to illustrate the race/duplication issue and the protect-and-hardened path. It requires a test environment with a controlled LUO test interface and a resource whose identifier can be made stable across processes.

Commit Details

Author: Pasha Tatashin

Date: 2026-03-26 16:39 UTC

Message:

liveupdate: prevent double management of files Patch series "liveupdate: prevent double preservation", v4. Currently, LUO does not prevent the same file from being managed twice across different active sessions. Because LUO preserves files of absolutely different types: memfd, and upcoming vfiofd [1], iommufd [2], guestmefd (and possible kvmfd/cpufd). There is no common private data or guarantee on how to prevent that the same file is not preserved twice beside using inode or some slower and expensive method like hashtables. This patch (of 4) Currently, LUO does not prevent the same file from being managed twice across different active sessions. Use a global xarray luo_preserved_files to keep track of file identifiers being preserved by LUO. Update luo_preserve_file() to check and insert the file identifier into this xarray when it is preserved, and erase it in luo_file_unpreserve_files() when it is released. To allow handlers to define what constitutes a "unique" file (e.g., different struct file objects pointing to the same hardware resource), add a get_id() callback to struct liveupdate_file_ops. If not provided, the default identifier is the struct file pointer itself. This ensures that the same file (or resource) cannot be managed by multiple sessions. If another session attempts to preserve an already managed file, it will now fail with -EBUSY. Link: https://lore.kernel.org/20260326163943.574070-1-pasha.tatashin@soleen.com Link: https://lore.kernel.org/20260326163943.574070-2-pasha.tatashin@soleen.com Link: https://lore.kernel.org/all/20260129212510.967611-1-dmatlack@google.com [1] Link: https://lore.kernel.org/all/20260203220948.2176157-1-skhawaja@google.com [2] Signed-off-by: Pasha Tatashin <pasha.tatashin@soleen.com> Reviewed-by: Samiullah Khawaja <skhawaja@google.com> Reviewed-by: Mike Rapoport (Microsoft) <rppt@kernel.org> Cc: David Matlack <dmatlack@google.com> Cc: Pratyush Yadav <pratyush@kernel.org> Cc: Shuah Khan <shuah@kernel.org> Cc: Christian Brauner <brauner@kernel.org> Signed-off-by: Andrew Morton <akpm@linux-foundation.org>

Triage Assessment

Vulnerability Type: Race condition

Confidence: MEDIUM

Reasoning:

The patch adds a global tracking mechanism to prevent the same file/resource from being preserved by multiple LUO sessions, preventing concurrent or conflicting access. This mitigates a class of race/consistency issues that could lead to improper access or resource mismanagement during live updates, which has security implications.

Verification Assessment

Vulnerability Type: Race condition / concurrent resource management (double preservation) across liveupdate sessions

Confidence: MEDIUM

Affected Versions: v7.0-rc6 and earlier (versions containing the liveupdate LUO feature prior to this patch)

Code Diff

diff --git a/include/linux/liveupdate.h b/include/linux/liveupdate.h index dd11fdc76a5f27..61325ad2652654 100644 --- a/include/linux/liveupdate.h +++ b/include/linux/liveupdate.h @@ -63,6 +63,7 @@ struct liveupdate_file_op_args { * finish, in order to do successful finish calls for all * resources in the session. * @finish: Required. Final cleanup in the new kernel. + * @get_id: Optional. Returns a unique identifier for the file. * @owner: Module reference * * All operations (except can_preserve) receive a pointer to a @@ -78,6 +79,7 @@ struct liveupdate_file_ops { int (*retrieve)(struct liveupdate_file_op_args *args); bool (*can_finish)(struct liveupdate_file_op_args *args); void (*finish)(struct liveupdate_file_op_args *args); + unsigned long (*get_id)(struct file *file); struct module *owner; }; diff --git a/kernel/liveupdate/luo_file.c b/kernel/liveupdate/luo_file.c index 5acee4174bf00f..09103cf81107bb 100644 --- a/kernel/liveupdate/luo_file.c +++ b/kernel/liveupdate/luo_file.c @@ -108,12 +108,16 @@ #include <linux/liveupdate.h> #include <linux/module.h> #include <linux/sizes.h> +#include <linux/xarray.h> #include <linux/slab.h> #include <linux/string.h> #include "luo_internal.h" static LIST_HEAD(luo_file_handler_list); +/* Keep track of files being preserved by LUO */ +static DEFINE_XARRAY(luo_preserved_files); + /* 2 4K pages, give space for 128 files per file_set */ #define LUO_FILE_PGCNT 2ul #define LUO_FILE_MAX \ @@ -203,6 +207,12 @@ static void luo_free_files_mem(struct luo_file_set *file_set) file_set->files = NULL; } +static unsigned long luo_get_id(struct liveupdate_file_handler *fh, + struct file *file) +{ + return fh->ops->get_id ? fh->ops->get_id(file) : (unsigned long)file; +} + static bool luo_token_is_used(struct luo_file_set *file_set, u64 token) { struct luo_file *iter; @@ -248,6 +258,7 @@ static bool luo_token_is_used(struct luo_file_set *file_set, u64 token) * Context: Can be called from an ioctl handler during normal system operation. * Return: 0 on success. Returns a negative errno on failure: * -EEXIST if the token is already used. + * -EBUSY if the file descriptor is already preserved by another session. * -EBADF if the file descriptor is invalid. * -ENOSPC if the file_set is full. * -ENOENT if no compatible handler is found. @@ -288,10 +299,15 @@ int luo_preserve_file(struct luo_file_set *file_set, u64 token, int fd) if (err) goto err_free_files_mem; - err = luo_flb_file_preserve(fh); + err = xa_insert(&luo_preserved_files, luo_get_id(fh, file), + file, GFP_KERNEL); if (err) goto err_free_files_mem; + err = luo_flb_file_preserve(fh); + if (err) + goto err_erase_xa; + luo_file = kzalloc_obj(*luo_file); if (!luo_file) { err = -ENOMEM; @@ -320,6 +336,8 @@ int luo_preserve_file(struct luo_file_set *file_set, u64 token, int fd) kfree(luo_file); err_flb_unpreserve: luo_flb_file_unpreserve(fh); +err_erase_xa: + xa_erase(&luo_preserved_files, luo_get_id(fh, file)); err_free_files_mem: luo_free_files_mem(file_set); err_fput: @@ -363,6 +381,8 @@ void luo_file_unpreserve_files(struct luo_file_set *file_set) luo_file->fh->ops->unpreserve(&args); luo_flb_file_unpreserve(luo_file->fh); + xa_erase(&luo_preserved_files, + luo_get_id(luo_file->fh, luo_file->file)); list_del(&luo_file->list); file_set->count--; @@ -606,6 +626,11 @@ int luo_retrieve_file(struct luo_file_set *file_set, u64 token, luo_file->file = args.file; /* Get reference so we can keep this file in LUO until finish */ get_file(luo_file->file); + + WARN_ON(xa_insert(&luo_preserved_files, + luo_get_id(luo_file->fh, luo_file->file), + luo_file->file, GFP_KERNEL)); + *filep = luo_file->file; luo_file->retrieve_status = 1; @@ -701,8 +726,11 @@ int luo_file_finish(struct luo_file_set *file_set) luo_file_finish_one(file_set, luo_file); - if (luo_file->file) + if (luo_file->file) { + xa_erase(&luo_preserved_files, + luo_get_id(luo_file->fh, luo_file->file)); fput(luo_file->file); + } list_del(&luo_file->list); file_set->count--; mutex_destroy(&luo_file->mutex);
← Back to Alerts View on GitHub →