* [PATCH v2 1/3] kexec_file: update IMA after machine post-load
2026-11-12 12:52 [PATCH v2 0/3] ima: keep kexec dump state tied to installed image Jérémy Jean
@ 2026-11-12 12:52 ` Jérémy Jean
2026-11-12 12:52 ` [PATCH v2 2/3] ima: keep kexec dump state tied to installed image Jérémy Jean
2026-11-12 12:52 ` [PATCH v2 3/3] ima: clear kexec state when replacing file image Jérémy Jean
2 siblings, 0 replies; 5+ messages in thread
From: Jérémy Jean @ 2026-11-12 12:52 UTC (permalink / raw)
To: Andrew Morton, Baoquan He, Mike Rapoport, Pasha Tatashin,
Pratyush Yadav, Mimi Zohar, Roberto Sassu, Dmitry Kasatkin
Cc: Eric Snowberg, linux-integrity, linux-security-module,
linux-kernel, kexec, Jérémy Jean, stable
ima_kexec_post_load() replaces the global IMA mapping with a mapping for
the new image. If machine_kexec_post_load() fails, the new image is
freed but that mapping remains. The installed image no longer receives
the execute-time IMA update, and the notifier can write the
measurement list into freed pages.
Call the machine hook first. Return its error before updating IMA so IMA
changes the mapping only for an image which will be installed.
Fixes: 9f0ec4b16f2b ("ima: kexec: move IMA log copy from kexec load to execute")
Cc: stable@vger.kernel.org
Signed-off-by: Jérémy Jean <Jeremy.Jean@oss.cyber.gouv.fr>
---
kernel/kexec_file.c | 8 +++++++-
1 file changed, 7 insertions(+), 1 deletion(-)
diff --git a/kernel/kexec_file.c b/kernel/kexec_file.c
index b668ce8d68ee..59f9ba1b0cd7 100644
--- a/kernel/kexec_file.c
+++ b/kernel/kexec_file.c
@@ -206,11 +206,17 @@ kimage_validate_signature(struct kimage *image)
static int kexec_post_load(struct kimage *image, unsigned long flags)
{
+ int ret;
+
+ ret = machine_kexec_post_load(image);
+ if (ret)
+ return ret;
+
#ifdef CONFIG_IMA_KEXEC
if (!(flags & KEXEC_FILE_ON_CRASH))
ima_kexec_post_load(image);
#endif
- return machine_kexec_post_load(image);
+ return 0;
}
/*
^ permalink raw reply [flat|nested] 5+ messages in thread* [PATCH v2 2/3] ima: keep kexec dump state tied to installed image
2026-11-12 12:52 [PATCH v2 0/3] ima: keep kexec dump state tied to installed image Jérémy Jean
2026-11-12 12:52 ` [PATCH v2 1/3] kexec_file: update IMA after machine post-load Jérémy Jean
@ 2026-11-12 12:52 ` Jérémy Jean
2026-11-13 20:50 ` Mimi Zohar
2026-11-12 12:52 ` [PATCH v2 3/3] ima: clear kexec state when replacing file image Jérémy Jean
2 siblings, 1 reply; 5+ messages in thread
From: Jérémy Jean @ 2026-11-12 12:52 UTC (permalink / raw)
To: Andrew Morton, Baoquan He, Mike Rapoport, Pasha Tatashin,
Pratyush Yadav, Mimi Zohar, Roberto Sassu, Dmitry Kasatkin
Cc: Eric Snowberg, linux-integrity, linux-security-module,
linux-kernel, kexec, Jérémy Jean, stable
ima_add_kexec_buffer() updates the global measurement scratch buffer
and segment size for the new image. If kexec_file_load() fails, the
new image is freed but the scratch buffer and segment size remain. When
kexec starts the installed image, ima_update_kexec_buffer() dumps the
measurement list into the IMA segment. A larger failed segment causes an
OOB write, while a smaller one truncates the log and reports the wrong
size in the kexec_execute event. KASAN for instsance reports the OOB
writes as:
BUG: KASAN: vmalloc-out-of-bounds in ima_update_kexec_buffer+0x265/0x280
Write of size 868426 at addr ffffc9000004e000 by task init/1
...
__asan_memcpy+0x3b/0x60
ima_update_kexec_buffer+0x265/0x280
Leave installed scratch state unchanged until all later load steps pass.
Store new buffers in the candidate and reuse only equal-size installed
buffers. Emit kexec_load before those steps with the local size.
Allocation failure still omits the optional segment.
Fixes: 9f0ec4b16f2b ("ima: kexec: move IMA log copy from kexec load to execute")
Cc: stable@vger.kernel.org
Signed-off-by: Jérémy Jean <Jeremy.Jean@oss.cyber.gouv.fr>
---
security/integrity/ima/ima_kexec.c | 74 ++++++++++++++++--------------
1 file changed, 39 insertions(+), 35 deletions(-)
diff --git a/security/integrity/ima/ima_kexec.c b/security/integrity/ima/ima_kexec.c
index 0d845693a1f7..3aa8b1096f81 100644
--- a/security/integrity/ima/ima_kexec.c
+++ b/security/integrity/ima/ima_kexec.c
@@ -35,7 +35,8 @@ static void ima_free_kexec_file_buf(struct seq_file *sf)
sf->count = 0;
}
-void ima_measure_kexec_event(const char *event_name)
+static void ima_measure_kexec_event_size(const char *event_name,
+ size_t segment_size)
{
char ima_kexec_event[IMA_KEXEC_EVENT_LEN];
size_t buf_size = 0;
@@ -48,35 +49,26 @@ void ima_measure_kexec_event(const char *event_name)
n = scnprintf(ima_kexec_event, IMA_KEXEC_EVENT_LEN,
"kexec_segment_size=%lu;ima_binary_runtime_size=%lu;"
"ima_runtime_measurements_count=%ld;",
- kexec_segment_size, buf_size, len);
+ segment_size, buf_size, len);
ima_measure_critical_data("ima_kexec", event_name, ima_kexec_event, n, false, NULL, 0);
}
-static int ima_alloc_kexec_file_buf(size_t segment_size)
+void ima_measure_kexec_event(const char *event_name)
{
- /*
- * kexec 'load' may be called multiple times.
- * Free and realloc the buffer only if the segment_size is
- * changed from the previous kexec 'load' call.
- */
- if (ima_kexec_file.buf && ima_kexec_file.size == segment_size)
- goto out;
+ ima_measure_kexec_event_size(event_name, kexec_segment_size);
+}
- ima_free_kexec_file_buf(&ima_kexec_file);
+static int ima_alloc_kexec_file_buf(struct kimage *image, size_t segment_size)
+{
+ if (ima_kexec_file.buf && ima_kexec_file.size == segment_size)
+ return 0;
/* segment size can't change between kexec load and execute */
- ima_kexec_file.buf = vmalloc(segment_size);
- if (!ima_kexec_file.buf)
+ image->ima_buffer = vmalloc(segment_size);
+ if (!image->ima_buffer)
return -ENOMEM;
- ima_kexec_file.size = segment_size;
-
-out:
- ima_kexec_file.read_pos = 0;
- ima_kexec_file.count = sizeof(struct ima_kexec_hdr); /* reserved space */
- ima_measure_kexec_event("kexec_load");
-
return 0;
}
@@ -160,10 +152,8 @@ void ima_add_kexec_buffer(struct kimage *image)
.top_down = true };
unsigned long binary_runtime_size;
unsigned long extra_memory;
+ size_t segment_size;
- /* use more understandable variable names than defined in kbuf */
- size_t kexec_buffer_size = 0;
- void *kexec_buffer = NULL;
int ret;
if (image->type == KEXEC_TYPE_CRASH)
@@ -182,36 +172,36 @@ void ima_add_kexec_buffer(struct kimage *image)
extra_memory;
if (binary_runtime_size >= ULONG_MAX - PAGE_SIZE)
- kexec_segment_size = ULONG_MAX;
+ segment_size = ULONG_MAX;
else
- kexec_segment_size = ALIGN(binary_runtime_size, PAGE_SIZE);
+ segment_size = ALIGN(binary_runtime_size, PAGE_SIZE);
- if ((kexec_segment_size == ULONG_MAX) ||
- ((kexec_segment_size >> PAGE_SHIFT) > totalram_pages() / 2)) {
+ if (segment_size == ULONG_MAX ||
+ (segment_size >> PAGE_SHIFT) > totalram_pages() / 2) {
pr_err("Binary measurement list too large.\n");
return;
}
- ret = ima_alloc_kexec_file_buf(kexec_segment_size);
+ ret = ima_alloc_kexec_file_buf(image, segment_size);
if (ret < 0) {
pr_err("Not enough memory for the kexec measurement buffer.\n");
return;
}
- kbuf.buffer = kexec_buffer;
- kbuf.bufsz = kexec_buffer_size;
- kbuf.memsz = kexec_segment_size;
+ ima_measure_kexec_event_size("kexec_load", segment_size);
+
+ kbuf.memsz = segment_size;
image->is_ima_segment_index_set = false;
ret = kexec_add_buffer(&kbuf);
if (ret) {
pr_err("Error passing over kexec measurement buffer.\n");
- vfree(kexec_buffer);
+ vfree(image->ima_buffer);
+ image->ima_buffer = NULL;
return;
}
image->ima_buffer_addr = kbuf.mem;
- image->ima_buffer_size = kexec_segment_size;
- image->ima_buffer = kexec_buffer;
+ image->ima_buffer_size = segment_size;
image->ima_segment_index = image->nr_segments - 1;
image->is_ima_segment_index_set = true;
@@ -269,8 +259,22 @@ void ima_kexec_post_load(struct kimage *image)
ima_kexec_buffer = NULL;
}
- if (!image->ima_buffer_addr)
+ if (!image->ima_buffer_addr) {
+ kexec_segment_size = 0;
+ ima_free_kexec_file_buf(&ima_kexec_file);
return;
+ }
+
+ if (image->ima_buffer) {
+ ima_free_kexec_file_buf(&ima_kexec_file);
+ ima_kexec_file.buf = image->ima_buffer;
+ image->ima_buffer = NULL;
+ }
+
+ kexec_segment_size = image->ima_buffer_size;
+ ima_kexec_file.size = image->ima_buffer_size;
+ ima_kexec_file.read_pos = 0;
+ ima_kexec_file.count = sizeof(struct ima_kexec_hdr); /* reserved space */
ima_kexec_buffer = kimage_map_segment(image, image->ima_segment_index);
if (!ima_kexec_buffer) {
^ permalink raw reply [flat|nested] 5+ messages in thread* Re: [PATCH v2 2/3] ima: keep kexec dump state tied to installed image
2026-11-12 12:52 ` [PATCH v2 2/3] ima: keep kexec dump state tied to installed image Jérémy Jean
@ 2026-11-13 20:50 ` Mimi Zohar
0 siblings, 0 replies; 5+ messages in thread
From: Mimi Zohar @ 2026-11-13 20:50 UTC (permalink / raw)
To: Jérémy Jean, Andrew Morton, Baoquan He, Mike Rapoport,
Pasha Tatashin, Pratyush Yadav, Roberto Sassu, Dmitry Kasatkin
Cc: Eric Snowberg, linux-integrity, linux-security-module,
linux-kernel, kexec, stable
On Thu, 2026-11-12 at 12:52 +0000, Jérémy Jean wrote:
[...]
> diff --git a/security/integrity/ima/ima_kexec.c b/security/integrity/ima/ima_kexec.c
> index 0d845693a1f7..3aa8b1096f81 100644
> --- a/security/integrity/ima/ima_kexec.c
> +++ b/security/integrity/ima/ima_kexec.c
> @@ -35,7 +35,8 @@ static void ima_free_kexec_file_buf(struct seq_file *sf)
> sf->count = 0;
> }
>
> -void ima_measure_kexec_event(const char *event_name)
> +static void ima_measure_kexec_event_size(const char *event_name,
> + size_t segment_size)
> {
> char ima_kexec_event[IMA_KEXEC_EVENT_LEN];
> size_t buf_size = 0;
> @@ -48,35 +49,26 @@ void ima_measure_kexec_event(const char *event_name)
> n = scnprintf(ima_kexec_event, IMA_KEXEC_EVENT_LEN,
> "kexec_segment_size=%lu;ima_binary_runtime_size=%lu;"
> "ima_runtime_measurements_count=%ld;",
> - kexec_segment_size, buf_size, len);
> + segment_size, buf_size, len);
>
> ima_measure_critical_data("ima_kexec", event_name, ima_kexec_event, n, false, NULL, 0);
> }
>
> -static int ima_alloc_kexec_file_buf(size_t segment_size)
> +void ima_measure_kexec_event(const char *event_name)
> {
> - /*
> - * kexec 'load' may be called multiple times.
> - * Free and realloc the buffer only if the segment_size is
> - * changed from the previous kexec 'load' call.
> - */
> - if (ima_kexec_file.buf && ima_kexec_file.size == segment_size)
> - goto out;
> + ima_measure_kexec_event_size(event_name, kexec_segment_size);
> +}
>
> - ima_free_kexec_file_buf(&ima_kexec_file);
> +static int ima_alloc_kexec_file_buf(struct kimage *image, size_t segment_size)
> +{
> + if (ima_kexec_file.buf && ima_kexec_file.size == segment_size)
> + return 0;
Please don't drop the comment. The crux of the problem is that a kexec
load may fail, so the installed image's state shouldn't change until
the new image is installed.
To keep this patch as small as possible, could the event-size change be
a separate patch, applied after this one? That would make the overflow
fix easier to review and backport.
Suggested comment:
* kexec 'load' may be called multiple times, and a load may fail.
* ima_kexec_file.size is the segment size of the installed image and
* is only set in ima_kexec_post_load(). Reuse the installed buffer if
* the segment_size is unchanged, otherwise stage a new one on the image.
Mimi
>
> /* segment size can't change between kexec load and execute */
> - ima_kexec_file.buf = vmalloc(segment_size);
> - if (!ima_kexec_file.buf)
> + image->ima_buffer = vmalloc(segment_size);
> + if (!image->ima_buffer)
> return -ENOMEM;
>
> - ima_kexec_file.size = segment_size;
> -
> -out:
> - ima_kexec_file.read_pos = 0;
> - ima_kexec_file.count = sizeof(struct ima_kexec_hdr); /* reserved space */
> - ima_measure_kexec_event("kexec_load");
> -
> return 0;
> }
>
^ permalink raw reply [flat|nested] 5+ messages in thread
* [PATCH v2 3/3] ima: clear kexec state when replacing file image
2026-11-12 12:52 [PATCH v2 0/3] ima: keep kexec dump state tied to installed image Jérémy Jean
2026-11-12 12:52 ` [PATCH v2 1/3] kexec_file: update IMA after machine post-load Jérémy Jean
2026-11-12 12:52 ` [PATCH v2 2/3] ima: keep kexec dump state tied to installed image Jérémy Jean
@ 2026-11-12 12:52 ` Jérémy Jean
2 siblings, 0 replies; 5+ messages in thread
From: Jérémy Jean @ 2026-11-12 12:52 UTC (permalink / raw)
To: Andrew Morton, Baoquan He, Mike Rapoport, Pasha Tatashin,
Pratyush Yadav, Mimi Zohar, Roberto Sassu, Dmitry Kasatkin
Cc: Eric Snowberg, linux-integrity, linux-security-module,
linux-kernel, kexec, Jérémy Jean, stable
ima_kexec_buffer remains mapped to the IMA segment of the installed
kexec_file_load image. KEXEC_FILE_UNLOAD and a successful normal
kexec_load can replace and free that image without clearing the mapping.
Executing a later kexec_load image can then write the measurement list
into the freed pages. The new image has an IMA segment size of zero, but
size does not establish ownership of the mapping.
Record the image whose segment is mapped and verify it before the
execute-time update. Clear the mapping and scratch state under kexec_lock
when a normal image is removed without another file image taking its
place.
Fixes: 9f0ec4b16f2b ("ima: kexec: move IMA log copy from kexec load to execute")
Cc: stable@vger.kernel.org
Signed-off-by: Jérémy Jean <Jeremy.Jean@oss.cyber.gouv.fr>
---
include/linux/ima.h | 1 +
kernel/kexec.c | 11 +++++++++++
kernel/kexec_file.c | 7 ++++++-
security/integrity/ima/ima_kexec.c | 24 ++++++++++++++++++++++++
4 files changed, 42 insertions(+), 1 deletion(-)
diff --git a/include/linux/ima.h b/include/linux/ima.h
index 8e08baf16c2f..d61b7b7756b7 100644
--- a/include/linux/ima.h
+++ b/include/linux/ima.h
@@ -34,6 +34,7 @@ static inline void ima_appraise_parse_cmdline(void) {}
#ifdef CONFIG_IMA_KEXEC
extern void ima_add_kexec_buffer(struct kimage *image);
extern void ima_kexec_post_load(struct kimage *image);
+void ima_kexec_cleanup(void);
#else
static inline void ima_kexec_post_load(struct kimage *image) {}
#endif
diff --git a/kernel/kexec.c b/kernel/kexec.c
index 90756dc6339b..297f4657869b 100644
--- a/kernel/kexec.c
+++ b/kernel/kexec.c
@@ -11,6 +11,7 @@
#include <linux/file.h>
#include <linux/security.h>
#include <linux/kexec.h>
+#include <linux/ima.h>
#include <linux/mutex.h>
#include <linux/list.h>
#include <linux/syscalls.h>
@@ -114,6 +115,10 @@ static int do_kexec_load(unsigned long entry, unsigned long nr_segments,
if (nr_segments == 0) {
/* Uninstall image */
+#ifdef CONFIG_IMA_KEXEC
+ if (!(flags & KEXEC_ON_CRASH))
+ ima_kexec_cleanup();
+#endif
kimage_free(xchg(dest_image, NULL));
ret = 0;
goto out_unlock;
@@ -163,6 +168,12 @@ static int do_kexec_load(unsigned long entry, unsigned long nr_segments,
if (ret)
goto out;
+ /* A kexec_load image has no IMA measurement segment. */
+#ifdef CONFIG_IMA_KEXEC
+ if (!(flags & KEXEC_ON_CRASH))
+ ima_kexec_cleanup();
+#endif
+
/* Install the new kernel and uninstall the old */
image = xchg(dest_image, image);
diff --git a/kernel/kexec_file.c b/kernel/kexec_file.c
index 59f9ba1b0cd7..95b3a9ab1e3b 100644
--- a/kernel/kexec_file.c
+++ b/kernel/kexec_file.c
@@ -401,8 +401,13 @@ SYSCALL_DEFINE5(kexec_file_load, int, kernel_fd, int, initrd_fd,
#endif
dest_image = &kexec_image;
- if (flags & KEXEC_FILE_UNLOAD)
+ if (flags & KEXEC_FILE_UNLOAD) {
+#ifdef CONFIG_IMA_KEXEC
+ if (image_type == KEXEC_TYPE_DEFAULT)
+ ima_kexec_cleanup();
+#endif
goto exchange;
+ }
/*
* In case of crash, new kernel gets loaded in reserved region. It is
diff --git a/security/integrity/ima/ima_kexec.c b/security/integrity/ima/ima_kexec.c
index 3aa8b1096f81..cc15aed8df48 100644
--- a/security/integrity/ima/ima_kexec.c
+++ b/security/integrity/ima/ima_kexec.c
@@ -25,6 +25,7 @@ static bool ima_kexec_update_registered;
static struct seq_file ima_kexec_file;
static size_t kexec_segment_size;
static void *ima_kexec_buffer;
+static struct kimage *ima_kexec_image;
static void ima_free_kexec_file_buf(struct seq_file *sf)
{
@@ -229,6 +230,11 @@ static int ima_update_kexec_buffer(struct notifier_block *self,
return ret;
}
+ if (ima_kexec_image != kexec_image) {
+ pr_err("Kexec buffer does not match current image.\n");
+ return ret;
+ }
+
ret = ima_dump_measurement_list(&buf_size, &buf, kexec_segment_size);
if (ret)
@@ -239,6 +245,7 @@ static int ima_update_kexec_buffer(struct notifier_block *self,
kimage_unmap_segment(ima_kexec_buffer);
ima_kexec_buffer = NULL;
+ ima_kexec_image = NULL;
return ret;
}
@@ -258,6 +265,7 @@ void ima_kexec_post_load(struct kimage *image)
kimage_unmap_segment(ima_kexec_buffer);
ima_kexec_buffer = NULL;
}
+ ima_kexec_image = image;
if (!image->ima_buffer_addr) {
kexec_segment_size = 0;
@@ -288,6 +296,22 @@ void ima_kexec_post_load(struct kimage *image)
}
}
+/*
+ * Drop the dump state before its kexec image is replaced.
+ *
+ * This function assumes that kexec_lock is held.
+ */
+void ima_kexec_cleanup(void)
+{
+ if (ima_kexec_buffer) {
+ kimage_unmap_segment(ima_kexec_buffer);
+ ima_kexec_buffer = NULL;
+ }
+ ima_kexec_image = NULL;
+ kexec_segment_size = 0;
+ ima_free_kexec_file_buf(&ima_kexec_file);
+}
+
#endif /* IMA_KEXEC */
/*
^ permalink raw reply [flat|nested] 5+ messages in thread