Re: [PATCH v4 03/19] kho, memfd_luo: Reference struct kho_vmalloc via serialized pointer
From: Pasha Tatashin
Date: Tue Nov 10 2026 - 09:31:20 EST
> Subsystems preserving vmalloc allocations currently embed struct
> kho_vmalloc directly by value in their serialized structures or device
> tree properties. For example, struct memfd_luo_ser embeds struct
> kho_vmalloc folios, and test_kho writes the structure directly into the
> folios_info FDT property.
>
> Embedding struct kho_vmalloc directly couples the containing layout's
> field offsets and size to struct kho_vmalloc. Any modification to struct
> kho_vmalloc shifts subsequent fields in the containing structure and
> breaks the handover ABI across kexec.
>
> Convert callers to hold a DECLARE_KHOSER_PTR pointer to struct
> kho_vmalloc instead of embedding it by value. In memfd_luo, allocate the
> main structure and struct kho_vmalloc in the same preserved folio, placing
> struct kho_vmalloc in the remaining 8-byte-aligned space, and bump the
> compatibility string to memfd-v3. In test_kho, place struct kho_vmalloc
> in the remaining space of the FDT folio, store the serialized pointer in
> the folios_info property, and bump the compatibility string to
> kho-test-v2.
>
> Signed-off-by: Tarun Sahu <tarunsahu@xxxxxxxxxx>
>
> diff --git a/include/linux/kho/abi/kexec_handover.h b/include/linux/kho/abi/kexec_handover.h
> index 6f12f5037ea5..bee75a7006e8 100644
> --- a/include/linux/kho/abi/kexec_handover.h
> +++ b/include/linux/kho/abi/kexec_handover.h
> @@ -11,6 +11,8 @@
> #define _LINUX_KHO_ABI_KEXEC_HANDOVER_H
>
> #include <linux/bits.h>
> +#include <linux/build_bug.h>
> +#include <linux/kconfig.h>
> #include <linux/log2.h>
> #include <linux/math.h>
> #include <linux/types.h>
> @@ -177,6 +179,10 @@ struct kho_vmalloc {
> unsigned short order;
> };
>
> +static_assert(sizeof(struct kho_vmalloc) == 16);
> +static_assert(!IS_ENABLED(CONFIG_64BIT) ||
> + __alignof__(struct kho_vmalloc) == 8);
> +
> /**
> * DOC: KHO persistent memory tracker
> *
> diff --git a/include/linux/kho/abi/memfd.h b/include/linux/kho/abi/memfd.h
> index 376a724a1d8c..7b49dd6d7e2a 100644
> --- a/include/linux/kho/abi/memfd.h
> +++ b/include/linux/kho/abi/memfd.h
> @@ -81,7 +81,7 @@ static_assert(!IS_ENABLED(CONFIG_64BIT) ||
> * to directly use them in the ABI.
> * @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
> + * @folios: Pointer to KHO vmalloc descriptor for the array of
> * struct memfd_luo_folio_ser.
> */
> struct memfd_luo_ser {
> @@ -90,14 +90,14 @@ struct memfd_luo_ser {
> u32 seals;
> u32 flags;
> u64 nr_folios;
> - struct kho_vmalloc folios;
> + DECLARE_KHOSER_PTR(folios, struct kho_vmalloc *);
> };
>
> -static_assert(sizeof(struct memfd_luo_ser) == 48);
> +static_assert(sizeof(struct memfd_luo_ser) == 40);
> static_assert(!IS_ENABLED(CONFIG_64BIT) ||
> __alignof__(struct memfd_luo_ser) == 8);
>
> /* 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/lib/test_kho.c b/lib/test_kho.c
> index aa6a0956bb8b..cd0f183ff68e 100644
> --- a/lib/test_kho.c
> +++ b/lib/test_kho.c
> @@ -25,7 +25,7 @@
>
> #define KHO_TEST_MAGIC 0x4b484f21 /* KHO! */
> #define KHO_TEST_FDT "kho_test"
> -#define KHO_TEST_COMPAT "kho-test-v1"
> +#define KHO_TEST_COMPAT "kho-test-v2"
>
> static long max_mem = (PAGE_SIZE << MAX_PAGE_ORDER) * 2;
> module_param(max_mem, long, 0644);
> @@ -34,7 +34,7 @@ struct kho_test_state {
> unsigned int nr_folios;
> struct folio **folios;
> phys_addr_t *folios_info;
> - struct kho_vmalloc folios_info_phys;
> + DECLARE_KHOSER_PTR(folios_info_phys, struct kho_vmalloc *);
> int nr_folios_preserved;
> struct folio *fdt;
> __wsum csum;
> @@ -47,13 +47,13 @@ static void kho_test_unpreserve_data(struct kho_test_state *state)
> for (int i = 0; i < state->nr_folios_preserved; i++)
> kho_unpreserve_folio(state->folios[i]);
>
> - kho_unpreserve_vmalloc(&state->folios_info_phys);
> + if (KHOSER_LOAD_PTR(state->folios_info_phys))
> + kho_unpreserve_vmalloc(KHOSER_LOAD_PTR(state->folios_info_phys));
> vfree(state->folios_info);
> }
>
> static int kho_test_preserve_data(struct kho_test_state *state)
> {
> - struct kho_vmalloc folios_info_phys;
> phys_addr_t *folios_info;
> int err;
>
> @@ -61,11 +61,10 @@ static int kho_test_preserve_data(struct kho_test_state *state)
> if (!folios_info)
> return -ENOMEM;
>
> - err = kho_preserve_vmalloc(folios_info, &folios_info_phys);
> + err = kho_preserve_vmalloc(folios_info, KHOSER_LOAD_PTR(state->folios_info_phys));
> if (err)
> goto err_free_info;
>
> - state->folios_info_phys = folios_info_phys;
> state->folios_info = folios_info;
>
> for (int i = 0; i < state->nr_folios; i++) {
> @@ -123,14 +122,19 @@ static int kho_test_prepare_fdt(struct kho_test_state *state, ssize_t fdt_size)
>
> static int kho_test_preserve(struct kho_test_state *state)
> {
> - ssize_t fdt_size;
> + struct kho_vmalloc *folios_vmalloc;
> + ssize_t fdt_size, alloc_size;
> int err;
>
> fdt_size = state->nr_folios * sizeof(phys_addr_t) + PAGE_SIZE;
> - state->fdt = folio_alloc(GFP_KERNEL, get_order(fdt_size));
> + alloc_size = fdt_size + sizeof(struct kho_vmalloc);
> + state->fdt = folio_alloc(GFP_KERNEL, get_order(alloc_size));
> if (!state->fdt)
> return -ENOMEM;
>
> + folios_vmalloc = folio_address(state->fdt) + fdt_size;
> + KHOSER_STORE_PTR(state->folios_info_phys, folios_vmalloc);
> +
> err = kho_preserve_folio(state->fdt);
> if (err)
> goto err_free_fdt;
> @@ -234,7 +238,8 @@ static int kho_test_save(void)
>
> static int kho_test_restore_data(const void *fdt, int node)
> {
> - const struct kho_vmalloc *folios_info_phys;
> + const DECLARE_KHOSER_PTR(*folios_info_phys, struct kho_vmalloc *);
> + const struct kho_vmalloc *folios_vmalloc;
> const unsigned int *nr_folios;
> phys_addr_t *folios_info;
> const __wsum *old_csum;
> @@ -255,7 +260,11 @@ static int kho_test_restore_data(const void *fdt, int node)
> if (!folios_info_phys || len != sizeof(*folios_info_phys))
> return -EINVAL;
>
> - folios_info = kho_restore_vmalloc(folios_info_phys);
> + folios_vmalloc = KHOSER_LOAD_PTR(*folios_info_phys);
> + if (!folios_vmalloc)
> + return -EINVAL;
> +
> + folios_info = kho_restore_vmalloc(folios_vmalloc);
> if (!folios_info)
> return -EINVAL;
Why do we need DECLARE_KHOSER_PTR() in test_kho.c at all?
DECLARE_KHOSER_PTR() makes sense in shared ABI structs like struct
memfd_luo_ser where both kernels reference the same struct field and get
type checking between KHOSER_STORE_PTR() and KHOSER_LOAD_PTR().
Here, struct kho_test_state is purely local to the saving kernel and the
handover ABI is just the "folios_info" FDT property carrying a u64
physical address. Having two unrelated DECLARE_KHOSER_PTR() declarations
doesn't buy any cross-kernel type safety
Can we drop DECLARE_KHOSER_PTR() here, keep a plain pointer in struct
kho_test_state, serialize a u64 phys addr into the "folios_info" FDT
property, and read it back as const u64 * + phys_to_virt() like the rest
of the KHO FDT code?
--
Pasha