* [PATCH v2 01/10] liveupdate: Pass user argument to file preserve and retrieve
2026-10-22 22:50 [PATCH v2 00/10] liveupdate: Preserve tmpfs files across Live Update David Matlack
@ 2026-10-22 22:50 ` David Matlack
2026-10-23 22:51 ` sashiko-bot
2026-10-22 22:50 ` [PATCH v2 02/10] selftests: liveupdate: Add tests for preserve and retrieve user argument David Matlack
` (8 subsequent siblings)
9 siblings, 1 reply; 20+ messages in thread
From: David Matlack @ 2026-10-22 22:50 UTC (permalink / raw)
To: kexec, linux-doc, linux-kernel, linux-kselftest, linux-mm
Cc: Alexander Graf, Andrew Morton, Baolin Wang, Christian Brauner,
David Hildenbrand, Hugh Dickins, Jonathan Corbet, Liam R. Howlett,
Lorenzo Stoakes, Michal Hocko, Mike Rapoport, Pasha Tatashin,
Pratyush Yadav, Randy Dunlap, Samiullah Khawaja, Shuah Khan,
Suren Baghdasaryan, Vlastimil Babka, David Matlack
From: Pasha Tatashin <pasha.tatashin@soleen.com>
Allow userspace to pass a file-handler-specific argument structure when
preserving or retrieving an FD via LUO. File handlers may need
additional context during preservation or retrieval, such as a target
FD, device location, or handler-specific parameters, without introducing
separate ioctls for each file type. Whether the argument is required,
optional, or unused is defined by the file handler.
Extend struct liveupdate_session_preserve_fd and
struct liveupdate_session_retrieve_fd with a 64-bit arg field pointing
to a handler-defined argument structure that begins with a 32-bit size
field. Because IOCTL_OP validates a minimum size up to the token field,
existing callers passing the 16-byte struct remain compatible and have
arg zero-filled by copy_struct_from_user().
Add parg and rarg (struct liveupdate_arg_spec) to
struct liveupdate_file_ops, initialized via LIVEUPDATE_ARG(_struct,
_min_last), which records the full struct size and minimum required size
while enforcing at compile time that handler argument structures begin
with a 32-bit size field. When arg is non-zero, LUO validates the
minimum size and copies the user argument via copy_struct_from_user() up
to the handler's expected size and passes the kernel copy in
struct liveupdate_file_op_args, or returns -EOPNOTSUPP if the handler
does not accept an argument.
The argument only applies to the retrieve that restores the file.
Retrieving a token that was already retrieved returns the existing file,
so fail with -EBUSY if a non-zero argument is passed then, instead of
silently ignoring it.
Signed-off-by: Pasha Tatashin <pasha.tatashin@soleen.com>
Signed-off-by: David Matlack <dmatlack@google.com>
---
include/linux/liveupdate.h | 44 +++++++++++++++++++
include/uapi/linux/liveupdate.h | 10 +++++
kernel/liveupdate/luo_file.c | 73 +++++++++++++++++++++++++++++---
kernel/liveupdate/luo_internal.h | 5 ++-
kernel/liveupdate/luo_session.c | 6 ++-
5 files changed, 128 insertions(+), 10 deletions(-)
diff --git a/include/linux/liveupdate.h b/include/linux/liveupdate.h
index 6051abc0612c..7543f5d648d3 100644
--- a/include/linux/liveupdate.h
+++ b/include/linux/liveupdate.h
@@ -22,6 +22,36 @@ struct liveupdate_flb;
struct liveupdate_session;
struct file;
+/**
+ * struct liveupdate_arg_spec - Specification of a file handler user argument.
+ * @size: Full size of the argument structure in the current kernel.
+ * @min_size: Minimum valid user-supplied size (offsetofend of the last
+ * required field).
+ */
+struct liveupdate_arg_spec {
+ size_t size;
+ size_t min_size;
+};
+
+/**
+ * LIVEUPDATE_ARG - Validated specification of a file handler argument struct.
+ * @_struct: The argument structure type.
+ * @_min_last: The last required member in @_struct.
+ *
+ * All file handler argument structures passed via
+ * &liveupdate_session_preserve_fd.arg or &liveupdate_session_retrieve_fd.arg
+ * must begin with a 32-bit @size field so they can be extended compatibly.
+ * Use this macro to initialize &liveupdate_file_ops.parg and
+ * &liveupdate_file_ops.rarg.
+ */
+#define LIVEUPDATE_ARG(_struct, _min_last) \
+ { \
+ .size = sizeof(_struct) + \
+ BUILD_BUG_ON_ZERO(offsetof(_struct, size) != 0) + \
+ BUILD_BUG_ON_ZERO(sizeof_field(_struct, size) != sizeof(u32)), \
+ .min_size = offsetofend(_struct, _min_last), \
+ }
+
/**
* struct liveupdate_file_op_args - Arguments for file operation callbacks.
* @handler: The file handler being called.
@@ -40,6 +70,11 @@ struct file;
* is not preserved. Set by the handler's .preserve()
* callback, and must be freed in the handler's
* .unpreserve() callback.
+ * @arg: Kernel copy of the user-provided argument for .preserve()
+ * and .retrieve(), or %NULL if none was provided. Allocated
+ * to the handler's expected argument size with any trailing
+ * bytes beyond the user-supplied size zero-filled. Valid
+ * only for the duration of the callback.
*
* This structure bundles all parameters for the file operation callbacks.
* The 'data' and 'file' fields are used for both input and output.
@@ -51,6 +86,7 @@ struct liveupdate_file_op_args {
struct file *file;
u64 serialized_data;
void *private_data;
+ const void *arg;
};
/**
@@ -68,6 +104,12 @@ struct liveupdate_file_op_args {
* resources in the session.
* @finish: Required. Final cleanup in the new kernel.
* @get_id: Optional. Returns a unique identifier for the file.
+ * @parg: Optional. Specification of the user argument structure
+ * accepted by @preserve, initialized via LIVEUPDATE_ARG(). If
+ * unset, non-zero user arguments are rejected with -EOPNOTSUPP.
+ * @rarg: Optional. Specification of the user argument structure
+ * accepted by @retrieve, initialized via LIVEUPDATE_ARG(). If
+ * unset, non-zero user arguments are rejected with -EOPNOTSUPP.
* @owner: Module reference
*
* All operations (except can_preserve) receive a pointer to a
@@ -84,6 +126,8 @@ struct liveupdate_file_ops {
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 liveupdate_arg_spec parg;
+ struct liveupdate_arg_spec rarg;
struct module *owner;
};
diff --git a/include/uapi/linux/liveupdate.h b/include/uapi/linux/liveupdate.h
index 4043d4038712..e806752c2fc6 100644
--- a/include/uapi/linux/liveupdate.h
+++ b/include/uapi/linux/liveupdate.h
@@ -126,6 +126,8 @@ struct liveupdate_ioctl_retrieve_session {
* @size: Input; sizeof(struct liveupdate_session_preserve_fd)
* @fd: Input; The user-space file descriptor to be preserved.
* @token: Input; An opaque, unique token for preserved resource.
+ * @arg: Input; User-space pointer to a file-handler-specific argument
+ * structure starting with a 32-bit size field, or 0 if unused.
*
* Holds parameters for preserving a file descriptor.
*
@@ -149,6 +151,7 @@ struct liveupdate_session_preserve_fd {
__u32 size;
__s32 fd;
__aligned_u64 token;
+ __aligned_u64 arg;
};
#define LIVEUPDATE_SESSION_PRESERVE_FD \
@@ -160,6 +163,8 @@ struct liveupdate_session_preserve_fd {
* @fd: Output; The new file descriptor representing the fully restored
* kernel resource.
* @token: Input; An opaque, token that was used to preserve the resource.
+ * @arg: Input; User-space pointer to a file-handler-specific argument
+ * structure starting with a 32-bit size field, or 0 if unused.
*
* Retrieve a previously preserved file descriptor.
*
@@ -173,12 +178,17 @@ struct liveupdate_session_preserve_fd {
* attempts to retrieve the token fail with the same error code. Failed
* retrieves are not retried.
*
+ * Retrieving a token that was already retrieved successfully returns a new file
+ * descriptor for the same file. The @arg only applies to the retrieve that
+ * restores the file, so passing a non-zero @arg then fails with -EBUSY.
+ *
* Return: 0 on success, negative error code on failure (e.g., invalid token).
*/
struct liveupdate_session_retrieve_fd {
__u32 size;
__s32 fd;
__aligned_u64 token;
+ __aligned_u64 arg;
};
#define LIVEUPDATE_SESSION_RETRIEVE_FD \
diff --git a/kernel/liveupdate/luo_file.c b/kernel/liveupdate/luo_file.c
index a39992da504f..eeeb3b5ff2fa 100644
--- a/kernel/liveupdate/luo_file.c
+++ b/kernel/liveupdate/luo_file.c
@@ -126,6 +126,7 @@
#include <linux/xarray.h>
#include <linux/slab.h>
#include <linux/string.h>
+#include <linux/uaccess.h>
#include "luo_internal.h"
static LIST_HEAD(luo_file_handler_list);
@@ -202,11 +203,45 @@ static bool luo_token_is_used(struct luo_file_set *file_set, u64 token)
return false;
}
+static int luo_copy_user_arg(u64 user_arg, const void **argp,
+ const struct liveupdate_arg_spec *spec)
+{
+ void *karg __free(kfree) = NULL;
+ void __user *uptr;
+ u32 user_size;
+ int err;
+
+ if (!user_arg)
+ return 0;
+ if (!spec->size)
+ return -EOPNOTSUPP;
+
+ uptr = u64_to_user_ptr(user_arg);
+ if (get_user(user_size, (u32 __user *)uptr))
+ return -EFAULT;
+ if (user_size < spec->min_size)
+ return -EINVAL;
+ if (user_size > PAGE_SIZE)
+ return -E2BIG;
+
+ karg = kmalloc(spec->size, GFP_KERNEL);
+ if (!karg)
+ return -ENOMEM;
+
+ err = copy_struct_from_user(karg, spec->size, uptr, user_size);
+ if (err)
+ return err;
+
+ *argp = no_free_ptr(karg);
+ return 0;
+}
+
/**
* luo_preserve_file - Initiate the preservation of a file descriptor.
* @file_set: The file_set to which the preserved file will be added.
* @token: A unique, user-provided identifier for the file.
* @fd: The file descriptor to be preserved.
+ * @arg: User-space pointer to the handler argument structure, or 0.
*
* This function orchestrates the first phase of preserving a file. Upon entry,
* it takes a reference to the 'struct file' via fget(), effectively making LUO
@@ -222,9 +257,11 @@ static bool luo_token_is_used(struct luo_file_set *file_set, u64 token)
* (allocates if needed).
* 3. Iterates through registered handlers, calling can_preserve() to find one
* compatible with the given @fd.
- * 4. Calls the handler's .preserve() operation, which saves the file's state
+ * 4. Copies the user argument structure (if @arg is non-zero) according to the
+ * handler's parg specification.
+ * 5. Calls the handler's .preserve() operation, which saves the file's state
* and returns an opaque private data handle.
- * 5. Adds the new instance to the file_set's internal list.
+ * 6. Adds the new instance to the file_set's internal list.
*
* On success, LUO takes a reference to the 'struct file' and considers it
* under its management until it is unpreserved or finished.
@@ -239,10 +276,12 @@ static bool luo_token_is_used(struct luo_file_set *file_set, u64 token)
* -EBADF if the file descriptor is invalid.
* -ENOSPC if the file_set is full.
* -ENOENT if no compatible handler is found.
+ * -EOPNOTSUPP if @arg is non-zero and the handler does not accept an arg.
* -ENOMEM on memory allocation failure.
* Other erros might be returned by .preserve().
*/
-int luo_preserve_file(struct luo_file_set *file_set, u64 token, int fd)
+int luo_preserve_file(struct luo_file_set *file_set, u64 token, int fd,
+ u64 arg)
{
struct liveupdate_file_op_args args = {0};
struct liveupdate_file_handler *fh;
@@ -278,10 +317,14 @@ int luo_preserve_file(struct luo_file_set *file_set, u64 token, int fd)
if (err)
goto err_fput;
+ err = luo_copy_user_arg(arg, &args.arg, &fh->ops->parg);
+ if (err)
+ goto err_module_put;
+
err = xa_insert(&luo_preserved_files, luo_get_id(fh, file),
file, GFP_KERNEL);
if (err)
- goto err_module_put;
+ goto err_free_arg;
err = luo_flb_file_preserve(fh);
if (err)
@@ -305,6 +348,7 @@ int luo_preserve_file(struct luo_file_set *file_set, u64 token, int fd)
if (err)
goto err_kfree;
+ kfree(args.arg);
luo_file->serialized_data = args.serialized_data;
luo_file->private_data = args.private_data;
list_add_tail(&luo_file->list, &file_set->files_list);
@@ -318,6 +362,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_free_arg:
+ kfree(args.arg);
err_module_put:
module_put(fh->ops->owner);
err_fput:
@@ -547,6 +593,7 @@ void luo_file_unfreeze(struct luo_file_set *file_set,
* luo_retrieve_file - Restores a preserved file from a file_set by its token.
* @file_set: The file_set from which to retrieve the file.
* @token: The unique token identifying the file to be restored.
+ * @arg: User-space pointer to the handler argument structure, or 0.
* @filep: Output parameter; on success, this is populated with a pointer
* to the newly retrieved 'struct file'.
*
@@ -566,9 +613,11 @@ void luo_file_unfreeze(struct luo_file_set *file_set,
* kernel.
* Return: 0 on success. Returns a negative errno on failure:
* -ENOENT if no file with the matching token is found.
+ * -EOPNOTSUPP if @arg is non-zero and the handler does not accept an arg.
+ * -EBUSY if @arg is non-zero and the file was already retrieved.
* Any error code returned by the handler's .retrieve() op.
*/
-int luo_retrieve_file(struct luo_file_set *file_set, u64 token,
+int luo_retrieve_file(struct luo_file_set *file_set, u64 token, u64 arg,
struct file **filep)
{
struct liveupdate_file_op_args args = {0};
@@ -596,6 +645,13 @@ int luo_retrieve_file(struct luo_file_set *file_set, u64 token,
}
if (luo_file->retrieve_status > 0) {
+ /*
+ * The argument only applies to the retrieve that restores the
+ * file. Don't silently ignore it.
+ */
+ if (arg)
+ return -EBUSY;
+
/*
* Someone is asking for this file again, so get a reference
* for them.
@@ -605,10 +661,15 @@ int luo_retrieve_file(struct luo_file_set *file_set, u64 token,
return 0;
}
+ err = luo_copy_user_arg(arg, &args.arg, &luo_file->fh->ops->rarg);
+ if (err)
+ return err;
+
args.handler = luo_file->fh;
args.session = luo_session_from_file_set(file_set);
args.serialized_data = luo_file->serialized_data;
err = luo_file->fh->ops->retrieve(&args);
+ kfree(args.arg);
if (err) {
/* Keep the error code for later use. */
luo_file->retrieve_status = err;
@@ -1007,6 +1068,6 @@ int liveupdate_get_file_incoming(struct liveupdate_session *s, u64 token,
struct file **filep)
{
return luo_retrieve_file(luo_file_set_from_session_locked(s),
- token, filep);
+ token, 0, filep);
}
EXPORT_SYMBOL_GPL(liveupdate_get_file_incoming);
diff --git a/kernel/liveupdate/luo_internal.h b/kernel/liveupdate/luo_internal.h
index dac6644bfb18..4126b71e9a2e 100644
--- a/kernel/liveupdate/luo_internal.h
+++ b/kernel/liveupdate/luo_internal.h
@@ -101,13 +101,14 @@ int __init luo_session_setup_incoming(u64 sessions_pa);
int luo_session_serialize(void);
int luo_session_deserialize(void);
-int luo_preserve_file(struct luo_file_set *file_set, u64 token, int fd);
+int luo_preserve_file(struct luo_file_set *file_set, u64 token, int fd,
+ u64 arg);
void luo_file_unpreserve_files(struct luo_file_set *file_set);
int luo_file_freeze(struct luo_file_set *file_set,
struct luo_file_set_ser *file_set_ser);
void luo_file_unfreeze(struct luo_file_set *file_set,
struct luo_file_set_ser *file_set_ser);
-int luo_retrieve_file(struct luo_file_set *file_set, u64 token,
+int luo_retrieve_file(struct luo_file_set *file_set, u64 token, u64 arg,
struct file **filep);
int luo_file_finish(struct luo_file_set *file_set);
int luo_file_deserialize(struct luo_file_set *file_set,
diff --git a/kernel/liveupdate/luo_session.c b/kernel/liveupdate/luo_session.c
index f48e9a4185f9..ed9d53c7db77 100644
--- a/kernel/liveupdate/luo_session.c
+++ b/kernel/liveupdate/luo_session.c
@@ -267,7 +267,8 @@ static int luo_session_preserve_fd(struct luo_session *session,
int err;
guard(mutex)(&session->mutex);
- err = luo_preserve_file(&session->file_set, argp->token, argp->fd);
+ err = luo_preserve_file(&session->file_set, argp->token, argp->fd,
+ argp->arg);
if (err)
return err;
@@ -290,7 +291,8 @@ static int luo_session_retrieve_fd(struct luo_session *session,
return argp->fd;
mutex_lock(&session->mutex);
- err = luo_retrieve_file(&session->file_set, argp->token, &file);
+ err = luo_retrieve_file(&session->file_set, argp->token, argp->arg,
+ &file);
mutex_unlock(&session->mutex);
if (err < 0)
goto err_put_fd;
--
2.56.0.257.gf7497c7d27-goog
^ permalink raw reply related [flat|nested] 20+ messages in thread* Re: [PATCH v2 01/10] liveupdate: Pass user argument to file preserve and retrieve
2026-10-22 22:50 ` [PATCH v2 01/10] liveupdate: Pass user argument to file preserve and retrieve David Matlack
@ 2026-10-23 22:51 ` sashiko-bot
0 siblings, 0 replies; 20+ messages in thread
From: sashiko-bot @ 2026-10-23 22:51 UTC (permalink / raw)
To: David Matlack, kexec
Cc: Alexander Graf, Andrew Morton, Baolin Wang, Christian Brauner,
David Hildenbrand, Hugh Dickins, Jonathan Corbet, Liam R. Howlett,
Lorenzo Stoakes, Michal Hocko, Mike Rapoport, Pasha Tatashin,
Pratyush Yadav, Randy Dunlap, Samiullah Khawaja, Shuah Khan,
Suren Baghdasaryan, Vlastimil Babka
> From: Pasha Tatashin <pasha.tatashin@soleen.com>
>
> Allow userspace to pass a file-handler-specific argument structure when
> preserving or retrieving an FD via LUO. File handlers may need
> additional context during preservation or retrieval, such as a target
> FD, device location, or handler-specific parameters, without introducing
> separate ioctls for each file type. Whether the argument is required,
> optional, or unused is defined by the file handler.
>
> Extend struct liveupdate_session_preserve_fd and
> struct liveupdate_session_retrieve_fd with a 64-bit arg field pointing
> to a handler-defined argument structure that begins with a 32-bit size
> field. Because IOCTL_OP validates a minimum size up to the token field,
> existing callers passing the 16-byte struct remain compatible and have
> arg zero-filled by copy_struct_from_user().
> [ ... ]
> so fail with -EBUSY if a non-zero argument is passed then, instead of
> silently ignoring it.
>
> Signed-off-by: Pasha Tatashin <pasha.tatashin@soleen.com>
> Signed-off-by: David Matlack <dmatlack@google.com>
Sashiko has reviewed this patch and found no issues.
--
Sashiko AI review · https://sashiko.dev/#/patchset/20261022225035.377445-1-dmatlack@google.com?part=1
^ permalink raw reply [flat|nested] 20+ messages in thread
* [PATCH v2 02/10] selftests: liveupdate: Add tests for preserve and retrieve user argument
2026-10-22 22:50 [PATCH v2 00/10] liveupdate: Preserve tmpfs files across Live Update David Matlack
2026-10-22 22:50 ` [PATCH v2 01/10] liveupdate: Pass user argument to file preserve and retrieve David Matlack
@ 2026-10-22 22:50 ` David Matlack
2026-10-23 22:51 ` sashiko-bot
2026-10-22 22:50 ` [PATCH v2 03/10] liveupdate: Allow file handlers to mark retrieve failures as retryable David Matlack
` (7 subsequent siblings)
9 siblings, 1 reply; 20+ messages in thread
From: David Matlack @ 2026-10-22 22:50 UTC (permalink / raw)
To: kexec, linux-doc, linux-kernel, linux-kselftest, linux-mm
Cc: Alexander Graf, Andrew Morton, Baolin Wang, Christian Brauner,
David Hildenbrand, Hugh Dickins, Jonathan Corbet, Liam R. Howlett,
Lorenzo Stoakes, Michal Hocko, Mike Rapoport, Pasha Tatashin,
Pratyush Yadav, Randy Dunlap, Samiullah Khawaja, Shuah Khan,
Suren Baghdasaryan, Vlastimil Babka, David Matlack
From: Pasha Tatashin <pasha.tatashin@soleen.com>
Verify the user argument support in LIVEUPDATE_SESSION_PRESERVE_FD and
LIVEUPDATE_SESSION_RETRIEVE_FD. Add luo_session_preserve_fd_arg() and
luo_session_retrieve_fd_arg() helpers to libliveupdate so tests can pass
a handler-specific argument.
Add a preserve_memfd_with_arg test case to verify that memfd
preservation rejects a non-zero argument with -EOPNOTSUPP, accepts an
explicit zero argument, and remains compatible with callers passing the
legacy struct size up to the token field.
Signed-off-by: Pasha Tatashin <pasha.tatashin@soleen.com>
Signed-off-by: David Matlack <dmatlack@google.com>
---
.../liveupdate/lib/include/libliveupdate.h | 3 ++
.../selftests/liveupdate/lib/lu_utils.c | 17 ++++++-
.../testing/selftests/liveupdate/liveupdate.c | 44 +++++++++++++++++++
3 files changed, 62 insertions(+), 2 deletions(-)
diff --git a/tools/testing/selftests/liveupdate/lib/include/libliveupdate.h b/tools/testing/selftests/liveupdate/lib/include/libliveupdate.h
index fa07fed08364..cd1d843a0d3d 100644
--- a/tools/testing/selftests/liveupdate/lib/include/libliveupdate.h
+++ b/tools/testing/selftests/liveupdate/lib/include/libliveupdate.h
@@ -29,7 +29,10 @@ int luo_get_session_name(int session_fd, char *name, size_t name_len);
int luo_ensure_nofile_limit(long min_limit);
int luo_session_preserve_fd(int session_fd, int fd, __u64 token);
+int luo_session_preserve_fd_arg(int session_fd, int fd, __u64 token,
+ __u64 user_arg);
int luo_session_retrieve_fd(int session_fd, __u64 token);
+int luo_session_retrieve_fd_arg(int session_fd, __u64 token, __u64 user_arg);
int create_and_preserve_memfd(int session_fd, int token, const char *data);
int restore_and_verify_memfd(int session_fd, int token, const char *expected_data);
diff --git a/tools/testing/selftests/liveupdate/lib/lu_utils.c b/tools/testing/selftests/liveupdate/lib/lu_utils.c
index 74d41115c281..db7a90b002cb 100644
--- a/tools/testing/selftests/liveupdate/lib/lu_utils.c
+++ b/tools/testing/selftests/liveupdate/lib/lu_utils.c
@@ -79,12 +79,14 @@ int luo_retrieve_session(int luo_fd, const char *name)
return arg.fd;
}
-int luo_session_preserve_fd(int session_fd, int fd, __u64 token)
+int luo_session_preserve_fd_arg(int session_fd, int fd, __u64 token,
+ __u64 user_arg)
{
struct liveupdate_session_preserve_fd arg = {
.size = sizeof(arg),
.fd = fd,
.token = token,
+ .arg = user_arg,
};
if (ioctl(session_fd, LIVEUPDATE_SESSION_PRESERVE_FD, &arg))
@@ -93,11 +95,17 @@ int luo_session_preserve_fd(int session_fd, int fd, __u64 token)
return 0;
}
-int luo_session_retrieve_fd(int session_fd, __u64 token)
+int luo_session_preserve_fd(int session_fd, int fd, __u64 token)
+{
+ return luo_session_preserve_fd_arg(session_fd, fd, token, 0);
+}
+
+int luo_session_retrieve_fd_arg(int session_fd, __u64 token, __u64 user_arg)
{
struct liveupdate_session_retrieve_fd arg = {
.size = sizeof(arg),
.token = token,
+ .arg = user_arg,
};
if (ioctl(session_fd, LIVEUPDATE_SESSION_RETRIEVE_FD, &arg))
@@ -106,6 +114,11 @@ int luo_session_retrieve_fd(int session_fd, __u64 token)
return arg.fd;
}
+int luo_session_retrieve_fd(int session_fd, __u64 token)
+{
+ return luo_session_retrieve_fd_arg(session_fd, token, 0);
+}
+
/* Helper function to get a session name via ioctl. */
int luo_get_session_name(int session_fd, char *name, size_t name_len)
{
diff --git a/tools/testing/selftests/liveupdate/liveupdate.c b/tools/testing/selftests/liveupdate/liveupdate.c
index 2dedd5fc2534..4c4272167635 100644
--- a/tools/testing/selftests/liveupdate/liveupdate.c
+++ b/tools/testing/selftests/liveupdate/liveupdate.c
@@ -20,6 +20,7 @@
#include <errno.h>
#include <fcntl.h>
+#include <stddef.h>
#include <string.h>
#include <sys/ioctl.h>
#include <unistd.h>
@@ -530,4 +531,47 @@ TEST_F(liveupdate_device, preserve_many_files)
ASSERT_EQ(close(session_fd), 0);
}
+/*
+ * Test Case: Preserve MemFD with User Argument
+ *
+ * Verifies that passing a non-zero user argument to memfd preservation fails
+ * with -EOPNOTSUPP, while passing a zero argument or using the minimum struct
+ * size without the arg field succeeds.
+ */
+TEST_F(liveupdate_device, preserve_memfd_with_arg)
+{
+ struct liveupdate_session_preserve_fd min_arg = {};
+ int session_fd, mem_fd1, mem_fd2;
+
+ self->fd1 = open(LIVEUPDATE_DEV, O_RDWR);
+ if (self->fd1 < 0 && errno == ENOENT)
+ SKIP(return, "%s does not exist", LIVEUPDATE_DEV);
+ ASSERT_GE(self->fd1, 0);
+
+ session_fd = luo_create_session(self->fd1, "preserve-arg-test");
+ ASSERT_GE(session_fd, 0);
+
+ mem_fd1 = memfd_create("arg-memfd-1", 0);
+ ASSERT_GE(mem_fd1, 0);
+
+ /* Non-zero arg is not supported by memfd handler */
+ EXPECT_EQ(luo_session_preserve_fd_arg(session_fd, mem_fd1, 0x1111, 1),
+ -EOPNOTSUPP);
+
+ /* Legacy struct size (up to token) should zero-fill arg and succeed */
+ min_arg.size = offsetof(struct liveupdate_session_preserve_fd, arg);
+ min_arg.fd = mem_fd1;
+ min_arg.token = 0x1111;
+ ASSERT_EQ(ioctl(session_fd, LIVEUPDATE_SESSION_PRESERVE_FD, &min_arg), 0);
+
+ /* Explicit zero arg with full struct size should also succeed */
+ mem_fd2 = memfd_create("arg-memfd-2", 0);
+ ASSERT_GE(mem_fd2, 0);
+ ASSERT_EQ(luo_session_preserve_fd_arg(session_fd, mem_fd2, 0x2222, 0), 0);
+
+ ASSERT_EQ(close(session_fd), 0);
+ ASSERT_EQ(close(mem_fd1), 0);
+ ASSERT_EQ(close(mem_fd2), 0);
+}
+
TEST_HARNESS_MAIN
--
2.56.0.257.gf7497c7d27-goog
^ permalink raw reply related [flat|nested] 20+ messages in thread* Re: [PATCH v2 02/10] selftests: liveupdate: Add tests for preserve and retrieve user argument
2026-10-22 22:50 ` [PATCH v2 02/10] selftests: liveupdate: Add tests for preserve and retrieve user argument David Matlack
@ 2026-10-23 22:51 ` sashiko-bot
0 siblings, 0 replies; 20+ messages in thread
From: sashiko-bot @ 2026-10-23 22:51 UTC (permalink / raw)
To: David Matlack, kexec
Cc: Alexander Graf, Andrew Morton, Baolin Wang, Christian Brauner,
David Hildenbrand, Hugh Dickins, Jonathan Corbet, Liam R. Howlett,
Lorenzo Stoakes, Michal Hocko, Mike Rapoport, Pasha Tatashin,
Pratyush Yadav, Randy Dunlap, Samiullah Khawaja, Shuah Khan,
Suren Baghdasaryan, Vlastimil Babka
> From: Pasha Tatashin <pasha.tatashin@soleen.com>
>
> Verify the user argument support in LIVEUPDATE_SESSION_PRESERVE_FD and
> LIVEUPDATE_SESSION_RETRIEVE_FD. Add luo_session_preserve_fd_arg() and
> luo_session_retrieve_fd_arg() helpers to libliveupdate so tests can pass
> a handler-specific argument.
>
> Add a preserve_memfd_with_arg test case to verify that memfd
> preservation rejects a non-zero argument with -EOPNOTSUPP, accepts an
> explicit zero argument, and remains compatible with callers passing the
> legacy struct size up to the token field.
>
> Signed-off-by: Pasha Tatashin <pasha.tatashin@soleen.com>
> Signed-off-by: David Matlack <dmatlack@google.com>
Sashiko has reviewed this patch and found no issues.
--
Sashiko AI review · https://sashiko.dev/#/patchset/20261022225035.377445-1-dmatlack@google.com?part=2
^ permalink raw reply [flat|nested] 20+ messages in thread
* [PATCH v2 03/10] liveupdate: Allow file handlers to mark retrieve failures as retryable
2026-10-22 22:50 [PATCH v2 00/10] liveupdate: Preserve tmpfs files across Live Update David Matlack
2026-10-22 22:50 ` [PATCH v2 01/10] liveupdate: Pass user argument to file preserve and retrieve David Matlack
2026-10-22 22:50 ` [PATCH v2 02/10] selftests: liveupdate: Add tests for preserve and retrieve user argument David Matlack
@ 2026-10-22 22:50 ` David Matlack
2026-10-23 22:51 ` sashiko-bot
2026-10-22 22:50 ` [PATCH v2 04/10] mm: Rename memfd_luo to shmem_luo David Matlack
` (6 subsequent siblings)
9 siblings, 1 reply; 20+ messages in thread
From: David Matlack @ 2026-10-22 22:50 UTC (permalink / raw)
To: kexec, linux-doc, linux-kernel, linux-kselftest, linux-mm
Cc: Alexander Graf, Andrew Morton, Baolin Wang, Christian Brauner,
David Hildenbrand, Hugh Dickins, Jonathan Corbet, Liam R. Howlett,
Lorenzo Stoakes, Michal Hocko, Mike Rapoport, Pasha Tatashin,
Pratyush Yadav, Randy Dunlap, Samiullah Khawaja, Shuah Khan,
Suren Baghdasaryan, Vlastimil Babka, David Matlack
From: Pasha Tatashin <pasha.tatashin@soleen.com>
When luo_retrieve_file() invokes a handler's .retrieve() callback and it
returns an error, LUO currently records the error in
luo_file->retrieve_status and blocks any subsequent retrieve attempts.
This is necessary when the handler has already partially consumed or
freed the preserved KHO state.
However, with user arguments passed to .retrieve(), a handler may fail
early while validating caller-supplied parameters (such as a missing
argument, an invalid or read-only target file descriptor, or a non-empty
target file) before touching any preserved KHO state. In such cases,
userspace should be able to fix the argument and retry
LIVEUPDATE_SESSION_RETRIEVE_FD, and if the file is never retrieved,
.finish() must still run to release the preserved KHO state.
Add a retryable output flag to struct liveupdate_file_op_args. When a
handler sets args->retryable = true on error, luo_retrieve_file() leaves
luo_file->retrieve_status at 0 so the retrieve operation can be retried
and .finish() will still clean up the unconsumed preserved state if the
session is finished without retrieving the file.
Signed-off-by: Pasha Tatashin <pasha.tatashin@soleen.com>
Signed-off-by: David Matlack <dmatlack@google.com>
---
include/linux/liveupdate.h | 11 ++++++++---
include/uapi/linux/liveupdate.h | 7 ++++---
kernel/liveupdate/luo_file.c | 20 ++++++++++++++------
3 files changed, 26 insertions(+), 12 deletions(-)
diff --git a/include/linux/liveupdate.h b/include/linux/liveupdate.h
index 7543f5d648d3..e4ad0bf78a5c 100644
--- a/include/linux/liveupdate.h
+++ b/include/linux/liveupdate.h
@@ -58,9 +58,13 @@ struct liveupdate_arg_spec {
* @session: The session this file belongs to.
* @retrieve_status: The retrieve status for the 'can_finish / finish'
* operation. A value of 0 means the retrieve has not been
- * attempted, a positive value means the retrieve was
- * successful, and a negative value means the retrieve failed,
- * and the value is the error code of the call.
+ * attempted (or only failed with a retryable error), a
+ * positive value means the retrieve was successful, and a
+ * negative value means the retrieve permanently failed, and
+ * the value is the error code of the call.
+ * @retryable: For .retrieve(): [OUT] Set to true by the callback on
+ * error when the preserved state has not been consumed and
+ * retrieval may be retried.
* @file: The file object. For retrieve: [OUT] The callback sets
* this to the new file. For other ops: [IN] The caller sets
* this to the file being operated on.
@@ -83,6 +87,7 @@ struct liveupdate_file_op_args {
struct liveupdate_file_handler *handler;
struct liveupdate_session *session;
int retrieve_status;
+ bool retryable;
struct file *file;
u64 serialized_data;
void *private_data;
diff --git a/include/uapi/linux/liveupdate.h b/include/uapi/linux/liveupdate.h
index e806752c2fc6..953ffe0823be 100644
--- a/include/uapi/linux/liveupdate.h
+++ b/include/uapi/linux/liveupdate.h
@@ -174,9 +174,10 @@ struct liveupdate_session_preserve_fd {
* associated with the token and populates the @fd field with a new file
* descriptor referencing the restored resource in the current (new) kernel.
* This operation must be performed *before* signaling completion via
- * %LIVEUPDATE_IOCTL_FINISH. If a retrieve of a token fails, subsequent
- * attempts to retrieve the token fail with the same error code. Failed
- * retrieves are not retried.
+ * %LIVEUPDATE_IOCTL_FINISH. If a retrieve of a token fails after consuming
+ * preserved state, subsequent attempts to retrieve the token fail with the
+ * same error code. Early argument or target validation errors before
+ * preserved state is consumed leave the token intact and may be retried.
*
* Retrieving a token that was already retrieved successfully returns a new file
* descriptor for the same file. The @arg only applies to the retrieve that
diff --git a/kernel/liveupdate/luo_file.c b/kernel/liveupdate/luo_file.c
index eeeb3b5ff2fa..908ae124ea5e 100644
--- a/kernel/liveupdate/luo_file.c
+++ b/kernel/liveupdate/luo_file.c
@@ -152,9 +152,10 @@ static DEFINE_XARRAY(luo_preserved_files);
* @retrieve_status: Status code indicating whether a user/kernel in the new kernel has
* successfully called retrieve() on this file. This prevents
* multiple retrieval attempts. A value of 0 means a retrieve()
- * has not been attempted, a positive value means the retrieve()
- * was successful, and a negative value means the retrieve()
- * failed, and the value is the error code of the call.
+ * has not been attempted (or only failed with a retryable
+ * error), a positive value means the retrieve() was successful,
+ * and a negative value means the retrieve() permanently failed,
+ * and the value is the error code of the call.
* @mutex: A mutex that protects the fields of this specific instance
* (e.g., @retrieved, @file), ensuring that operations like
* retrieving or finishing a file are atomic.
@@ -604,7 +605,9 @@ void luo_file_unfreeze(struct luo_file_set *file_set,
* The operation is idempotent: if a file has already been successfully
* retrieved, this function will simply return a pointer to the existing
* 'struct file' and report success without re-executing the retrieve
- * operation. This is handled by checking the 'retrieved' flag under a lock.
+ * operation. If a previous retrieve failed permanently, it returns that
+ * saved error code; if a previous retrieve failed with a retryable error
+ * before consuming preserved state, the retrieve operation is re-attempted.
*
* File retrieval can happen in any order; it is not bound by the order of
* preservation.
@@ -671,8 +674,13 @@ int luo_retrieve_file(struct luo_file_set *file_set, u64 token, u64 arg,
err = luo_file->fh->ops->retrieve(&args);
kfree(args.arg);
if (err) {
- /* Keep the error code for later use. */
- luo_file->retrieve_status = err;
+ /*
+ * Keep the error code for later use unless the handler marked
+ * the failure as retryable (e.g. invalid user argument before
+ * preserved state was consumed).
+ */
+ if (!args.retryable)
+ luo_file->retrieve_status = err;
return err;
}
--
2.56.0.257.gf7497c7d27-goog
^ permalink raw reply related [flat|nested] 20+ messages in thread* Re: [PATCH v2 03/10] liveupdate: Allow file handlers to mark retrieve failures as retryable
2026-10-22 22:50 ` [PATCH v2 03/10] liveupdate: Allow file handlers to mark retrieve failures as retryable David Matlack
@ 2026-10-23 22:51 ` sashiko-bot
0 siblings, 0 replies; 20+ messages in thread
From: sashiko-bot @ 2026-10-23 22:51 UTC (permalink / raw)
To: David Matlack, kexec
Cc: Alexander Graf, Andrew Morton, Baolin Wang, Christian Brauner,
David Hildenbrand, Hugh Dickins, Jonathan Corbet, Liam R. Howlett,
Lorenzo Stoakes, Michal Hocko, Mike Rapoport, Pasha Tatashin,
Pratyush Yadav, Randy Dunlap, Samiullah Khawaja, Shuah Khan,
Suren Baghdasaryan, Vlastimil Babka
> From: Pasha Tatashin <pasha.tatashin@soleen.com>
>
> When luo_retrieve_file() invokes a handler's .retrieve() callback and it
> returns an error, LUO currently records the error in
> luo_file->retrieve_status and blocks any subsequent retrieve attempts.
> This is necessary when the handler has already partially consumed or
> freed the preserved KHO state.
>
> However, with user arguments passed to .retrieve(), a handler may fail
> early while validating caller-supplied parameters (such as a missing
> argument, an invalid or read-only target file descriptor, or a non-empty
> target file) before touching any preserved KHO state. In such cases,
> userspace should be able to fix the argument and retry
> LIVEUPDATE_SESSION_RETRIEVE_FD, and if the file is never retrieved,
> .finish() must still run to release the preserved KHO state.
>
> Add a retryable output flag to struct liveupdate_file_op_args. When a
> handler sets args->retryable = true on error, luo_retrieve_file() leaves
> luo_file->retrieve_status at 0 so the retrieve operation can be retried
> and .finish() will still clean up the unconsumed preserved state if the
> session is finished without retrieving the file.
>
> Signed-off-by: Pasha Tatashin <pasha.tatashin@soleen.com>
> Signed-off-by: David Matlack <dmatlack@google.com>
Sashiko has reviewed this patch and found no issues.
--
Sashiko AI review · https://sashiko.dev/#/patchset/20261022225035.377445-1-dmatlack@google.com?part=3
^ permalink raw reply [flat|nested] 20+ messages in thread
* [PATCH v2 04/10] mm: Rename memfd_luo to shmem_luo
2026-10-22 22:50 [PATCH v2 00/10] liveupdate: Preserve tmpfs files across Live Update David Matlack
` (2 preceding siblings ...)
2026-10-22 22:50 ` [PATCH v2 03/10] liveupdate: Allow file handlers to mark retrieve failures as retryable David Matlack
@ 2026-10-22 22:50 ` David Matlack
2026-10-23 22:51 ` sashiko-bot
2026-10-22 22:50 ` [PATCH v2 05/10] liveupdate: Don't WARN if tracking a retrieved file fails David Matlack
` (5 subsequent siblings)
9 siblings, 1 reply; 20+ messages in thread
From: David Matlack @ 2026-10-22 22:50 UTC (permalink / raw)
To: kexec, linux-doc, linux-kernel, linux-kselftest, linux-mm
Cc: Alexander Graf, Andrew Morton, Baolin Wang, Christian Brauner,
David Hildenbrand, Hugh Dickins, Jonathan Corbet, Liam R. Howlett,
Lorenzo Stoakes, Michal Hocko, Mike Rapoport, Pasha Tatashin,
Pratyush Yadav, Randy Dunlap, Samiullah Khawaja, Shuah Khan,
Suren Baghdasaryan, Vlastimil Babka, David Matlack
Rename the memfd Live Update file handler from memfd_luo to shmem_luo,
including the source file, its KHO ABI header, its documentation and its
Kconfig option (CONFIG_LIVEUPDATE_MEMFD to CONFIG_LIVEUPDATE_SHMEM).
The handler preserves shmem files. Today those are memfds, but following
commits extend it to other files on tmpfs mounts, for which the memfd
name is confusing. Preserving memfds created with MFD_HUGETLB, which are
hugetlbfs files, will need a separate handler, so naming the handler
after the filesystem that backs the files also avoids having to rename
it again then.
Don't change the compatible string of the handler, "memfd-v2". It is
matched against the preserved state from the previous kernel, so
changing it would break live updates from kernels that use the old
name. The serialized format doesn't change either.
Also clarify in the documentation that memfd means a memfd created
without MFD_HUGETLB, and name the ABI documentation after the handler.
No functional change intended.
Assisted-by: LLM
Signed-off-by: David Matlack <dmatlack@google.com>
---
Documentation/core-api/liveupdate.rst | 2 +-
Documentation/mm/index.rst | 2 +-
...reservation.rst => shmem_preservation.rst} | 8 +-
MAINTAINERS | 4 +-
include/linux/kho/abi/{memfd.h => shmem.h} | 41 ++++---
kernel/liveupdate/Kconfig | 2 +-
mm/Makefile | 2 +-
mm/{memfd_luo.c => shmem_luo.c} | 115 +++++++++---------
8 files changed, 89 insertions(+), 87 deletions(-)
rename Documentation/mm/{memfd_preservation.rst => shmem_preservation.rst} (66%)
rename include/linux/kho/abi/{memfd.h => shmem.h} (65%)
rename mm/{memfd_luo.c => shmem_luo.c} (81%)
diff --git a/Documentation/core-api/liveupdate.rst b/Documentation/core-api/liveupdate.rst
index 2bce2644eba2..669722002440 100644
--- a/Documentation/core-api/liveupdate.rst
+++ b/Documentation/core-api/liveupdate.rst
@@ -37,7 +37,7 @@ The following types of file descriptors can be preserved
.. toctree::
:maxdepth: 1
- ../mm/memfd_preservation
+ ../mm/shmem_preservation
Public API
==========
diff --git a/Documentation/mm/index.rst b/Documentation/mm/index.rst
index 13a79f5d092c..b8ab2f89002d 100644
--- a/Documentation/mm/index.rst
+++ b/Documentation/mm/index.rst
@@ -61,7 +61,7 @@ documentation, or deleted if it has served its purpose.
hugetlbfs_reserv
ksm
memory-model
- memfd_preservation
+ shmem_preservation
mmu_notifier
multigen_lru
numa
diff --git a/Documentation/mm/memfd_preservation.rst b/Documentation/mm/shmem_preservation.rst
similarity index 66%
rename from Documentation/mm/memfd_preservation.rst
rename to Documentation/mm/shmem_preservation.rst
index c908a12dffa7..e79bf8afaa9e 100644
--- a/Documentation/mm/memfd_preservation.rst
+++ b/Documentation/mm/shmem_preservation.rst
@@ -4,16 +4,16 @@
Memfd Preservation via LUO
==========================
-.. kernel-doc:: mm/memfd_luo.c
+.. kernel-doc:: mm/shmem_luo.c
:doc: Memfd Preservation via LUO
Memfd Preservation ABI
======================
-.. kernel-doc:: include/linux/kho/abi/memfd.h
- :doc: memfd Live Update ABI
+.. kernel-doc:: include/linux/kho/abi/shmem.h
+ :doc: shmem Live Update ABI
-.. kernel-doc:: include/linux/kho/abi/memfd.h
+.. kernel-doc:: include/linux/kho/abi/shmem.h
:internal:
See Also
diff --git a/MAINTAINERS b/MAINTAINERS
index 5d1b58271989..742cdce14cf9 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -15184,14 +15184,14 @@ L: linux-kernel@vger.kernel.org
S: Maintained
T: git git://git.kernel.org/pub/scm/linux/kernel/git/liveupdate/linux.git
F: Documentation/core-api/liveupdate.rst
-F: Documentation/mm/memfd_preservation.rst
+F: Documentation/mm/shmem_preservation.rst
F: Documentation/userspace-api/liveupdate.rst
F: include/linux/kho/abi/
F: include/linux/liveupdate.h
F: include/uapi/linux/liveupdate.h
F: kernel/liveupdate/
F: lib/tests/liveupdate.c
-F: mm/memfd_luo.c
+F: mm/shmem_luo.c
F: tools/testing/selftests/liveupdate/
LLC (802.2)
diff --git a/include/linux/kho/abi/memfd.h b/include/linux/kho/abi/shmem.h
similarity index 65%
rename from include/linux/kho/abi/memfd.h
rename to include/linux/kho/abi/shmem.h
index 08b10fea2afc..b61687e358a2 100644
--- a/include/linux/kho/abi/memfd.h
+++ b/include/linux/kho/abi/shmem.h
@@ -8,49 +8,50 @@
* Pratyush Yadav <ptyadav@amazon.de>
*/
-#ifndef _LINUX_KHO_ABI_MEMFD_H
-#define _LINUX_KHO_ABI_MEMFD_H
+#ifndef _LINUX_KHO_ABI_SHMEM_H
+#define _LINUX_KHO_ABI_SHMEM_H
#include <linux/types.h>
#include <linux/kho/abi/kexec_handover.h>
/**
- * DOC: memfd Live Update ABI
+ * DOC: shmem Live Update ABI
*
- * memfd uses the ABI defined below for preserving its state across a kexec
- * reboot using the LUO.
+ * The shmem file handler, which preserves memfds created without
+ * ``MFD_HUGETLB``, uses the ABI defined below for preserving their state across
+ * a kexec reboot using the LUO.
*
- * The state is serialized into a packed structure `struct memfd_luo_ser`
+ * The state is serialized into a packed structure `struct shmem_luo_ser`
* which is handed over to the next kernel via the KHO mechanism.
*
* This interface is a contract. Any modification to the structure layout
* constitutes a breaking change. Such changes require incrementing the
- * version number in the MEMFD_LUO_FH_COMPATIBLE string.
+ * version number in the SHMEM_LUO_FH_COMPATIBLE string.
*/
/**
- * MEMFD_LUO_FOLIO_DIRTY - The folio is dirty.
+ * SHMEM_LUO_FOLIO_DIRTY - The folio is dirty.
*
* This flag indicates the folio contains data from user. A non-dirty folio is
* one that was allocated (say using fallocate(2)) but not written to.
*/
-#define MEMFD_LUO_FOLIO_DIRTY BIT(0)
+#define SHMEM_LUO_FOLIO_DIRTY BIT(0)
/**
- * MEMFD_LUO_FOLIO_UPTODATE - The folio is up-to-date.
+ * SHMEM_LUO_FOLIO_UPTODATE - The folio is up-to-date.
*
* An up-to-date folio has been zeroed out. shmem zeroes out folios on first
* use. This flag tracks which folios need zeroing.
*/
-#define MEMFD_LUO_FOLIO_UPTODATE BIT(1)
+#define SHMEM_LUO_FOLIO_UPTODATE BIT(1)
/**
- * struct memfd_luo_folio_ser - Serialized state of a single folio.
+ * struct shmem_luo_folio_ser - Serialized state of a single folio.
* @pfn: The page frame number of the folio.
* @flags: Flags to describe the state of the folio.
* @index: The page offset (pgoff_t) of the folio within the original file.
*/
-struct memfd_luo_folio_ser {
+struct shmem_luo_folio_ser {
u64 pfn:52;
u64 flags:12;
u64 index;
@@ -60,7 +61,7 @@ struct memfd_luo_folio_ser {
* The set of seals this version supports preserving. If support for any new
* seals is needed, add it here and bump version.
*/
-#define MEMFD_LUO_ALL_SEALS (F_SEAL_SEAL | \
+#define SHMEM_LUO_ALL_SEALS (F_SEAL_SEAL | \
F_SEAL_SHRINK | \
F_SEAL_GROW | \
F_SEAL_WRITE | \
@@ -68,7 +69,7 @@ struct memfd_luo_folio_ser {
F_SEAL_EXEC)
/**
- * struct memfd_luo_ser - Main serialization structure for a memfd.
+ * struct shmem_luo_ser - Main serialization structure for a memfd.
* @pos: The file's current position (f_pos).
* @size: The total size of the file in bytes (i_size).
* @seals: The seals present on the memfd. The seals are uABI so it is safe
@@ -76,9 +77,9 @@ struct memfd_luo_folio_ser {
* @flags: Flags for the file. Unused flag bits must be set to 0.
* @nr_folios: Number of folios in the folios array.
* @folios: KHO vmalloc descriptor pointing to the array of
- * struct memfd_luo_folio_ser.
+ * struct shmem_luo_folio_ser.
*/
-struct memfd_luo_ser {
+struct shmem_luo_ser {
u64 pos;
u64 size;
u32 seals;
@@ -87,7 +88,7 @@ struct memfd_luo_ser {
struct kho_vmalloc folios;
} __packed;
-/* The compatibility string for memfd file handler */
-#define MEMFD_LUO_FH_COMPATIBLE "memfd-v2"
+/* The compatibility string for the shmem file handler */
+#define SHMEM_LUO_FH_COMPATIBLE "memfd-v2"
-#endif /* _LINUX_KHO_ABI_MEMFD_H */
+#endif /* _LINUX_KHO_ABI_SHMEM_H */
diff --git a/kernel/liveupdate/Kconfig b/kernel/liveupdate/Kconfig
index f50daee8eff0..9a1c49e2e0c4 100644
--- a/kernel/liveupdate/Kconfig
+++ b/kernel/liveupdate/Kconfig
@@ -69,7 +69,7 @@ config LIVEUPDATE
If unsure, say N.
-config LIVEUPDATE_MEMFD
+config LIVEUPDATE_SHMEM
bool "Live update support for memfd"
depends on LIVEUPDATE
depends on MEMFD_CREATE
diff --git a/mm/Makefile b/mm/Makefile
index e7245cb88c66..cbdcc2c6b296 100644
--- a/mm/Makefile
+++ b/mm/Makefile
@@ -100,7 +100,7 @@ obj-$(CONFIG_NUMA) += memory-tiers.o
obj-$(CONFIG_DEVICE_MIGRATION) += migrate_device.o
obj-$(CONFIG_TRANSPARENT_HUGEPAGE) += huge_memory.o khugepaged.o
obj-$(CONFIG_PAGE_COUNTER) += page_counter.o
-obj-$(CONFIG_LIVEUPDATE_MEMFD) += memfd_luo.o
+obj-$(CONFIG_LIVEUPDATE_SHMEM) += shmem_luo.o
obj-$(CONFIG_MEMCG_V1) += memcontrol-v1.o
obj-$(CONFIG_MEMCG) += memcontrol.o vmpressure.o
ifdef CONFIG_BPF_SYSCALL
diff --git a/mm/memfd_luo.c b/mm/shmem_luo.c
similarity index 81%
rename from mm/memfd_luo.c
rename to mm/shmem_luo.c
index 6b68220275eb..b8d86055912d 100644
--- a/mm/memfd_luo.c
+++ b/mm/shmem_luo.c
@@ -18,6 +18,11 @@
* Update Orchestrator (LUO) file preservation. This allows userspace to
* transfer its memory contents to the next kernel after a kexec.
*
+ * Here, a memfd is a memfd created without ``MFD_HUGETLB``, which is a shmem
+ * file on the kernel's internal shmem mount. Memfds created with
+ * ``MFD_HUGETLB`` are hugetlbfs files, which this file handler doesn't
+ * preserve. Preserving them fails with ``-ENOENT``.
+ *
* The preservation is not intended to be transparent. Only select properties of
* the file are preserved. All others are reset to default. The preserved
* properties are described below.
@@ -26,10 +31,6 @@
* The LUO API is not stabilized yet, so the preserved properties of a memfd
* are also not stable and are subject to backwards incompatible changes.
*
- * .. note::
- * Currently a memfd backed by Hugetlb is not supported. Memfds created
- * with ``MFD_HUGETLB`` will be rejected.
- *
* Preserved Properties
* ====================
*
@@ -52,7 +53,7 @@
*
* Seals
* File seals set on the memfd are preserved and re-applied on restore.
- * Only seals known to this LUO version (see ``MEMFD_LUO_ALL_SEALS``) may
+ * Only seals known to this LUO version (see ``SHMEM_LUO_ALL_SEALS``) may
* be present; preservation fails with ``-EOPNOTSUPP`` otherwise.
*
* Non-Preserved Properties
@@ -75,7 +76,7 @@
#include <linux/file.h>
#include <linux/io.h>
#include <linux/kexec_handover.h>
-#include <linux/kho/abi/memfd.h>
+#include <linux/kho/abi/shmem.h>
#include <linux/liveupdate.h>
#include <linux/shmem_fs.h>
#include <linux/vmalloc.h>
@@ -84,13 +85,13 @@
#include "internal.h"
-static int memfd_luo_preserve_folios(struct file *file,
+static int shmem_luo_preserve_folios(struct file *file,
struct kho_vmalloc *kho_vmalloc,
- struct memfd_luo_folio_ser **out_folios_ser,
+ struct shmem_luo_folio_ser **out_folios_ser,
u64 *nr_foliosp)
{
struct inode *inode = file_inode(file);
- struct memfd_luo_folio_ser *folios_ser;
+ struct shmem_luo_folio_ser *folios_ser;
unsigned int max_folios;
long i, size, nr_pinned;
struct folio **folios;
@@ -145,7 +146,7 @@ static int memfd_luo_preserve_folios(struct file *file,
}
for (i = 0; i < nr_folios; i++) {
- struct memfd_luo_folio_ser *pfolio = &folios_ser[i];
+ struct shmem_luo_folio_ser *pfolio = &folios_ser[i];
struct folio *folio = folios[i];
err = kho_preserve_folio(folio);
@@ -196,7 +197,7 @@ static int memfd_luo_preserve_folios(struct file *file,
folio_unlock(folio);
pfolio->pfn = folio_pfn(folio);
- pfolio->flags = MEMFD_LUO_FOLIO_DIRTY | MEMFD_LUO_FOLIO_UPTODATE;
+ pfolio->flags = SHMEM_LUO_FOLIO_DIRTY | SHMEM_LUO_FOLIO_UPTODATE;
pfolio->index = folio->index;
}
@@ -227,8 +228,8 @@ static int memfd_luo_preserve_folios(struct file *file,
return err;
}
-static void memfd_luo_unpreserve_folios(struct kho_vmalloc *kho_vmalloc,
- struct memfd_luo_folio_ser *folios_ser,
+static void shmem_luo_unpreserve_folios(struct kho_vmalloc *kho_vmalloc,
+ struct shmem_luo_folio_ser *folios_ser,
u64 nr_folios)
{
long i;
@@ -239,7 +240,7 @@ static void memfd_luo_unpreserve_folios(struct kho_vmalloc *kho_vmalloc,
kho_unpreserve_vmalloc(kho_vmalloc);
for (i = 0; i < nr_folios; i++) {
- const struct memfd_luo_folio_ser *pfolio = &folios_ser[i];
+ const struct shmem_luo_folio_ser *pfolio = &folios_ser[i];
struct folio *folio;
if (!pfolio->pfn)
@@ -254,11 +255,11 @@ static void memfd_luo_unpreserve_folios(struct kho_vmalloc *kho_vmalloc,
vfree(folios_ser);
}
-static int memfd_luo_preserve(struct liveupdate_file_op_args *args)
+static int shmem_luo_preserve(struct liveupdate_file_op_args *args)
{
struct inode *inode = file_inode(args->file);
- struct memfd_luo_folio_ser *folios_ser;
- struct memfd_luo_ser *ser;
+ struct shmem_luo_folio_ser *folios_ser;
+ struct shmem_luo_ser *ser;
u64 nr_folios, inode_size;
int err = 0, seals;
@@ -279,7 +280,7 @@ static int memfd_luo_preserve(struct liveupdate_file_op_args *args)
}
/* Make sure the file only has the seals supported by this version. */
- if (seals & ~MEMFD_LUO_ALL_SEALS) {
+ if (seals & ~SHMEM_LUO_ALL_SEALS) {
err = -EOPNOTSUPP;
goto err_free_ser;
}
@@ -299,7 +300,7 @@ static int memfd_luo_preserve(struct liveupdate_file_op_args *args)
ser->size = inode_size;
ser->seals = seals;
- err = memfd_luo_preserve_folios(args->file, &ser->folios,
+ err = shmem_luo_preserve_folios(args->file, &ser->folios,
&folios_ser, &nr_folios);
if (err)
goto err_free_ser;
@@ -320,9 +321,9 @@ static int memfd_luo_preserve(struct liveupdate_file_op_args *args)
return err;
}
-static int memfd_luo_freeze(struct liveupdate_file_op_args *args)
+static int shmem_luo_freeze(struct liveupdate_file_op_args *args)
{
- struct memfd_luo_ser *ser;
+ struct shmem_luo_ser *ser;
if (WARN_ON_ONCE(!args->serialized_data))
return -EINVAL;
@@ -338,10 +339,10 @@ static int memfd_luo_freeze(struct liveupdate_file_op_args *args)
return 0;
}
-static void memfd_luo_unpreserve(struct liveupdate_file_op_args *args)
+static void shmem_luo_unpreserve(struct liveupdate_file_op_args *args)
{
struct inode *inode = file_inode(args->file);
- struct memfd_luo_ser *ser;
+ struct shmem_luo_ser *ser;
if (WARN_ON_ONCE(!args->serialized_data))
return;
@@ -351,20 +352,20 @@ static void memfd_luo_unpreserve(struct liveupdate_file_op_args *args)
ser = phys_to_virt(args->serialized_data);
- memfd_luo_unpreserve_folios(&ser->folios, args->private_data,
+ shmem_luo_unpreserve_folios(&ser->folios, args->private_data,
ser->nr_folios);
kho_unpreserve_free(ser);
inode_unlock(inode);
}
-static void memfd_luo_discard_folios(const struct memfd_luo_folio_ser *folios_ser,
+static void shmem_luo_discard_folios(const struct shmem_luo_folio_ser *folios_ser,
u64 nr_folios)
{
u64 i;
for (i = 0; i < nr_folios; i++) {
- const struct memfd_luo_folio_ser *pfolio = &folios_ser[i];
+ const struct shmem_luo_folio_ser *pfolio = &folios_ser[i];
struct folio *folio;
phys_addr_t phys;
@@ -383,10 +384,10 @@ static void memfd_luo_discard_folios(const struct memfd_luo_folio_ser *folios_se
}
}
-static void memfd_luo_finish(struct liveupdate_file_op_args *args)
+static void shmem_luo_finish(struct liveupdate_file_op_args *args)
{
- struct memfd_luo_folio_ser *folios_ser;
- struct memfd_luo_ser *ser;
+ struct shmem_luo_folio_ser *folios_ser;
+ struct shmem_luo_ser *ser;
/*
* If retrieve was successful, nothing to do. If it failed, retrieve()
@@ -405,7 +406,7 @@ static void memfd_luo_finish(struct liveupdate_file_op_args *args)
if (!folios_ser)
goto out;
- memfd_luo_discard_folios(folios_ser, ser->nr_folios);
+ shmem_luo_discard_folios(folios_ser, ser->nr_folios);
vfree(folios_ser);
}
@@ -413,8 +414,8 @@ static void memfd_luo_finish(struct liveupdate_file_op_args *args)
kho_restore_free(ser);
}
-static int memfd_luo_retrieve_folios(struct file *file,
- struct memfd_luo_folio_ser *folios_ser,
+static int shmem_luo_retrieve_folios(struct file *file,
+ struct shmem_luo_folio_ser *folios_ser,
u64 nr_folios)
{
struct inode *inode = file_inode(file);
@@ -425,7 +426,7 @@ static int memfd_luo_retrieve_folios(struct file *file,
long i;
for (i = 0; i < nr_folios; i++) {
- const struct memfd_luo_folio_ser *pfolio = &folios_ser[i];
+ const struct shmem_luo_folio_ser *pfolio = &folios_ser[i];
phys_addr_t phys;
u64 index;
int flags;
@@ -463,9 +464,9 @@ static int memfd_luo_retrieve_folios(struct file *file,
goto unlock_folio;
}
- if (flags & MEMFD_LUO_FOLIO_UPTODATE)
+ if (flags & SHMEM_LUO_FOLIO_UPTODATE)
folio_mark_uptodate(folio);
- if (flags & MEMFD_LUO_FOLIO_DIRTY)
+ if (flags & SHMEM_LUO_FOLIO_DIRTY)
folio_mark_dirty(folio);
npages = folio_nr_pages(folio);
@@ -497,7 +498,7 @@ static int memfd_luo_retrieve_folios(struct file *file,
* freed when the file is freed. Free the ones not added yet here.
*/
for (long j = i + 1; j < nr_folios; j++) {
- const struct memfd_luo_folio_ser *pfolio = &folios_ser[j];
+ const struct shmem_luo_folio_ser *pfolio = &folios_ser[j];
phys_addr_t phys;
if (!pfolio->pfn)
@@ -514,10 +515,10 @@ static int memfd_luo_retrieve_folios(struct file *file,
return err;
}
-static int memfd_luo_retrieve(struct liveupdate_file_op_args *args)
+static int shmem_luo_retrieve(struct liveupdate_file_op_args *args)
{
- struct memfd_luo_folio_ser *folios_ser;
- struct memfd_luo_ser *ser;
+ struct shmem_luo_folio_ser *folios_ser;
+ struct shmem_luo_ser *ser;
struct file *file;
int err;
@@ -526,7 +527,7 @@ static int memfd_luo_retrieve(struct liveupdate_file_op_args *args)
return -EINVAL;
/* Make sure the file only has seals supported by this version. */
- if (ser->seals & ~MEMFD_LUO_ALL_SEALS) {
+ if (ser->seals & ~SHMEM_LUO_ALL_SEALS) {
err = -EOPNOTSUPP;
goto free_ser;
}
@@ -558,7 +559,7 @@ static int memfd_luo_retrieve(struct liveupdate_file_op_args *args)
goto put_file;
}
- err = memfd_luo_retrieve_folios(file, folios_ser, ser->nr_folios);
+ err = shmem_luo_retrieve_folios(file, folios_ser, ser->nr_folios);
vfree(folios_ser);
if (err)
goto put_file;
@@ -576,7 +577,7 @@ static int memfd_luo_retrieve(struct liveupdate_file_op_args *args)
return err;
}
-static bool memfd_luo_can_preserve(struct liveupdate_file_handler *handler,
+static bool shmem_luo_can_preserve(struct liveupdate_file_handler *handler,
struct file *file)
{
struct inode *inode = file_inode(file);
@@ -584,30 +585,30 @@ static bool memfd_luo_can_preserve(struct liveupdate_file_handler *handler,
return shmem_file(file) && !inode->i_nlink;
}
-static unsigned long memfd_luo_get_id(struct file *file)
+static unsigned long shmem_luo_get_id(struct file *file)
{
return (unsigned long)file_inode(file);
}
-static const struct liveupdate_file_ops memfd_luo_file_ops = {
- .freeze = memfd_luo_freeze,
- .finish = memfd_luo_finish,
- .retrieve = memfd_luo_retrieve,
- .preserve = memfd_luo_preserve,
- .unpreserve = memfd_luo_unpreserve,
- .can_preserve = memfd_luo_can_preserve,
- .get_id = memfd_luo_get_id,
+static const struct liveupdate_file_ops shmem_luo_file_ops = {
+ .freeze = shmem_luo_freeze,
+ .finish = shmem_luo_finish,
+ .retrieve = shmem_luo_retrieve,
+ .preserve = shmem_luo_preserve,
+ .unpreserve = shmem_luo_unpreserve,
+ .can_preserve = shmem_luo_can_preserve,
+ .get_id = shmem_luo_get_id,
.owner = THIS_MODULE,
};
-static struct liveupdate_file_handler memfd_luo_handler = {
- .ops = &memfd_luo_file_ops,
- .compatible = MEMFD_LUO_FH_COMPATIBLE,
+static struct liveupdate_file_handler shmem_luo_handler = {
+ .ops = &shmem_luo_file_ops,
+ .compatible = SHMEM_LUO_FH_COMPATIBLE,
};
-static int __init memfd_luo_init(void)
+static int __init shmem_luo_init(void)
{
- int err = liveupdate_register_file_handler(&memfd_luo_handler);
+ int err = liveupdate_register_file_handler(&shmem_luo_handler);
if (err && err != -EOPNOTSUPP) {
pr_err("Could not register luo filesystem handler: %pe\n",
@@ -618,4 +619,4 @@ static int __init memfd_luo_init(void)
return 0;
}
-late_initcall(memfd_luo_init);
+late_initcall(shmem_luo_init);
--
2.56.0.257.gf7497c7d27-goog
^ permalink raw reply related [flat|nested] 20+ messages in thread* Re: [PATCH v2 04/10] mm: Rename memfd_luo to shmem_luo
2026-10-22 22:50 ` [PATCH v2 04/10] mm: Rename memfd_luo to shmem_luo David Matlack
@ 2026-10-23 22:51 ` sashiko-bot
0 siblings, 0 replies; 20+ messages in thread
From: sashiko-bot @ 2026-10-23 22:51 UTC (permalink / raw)
To: David Matlack, kexec
Cc: Alexander Graf, Andrew Morton, Baolin Wang, Christian Brauner,
David Hildenbrand, Hugh Dickins, Jonathan Corbet, Liam R. Howlett,
Lorenzo Stoakes, Michal Hocko, Mike Rapoport, Pasha Tatashin,
Pratyush Yadav, Randy Dunlap, Samiullah Khawaja, Shuah Khan,
Suren Baghdasaryan, Vlastimil Babka
> Rename the memfd Live Update file handler from memfd_luo to shmem_luo,
> including the source file, its KHO ABI header, its documentation and its
> Kconfig option (CONFIG_LIVEUPDATE_MEMFD to CONFIG_LIVEUPDATE_SHMEM).
>
> The handler preserves shmem files. Today those are memfds, but following
> commits extend it to other files on tmpfs mounts, for which the memfd
> name is confusing. Preserving memfds created with MFD_HUGETLB, which are
> hugetlbfs files, will need a separate handler, so naming the handler
> after the filesystem that backs the files also avoids having to rename
> it again then.
>
> Don't change the compatible string of the handler, "memfd-v2". It is
> matched against the preserved state from the previous kernel, so
> changing it would break live updates from kernels that use the old
> name. The serialized format doesn't change either.
>
> Also clarify in the documentation that memfd means a memfd created
> without MFD_HUGETLB, and name the ABI documentation after the handler.
>
> No functional change intended.
>
> Assisted-by: LLM
> Signed-off-by: David Matlack <dmatlack@google.com>
Sashiko has reviewed this patch and found no issues.
--
Sashiko AI review · https://sashiko.dev/#/patchset/20261022225035.377445-1-dmatlack@google.com?part=4
^ permalink raw reply [flat|nested] 20+ messages in thread
* [PATCH v2 05/10] liveupdate: Don't WARN if tracking a retrieved file fails
2026-10-22 22:50 [PATCH v2 00/10] liveupdate: Preserve tmpfs files across Live Update David Matlack
` (3 preceding siblings ...)
2026-10-22 22:50 ` [PATCH v2 04/10] mm: Rename memfd_luo to shmem_luo David Matlack
@ 2026-10-22 22:50 ` David Matlack
2026-10-23 22:51 ` sashiko-bot
2026-10-22 22:50 ` [PATCH v2 06/10] mm/shmem_luo: Don't make early retrieve failures permanent David Matlack
` (4 subsequent siblings)
9 siblings, 1 reply; 20+ messages in thread
From: David Matlack @ 2026-10-22 22:50 UTC (permalink / raw)
To: kexec, linux-doc, linux-kernel, linux-kselftest, linux-mm
Cc: Alexander Graf, Andrew Morton, Baolin Wang, Christian Brauner,
David Hildenbrand, Hugh Dickins, Jonathan Corbet, Liam R. Howlett,
Lorenzo Stoakes, Michal Hocko, Mike Rapoport, Pasha Tatashin,
Pratyush Yadav, Randy Dunlap, Samiullah Khawaja, Shuah Khan,
Suren Baghdasaryan, Vlastimil Babka, David Matlack
Handle a failure to add a retrieved file to luo_preserved_files instead
of triggering a WARN_ON(). xa_insert() can fail with -ENOMEM, and
userspace can trigger that with LIVEUPDATE_SESSION_RETRIEVE_FD, so
warning about it lets userspace crash systems with panic_on_warn.
The handler has already consumed the preserved state when this happens,
so make the failure permanent. Keep the file until the file set is
finished, like any other retrieved file, so that its resources are not
released before then. Finishing the file set then erases the file from
luo_preserved_files, which does nothing for a file that was never added.
Fixes: 00d0b372374f ("liveupdate: prevent double management of files")
Assisted-by: LLM
Signed-off-by: David Matlack <dmatlack@google.com>
---
kernel/liveupdate/luo_file.c | 19 +++++++++++++++----
1 file changed, 15 insertions(+), 4 deletions(-)
diff --git a/kernel/liveupdate/luo_file.c b/kernel/liveupdate/luo_file.c
index 908ae124ea5e..5a82e45b9062 100644
--- a/kernel/liveupdate/luo_file.c
+++ b/kernel/liveupdate/luo_file.c
@@ -684,14 +684,25 @@ int luo_retrieve_file(struct luo_file_set *file_set, u64 token, u64 arg,
return err;
}
+ err = xa_insert(&luo_preserved_files,
+ luo_get_id(luo_file->fh, args.file), args.file,
+ GFP_KERNEL);
+ if (err) {
+ /*
+ * The handler has consumed the preserved state, so the retrieve
+ * can't be retried. Keep the file until the file set is
+ * finished, so that its resources are released at the same
+ * time as those of other retrieved files.
+ */
+ luo_file->file = args.file;
+ luo_file->retrieve_status = err;
+ return err;
+ }
+
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;
--
2.56.0.257.gf7497c7d27-goog
^ permalink raw reply related [flat|nested] 20+ messages in thread* Re: [PATCH v2 05/10] liveupdate: Don't WARN if tracking a retrieved file fails
2026-10-22 22:50 ` [PATCH v2 05/10] liveupdate: Don't WARN if tracking a retrieved file fails David Matlack
@ 2026-10-23 22:51 ` sashiko-bot
0 siblings, 0 replies; 20+ messages in thread
From: sashiko-bot @ 2026-10-23 22:51 UTC (permalink / raw)
To: David Matlack, kexec
Cc: Alexander Graf, Andrew Morton, Baolin Wang, Christian Brauner,
David Hildenbrand, Hugh Dickins, Jonathan Corbet, Liam R. Howlett,
Lorenzo Stoakes, Michal Hocko, Mike Rapoport, Pasha Tatashin,
Pratyush Yadav, Randy Dunlap, Samiullah Khawaja, Shuah Khan,
Suren Baghdasaryan, Vlastimil Babka
> Handle a failure to add a retrieved file to luo_preserved_files instead
> of triggering a WARN_ON(). xa_insert() can fail with -ENOMEM, and
> userspace can trigger that with LIVEUPDATE_SESSION_RETRIEVE_FD, so
> warning about it lets userspace crash systems with panic_on_warn.
>
> The handler has already consumed the preserved state when this happens,
> so make the failure permanent. Keep the file until the file set is
> finished, like any other retrieved file, so that its resources are not
> released before then. Finishing the file set then erases the file from
> luo_preserved_files, which does nothing for a file that was never added.
>
> Fixes: 00d0b372374f ("liveupdate: prevent double management of files")
> Assisted-by: LLM
> Signed-off-by: David Matlack <dmatlack@google.com>
Sashiko has reviewed this patch and found no issues.
--
Sashiko AI review · https://sashiko.dev/#/patchset/20261022225035.377445-1-dmatlack@google.com?part=5
^ permalink raw reply [flat|nested] 20+ messages in thread
* [PATCH v2 06/10] mm/shmem_luo: Don't make early retrieve failures permanent
2026-10-22 22:50 [PATCH v2 00/10] liveupdate: Preserve tmpfs files across Live Update David Matlack
` (4 preceding siblings ...)
2026-10-22 22:50 ` [PATCH v2 05/10] liveupdate: Don't WARN if tracking a retrieved file fails David Matlack
@ 2026-10-22 22:50 ` David Matlack
2026-10-23 22:51 ` sashiko-bot
2026-10-22 22:50 ` [PATCH v2 07/10] mm/shmem_luo: Retrieve tmpfs files onto a tmpfs mount chosen by userspace David Matlack
` (3 subsequent siblings)
9 siblings, 1 reply; 20+ messages in thread
From: David Matlack @ 2026-10-22 22:50 UTC (permalink / raw)
To: kexec, linux-doc, linux-kernel, linux-kselftest, linux-mm
Cc: Alexander Graf, Andrew Morton, Baolin Wang, Christian Brauner,
David Hildenbrand, Hugh Dickins, Jonathan Corbet, Liam R. Howlett,
Lorenzo Stoakes, Michal Hocko, Mike Rapoport, Pasha Tatashin,
Pratyush Yadav, Randy Dunlap, Samiullah Khawaja, Shuah Khan,
Suren Baghdasaryan, Vlastimil Babka, David Matlack
If shmem_luo_retrieve() fails before it starts restoring the folios,
mark the failure as retryable and return the error without freeing the
serialized state. Nothing has consumed the preserved state at that
point, so the retrieve can be tried again. If no retrieve succeeds,
finish() frees the preserved folios, like it does for a file that was
never retrieved.
This fixes leaking the preserved folios when one of these early steps
fails, e.g. because the file has seals that this kernel doesn't support.
The retrieve freed the serialized state but not the folios, and finish()
then skipped the file because its retrieve had failed.
Assisted-by: LLM
Signed-off-by: David Matlack <dmatlack@google.com>
---
mm/shmem_luo.c | 33 ++++++++++++++++++++-------------
1 file changed, 20 insertions(+), 13 deletions(-)
diff --git a/mm/shmem_luo.c b/mm/shmem_luo.c
index b8d86055912d..bcac1bbb3db3 100644
--- a/mm/shmem_luo.c
+++ b/mm/shmem_luo.c
@@ -390,9 +390,9 @@ static void shmem_luo_finish(struct liveupdate_file_op_args *args)
struct shmem_luo_ser *ser;
/*
- * If retrieve was successful, nothing to do. If it failed, retrieve()
- * already cleaned up everything it could. So nothing to do there
- * either. Only need to clean up when retrieve was not called.
+ * If retrieve was successful, nothing to do. If it failed permanently,
+ * retrieve() already cleaned up everything it could. So nothing to do
+ * there either. Only need to clean up when the file was not retrieved.
*/
if (args->retrieve_status)
return;
@@ -526,11 +526,16 @@ static int shmem_luo_retrieve(struct liveupdate_file_op_args *args)
if (!ser)
return -EINVAL;
+ /*
+ * Nothing has consumed the preserved state until the folios are
+ * restored below, so failures before that can be retried. If no
+ * retrieve succeeds, finish() frees the preserved state.
+ */
+ args->retryable = true;
+
/* Make sure the file only has seals supported by this version. */
- if (ser->seals & ~SHMEM_LUO_ALL_SEALS) {
- err = -EOPNOTSUPP;
- goto free_ser;
- }
+ if (ser->seals & ~SHMEM_LUO_ALL_SEALS)
+ return -EOPNOTSUPP;
/*
* The seals are preserved. Allow sealing here so they can be added
@@ -539,8 +544,7 @@ static int shmem_luo_retrieve(struct liveupdate_file_op_args *args)
file = memfd_alloc_file("", MFD_ALLOW_SEALING);
if (IS_ERR(file)) {
pr_err("failed to setup file: %pe\n", file);
- err = PTR_ERR(file);
- goto free_ser;
+ return PTR_ERR(file);
}
err = memfd_add_seals(file, ser->seals);
@@ -552,17 +556,20 @@ static int shmem_luo_retrieve(struct liveupdate_file_op_args *args)
vfs_setpos(file, ser->pos, MAX_LFS_FILESIZE);
i_size_write(file_inode(file), ser->size);
+ /* The preserved state is consumed from here on. */
+ args->retryable = false;
+
if (ser->nr_folios) {
folios_ser = kho_restore_vmalloc(&ser->folios);
if (!folios_ser) {
err = -EINVAL;
- goto put_file;
+ goto free_ser;
}
err = shmem_luo_retrieve_folios(file, folios_ser, ser->nr_folios);
vfree(folios_ser);
if (err)
- goto put_file;
+ goto free_ser;
}
args->file = file;
@@ -570,10 +577,10 @@ static int shmem_luo_retrieve(struct liveupdate_file_op_args *args)
return 0;
-put_file:
- fput(file);
free_ser:
kho_restore_free(ser);
+put_file:
+ fput(file);
return err;
}
--
2.56.0.257.gf7497c7d27-goog
^ permalink raw reply related [flat|nested] 20+ messages in thread* Re: [PATCH v2 06/10] mm/shmem_luo: Don't make early retrieve failures permanent
2026-10-22 22:50 ` [PATCH v2 06/10] mm/shmem_luo: Don't make early retrieve failures permanent David Matlack
@ 2026-10-23 22:51 ` sashiko-bot
0 siblings, 0 replies; 20+ messages in thread
From: sashiko-bot @ 2026-10-23 22:51 UTC (permalink / raw)
To: David Matlack, kexec
Cc: Alexander Graf, Andrew Morton, Baolin Wang, Christian Brauner,
David Hildenbrand, Hugh Dickins, Jonathan Corbet, Liam R. Howlett,
Lorenzo Stoakes, Michal Hocko, Mike Rapoport, Pasha Tatashin,
Pratyush Yadav, Randy Dunlap, Samiullah Khawaja, Shuah Khan,
Suren Baghdasaryan, Vlastimil Babka
> If shmem_luo_retrieve() fails before it starts restoring the folios,
> mark the failure as retryable and return the error without freeing the
> serialized state. Nothing has consumed the preserved state at that
> point, so the retrieve can be tried again. If no retrieve succeeds,
> finish() frees the preserved folios, like it does for a file that was
> never retrieved.
>
> This fixes leaking the preserved folios when one of these early steps
> fails, e.g. because the file has seals that this kernel doesn't support.
> The retrieve freed the serialized state but not the folios, and finish()
> then skipped the file because its retrieve had failed.
>
> Assisted-by: LLM
> Signed-off-by: David Matlack <dmatlack@google.com>
Sashiko has reviewed this patch and found no issues.
--
Sashiko AI review · https://sashiko.dev/#/patchset/20261022225035.377445-1-dmatlack@google.com?part=6
^ permalink raw reply [flat|nested] 20+ messages in thread
* [PATCH v2 07/10] mm/shmem_luo: Retrieve tmpfs files onto a tmpfs mount chosen by userspace
2026-10-22 22:50 [PATCH v2 00/10] liveupdate: Preserve tmpfs files across Live Update David Matlack
` (5 preceding siblings ...)
2026-10-22 22:50 ` [PATCH v2 06/10] mm/shmem_luo: Don't make early retrieve failures permanent David Matlack
@ 2026-10-22 22:50 ` David Matlack
2026-10-23 22:51 ` sashiko-bot
2026-10-22 22:50 ` [PATCH v2 08/10] mm/shmem_luo: Allow preserving tmpfs files that are still linked David Matlack
` (2 subsequent siblings)
9 siblings, 1 reply; 20+ messages in thread
From: David Matlack @ 2026-10-22 22:50 UTC (permalink / raw)
To: kexec, linux-doc, linux-kernel, linux-kselftest, linux-mm
Cc: Alexander Graf, Andrew Morton, Baolin Wang, Christian Brauner,
David Hildenbrand, Hugh Dickins, Jonathan Corbet, Liam R. Howlett,
Lorenzo Stoakes, Michal Hocko, Mike Rapoport, Pasha Tatashin,
Pratyush Yadav, Randy Dunlap, Samiullah Khawaja, Shuah Khan,
Suren Baghdasaryan, Vlastimil Babka, David Matlack
Retrieve files that were preserved from a tmpfs mount onto a tmpfs mount
chosen by userspace, instead of as memfds on the internal tmpfs mount.
Record which mount a file was on with a new SHMEM_LUO_FLAG_TMPFS flag in
struct shmem_luo_ser, based on the superblock of the file. shmem_luo
already accepts unlinked tmpfs files, such as files created with
O_TMPFILE, and a following commit also accepts linked ones.
The mount a tmpfs file was on doesn't exist in the new kernel, and
userspace may want to put the file elsewhere anyway. So add struct
liveupdate_shmem_retrieve_arg, which userspace passes as the retrieve
argument of LIVEUPDATE_SESSION_RETRIEVE_FD. It is modeled on open(): The
kernel creates a new file as if by openat(dirfd, ".", O_TMPFILE | flags,
mode), so the file gets the owner, permission checks and security labels
of any new file in that directory, and is charged to that mount. The
kernel then fills the file with the preserved contents, size, position
and seals before returning it, so nothing else can access the file
before it is complete. Userspace links the file at the path it wants
with linkat(), or passes O_EXCL to keep it unlinked. Neither the path
nor any other inode attribute of the file is part of the preserved
state, which keeps them out of the KHO ABI.
Pass the flags from userspace to file_open_root(), so that the VFS
checks them like for open(). file_open_root() silently ignores unknown
flags and mode bits, so reject those like openat2() does. Also reject
O_PATH, with which file_open_root() would ignore O_TMPFILE and open the
directory.
Keep the type of the file: memfds can only be retrieved as memfds, and
files on tmpfs mounts only onto tmpfs mounts, so that e.g. a file
can't be moved off the mount whose size limit and quotas it was subject
to. Passing the argument when retrieving a memfd, or not passing it when
retrieving a file from a tmpfs mount, fails with -EINVAL. Unlinked tmpfs
files were retrieved as memfds until now, but nothing relies on that, as
the LUO uAPI is not stable yet.
Like the other failures before the folios are restored, problems with
the argument and with creating the file can be retried.
Assisted-by: LLM
Signed-off-by: David Matlack <dmatlack@google.com>
---
Documentation/mm/shmem_preservation.rst | 8 +-
include/linux/kho/abi/shmem.h | 21 ++-
include/uapi/linux/liveupdate.h | 35 +++++
kernel/liveupdate/Kconfig | 6 +-
mm/internal.h | 1 +
mm/shmem.c | 9 ++
mm/shmem_luo.c | 180 ++++++++++++++++++++----
7 files changed, 221 insertions(+), 39 deletions(-)
diff --git a/Documentation/mm/shmem_preservation.rst b/Documentation/mm/shmem_preservation.rst
index e79bf8afaa9e..030a8f18680c 100644
--- a/Documentation/mm/shmem_preservation.rst
+++ b/Documentation/mm/shmem_preservation.rst
@@ -1,11 +1,11 @@
.. SPDX-License-Identifier: GPL-2.0-or-later
-==========================
-Memfd Preservation via LUO
-==========================
+====================================
+Memfd and Tmpfs Preservation via LUO
+====================================
.. kernel-doc:: mm/shmem_luo.c
- :doc: Memfd Preservation via LUO
+ :doc: Memfd and Tmpfs Preservation via LUO
Memfd Preservation ABI
======================
diff --git a/include/linux/kho/abi/shmem.h b/include/linux/kho/abi/shmem.h
index b61687e358a2..88ef7d49b5f3 100644
--- a/include/linux/kho/abi/shmem.h
+++ b/include/linux/kho/abi/shmem.h
@@ -18,8 +18,8 @@
* DOC: shmem Live Update ABI
*
* The shmem file handler, which preserves memfds created without
- * ``MFD_HUGETLB``, uses the ABI defined below for preserving their state across
- * a kexec reboot using the LUO.
+ * ``MFD_HUGETLB`` and tmpfs files, uses the ABI defined below for preserving
+ * their state across a kexec reboot using the LUO.
*
* The state is serialized into a packed structure `struct shmem_luo_ser`
* which is handed over to the next kernel via the KHO mechanism.
@@ -69,12 +69,25 @@ struct shmem_luo_folio_ser {
F_SEAL_EXEC)
/**
- * struct shmem_luo_ser - Main serialization structure for a memfd.
+ * SHMEM_LUO_FLAG_TMPFS - The file was on a tmpfs mount.
+ *
+ * Set for files that were not on the internal shmem mount that memfds are
+ * created on. Such a file is retrieved onto a tmpfs mount chosen by userspace,
+ * instead of as a memfd.
+ */
+#define SHMEM_LUO_FLAG_TMPFS BIT(0)
+
+/* The set of flags this version supports. */
+#define SHMEM_LUO_ALL_FLAGS SHMEM_LUO_FLAG_TMPFS
+
+/**
+ * struct shmem_luo_ser - Main serialization structure for a memfd or tmpfs file.
* @pos: The file's current position (f_pos).
* @size: The total size of the file in bytes (i_size).
* @seals: The seals present on the memfd. The seals are uABI so it is safe
* to directly use them in the ABI.
- * @flags: Flags for the file. Unused flag bits must be set to 0.
+ * @flags: Flags for the file (SHMEM_LUO_FLAG_*). Unused flag bits must be
+ * set to 0.
* @nr_folios: Number of folios in the folios array.
* @folios: KHO vmalloc descriptor pointing to the array of
* struct shmem_luo_folio_ser.
diff --git a/include/uapi/linux/liveupdate.h b/include/uapi/linux/liveupdate.h
index 953ffe0823be..a9dcc4fcc87b 100644
--- a/include/uapi/linux/liveupdate.h
+++ b/include/uapi/linux/liveupdate.h
@@ -247,4 +247,39 @@ struct liveupdate_session_get_name {
#define LIVEUPDATE_SESSION_GET_NAME \
_IO(LIVEUPDATE_IOCTL_TYPE, LIVEUPDATE_CMD_SESSION_GET_NAME)
+/**
+ * struct liveupdate_shmem_retrieve_arg - Argument to retrieve a tmpfs file
+ * @size: Input; sizeof(struct liveupdate_shmem_retrieve_arg)
+ * @dirfd: Input; A file descriptor for a directory on the tmpfs mount to create
+ * the file on.
+ * @flags: Input; The flags of the file, like the flags argument of open(),
+ * except for O_TMPFILE, which is implied, and O_PATH, which is not
+ * allowed.
+ * @mode: Input; The mode of the file, like the mode argument of open().
+ *
+ * Passed via &struct liveupdate_session_retrieve_fd.arg to retrieve a file that
+ * was preserved from a tmpfs mount. Such files can only be retrieved with this
+ * argument, and memfds, i.e. files that were preserved from the kernel's
+ * internal shmem mount, can only be retrieved without it. Otherwise, the
+ * retrieve fails with -EINVAL.
+ *
+ * The kernel creates a new file as if by openat(@dirfd, ".", O_TMPFILE |
+ * @flags, @mode), fills it with the preserved contents, and returns it in
+ * &struct liveupdate_session_retrieve_fd.fd. Like any O_TMPFILE file, the file
+ * is not linked into the filesystem. Userspace can link it with linkat(),
+ * unless @flags contains O_EXCL.
+ *
+ * If @flags contains unknown flags or O_PATH, if @mode contains unknown mode
+ * bits, or if @dirfd is not on a tmpfs mount, the retrieve fails with -EINVAL.
+ * If the file can't be created, the retrieve fails with the same errors as
+ * open(). A retrieve that fails before the preserved contents are restored can
+ * be retried.
+ */
+struct liveupdate_shmem_retrieve_arg {
+ __u32 size;
+ __s32 dirfd;
+ __aligned_u64 flags;
+ __aligned_u64 mode;
+};
+
#endif /* _UAPI_LIVEUPDATE_H */
diff --git a/kernel/liveupdate/Kconfig b/kernel/liveupdate/Kconfig
index 9a1c49e2e0c4..61d01f5a336e 100644
--- a/kernel/liveupdate/Kconfig
+++ b/kernel/liveupdate/Kconfig
@@ -70,14 +70,14 @@ config LIVEUPDATE
If unsure, say N.
config LIVEUPDATE_SHMEM
- bool "Live update support for memfd"
+ bool "Live update support for memfd and tmpfs files"
depends on LIVEUPDATE
depends on MEMFD_CREATE
depends on SHMEM
default LIVEUPDATE
help
- Enable live update support for memfd regions. This allows preserving
- memfd-backed memory across kernel live updates.
+ Enable live update support for memfds and tmpfs files. This allows
+ preserving their contents across kernel live updates.
This can be used to back VM memory with memfds, allowing the guest
memory to persist, or for other user workloads needing to preserve
diff --git a/mm/internal.h b/mm/internal.h
index 38b1165212c9..630ae0297dd5 100644
--- a/mm/internal.h
+++ b/mm/internal.h
@@ -1436,6 +1436,7 @@ int shmem_add_to_page_cache(struct folio *folio,
pgoff_t index, void *expected, gfp_t gfp);
int shmem_inode_acct_blocks(struct inode *inode, long pages);
bool shmem_recalc_inode(struct inode *inode, long alloced, long swapped);
+bool shmem_inode_on_internal_mount(const struct inode *inode);
#ifdef CONFIG_SHRINKER_DEBUG
static inline __printf(2, 0) int shrinker_debugfs_name_alloc(
diff --git a/mm/shmem.c b/mm/shmem.c
index 897fa2b61346..fe2be9300ae7 100644
--- a/mm/shmem.c
+++ b/mm/shmem.c
@@ -5860,6 +5860,15 @@ struct file *shmem_kernel_file_setup(const char *name, loff_t size,
}
EXPORT_SYMBOL_GPL(shmem_kernel_file_setup);
+/*
+ * Whether @inode is on the internal shmem mount, which is used by
+ * shmem_file_setup() and thus by memfd_create().
+ */
+bool shmem_inode_on_internal_mount(const struct inode *inode)
+{
+ return inode->i_sb == shm_mnt->mnt_sb;
+}
+
/**
* shmem_file_setup - get an unlinked file living in tmpfs
* @name: name for dentry (to be seen in /proc/<pid>/maps)
diff --git a/mm/shmem_luo.c b/mm/shmem_luo.c
index bcac1bbb3db3..ecbeb37d33a8 100644
--- a/mm/shmem_luo.c
+++ b/mm/shmem_luo.c
@@ -9,19 +9,20 @@
*/
/**
- * DOC: Memfd Preservation via LUO
+ * DOC: Memfd and Tmpfs Preservation via LUO
*
* Overview
* ========
*
- * Memory file descriptors (memfd) can be preserved over a kexec using the Live
- * Update Orchestrator (LUO) file preservation. This allows userspace to
- * transfer its memory contents to the next kernel after a kexec.
+ * Memory file descriptors (memfd) and tmpfs files can be preserved over a kexec
+ * using the Live Update Orchestrator (LUO) file preservation. This allows
+ * userspace to transfer its memory contents to the next kernel after a kexec.
*
* Here, a memfd is a memfd created without ``MFD_HUGETLB``, which is a shmem
- * file on the kernel's internal shmem mount. Memfds created with
- * ``MFD_HUGETLB`` are hugetlbfs files, which this file handler doesn't
- * preserve. Preserving them fails with ``-ENOENT``.
+ * file on the kernel's internal shmem mount, and a tmpfs file is a file on any
+ * other tmpfs mount. Memfds created with ``MFD_HUGETLB`` and other files on
+ * hugetlbfs are not shmem files, and this file handler doesn't preserve them.
+ * Preserving them fails with ``-ENOENT``.
*
* The preservation is not intended to be transparent. Only select properties of
* the file are preserved. All others are reset to default. The preserved
@@ -29,12 +30,13 @@
*
* .. note::
* The LUO API is not stabilized yet, so the preserved properties of a memfd
- * are also not stable and are subject to backwards incompatible changes.
+ * or tmpfs file are also not stable and are subject to backwards
+ * incompatible changes.
*
* Preserved Properties
* ====================
*
- * The following properties of the memfd are preserved across kexec:
+ * The following properties of the file are preserved across kexec:
*
* File Contents
* All data stored in the file is preserved.
@@ -49,10 +51,12 @@
*
* File Status Flags
* memfds are always opened with ``O_RDWR`` and ``O_LARGEFILE``. This property
- * is maintained.
+ * is maintained. The file status flags of tmpfs files are not preserved.
+ * Userspace chooses the flags when retrieving them, as described in
+ * "Retrieving Tmpfs Files" below.
*
* Seals
- * File seals set on the memfd are preserved and re-applied on restore.
+ * The seals of the file are preserved and re-applied on restore.
* Only seals known to this LUO version (see ``SHMEM_LUO_ALL_SEALS``) may
* be present; preservation fails with ``-EOPNOTSUPP`` otherwise.
*
@@ -67,12 +71,50 @@
* A memfd can be created with the ``MFD_CLOEXEC`` flag that sets the
* ``FD_CLOEXEC`` on the file. This flag is not preserved and must be set
* again after restore via ``fcntl()``.
+ *
+ * Inode Attributes
+ * The name, path, owner, mode, timestamps and extended attributes of the
+ * file are not preserved.
+ *
+ * Retrieving Tmpfs Files
+ * ======================
+ *
+ * A tmpfs file can be retrieved onto any tmpfs mount that userspace chooses.
+ * The mount that the file was on doesn't exist in the new kernel, and userspace
+ * may want to put the file elsewhere anyway.
+ *
+ * To retrieve a tmpfs file, userspace passes a
+ * ``struct liveupdate_shmem_retrieve_arg`` to
+ * ``LIVEUPDATE_SESSION_RETRIEVE_FD``. The argument specifies a directory on the
+ * target mount, and the flags and mode of the new file. The kernel creates the
+ * file as if by ``openat(dirfd, ".", O_TMPFILE | flags, mode)``, so the file
+ * gets the same flags, owner, permission checks and security labels as any
+ * other new file in that directory. The kernel then fills the file with the
+ * preserved contents, size, position and seals before it returns the file, so
+ * nothing else can access the file before it is complete.
+ *
+ * Like any ``O_TMPFILE`` file, the new file is not linked into the filesystem.
+ * Userspace can link it at the path it wants with ``linkat()``, unless it asked
+ * for ``O_EXCL``. Like with ``open()``, the file must be opened for writing.
+ * LUO keeps the file open until the session is finished, so a retrieved binary
+ * can't be executed until then.
+ *
+ * The blocks of the file are charged to the target mount, so the size limit
+ * and quotas of the mount apply. If the file doesn't fit, the retrieve fails
+ * permanently, since the preserved contents have already been restored at that
+ * point. If the argument is invalid or the file can't be created, the retrieve
+ * fails before the preserved contents are restored, so it can be retried.
+ *
+ * memfds can only be retrieved as memfds, and files on tmpfs mounts can only be
+ * retrieved onto tmpfs mounts. Retrieving a memfd with the argument, or a tmpfs
+ * file without it, fails with ``-EINVAL``.
*/
#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
#include <linux/bits.h>
#include <linux/err.h>
+#include <linux/fcntl.h>
#include <linux/file.h>
#include <linux/io.h>
#include <linux/kexec_handover.h>
@@ -81,6 +123,7 @@
#include <linux/shmem_fs.h>
#include <linux/vmalloc.h>
#include <linux/memfd.h>
+#include <uapi/linux/magic.h>
#include <uapi/linux/memfd.h>
#include "internal.h"
@@ -299,6 +342,8 @@ static int shmem_luo_preserve(struct liveupdate_file_op_args *args)
ser->size = inode_size;
ser->seals = seals;
+ if (!shmem_inode_on_internal_mount(inode))
+ ser->flags |= SHMEM_LUO_FLAG_TMPFS;
err = shmem_luo_preserve_folios(args->file, &ser->folios,
&folios_ser, &nr_folios);
@@ -515,6 +560,92 @@ static int shmem_luo_retrieve_folios(struct file *file,
return err;
}
+/* Create the memfd to retrieve a file from the internal shmem mount into. */
+static struct file *shmem_luo_create_memfd(const struct liveupdate_shmem_retrieve_arg *arg,
+ unsigned int seals)
+{
+ struct file *file;
+ int err;
+
+ /* memfds are retrieved as memfds, so there is nothing to choose. */
+ if (arg)
+ return ERR_PTR(-EINVAL);
+
+ /*
+ * The seals are preserved. Allow sealing here so they can be added
+ * later.
+ */
+ file = memfd_alloc_file("", MFD_ALLOW_SEALING);
+ if (IS_ERR(file)) {
+ pr_err("failed to setup file: %pe\n", file);
+ return file;
+ }
+
+ err = memfd_add_seals(file, seals);
+ if (err) {
+ pr_err("failed to add seals: %pe\n", ERR_PTR(err));
+ fput(file);
+ return ERR_PTR(err);
+ }
+
+ return file;
+}
+
+/*
+ * Create the file to retrieve a file from a tmpfs mount into, on the tmpfs
+ * mount chosen by userspace.
+ */
+static struct file *shmem_luo_create_tmpfs_file(const struct liveupdate_shmem_retrieve_arg *arg,
+ unsigned int seals)
+{
+ struct file *file;
+ int err;
+
+ if (!arg)
+ return ERR_PTR(-EINVAL);
+
+ /*
+ * Like openat2(), reject unknown flags and mode bits instead of
+ * ignoring them. Also reject O_PATH, which would make file_open_root()
+ * ignore O_TMPFILE and open the directory instead of creating a file.
+ */
+ if (arg->flags & ~(u64)VALID_OPEN_FLAGS || arg->flags & O_PATH ||
+ arg->mode & ~(u64)S_IALLUGO)
+ return ERR_PTR(-EINVAL);
+
+ CLASS(fd_raw, dir)(arg->dirfd);
+ if (fd_empty(dir))
+ return ERR_PTR(-EBADF);
+
+ if (fd_file(dir)->f_path.dentry->d_sb->s_magic != TMPFS_MAGIC)
+ return ERR_PTR(-EINVAL);
+
+ /*
+ * Create the file like open(O_TMPFILE) does, so that it gets the same
+ * flag checks, permission checks, owner and security labels.
+ */
+ file = file_open_root(&fd_file(dir)->f_path, ".",
+ O_TMPFILE | O_LARGEFILE | arg->flags, arg->mode);
+ if (IS_ERR(file))
+ return file;
+
+ /*
+ * Nothing else can access the new file yet, so set the preserved seals
+ * directly. memfd_add_seals() refuses to, since files on tmpfs mounts
+ * are created with F_SEAL_SEAL.
+ */
+ if (seals & F_SEAL_WRITE) {
+ err = mapping_deny_writable(file->f_mapping);
+ if (err) {
+ fput(file);
+ return ERR_PTR(err);
+ }
+ }
+ SHMEM_I(file_inode(file))->seals = seals;
+
+ return file;
+}
+
static int shmem_luo_retrieve(struct liveupdate_file_op_args *args)
{
struct shmem_luo_folio_ser *folios_ser;
@@ -533,25 +664,18 @@ static int shmem_luo_retrieve(struct liveupdate_file_op_args *args)
*/
args->retryable = true;
- /* Make sure the file only has seals supported by this version. */
- if (ser->seals & ~SHMEM_LUO_ALL_SEALS)
+ /* Make sure the file only has seals and flags this version supports. */
+ if ((ser->seals & ~SHMEM_LUO_ALL_SEALS) ||
+ (ser->flags & ~SHMEM_LUO_ALL_FLAGS))
return -EOPNOTSUPP;
- /*
- * The seals are preserved. Allow sealing here so they can be added
- * later.
- */
- file = memfd_alloc_file("", MFD_ALLOW_SEALING);
- if (IS_ERR(file)) {
- pr_err("failed to setup file: %pe\n", file);
+ /* Retrieve memfds as memfds, and other files onto a tmpfs mount. */
+ if (ser->flags & SHMEM_LUO_FLAG_TMPFS)
+ file = shmem_luo_create_tmpfs_file(args->arg, ser->seals);
+ else
+ file = shmem_luo_create_memfd(args->arg, ser->seals);
+ if (IS_ERR(file))
return PTR_ERR(file);
- }
-
- err = memfd_add_seals(file, ser->seals);
- if (err) {
- pr_err("failed to add seals: %pe\n", ERR_PTR(err));
- goto put_file;
- }
vfs_setpos(file, ser->pos, MAX_LFS_FILESIZE);
i_size_write(file_inode(file), ser->size);
@@ -579,7 +703,6 @@ static int shmem_luo_retrieve(struct liveupdate_file_op_args *args)
free_ser:
kho_restore_free(ser);
-put_file:
fput(file);
return err;
}
@@ -605,6 +728,7 @@ static const struct liveupdate_file_ops shmem_luo_file_ops = {
.unpreserve = shmem_luo_unpreserve,
.can_preserve = shmem_luo_can_preserve,
.get_id = shmem_luo_get_id,
+ .rarg = LIVEUPDATE_ARG(struct liveupdate_shmem_retrieve_arg, mode),
.owner = THIS_MODULE,
};
--
2.56.0.257.gf7497c7d27-goog
^ permalink raw reply related [flat|nested] 20+ messages in thread* Re: [PATCH v2 07/10] mm/shmem_luo: Retrieve tmpfs files onto a tmpfs mount chosen by userspace
2026-10-22 22:50 ` [PATCH v2 07/10] mm/shmem_luo: Retrieve tmpfs files onto a tmpfs mount chosen by userspace David Matlack
@ 2026-10-23 22:51 ` sashiko-bot
0 siblings, 0 replies; 20+ messages in thread
From: sashiko-bot @ 2026-10-23 22:51 UTC (permalink / raw)
To: David Matlack, kexec
Cc: Alexander Graf, Andrew Morton, Baolin Wang, Christian Brauner,
David Hildenbrand, Hugh Dickins, Jonathan Corbet, Liam R. Howlett,
Lorenzo Stoakes, Michal Hocko, Mike Rapoport, Pasha Tatashin,
Pratyush Yadav, Randy Dunlap, Samiullah Khawaja, Shuah Khan,
Suren Baghdasaryan, Vlastimil Babka
> Retrieve files that were preserved from a tmpfs mount onto a tmpfs mount
> chosen by userspace, instead of as memfds on the internal tmpfs mount.
> Record which mount a file was on with a new SHMEM_LUO_FLAG_TMPFS flag in
> struct shmem_luo_ser, based on the superblock of the file. shmem_luo
> already accepts unlinked tmpfs files, such as files created with
> O_TMPFILE, and a following commit also accepts linked ones.
>
> The mount a tmpfs file was on doesn't exist in the new kernel, and
> userspace may want to put the file elsewhere anyway. So add struct
> liveupdate_shmem_retrieve_arg, which userspace passes as the retrieve
> argument of LIVEUPDATE_SESSION_RETRIEVE_FD. It is modeled on open(): The
> kernel creates a new file as if by openat(dirfd, ".", O_TMPFILE | flags,
> mode), so the file gets the owner, permission checks and security labels
> of any new file in that directory, and is charged to that mount. The
> kernel then fills the file with the preserved contents, size, position
> [ ... ]
> Like the other failures before the folios are restored, problems with
> the argument and with creating the file can be retried.
>
> Assisted-by: LLM
> Signed-off-by: David Matlack <dmatlack@google.com>
Sashiko has reviewed this patch and found no issues.
--
Sashiko AI review · https://sashiko.dev/#/patchset/20261022225035.377445-1-dmatlack@google.com?part=7
^ permalink raw reply [flat|nested] 20+ messages in thread
* [PATCH v2 08/10] mm/shmem_luo: Allow preserving tmpfs files that are still linked
2026-10-22 22:50 [PATCH v2 00/10] liveupdate: Preserve tmpfs files across Live Update David Matlack
` (6 preceding siblings ...)
2026-10-22 22:50 ` [PATCH v2 07/10] mm/shmem_luo: Retrieve tmpfs files onto a tmpfs mount chosen by userspace David Matlack
@ 2026-10-22 22:50 ` David Matlack
2026-10-23 22:51 ` sashiko-bot
2026-10-22 22:50 ` [PATCH v2 09/10] mm/shmem_luo: Allow retrieving tmpfs files with O_RDONLY David Matlack
[not found] ` <20261022225035.377445-11-dmatlack@google.com>
9 siblings, 1 reply; 20+ messages in thread
From: David Matlack @ 2026-10-22 22:50 UTC (permalink / raw)
To: kexec, linux-doc, linux-kernel, linux-kselftest, linux-mm
Cc: Alexander Graf, Andrew Morton, Baolin Wang, Christian Brauner,
David Hildenbrand, Hugh Dickins, Jonathan Corbet, Liam R. Howlett,
Lorenzo Stoakes, Michal Hocko, Mike Rapoport, Pasha Tatashin,
Pratyush Yadav, Randy Dunlap, Samiullah Khawaja, Shuah Khan,
Suren Baghdasaryan, Vlastimil Babka, David Matlack
shmem_luo already accepts any shmem file without links, which includes
unlinked tmpfs files and files created with O_TMPFILE. Also accept tmpfs
files that are still linked, so that userspace doesn't have to unlink
files to preserve them and can keep using them by path until the live
update.
The path of the file isn't preserved. Like other files from tmpfs
mounts, a linked file is retrieved onto a tmpfs mount chosen by
userspace, which can then link it at the path it wants.
While a file is preserved, its size is frozen, so truncating it,
fallocate() and writes past its end fail with -EPERM. For a linked file,
this affects everyone who opens the file by its path, so a caller that
can only read the file, e.g. a world-readable file in /dev/shm, could
use it to break the owner of the file. So require permission to write to
the file to preserve it, for all files. Like FS_IOC_ENABLE_VERITY, don't
require a file opened for writing, so that binaries that are being
executed can still be preserved through read-only fds, nor a writable
mount. If the size of the file can't change anyway, because it is
immutable or on a read-only superblock, also let its owner preserve it.
Don't let others do that, since the size could change again once the
file is made writable.
Preserving a file also fills its holes, but don't require any permission
for that. Anyone who can read a tmpfs file can already fill its holes by
faulting them in through a read-only mapping, also on read-only mounts
and for immutable files.
Document the frozen size and the permission requirement.
Assisted-by: LLM
Signed-off-by: David Matlack <dmatlack@google.com>
---
mm/shmem_luo.c | 55 +++++++++++++++++++++++++++++++++++++++++++++++---
1 file changed, 52 insertions(+), 3 deletions(-)
diff --git a/mm/shmem_luo.c b/mm/shmem_luo.c
index ecbeb37d33a8..aba2bc1f18c5 100644
--- a/mm/shmem_luo.c
+++ b/mm/shmem_luo.c
@@ -28,6 +28,23 @@
* the file are preserved. All others are reset to default. The preserved
* properties are described below.
*
+ * A tmpfs file can be preserved even if it is linked, but its path is not
+ * preserved. Userspace chooses where to put the file when retrieving it, as
+ * described in "Retrieving Tmpfs Files" below.
+ *
+ * Preserving a file requires permission to write to it, since preserving it
+ * affects all of its users, as described below. But the file doesn't need to
+ * be opened for writing. For example, a binary that is being executed can be
+ * preserved through a read-only file descriptor. The owner of a file whose
+ * size can't change anyway, because it is immutable or on a read-only
+ * superblock, may also preserve it. The file can be preserved through a
+ * read-only mount.
+ *
+ * While a file is preserved, its size can't change and no holes can be punched
+ * into it. Truncating the file, ``fallocate()`` and writes past the end of the
+ * file fail with ``-EPERM``. This applies to all users of the file, including
+ * users that open it by its path.
+ *
* .. note::
* The LUO API is not stabilized yet, so the preserved properties of a memfd
* or tmpfs file are also not stable and are subject to backwards
@@ -298,6 +315,34 @@ static void shmem_luo_unpreserve_folios(struct kho_vmalloc *kho_vmalloc,
vfree(folios_ser);
}
+/*
+ * Check whether the caller may preserve @file. Preserving the file freezes its
+ * size until it is unpreserved, for all users of the file, so require
+ * permission to write to the file. Like FS_IOC_ENABLE_VERITY, don't require a
+ * file opened for writing, so that e.g. a binary that is being executed can be
+ * preserved, nor a writable mount. If the size of the file can't change
+ * anyway, because it is on a read-only superblock or immutable, its owner may
+ * also preserve it. The size could only change again after the file is made
+ * writable, which is up to the owner or root.
+ *
+ * Preserving the file also fills its holes, but that doesn't need any
+ * permission: Anyone who can read the file can fill its holes by faulting
+ * them in through a read-only mapping.
+ */
+static int shmem_luo_may_preserve(struct file *file)
+{
+ struct mnt_idmap *idmap = file_mnt_idmap(file);
+ struct inode *inode = file_inode(file);
+ int err;
+
+ err = inode_permission(idmap, inode, MAY_WRITE);
+ if (err && (sb_rdonly(inode->i_sb) || IS_IMMUTABLE(inode)) &&
+ inode_owner_or_capable(idmap, inode))
+ return 0;
+
+ return err;
+}
+
static int shmem_luo_preserve(struct liveupdate_file_op_args *args)
{
struct inode *inode = file_inode(args->file);
@@ -307,6 +352,11 @@ static int shmem_luo_preserve(struct liveupdate_file_op_args *args)
int err = 0, seals;
inode_lock(inode);
+
+ err = shmem_luo_may_preserve(args->file);
+ if (err)
+ goto err_unlock_inode;
+
shmem_freeze(inode, true);
/* Allocate the main serialization structure in preserved memory */
@@ -362,6 +412,7 @@ static int shmem_luo_preserve(struct liveupdate_file_op_args *args)
kho_unpreserve_free(ser);
err_unlock:
shmem_freeze(inode, false);
+err_unlock_inode:
inode_unlock(inode);
return err;
}
@@ -710,9 +761,7 @@ static int shmem_luo_retrieve(struct liveupdate_file_op_args *args)
static bool shmem_luo_can_preserve(struct liveupdate_file_handler *handler,
struct file *file)
{
- struct inode *inode = file_inode(file);
-
- return shmem_file(file) && !inode->i_nlink;
+ return shmem_file(file);
}
static unsigned long shmem_luo_get_id(struct file *file)
--
2.56.0.257.gf7497c7d27-goog
^ permalink raw reply related [flat|nested] 20+ messages in thread* Re: [PATCH v2 08/10] mm/shmem_luo: Allow preserving tmpfs files that are still linked
2026-10-22 22:50 ` [PATCH v2 08/10] mm/shmem_luo: Allow preserving tmpfs files that are still linked David Matlack
@ 2026-10-23 22:51 ` sashiko-bot
0 siblings, 0 replies; 20+ messages in thread
From: sashiko-bot @ 2026-10-23 22:51 UTC (permalink / raw)
To: David Matlack, kexec, linux-mm
Cc: Alexander Graf, Andrew Morton, Baolin Wang, Christian Brauner,
David Hildenbrand, Hugh Dickins, Jonathan Corbet, Liam R. Howlett,
Lorenzo Stoakes, Michal Hocko, Mike Rapoport, Pasha Tatashin,
Pratyush Yadav, Randy Dunlap, Samiullah Khawaja, Shuah Khan,
Suren Baghdasaryan, Vlastimil Babka
Thank you for your contribution! Sashiko AI review found 2 potential issue(s) to consider:
- [High] mm/shmem_luo: missing FMODE_READ and MAY_READ check in shmem_luo_may_preserve()
- [Medium] mm/shmem_luo: missing IS_APPEND() check in shmem_luo_may_preserve()
--
commit c078673247a58afcd0e7d24e1253ad5aeb43414c
Author: David Matlack <dmatlack@google.com>
mm/shmem_luo: Allow preserving tmpfs files that are still linked
This commit allows preserving tmpfs files that still have links in the
filesystem by removing the !inode->i_nlink restriction in
shmem_luo_can_preserve(). Because preserving a file freezes its size for all
users, it adds shmem_luo_may_preserve() to require write permission (or
ownership when the file is immutable or on a read-only superblock) and
documents the new behavior.
> diff --git a/mm/shmem_luo.c b/mm/shmem_luo.c
> index ecbeb37d33a8a..aba2bc1f18c52 100644
> --- a/mm/shmem_luo.c
> +++ b/mm/shmem_luo.c
[ ... ]
> @@ -298,6 +315,34 @@ static void shmem_luo_unpreserve_folios(struct kho_vmalloc *kho_vmalloc,
> vfree(folios_ser);
> }
>
> +/*
> + * Check whether the caller may preserve @file. Preserving the file freezes its
> + * size until it is unpreserved, for all users of the file, so require
> + * permission to write to the file. Like FS_IOC_ENABLE_VERITY, don't require a
> + * file opened for writing, so that e.g. a binary that is being executed can be
> + * preserved, nor a writable mount. If the size of the file can't change
> + * anyway, because it is on a read-only superblock or immutable, its owner may
> + * also preserve it. The size could only change again after the file is made
> + * writable, which is up to the owner or root.
> + *
> + * Preserving the file also fills its holes, but that doesn't need any
> + * permission: Anyone who can read the file can fill its holes by faulting
> + * them in through a read-only mapping.
> + */
> +static int shmem_luo_may_preserve(struct file *file)
> +{
> + struct mnt_idmap *idmap = file_mnt_idmap(file);
> + struct inode *inode = file_inode(file);
> + int err;
> +
> + err = inode_permission(idmap, inode, MAY_WRITE);
[Severity: High]
Should shmem_luo_may_preserve() in mm/shmem_luo.c also verify that the file
is open for reading (file->f_mode & FMODE_READ) or that the caller has
MAY_READ permission?
The comment above notes that anyone who can read the file can fill its holes
through a read-only mapping, and models the check on FS_IOC_ENABLE_VERITY.
However, fsverity_ioctl_enable() in fs/verity/enable.c explicitly checks
FMODE_READ in addition to MAY_WRITE:
err = file_permission(filp, MAY_WRITE);
if (err)
return err;
if (!(filp->f_mode & FMODE_READ))
return -EBADF;
Similarly, do_mmap() in mm/mmap.c rejects mapping a file when FMODE_READ is
not set:
if (!(file->f_mode & FMODE_READ))
return -EACCES;
Without a read check in shmem_luo_may_preserve(), a user who opens a
write-only tmpfs file (for example, mode 0622 or 0222 in /dev/shm owned by
another user) with O_WRONLY, or who holds an ioctl-only mode-3 file
descriptor lacking FMODE_READ, can pass it to LIVEUPDATE_SESSION_PRESERVE_FD:
luo_preserve_file()
-> shmem_luo_preserve()
-> shmem_luo_may_preserve()
-> shmem_luo_preserve_folios()
-> memfd_pin_folios()
This faults in and pins folios for the entire file (including holes), and on
LIVEUPDATE_SESSION_RETRIEVE_FD, shmem_luo_retrieve() ->
shmem_luo_create_tmpfs_file() in mm/shmem_luo.c places those folios into a
new tmpfs file opened with caller-chosen flags (such as O_RDWR or O_RDONLY)
and mode:
file = file_open_root(&fd_file(dir)->f_path, ".",
O_TMPFILE | O_LARGEFILE | arg->flags, arg->mode);
Wouldn't that allow a caller with only write access to read the file's
contents after retrieval?
> + if (err && (sb_rdonly(inode->i_sb) || IS_IMMUTABLE(inode)) &&
> + inode_owner_or_capable(idmap, inode))
> + return 0;
> +
> + return err;
> +}
[Severity: Medium]
What happens if shmem_luo_may_preserve() in mm/shmem_luo.c is called on an
append-only (S_APPEND) tmpfs inode?
Tmpfs supports setting S_APPEND via FS_IOC_SETFLAGS in
shmem_fileattr_set() -> shmem_set_inode_flags() in mm/shmem.c:
if (fsflags & FS_APPEND_FL)
i_flags |= S_APPEND;
Unlike IS_IMMUTABLE(inode), inode_permission(idmap, inode, MAY_WRITE) does
not reject S_APPEND inodes. In fs/verity/enable.c, fsverity_ioctl_enable()
explicitly checks IS_APPEND(inode) after checking MAY_WRITE:
if (IS_APPEND(inode))
return -EPERM;
When a caller with MAY_WRITE permission on an S_APPEND tmpfs file preserves
it via LIVEUPDATE_SESSION_PRESERVE_FD -> luo_preserve_file() ->
shmem_luo_preserve(), shmem_freeze(inode, true) sets SHMEM_F_MAPPING_FROZEN.
Because all writes to an S_APPEND file extend past i_size,
shmem_write_begin() and shmem_fallocate() in mm/shmem.c will then fail with
-EPERM:
if (unlikely((info->flags & SHMEM_F_MAPPING_FROZEN) &&
pos + len > inode->i_size))
return -EPERM;
Should shmem_luo_may_preserve() also reject IS_APPEND(inode) with -EPERM so
an append-only writer cannot freeze the file and block future appends by all
users?
--
Sashiko AI review · https://sashiko.dev/#/patchset/20261022225035.377445-1-dmatlack@google.com?part=8
^ permalink raw reply [flat|nested] 20+ messages in thread
* [PATCH v2 09/10] mm/shmem_luo: Allow retrieving tmpfs files with O_RDONLY
2026-10-22 22:50 [PATCH v2 00/10] liveupdate: Preserve tmpfs files across Live Update David Matlack
` (7 preceding siblings ...)
2026-10-22 22:50 ` [PATCH v2 08/10] mm/shmem_luo: Allow preserving tmpfs files that are still linked David Matlack
@ 2026-10-22 22:50 ` David Matlack
2026-10-23 22:51 ` sashiko-bot
[not found] ` <20261022225035.377445-11-dmatlack@google.com>
9 siblings, 1 reply; 20+ messages in thread
From: David Matlack @ 2026-10-22 22:50 UTC (permalink / raw)
To: kexec, linux-doc, linux-kernel, linux-kselftest, linux-mm
Cc: Alexander Graf, Andrew Morton, Baolin Wang, Christian Brauner,
David Hildenbrand, Hugh Dickins, Jonathan Corbet, Liam R. Howlett,
Lorenzo Stoakes, Michal Hocko, Mike Rapoport, Pasha Tatashin,
Pratyush Yadav, Randy Dunlap, Samiullah Khawaja, Shuah Khan,
Suren Baghdasaryan, Vlastimil Babka, David Matlack
Allow userspace to pass O_RDONLY in struct liveupdate_shmem_retrieve_arg
to retrieve a tmpfs file read-only.
open() requires write access for O_TMPFILE, so create the file writable
like before, then reopen it read-only with dentry_open() using the same
flags, and drop the writable file. Like open(), which grants the
requested access to a new file regardless of its mode, don't check
permission for the read-only open.
Retrieving a file read-only allows a restored binary to be executed
before the session is finished. LUO keeps the retrieved file open until
then, and executing a binary that is open for writing fails with
-ETXTBSY. Being able to execute restored binaries right away matters
when they are needed to finish restoring the system, e.g. to bring up
the services that retrieve and finish the remaining sessions.
Assisted-by: LLM
Signed-off-by: David Matlack <dmatlack@google.com>
---
include/uapi/linux/liveupdate.h | 6 +++---
mm/shmem_luo.c | 28 +++++++++++++++++++++-------
2 files changed, 24 insertions(+), 10 deletions(-)
diff --git a/include/uapi/linux/liveupdate.h b/include/uapi/linux/liveupdate.h
index a9dcc4fcc87b..790742a247e8 100644
--- a/include/uapi/linux/liveupdate.h
+++ b/include/uapi/linux/liveupdate.h
@@ -265,9 +265,9 @@ struct liveupdate_session_get_name {
*
* The kernel creates a new file as if by openat(@dirfd, ".", O_TMPFILE |
* @flags, @mode), fills it with the preserved contents, and returns it in
- * &struct liveupdate_session_retrieve_fd.fd. Like any O_TMPFILE file, the file
- * is not linked into the filesystem. Userspace can link it with linkat(),
- * unless @flags contains O_EXCL.
+ * &struct liveupdate_session_retrieve_fd.fd. Unlike open(), O_RDONLY is
+ * allowed. Like any O_TMPFILE file, the file is not linked into the filesystem.
+ * Userspace can link it with linkat(), unless @flags contains O_EXCL.
*
* If @flags contains unknown flags or O_PATH, if @mode contains unknown mode
* bits, or if @dirfd is not on a tmpfs mount, the retrieve fails with -EINVAL.
diff --git a/mm/shmem_luo.c b/mm/shmem_luo.c
index aba2bc1f18c5..0b4a9d6e2968 100644
--- a/mm/shmem_luo.c
+++ b/mm/shmem_luo.c
@@ -112,9 +112,10 @@
*
* Like any ``O_TMPFILE`` file, the new file is not linked into the filesystem.
* Userspace can link it at the path it wants with ``linkat()``, unless it asked
- * for ``O_EXCL``. Like with ``open()``, the file must be opened for writing.
- * LUO keeps the file open until the session is finished, so a retrieved binary
- * can't be executed until then.
+ * for ``O_EXCL``. Unlike ``open()``, the file can be retrieved with
+ * ``O_RDONLY``. LUO keeps the file open until the session is finished, so a
+ * binary retrieved with ``O_RDWR`` or ``O_WRONLY`` can't be executed until
+ * then.
*
* The blocks of the file are charged to the target mount, so the size limit
* and quotas of the mount apply. If the file doesn't fit, the retrieve fails
@@ -649,7 +650,9 @@ static struct file *shmem_luo_create_memfd(const struct liveupdate_shmem_retriev
static struct file *shmem_luo_create_tmpfs_file(const struct liveupdate_shmem_retrieve_arg *arg,
unsigned int seals)
{
- struct file *file;
+ struct file *file, *ro_file;
+ bool rdonly;
+ int flags;
int err;
if (!arg)
@@ -673,10 +676,16 @@ static struct file *shmem_luo_create_tmpfs_file(const struct liveupdate_shmem_re
/*
* Create the file like open(O_TMPFILE) does, so that it gets the same
- * flag checks, permission checks, owner and security labels.
+ * flag checks, permission checks, owner and security labels. open()
+ * requires write access for O_TMPFILE, so create the file writable and
+ * reopen it read-only below if that is what userspace asked for. LUO
+ * keeps the file until the session is finished, and a file opened for
+ * writing couldn't be executed until then.
*/
+ flags = O_TMPFILE | O_LARGEFILE | arg->flags;
+ rdonly = (flags & O_ACCMODE) == O_RDONLY;
file = file_open_root(&fd_file(dir)->f_path, ".",
- O_TMPFILE | O_LARGEFILE | arg->flags, arg->mode);
+ flags | (rdonly ? O_RDWR : 0), arg->mode);
if (IS_ERR(file))
return file;
@@ -694,7 +703,12 @@ static struct file *shmem_luo_create_tmpfs_file(const struct liveupdate_shmem_re
}
SHMEM_I(file_inode(file))->seals = seals;
- return file;
+ if (!rdonly)
+ return file;
+
+ ro_file = dentry_open(&file->f_path, flags, current_cred());
+ fput(file);
+ return ro_file;
}
static int shmem_luo_retrieve(struct liveupdate_file_op_args *args)
--
2.56.0.257.gf7497c7d27-goog
^ permalink raw reply related [flat|nested] 20+ messages in thread* Re: [PATCH v2 09/10] mm/shmem_luo: Allow retrieving tmpfs files with O_RDONLY
2026-10-22 22:50 ` [PATCH v2 09/10] mm/shmem_luo: Allow retrieving tmpfs files with O_RDONLY David Matlack
@ 2026-10-23 22:51 ` sashiko-bot
0 siblings, 0 replies; 20+ messages in thread
From: sashiko-bot @ 2026-10-23 22:51 UTC (permalink / raw)
To: David Matlack, kexec, linux-mm
Cc: Alexander Graf, Andrew Morton, Baolin Wang, Christian Brauner,
David Hildenbrand, Hugh Dickins, Jonathan Corbet, Liam R. Howlett,
Lorenzo Stoakes, Michal Hocko, Mike Rapoport, Pasha Tatashin,
Pratyush Yadav, Randy Dunlap, Samiullah Khawaja, Shuah Khan,
Suren Baghdasaryan, Vlastimil Babka
Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
- [Medium] mm/shmem_luo: unnormalized flags passed to dentry_open() on O_RDONLY reopen
--
commit 18d00c8fbd0dd510d3e12bc0fbf7c761642eaf15
Author: David Matlack <dmatlack@google.com>
mm/shmem_luo: Allow retrieving tmpfs files with O_RDONLY
This commit allows userspace to retrieve preserved tmpfs files read-only by
passing O_RDONLY in struct liveupdate_shmem_retrieve_arg. It creates the
temporary file as writable via file_open_root() and then reopens it read-only
with dentry_open().
> diff --git a/mm/shmem_luo.c b/mm/shmem_luo.c
> index aba2bc1f18c52..0b4a9d6e2968c 100644
> --- a/mm/shmem_luo.c
> +++ b/mm/shmem_luo.c
[ ... ]
> @@ -649,7 +650,9 @@ static struct file *shmem_luo_create_memfd(const struct liveupdate_shmem_retriev
> static struct file *shmem_luo_create_tmpfs_file(const struct liveupdate_shmem_retrieve_arg *arg,
> unsigned int seals)
> {
> - struct file *file;
> + struct file *file, *ro_file;
> + bool rdonly;
> + int flags;
> int err;
>
> if (!arg)
> @@ -673,10 +676,16 @@ static struct file *shmem_luo_create_tmpfs_file(const struct liveupdate_shmem_re
>
> /*
> * Create the file like open(O_TMPFILE) does, so that it gets the same
> - * flag checks, permission checks, owner and security labels.
> + * flag checks, permission checks, owner and security labels. open()
> + * requires write access for O_TMPFILE, so create the file writable and
> + * reopen it read-only below if that is what userspace asked for. LUO
> + * keeps the file until the session is finished, and a file opened for
> + * writing couldn't be executed until then.
> */
> + flags = O_TMPFILE | O_LARGEFILE | arg->flags;
> + rdonly = (flags & O_ACCMODE) == O_RDONLY;
> file = file_open_root(&fd_file(dir)->f_path, ".",
> - O_TMPFILE | O_LARGEFILE | arg->flags, arg->mode);
> + flags | (rdonly ? O_RDWR : 0), arg->mode);
> if (IS_ERR(file))
> return file;
>
> @@ -694,7 +703,12 @@ static struct file *shmem_luo_create_tmpfs_file(const struct liveupdate_shmem_re
> }
> SHMEM_I(file_inode(file))->seals = seals;
>
> - return file;
> + if (!rdonly)
> + return file;
> +
> + ro_file = dentry_open(&file->f_path, flags, current_cred());
> + fput(file);
> + return ro_file;
> }
>
> static int shmem_luo_retrieve(struct liveupdate_file_op_args *args)
[Severity: Medium]
When userspace retrieves a tmpfs file via LIVEUPDATE_SESSION_RETRIEVE_FD with
O_RDONLY in arg->flags, does passing the raw flags variable to dentry_open()
in shmem_luo_create_tmpfs_file() in mm/shmem_luo.c bypass the flag
normalization performed by file_open_root() -> build_open_flags()?
In fs/open.c, build_open_flags() strips O_CLOEXEC and ensures O_DSYNC is set
when __O_SYNC is present:
u64 flags = how->flags;
u64 strip = O_CLOEXEC;
...
flags &= ~strip;
...
if (flags & __O_SYNC)
flags |= O_DSYNC;
Because dentry_open() -> alloc_empty_file() -> init_file() initializes
ro_file->f_flags directly from the passed flags and do_dentry_open() only
clears:
f->f_flags &= ~(O_CREAT | O_EXCL | O_NOCTTY | O_TRUNC | __O_REGULAR);
f->f_iocb_flags = iocb_flags(f);
passing O_RDONLY | O_CLOEXEC in arg->flags leaves O_CLOEXEC in
ro_file->f_flags (visible via fcntl(fd, F_GETFL) and /proc/<pid>/fdinfo/<fd>),
and passing O_RDONLY | __O_SYNC without O_DSYNC leaves O_DSYNC and IOCB_DSYNC
unset on ro_file.
Should dentry_open() be passed (file->f_flags & ~O_ACCMODE) | O_RDONLY instead
of flags so that ro_file uses the normalized flags from file?
--
Sashiko AI review · https://sashiko.dev/#/patchset/20261022225035.377445-1-dmatlack@google.com?part=9
^ permalink raw reply [flat|nested] 20+ messages in thread
[parent not found: <20261022225035.377445-11-dmatlack@google.com>]
* Re: [PATCH v2 10/10] selftests/liveupdate: Add a test for preserving tmpfs files
[not found] ` <20261022225035.377445-11-dmatlack@google.com>
@ 2026-10-23 22:51 ` sashiko-bot
0 siblings, 0 replies; 20+ messages in thread
From: sashiko-bot @ 2026-10-23 22:51 UTC (permalink / raw)
To: David Matlack, kexec
Cc: Alexander Graf, Andrew Morton, Baolin Wang, Christian Brauner,
David Hildenbrand, Hugh Dickins, Jonathan Corbet, Liam R. Howlett,
Lorenzo Stoakes, Michal Hocko, Mike Rapoport, Pasha Tatashin,
Pratyush Yadav, Randy Dunlap, Samiullah Khawaja, Shuah Khan,
Suren Baghdasaryan, Vlastimil Babka
> Add luo_tmpfs, a kexec test for preserving tmpfs files and memfds, and
> for retrieving tmpfs files onto a tmpfs mount with struct
> liveupdate_shmem_retrieve_arg.
>
> Stage 1 preserves the source files of each test case. Stage 2 runs
> each test case in its own directory, with its own tmpfs mounts, so that
> block usage can be checked exactly with statfs() and st_blocks. The test
> cases cover:
>
> - The restored file: contents, size, position, seals and holes, for
> linked and unlinked source files.
>
> - Creating the file: it is created like with open(O_TMPFILE), with the
> requested mode minus the umask, owned by the caller, and not linked
> until userspace links it, unless it asked for O_EXCL. Also O_RDONLY
> [ ... ]
> tmpfs quotas are not tested, since the test runs as root, which is not
> subject to quota limits.
>
> Assisted-by: LLM
> Signed-off-by: David Matlack <dmatlack@google.com>
Sashiko has reviewed this patch and found no issues.
--
Sashiko AI review · https://sashiko.dev/#/patchset/20261022225035.377445-1-dmatlack@google.com?part=10
^ permalink raw reply [flat|nested] 20+ messages in thread