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.

Also provide liveupdate_claim_file() and liveupdate_unclaim_file() so
handlers whose .retrieve() callback restores state into an existing
user-supplied file descriptor can claim it in luo_preserved_files before
modifying the file.

Signed-off-by: Pasha Tatashin <[email protected]>
---
 include/linux/liveupdate.h       | 60 +++++++++++++++++++
 include/uapi/linux/liveupdate.h  |  6 ++
 kernel/liveupdate/luo_file.c     | 99 +++++++++++++++++++++++++++++---
 kernel/liveupdate/luo_internal.h |  5 +-
 kernel/liveupdate/luo_session.c  |  6 +-
 5 files changed, 164 insertions(+), 12 deletions(-)

diff --git a/include/linux/liveupdate.h b/include/linux/liveupdate.h
index 885ec0bbc4b8c..52d3f453ae011 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.
@@ -39,6 +69,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.
@@ -49,6 +84,7 @@ struct liveupdate_file_op_args {
        struct file *file;
        u64 serialized_data;
        void *private_data;
+       const void *arg;
 };
 
 /**
@@ -67,6 +103,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
@@ -83,6 +125,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;
 };
 
@@ -237,6 +281,11 @@ int liveupdate_reboot(void);
 int liveupdate_register_file_handler(struct liveupdate_file_handler *fh);
 void liveupdate_unregister_file_handler(struct liveupdate_file_handler *fh);
 
+int liveupdate_claim_file(struct liveupdate_file_handler *fh,
+                         struct file *file);
+void liveupdate_unclaim_file(struct liveupdate_file_handler *fh,
+                            struct file *file);
+
 int liveupdate_register_flb(struct liveupdate_file_handler *fh,
                            struct liveupdate_flb *flb);
 void liveupdate_unregister_flb(struct liveupdate_file_handler *fh,
@@ -269,6 +318,17 @@ static inline void 
liveupdate_unregister_file_handler(struct liveupdate_file_han
 {
 }
 
+static inline int liveupdate_claim_file(struct liveupdate_file_handler *fh,
+                                       struct file *file)
+{
+       return -EOPNOTSUPP;
+}
+
+static inline void liveupdate_unclaim_file(struct liveupdate_file_handler *fh,
+                                          struct file *file)
+{
+}
+
 static inline int liveupdate_register_flb(struct liveupdate_file_handler *fh,
                                          struct liveupdate_flb *flb)
 {
diff --git a/include/uapi/linux/liveupdate.h b/include/uapi/linux/liveupdate.h
index 1644c502521e0..57a3c129d5a9d 100644
--- a/include/uapi/linux/liveupdate.h
+++ b/include/uapi/linux/liveupdate.h
@@ -127,6 +127,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.
  *
@@ -150,6 +152,7 @@ struct liveupdate_session_preserve_fd {
        __u32           size;
        __s32           fd;
        __aligned_u64   token;
+       __aligned_u64   arg;
 };
 
 #define LIVEUPDATE_SESSION_PRESERVE_FD                                 \
@@ -161,6 +164,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.
  *
@@ -180,6 +185,7 @@ 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 3c1c86ff14d69..e3bfdbc6baedc 100644
--- a/kernel/liveupdate/luo_file.c
+++ b/kernel/liveupdate/luo_file.c
@@ -112,6 +112,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);
@@ -176,6 +177,36 @@ static unsigned long luo_get_id(struct 
liveupdate_file_handler *fh,
        return fh->ops->get_id ? fh->ops->get_id(file) : (unsigned long)file;
 }
 
+/**
+ * liveupdate_claim_file - Claim a file in the LUO preserved files table.
+ * @fh:   The file handler claiming @file.
+ * @file: The file to claim.
+ *
+ * File handlers whose .retrieve() callback restores state into an existing
+ * user-supplied &struct file (rather than allocating a new one) should call
+ * this before modifying @file to ensure @file is not already managed by LUO.
+ *
+ * Return: 0 on success, -EBUSY if @file is already managed by LUO, or a
+ *         negative errno on allocation failure.
+ */
+int liveupdate_claim_file(struct liveupdate_file_handler *fh,
+                         struct file *file)
+{
+       return xa_insert(&luo_preserved_files, luo_get_id(fh, file),
+                        file, GFP_KERNEL);
+}
+
+/**
+ * liveupdate_unclaim_file - Release a claimed file from the LUO preserved 
files table.
+ * @fh:   The file handler that claimed @file.
+ * @file: The file to release.
+ */
+void liveupdate_unclaim_file(struct liveupdate_file_handler *fh,
+                            struct file *file)
+{
+       xa_erase(&luo_preserved_files, luo_get_id(fh, file));
+}
+
 static bool luo_token_is_used(struct luo_file_set *file_set, u64 token)
 {
        struct luo_file *iter;
@@ -188,11 +219,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
@@ -208,9 +273,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.
@@ -225,10 +292,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;
@@ -264,10 +333,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)
@@ -290,6 +363,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);
@@ -303,6 +377,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:
@@ -533,6 +609,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'.
  *
@@ -552,9 +629,10 @@ 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.
  *         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};
@@ -591,9 +669,14 @@ 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.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;
@@ -604,9 +687,9 @@ int luo_retrieve_file(struct luo_file_set *file_set, u64 
token,
        /* 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));
+       if (xa_load(&luo_preserved_files,
+                   luo_get_id(luo_file->fh, luo_file->file)) != luo_file->file)
+               WARN_ON(liveupdate_claim_file(luo_file->fh, luo_file->file));
 
        *filep = luo_file->file;
        luo_file->retrieve_status = 1;
diff --git a/kernel/liveupdate/luo_internal.h b/kernel/liveupdate/luo_internal.h
index b42369c3b1f30..3bd9ef442934e 100644
--- a/kernel/liveupdate/luo_internal.h
+++ b/kernel/liveupdate/luo_internal.h
@@ -87,13 +87,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 403906937bc0f..74f8a1bf1da8c 100644
--- a/kernel/liveupdate/luo_session.c
+++ b/kernel/liveupdate/luo_session.c
@@ -284,7 +284,8 @@ static int luo_session_preserve_fd(struct luo_session 
*session,
        if (session->frozen)
                return -EBUSY;
 
-       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;
 
@@ -307,7 +308,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.385.gd3acb90ef8-goog


Reply via email to