Sashiko AI code review pointed out a potential memory leak of
image->elf_headers when load_other_segments() fails on error paths.

In the arm64 kexec_file file-load path, kexec_image.c runs a retry loop
calling kexec_add_buffer() to find a suitable location for the kernel
segment. On each iteration, load_other_segments() is invoked to allocate
and populate alternative segments such as initrd, DTB, and ELF headers.

However, if a placement or allocation failure occurs later in
load_other_segments() (e.g., when adding initrd or dtb), the execution
jumps to the out_err label. While this path restores image->nr_segments
via orig_segments, it returns an error back to the caller without freeing
the previously allocated image->elf_headers vmalloc buffer.

As a result, the retry loop in image_load() unconditionally allocates
new ELF headers on the next iteration and overwrites image->elf_headers,
permanently leaking the memory blocks allocated in previous iterations.

To fix this, decouple the ELF header allocation from the target-seeking
retry loop. Since the contents and size of ELF headers only depend on
the host memory layout and do not change with the kernel's physical
placement, move prepare_elf_headers() completely outside and prior to
the while retry loop in image_load().

Concurrently, remove the prepare_elf_headers() call from inside
load_other_segments() and have it directly reuse the single, pre-allocated
image->elf_headers. Also, ensure that image->nr_segments is explicitly
rolled back to kernel_segment_number on retry failures to safely discard
stale segment tracking state.

This optimization eliminates redundant memory allocation/deallocation
overhead during kexec placement retries and eradicates the Use-After-Free
and memory leak risk.

Fixes: 108aa503657e ("arm64: kexec_file: try more regions if loading segments 
fails")
Signed-off-by: Jinjie Ruan <[email protected]>
---
 arch/arm64/include/asm/kexec.h         |  3 ++-
 arch/arm64/kernel/kexec_image.c        | 22 ++++++++++++++++++++--
 arch/arm64/kernel/machine_kexec_file.c | 16 +++-------------
 3 files changed, 25 insertions(+), 16 deletions(-)

diff --git a/arch/arm64/include/asm/kexec.h b/arch/arm64/include/asm/kexec.h
index 892e5bebda95..cc2f36b1b0d4 100644
--- a/arch/arm64/include/asm/kexec.h
+++ b/arch/arm64/include/asm/kexec.h
@@ -127,7 +127,8 @@ int arch_kimage_file_post_load_cleanup(struct kimage 
*image);
 extern int load_other_segments(struct kimage *image,
                unsigned long kernel_load_addr, unsigned long kernel_size,
                char *initrd, unsigned long initrd_len,
-               char *cmdline);
+               char *cmdline, void *headers, unsigned long headers_size);
+extern int prepare_elf_headers(void **addr, unsigned long *sz);
 #endif
 
 #endif /* __ASSEMBLER__ */
diff --git a/arch/arm64/kernel/kexec_image.c b/arch/arm64/kernel/kexec_image.c
index b70f4df15a1a..79efeaeb71e9 100644
--- a/arch/arm64/kernel/kexec_image.c
+++ b/arch/arm64/kernel/kexec_image.c
@@ -44,6 +44,11 @@ static void *image_load(struct kimage *image,
        struct kexec_buf kbuf = {};
        unsigned long text_offset, kernel_segment_number;
        struct kexec_segment *kernel_segment;
+#ifdef CONFIG_CRASH_DUMP
+       /* load elf core header */
+       unsigned long headers_sz;
+       void *headers;
+#endif
        int ret;
 
        /*
@@ -89,6 +94,18 @@ static void *image_load(struct kimage *image,
 
        kernel_segment_number = image->nr_segments;
 
+#ifdef CONFIG_CRASH_DUMP
+       if (image->type == KEXEC_TYPE_CRASH) {
+               ret = prepare_elf_headers(&headers, &headers_sz);
+               if (ret) {
+                       pr_err("Preparing elf core header failed\n");
+                       return ERR_PTR(ret);
+               }
+               image->elf_headers = headers;
+               image->elf_headers_sz = headers_sz;
+       }
+#endif
+
        /*
         * The location of the kernel segment may make it impossible to satisfy
         * the other segment requirements, so we try repeatedly to find a
@@ -99,7 +116,8 @@ static void *image_load(struct kimage *image,
                kernel_segment = &image->segment[kernel_segment_number];
                ret = load_other_segments(image, kernel_segment->mem,
                                          kernel_segment->memsz, initrd,
-                                         initrd_len, cmdline);
+                                         initrd_len, cmdline,
+                                         headers, headers_sz);
                if (!ret)
                        break;
 
@@ -107,7 +125,7 @@ static void *image_load(struct kimage *image,
                 * We couldn't find space for the other segments; erase the
                 * kernel segment and try the next available hole.
                 */
-               image->nr_segments -= 1;
+               image->nr_segments = kernel_segment_number;
                kbuf.buf_min = kernel_segment->mem + kernel_segment->memsz;
                kbuf.mem = KEXEC_BUF_MEM_UNKNOWN;
        }
diff --git a/arch/arm64/kernel/machine_kexec_file.c 
b/arch/arm64/kernel/machine_kexec_file.c
index e31fabed378a..daf81a873bbd 100644
--- a/arch/arm64/kernel/machine_kexec_file.c
+++ b/arch/arm64/kernel/machine_kexec_file.c
@@ -40,7 +40,7 @@ int arch_kimage_file_post_load_cleanup(struct kimage *image)
 }
 
 #ifdef CONFIG_CRASH_DUMP
-static int prepare_elf_headers(void **addr, unsigned long *sz)
+int prepare_elf_headers(void **addr, unsigned long *sz)
 {
        struct crash_mem *cmem;
        unsigned int nr_ranges;
@@ -92,7 +92,8 @@ int load_other_segments(struct kimage *image,
                        unsigned long kernel_load_addr,
                        unsigned long kernel_size,
                        char *initrd, unsigned long initrd_len,
-                       char *cmdline)
+                       char *cmdline, void *headers,
+                       unsigned long headers_sz)
 {
        struct kexec_buf kbuf = {};
        void *dtb = NULL;
@@ -105,16 +106,7 @@ int load_other_segments(struct kimage *image,
        kbuf.buf_min = kernel_load_addr + kernel_size;
 
 #ifdef CONFIG_CRASH_DUMP
-       /* load elf core header */
-       void *headers;
-       unsigned long headers_sz;
        if (image->type == KEXEC_TYPE_CRASH) {
-               ret = prepare_elf_headers(&headers, &headers_sz);
-               if (ret) {
-                       pr_err("Preparing elf core header failed\n");
-                       goto out_err;
-               }
-
                kbuf.buffer = headers;
                kbuf.bufsz = headers_sz;
                kbuf.mem = KEXEC_BUF_MEM_UNKNOWN;
@@ -128,9 +120,7 @@ int load_other_segments(struct kimage *image,
                        vfree(headers);
                        goto out_err;
                }
-               image->elf_headers = headers;
                image->elf_load_addr = kbuf.mem;
-               image->elf_headers_sz = headers_sz;
 
                kexec_dprintk("Loaded elf core header at 0x%lx bufsz=0x%lx 
memsz=0x%lx\n",
                              image->elf_load_addr, kbuf.bufsz, kbuf.memsz);
-- 
2.34.1


Reply via email to