Precede struct memfd_luo_ser with the 8-byte struct luo_ser_hdr for memfd preservation. The source kernel writes the state through struct memfd_luo_ser_wrap, declared with DECLARE_LUO_SER_WRAP(): DECLARE_LUO_SER_WRAP(memfd_luo_ser, 64, MEMFD_LUO_FEAT_MAX); This consists of hdr + memfd_luo_ser (payload). The existing struct memfd_luo_ser fields (pos, size, seals, flags, nr_folios, folios, and MEMFD_LUO_ALL_SEALS) form MEMFD_LUO_FEAT_CORE (index 0 of enum memfd_luo_feature). The source kernel marks it as LUO_FEAT_REQUIRED when preserving state, and the target kernel marks it as supported (true) in its memfd_luo_supported_feats table passed to luo_ser_hdr_validate(). Future changes append any new fields to the end of struct memfd_luo_ser under a new feature index so existing field offsets never shift. memfd_luo_retrieve() and memfd_luo_finish() validate the incoming header against memfd_luo_supported_feats with luo_ser_hdr_validate() before accessing any payload field; finish warns and skips discarding the folios when the header is invalid. Prepending the header shifts every field of the serialized memfd state, so bump MEMFD_LUO_FH_COMPATIBLE from memfd-v2 to memfd-v3. Signed-off-by: Logan Odell Signed-off-by: Tarun Sahu --- include/linux/kho/abi/memfd.h | 32 +++++++++++++++-- mm/memfd_luo.c | 68 ++++++++++++++++++++++++----------- 2 files changed, 77 insertions(+), 23 deletions(-) diff --git a/include/linux/kho/abi/memfd.h b/include/linux/kho/abi/memfd.h index 7115a8a77cfd..d467d18fe6e8 100644 --- a/include/linux/kho/abi/memfd.h +++ b/include/linux/kho/abi/memfd.h @@ -14,6 +14,7 @@ #include #include #include +#include /** * DOC: memfd Live Update ABI @@ -22,9 +23,18 @@ * reboot using the LUO. * * The state is serialized into `struct memfd_luo_ser` - * which is handed over to the next kernel via the KHO mechanism. + * which is handed over to the target kernel via the KHO mechanism. * - * This interface is a contract. Any modification to the structure layout + * This interface is a contract. The serialized state starts with an + * 8-byte `struct luo_ser_hdr` followed by `struct memfd_luo_ser`. + * The relative order and offsets of fields must remain unchanged as + * features are added or retired: new fields are appended at the end of + * `struct memfd_luo_ser` under a new feature flag, and retired fields are + * kept in place so subsequent offsets never shift. In the target kernel, + * the fields of a feature are read only after `luo_ser_get_feat()` shows + * the feature present. + * + * Any modification to the structure layout beyond feature extensions * constitutes a breaking change. Such changes require incrementing the * version number in the MEMFD_LUO_FH_COMPATIBLE string. */ @@ -70,6 +80,17 @@ static_assert(sizeof(struct memfd_luo_folio_ser) == 16); F_SEAL_FUTURE_WRITE | \ F_SEAL_EXEC) +/** + * enum memfd_luo_feature - Feature indices for &struct memfd_luo_ser. + * @MEMFD_LUO_FEAT_CORE: Core feature covering `pos`, `size`, `seals`, `flags`, + * `nr_folios`, `folios`, and %MEMFD_LUO_ALL_SEALS. + * @MEMFD_LUO_FEAT_MAX: Number of defined &enum memfd_luo_feature features. + */ +enum memfd_luo_feature { + MEMFD_LUO_FEAT_CORE = 0, + MEMFD_LUO_FEAT_MAX, +}; + /** * struct memfd_luo_ser - Main serialization structure for a memfd. * @pos: The file's current position (f_pos). @@ -80,6 +101,8 @@ static_assert(sizeof(struct memfd_luo_folio_ser) == 16); * @nr_folios: Number of folios in the folios array. * @folios: KHO vmalloc descriptor pointing to the array of * struct memfd_luo_folio_ser. + * + * Preceded by &struct luo_ser_hdr. */ struct memfd_luo_ser { u64 pos; @@ -92,7 +115,10 @@ struct memfd_luo_ser { static_assert(sizeof(struct memfd_luo_ser) == 48); +/* memfd state written by this kernel. Never cast incoming state to it. */ +DECLARE_LUO_SER_WRAP(memfd_luo_ser, 64, MEMFD_LUO_FEAT_MAX); + /* The compatibility string for memfd file handler */ -#define MEMFD_LUO_FH_COMPATIBLE "memfd-v2" +#define MEMFD_LUO_FH_COMPATIBLE "memfd-v3" #endif /* _LINUX_KHO_ABI_MEMFD_H */ diff --git a/mm/memfd_luo.c b/mm/memfd_luo.c index 6b68220275eb..85424a5fbc20 100644 --- a/mm/memfd_luo.c +++ b/mm/memfd_luo.c @@ -84,6 +84,10 @@ #include "internal.h" +static const bool memfd_luo_supported_feats[MEMFD_LUO_FEAT_MAX] = { + [MEMFD_LUO_FEAT_CORE] = true, +}; + static int memfd_luo_preserve_folios(struct file *file, struct kho_vmalloc *kho_vmalloc, struct memfd_luo_folio_ser **out_folios_ser, @@ -258,6 +262,7 @@ static int memfd_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_wrap *wrap; struct memfd_luo_ser *ser; u64 nr_folios, inode_size; int err = 0, seals; @@ -266,9 +271,9 @@ static int memfd_luo_preserve(struct liveupdate_file_op_args *args) shmem_freeze(inode, true); /* Allocate the main serialization structure in preserved memory */ - ser = kho_alloc_preserve(sizeof(*ser)); - if (IS_ERR(ser)) { - err = PTR_ERR(ser); + wrap = kho_alloc_preserve(sizeof(*wrap)); + if (IS_ERR(wrap)) { + err = PTR_ERR(wrap); goto err_unlock; } @@ -284,6 +289,10 @@ static int memfd_luo_preserve(struct liveupdate_file_op_args *args) goto err_free_ser; } + memfd_luo_ser_wrap_init(wrap); + luo_ser_set_feat(&wrap->hdr, MEMFD_LUO_FEAT_CORE, LUO_FEAT_REQUIRED); + + ser = &wrap->ser; ser->pos = args->file->f_pos; inode_size = i_size_read(inode); @@ -308,12 +317,12 @@ static int memfd_luo_preserve(struct liveupdate_file_op_args *args) inode_unlock(inode); args->private_data = folios_ser; - args->serialized_data = virt_to_phys(ser); + args->serialized_data = virt_to_phys(wrap); return 0; err_free_ser: - kho_unpreserve_free(ser); + kho_unpreserve_free(wrap); err_unlock: shmem_freeze(inode, false); inode_unlock(inode); @@ -322,18 +331,18 @@ static int memfd_luo_preserve(struct liveupdate_file_op_args *args) static int memfd_luo_freeze(struct liveupdate_file_op_args *args) { - struct memfd_luo_ser *ser; + struct memfd_luo_ser_wrap *wrap; if (WARN_ON_ONCE(!args->serialized_data)) return -EINVAL; - ser = phys_to_virt(args->serialized_data); + wrap = phys_to_virt(args->serialized_data); /* * The pos might have changed since prepare. Everything else stays the * same. */ - ser->pos = args->file->f_pos; + wrap->ser.pos = args->file->f_pos; return 0; } @@ -341,7 +350,7 @@ static int memfd_luo_freeze(struct liveupdate_file_op_args *args) static void memfd_luo_unpreserve(struct liveupdate_file_op_args *args) { struct inode *inode = file_inode(args->file); - struct memfd_luo_ser *ser; + struct memfd_luo_ser_wrap *wrap; if (WARN_ON_ONCE(!args->serialized_data)) return; @@ -349,12 +358,12 @@ static void memfd_luo_unpreserve(struct liveupdate_file_op_args *args) inode_lock(inode); shmem_freeze(inode, false); - ser = phys_to_virt(args->serialized_data); + wrap = phys_to_virt(args->serialized_data); - memfd_luo_unpreserve_folios(&ser->folios, args->private_data, - ser->nr_folios); + memfd_luo_unpreserve_folios(&wrap->ser.folios, args->private_data, + wrap->ser.nr_folios); - kho_unpreserve_free(ser); + kho_unpreserve_free(wrap); inode_unlock(inode); } @@ -385,6 +394,7 @@ 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) { + struct luo_ser_hdr *hdr; struct memfd_luo_folio_ser *folios_ser; struct memfd_luo_ser *ser; @@ -396,10 +406,17 @@ static void memfd_luo_finish(struct liveupdate_file_op_args *args) if (args->retrieve_status) return; - ser = phys_to_virt(args->serialized_data); - if (!ser) + if (WARN_ON_ONCE(!args->serialized_data)) return; + hdr = phys_to_virt(args->serialized_data); + if (luo_ser_hdr_validate(hdr, memfd_luo_supported_feats, + ARRAY_SIZE(memfd_luo_supported_feats))) { + pr_warn("invalid feature header, folios not discarded\n"); + goto out; + } + + ser = luo_ser_payload(hdr); if (ser->nr_folios) { folios_ser = kho_restore_vmalloc(&ser->folios); if (!folios_ser) @@ -410,7 +427,7 @@ static void memfd_luo_finish(struct liveupdate_file_op_args *args) } out: - kho_restore_free(ser); + kho_restore_free(hdr); } static int memfd_luo_retrieve_folios(struct file *file, @@ -516,15 +533,26 @@ static int memfd_luo_retrieve_folios(struct file *file, static int memfd_luo_retrieve(struct liveupdate_file_op_args *args) { + struct luo_ser_hdr *hdr; struct memfd_luo_folio_ser *folios_ser; struct memfd_luo_ser *ser; struct file *file; int err; - ser = phys_to_virt(args->serialized_data); - if (!ser) + if (WARN_ON_ONCE(!args->serialized_data)) return -EINVAL; + hdr = phys_to_virt(args->serialized_data); + err = luo_ser_hdr_validate(hdr, memfd_luo_supported_feats, + ARRAY_SIZE(memfd_luo_supported_feats)); + if (err) { + pr_err("memfd_luo_ser feature header validation failed (size=%u, nr_features=%u): %pe\n", + hdr->size, hdr->nr_features, ERR_PTR(err)); + goto free_ser; + } + + ser = luo_ser_payload(hdr); + /* Make sure the file only has seals supported by this version. */ if (ser->seals & ~MEMFD_LUO_ALL_SEALS) { err = -EOPNOTSUPP; @@ -565,14 +593,14 @@ static int memfd_luo_retrieve(struct liveupdate_file_op_args *args) } args->file = file; - kho_restore_free(ser); + kho_restore_free(hdr); return 0; put_file: fput(file); free_ser: - kho_restore_free(ser); + kho_restore_free(hdr); return err; } -- 2.56.0.307.g5fe7bd680c-goog