Restore code should not use phys_to_virt() on physical addresses from restored metadata until it verifies that the range was preserved by KHO. Add kho_is_preserved() so callers can check a preserved page range without restoring, freeing, or taking ownership of the pages. Signed-off-by: Cris Jacob Maamor --- include/linux/kexec_handover.h | 6 +++++ kernel/liveupdate/kexec_handover.c | 35 ++++++++++++++++++++++++++++++ 2 files changed, 41 insertions(+) diff --git a/include/linux/kexec_handover.h b/include/linux/kexec_handover.h index 8968c56d2d73..fb09943ab232 100644 --- a/include/linux/kexec_handover.h +++ b/include/linux/kexec_handover.h @@ -19,6 +19,7 @@ struct page; #ifdef CONFIG_KEXEC_HANDOVER bool kho_is_enabled(void); bool is_kho_boot(void); +bool kho_is_preserved(phys_addr_t phys, unsigned long nr_pages); int kho_preserve_folio(struct folio *folio); void kho_unpreserve_folio(struct folio *folio); @@ -51,6 +52,11 @@ static inline bool is_kho_boot(void) return false; } +static inline bool kho_is_preserved(phys_addr_t phys, unsigned long nr_pages) +{ + return false; +} + static inline int kho_preserve_folio(struct folio *folio) { return -EOPNOTSUPP; diff --git a/kernel/liveupdate/kexec_handover.c b/kernel/liveupdate/kexec_handover.c index 94762de1fe5f..fe9f11190705 100644 --- a/kernel/liveupdate/kexec_handover.c +++ b/kernel/liveupdate/kexec_handover.c @@ -10,6 +10,7 @@ #define pr_fmt(fmt) "KHO: " fmt +#include #include #include #include @@ -429,6 +430,40 @@ static struct page *kho_restore_page(phys_addr_t phys, bool is_folio) return page; } +/** + * kho_is_preserved - Verify that a physical page range belongs to KHO. + * @phys: physical address of the first page in the range. + * @nr_pages: number of pages that the caller expects to access. + * + * Use this before phys_to_virt() when a physical address comes from restored + * metadata. It checks that @phys starts a KHO-preserved allocation large + * enough to cover @nr_pages. + * + * This only checks the KHO marker. It does not restore, free, or take + * ownership of the pages. + * + * Return: true if @phys starts a preserved KHO allocation large enough to cover + * @nr_pages, false otherwise. + */ +bool kho_is_preserved(phys_addr_t phys, unsigned long nr_pages) +{ + struct page *page; + union kho_page_info info; + + if (!nr_pages || !IS_ALIGNED(phys, PAGE_SIZE)) + return false; + + page = pfn_to_online_page(PHYS_PFN(phys)); + if (!page) + return false; + + info.page_private = page->private; + if (info.magic != KHO_PAGE_MAGIC || info.order >= BITS_PER_LONG) + return false; + + return nr_pages <= BIT(info.order); +} + /** * kho_restore_folio - recreates the folio from the preserved memory. * @phys: physical address of the folio. -- 2.53.0 LUO gets the restored FDT address from KHO and maps it with phys_to_virt(). Check the FDT size and make sure the address range is KHO-preserved before mapping it. Reject empty or oversized FDT metadata. Signed-off-by: Cris Jacob Maamor --- kernel/liveupdate/luo_core.c | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/kernel/liveupdate/luo_core.c b/kernel/liveupdate/luo_core.c index 803f51c84275..633a16434164 100644 --- a/kernel/liveupdate/luo_core.c +++ b/kernel/liveupdate/luo_core.c @@ -82,6 +82,7 @@ early_param("liveupdate", early_liveupdate_param); static int __init luo_early_startup(void) { + size_t fdt_size; phys_addr_t fdt_phys; int err, ln_size; const void *ptr; @@ -94,7 +95,8 @@ static int __init luo_early_startup(void) } /* Retrieve LUO subtree, and verify its format. */ - err = kho_retrieve_subtree(LUO_FDT_KHO_ENTRY_NAME, &fdt_phys, NULL); + err = kho_retrieve_subtree(LUO_FDT_KHO_ENTRY_NAME, &fdt_phys, + &fdt_size); if (err) { if (err != -ENOENT) { pr_err("failed to retrieve FDT '%s' from KHO: %pe\n", @@ -105,6 +107,12 @@ static int __init luo_early_startup(void) return 0; } + if (!fdt_size || fdt_size > LUO_FDT_SIZE || + !kho_is_preserved(fdt_phys, DIV_ROUND_UP(fdt_size, PAGE_SIZE))) { + pr_err("Invalid LUO FDT from KHO\n"); + return -EINVAL; + } + luo_global.fdt_in = phys_to_virt(fdt_phys); err = fdt_node_check_compatible(luo_global.fdt_in, 0, LUO_FDT_COMPATIBLE); -- 2.53.0 The restored FDT contains the physical address of the LUO session header, which LUO maps before reading session metadata. Check that physical range against KHO-preserved memory before calling phys_to_virt(), and abort the restore if the physical range is not covered. The session header has a count that controls how far LUO walks the serialized session array. Check that count against LUO_SESSION_MAX before deserialization, and abort the restore if the value is out of range. Signed-off-by: Cris Jacob Maamor --- kernel/liveupdate/luo_session.c | 22 ++++++++++++++++++++-- 1 file changed, 20 insertions(+), 2 deletions(-) diff --git a/kernel/liveupdate/luo_session.c b/kernel/liveupdate/luo_session.c index a3327a28fc1f..0244c071936d 100644 --- a/kernel/liveupdate/luo_session.c +++ b/kernel/liveupdate/luo_session.c @@ -501,7 +501,18 @@ int __init luo_session_setup_incoming(void *fdt_in) } header_ser_pa = get_unaligned((u64 *)ptr); + if (!kho_is_preserved(header_ser_pa, LUO_SESSION_PGCNT)) { + pr_err("Session header is not KHO preserved: %#llx\n", + (unsigned long long)header_ser_pa); + return -EINVAL; + } + header_ser = phys_to_virt(header_ser_pa); + if (header_ser->count > LUO_SESSION_MAX) { + pr_err("Invalid session count: %llu\n", + (unsigned long long)header_ser->count); + return -EINVAL; + } luo_session_global.incoming.header_ser = header_ser; luo_session_global.incoming.ser = (void *)(header_ser + 1); @@ -515,6 +526,7 @@ int luo_session_deserialize(void) struct luo_session_header *sh = &luo_session_global.incoming; static bool is_deserialized; static int err; + u64 count; /* If has been deserialized, always return the same error code */ if (is_deserialized) @@ -524,6 +536,13 @@ int luo_session_deserialize(void) if (!sh->active) return 0; + count = sh->header_ser->count; + if (count > LUO_SESSION_MAX) { + pr_err("Invalid session count: %llu\n", + (unsigned long long)count); + return -EINVAL; + } + /* * Note on error handling: * @@ -539,7 +558,7 @@ int luo_session_deserialize(void) * userspace to detect the failure and trigger a reboot, which will * reliably reset devices and reclaim memory. */ - for (int i = 0; i < sh->header_ser->count; i++) { + for (u64 i = 0; i < count; i++) { struct luo_session *session; session = luo_session_alloc(sh->ser[i].name); @@ -606,4 +625,3 @@ int luo_session_serialize(void) return err; } - -- 2.53.0 The restored session metadata provides the LUO file set address and count. LUO maps that address with phys_to_virt() and uses the restored count to walk the serialized file array. Reject invalid empty file set metadata, reject counts above LUO_FILE_MAX, and check that the physical range is KHO-preserved before mapping it. Signed-off-by: Cris Jacob Maamor --- kernel/liveupdate/luo_file.c | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/kernel/liveupdate/luo_file.c b/kernel/liveupdate/luo_file.c index a0a419085e28..cde43d822f8f 100644 --- a/kernel/liveupdate/luo_file.c +++ b/kernel/liveupdate/luo_file.c @@ -783,11 +783,21 @@ int luo_file_deserialize(struct luo_file_set *file_set, struct luo_file_ser *file_ser; u64 i; - if (!file_set_ser->files) { - WARN_ON(file_set_ser->count); + if (!file_set_ser->count) { + if (file_set_ser->files) + return -EINVAL; return 0; } + if (file_set_ser->count > LUO_FILE_MAX) + return -EINVAL; + + if (!file_set_ser->files) + return -EINVAL; + + if (!kho_is_preserved(file_set_ser->files, LUO_FILE_PGCNT)) + return -EINVAL; + file_set->count = file_set_ser->count; file_set->files = phys_to_virt(file_set_ser->files); -- 2.53.0 The restored FDT contains the physical address of the LUO FLB header, which LUO maps before using the restored FLB metadata. Check that the FLB header range is KHO-preserved before calling phys_to_virt(). Reject invalid page counts and counts above LUO_FLB_MAX before walking the restored FLB array. Signed-off-by: Cris Jacob Maamor --- kernel/liveupdate/luo_flb.c | 23 ++++++++++++++++++++++- 1 file changed, 22 insertions(+), 1 deletion(-) diff --git a/kernel/liveupdate/luo_flb.c b/kernel/liveupdate/luo_flb.c index 00f5494812c4..e80032669cea 100644 --- a/kernel/liveupdate/luo_flb.c +++ b/kernel/liveupdate/luo_flb.c @@ -162,6 +162,7 @@ static int luo_flb_retrieve_one(struct liveupdate_flb *flb) struct luo_flb_header *fh = &luo_flb_global.incoming; struct liveupdate_flb_op_args args = {0}; bool found = false; + u64 count; int err; guard(mutex)(&private->incoming.lock); @@ -175,7 +176,14 @@ static int luo_flb_retrieve_one(struct liveupdate_flb *flb) if (!fh->active) return -ENODATA; - for (int i = 0; i < fh->header_ser->count; i++) { + count = fh->header_ser->count; + if (count > LUO_FLB_MAX) { + pr_err("Invalid FLB count: %llu\n", + (unsigned long long)count); + return -EINVAL; + } + + for (u64 i = 0; i < count; i++) { if (!strcmp(fh->ser[i].name, flb->compatible)) { private->incoming.data = fh->ser[i].data; private->incoming.count = fh->ser[i].count; @@ -620,7 +628,20 @@ int __init luo_flb_setup_incoming(void *fdt_in) } header_ser_pa = get_unaligned((u64 *)ptr); + if (!kho_is_preserved(header_ser_pa, LUO_FLB_PGCNT)) { + pr_err("FLB header is not KHO preserved: %#llx\n", + (unsigned long long)header_ser_pa); + return -EINVAL; + } + header_ser = phys_to_virt(header_ser_pa); + if (header_ser->pgcnt != LUO_FLB_PGCNT || + header_ser->count > LUO_FLB_MAX) { + pr_err("Invalid FLB header: pgcnt %llu count %llu\n", + (unsigned long long)header_ser->pgcnt, + (unsigned long long)header_ser->count); + return -EINVAL; + } luo_flb_global.incoming.header_ser = header_ser; luo_flb_global.incoming.ser = (void *)(header_ser + 1); -- 2.53.0