From mboxrd@z Thu Jan 1 00:00:00 1970 Received: from toronto-edge.smtp.mymangomail.com (toronto-edge.smtp.mymangomail.com [209.38.81.170]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id A909D38C403 for ; Sat, 21 Mar 2026 14:42:27 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=209.38.81.170 ARC-Seal:i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1774104150; cv=none; b=QtemCxwsgKO7tlWwUPkgyblyZACgwyrSj2duIZ6VyYI98Xp4oD5qZGOk1YIaFrDU4h+NMEB0MWQt7t6RYb1Sb64XXC8zAUhWxJjl3bVtuceIWNZf6qEJ/iFiAlPCS5S6sg7EL+K0zlDFuZvSUT1jzZFgCiRG3Aw1gs/eyQKXlfw= ARC-Message-Signature:i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1774104150; c=relaxed/simple; bh=M16vWRuu6EJkpwXaqhKmqlJhWbpvl6ngwctG70jXS/g=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=YZhgbIediEI1l/4ZWtXW5eP0MfU0W45lcE+eXk/JORITb/DbCxD2HGVa++FXvfEVYiVfNPPupXVg4QmszQ4Szy6f8AYFPIY6hc7Anp9LWFFw+Qg5Yl9Xzaq35GdKCnc3AFZ0A2SUPfDM1DrIr6Aao7li7WKU1A6plipTnKiRX/Q= ARC-Authentication-Results:i=1; smtp.subspace.kernel.org; dmarc=pass (p=quarantine dis=none) header.from=gerlicz.space; spf=pass smtp.mailfrom=gerlicz.space; dkim=pass (1024-bit key) header.d=gerlicz.space header.i=@gerlicz.space header.b=WDFSH3r7; arc=none smtp.client-ip=209.38.81.170 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=quarantine dis=none) header.from=gerlicz.space Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=gerlicz.space Authentication-Results: smtp.subspace.kernel.org; dkim=pass (1024-bit key) header.d=gerlicz.space header.i=@gerlicz.space header.b="WDFSH3r7" Received: from smtp.mymangomail.com (localhost [127.0.0.1]) by smtp.mymangomail.com (Mango Mail) with ESMTP id 0E1984162F; Sat, 21 Mar 2026 10:42:24 -0400 (EDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=gerlicz.space; s=mango-1; t=1774104144; bh=M16vWRuu6EJkpwXaqhKmqlJhWbpvl6ngwctG70jXS/g=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=WDFSH3r7vCzPpVucnTR94PZi863BPmtaFpe0ISW2ggCWJqC8NFfqCbdYsv6HUcTmy mkX7wUb9/+ghf7hrs93FxFRmmjsk9EZb2i7U0iQReplJRGQipVhfs9CLCIGkntyMSF 67QrCnU6StQgl5O5hcQrmdCTl3eCoJrXAF6eGK8Q= X-Mango-Origin: 1 X-Mango-Origin: 1 X-Mango-Origin: 1 X-Mango-Origin: 1 X-Mango-Origin: 1 X-Mango-Origin: 1 X-Mango-Origin: 1 X-Mango-Origin: 1 X-Mango-Origin: 1 Received: from authenticated-user (smtp.mymangomail.com [205.185.121.143]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange ECDHE (P-256) server-signature RSA-PSS (2048 bits) server-digest SHA256) (No client certificate requested) by smtp.mymangomail.com (Mango Mail) with ESMTPSA id B0C1D41627; Sat, 21 Mar 2026 10:40:55 -0400 (EDT) From: Oskar Gerlicz Kowalczuk To: Pasha Tatashin , Mike Rapoport , Baoquan He Cc: Pratyush Yadav , Andrew Morton , linux-kernel@vger.kernel.org, kexec@lists.infradead.org, linux-mm@kvack.org, Oskar Gerlicz Kowalczuk Subject: [PATCH v3 4/5] liveupdate: validate handover metadata before using it Date: Sat, 21 Mar 2026 15:36:41 +0100 Message-ID: <20260321143642.166313-4-oskar@gerlicz.space> In-Reply-To: <20260321143642.166313-3-oskar@gerlicz.space> References: <20260321143642.166313-1-oskar@gerlicz.space> <20260321143642.166313-2-oskar@gerlicz.space> <20260321143642.166313-3-oskar@gerlicz.space> Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Transfer-Encoding: 8bit The incoming handover path was still trusting counts and preserved addresses more than it should. Corrupted metadata could drive phys_to_virt() or kho_restore_free() with invalid addresses, and memfd restore could walk beyond the serialized folio array. That turns malformed handover state into out-of-bounds iteration, invalid restore frees, or partially restored incoming state. Validate session, file and FLB metadata before consuming it, reject non-restorable preserved addresses, and bound memfd folio restore by both the serialized size and the restored vmalloc backing. Fixes: 1c18e3e1efcd ("liveupdate: validate handover metadata before using it") Signed-off-by: Oskar Gerlicz Kowalczuk --- include/linux/kexec_handover.h | 5 +++ kernel/liveupdate/kexec_handover.c | 13 +++++++ kernel/liveupdate/luo_file.c | 22 +++++++++--- kernel/liveupdate/luo_flb.c | 33 ++++++++++++++++-- kernel/liveupdate/luo_session.c | 29 ++++++++++++++-- mm/memfd_luo.c | 56 ++++++++++++++++++++++++++++-- 6 files changed, 147 insertions(+), 11 deletions(-) diff --git a/include/linux/kexec_handover.h b/include/linux/kexec_handover.h index ac4129d1d741..af8284a440bf 100644 --- a/include/linux/kexec_handover.h +++ b/include/linux/kexec_handover.h @@ -29,6 +29,7 @@ void kho_unpreserve_vmalloc(struct kho_vmalloc *preservation); void *kho_alloc_preserve(size_t size); void kho_unpreserve_free(void *mem); void kho_restore_free(void *mem); +bool kho_is_restorable_phys(phys_addr_t phys); 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); @@ -80,6 +81,10 @@ static inline void *kho_alloc_preserve(size_t size) static inline void kho_unpreserve_free(void *mem) { } static inline void kho_restore_free(void *mem) { } +static inline bool kho_is_restorable_phys(phys_addr_t phys) +{ + return false; +} static inline struct folio *kho_restore_folio(phys_addr_t phys) { diff --git a/kernel/liveupdate/kexec_handover.c b/kernel/liveupdate/kexec_handover.c index cc68a3692905..215b27f5f85f 100644 --- a/kernel/liveupdate/kexec_handover.c +++ b/kernel/liveupdate/kexec_handover.c @@ -291,6 +291,19 @@ struct folio *kho_restore_folio(phys_addr_t phys) } EXPORT_SYMBOL_GPL(kho_restore_folio); +bool kho_is_restorable_phys(phys_addr_t phys) +{ + struct page *page = pfn_to_online_page(PHYS_PFN(phys)); + union kho_page_info info; + + if (!page || !PAGE_ALIGNED(phys)) + return false; + + info.page_private = READ_ONCE(page->private); + return info.magic == KHO_PAGE_MAGIC && info.order <= MAX_PAGE_ORDER; +} +EXPORT_SYMBOL_GPL(kho_is_restorable_phys); + /** * kho_restore_pages - restore list of contiguous order 0 pages. * @phys: physical address of the first page. diff --git a/kernel/liveupdate/luo_file.c b/kernel/liveupdate/luo_file.c index 939ef8d762ce..3eb9aeee6524 100644 --- a/kernel/liveupdate/luo_file.c +++ b/kernel/liveupdate/luo_file.c @@ -785,10 +785,17 @@ int luo_file_deserialize(struct luo_file_set *file_set, u64 i; int err; - if (!file_set_ser->files) { - WARN_ON(file_set_ser->count); - return 0; - } + if (!file_set_ser->count) + return file_set_ser->files ? -EINVAL : 0; + + if (!file_set_ser->files) + return -EINVAL; + + if (file_set_ser->count > LUO_FILE_MAX) + return -EINVAL; + + if (!kho_is_restorable_phys(file_set_ser->files)) + return -EINVAL; file_set->count = file_set_ser->count; file_set->files = phys_to_virt(file_set_ser->files); @@ -799,6 +806,13 @@ int luo_file_deserialize(struct luo_file_set *file_set, bool handler_found = false; struct luo_file *luo_file; + if (strnlen(file_ser[i].compatible, + sizeof(file_ser[i].compatible)) == + sizeof(file_ser[i].compatible)) { + err = -EINVAL; + goto err_discard; + } + list_private_for_each_entry(fh, &luo_file_handler_list, list) { if (!strcmp(fh->compatible, file_ser[i].compatible)) { handler_found = true; diff --git a/kernel/liveupdate/luo_flb.c b/kernel/liveupdate/luo_flb.c index f52e8114837e..cdd293408138 100644 --- a/kernel/liveupdate/luo_flb.c +++ b/kernel/liveupdate/luo_flb.c @@ -165,7 +165,14 @@ static int luo_flb_retrieve_one(struct liveupdate_flb *flb) return -ENODATA; for (int i = 0; i < fh->header_ser->count; i++) { + if (strnlen(fh->ser[i].name, sizeof(fh->ser[i].name)) == + sizeof(fh->ser[i].name)) + return -EINVAL; + if (!strcmp(fh->ser[i].name, flb->compatible)) { + if (!fh->ser[i].count) + return -EINVAL; + private->incoming.data = fh->ser[i].data; private->incoming.count = fh->ser[i].count; found = true; @@ -578,6 +585,7 @@ int __init luo_flb_setup_outgoing(void *fdt_out) int __init luo_flb_setup_incoming(void *fdt_in) { + struct luo_flb_header_ser *header_copy; struct luo_flb_header_ser *header_ser; int err, header_size, offset; const void *ptr; @@ -609,10 +617,31 @@ int __init luo_flb_setup_incoming(void *fdt_in) } header_ser_pa = get_unaligned((u64 *)ptr); + if (!header_ser_pa) { + pr_err("FLB header address is missing\n"); + return -EINVAL; + } + + if (!kho_is_restorable_phys(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) { + kho_restore_free(header_ser); + return -EINVAL; + } + + header_copy = kmemdup(header_ser, + sizeof(*header_copy) + + header_ser->count * + sizeof(*luo_flb_global.incoming.ser), + GFP_KERNEL); + kho_restore_free(header_ser); + if (!header_copy) + return -ENOMEM; - luo_flb_global.incoming.header_ser = header_ser; - luo_flb_global.incoming.ser = (void *)(header_ser + 1); + luo_flb_global.incoming.header_ser = header_copy; + luo_flb_global.incoming.ser = (void *)(header_copy + 1); luo_flb_global.incoming.active = true; return 0; diff --git a/kernel/liveupdate/luo_session.c b/kernel/liveupdate/luo_session.c index 602001327f58..f7f62f11d17c 100644 --- a/kernel/liveupdate/luo_session.c +++ b/kernel/liveupdate/luo_session.c @@ -673,6 +673,14 @@ int __init luo_session_setup_incoming(void *fdt_in) } header_ser_pa = get_unaligned((u64 *)ptr); + if (!header_ser_pa) { + pr_err("Session header address is missing\n"); + return -EINVAL; + } + + if (!kho_is_restorable_phys(header_ser_pa)) + return -EINVAL; + header_ser = phys_to_virt(header_ser_pa); luo_session_global.incoming.header_ser = header_ser; @@ -697,9 +705,22 @@ int luo_session_deserialize(void) if (!sh->active) return err; + if (sh->header_ser->count > LUO_SESSION_MAX) { + pr_warn("Invalid session count %llu\n", sh->header_ser->count); + err = -EINVAL; + goto out_free_header; + } + for (int i = 0; i < sh->header_ser->count; i++) { struct luo_session *session; + if (strnlen(sh->ser[i].name, sizeof(sh->ser[i].name)) == + sizeof(sh->ser[i].name)) { + pr_warn("Session name is not NUL-terminated\n"); + err = -EINVAL; + goto out_discard; + } + session = luo_session_alloc(sh->ser[i].name); if (IS_ERR(session)) { pr_warn("Failed to allocate session [%s] during deserialization %pe\n", @@ -728,9 +749,11 @@ int luo_session_deserialize(void) } out_free_header: - kho_restore_free(sh->header_ser); - sh->header_ser = NULL; - sh->ser = NULL; + if (sh->header_ser) { + kho_restore_free(sh->header_ser); + sh->header_ser = NULL; + sh->ser = NULL; + } return err; diff --git a/mm/memfd_luo.c b/mm/memfd_luo.c index 8a453c8bfdf5..b7f996176ad8 100644 --- a/mm/memfd_luo.c +++ b/mm/memfd_luo.c @@ -363,6 +363,10 @@ static void memfd_luo_abort(struct liveupdate_file_op_args *args) struct memfd_luo_folio_ser *folios_ser; struct memfd_luo_ser *ser; + if (!args->serialized_data || + !kho_is_restorable_phys(args->serialized_data)) + return; + ser = phys_to_virt(args->serialized_data); if (!ser) return; @@ -399,10 +403,16 @@ static int memfd_luo_retrieve_folios(struct file *file, { struct inode *inode = file_inode(file); struct address_space *mapping = inode->i_mapping; + u64 max_index = i_size_read(inode); + u64 prev_index = 0; struct folio *folio; + long put_from = 0; int err = -EIO; long i; + if (max_index) + max_index = (max_index - 1) >> PAGE_SHIFT; + for (i = 0; i < nr_folios; i++) { const struct memfd_luo_folio_ser *pfolio = &folios_ser[i]; phys_addr_t phys; @@ -412,6 +422,19 @@ static int memfd_luo_retrieve_folios(struct file *file, if (!pfolio->pfn) continue; + put_from = i; + if (pfolio->flags & + ~(MEMFD_LUO_FOLIO_DIRTY | MEMFD_LUO_FOLIO_UPTODATE)) { + err = -EINVAL; + goto put_folios; + } + + if (pfolio->index > max_index || + (i && pfolio->index <= prev_index)) { + err = -EINVAL; + goto put_folios; + } + phys = PFN_PHYS(pfolio->pfn); folio = kho_restore_folio(phys); if (!folio) { @@ -419,7 +442,9 @@ static int memfd_luo_retrieve_folios(struct file *file, phys); goto put_folios; } + put_from = i + 1; index = pfolio->index; + prev_index = index; flags = pfolio->flags; /* Set up the folio for insertion. */ @@ -469,10 +494,15 @@ static int memfd_luo_retrieve_folios(struct file *file, * Note: don't free the folios already added to the file. They will be * freed when the file is freed. Free the ones not added yet here. */ - for (long j = i + 1; j < nr_folios; j++) { + for (long j = put_from; j < nr_folios; j++) { const struct memfd_luo_folio_ser *pfolio = &folios_ser[j]; + phys_addr_t phys; + + if (!pfolio->pfn) + continue; - folio = kho_restore_folio(pfolio->pfn); + phys = PFN_PHYS(pfolio->pfn); + folio = kho_restore_folio(phys); if (folio) folio_put(folio); } @@ -487,10 +517,32 @@ static int memfd_luo_retrieve(struct liveupdate_file_op_args *args) struct file *file; int err; + if (!kho_is_restorable_phys(args->serialized_data)) + return -EINVAL; + ser = phys_to_virt(args->serialized_data); if (!ser) return -EINVAL; + if (!!ser->nr_folios != !!ser->folios.first.phys) { + err = -EINVAL; + goto free_ser; + } + + if (ser->nr_folios > + (((u64)ser->folios.total_pages << PAGE_SHIFT) / + sizeof(*folios_ser))) { + err = -EINVAL; + goto free_ser; + } + + if (ser->nr_folios && + (!ser->size || + ser->nr_folios > ((ser->size - 1) >> PAGE_SHIFT) + 1)) { + err = -EINVAL; + goto free_ser; + } + file = memfd_alloc_file("", 0); if (IS_ERR(file)) { pr_err("failed to setup file: %pe\n", file); -- 2.53.0