* [RFC PATCH v2 0/4] liveupdate: Feature header ABI and named handler
@ 2026-10-21 15:25 Tarun Sahu
2026-10-21 15:25 ` [RFC PATCH v2 1/4] liveupdate: Rename handler compatible to name Tarun Sahu
` (3 more replies)
0 siblings, 4 replies; 5+ messages in thread
From: Tarun Sahu @ 2026-10-21 15:25 UTC (permalink / raw)
To: Andrew Morton, joro, Mike Rapoport, Pratyush Yadav, maz, arnd,
David Matlack, jgg, robin.murphy, loganodell, oupton, kevin.tian,
alex, dwmw2, pbonzini, seanjc, will, Pasha Tatashin,
Alexander Graf, Bjorn Helgaas, baolu.lu
Cc: linux-arm-kernel, linux-arch, sagis, linux-mm, kvmarm, iommu,
linux-pci, kexec, skhawaja, kvm, linux-kernel, Tarun Sahu
Live Update Orchestrator (LUO) and its file handlers currently validate
handed-over KHO state using monolithic compatibility strings ("luo-v5",
"memfd-v2"). Any change to a serialized structure, even an optional
additive field, requires bumping the compatibility string, breaking live
update between adjacent kernel versions across both forward updates and
rollbacks. This series introduces the feature flags per serialized
struct to communicate between the kernel about their active and required
features. Having feature flags increase the compatibility matching
granularity.
This series also renames the file-handler and FLB compatible identifier to
name, extends LIVEUPDATE_SESSION_PRESERVE_FD so userspace can
optionally select which registered file handler preserves a file
descriptor by name, and replaces monolithic version strings in serialized
payloads with a fixed-size 2-bit pair feature header
(struct luo_feature_hdr). So in future, If a change to a subsystem
preservation can not be handled with feature flags, we can attach a new
handler to support the preservation.
This change is motivated by the LPC discussion.
Named File Handler Selection
============================
With fine-grained compatibility tracked via feature headers, the
handler and FLB compatible field is renamed to name. When a subsystem
needs a genuinely incompatible new handler alongside an existing one for
the same file type, struct liveupdate_session_preserve_fd now includes
char handler[LIVEUPDATE_HNDL_NAME_LENGTH] after token (keeping
min_size at 16 bytes for backward compatibility):
- Empty handler (or 16-byte struct): selects the first registered
handler whose .can_preserve() accepts the fd, keeping default callers
on the original rollback-safe handler.
- Non-empty handler: selects the registered handler with that exact
name and verifies .can_preserve() accepts the fd.
2-Bit Pair Feature Header (struct luo_feature_hdr)
==================================================
Each serialized ABI structure (struct luo_ser, struct memfd_luo_ser)
starts with a fixed 128-byte struct luo_feature_hdr containing:
- size: byte size of the enclosing serialized structure from the source
kernel.
- flags[15]: 15 chained 64-bit feature flags entries. Each entry
encodes up to 31 features as 2-bit pair (req, active) states
(enum luo_feat_state: LUO_FEAT_DISABLED = 0b00,
LUO_FEAT_OPTIONAL = 0b01, LUO_FEAT_REQUIRED = 0b11) plus
LUO_FEAT_EXT_MASK (bits[63:62] = 0b11) chaining to the next entry
(up to 465 features total). Because the header is fixed at 128 bytes,
payload field offsets never shift when extended flags entries are used.
Each subsystem defines its feature states as a static const u8 array
indexed by its feature enum, which luo_feature_hdr_init() packs into
the payload feature header. During deserialization,
luo_feature_hdr_validate() checks the header from the source kernel
against the feature array in the target kernel according to the
per-feature (req, active) truth table:
Source Target Action
(1,0) (x,x) Reject -EINVAL: invalid source kernel (req without active)
(x,x) (1,0) Reject -EINVAL + WARN_ON_ONCE: invalid target kernel
(0,0) (0,0) Accept: feature absent
(0,0) (0,1) Accept: feature absent
(0,0) (1,1) Reject -EOPNOTSUPP: target kernel requires the feature
(0,1) (0,0) Accept: ignore payload data
(1,1) (0,0) Reject -EOPNOTSUPP: source kernel requires the feature
(x,1) (x,1) Accept: use payload data
Structure Evolution
===================
The relative order and offsets of fields in serialized structures must
remain unchanged as features are added or retired. New fields are always
appended at the end of the structure under a new feature flag, and
retired fields are kept in place so that the offsets of subsequent
fields never shift. If the type or format of a field needs to change,
the existing field and feature are kept intact (and retired when no
longer needed) and a new feature flag with a new appended field of the
new type is added.
In the target kernel, fields of a LUO_FEAT_REQUIRED feature are
guaranteed to be present once luo_feature_hdr_validate() succeeds,
whereas fields of a LUO_FEAT_OPTIONAL feature should be accessed only
after verifying that the feature is active in the source kernel header:
if (luo_feature_is_active(&ser->features, FEAT_X))
val = ser->x_field;
Changes since RFC v1 [1]:
- Renamed file-handler and FLB compatible to name and added optional
handler selection by name in LIVEUPDATE_SESSION_PRESERVE_FD.
- Replaced the 3-mask (supp, req, active) + variable-length FDT and
feature-section design with a fixed 128-byte struct luo_feature_hdr
embedding size and 15 chained 64-bit 2-bit pair (req, active)
flags entries directly in struct luo_ser and struct memfd_luo_ser.
- Replaced compile-time bitmask macros with a static const u8 feature
array per subsystem packed by luo_feature_hdr_init() and validated
in a single pass by luo_feature_hdr_validate().
[1] https://lore.kernel.org/all/20260903023452.721732-1-loganodell@google.com/
Tarun Sahu (4):
liveupdate: Rename handler compatible to name
liveupdate: Select file handler by name in the preserve ioctl
liveupdate: Replace compatibility string with a 2-bit pair feature
header
mm: memfd_luo: Add the 2-bit pair feature header
drivers/pci/liveupdate.c | 10 +-
include/linux/kho/abi/luo.h | 367 +++++++++++++++++++++++++++----
include/linux/kho/abi/memfd.h | 39 +++-
include/linux/kho/abi/pci.h | 2 +-
include/linux/liveupdate.h | 18 +-
include/uapi/linux/liveupdate.h | 15 +-
kernel/liveupdate/luo_core.c | 27 ++-
kernel/liveupdate/luo_file.c | 64 ++++--
kernel/liveupdate/luo_flb.c | 16 +-
kernel/liveupdate/luo_internal.h | 3 +-
kernel/liveupdate/luo_session.c | 7 +-
lib/tests/liveupdate.c | 22 +-
mm/memfd_luo.c | 16 +-
13 files changed, 481 insertions(+), 125 deletions(-)
--
2.56.0.212.g129ede20d9-goog
^ permalink raw reply [flat|nested] 5+ messages in thread
* [RFC PATCH v2 1/4] liveupdate: Rename handler compatible to name
2026-10-21 15:25 [RFC PATCH v2 0/4] liveupdate: Feature header ABI and named handler Tarun Sahu
@ 2026-10-21 15:25 ` Tarun Sahu
2026-10-21 15:25 ` [RFC PATCH v2 2/4] liveupdate: Select file handler by name in the preserve ioctl Tarun Sahu
` (2 subsequent siblings)
3 siblings, 0 replies; 5+ messages in thread
From: Tarun Sahu @ 2026-10-21 15:25 UTC (permalink / raw)
To: Andrew Morton, joro, Mike Rapoport, Pratyush Yadav, maz, arnd,
David Matlack, jgg, robin.murphy, loganodell, oupton, kevin.tian,
alex, dwmw2, pbonzini, seanjc, will, Pasha Tatashin,
Alexander Graf, Bjorn Helgaas, baolu.lu
Cc: linux-arm-kernel, linux-arch, sagis, linux-mm, kvmarm, iommu,
linux-pci, kexec, skhawaja, kvm, linux-kernel, Tarun Sahu
File handler and File-Lifecycle-Bound (FLB) identifiers name the
registered handler or FLB rather than a monolithic compatibility version.
Rename compatible to name across LUO headers, structures, and consumers:
- struct liveupdate_file_handler and struct luo_file_ser name field
- LIVEUPDATE_HNDL_NAME_LENGTH (in include/uapi/linux/liveupdate.h) and
MEMFD_LUO_FH_NAME
- struct liveupdate_flb name field, LIVEUPDATE_FLB_NAME_LENGTH,
PCI_LUO_FLB_NAME, and LIVEUPDATE_TEST_FLB_NAME
Signed-off-by: Tarun Sahu <tarunsahu@google.com>
---
drivers/pci/liveupdate.c | 10 ++++----
include/linux/kho/abi/luo.h | 26 +++++++++----------
include/linux/kho/abi/memfd.h | 4 +--
include/linux/kho/abi/pci.h | 2 +-
include/linux/liveupdate.h | 18 +++++--------
include/uapi/linux/liveupdate.h | 3 +++
kernel/liveupdate/luo_file.c | 45 +++++++++++++++++----------------
kernel/liveupdate/luo_flb.c | 16 ++++++------
lib/tests/liveupdate.c | 22 ++++++++--------
mm/memfd_luo.c | 2 +-
10 files changed, 73 insertions(+), 75 deletions(-)
diff --git a/drivers/pci/liveupdate.c b/drivers/pci/liveupdate.c
index 51695782ed42..3cf01d7d615c 100644
--- a/drivers/pci/liveupdate.c
+++ b/drivers/pci/liveupdate.c
@@ -424,7 +424,7 @@ static struct liveupdate_flb_ops pci_liveupdate_flb_ops = {
static struct liveupdate_flb pci_liveupdate_flb = {
.ops = &pci_liveupdate_flb_ops,
- .compatible = PCI_LUO_FLB_COMPATIBLE,
+ .name = PCI_LUO_FLB_NAME,
};
static void pci_liveupdate_flb_put_outgoing(void)
@@ -667,13 +667,13 @@ static struct pci_flb_incoming *pci_liveupdate_flb_get_incoming(void)
/*
* Live Update is enabled and there is incoming FLB data, but none of it
- * matches pci_liveupdate_flb.compatible.
+ * matches pci_liveupdate_flb.name.
*/
if (ret == -ENOENT)
return NULL;
/*
- * There is incoming FLB data that matches pci_liveupdate_flb.compatible
+ * There is incoming FLB data that matches pci_liveupdate_flb.name
* but retrieve failed (pci_flb_retrieve() returned an error or LUO
* failed to acquire a reference to pci_liveupdate_flb_ops.owner).
*/
@@ -998,7 +998,7 @@ EXPORT_SYMBOL_GPL(pci_liveupdate_is_incoming);
*/
int pci_liveupdate_register_flb(struct liveupdate_file_handler *fh)
{
- pr_debug("Registering file handler \"%s\"\n", fh->compatible);
+ pr_debug("Registering file handler \"%s\"\n", fh->name);
return liveupdate_register_flb(fh, &pci_liveupdate_flb);
}
EXPORT_SYMBOL_GPL(pci_liveupdate_register_flb);
@@ -1009,7 +1009,7 @@ EXPORT_SYMBOL_GPL(pci_liveupdate_register_flb);
*/
void pci_liveupdate_unregister_flb(struct liveupdate_file_handler *fh)
{
- pr_debug("Unregistering file handler \"%s\"\n", fh->compatible);
+ pr_debug("Unregistering file handler \"%s\"\n", fh->name);
liveupdate_unregister_flb(fh, &pci_liveupdate_flb);
}
EXPORT_SYMBOL_GPL(pci_liveupdate_unregister_flb);
diff --git a/include/linux/kho/abi/luo.h b/include/linux/kho/abi/luo.h
index 288076de6d4a..5fb2f46425d7 100644
--- a/include/linux/kho/abi/luo.h
+++ b/include/linux/kho/abi/luo.h
@@ -39,9 +39,9 @@
* Multiple blocks are linked via the `next` field in the header.
*
* - struct luo_file_ser:
- * Metadata for a single preserved file. Contains the `compatible` string to
- * find the correct handler in the new kernel, a user-provided `token` for
- * identification, and an opaque `data` handle for the handler to use.
+ * Metadata for a single preserved file. Contains the handler `name` string
+ * to find the correct handler in the new kernel, a user-provided `token`
+ * for identification, and an opaque `data` handle for the handler to use.
*
* - struct luo_flb_header_ser:
* Header for the FLB array. Contains the total page count of the
@@ -49,9 +49,9 @@
* that follow.
*
* - struct luo_flb_ser:
- * Metadata for a single preserved global object. Contains its `name`
- * (compatible string), an opaque `data` handle, and the `count`
- * number of files depending on it.
+ * Metadata for a single preserved global object. Contains its FLB `name`
+ * string, an opaque `data` handle, and the `count` number of files
+ * depending on it.
*/
#ifndef _LINUX_KHO_ABI_LUO_H
@@ -84,18 +84,16 @@ struct luo_ser {
u64 flbs_pa;
} __packed;
-#define LIVEUPDATE_HNDL_COMPAT_LENGTH 48
-
/**
* struct luo_file_ser - Represents the serialized preserves files.
- * @compatible: File handler compatible string.
+ * @name: File handler name string.
* @data: Private data
* @token: User provided token for this file
*
* If this structure is modified, `LUO_ABI_COMPATIBLE` must be updated.
*/
struct luo_file_ser {
- char compatible[LIVEUPDATE_HNDL_COMPAT_LENGTH];
+ char name[LIVEUPDATE_HNDL_NAME_LENGTH];
u64 data;
u64 token;
} __packed;
@@ -134,7 +132,7 @@ struct luo_session_ser {
} __packed;
/* The max size is set so it can be reliably used during in serialization */
-#define LIVEUPDATE_FLB_COMPAT_LENGTH 48
+#define LIVEUPDATE_FLB_NAME_LENGTH 48
/**
* struct luo_flb_header_ser - Header for the serialized FLB data block.
@@ -156,7 +154,7 @@ struct luo_flb_header_ser {
/**
* struct luo_flb_ser - Represents the serialized state of a single FLB object.
- * @name: The unique compatibility string of the FLB object, used to find the
+ * @name: The unique name string of the FLB object, used to find the
* corresponding &struct liveupdate_flb handler in the new kernel.
* @data: The opaque u64 handle returned by the FLB's .preserve() operation
* in the old kernel. This handle encapsulates the entire state needed
@@ -172,14 +170,14 @@ struct luo_flb_header_ser {
* If this structure is modified, `LUO_ABI_COMPATIBLE` must be updated.
*/
struct luo_flb_ser {
- char name[LIVEUPDATE_FLB_COMPAT_LENGTH];
+ char name[LIVEUPDATE_FLB_NAME_LENGTH];
u64 data;
u64 count;
} __packed;
/* Kernel Live Update Test ABI */
#ifdef CONFIG_LIVEUPDATE_TEST
-#define LIVEUPDATE_TEST_FLB_COMPATIBLE(i) "liveupdate-test-flb-v" #i
+#define LIVEUPDATE_TEST_FLB_NAME(i) "liveupdate-test-flb-v" #i
#endif
#endif /* _LINUX_KHO_ABI_LUO_H */
diff --git a/include/linux/kho/abi/memfd.h b/include/linux/kho/abi/memfd.h
index 08b10fea2afc..371ffd108a79 100644
--- a/include/linux/kho/abi/memfd.h
+++ b/include/linux/kho/abi/memfd.h
@@ -87,7 +87,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 name string for memfd file handler */
+#define MEMFD_LUO_FH_NAME "memfd-v2"
#endif /* _LINUX_KHO_ABI_MEMFD_H */
diff --git a/include/linux/kho/abi/pci.h b/include/linux/kho/abi/pci.h
index 9485ed73c351..973ca5b26dbd 100644
--- a/include/linux/kho/abi/pci.h
+++ b/include/linux/kho/abi/pci.h
@@ -23,7 +23,7 @@
* incrementing the version number in the PCI_LUO_FLB_VERSION number.
*/
-#define PCI_LUO_FLB_COMPATIBLE "pci"
+#define PCI_LUO_FLB_NAME "pci"
#define PCI_LUO_FLB_VERSION 2
/**
diff --git a/include/linux/liveupdate.h b/include/linux/liveupdate.h
index 6051abc0612c..475da5fc22ee 100644
--- a/include/linux/liveupdate.h
+++ b/include/linux/liveupdate.h
@@ -90,10 +90,8 @@ struct liveupdate_file_ops {
/**
* struct liveupdate_file_handler - Represents a handler for a live-updatable file type.
* @ops: Callback functions
- * @compatible: The compatibility string (e.g., "memfd-v1", "vfiofd-v1")
- * that uniquely identifies the file type this handler
- * supports. This is matched against the compatible string
- * associated with individual &struct file instances.
+ * @name: Unique handler name string (e.g., "memfd-v2"), matched
+ * against &struct luo_file_ser.name in the target kernel.
*
* Modules that want to support live update for specific file types should
* register an instance of this structure. LUO uses this registration to
@@ -102,7 +100,7 @@ struct liveupdate_file_ops {
*/
struct liveupdate_file_handler {
const struct liveupdate_file_ops *ops;
- const char compatible[LIVEUPDATE_HNDL_COMPAT_LENGTH];
+ const char name[LIVEUPDATE_HNDL_NAME_LENGTH];
/* private: */
@@ -209,11 +207,9 @@ struct luo_flb_private {
/**
* struct liveupdate_flb - A global definition for a shared data object.
* @ops: Callback functions
- * @compatible: The compatibility string (e.g., "iommu-core-v1"
- * that uniquely identifies the FLB type this handler
- * supports. This is matched against the compatible string
- * associated with individual &struct liveupdate_flb
- * instances.
+ * @name: The FLB name string (e.g., "iommu-core-v1") that uniquely
+ * identifies this FLB type. In the target kernel, it is
+ * matched against the name stored in &struct luo_flb_ser.
*
* This struct is the "template" that a driver registers to define a shared,
* file-lifecycle-bound object. The actual runtime state (the live object,
@@ -221,7 +217,7 @@ struct luo_flb_private {
*/
struct liveupdate_flb {
const struct liveupdate_flb_ops *ops;
- const char compatible[LIVEUPDATE_FLB_COMPAT_LENGTH];
+ const char name[LIVEUPDATE_FLB_NAME_LENGTH];
/* private: */
struct luo_flb_private __private private;
diff --git a/include/uapi/linux/liveupdate.h b/include/uapi/linux/liveupdate.h
index 4043d4038712..509c6236720d 100644
--- a/include/uapi/linux/liveupdate.h
+++ b/include/uapi/linux/liveupdate.h
@@ -46,6 +46,9 @@
/* The maximum length of session name including null termination */
#define LIVEUPDATE_SESSION_NAME_LENGTH 64
+/* The maximum length of file handler name including null termination */
+#define LIVEUPDATE_HNDL_NAME_LENGTH 48
+
/* The /dev/liveupdate ioctl commands */
enum {
LIVEUPDATE_CMD_BASE = 0x00,
diff --git a/kernel/liveupdate/luo_file.c b/kernel/liveupdate/luo_file.c
index a39992da504f..fb81239bedf3 100644
--- a/kernel/liveupdate/luo_file.c
+++ b/kernel/liveupdate/luo_file.c
@@ -50,7 +50,7 @@
*
* 2. Freeze (Pre-Reboot): Just before the kexec, luo_file_freeze() is called.
* It iterates through all preserved files, calls their respective .freeze()
- * operation, and serializes their final metadata (compatible string, token,
+ * operation, and serializes their final metadata (handler name, token,
* and data handle) into a contiguous memory block for KHO.
*
* 3. Deserialize: After kexec, luo_file_deserialize() runs when session gets
@@ -220,7 +220,7 @@ static bool luo_token_is_used(struct luo_file_set *file_set, u64 token)
* 1. Validates that the @token is not already in use within the file_set.
* 2. Ensures the file_set's memory for files serialization is allocated
* (allocates if needed).
- * 3. Iterates through registered handlers, calling can_preserve() to find one
+ * 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
* and returns an opaque private data handle.
@@ -238,7 +238,7 @@ static bool luo_token_is_used(struct luo_file_set *file_set, u64 token)
* -EBUSY if the file descriptor is already preserved by another session.
* -EBADF if the file descriptor is invalid.
* -ENOSPC if the file_set is full.
- * -ENOENT if no compatible handler is found.
+ * -ENOENT if no compatible handler is found for the file type.
* -ENOMEM on memory allocation failure.
* Other erros might be returned by .preserve().
*/
@@ -456,9 +456,10 @@ static void __luo_file_unfreeze(struct luo_file_set *file_set,
* private data handle during this step.
*
* 2. Serializes Metadata: After a successful freeze, it copies the final file
- * metadata—the handler's compatible string, the user token, and the final
- * private data handle—into the pre-allocated contiguous memory buffer
- * (file_set->files) that will be handed over to the next kernel via KHO.
+ * metadata - the handler's name string, the user token, and the final
+ * private data handle - into the pre-allocated contiguous memory buffer
+ * (file_set->files) that will be handed over to the target kernel via
+ * KHO.
*
* Error Handling (Rollback):
* This function is atomic. If any handler's .freeze() operation fails, the
@@ -495,13 +496,13 @@ int luo_file_freeze(struct luo_file_set *file_set,
err = luo_file_freeze_one(file_set, luo_file);
if (err < 0) {
pr_warn("Freeze failed for token[%#0llx] handler[%s] err[%pe]\n",
- luo_file->token, luo_file->fh->compatible,
+ luo_file->token, luo_file->fh->name,
ERR_PTR(err));
goto err_unfreeze;
}
- strscpy(file_ser->compatible, luo_file->fh->compatible,
- sizeof(file_ser->compatible));
+ strscpy(file_ser->name, luo_file->fh->name,
+ sizeof(file_ser->name));
file_ser->data = luo_file->serialized_data;
file_ser->token = luo_file->token;
}
@@ -747,7 +748,7 @@ static int luo_file_deserialize_one(struct luo_file_set *file_set,
down_read(&luo_register_rwlock);
list_private_for_each_entry(fh, &luo_file_handler_list, list) {
- if (!strcmp(fh->compatible, ser->compatible)) {
+ if (!strcmp(fh->name, ser->name)) {
if (try_module_get(fh->ops->owner))
handler_found = true;
break;
@@ -756,9 +757,9 @@ static int luo_file_deserialize_one(struct luo_file_set *file_set,
up_read(&luo_register_rwlock);
if (!handler_found) {
- pr_warn("No registered handler for compatible '%.*s'\n",
- (int)sizeof(ser->compatible),
- ser->compatible);
+ pr_warn("No registered handler for name '%.*s'\n",
+ (int)sizeof(ser->name),
+ ser->name);
return -ENOENT;
}
@@ -789,9 +790,9 @@ static int luo_file_deserialize_one(struct luo_file_set *file_set,
* in-memory linked list of 'struct luo_file' instances.
*
* For each serialized entry, it performs the following steps:
- * 1. Reads the 'compatible' string.
+ * 1. Reads the handler 'name' string.
* 2. Searches the global list of registered file handlers for one that
- * matches the compatible string.
+ * matches the handler name.
* 3. Allocates a new 'struct luo_file'.
* 4. Populates the new structure with the deserialized data (token, private
* data handle) and links it to the found handler. The 'file' pointer is
@@ -884,9 +885,9 @@ void luo_file_set_destroy(struct luo_file_set *file_set)
/**
* liveupdate_register_file_handler - Register a file handler with LUO.
* @fh: Pointer to a caller-allocated &struct liveupdate_file_handler.
- * The caller must initialize this structure, including a unique
- * 'compatible' string and a valid 'fh' callbacks. This function adds the
- * handler to the global list of supported file handlers.
+ * The caller must initialize this structure, including a unique 'name'
+ * string and valid 'fh' callbacks. This function adds the handler to the
+ * global list of supported file handlers.
*
* Context: Typically called during module initialization for file types that
* support live update preservation.
@@ -908,11 +909,11 @@ int liveupdate_register_file_handler(struct liveupdate_file_handler *fh)
}
down_write(&luo_register_rwlock);
- /* Check for duplicate compatible strings */
+ /* Check for duplicate handler names */
list_private_for_each_entry(fh_iter, &luo_file_handler_list, list) {
- if (!strcmp(fh_iter->compatible, fh->compatible)) {
- pr_err("File handler registration failed: Compatible string '%s' already registered.\n",
- fh->compatible);
+ if (!strcmp(fh_iter->name, fh->name)) {
+ pr_err("File handler registration failed: Handler name '%s' already registered.\n",
+ fh->name);
err = -EEXIST;
goto err_unlock;
}
diff --git a/kernel/liveupdate/luo_flb.c b/kernel/liveupdate/luo_flb.c
index cd715a7c1d99..222c6d7f821e 100644
--- a/kernel/liveupdate/luo_flb.c
+++ b/kernel/liveupdate/luo_flb.c
@@ -178,7 +178,7 @@ static int luo_flb_retrieve_one(struct liveupdate_flb *flb)
return -ENODATA;
for (int i = 0; i < fh->header_ser->count; i++) {
- if (!strcmp(fh->ser[i].name, flb->compatible)) {
+ if (!strcmp(fh->ser[i].name, flb->name)) {
private->incoming.data = fh->ser[i].data;
refcount_set(&private->incoming.count, fh->ser[i].count);
found = true;
@@ -337,7 +337,7 @@ static void luo_flb_unregister_one(struct liveupdate_file_handler *fh,
if (!found) {
pr_warn("Failed to unregister FLB '%s': not found in file handler '%s'\n",
- flb->compatible, fh->compatible);
+ flb->name, fh->name);
return;
}
@@ -442,9 +442,9 @@ int liveupdate_register_flb(struct liveupdate_file_handler *fh,
if (luo_flb_global.count == LUO_FLB_MAX)
return -ENOSPC;
- /* Check that compatible string is unique in global list */
+ /* Check that FLB name is unique in global list */
list_private_for_each_entry(gflb, &luo_flb_global.list, private.list) {
- if (!strcmp(gflb->compatible, flb->compatible))
+ if (!strcmp(gflb->name, flb->name))
return -EEXIST;
}
@@ -596,9 +596,9 @@ void __init luo_flb_setup_incoming(u64 flbs_pa)
* preserved (i.e., its reference count is greater than zero), it writes its
* metadata into the memory region designated for Kexec Handover.
*
- * The serialized data includes the FLB's compatibility string, its opaque
- * data handle, and the final reference count. This allows the new kernel to
- * find the appropriate handler and reconstruct the FLB's state.
+ * The serialized data includes the FLB's name string, its opaque
+ * data handle, and the final reference count. This allows the target
+ * kernel to find the appropriate FLB and reconstruct its state.
*
* Context: Called from liveupdate_reboot() just before kho_finalize().
*/
@@ -614,7 +614,7 @@ void luo_flb_serialize(void)
long count = refcount_read(&private->outgoing.count);
if (count > 0) {
- strscpy(fh->ser[i].name, gflb->compatible,
+ strscpy(fh->ser[i].name, gflb->name,
sizeof(fh->ser[i].name));
fh->ser[i].data = private->outgoing.data;
fh->ser[i].count = count;
diff --git a/lib/tests/liveupdate.c b/lib/tests/liveupdate.c
index 4c08a7c6fb78..e4df09eed1f3 100644
--- a/lib/tests/liveupdate.c
+++ b/lib/tests/liveupdate.c
@@ -17,7 +17,7 @@
static const struct liveupdate_flb_ops test_flb_ops;
#define DEFINE_TEST_FLB(i) { \
.ops = &test_flb_ops, \
- .compatible = LIVEUPDATE_TEST_FLB_COMPATIBLE(i), \
+ .name = LIVEUPDATE_TEST_FLB_NAME(i), \
}
/* Number of Test FLBs to register with every file handler */
@@ -34,7 +34,7 @@ static int test_flb_preserve(struct liveupdate_flb_op_args *argp)
{
ptrdiff_t index = argp->flb - test_flbs;
- pr_info("%s: preserve was triggered\n", argp->flb->compatible);
+ pr_info("%s: preserve was triggered\n", argp->flb->name);
argp->data = TEST_FLB_MAGIC_BASE + index;
return 0;
@@ -42,7 +42,7 @@ static int test_flb_preserve(struct liveupdate_flb_op_args *argp)
static void test_flb_unpreserve(struct liveupdate_flb_op_args *argp)
{
- pr_info("%s: unpreserve was triggered\n", argp->flb->compatible);
+ pr_info("%s: unpreserve was triggered\n", argp->flb->name);
}
static int test_flb_retrieve(struct liveupdate_flb_op_args *argp)
@@ -52,11 +52,11 @@ static int test_flb_retrieve(struct liveupdate_flb_op_args *argp)
if (argp->data == expected_data) {
pr_info("%s: found flb data from the previous boot\n",
- argp->flb->compatible);
+ argp->flb->name);
argp->obj = (void *)argp->data;
} else {
pr_err("%s: ERROR - incorrect data handle: %llx, expected %llx\n",
- argp->flb->compatible, argp->data, expected_data);
+ argp->flb->name, argp->data, expected_data);
return -EINVAL;
}
@@ -69,10 +69,10 @@ static void test_flb_finish(struct liveupdate_flb_op_args *argp)
void *expected_obj = (void *)(TEST_FLB_MAGIC_BASE + index);
if (argp->obj == expected_obj) {
- pr_info("%s: finish was triggered\n", argp->flb->compatible);
+ pr_info("%s: finish was triggered\n", argp->flb->name);
} else {
pr_err("%s: ERROR - finish called with invalid object\n",
- argp->flb->compatible);
+ argp->flb->name);
}
}
@@ -103,7 +103,7 @@ static void liveupdate_test_init(void)
err = liveupdate_flb_get_incoming(flb, &obj);
if (err && err != -ENODATA && err != -ENOENT) {
pr_err("liveupdate_flb_get_incoming for %s failed: %pe\n",
- flb->compatible, ERR_PTR(err));
+ flb->name, ERR_PTR(err));
}
if (!err)
@@ -124,18 +124,18 @@ void liveupdate_test_register(struct liveupdate_file_handler *fh)
err = liveupdate_register_flb(fh, flb);
if (err) {
pr_err("Failed to register %s %pe\n",
- flb->compatible, ERR_PTR(err));
+ flb->name, ERR_PTR(err));
}
}
err = liveupdate_register_flb(fh, &test_flbs[0]);
if (!err || err != -EEXIST) {
pr_err("Failed: %s should be already registered, but got err: %pe\n",
- test_flbs[0].compatible, ERR_PTR(err));
+ test_flbs[0].name, ERR_PTR(err));
}
pr_info("Registered %d FLBs with file handler: [%s]\n",
- TEST_NFLBS, fh->compatible);
+ TEST_NFLBS, fh->name);
}
MODULE_LICENSE("GPL");
diff --git a/mm/memfd_luo.c b/mm/memfd_luo.c
index 6b68220275eb..64f8b8cb75bd 100644
--- a/mm/memfd_luo.c
+++ b/mm/memfd_luo.c
@@ -602,7 +602,7 @@ static const struct liveupdate_file_ops memfd_luo_file_ops = {
static struct liveupdate_file_handler memfd_luo_handler = {
.ops = &memfd_luo_file_ops,
- .compatible = MEMFD_LUO_FH_COMPATIBLE,
+ .name = MEMFD_LUO_FH_NAME,
};
static int __init memfd_luo_init(void)
--
2.56.0.212.g129ede20d9-goog
^ permalink raw reply [flat|nested] 5+ messages in thread
* [RFC PATCH v2 2/4] liveupdate: Select file handler by name in the preserve ioctl
2026-10-21 15:25 [RFC PATCH v2 0/4] liveupdate: Feature header ABI and named handler Tarun Sahu
2026-10-21 15:25 ` [RFC PATCH v2 1/4] liveupdate: Rename handler compatible to name Tarun Sahu
@ 2026-10-21 15:25 ` Tarun Sahu
2026-10-21 15:25 ` [RFC PATCH v2 3/4] liveupdate: Replace compatibility string with a 2-bit pair feature header Tarun Sahu
2026-10-21 15:25 ` [RFC PATCH v2 4/4] mm: memfd_luo: Add the " Tarun Sahu
3 siblings, 0 replies; 5+ messages in thread
From: Tarun Sahu @ 2026-10-21 15:25 UTC (permalink / raw)
To: Andrew Morton, joro, Mike Rapoport, Pratyush Yadav, maz, arnd,
David Matlack, jgg, robin.murphy, loganodell, oupton, kevin.tian,
alex, dwmw2, pbonzini, seanjc, will, Pasha Tatashin,
Alexander Graf, Bjorn Helgaas, baolu.lu
Cc: linux-arm-kernel, linux-arch, sagis, linux-mm, kvmarm, iommu,
linux-pci, kexec, skhawaja, kvm, linux-kernel, Tarun Sahu
Currently luo_preserve_file() iterates over luo_file_handler_list and
picks the first registered handler whose .can_preserve() callback
returns true. When a subsystem introduces a new, backwards-incompatible
file handler alongside an existing one for the same file type, userspace
has no way to choose which handler preserves a file descriptor, nor can
the kernel keep the existing rollback-safe handler as the default while
allowing callers to opt into the new handler.
Append char handler[LIVEUPDATE_HNDL_NAME_LENGTH] to struct
liveupdate_session_preserve_fd immediately after token, keeping
IOCTL_OP min_size at offsetofend(..., token) so callers passing the
original 16-byte structure continue to work:
- When handler is empty (all-zero, or zero-filled when userspace
passes the 16-byte structure), luo_preserve_file() selects the first
registered handler whose .can_preserve() callback accepts the fd.
- When handler is non-empty, it must be null-terminated within
LIVEUPDATE_HNDL_NAME_LENGTH bytes (returning -EINVAL otherwise);
luo_preserve_file() looks up the registered handler with that exact
name (returning -ENOENT if unknown) and verifies that its
.can_preserve() callback accepts the fd (returning -EINVAL if not).
The retrieve ioctl is unchanged because the handler is identified by the
name stored in the serialized payload from the source kernel.
Signed-off-by: Tarun Sahu <tarunsahu@google.com>
---
include/uapi/linux/liveupdate.h | 12 +++++++++---
kernel/liveupdate/luo_file.c | 21 ++++++++++++++++++---
kernel/liveupdate/luo_internal.h | 3 ++-
kernel/liveupdate/luo_session.c | 7 ++++++-
4 files changed, 35 insertions(+), 8 deletions(-)
diff --git a/include/uapi/linux/liveupdate.h b/include/uapi/linux/liveupdate.h
index 509c6236720d..2413b19dabc1 100644
--- a/include/uapi/linux/liveupdate.h
+++ b/include/uapi/linux/liveupdate.h
@@ -126,9 +126,14 @@ struct liveupdate_ioctl_retrieve_session {
/**
* struct liveupdate_session_preserve_fd - ioctl(LIVEUPDATE_SESSION_PRESERVE_FD)
- * @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.
+ * @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.
+ * @handler: Input; Optional null-terminated file handler name (max length
+ * %LIVEUPDATE_HNDL_NAME_LENGTH including null terminator) selecting
+ * which registered handler preserves @fd. If empty (all-zero, or
+ * omitted when passing the 16-byte structure), the kernel selects
+ * the first registered handler whose .can_preserve() accepts @fd.
*
* Holds parameters for preserving a file descriptor.
*
@@ -152,6 +157,7 @@ struct liveupdate_session_preserve_fd {
__u32 size;
__s32 fd;
__aligned_u64 token;
+ char handler[LIVEUPDATE_HNDL_NAME_LENGTH];
};
#define LIVEUPDATE_SESSION_PRESERVE_FD \
diff --git a/kernel/liveupdate/luo_file.c b/kernel/liveupdate/luo_file.c
index fb81239bedf3..be20e0f1e0aa 100644
--- a/kernel/liveupdate/luo_file.c
+++ b/kernel/liveupdate/luo_file.c
@@ -207,6 +207,8 @@ static bool luo_token_is_used(struct luo_file_set *file_set, u64 token)
* @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.
+ * @handler: Optional NUL-terminated handler name. If NULL or empty, the first
+ * registered handler whose .can_preserve() accepts @fd is selected.
*
* 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
@@ -238,11 +240,13 @@ static bool luo_token_is_used(struct luo_file_set *file_set, u64 token)
* -EBUSY if the file descriptor is already preserved by another session.
* -EBADF if the file descriptor is invalid.
* -ENOSPC if the file_set is full.
- * -ENOENT if no compatible handler is found for the file type.
+ * -ENOENT if no matching handler is registered or accepts @fd.
+ * -EINVAL if the named handler cannot preserve @fd.
* -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,
+ const char *handler)
{
struct liveupdate_file_op_args args = {0};
struct liveupdate_file_handler *fh;
@@ -266,6 +270,18 @@ int luo_preserve_file(struct luo_file_set *file_set, u64 token, int fd)
err = -ENOENT;
down_read(&luo_register_rwlock);
list_private_for_each_entry(fh, &luo_file_handler_list, list) {
+ if (handler && handler[0]) {
+ if (strcmp(fh->name, handler))
+ continue;
+ if (!fh->ops->can_preserve(fh, file)) {
+ err = -EINVAL;
+ break;
+ }
+ if (try_module_get(fh->ops->owner))
+ err = 0;
+ break;
+ }
+
if (fh->ops->can_preserve(fh, file)) {
if (try_module_get(fh->ops->owner))
err = 0;
@@ -274,7 +290,6 @@ int luo_preserve_file(struct luo_file_set *file_set, u64 token, int fd)
}
up_read(&luo_register_rwlock);
- /* err is still -ENOENT if no handler was found */
if (err)
goto err_fput;
diff --git a/kernel/liveupdate/luo_internal.h b/kernel/liveupdate/luo_internal.h
index dac6644bfb18..911ffedec9f3 100644
--- a/kernel/liveupdate/luo_internal.h
+++ b/kernel/liveupdate/luo_internal.h
@@ -101,7 +101,8 @@ 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,
+ const char *handler);
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);
diff --git a/kernel/liveupdate/luo_session.c b/kernel/liveupdate/luo_session.c
index f48e9a4185f9..194ef45907c1 100644
--- a/kernel/liveupdate/luo_session.c
+++ b/kernel/liveupdate/luo_session.c
@@ -266,8 +266,13 @@ static int luo_session_preserve_fd(struct luo_session *session,
struct liveupdate_session_preserve_fd *argp = ucmd->cmd;
int err;
+ if (strnlen(argp->handler, sizeof(argp->handler)) ==
+ sizeof(argp->handler))
+ return -EINVAL;
+
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->handler);
if (err)
return err;
--
2.56.0.212.g129ede20d9-goog
^ permalink raw reply [flat|nested] 5+ messages in thread
* [RFC PATCH v2 3/4] liveupdate: Replace compatibility string with a 2-bit pair feature header
2026-10-21 15:25 [RFC PATCH v2 0/4] liveupdate: Feature header ABI and named handler Tarun Sahu
2026-10-21 15:25 ` [RFC PATCH v2 1/4] liveupdate: Rename handler compatible to name Tarun Sahu
2026-10-21 15:25 ` [RFC PATCH v2 2/4] liveupdate: Select file handler by name in the preserve ioctl Tarun Sahu
@ 2026-10-21 15:25 ` Tarun Sahu
2026-10-21 15:25 ` [RFC PATCH v2 4/4] mm: memfd_luo: Add the " Tarun Sahu
3 siblings, 0 replies; 5+ messages in thread
From: Tarun Sahu @ 2026-10-21 15:25 UTC (permalink / raw)
To: Andrew Morton, joro, Mike Rapoport, Pratyush Yadav, maz, arnd,
David Matlack, jgg, robin.murphy, loganodell, oupton, kevin.tian,
alex, dwmw2, pbonzini, seanjc, will, Pasha Tatashin,
Alexander Graf, Bjorn Helgaas, baolu.lu
Cc: linux-arm-kernel, linux-arch, sagis, linux-mm, kvmarm, iommu,
linux-pci, kexec, skhawaja, kvm, linux-kernel, Tarun Sahu
LUO and its file handlers validate handed-over state with monolithic
compatibility strings ("luo-v5", "memfd-v2"). Any change to a serialized
structure, even an optional additive one, must bump the string, which
breaks live update between adjacent kernels. Replace the string in
struct luo_ser with a feature header, so that the target kernel can
accept or reject the source kernel state per feature.
struct luo_feature_hdr is a fixed 128-byte header: the serialized
structure size from the source kernel followed by 15 chained u64 feature
flags entries. The fixed size keeps payload field offsets identical in
every kernel, regardless of how many flags entries a source kernel uses.
Each feature has a 2-bit pair (req, active) state (enum luo_feat_state:
LUO_FEAT_DISABLED, LUO_FEAT_OPTIONAL, LUO_FEAT_REQUIRED). A flags entry
holds 31 features, and bits [63:62] = 0b11 chain to the next entry (465
features in total). Each subsystem defines its feature states as a
static const u8 array indexed by its feature enum, which
luo_feature_hdr_init() packs into 64-bit flags entries with
LUO_FEAT_EXT_MASK chain bits. The target kernel validates the header from
the source kernel against its own feature array via
luo_feature_hdr_validate() according to the per-feature (req, active)
truth table:
Source Target Action
(1,0) (x,x) Reject -EINVAL: invalid source kernel (req without active)
(x,x) (1,0) Reject -EINVAL + WARN_ON_ONCE: invalid target kernel
(0,0) (0,0) Accept: feature absent
(0,0) (0,1) Accept: feature absent
(0,0) (1,1) Reject -EOPNOTSUPP: target kernel requires the feature
(0,1) (0,0) Accept: ignore payload data
(1,1) (0,0) Reject -EOPNOTSUPP: source kernel requires the feature
(x,1) (x,1) Accept: use payload data
The relative order and offsets of fields in serialized structures must
remain unchanged as features are added or retired. New fields are always
appended at the end of the structure under a new feature flag, and
retired fields are kept in place so that the offsets of subsequent
fields never shift. If the type or format of a field needs to change, the
existing field and feature are kept intact (and retired when no longer
needed) and a new feature flag with a new appended field of the new type
is added.
In the target kernel, fields of a LUO_FEAT_REQUIRED feature are
guaranteed to be present once luo_feature_hdr_validate() succeeds,
whereas fields of a LUO_FEAT_OPTIONAL feature should be accessed only
after verifying that the feature is active in the source kernel header:
if (luo_feature_is_active(&ser->features, FEAT_X))
val = ser->x_field;
All current struct luo_ser fields (liveupdate_num, sessions_pa, flbs_pa)
belong to LUO_CORE_FEAT_CORE (LUO_FEAT_REQUIRED in luo_core_features).
Future changes add new entries to enum luo_core_feature before
LUO_CORE_FEAT_MAX.
Signed-off-by: Logan Odell <loganodell@google.com>
Signed-off-by: Tarun Sahu <tarunsahu@google.com>
---
include/linux/kho/abi/luo.h | 345 +++++++++++++++++++++++++++++++----
kernel/liveupdate/luo_core.c | 27 ++-
2 files changed, 332 insertions(+), 40 deletions(-)
diff --git a/include/linux/kho/abi/luo.h b/include/linux/kho/abi/luo.h
index 5fb2f46425d7..be35cfbfdb2b 100644
--- a/include/linux/kho/abi/luo.h
+++ b/include/linux/kho/abi/luo.h
@@ -9,19 +9,53 @@
* DOC: Live Update Orchestrator ABI
*
* Live Update Orchestrator uses the stable Application Binary Interface
- * defined below to pass state from a pre-update kernel to a post-update
- * kernel. The ABI is built upon the Kexec HandOver framework and registers
- * the central `struct luo_ser` via the KHO raw subtree API.
+ * defined below to pass state from a source kernel to a target kernel.
+ * The ABI is built upon the Kexec HandOver framework and registers the
+ * central `struct luo_ser` via the KHO raw subtree API.
*
- * This interface is a contract. Any modification to the structure fields,
- * compatible strings, or the layout of the `__packed` serialization
- * structures defined here constitutes a breaking change. Such changes require
- * incrementing the version number in the relevant `_COMPATIBLE` string to
- * prevent a new kernel from misinterpreting data from an old kernel.
+ * This interface is a contract. Serialized structures start with a fixed
+ * 128-byte feature header (`struct luo_feature_hdr`) holding the source
+ * kernel's structure `size` and 15 chained 64-bit feature flags entries
+ * (`flags[LUO_FEAT_NR_FLAGS]`). Each entry encodes up to 31 features as
+ * 2-bit `0b<req><active>` pairs (`enum luo_feat_state`), with
+ * `LUO_FEAT_EXT_MASK` in bits `[63:62]` chaining to the next entry (up to
+ * 465 features total). Because the header size is fixed at 128 bytes,
+ * payload field offsets do not depend on how many flags entries a source
+ * kernel uses.
*
- * Changes are allowed provided the compatibility version is incremented;
- * however, backward/forward compatibility is only guaranteed for kernels
- * supporting the same ABI version.
+ * Truth table (`(req,active)` pairs per feature)::
+ *
+ * Source Target Action
+ * (1,0) (x,x) Reject -EINVAL: invalid source kernel (req without active)
+ * (x,x) (1,0) Reject -EINVAL: invalid target kernel (req without active)
+ * (0,0) (0,0) Accept: feature absent
+ * (0,0) (0,1) Accept: feature absent
+ * (0,0) (1,1) Reject -EOPNOTSUPP: target kernel requires the feature
+ * (0,1) (0,0) Accept: ignore payload data
+ * (1,1) (0,0) Reject -EOPNOTSUPP: source kernel requires the feature
+ * (x,1) (x,1) Accept: use payload data
+ *
+ * The relative order and offsets of fields in serialized structures must
+ * remain unchanged as features are added or retired. New fields are always
+ * appended at the end of the structure under a new feature flag, and
+ * retired fields are kept in place so that the offsets of subsequent
+ * fields never shift. If a field's type or format needs to change, the
+ * existing field and feature are kept intact (and retired when no longer
+ * needed) and a new feature flag with a new appended field of the new type
+ * is added.
+ *
+ * In the target kernel, fields of a `LUO_FEAT_REQUIRED` feature are
+ * guaranteed to be present once `luo_feature_hdr_validate()` succeeds,
+ * whereas fields of a `LUO_FEAT_OPTIONAL` feature should be accessed only
+ * after verifying that the feature is active in the source kernel's header::
+ *
+ * if (luo_feature_is_active(&ser->features, FEAT_X))
+ * val = ser->x_field;
+ *
+ * All current `struct luo_ser` fields (`liveupdate_num`, `sessions_pa`,
+ * `flbs_pa`) belong to `LUO_CORE_FEAT_CORE` (`LUO_FEAT_REQUIRED` in
+ * `luo_core_features`). Future changes add new entries to
+ * `enum luo_core_feature` before `LUO_CORE_FEAT_MAX`.
*
* KHO Structure Overview:
* The entire LUO state is encapsulated within a single KHO entry named "LUO".
@@ -30,8 +64,9 @@
* Serialization Structures:
* - struct luo_ser:
* The central ABI structure that contains the overall state of the LUO.
- * It includes the compatibility string, the liveupdate-number, and pointers
- * to sessions and FLBs.
+ * It includes the feature header (with struct size and 2-bit feature flag
+ * pairs), and the core feature (`LUO_CORE_FEAT_CORE`) fields: the
+ * liveupdate-number and pointers to sessions and FLBs.
*
* - struct luo_session_ser:
* Metadata for a single session, including its name and a physical pointer
@@ -40,8 +75,9 @@
*
* - struct luo_file_ser:
* Metadata for a single preserved file. Contains the handler `name` string
- * to find the correct handler in the new kernel, a user-provided `token`
- * for identification, and an opaque `data` handle for the handler to use.
+ * to find the correct handler in the target kernel, a user-provided
+ * `token` for identification, and an opaque `data` handle for the handler
+ * to use.
*
* - struct luo_flb_header_ser:
* Header for the FLB array. Contains the total page count of the
@@ -57,20 +93,265 @@
#ifndef _LINUX_KHO_ABI_LUO_H
#define _LINUX_KHO_ABI_LUO_H
-#include <linux/align.h>
+#include <linux/array_size.h>
+#include <linux/bits.h>
+#include <linux/bug.h>
+#include <linux/build_bug.h>
+#include <linux/errno.h>
#include <linux/kho/abi/block.h>
+#include <linux/types.h>
#include <uapi/linux/liveupdate.h>
/*
* The LUO state is registered under this KHO entry name.
*/
#define LUO_KHO_ENTRY_NAME "LUO"
-#define LUO_ABI_COMPATIBLE "luo-v5"
-#define LUO_ABI_COMPAT_LEN ALIGN(sizeof(LUO_ABI_COMPATIBLE), 8)
+
+#define LUO_FEAT_PER_FLAGS 31
+#define LUO_FEAT_NR_FLAGS 15
+#define LUO_FEAT_MAX (LUO_FEAT_NR_FLAGS * LUO_FEAT_PER_FLAGS)
+#define LUO_FEAT_EXT_MASK GENMASK_ULL(63, 62)
+#define LUO_FEAT_ACTIVE_MASK (0x5555555555555555ULL & ~LUO_FEAT_EXT_MASK)
+#define LUO_FEAT_REQ_MASK (0xaaaaaaaaaaaaaaaaULL & ~LUO_FEAT_EXT_MASK)
+
+#define LUO_FEAT_FLAGS_IDX(f) ((unsigned int)(f) / LUO_FEAT_PER_FLAGS)
+#define LUO_FEAT_SLOT(f) ((unsigned int)(f) % LUO_FEAT_PER_FLAGS)
+
+#define LUO_FEAT_ACTIVE(f) BIT_ULL(2 * LUO_FEAT_SLOT(f))
+#define LUO_FEAT_REQ(f) BIT_ULL(2 * LUO_FEAT_SLOT(f) + 1)
+
+/**
+ * enum luo_feat_state - 2-bit state of a LUO feature (`0b<req><active>`).
+ * @LUO_FEAT_DISABLED: Feature is inactive (`req=0, active=0`).
+ * @LUO_FEAT_OPTIONAL: Feature is active and optional (`req=0, active=1`).
+ * @LUO_FEAT_REQUIRED: Feature is active and required (`req=1, active=1`).
+ */
+enum luo_feat_state {
+ LUO_FEAT_DISABLED = 0b00,
+ LUO_FEAT_OPTIONAL = 0b01,
+ LUO_FEAT_REQUIRED = 0b11,
+};
+
+/**
+ * struct luo_feature_hdr - Fixed 128-byte header for serialized LUO ABI structures.
+ * @size: Size in bytes of the source kernel's enclosing serialized structure.
+ * @flags: Chained 64-bit feature flags entries (`0 .. LUO_FEAT_NR_FLAGS - 1`).
+ * Each entry holds 31 2-bit feature pairs (`[2*slot + 1 : 2*slot]`)
+ * plus %LUO_FEAT_EXT_MASK in bits `[63:62]` when the next entry is
+ * active.
+ */
+struct luo_feature_hdr {
+ u64 size;
+ u64 flags[LUO_FEAT_NR_FLAGS];
+} __packed;
+
+static_assert(sizeof(struct luo_feature_hdr) == 128);
+
+/**
+ * luo_feature_hdr_for_each_flags - Iterate over chained flags entries in @hdr.
+ * @hdr: Pointer to the &struct luo_feature_hdr to iterate.
+ * @idx: Loop cursor of type `unsigned int` (`0 .. LUO_FEAT_NR_FLAGS - 1`).
+ */
+#define luo_feature_hdr_for_each_flags(hdr, idx) \
+ for ((idx) = 0; (idx) < LUO_FEAT_NR_FLAGS; \
+ (idx) = (((hdr)->flags[idx] & LUO_FEAT_EXT_MASK) == \
+ LUO_FEAT_EXT_MASK) ? (idx) + 1 : LUO_FEAT_NR_FLAGS)
+
+/**
+ * luo_feature_hdr_get_flags - Get the flags entry containing feature @feat.
+ * @hdr: Pointer to the source kernel's feature header.
+ * @feat: Feature index (`0 .. LUO_FEAT_MAX - 1`).
+ *
+ * Return: The u64 flags entry containing @feat if the extension chain reaches
+ * it, or 0 otherwise.
+ */
+static inline u64
+luo_feature_hdr_get_flags(const struct luo_feature_hdr *hdr,
+ unsigned int feat)
+{
+ unsigned int target_idx = LUO_FEAT_FLAGS_IDX(feat);
+ unsigned int idx;
+
+ if (target_idx >= LUO_FEAT_NR_FLAGS)
+ return 0;
+
+ luo_feature_hdr_for_each_flags(hdr, idx) {
+ if (idx == target_idx)
+ return hdr->flags[idx];
+ }
+
+ return 0;
+}
+
+/**
+ * luo_feature_is_active - Test whether feature @feat is active in @hdr.
+ * @hdr: Pointer to the source kernel's feature header.
+ * @feat: Feature index (`0 .. LUO_FEAT_MAX - 1`).
+ *
+ * Return: true if the source kernel marked @feat active, false otherwise.
+ */
+static inline bool luo_feature_is_active(const struct luo_feature_hdr *hdr,
+ unsigned int feat)
+{
+ return luo_feature_hdr_get_flags(hdr, feat) & LUO_FEAT_ACTIVE(feat);
+}
+
+/**
+ * luo_feature_is_req - Test whether feature @feat is required in @hdr.
+ * @hdr: Pointer to the source kernel's feature header.
+ * @feat: Feature index (`0 .. LUO_FEAT_MAX - 1`).
+ *
+ * Return: true if the source kernel marked @feat required, false otherwise.
+ */
+static inline bool luo_feature_is_req(const struct luo_feature_hdr *hdr,
+ unsigned int feat)
+{
+ return luo_feature_hdr_get_flags(hdr, feat) & LUO_FEAT_REQ(feat);
+}
+
+/**
+ * luo_feature_pack_entry - Pack up to 31 feature states into a flags entry.
+ * @features: Array of &enum luo_feat_state values indexed by feature ID.
+ * @nr_features: Number of elements in @features.
+ * @idx: Flags entry index (`0 .. LUO_FEAT_NR_FLAGS - 1`).
+ *
+ * Return: The packed 64-bit flags entry for @idx (without %LUO_FEAT_EXT_MASK).
+ */
+static inline u64
+luo_feature_pack_entry(const u8 *features, size_t nr_features,
+ unsigned int idx)
+{
+ unsigned int base = idx * LUO_FEAT_PER_FLAGS;
+ unsigned int slot;
+ u64 flags = 0;
+
+ for (slot = 0; slot < LUO_FEAT_PER_FLAGS && base + slot < nr_features;
+ slot++)
+ flags |= (u64)(features[base + slot] & 0b11) << (2 * slot);
+
+ return flags;
+}
+
+/**
+ * __luo_feature_hdr_init - Initialize a serialized LUO feature header.
+ * @hdr: Pointer to the feature header to initialize.
+ * @size: Byte size of the enclosing serialized structure.
+ * @features: Array of &enum luo_feat_state values for the source kernel.
+ * @nr_features: Number of elements in @features (`<= LUO_FEAT_MAX`).
+ */
+static inline void
+__luo_feature_hdr_init(struct luo_feature_hdr *hdr, u64 size,
+ const u8 *features, size_t nr_features)
+{
+ bool ext = false;
+ int idx;
+
+ hdr->size = size;
+ for (idx = LUO_FEAT_NR_FLAGS - 1; idx >= 0; idx--) {
+ u64 flags = luo_feature_pack_entry(features, nr_features, idx);
+
+ WARN_ON_ONCE(((flags & LUO_FEAT_REQ_MASK) >> 1) &
+ ~(flags & LUO_FEAT_ACTIVE_MASK));
+ if (ext)
+ flags |= LUO_FEAT_EXT_MASK;
+ else if (flags)
+ ext = true;
+ hdr->flags[idx] = flags;
+ }
+}
+
+/**
+ * luo_feature_hdr_init - Initialize a serialized LUO feature header.
+ * @hdr: Pointer to the feature header to initialize.
+ * @size: Byte size of the enclosing serialized structure.
+ * @features: Fixed-size `u8` array of &enum luo_feat_state entries.
+ */
+#define luo_feature_hdr_init(hdr, size, features) \
+({ \
+ BUILD_BUG_ON(ARRAY_SIZE(features) > LUO_FEAT_MAX); \
+ __luo_feature_hdr_init(hdr, size, features, \
+ ARRAY_SIZE(features)); \
+})
+
+/**
+ * __luo_feature_hdr_validate - Validate a source kernel's LUO feature header
+ * against the target kernel's feature array.
+ * @hdr: Pointer to the feature header from the source kernel.
+ * @features: Array of &enum luo_feat_state values for the target kernel.
+ * @nr_features: Number of elements in @features (`<= LUO_FEAT_MAX`).
+ *
+ * Return: 0 if compatible, -EINVAL on malformed header or `(req=1, active=0)`
+ * feature state, or -EOPNOTSUPP on required feature mismatch.
+ */
+static inline int
+__luo_feature_hdr_validate(const struct luo_feature_hdr *hdr,
+ const u8 *features, size_t nr_features)
+{
+ bool src_ext = true, target_ext = (nr_features > 0);
+ unsigned int idx;
+
+ if (hdr->size < sizeof(*hdr))
+ return -EINVAL;
+
+ for (idx = 0; idx < LUO_FEAT_NR_FLAGS && (src_ext || target_ext); idx++) {
+ u64 src = src_ext ? hdr->flags[idx] : 0;
+ u64 target = target_ext ?
+ luo_feature_pack_entry(features, nr_features, idx) : 0;
+ u64 ext = src & LUO_FEAT_EXT_MASK;
+ u64 src_act = src & LUO_FEAT_ACTIVE_MASK;
+ u64 src_req = (src & LUO_FEAT_REQ_MASK) >> 1;
+ u64 target_act = target & LUO_FEAT_ACTIVE_MASK;
+ u64 target_req = (target & LUO_FEAT_REQ_MASK) >> 1;
+
+ if (ext && (ext != LUO_FEAT_EXT_MASK ||
+ idx == LUO_FEAT_NR_FLAGS - 1))
+ return -EINVAL;
+ if (src_req & ~src_act)
+ return -EINVAL;
+ if (WARN_ON_ONCE(target_req & ~target_act))
+ return -EINVAL;
+ if ((src_req & ~target_act) || (target_req & ~src_act))
+ return -EOPNOTSUPP;
+
+ src_ext = (ext == LUO_FEAT_EXT_MASK);
+ target_ext = ((idx + 1) * LUO_FEAT_PER_FLAGS < nr_features);
+ }
+
+ return 0;
+}
+
+/**
+ * luo_feature_hdr_validate - Validate a source kernel's LUO feature header
+ * against the target kernel's feature array.
+ * @hdr: Pointer to the feature header from the source kernel.
+ * @features: Fixed-size `u8` array of &enum luo_feat_state entries.
+ *
+ * Return: 0 if compatible, -EINVAL on malformed header or feature state, or
+ * -EOPNOTSUPP on required feature mismatch.
+ */
+#define luo_feature_hdr_validate(hdr, features) \
+({ \
+ BUILD_BUG_ON(ARRAY_SIZE(features) > LUO_FEAT_MAX); \
+ __luo_feature_hdr_validate(hdr, features, \
+ ARRAY_SIZE(features)); \
+})
+
+/**
+ * enum luo_core_feature - Feature indices for &struct luo_ser.
+ * @LUO_CORE_FEAT_CORE: Core feature covering `liveupdate_num`, `sessions_pa`,
+ * and `flbs_pa`.
+ * @LUO_CORE_FEAT_MAX: Number of defined &enum luo_core_feature features.
+ */
+enum luo_core_feature {
+ LUO_CORE_FEAT_CORE = 0,
+ LUO_CORE_FEAT_MAX,
+};
+
+static_assert((int)LUO_CORE_FEAT_MAX <= LUO_FEAT_MAX);
/**
* struct luo_ser - Centralized LUO ABI header.
- * @compatible: Compatibility string identifying the LUO ABI version.
+ * @features: ABI feature and size header for luo_ser.
* @liveupdate_num: A counter tracking the number of successful live updates.
* @sessions_pa: Physical address of the first session block header.
* @flbs_pa: Physical address of the FLB header.
@@ -78,7 +359,7 @@
* This structure is the root of all preserved LUO state.
*/
struct luo_ser {
- char compatible[LUO_ABI_COMPAT_LEN];
+ struct luo_feature_hdr features;
u64 liveupdate_num;
u64 sessions_pa;
u64 flbs_pa;
@@ -90,7 +371,7 @@ struct luo_ser {
* @data: Private data
* @token: User provided token for this file
*
- * If this structure is modified, `LUO_ABI_COMPATIBLE` must be updated.
+ * Layout changes must be gated by a required feature in &enum luo_core_feature.
*/
struct luo_file_ser {
char name[LIVEUPDATE_HNDL_NAME_LENGTH];
@@ -121,10 +402,10 @@ struct luo_file_set_ser {
*
* This structure is used to package session-specific metadata for transfer
* between kernels via Kexec Handover. An array of these structures (one per
- * session) is created and passed to the new kernel, allowing it to reconstruct
- * the session context.
+ * session) is created and passed to the target kernel, allowing it to
+ * reconstruct the session context.
*
- * If this structure is modified, `LUO_ABI_COMPATIBLE` must be updated.
+ * Layout changes must be gated by a required feature in &enum luo_core_feature.
*/
struct luo_session_ser {
char name[LIVEUPDATE_SESSION_NAME_LENGTH];
@@ -145,7 +426,7 @@ struct luo_session_ser {
* This structure is located at the physical address specified by the
* flbs_pa in luo_ser.
*
- * If this structure is modified, `LUO_ABI_COMPATIBLE` must be updated.
+ * Layout changes must be gated by a required feature in &enum luo_core_feature.
*/
struct luo_flb_header_ser {
u64 pgcnt;
@@ -155,19 +436,19 @@ struct luo_flb_header_ser {
/**
* struct luo_flb_ser - Represents the serialized state of a single FLB object.
* @name: The unique name string of the FLB object, used to find the
- * corresponding &struct liveupdate_flb handler in the new kernel.
+ * corresponding &struct liveupdate_flb in the target kernel.
* @data: The opaque u64 handle returned by the FLB's .preserve() operation
- * in the old kernel. This handle encapsulates the entire state needed
- * for restoration.
+ * in the source kernel. This handle encapsulates the entire state
+ * needed for restoration.
* @count: The reference count at the time of serialization; i.e., the number
* of preserved files that depended on this FLB. This is used by the
- * new kernel to correctly manage the FLB's lifecycle.
+ * target kernel to correctly manage the FLB's lifecycle.
*
* An array of these structures is created in a preserved memory region and
- * passed to the new kernel. Each entry allows the LUO core to restore one
- * global, shared object.
+ * passed to the target kernel. Each entry allows the LUO core to restore
+ * one global, shared object.
*
- * If this structure is modified, `LUO_ABI_COMPATIBLE` must be updated.
+ * Layout changes must be gated by a required feature in &enum luo_core_feature.
*/
struct luo_flb_ser {
char name[LIVEUPDATE_FLB_NAME_LENGTH];
diff --git a/kernel/liveupdate/luo_core.c b/kernel/liveupdate/luo_core.c
index 1b2bda22902d..3627a206a79c 100644
--- a/kernel/liveupdate/luo_core.c
+++ b/kernel/liveupdate/luo_core.c
@@ -70,6 +70,10 @@ static struct {
u64 liveupdate_num;
} luo_global;
+static const u8 luo_core_features[LUO_CORE_FEAT_MAX] = {
+ [LUO_CORE_FEAT_CORE] = LUO_FEAT_REQUIRED,
+};
+
/*
* luo_register_rwlock - Protects registration of file handlers and FLBs.
*/
@@ -107,15 +111,21 @@ static int __init luo_early_startup(void)
return 0;
}
- if (len < sizeof(*luo_ser)) {
- pr_err("LUO state is too small (%zu < %zu)\n", len, sizeof(*luo_ser));
- return -EINVAL;
+ luo_ser = phys_to_virt(luo_ser_phys);
+ if (len < sizeof(luo_ser->features) || luo_ser->features.size > len) {
+ pr_err("LUO state size invalid (source kernel header size=%llu, KHO len=%zu)\n",
+ len >= sizeof(luo_ser->features) ? luo_ser->features.size : 0,
+ len);
+ err = -EINVAL;
+ goto out_free_ser;
}
- luo_ser = phys_to_virt(luo_ser_phys);
- if (strncmp(luo_ser->compatible, LUO_ABI_COMPATIBLE, LUO_ABI_COMPAT_LEN)) {
- pr_err("LUO state is incompatible with '%s'\n", LUO_ABI_COMPATIBLE);
- return -EINVAL;
+ err = luo_feature_hdr_validate(&luo_ser->features, luo_core_features);
+ if (err) {
+ pr_err("LUO state feature header validation failed (size=%llu, flags[0]=%#llx): %pe\n",
+ luo_ser->features.size, luo_ser->features.flags[0],
+ ERR_PTR(err));
+ goto out_free_ser;
}
luo_global.liveupdate_num = luo_ser->liveupdate_num;
@@ -162,7 +172,8 @@ static int __init luo_state_setup(void)
return PTR_ERR(luo_ser);
}
- strscpy(luo_ser->compatible, LUO_ABI_COMPATIBLE, sizeof(luo_ser->compatible));
+ luo_feature_hdr_init(&luo_ser->features, sizeof(*luo_ser),
+ luo_core_features);
luo_ser->liveupdate_num = luo_global.liveupdate_num + 1;
luo_session_setup_outgoing(&luo_ser->sessions_pa);
--
2.56.0.212.g129ede20d9-goog
^ permalink raw reply [flat|nested] 5+ messages in thread
* [RFC PATCH v2 4/4] mm: memfd_luo: Add the 2-bit pair feature header
2026-10-21 15:25 [RFC PATCH v2 0/4] liveupdate: Feature header ABI and named handler Tarun Sahu
` (2 preceding siblings ...)
2026-10-21 15:25 ` [RFC PATCH v2 3/4] liveupdate: Replace compatibility string with a 2-bit pair feature header Tarun Sahu
@ 2026-10-21 15:25 ` Tarun Sahu
3 siblings, 0 replies; 5+ messages in thread
From: Tarun Sahu @ 2026-10-21 15:25 UTC (permalink / raw)
To: Andrew Morton, joro, Mike Rapoport, Pratyush Yadav, maz, arnd,
David Matlack, jgg, robin.murphy, loganodell, oupton, kevin.tian,
alex, dwmw2, pbonzini, seanjc, will, Pasha Tatashin,
Alexander Graf, Bjorn Helgaas, baolu.lu
Cc: linux-arm-kernel, linux-arch, sagis, linux-mm, kvmarm, iommu,
linux-pci, kexec, skhawaja, kvm, linux-kernel, Tarun Sahu
Embed the fixed-size 128-byte struct luo_feature_hdr at the start of
struct memfd_luo_ser and define enum memfd_luo_feature
(MEMFD_LUO_FEAT_CORE and MEMFD_LUO_FEAT_MAX) for memfd preservation.
All current struct memfd_luo_ser fields (pos, size, seals, flags,
nr_folios, folios) and MEMFD_LUO_ALL_SEALS belong to
MEMFD_LUO_FEAT_CORE (LUO_FEAT_REQUIRED in memfd_luo_features). Future
changes add new entries to enum memfd_luo_feature before
MEMFD_LUO_FEAT_MAX and append any new fields to the end of struct
memfd_luo_ser so existing field offsets never shift.
Signed-off-by: Logan Odell <loganodell@google.com>
Signed-off-by: Tarun Sahu <tarunsahu@google.com>
---
include/linux/kho/abi/memfd.h | 35 ++++++++++++++++++++++++++++++-----
mm/memfd_luo.c | 14 ++++++++++++++
2 files changed, 44 insertions(+), 5 deletions(-)
diff --git a/include/linux/kho/abi/memfd.h b/include/linux/kho/abi/memfd.h
index 371ffd108a79..f0b5235a408d 100644
--- a/include/linux/kho/abi/memfd.h
+++ b/include/linux/kho/abi/memfd.h
@@ -13,6 +13,7 @@
#include <linux/types.h>
#include <linux/kho/abi/kexec_handover.h>
+#include <linux/kho/abi/luo.h>
/**
* DOC: memfd Live Update ABI
@@ -21,11 +22,20 @@
* reboot using the LUO.
*
* The state is serialized into a packed structure `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
- * constitutes a breaking change. Such changes require incrementing the
- * version number in the MEMFD_LUO_FH_COMPATIBLE string.
+ * This interface is a contract. `struct memfd_luo_ser` starts with a fixed
+ * 128-byte `struct luo_feature_hdr`. 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 in `enum memfd_luo_feature` (before `MEMFD_LUO_FEAT_MAX`), and
+ * retired fields are kept in place so subsequent offsets never shift. In
+ * the target kernel, fields of a `LUO_FEAT_REQUIRED` feature are guaranteed
+ * present once `luo_feature_hdr_validate()` succeeds, whereas fields of a
+ * `LUO_FEAT_OPTIONAL` feature should be accessed only after checking
+ * `luo_feature_is_active()`. All current fields (`pos`, `size`, `seals`,
+ * `flags`, `nr_folios`, `folios`) and `MEMFD_LUO_ALL_SEALS` belong to
+ * `MEMFD_LUO_FEAT_CORE` (`LUO_FEAT_REQUIRED` in `memfd_luo_features`).
*/
/**
@@ -58,7 +68,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.
+ * seals is needed, gate them behind a new feature flag.
*/
#define MEMFD_LUO_ALL_SEALS (F_SEAL_SEAL | \
F_SEAL_SHRINK | \
@@ -67,8 +77,22 @@ struct memfd_luo_folio_ser {
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,
+};
+
+static_assert((int)MEMFD_LUO_FEAT_MAX <= LUO_FEAT_MAX);
+
/**
* struct memfd_luo_ser - Main serialization structure for a memfd.
+ * @features: ABI feature and size header for memfd_luo_ser.
* @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
@@ -79,6 +103,7 @@ struct memfd_luo_folio_ser {
* struct memfd_luo_folio_ser.
*/
struct memfd_luo_ser {
+ struct luo_feature_hdr features;
u64 pos;
u64 size;
u32 seals;
diff --git a/mm/memfd_luo.c b/mm/memfd_luo.c
index 64f8b8cb75bd..ea1f4c8cd259 100644
--- a/mm/memfd_luo.c
+++ b/mm/memfd_luo.c
@@ -84,6 +84,10 @@
#include "internal.h"
+static const u8 memfd_luo_features[MEMFD_LUO_FEAT_MAX] = {
+ [MEMFD_LUO_FEAT_CORE] = LUO_FEAT_REQUIRED,
+};
+
static int memfd_luo_preserve_folios(struct file *file,
struct kho_vmalloc *kho_vmalloc,
struct memfd_luo_folio_ser **out_folios_ser,
@@ -284,6 +288,8 @@ static int memfd_luo_preserve(struct liveupdate_file_op_args *args)
goto err_free_ser;
}
+ luo_feature_hdr_init(&ser->features, sizeof(*ser),
+ memfd_luo_features);
ser->pos = args->file->f_pos;
inode_size = i_size_read(inode);
@@ -525,6 +531,14 @@ static int memfd_luo_retrieve(struct liveupdate_file_op_args *args)
if (!ser)
return -EINVAL;
+ err = luo_feature_hdr_validate(&ser->features, memfd_luo_features);
+ if (err) {
+ pr_err("memfd_luo_ser feature header validation failed (size=%llu, flags[0]=%#llx): %pe\n",
+ ser->features.size, ser->features.flags[0],
+ ERR_PTR(err));
+ goto free_ser;
+ }
+
/* Make sure the file only has seals supported by this version. */
if (ser->seals & ~MEMFD_LUO_ALL_SEALS) {
err = -EOPNOTSUPP;
--
2.56.0.212.g129ede20d9-goog
^ permalink raw reply [flat|nested] 5+ messages in thread
end of thread, other threads:[~2026-10-21 15:25 UTC | newest]
Thread overview: 5+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2026-10-21 15:25 [RFC PATCH v2 0/4] liveupdate: Feature header ABI and named handler Tarun Sahu
2026-10-21 15:25 ` [RFC PATCH v2 1/4] liveupdate: Rename handler compatible to name Tarun Sahu
2026-10-21 15:25 ` [RFC PATCH v2 2/4] liveupdate: Select file handler by name in the preserve ioctl Tarun Sahu
2026-10-21 15:25 ` [RFC PATCH v2 3/4] liveupdate: Replace compatibility string with a 2-bit pair feature header Tarun Sahu
2026-10-21 15:25 ` [RFC PATCH v2 4/4] mm: memfd_luo: Add the " Tarun Sahu
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox
all inboxes | Powered by JetHome®