kho_add_subtree() assumes the fdt argument is always an FDT and calls
fdt_totalsize() on it in the debugfs code path. This assumption will
break if a caller passes arbitrary data instead of an FDT.
When CONFIG_KEXEC_HANDOVER_DEBUGFS is enabled, kho_debugfs_fdt_add()
calls __kho_debugfs_fdt_add(), which executes:
f->wrapper.size = fdt_totalsize(fdt);
Fix this by adding an explicit size parameter to kho_add_subtree() so
callers specify the blob size. This allows subtrees to contain
arbitrary data formats, not just FDTs. Update all callers:
- memblock.c: use fdt_totalsize(fdt)
- luo_core.c: use fdt_totalsize(fdt_out)
- test_kho.c: use fdt_totalsize()
- kexec_handover.c (root fdt): use fdt_totalsize(kho_out.fdt)
Also update kho_in_debugfs_init() to compute sizes using fdt_totalsize()
for the root and sub-FDTs it processes, since these are known to be
actual FDT blobs.
Suggested-by: Pratyush Yadav <pratyush@kernel.org>
Reviewed-by: Mike Rapoport (Microsoft) <rppt@kernel.org>
Signed-off-by: Breno Leitao <leitao@debian.org>
---
include/linux/kexec_handover.h | 4 ++--
kernel/liveupdate/kexec_handover.c | 8 +++++---
kernel/liveupdate/kexec_handover_debugfs.c | 15 +++++++++------
kernel/liveupdate/kexec_handover_internal.h | 5 +++--
kernel/liveupdate/luo_core.c | 3 ++-
lib/test_kho.c | 3 ++-
mm/memblock.c | 2 +-
7 files changed, 24 insertions(+), 16 deletions(-)
diff --git a/include/linux/kexec_handover.h b/include/linux/kexec_handover.h
index ac4129d1d7416..abb1d324f42d0 100644
--- a/include/linux/kexec_handover.h
+++ b/include/linux/kexec_handover.h
@@ -32,7 +32,7 @@ void kho_restore_free(void *mem);
struct folio *kho_restore_folio(phys_addr_t phys);
struct page *kho_restore_pages(phys_addr_t phys, unsigned long nr_pages);
void *kho_restore_vmalloc(const struct kho_vmalloc *preservation);
-int kho_add_subtree(const char *name, void *fdt);
+int kho_add_subtree(const char *name, void *fdt, size_t size);
void kho_remove_subtree(void *fdt);
int kho_retrieve_subtree(const char *name, phys_addr_t *phys);
@@ -97,7 +97,7 @@ static inline void *kho_restore_vmalloc(const struct kho_vmalloc *preservation)
return NULL;
}
-static inline int kho_add_subtree(const char *name, void *fdt)
+static inline int kho_add_subtree(const char *name, void *fdt, size_t size)
{
return -EOPNOTSUPP;
}
diff --git a/kernel/liveupdate/kexec_handover.c b/kernel/liveupdate/kexec_handover.c
index 8a2b2a7e50fc6..ad2da9e4e6a04 100644
--- a/kernel/liveupdate/kexec_handover.c
+++ b/kernel/liveupdate/kexec_handover.c
@@ -726,6 +726,7 @@ static void __init kho_reserve_scratch(void)
* kho_add_subtree - record the physical address of a sub FDT in KHO root tree.
* @name: name of the sub tree.
* @fdt: the sub tree blob.
+ * @size: size of the blob in bytes.
*
* Creates a new child node named @name in KHO root FDT and records
* the physical address of @fdt. The pages of @fdt must also be preserved
@@ -737,7 +738,7 @@ static void __init kho_reserve_scratch(void)
*
* Return: 0 on success, error code on failure
*/
-int kho_add_subtree(const char *name, void *fdt)
+int kho_add_subtree(const char *name, void *fdt, size_t size)
{
phys_addr_t phys = virt_to_phys(fdt);
void *root_fdt = kho_out.fdt;
@@ -762,7 +763,7 @@ int kho_add_subtree(const char *name, void *fdt)
if (err < 0)
goto out_pack;
- WARN_ON_ONCE(kho_debugfs_fdt_add(&kho_out.dbg, name, fdt, false));
+ WARN_ON_ONCE(kho_debugfs_fdt_add(&kho_out.dbg, name, fdt, size, false));
out_pack:
fdt_pack(root_fdt);
@@ -1402,7 +1403,8 @@ static __init int kho_init(void)
}
WARN_ON_ONCE(kho_debugfs_fdt_add(&kho_out.dbg, "fdt",
- kho_out.fdt, true));
+ kho_out.fdt,
+ fdt_totalsize(kho_out.fdt), true));
return 0;
diff --git a/kernel/liveupdate/kexec_handover_debugfs.c b/kernel/liveupdate/kexec_handover_debugfs.c
index 2abbf62ba9424..64970c88c483c 100644
--- a/kernel/liveupdate/kexec_handover_debugfs.c
+++ b/kernel/liveupdate/kexec_handover_debugfs.c
@@ -24,7 +24,7 @@ struct fdt_debugfs {
};
static int __kho_debugfs_fdt_add(struct list_head *list, struct dentry *dir,
- const char *name, const void *fdt)
+ const char *name, const void *fdt, size_t size)
{
struct fdt_debugfs *f;
struct dentry *file;
@@ -34,7 +34,7 @@ static int __kho_debugfs_fdt_add(struct list_head *list, struct dentry *dir,
return -ENOMEM;
f->wrapper.data = (void *)fdt;
- f->wrapper.size = fdt_totalsize(fdt);
+ f->wrapper.size = size;
file = debugfs_create_blob(name, 0400, dir, &f->wrapper);
if (IS_ERR(file)) {
@@ -49,7 +49,7 @@ static int __kho_debugfs_fdt_add(struct list_head *list, struct dentry *dir,
}
int kho_debugfs_fdt_add(struct kho_debugfs *dbg, const char *name,
- const void *fdt, bool root)
+ const void *fdt, size_t size, bool root)
{
struct dentry *dir;
@@ -58,7 +58,7 @@ int kho_debugfs_fdt_add(struct kho_debugfs *dbg, const char *name,
else
dir = dbg->sub_fdt_dir;
- return __kho_debugfs_fdt_add(&dbg->fdt_list, dir, name, fdt);
+ return __kho_debugfs_fdt_add(&dbg->fdt_list, dir, name, fdt, size);
}
void kho_debugfs_fdt_remove(struct kho_debugfs *dbg, void *fdt)
@@ -130,7 +130,8 @@ __init void kho_in_debugfs_init(struct kho_debugfs *dbg, const void *fdt)
goto err_rmdir;
}
- err = __kho_debugfs_fdt_add(&dbg->fdt_list, dir, "fdt", fdt);
+ err = __kho_debugfs_fdt_add(&dbg->fdt_list, dir, "fdt", fdt,
+ fdt_totalsize(fdt));
if (err)
goto err_rmdir;
@@ -138,6 +139,7 @@ __init void kho_in_debugfs_init(struct kho_debugfs *dbg, const void *fdt)
int len = 0;
const char *name = fdt_get_name(fdt, child, NULL);
const u64 *fdt_phys;
+ void *sub_fdt;
fdt_phys = fdt_getprop(fdt, child, "fdt", &len);
if (!fdt_phys)
@@ -147,8 +149,9 @@ __init void kho_in_debugfs_init(struct kho_debugfs *dbg, const void *fdt)
name, len);
continue;
}
+ sub_fdt = phys_to_virt(*fdt_phys);
err = __kho_debugfs_fdt_add(&dbg->fdt_list, sub_fdt_dir, name,
- phys_to_virt(*fdt_phys));
+ sub_fdt, fdt_totalsize(sub_fdt));
if (err) {
pr_warn("failed to add fdt %s to debugfs: %pe\n", name,
ERR_PTR(err));
diff --git a/kernel/liveupdate/kexec_handover_internal.h b/kernel/liveupdate/kexec_handover_internal.h
index 0202c85ad14f9..a51f97f0fa0e6 100644
--- a/kernel/liveupdate/kexec_handover_internal.h
+++ b/kernel/liveupdate/kexec_handover_internal.h
@@ -30,7 +30,7 @@ int kho_debugfs_init(void);
void kho_in_debugfs_init(struct kho_debugfs *dbg, const void *fdt);
int kho_out_debugfs_init(struct kho_debugfs *dbg);
int kho_debugfs_fdt_add(struct kho_debugfs *dbg, const char *name,
- const void *fdt, bool root);
+ const void *fdt, size_t size, bool root);
void kho_debugfs_fdt_remove(struct kho_debugfs *dbg, void *fdt);
#else
static inline int kho_debugfs_init(void) { return 0; }
@@ -38,7 +38,8 @@ static inline void kho_in_debugfs_init(struct kho_debugfs *dbg,
const void *fdt) { }
static inline int kho_out_debugfs_init(struct kho_debugfs *dbg) { return 0; }
static inline int kho_debugfs_fdt_add(struct kho_debugfs *dbg, const char *name,
- const void *fdt, bool root) { return 0; }
+ const void *fdt, size_t size,
+ bool root) { return 0; }
static inline void kho_debugfs_fdt_remove(struct kho_debugfs *dbg,
void *fdt) { }
#endif /* CONFIG_KEXEC_HANDOVER_DEBUGFS */
diff --git a/kernel/liveupdate/luo_core.c b/kernel/liveupdate/luo_core.c
index dda7bb57d421c..a4721813dd994 100644
--- a/kernel/liveupdate/luo_core.c
+++ b/kernel/liveupdate/luo_core.c
@@ -172,7 +172,8 @@ static int __init luo_fdt_setup(void)
if (err)
goto exit_free;
- err = kho_add_subtree(LUO_FDT_KHO_ENTRY_NAME, fdt_out);
+ err = kho_add_subtree(LUO_FDT_KHO_ENTRY_NAME, fdt_out,
+ fdt_totalsize(fdt_out));
if (err)
goto exit_free;
luo_global.fdt_out = fdt_out;
diff --git a/lib/test_kho.c b/lib/test_kho.c
index a20fafaf9846b..f2d7d9108cf41 100644
--- a/lib/test_kho.c
+++ b/lib/test_kho.c
@@ -143,7 +143,8 @@ static int kho_test_preserve(struct kho_test_state *state)
if (err)
goto err_unpreserve_data;
- err = kho_add_subtree(KHO_TEST_FDT, folio_address(state->fdt));
+ err = kho_add_subtree(KHO_TEST_FDT, folio_address(state->fdt),
+ fdt_totalsize(folio_address(state->fdt)));
if (err)
goto err_unpreserve_data;
diff --git a/mm/memblock.c b/mm/memblock.c
index b3ddfdec7a809..91d4162eec63f 100644
--- a/mm/memblock.c
+++ b/mm/memblock.c
@@ -2510,7 +2510,7 @@ static int __init prepare_kho_fdt(void)
if (err)
goto err_unpreserve_fdt;
- err = kho_add_subtree(MEMBLOCK_KHO_FDT, fdt);
+ err = kho_add_subtree(MEMBLOCK_KHO_FDT, fdt, fdt_totalsize(fdt));
if (err)
goto err_unpreserve_fdt;
--
2.47.3
Hi Breno, On Tue, Jan 27 2026, Breno Leitao wrote: > kho_add_subtree() assumes the fdt argument is always an FDT and calls > fdt_totalsize() on it in the debugfs code path. This assumption will > break if a caller passes arbitrary data instead of an FDT. > > When CONFIG_KEXEC_HANDOVER_DEBUGFS is enabled, kho_debugfs_fdt_add() > calls __kho_debugfs_fdt_add(), which executes: > > f->wrapper.size = fdt_totalsize(fdt); > > Fix this by adding an explicit size parameter to kho_add_subtree() so > callers specify the blob size. This allows subtrees to contain > arbitrary data formats, not just FDTs. Update all callers: > > - memblock.c: use fdt_totalsize(fdt) > - luo_core.c: use fdt_totalsize(fdt_out) > - test_kho.c: use fdt_totalsize() > - kexec_handover.c (root fdt): use fdt_totalsize(kho_out.fdt) > > Also update kho_in_debugfs_init() to compute sizes using fdt_totalsize() > for the root and sub-FDTs it processes, since these are known to be > actual FDT blobs. No, this doesn't seem right. The "fdt" field that kho_in_debugfs_init() uses is the same "fdt" field where we put our non-FDT blobs. So I don't see how these can be known to be actual FDTs. All this happened to work because so far all users were FDT, but I bet it will break if you use your blob here. Perhaps give it a try and see if I am understanding this correctly? To be honest, I didn't think of this when I suggested you use the size parameter. We have lost the size information, and it is hard for kho_in_debugfs_init() to find out since it has no idea what the underlying format is. One option is to have it in the KHO FDT, but I am not sure that is a good idea. Adding to ABI for debug feature sounds odd (not that I am completely against it, it just feels off). Another would be to give users a hook to populate the blobs when they call kho_retrieve_subtree(), so they can figure out how large the blob needs to be. This has another benefit: once we move away from FDT, it makes little sense to dump the blob since userspace won't have a way to parse it. Even with FDT, userspace still can't parse everything. For example, say the FDT has a reference to a struct kho_vmalloc. You'd get a pointer to the head of the list, but you would have no way of knowing what is inside the vmalloc buffer. This has the downside of not being able to show anything if the subsystem never calls kho_retrieve_subtree(). Unfortunately I don't have much time this week to dive deeper into this. These are only things off the top of my head and I haven't thought too deeply, so please don't take them as strong suggestions. It would be great if you can think a bit more about the problem and come up with a recommendation? I will try to get back to this series in the next 1-2 weeks and hopefully find some way to make progress. I skimmed the rest of the patches and they all LGTM at a high level. [...] -- Regards, Pratyush Yadav
© 2016 - 2026 Red Hat, Inc.