From: David Matlack <[email protected]> Relax memfd_luo_can_preserve() to allow general shmem-backed files beyond unlinked memfd pseudofiles, enabling userspace to preserve standard tmpfs files across kexec.
Record MEMFD_LUO_FLAG_TMPFS in struct memfd_luo_ser.flags when a linked tmpfs file is preserved so that tmpfs files must be retrieved as tmpfs files and anonymous memfd files must be retrieved as memfd files. To support restoring preserved tmpfs folios to a newly created tmpfs file on the post-kexec filesystem, introduce struct liveupdate_memfd_retrieve_arg in the UAPI and wire it up via .rarg on memfd_luo_file_ops. When retrieving a preserved tmpfs file, memfd_luo_retrieve() requires this argument, validates that the specified file descriptor is a writable, empty, linked tmpfs file not already claimed by LUO, locks its inode, and populates its address space with the preserved folios. Validation failures on the user-provided retrieve argument are marked retryable so userspace can retry LIVEUPDATE_SESSION_RETRIEVE_FD without losing the preserved state. Signed-off-by: David Matlack <[email protected]> Co-developed-by: Pasha Tatashin <[email protected]> Signed-off-by: Pasha Tatashin <[email protected]> --- include/linux/kho/abi/memfd.h | 13 ++- include/uapi/linux/liveupdate.h | 21 +++++ mm/memfd_luo.c | 139 +++++++++++++++++++++++++------- 3 files changed, 141 insertions(+), 32 deletions(-) diff --git a/include/linux/kho/abi/memfd.h b/include/linux/kho/abi/memfd.h index 08b10fea2afc8..47ff5ab967501 100644 --- a/include/linux/kho/abi/memfd.h +++ b/include/linux/kho/abi/memfd.h @@ -68,7 +68,18 @@ struct memfd_luo_folio_ser { F_SEAL_EXEC) /** - * struct memfd_luo_ser - Main serialization structure for a memfd. + * MEMFD_LUO_FLAG_TMPFS - The preserved file is a tmpfs file. + * + * Indicates the file was preserved from a linked tmpfs file rather than an + * anonymous memfd, and must be retrieved to an empty tmpfs file. + */ +#define MEMFD_LUO_FLAG_TMPFS BIT(0) + +/* The set of flags this version supports preserving. */ +#define MEMFD_LUO_ALL_FLAGS MEMFD_LUO_FLAG_TMPFS + +/** + * struct memfd_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 diff --git a/include/uapi/linux/liveupdate.h b/include/uapi/linux/liveupdate.h index 0ab28fd58fa5f..24e938d91392c 100644 --- a/include/uapi/linux/liveupdate.h +++ b/include/uapi/linux/liveupdate.h @@ -268,4 +268,25 @@ struct liveupdate_session_freeze { #define LIVEUPDATE_SESSION_FREEZE \ _IO(LIVEUPDATE_IOCTL_TYPE, LIVEUPDATE_CMD_SESSION_FREEZE) +/** + * struct liveupdate_memfd_retrieve_arg - Argument for tmpfs file retrieval + * @size: Input; sizeof(struct liveupdate_memfd_retrieve_arg) + * @fd: Input; Writable, empty, linked tmpfs file descriptor to restore into. + * + * Passed via &struct liveupdate_session_retrieve_fd.arg on + * %LIVEUPDATE_SESSION_RETRIEVE_FD. This argument is required when retrieving + * a preserved linked tmpfs file and is rejected with -EINVAL when retrieving + * an anonymous memfd. + * + * @fd must refer to a writable, empty, linked tmpfs file that is not already + * claimed by LUO. On success, the file is populated with the preserved folios, + * its size and f_pos are restored, and the file descriptor returned in + * &struct liveupdate_session_retrieve_fd.fd references the same underlying open + * file description as @fd. + */ +struct liveupdate_memfd_retrieve_arg { + __u32 size; + __s32 fd; +}; + #endif /* _UAPI_LIVEUPDATE_H */ diff --git a/mm/memfd_luo.c b/mm/memfd_luo.c index 26929495786e0..9d18f01191c07 100644 --- a/mm/memfd_luo.c +++ b/mm/memfd_luo.c @@ -9,14 +9,14 @@ */ /** - * 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 and generic tmpfs files can be preserved over a + * kexec using the LUO file preservation. This allows userspace to transfer its + * memory contents to the next kernel after a kexec. * * The preservation is not intended to be transparent. Only select properties of * the file are preserved. All others are reset to default. The preserved @@ -24,16 +24,17 @@ * * .. 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. * * .. note:: - * Currently a memfd backed by Hugetlb is not supported. Memfds created + * Currently a file backed by Hugetlb is not supported. Memfds created * with ``MFD_HUGETLB`` will be rejected. * * Preserved Properties * ==================== * - * The following properties of the memfd are preserved across kexec: + * The following properties of the memfd/tmpfs file are preserved across kexec: * * File Contents * All data stored in the file is preserved. @@ -300,6 +301,8 @@ static int memfd_luo_preserve(struct liveupdate_file_op_args *args) ser->size = inode_size; ser->seals = seals; + if (inode->i_nlink) + ser->flags |= MEMFD_LUO_FLAG_TMPFS; err = memfd_luo_preserve_folios(args->file, &ser->folios, &folios_ser, &nr_folios); @@ -516,10 +519,60 @@ static int memfd_luo_retrieve_folios(struct file *file, return err; } +static struct file *memfd_luo_get_tmpfs_file(struct liveupdate_file_op_args *args) +{ + const struct liveupdate_memfd_retrieve_arg *rarg = args->arg; + struct inode *inode; + struct file *file; + int err; + + if (!rarg) + return ERR_PTR(-EINVAL); + + file = fget(rarg->fd); + if (!file) + return ERR_PTR(-EBADF); + + if (!(file->f_mode & FMODE_WRITE)) { + err = -EACCES; + goto put_file; + } + + if (!shmem_file(file)) { + err = -EINVAL; + goto put_file; + } + + inode = file_inode(file); + inode_lock(inode); + if (!inode->i_nlink) { + err = -EINVAL; + goto unlock_inode; + } + + if (i_size_read(inode) != 0 || !mapping_empty(inode->i_mapping)) { + err = -EBUSY; + goto unlock_inode; + } + + err = liveupdate_claim_file(args->handler, file); + if (err) + goto unlock_inode; + + return file; + +unlock_inode: + inode_unlock(inode); +put_file: + fput(file); + return ERR_PTR(err); +} + static int memfd_luo_retrieve(struct liveupdate_file_op_args *args) { struct memfd_luo_folio_ser *folios_ser; struct memfd_luo_ser *ser; + struct inode *inode; struct file *file; int err; @@ -527,50 +580,75 @@ static int memfd_luo_retrieve(struct liveupdate_file_op_args *args) if (!ser) return -EINVAL; - /* Make sure the file only has seals supported by this version. */ - if (ser->seals & ~MEMFD_LUO_ALL_SEALS) { + /* Make sure the file only has seals and flags supported by this version. */ + if ((ser->seals & ~MEMFD_LUO_ALL_SEALS) || + (ser->flags & ~MEMFD_LUO_ALL_FLAGS)) { err = -EOPNOTSUPP; goto free_ser; } - /* - * 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); - err = PTR_ERR(file); - goto free_ser; - } + if (ser->flags & MEMFD_LUO_FLAG_TMPFS) { + file = memfd_luo_get_tmpfs_file(args); + if (IS_ERR(file)) { + args->retryable = true; + return PTR_ERR(file); + } + inode = file_inode(file); + } else { + if (args->arg) { + args->retryable = true; + return -EINVAL; + } - err = memfd_add_seals(file, ser->seals); - if (err) { - pr_err("failed to add seals: %pe\n", ERR_PTR(err)); - goto put_file; - } + /* + * 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); + err = PTR_ERR(file); + goto free_ser; + } - vfs_setpos(file, ser->pos, MAX_LFS_FILESIZE); - i_size_write(file_inode(file), ser->size); + err = memfd_add_seals(file, ser->seals); + if (err) { + pr_err("failed to add seals: %pe\n", ERR_PTR(err)); + goto put_file; + } + + inode = file_inode(file); + inode_lock(inode); + } if (ser->nr_folios) { folios_ser = kho_restore_vmalloc(&ser->folios); if (!folios_ser) { err = -EINVAL; - goto put_file; + goto unclaim_file; } err = memfd_luo_retrieve_folios(file, folios_ser, ser->nr_folios); vfree(folios_ser); if (err) - goto put_file; + goto unclaim_file; } + vfs_setpos(file, ser->pos, MAX_LFS_FILESIZE); + i_size_write(inode, ser->size); + inode_unlock(inode); + args->file = file; kho_restore_free(ser); return 0; +unclaim_file: + if (ser->flags & MEMFD_LUO_FLAG_TMPFS) { + shmem_truncate_range(inode, 0, (loff_t)-1); + liveupdate_unclaim_file(args->handler, file); + } + inode_unlock(inode); put_file: fput(file); free_ser: @@ -581,9 +659,7 @@ static int memfd_luo_retrieve(struct liveupdate_file_op_args *args) static bool memfd_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 memfd_luo_get_id(struct file *file) @@ -599,6 +675,7 @@ static const struct liveupdate_file_ops memfd_luo_file_ops = { .unpreserve = memfd_luo_unpreserve, .can_preserve = memfd_luo_can_preserve, .get_id = memfd_luo_get_id, + .rarg = LIVEUPDATE_ARG(struct liveupdate_memfd_retrieve_arg, fd), .owner = THIS_MODULE, }; -- 2.56.0.385.gd3acb90ef8-goog

