In read-only KHO mode, incoming serialized kho_block chains lie outside
the kernel's direct map and must not be modified or freed back into the
page allocator.

Map incoming serialized blocks read-only via kho_ro_map_folio(...,
PAGE_KERNEL_RO) during cycle detection and restoration, and unmap them
via kho_ro_unmap() instead of calling kho_restore_free() when destroying
or shrinking a block set in read-only mode.

Signed-off-by: Pasha Tatashin <[email protected]>
---
 kernel/liveupdate/Makefile    |  5 +--
 kernel/liveupdate/kho_block.c | 76 ++++++++++++++++++++++++++---------
 2 files changed, 60 insertions(+), 21 deletions(-)

diff --git a/kernel/liveupdate/Makefile b/kernel/liveupdate/Makefile
index f8b09e04bea2..d3587048e619 100644
--- a/kernel/liveupdate/Makefile
+++ b/kernel/liveupdate/Makefile
@@ -1,14 +1,13 @@
 # SPDX-License-Identifier: GPL-2.0
 
 luo-y :=                                                               \
-               kho_block.o                                             \
                luo_core.o                                              \
                luo_file.o                                              \
                luo_flb.o                                               \
                luo_session.o
 
-obj-$(CONFIG_KEXEC_HANDOVER)           += kexec_handover.o
-obj-$(CONFIG_KEXEC_HANDOVER_READONLY)  += kexec_handover_ro.o
+obj-$(CONFIG_KEXEC_HANDOVER)           += kexec_handover.o kho_block.o
+obj-$(CONFIG_KEXEC_HANDOVER_READONLY)  += kexec_handover_ro.o kho_block.o
 obj-$(CONFIG_KEXEC_HANDOVER_DEBUGFS)   += kexec_handover_debugfs.o
 
 obj-$(CONFIG_LIVEUPDATE)               += luo.o
diff --git a/kernel/liveupdate/kho_block.c b/kernel/liveupdate/kho_block.c
index a4e945f28fad..a3cf5b964b26 100644
--- a/kernel/liveupdate/kho_block.c
+++ b/kernel/liveupdate/kho_block.c
@@ -60,10 +60,32 @@ static void *kho_block_entry(struct kho_block_set_it *it, 
u64 index)
        return kho_block_entries(it->block) + (index * it->bs->entry_size);
 }
 
+static struct kho_block_header_ser *kho_block_map_ser(phys_addr_t pa)
+{
+       if (!pa)
+               return NULL;
+
+       if (kho_is_ro())
+               return kho_ro_map_folio(pa, PAGE_KERNEL_RO);
+
+       return phys_to_virt(pa);
+}
+
+static void kho_block_unmap_ser(struct kho_block_header_ser *ser)
+{
+       if (kho_is_ro())
+               kho_ro_unmap(ser);
+}
+
 /* Free serialized data */
 static void kho_block_free_ser(struct kho_block_set *bs,
                               struct kho_block_header_ser *ser)
 {
+       if (kho_is_ro()) {
+               kho_block_unmap_ser(ser);
+               return;
+       }
+
        if (bs->incoming)
                kho_restore_free(ser);
        else
@@ -197,6 +219,20 @@ void kho_block_set_shrink(struct kho_block_set *bs, u64 
count)
                kho_block_set_shrink_one(bs);
 }
 
+static bool kho_block_step_pa(phys_addr_t pa, phys_addr_t *next_pa)
+{
+       struct kho_block_header_ser *ser;
+
+       ser = kho_block_map_ser(pa);
+       if (!ser)
+               return false;
+
+       *next_pa = ser->next;
+       kho_block_unmap_ser(ser);
+
+       return true;
+}
+
 /*
  * kho_block_set_is_cyclic - Check for cycles in a linked list of blocks.
  * Uses Floyd's cycle-finding algorithm to ensure sanity of the incoming list.
@@ -205,30 +241,26 @@ void kho_block_set_shrink(struct kho_block_set *bs, u64 
count)
  */
 static bool kho_block_set_is_cyclic(struct kho_block_set *bs)
 {
-       struct kho_block_header_ser *fast;
-       struct kho_block_header_ser *slow;
+       phys_addr_t fast_pa = bs->head_pa;
+       phys_addr_t slow_pa = bs->head_pa;
        int count = 0;
 
-       fast = phys_to_virt(bs->head_pa);
-       slow = fast;
-
-       while (fast) {
+       while (fast_pa) {
                if (count++ >= KHO_MAX_BLOCKS) {
                        pr_err("Block set is corrupted\n");
                        return true;
                }
 
-               if (!fast->next)
-                       break;
-
-               fast = phys_to_virt(fast->next);
-               if (!fast->next)
+               if (!kho_block_step_pa(fast_pa, &fast_pa))
+                       return true;
+               if (!fast_pa)
                        break;
 
-               fast = phys_to_virt(fast->next);
-               slow = phys_to_virt(slow->next);
+               if (!kho_block_step_pa(fast_pa, &fast_pa) ||
+                   !kho_block_step_pa(slow_pa, &slow_pa))
+                       return true;
 
-               if (slow == fast) {
+               if (slow_pa == fast_pa) {
                        pr_err("Block set is corrupted\n");
                        return true;
                }
@@ -266,16 +298,23 @@ int kho_block_set_restore(struct kho_block_set *bs, u64 
head_pa)
        }
 
        while (next_pa) {
-               ser = phys_to_virt(next_pa);
+               ser = kho_block_map_ser(next_pa);
+               if (!ser) {
+                       err = -ENOMEM;
+                       goto err_destroy;
+               }
                if (!ser->count || ser->count > bs->count_per_block) {
                        pr_warn("Block contains invalid entry count: %llu\n",
                                ser->count);
+                       kho_block_unmap_ser(ser);
                        err = -EINVAL;
                        goto err_destroy;
                }
                err = kho_block_add(bs, ser);
-               if (err)
+               if (err) {
+                       kho_block_unmap_ser(ser);
                        goto err_destroy;
+               }
                next_pa = ser->next;
        }
 
@@ -285,9 +324,10 @@ int kho_block_set_restore(struct kho_block_set *bs, u64 
head_pa)
        kho_block_set_destroy(bs);
 
        /* Free the remaining un-restored blocks in the physical chain */
-       while (next_pa) {
-               struct kho_block_header_ser *next_ser = phys_to_virt(next_pa);
+       while (!kho_is_ro() && next_pa) {
+               struct kho_block_header_ser *next_ser;
 
+               next_ser = phys_to_virt(next_pa);
                next_pa = next_ser->next;
                kho_block_free_ser(bs, next_ser);
        }
-- 
2.56.0.176.g3575e65682-goog


Reply via email to