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

