[RFC RESEND v2] binfmt_elf: preserve original ELF e_flags for core dumps

Svetlana Parfenova posted 1 patch 1 month, 3 weeks ago
arch/riscv/include/asm/elf.h |  1 +
fs/binfmt_elf.c              | 34 ++++++++++++++++++++++++++++------
include/linux/mm_types.h     |  3 +++
3 files changed, 32 insertions(+), 6 deletions(-)
[RFC RESEND v2] binfmt_elf: preserve original ELF e_flags for core dumps
Posted by Svetlana Parfenova 1 month, 3 weeks ago
Some architectures, such as RISC-V, use the ELF e_flags field to encode
ABI-specific information (e.g., ISA extensions, fpu support). Debuggers
like GDB rely on these flags in core dumps to correctly interpret
optional register sets. If the flags are missing or incorrect, GDB may
warn and ignore valid data, for example:

    warning: Unexpected size of section '.reg2/213' in core file.

This can prevent access to fpu or other architecture-specific registers
even when they were dumped.

Save the e_flags field during ELF binary loading (in load_elf_binary())
into the mm_struct, and later retrieve it during core dump generation
(in fill_note_info()). A new macro ELF_CORE_USE_PROCESS_EFLAGS allows
architectures to enable this behavior - currently just RISC-V.

Signed-off-by: Svetlana Parfenova <svetlana.parfenova@syntacore.com>
---
Changes in v2:
 - Remove usage of Kconfig option.
 - Add an architecture-optional macro to set process e_flags. Enabled
   by defining ELF_CORE_USE_PROCESS_EFLAGS. Defaults to no-op if not
   used.

 arch/riscv/include/asm/elf.h |  1 +
 fs/binfmt_elf.c              | 34 ++++++++++++++++++++++++++++------
 include/linux/mm_types.h     |  3 +++
 3 files changed, 32 insertions(+), 6 deletions(-)

diff --git a/arch/riscv/include/asm/elf.h b/arch/riscv/include/asm/elf.h
index c7aea7886d22..5d9f0ac851ee 100644
--- a/arch/riscv/include/asm/elf.h
+++ b/arch/riscv/include/asm/elf.h
@@ -20,6 +20,7 @@
  * These are used to set parameters in the core dumps.
  */
 #define ELF_ARCH	EM_RISCV
+#define ELF_CORE_USE_PROCESS_EFLAGS
 
 #ifndef ELF_CLASS
 #ifdef CONFIG_64BIT
diff --git a/fs/binfmt_elf.c b/fs/binfmt_elf.c
index caeddccaa1fe..e52b1e077218 100644
--- a/fs/binfmt_elf.c
+++ b/fs/binfmt_elf.c
@@ -66,6 +66,14 @@
 #define elf_check_fdpic(ex) false
 #endif
 
+#ifdef ELF_CORE_USE_PROCESS_EFLAGS
+#define elf_coredump_get_process_eflags(dump_task, e_flags) \
+	(*(e_flags) = (dump_task)->mm->saved_e_flags)
+#else
+#define elf_coredump_get_process_eflags(dump_task, e_flags) \
+	do { (void)(dump_task); (void)(e_flags); } while (0)
+#endif
+
 static int load_elf_binary(struct linux_binprm *bprm);
 
 /*
@@ -1290,6 +1298,9 @@ static int load_elf_binary(struct linux_binprm *bprm)
 	mm->end_data = end_data;
 	mm->start_stack = bprm->p;
 
+	/* stash e_flags for use in core dumps */
+	mm->saved_e_flags = elf_ex->e_flags;
+
 	/**
 	 * DOC: "brk" handling
 	 *
@@ -1804,6 +1815,8 @@ static int fill_note_info(struct elfhdr *elf, int phdrs,
 	struct elf_thread_core_info *t;
 	struct elf_prpsinfo *psinfo;
 	struct core_thread *ct;
+	u16 machine;
+	u32 flags;
 
 	psinfo = kmalloc(sizeof(*psinfo), GFP_KERNEL);
 	if (!psinfo)
@@ -1831,17 +1844,26 @@ static int fill_note_info(struct elfhdr *elf, int phdrs,
 		return 0;
 	}
 
-	/*
-	 * Initialize the ELF file header.
-	 */
-	fill_elf_header(elf, phdrs,
-			view->e_machine, view->e_flags);
+	machine = view->e_machine;
+	flags = view->e_flags;
 #else
 	view = NULL;
 	info->thread_notes = 2;
-	fill_elf_header(elf, phdrs, ELF_ARCH, ELF_CORE_EFLAGS);
+	machine = ELF_ARCH;
+	flags = ELF_CORE_EFLAGS;
 #endif
 
+	/*
+	 * Override ELF e_flags with value taken from process,
+	 * if arch wants to.
+	 */
+	elf_coredump_get_process_eflags(dump_task, &flags);
+
+	/*
+	 * Initialize the ELF file header.
+	 */
+	fill_elf_header(elf, phdrs, machine, flags);
+
 	/*
 	 * Allocate a structure for each thread.
 	 */
diff --git a/include/linux/mm_types.h b/include/linux/mm_types.h
index d6b91e8a66d6..e46f554f8d91 100644
--- a/include/linux/mm_types.h
+++ b/include/linux/mm_types.h
@@ -1098,6 +1098,9 @@ struct mm_struct {
 
 		unsigned long saved_auxv[AT_VECTOR_SIZE]; /* for /proc/PID/auxv */
 
+		/* the ABI-related flags from the ELF header. Used for core dump */
+		unsigned long saved_e_flags;
+
 		struct percpu_counter rss_stat[NR_MM_COUNTERS];
 
 		struct linux_binfmt *binfmt;
-- 
2.50.1
Re: [RFC RESEND v2] binfmt_elf: preserve original ELF e_flags for core dumps
Posted by Kees Cook 1 month, 1 week ago
On Mon, Aug 11, 2025 at 03:53:28PM +0600, Svetlana Parfenova wrote:
> Some architectures, such as RISC-V, use the ELF e_flags field to encode
> ABI-specific information (e.g., ISA extensions, fpu support). Debuggers
> like GDB rely on these flags in core dumps to correctly interpret
> optional register sets. If the flags are missing or incorrect, GDB may
> warn and ignore valid data, for example:
> 
>     warning: Unexpected size of section '.reg2/213' in core file.
> 
> This can prevent access to fpu or other architecture-specific registers
> even when they were dumped.
> 
> Save the e_flags field during ELF binary loading (in load_elf_binary())
> into the mm_struct, and later retrieve it during core dump generation
> (in fill_note_info()). A new macro ELF_CORE_USE_PROCESS_EFLAGS allows
> architectures to enable this behavior - currently just RISC-V.
> 
> Signed-off-by: Svetlana Parfenova <svetlana.parfenova@syntacore.com>
> ---
> Changes in v2:
>  - Remove usage of Kconfig option.
>  - Add an architecture-optional macro to set process e_flags. Enabled
>    by defining ELF_CORE_USE_PROCESS_EFLAGS. Defaults to no-op if not
>    used.
> 
>  arch/riscv/include/asm/elf.h |  1 +
>  fs/binfmt_elf.c              | 34 ++++++++++++++++++++++++++++------
>  include/linux/mm_types.h     |  3 +++
>  3 files changed, 32 insertions(+), 6 deletions(-)
> 
> diff --git a/arch/riscv/include/asm/elf.h b/arch/riscv/include/asm/elf.h
> index c7aea7886d22..5d9f0ac851ee 100644
> --- a/arch/riscv/include/asm/elf.h
> +++ b/arch/riscv/include/asm/elf.h
> @@ -20,6 +20,7 @@
>   * These are used to set parameters in the core dumps.
>   */
>  #define ELF_ARCH	EM_RISCV
> +#define ELF_CORE_USE_PROCESS_EFLAGS

Let's move this to the per-arch Kconfig instead, that way we can use it
in other places. Maybe call in CONFIG_ARCH_HAS_ELF_CORE_EFLAGS?

>  
>  #ifndef ELF_CLASS
>  #ifdef CONFIG_64BIT
> diff --git a/fs/binfmt_elf.c b/fs/binfmt_elf.c
> index caeddccaa1fe..e52b1e077218 100644
> --- a/fs/binfmt_elf.c
> +++ b/fs/binfmt_elf.c
> @@ -66,6 +66,14 @@
>  #define elf_check_fdpic(ex) false
>  #endif
>  
> +#ifdef ELF_CORE_USE_PROCESS_EFLAGS
> +#define elf_coredump_get_process_eflags(dump_task, e_flags) \
> +	(*(e_flags) = (dump_task)->mm->saved_e_flags)
> +#else
> +#define elf_coredump_get_process_eflags(dump_task, e_flags) \
> +	do { (void)(dump_task); (void)(e_flags); } while (0)
> +#endif

Let's make specific set/get helpers here, instead.

static inline
u32 coredump_get_mm_eflags(struct mm_struct *mm, u32 flags)
{
#ifdef CONFIG_ARCH_HAS_ELF_CORE_EFLAGS
	flags = mm->saved_e_flags;
#else
	return flags;
}

static inline
void coredump_set_mm_eflags(struct mm_struct *mm, u32 flags)
{
#ifdef CONFIG_ARCH_HAS_ELF_CORE_EFLAGS
	mm->saved_e_flags = flags;
#endif
}


> +
>  static int load_elf_binary(struct linux_binprm *bprm);
>  
>  /*
> @@ -1290,6 +1298,9 @@ static int load_elf_binary(struct linux_binprm *bprm)
>  	mm->end_data = end_data;
>  	mm->start_stack = bprm->p;
>  
> +	/* stash e_flags for use in core dumps */
> +	mm->saved_e_flags = elf_ex->e_flags;

Then this is:

	coredump_set_mm_eflags(mm, elf_ex->e_flags);

> +
>  	/**
>  	 * DOC: "brk" handling
>  	 *
> @@ -1804,6 +1815,8 @@ static int fill_note_info(struct elfhdr *elf, int phdrs,
>  	struct elf_thread_core_info *t;
>  	struct elf_prpsinfo *psinfo;
>  	struct core_thread *ct;
> +	u16 machine;
> +	u32 flags;
>  
>  	psinfo = kmalloc(sizeof(*psinfo), GFP_KERNEL);
>  	if (!psinfo)
> @@ -1831,17 +1844,26 @@ static int fill_note_info(struct elfhdr *elf, int phdrs,
>  		return 0;
>  	}
>  
> -	/*
> -	 * Initialize the ELF file header.
> -	 */
> -	fill_elf_header(elf, phdrs,
> -			view->e_machine, view->e_flags);
> +	machine = view->e_machine;
> +	flags = view->e_flags;
>  #else
>  	view = NULL;
>  	info->thread_notes = 2;
> -	fill_elf_header(elf, phdrs, ELF_ARCH, ELF_CORE_EFLAGS);
> +	machine = ELF_ARCH;
> +	flags = ELF_CORE_EFLAGS;
>  #endif
>  
> +	/*
> +	 * Override ELF e_flags with value taken from process,
> +	 * if arch wants to.
> +	 */
> +	elf_coredump_get_process_eflags(dump_task, &flags);

And this is:

	flags = coredump_get_mm_eflags(dump_task->mm, flags);

> +
> +	/*
> +	 * Initialize the ELF file header.
> +	 */
> +	fill_elf_header(elf, phdrs, machine, flags);
> +
>  	/*
>  	 * Allocate a structure for each thread.
>  	 */
> diff --git a/include/linux/mm_types.h b/include/linux/mm_types.h
> index d6b91e8a66d6..e46f554f8d91 100644
> --- a/include/linux/mm_types.h
> +++ b/include/linux/mm_types.h
> @@ -1098,6 +1098,9 @@ struct mm_struct {
>  
>  		unsigned long saved_auxv[AT_VECTOR_SIZE]; /* for /proc/PID/auxv */
>  

And then add:

#ifdef CONFIG_ARCH_HAS_ELF_CORE_EFLAGS

> +		/* the ABI-related flags from the ELF header. Used for core dump */
> +		unsigned long saved_e_flags;

#endif

around this part

> +
>  		struct percpu_counter rss_stat[NR_MM_COUNTERS];
>  
>  		struct linux_binfmt *binfmt;
> -- 
> 2.50.1
> 

-- 
Kees Cook
Re: [RFC RESEND v2] binfmt_elf: preserve original ELF e_flags for core dumps
Posted by Svetlana Parfenova 1 month ago
On 26/08/2025 00.17, Kees Cook wrote:
> On Mon, Aug 11, 2025 at 03:53:28PM +0600, Svetlana Parfenova wrote:
>> Some architectures, such as RISC-V, use the ELF e_flags field to encode
>> ABI-specific information (e.g., ISA extensions, fpu support). Debuggers
>> like GDB rely on these flags in core dumps to correctly interpret
>> optional register sets. If the flags are missing or incorrect, GDB may
>> warn and ignore valid data, for example:
>>
>>      warning: Unexpected size of section '.reg2/213' in core file.
>>
>> This can prevent access to fpu or other architecture-specific registers
>> even when they were dumped.
>>
>> Save the e_flags field during ELF binary loading (in load_elf_binary())
>> into the mm_struct, and later retrieve it during core dump generation
>> (in fill_note_info()). A new macro ELF_CORE_USE_PROCESS_EFLAGS allows
>> architectures to enable this behavior - currently just RISC-V.
>>
>> Signed-off-by: Svetlana Parfenova <svetlana.parfenova@syntacore.com>
>> ---
>> Changes in v2:
>>   - Remove usage of Kconfig option.
>>   - Add an architecture-optional macro to set process e_flags. Enabled
>>     by defining ELF_CORE_USE_PROCESS_EFLAGS. Defaults to no-op if not
>>     used.
>>
>>   arch/riscv/include/asm/elf.h |  1 +
>>   fs/binfmt_elf.c              | 34 ++++++++++++++++++++++++++++------
>>   include/linux/mm_types.h     |  3 +++
>>   3 files changed, 32 insertions(+), 6 deletions(-)
>>
>> diff --git a/arch/riscv/include/asm/elf.h b/arch/riscv/include/asm/elf.h
>> index c7aea7886d22..5d9f0ac851ee 100644
>> --- a/arch/riscv/include/asm/elf.h
>> +++ b/arch/riscv/include/asm/elf.h
>> @@ -20,6 +20,7 @@
>>    * These are used to set parameters in the core dumps.
>>    */
>>   #define ELF_ARCH	EM_RISCV
>> +#define ELF_CORE_USE_PROCESS_EFLAGS
> 
> Let's move this to the per-arch Kconfig instead, that way we can use it
> in other places. Maybe call in CONFIG_ARCH_HAS_ELF_CORE_EFLAGS?
> 
>>   
>>   #ifndef ELF_CLASS
>>   #ifdef CONFIG_64BIT
>> diff --git a/fs/binfmt_elf.c b/fs/binfmt_elf.c
>> index caeddccaa1fe..e52b1e077218 100644
>> --- a/fs/binfmt_elf.c
>> +++ b/fs/binfmt_elf.c
>> @@ -66,6 +66,14 @@
>>   #define elf_check_fdpic(ex) false
>>   #endif
>>   
>> +#ifdef ELF_CORE_USE_PROCESS_EFLAGS
>> +#define elf_coredump_get_process_eflags(dump_task, e_flags) \
>> +	(*(e_flags) = (dump_task)->mm->saved_e_flags)
>> +#else
>> +#define elf_coredump_get_process_eflags(dump_task, e_flags) \
>> +	do { (void)(dump_task); (void)(e_flags); } while (0)
>> +#endif
> 
> Let's make specific set/get helpers here, instead.
> 
> static inline
> u32 coredump_get_mm_eflags(struct mm_struct *mm, u32 flags)
> {
> #ifdef CONFIG_ARCH_HAS_ELF_CORE_EFLAGS
> 	flags = mm->saved_e_flags;
> #else
> 	return flags;
> }
> 
> static inline
> void coredump_set_mm_eflags(struct mm_struct *mm, u32 flags)
> {
> #ifdef CONFIG_ARCH_HAS_ELF_CORE_EFLAGS
> 	mm->saved_e_flags = flags;
> #endif
> }
> 
> 
>> +
>>   static int load_elf_binary(struct linux_binprm *bprm);
>>   
>>   /*
>> @@ -1290,6 +1298,9 @@ static int load_elf_binary(struct linux_binprm *bprm)
>>   	mm->end_data = end_data;
>>   	mm->start_stack = bprm->p;
>>   
>> +	/* stash e_flags for use in core dumps */
>> +	mm->saved_e_flags = elf_ex->e_flags;
> 
> Then this is:
> 
> 	coredump_set_mm_eflags(mm, elf_ex->e_flags);
> 
>> +
>>   	/**
>>   	 * DOC: "brk" handling
>>   	 *
>> @@ -1804,6 +1815,8 @@ static int fill_note_info(struct elfhdr *elf, int phdrs,
>>   	struct elf_thread_core_info *t;
>>   	struct elf_prpsinfo *psinfo;
>>   	struct core_thread *ct;
>> +	u16 machine;
>> +	u32 flags;
>>   
>>   	psinfo = kmalloc(sizeof(*psinfo), GFP_KERNEL);
>>   	if (!psinfo)
>> @@ -1831,17 +1844,26 @@ static int fill_note_info(struct elfhdr *elf, int phdrs,
>>   		return 0;
>>   	}
>>   
>> -	/*
>> -	 * Initialize the ELF file header.
>> -	 */
>> -	fill_elf_header(elf, phdrs,
>> -			view->e_machine, view->e_flags);
>> +	machine = view->e_machine;
>> +	flags = view->e_flags;
>>   #else
>>   	view = NULL;
>>   	info->thread_notes = 2;
>> -	fill_elf_header(elf, phdrs, ELF_ARCH, ELF_CORE_EFLAGS);
>> +	machine = ELF_ARCH;
>> +	flags = ELF_CORE_EFLAGS;
>>   #endif
>>   
>> +	/*
>> +	 * Override ELF e_flags with value taken from process,
>> +	 * if arch wants to.
>> +	 */
>> +	elf_coredump_get_process_eflags(dump_task, &flags);
> 
> And this is:
> 
> 	flags = coredump_get_mm_eflags(dump_task->mm, flags);
> 
>> +
>> +	/*
>> +	 * Initialize the ELF file header.
>> +	 */
>> +	fill_elf_header(elf, phdrs, machine, flags);
>> +
>>   	/*
>>   	 * Allocate a structure for each thread.
>>   	 */
>> diff --git a/include/linux/mm_types.h b/include/linux/mm_types.h
>> index d6b91e8a66d6..e46f554f8d91 100644
>> --- a/include/linux/mm_types.h
>> +++ b/include/linux/mm_types.h
>> @@ -1098,6 +1098,9 @@ struct mm_struct {
>>   
>>   		unsigned long saved_auxv[AT_VECTOR_SIZE]; /* for /proc/PID/auxv */
>>   
> 
> And then add:
> 
> #ifdef CONFIG_ARCH_HAS_ELF_CORE_EFLAGS
> 
>> +		/* the ABI-related flags from the ELF header. Used for core dump */
>> +		unsigned long saved_e_flags;
> 
> #endif
> 
> around this part
> 
>> +
>>   		struct percpu_counter rss_stat[NR_MM_COUNTERS];
>>   
>>   		struct linux_binfmt *binfmt;
>> -- 
>> 2.50.1
>>
> 

Thank you for review! I have addressed your comments in v3 of the patch.

-- 
Best regards,
Svetlana Parfenova