[PATCH 1/4] kexec: do not place the next kernel in a fixed-address CMA area

From: Radhey Shyam Pandey

Date: Tue Nov 03 2026 - 11:27:57 EST


kexec_alloc_contig() takes the next kernel's destination from the default
CMA area and the next kernel runs from there in place, which is safe only
if that area does not exist at the same address in the kernel being loaded.
For a device-tree area it does: the loaded kernel declares the range it is
executing from as a shared-dma-pool and cma_activate_area() frees its own
image to the page allocator. Record in CMA_DYNAMIC_BASE which areas CMA
placed itself and accept only those, letting kexec_locate_mem_hole() fall
back to the memblock search. "cma=nn" works, "cma=nn@ss" no longer does.

Fixes: 07d24902977e ("kexec: enable CMA based contiguous allocation")
Suggested-by: Mike Rapoport <rppt@xxxxxxxxxx>
Signed-off-by: Radhey Shyam Pandey <radhey.shyam.pandey@xxxxxxx>
---
Changes since RFC:
- Use CMA_DYNAMIC_BASE flag instead of CMA_FIXED_BASE.
- Drop Pratyush reviewed-by tag as there is implementation change.
---
include/linux/cma.h | 10 ++++++++++
kernel/kexec_file.c | 12 ++++++++++++
mm/cma.c | 16 ++++++++++++++++
mm/cma.h | 2 ++
4 files changed, 40 insertions(+)

diff --git a/include/linux/cma.h b/include/linux/cma.h
index 8555d38a97b1..d4683a4ff3cc 100644
--- a/include/linux/cma.h
+++ b/include/linux/cma.h
@@ -61,4 +61,14 @@ extern int cma_for_each_area(int (*it)(struct cma *cma, void *data), void *data)
extern bool cma_intersects(struct cma *cma, unsigned long start, unsigned long end);

extern void cma_reserve_pages_on_error(struct cma *cma);
+
+#ifdef CONFIG_CMA
+bool cma_has_dynamic_base(const struct cma *cma);
+#else
+static inline bool cma_has_dynamic_base(const struct cma *cma)
+{
+ return false;
+}
+#endif
+
#endif
diff --git a/kernel/kexec_file.c b/kernel/kexec_file.c
index b668ce8d68ee..763dee85558f 100644
--- a/kernel/kexec_file.c
+++ b/kernel/kexec_file.c
@@ -28,6 +28,7 @@
#include <linux/vmalloc.h>
#include <linux/dma-map-ops.h>
#include <linux/kexec_handover.h>
+#include <linux/cma.h>
#include "kexec_internal.h"

#ifdef CONFIG_KEXEC_SIG
@@ -691,6 +692,7 @@ static int kexec_walk_resources(struct kexec_buf *kbuf,
static int kexec_alloc_contig(struct kexec_buf *kbuf)
{
size_t nr_pages = kbuf->memsz >> PAGE_SHIFT;
+ struct cma *cma;
unsigned long mem;
struct page *p;

@@ -702,6 +704,16 @@ static int kexec_alloc_contig(struct kexec_buf *kbuf)
if (kbuf->image->type == KEXEC_TYPE_CRASH)
return -EPERM;

+ /*
+ * The next kernel runs from here rather than being copied out, so it
+ * would declare this area again from the same description and release
+ * the pages holding its own image to the page allocator. Only an area
+ * this kernel placed itself is safe to use.
+ */
+ cma = dev_get_cma_area(NULL);
+ if (!cma || !cma_has_dynamic_base(cma))
+ return -EOPNOTSUPP;
+
p = dma_alloc_from_contiguous(NULL, nr_pages, get_order(kbuf->buf_align), true);
if (!p)
return -ENOMEM;
diff --git a/mm/cma.c b/mm/cma.c
index 9ec1d1fd5dff..9c53b2b70dc8 100644
--- a/mm/cma.c
+++ b/mm/cma.c
@@ -55,6 +55,19 @@ const char *cma_get_name(const struct cma *cma)
}
EXPORT_SYMBOL_GPL(cma_get_name);

+/**
+ * cma_has_dynamic_base() - was the area placed at an address CMA chose?
+ * @cma: Contiguous memory region
+ *
+ * An area without a dynamic base was placed where something outside the kernel
+ * said it should go, typically a device tree reserved-memory node, and so lands
+ * at the same address in every kernel booted with that description.
+ */
+bool cma_has_dynamic_base(const struct cma *cma)
+{
+ return test_bit(CMA_DYNAMIC_BASE, &cma->flags);
+}
+
static unsigned long cma_bitmap_aligned_mask(const struct cma *cma,
unsigned int align_order)
{
@@ -519,6 +532,8 @@ static int __init __cma_declare_contiguous_nid(phys_addr_t *basep,
}

(*res_cma)->nid = nid;
+ if (!fixed)
+ set_bit(CMA_DYNAMIC_BASE, &(*res_cma)->flags);
*basep = base;

return 0;
@@ -708,6 +723,7 @@ int __init cma_declare_contiguous_multi(phys_addr_t total_size,

cma->nranges = nr;
cma->nid = nid;
+ set_bit(CMA_DYNAMIC_BASE, &cma->flags);
*res_cma = cma;

out:
diff --git a/mm/cma.h b/mm/cma.h
index 68e574b0f951..4703e0958b01 100644
--- a/mm/cma.h
+++ b/mm/cma.h
@@ -80,6 +80,8 @@ enum cma_flags {
CMA_ZONES_VALID,
CMA_ZONES_INVALID,
CMA_ACTIVATED,
+ /* Base address was chosen by CMA, not dictated by the caller. */
+ CMA_DYNAMIC_BASE,
};

extern struct cma cma_areas[MAX_CMA_AREAS];
--
2.44.4