kho_restore_vmalloc() currently dereferences preservation before
checking for NULL, does not reject unknown KHO vmalloc flags in
kho_flags_to_vmalloc(), and does not validate preservation->first.phys,
preservation->total_pages, preservation->order, or potential size
overflow before allocating the pages array and shifting by order.

Introduce kho_vmalloc_validate() in kexec_handover_internal.h to
validate the struct kho_vmalloc header and convert its flags before
restoring the vmalloc area.

Signed-off-by: Pasha Tatashin <[email protected]>
---
 kernel/liveupdate/kexec_handover.c          | 17 ++++++------
 kernel/liveupdate/kexec_handover_internal.h | 30 +++++++++++++++++++++
 2 files changed, 38 insertions(+), 9 deletions(-)

diff --git a/kernel/liveupdate/kexec_handover.c 
b/kernel/liveupdate/kexec_handover.c
index f427cdd9a969..a9c52d998855 100644
--- a/kernel/liveupdate/kexec_handover.c
+++ b/kernel/liveupdate/kexec_handover.c
@@ -1384,28 +1384,27 @@ EXPORT_SYMBOL_GPL(kho_unpreserve_vmalloc);
  */
 void *kho_restore_vmalloc(const struct kho_vmalloc *preservation)
 {
-       struct kho_vmalloc_chunk *chunk = KHOSER_LOAD_PTR(preservation->first);
        kasan_vmalloc_flags_t kasan_flags = KASAN_VMALLOC_PROT_NORMAL;
-       unsigned int align, order, shift, vm_flags;
-       unsigned long total_pages, contig_pages;
+       unsigned long total_pages, contig_pages, align, idx = 0;
+       unsigned int order, shift, vm_flags;
+       struct kho_vmalloc_chunk *chunk;
        unsigned long addr, size;
        struct vm_struct *area;
        struct page **pages;
-       unsigned int idx = 0;
        int err;
 
-       vm_flags = kho_flags_to_vmalloc(preservation->flags);
-       if (vm_flags & ~KHO_VMALLOC_SUPPORTED_FLAGS)
+       if (!kho_vmalloc_validate(preservation, &vm_flags))
                return NULL;
 
+       chunk = KHOSER_LOAD_PTR(preservation->first);
        total_pages = preservation->total_pages;
        pages = kvmalloc_objs(*pages, total_pages);
        if (!pages)
                return NULL;
        order = preservation->order;
-       contig_pages = (1 << order);
+       contig_pages = 1UL << order;
        shift = PAGE_SHIFT + order;
-       align = 1 << shift;
+       align = 1UL << shift;
 
        while (chunk) {
                struct page *page;
@@ -1420,7 +1419,7 @@ void *kho_restore_vmalloc(const struct kho_vmalloc 
*preservation)
                        if (!page)
                                goto err_free_pages_array;
 
-                       for (int j = 0; j < contig_pages; j++)
+                       for (unsigned long j = 0; j < contig_pages; j++)
                                pages[idx++] = page + j;
 
                        phys += contig_pages * PAGE_SIZE;
diff --git a/kernel/liveupdate/kexec_handover_internal.h 
b/kernel/liveupdate/kexec_handover_internal.h
index 094b4917a0f1..878e21bd8b00 100644
--- a/kernel/liveupdate/kexec_handover_internal.h
+++ b/kernel/liveupdate/kexec_handover_internal.h
@@ -7,6 +7,7 @@
 #include <linux/libfdt.h>
 #include <linux/list.h>
 #include <linux/mm.h>
+#include <linux/overflow.h>
 #include <linux/printk.h>
 #include <linux/types.h>
 #include <linux/unaligned.h>
@@ -23,6 +24,9 @@ static inline unsigned int kho_flags_to_vmalloc(unsigned 
short kho_flags)
 {
        unsigned int vm_flags = 0;
 
+       if (kho_flags & ~(KHO_VMALLOC_ALLOC | KHO_VMALLOC_HUGE_VMAP))
+               return ~KHO_VMALLOC_SUPPORTED_FLAGS;
+
        if (kho_flags & KHO_VMALLOC_ALLOC)
                vm_flags |= VM_ALLOC;
        if (kho_flags & KHO_VMALLOC_HUGE_VMAP)
@@ -31,6 +35,32 @@ static inline unsigned int kho_flags_to_vmalloc(unsigned 
short kho_flags)
        return vm_flags;
 }
 
+static inline bool kho_vmalloc_validate(const struct kho_vmalloc *preservation,
+                                       unsigned int *vm_flags)
+{
+       size_t alloc_size;
+
+       if (!preservation || !preservation->first.phys ||
+           !PAGE_ALIGNED(preservation->first.phys) ||
+           !preservation->total_pages)
+               return false;
+
+       if (preservation->order >= BITS_PER_LONG - PAGE_SHIFT ||
+           (1UL << preservation->order) > preservation->total_pages ||
+           !IS_ALIGNED(preservation->total_pages, 1UL << preservation->order))
+               return false;
+
+       if (check_mul_overflow((size_t)preservation->total_pages, PAGE_SIZE,
+                              &alloc_size))
+               return false;
+
+       *vm_flags = kho_flags_to_vmalloc(preservation->flags);
+       if (*vm_flags & ~KHO_VMALLOC_SUPPORTED_FLAGS)
+               return false;
+
+       return true;
+}
+
 /**
  * kho_encode_radix_key - Encodes a physical address and order into a radix 
key.
  * @phys: The physical address of the page.
-- 
2.56.0.176.g3575e65682-goog


Reply via email to