[v3] readelf: Save and dump the original section header values

Message ID CAMe9rOrai4QD0Jq52-Zsd=hBNe9k2KnaqLZ7022yW=+8n_dX9w@mail.gmail.com
State New
Headers
Series [v3] readelf: Save and dump the original section header values |

Checks

Context Check Description
linaro-tcwg-bot/tcwg_binutils_build--master-aarch64 success Build passed
linaro-tcwg-bot/tcwg_binutils_build--master-arm success Build passed
linaro-tcwg-bot/tcwg_binutils_check--master-aarch64 success Test passed
linaro-tcwg-bot/tcwg_binutils_check--master-arm success Test passed

Commit Message

H.J. Lu July 4, 2026, 11:06 a.m. UTC
  On Sat, Jul 4, 2026 at 11:01 AM H.J. Lu <hjl.tools@gmail.com> wrote:
>
> On Sat, Jul 4, 2026 at 8:39 AM H.J. Lu <hjl.tools@gmail.com> wrote:
> >
> > On Sat, Jul 4, 2026 at 7:49 AM Alan Modra <amodra@gmail.com> wrote:
> > >
> > > On Fri, Jul 03, 2026 at 10:30:14PM +0800, H.J. Lu wrote:
> > > > validate_section_info clears the garbage values in the section header
> > > > to avoid crash later.  Save and dump the original section header values
> > > > to make the garbage values in the section header visible when dumping
> > > > section headers.
> > >
> > > I think this would be better done the other way around.  ie. have a
> > > Elf_Internal_Shdr **sane_section_headers that is initialised to point
> > > at entries in section_headers, with sane_section_headers[i] allocated
> > > as necessary when needing to correct a bogus header.
> > >
> >
> > I have thought about something similar and decided against it
> > since filedata->section_headers is used in many places.   However,
> > I can change filedata->orig_section_headers to on demand.
> >
> > --
> > H.J.
>
> Changes in v2:
>
> 1.  filedata->orig_section_headers is changed to on demand.

Changes in v3:

1.  Clear filedata->orig_section_headers first.

> --
> H.J.
> ---
> validate_section_info clears the garbage values in the section header
> to avoid crash later.  Save and dump the original section header values
> to make the garbage values in the section header visible when dumping
> section headers.
>
> Note: orig_section_headers, instead of sane_section_headers, is added to
> filedata since filedata->section_headers is used in many places.  Replace
> filedata->section_headers with filedata->sane_section_headers requires
> a much bigger change.
>
> * readelf.c (filedata): Add orig_section_headers.
> (save_original_section_header_values): New.
> (validate_section_info): Add a pointer to the original section
> header and call save_original_section_header_values to save the
> original section header values before clearing the section header
> fields.
> (get_32bit_section_headers): Allocate the original section header
> buffer.  Pass the original section header pointer to
> validate_section_info.
> (get_64bit_section_headers): Likewise.
> (process_section_headers): Dump the original section header
> values if they exist.
> (process_relocs): Pass a dummy original section pointer to
> validate_section_info.
> (free_filedata): Free filedata->orig_section_headers.
> * testsuite/binutils-all/corrupt-1.elf.bz2: New file.
> * testsuite/binutils-all/corrupt-1.r: Likewise.
> * testsuite/binutils-all/readelf.exp: Run corrupt-1.elf test.
  

Comments

Jan Beulich July 9, 2026, 6:06 a.m. UTC | #1
On 04.07.2026 13:06, H.J. Lu wrote:
> On Sat, Jul 4, 2026 at 11:01 AM H.J. Lu <hjl.tools@gmail.com> wrote:
>> On Sat, Jul 4, 2026 at 8:39 AM H.J. Lu <hjl.tools@gmail.com> wrote:
>>> On Sat, Jul 4, 2026 at 7:49 AM Alan Modra <amodra@gmail.com> wrote:
>>>>
>>>> On Fri, Jul 03, 2026 at 10:30:14PM +0800, H.J. Lu wrote:
>>>>> validate_section_info clears the garbage values in the section header
>>>>> to avoid crash later.  Save and dump the original section header values
>>>>> to make the garbage values in the section header visible when dumping
>>>>> section headers.
>>>>
>>>> I think this would be better done the other way around.  ie. have a
>>>> Elf_Internal_Shdr **sane_section_headers that is initialised to point
>>>> at entries in section_headers, with sane_section_headers[i] allocated
>>>> as necessary when needing to correct a bogus header.
>>>>
>>>
>>> I have thought about something similar and decided against it
>>> since filedata->section_headers is used in many places.   However,
>>> I can change filedata->orig_section_headers to on demand.

I find this a plausible argument, so ...

>> Changes in v2:
>>
>> 1.  filedata->orig_section_headers is changed to on demand.
> 
> Changes in v3:
> 
> 1.  Clear filedata->orig_section_headers first.

... the patch is okay unless you get an objection from Alan within another
day or two. Just one thing though: calloc() + memset(, 0,) is redundant.
Please drop those memset()s.

Jan
  
H.J. Lu July 9, 2026, 6:32 a.m. UTC | #2
On Thu, Jul 9, 2026 at 2:06 PM Jan Beulich <jbeulich@suse.com> wrote:
>
> On 04.07.2026 13:06, H.J. Lu wrote:
> > On Sat, Jul 4, 2026 at 11:01 AM H.J. Lu <hjl.tools@gmail.com> wrote:
> >> On Sat, Jul 4, 2026 at 8:39 AM H.J. Lu <hjl.tools@gmail.com> wrote:
> >>> On Sat, Jul 4, 2026 at 7:49 AM Alan Modra <amodra@gmail.com> wrote:
> >>>>
> >>>> On Fri, Jul 03, 2026 at 10:30:14PM +0800, H.J. Lu wrote:
> >>>>> validate_section_info clears the garbage values in the section header
> >>>>> to avoid crash later.  Save and dump the original section header values
> >>>>> to make the garbage values in the section header visible when dumping
> >>>>> section headers.
> >>>>
> >>>> I think this would be better done the other way around.  ie. have a
> >>>> Elf_Internal_Shdr **sane_section_headers that is initialised to point
> >>>> at entries in section_headers, with sane_section_headers[i] allocated
> >>>> as necessary when needing to correct a bogus header.
> >>>>
> >>>
> >>> I have thought about something similar and decided against it
> >>> since filedata->section_headers is used in many places.   However,
> >>> I can change filedata->orig_section_headers to on demand.
>
> I find this a plausible argument, so ...
>
> >> Changes in v2:
> >>
> >> 1.  filedata->orig_section_headers is changed to on demand.
> >
> > Changes in v3:
> >
> > 1.  Clear filedata->orig_section_headers first.
>
> ... the patch is okay unless you get an objection from Alan within another

Will do.

> day or two. Just one thing though: calloc() + memset(, 0,) is redundant.
> Please drop those memset()s.

It is cmalloc, not calloc:

  filedata->orig_section_headers = (Elf_Internal_Shdr **)
    cmalloc (num, sizeof (Elf_Internal_Shdr *));

cmalloc doesn't clear memory.  memset is needed.

> Jan
  
Jan Beulich July 9, 2026, 6:55 a.m. UTC | #3
On 09.07.2026 08:32, H.J. Lu wrote:
> On Thu, Jul 9, 2026 at 2:06 PM Jan Beulich <jbeulich@suse.com> wrote:
>>
>> On 04.07.2026 13:06, H.J. Lu wrote:
>>> On Sat, Jul 4, 2026 at 11:01 AM H.J. Lu <hjl.tools@gmail.com> wrote:
>>>> On Sat, Jul 4, 2026 at 8:39 AM H.J. Lu <hjl.tools@gmail.com> wrote:
>>>>> On Sat, Jul 4, 2026 at 7:49 AM Alan Modra <amodra@gmail.com> wrote:
>>>>>>
>>>>>> On Fri, Jul 03, 2026 at 10:30:14PM +0800, H.J. Lu wrote:
>>>>>>> validate_section_info clears the garbage values in the section header
>>>>>>> to avoid crash later.  Save and dump the original section header values
>>>>>>> to make the garbage values in the section header visible when dumping
>>>>>>> section headers.
>>>>>>
>>>>>> I think this would be better done the other way around.  ie. have a
>>>>>> Elf_Internal_Shdr **sane_section_headers that is initialised to point
>>>>>> at entries in section_headers, with sane_section_headers[i] allocated
>>>>>> as necessary when needing to correct a bogus header.
>>>>>>
>>>>>
>>>>> I have thought about something similar and decided against it
>>>>> since filedata->section_headers is used in many places.   However,
>>>>> I can change filedata->orig_section_headers to on demand.
>>
>> I find this a plausible argument, so ...
>>
>>>> Changes in v2:
>>>>
>>>> 1.  filedata->orig_section_headers is changed to on demand.
>>>
>>> Changes in v3:
>>>
>>> 1.  Clear filedata->orig_section_headers first.
>>
>> ... the patch is okay unless you get an objection from Alan within another
> 
> Will do.
> 
>> day or two. Just one thing though: calloc() + memset(, 0,) is redundant.
>> Please drop those memset()s.
> 
> It is cmalloc, not calloc:
> 
>   filedata->orig_section_headers = (Elf_Internal_Shdr **)
>     cmalloc (num, sizeof (Elf_Internal_Shdr *));
> 
> cmalloc doesn't clear memory.  memset is needed.

Seeing that cmalloc() uses xmalloc() (not malloc()), why don't you then
simply use xcmalloc2()?

Further, again seeing that cmalloc() uses xmalloc() - the NULL checks
that you add are then solely to cover the multiplication overflow case.
That doesn't match the use of malloc() elsewhere in the patch.

Taken together, why is it that calloc() isn't used here in the first
place (to match the use of malloc())? There's no truncation risk on the
first argument passed, and surely a sane implementation of calloc() has
to check for overflow of the multiplication when determining overall
size.

Jan
  
H.J. Lu July 9, 2026, 7:11 a.m. UTC | #4
On Thu, Jul 9, 2026 at 2:55 PM Jan Beulich <jbeulich@suse.com> wrote:
>
> On 09.07.2026 08:32, H.J. Lu wrote:
> > On Thu, Jul 9, 2026 at 2:06 PM Jan Beulich <jbeulich@suse.com> wrote:
> >>
> >> On 04.07.2026 13:06, H.J. Lu wrote:
> >>> On Sat, Jul 4, 2026 at 11:01 AM H.J. Lu <hjl.tools@gmail.com> wrote:
> >>>> On Sat, Jul 4, 2026 at 8:39 AM H.J. Lu <hjl.tools@gmail.com> wrote:
> >>>>> On Sat, Jul 4, 2026 at 7:49 AM Alan Modra <amodra@gmail.com> wrote:
> >>>>>>
> >>>>>> On Fri, Jul 03, 2026 at 10:30:14PM +0800, H.J. Lu wrote:
> >>>>>>> validate_section_info clears the garbage values in the section header
> >>>>>>> to avoid crash later.  Save and dump the original section header values
> >>>>>>> to make the garbage values in the section header visible when dumping
> >>>>>>> section headers.
> >>>>>>
> >>>>>> I think this would be better done the other way around.  ie. have a
> >>>>>> Elf_Internal_Shdr **sane_section_headers that is initialised to point
> >>>>>> at entries in section_headers, with sane_section_headers[i] allocated
> >>>>>> as necessary when needing to correct a bogus header.
> >>>>>>
> >>>>>
> >>>>> I have thought about something similar and decided against it
> >>>>> since filedata->section_headers is used in many places.   However,
> >>>>> I can change filedata->orig_section_headers to on demand.
> >>
> >> I find this a plausible argument, so ...
> >>
> >>>> Changes in v2:
> >>>>
> >>>> 1.  filedata->orig_section_headers is changed to on demand.
> >>>
> >>> Changes in v3:
> >>>
> >>> 1.  Clear filedata->orig_section_headers first.
> >>
> >> ... the patch is okay unless you get an objection from Alan within another
> >
> > Will do.
> >
> >> day or two. Just one thing though: calloc() + memset(, 0,) is redundant.
> >> Please drop those memset()s.
> >
> > It is cmalloc, not calloc:
> >
> >   filedata->orig_section_headers = (Elf_Internal_Shdr **)
> >     cmalloc (num, sizeof (Elf_Internal_Shdr *));
> >
> > cmalloc doesn't clear memory.  memset is needed.
>
> Seeing that cmalloc() uses xmalloc() (not malloc()), why don't you then
> simply use xcmalloc2()?

Did you mean xcalloc2?

> Further, again seeing that cmalloc() uses xmalloc() - the NULL checks
> that you add are then solely to cover the multiplication overflow case.
> That doesn't match the use of malloc() elsewhere in the patch.
>
> Taken together, why is it that calloc() isn't used here in the first
> place (to match the use of malloc())? There's no truncation risk on the
> first argument passed, and surely a sane implementation of calloc() has
> to check for overflow of the multiplication when determining overall

I guess we don't know what calloc will be used.

> size.
>
> Jan

Here is the v4 patch to use xcalloc2.
  
Jan Beulich July 9, 2026, 9:31 a.m. UTC | #5
On 09.07.2026 09:11, H.J. Lu wrote:
> On Thu, Jul 9, 2026 at 2:55 PM Jan Beulich <jbeulich@suse.com> wrote:
>>
>> On 09.07.2026 08:32, H.J. Lu wrote:
>>> On Thu, Jul 9, 2026 at 2:06 PM Jan Beulich <jbeulich@suse.com> wrote:
>>>>
>>>> On 04.07.2026 13:06, H.J. Lu wrote:
>>>>> On Sat, Jul 4, 2026 at 11:01 AM H.J. Lu <hjl.tools@gmail.com> wrote:
>>>>>> On Sat, Jul 4, 2026 at 8:39 AM H.J. Lu <hjl.tools@gmail.com> wrote:
>>>>>>> On Sat, Jul 4, 2026 at 7:49 AM Alan Modra <amodra@gmail.com> wrote:
>>>>>>>>
>>>>>>>> On Fri, Jul 03, 2026 at 10:30:14PM +0800, H.J. Lu wrote:
>>>>>>>>> validate_section_info clears the garbage values in the section header
>>>>>>>>> to avoid crash later.  Save and dump the original section header values
>>>>>>>>> to make the garbage values in the section header visible when dumping
>>>>>>>>> section headers.
>>>>>>>>
>>>>>>>> I think this would be better done the other way around.  ie. have a
>>>>>>>> Elf_Internal_Shdr **sane_section_headers that is initialised to point
>>>>>>>> at entries in section_headers, with sane_section_headers[i] allocated
>>>>>>>> as necessary when needing to correct a bogus header.
>>>>>>>>
>>>>>>>
>>>>>>> I have thought about something similar and decided against it
>>>>>>> since filedata->section_headers is used in many places.   However,
>>>>>>> I can change filedata->orig_section_headers to on demand.
>>>>
>>>> I find this a plausible argument, so ...
>>>>
>>>>>> Changes in v2:
>>>>>>
>>>>>> 1.  filedata->orig_section_headers is changed to on demand.
>>>>>
>>>>> Changes in v3:
>>>>>
>>>>> 1.  Clear filedata->orig_section_headers first.
>>>>
>>>> ... the patch is okay unless you get an objection from Alan within another
>>>
>>> Will do.
>>>
>>>> day or two. Just one thing though: calloc() + memset(, 0,) is redundant.
>>>> Please drop those memset()s.
>>>
>>> It is cmalloc, not calloc:
>>>
>>>   filedata->orig_section_headers = (Elf_Internal_Shdr **)
>>>     cmalloc (num, sizeof (Elf_Internal_Shdr *));
>>>
>>> cmalloc doesn't clear memory.  memset is needed.
>>
>> Seeing that cmalloc() uses xmalloc() (not malloc()), why don't you then
>> simply use xcmalloc2()?
> 
> Did you mean xcalloc2?

Oh, sorry - yes, I did.

>> Further, again seeing that cmalloc() uses xmalloc() - the NULL checks
>> that you add are then solely to cover the multiplication overflow case.
>> That doesn't match the use of malloc() elsewhere in the patch.
>>
>> Taken together, why is it that calloc() isn't used here in the first
>> place (to match the use of malloc())? There's no truncation risk on the
>> first argument passed, and surely a sane implementation of calloc() has
>> to check for overflow of the multiplication when determining overall
> 
> I guess we don't know what calloc will be used.

Well, is it really on use to work around basic library flaws?

> Here is the v4 patch to use xcalloc2.

LGTM, thanks.

Jan
  

Patch

From 2d16c2a7c0135c4da6c9d061398c7184e82e20fb Mon Sep 17 00:00:00 2001
From: "H.J. Lu" <hjl.tools@gmail.com>
Date: Thu, 2 Jul 2026 10:14:10 +0800
Subject: [PATCH v3] readelf: Save and dump the original section header values

validate_section_info clears the garbage values in the section header
to avoid crash later.  Save and dump the original section header values
to make the garbage values in the section header visible when dumping
section headers.

Note: orig_section_headers, instead of sane_section_headers, is added to
filedata since filedata->section_headers is used in many places.  Replace
filedata->section_headers with filedata->sane_section_headers requires
a much bigger change.

	* readelf.c (filedata): Add orig_section_headers.
	(save_original_section_header_values): New.
	(validate_section_info): Add a pointer to the original section
	header and call save_original_section_header_values to save the
	original section header values before clearing the section header
	fields.
	(get_32bit_section_headers): Allocate the original section header
	buffer.  Pass the original section header pointer to
	validate_section_info.
	(get_64bit_section_headers): Likewise.
	(process_section_headers): Dump the original section header
	values if they exist.
	(process_relocs): Pass a dummy original section pointer to
	validate_section_info.
	(free_filedata): Free filedata->orig_section_headers.
	* testsuite/binutils-all/corrupt-1.elf.bz2: New file.
	* testsuite/binutils-all/corrupt-1.r: Likewise.
	* testsuite/binutils-all/readelf.exp: Run corrupt-1.elf test.

Signed-off-by: H.J. Lu <hjl.tools@gmail.com>
---
 binutils/readelf.c                            | 114 ++++++++++++++++--
 .../testsuite/binutils-all/corrupt-1.elf.bz2  | Bin 0 -> 81 bytes
 binutils/testsuite/binutils-all/corrupt-1.r   |   3 +
 binutils/testsuite/binutils-all/readelf.exp   |  12 ++
 4 files changed, 119 insertions(+), 10 deletions(-)
 create mode 100644 binutils/testsuite/binutils-all/corrupt-1.elf.bz2
 create mode 100644 binutils/testsuite/binutils-all/corrupt-1.r

diff --git a/binutils/readelf.c b/binutils/readelf.c
index 2826e0cf195..b93d91a921d 100644
--- a/binutils/readelf.c
+++ b/binutils/readelf.c
@@ -282,6 +282,7 @@  typedef struct filedata
   uint64_t             archive_file_size;
   /* Everything below this point is cleared out by free_filedata.  */
   Elf_Internal_Shdr *  section_headers;
+  Elf_Internal_Shdr ** orig_section_headers;
   Elf_Internal_Phdr *  program_headers;
   char *               string_table;
   uint64_t             string_table_length;
@@ -7848,11 +7849,42 @@  offset_from_vma (Filedata * filedata, uint64_t vma, uint64_t size)
   return vma;
 }
 
+/* Save the original section header values.  */
+
+static void
+save_original_section_header_values (Elf_Internal_Shdr *internal,
+				     Elf_Internal_Shdr **orig_internal,
+				     const char *dynamic_tag,
+				     unsigned int i)
+{
+  /* Return if the original section header values have been saved.  */
+  if (*orig_internal != NULL)
+    return;
+
+  *orig_internal = (Elf_Internal_Shdr *)
+    malloc (sizeof (Elf_Internal_Shdr));
+  if (*orig_internal == NULL)
+    {
+      if (dynamic_tag)
+	error (_("Out of memory reading dynamic tag %s\n"),
+	       dynamic_tag);
+      else
+	error (_("Out of memory reading %u section headers\n"), i);
+      return;
+    }
+
+  /* Save the original section header values.  */
+  **orig_internal = *internal;
+}
+
+
 /* Valid section info and clear the invalid fields.  */
 
 static void
-validate_section_info (Elf_Internal_Shdr *internal, unsigned int i,
-		       Filedata *filedata, bool dynamic, bool probe)
+validate_section_info (Elf_Internal_Shdr *internal,
+		       Elf_Internal_Shdr **orig_internal,
+		       unsigned int i, Filedata *filedata, bool dynamic,
+		       bool probe)
 {
   const char *dynamic_tag = NULL;
   const char *dynamicsz_tag = NULL;
@@ -7880,6 +7912,10 @@  validate_section_info (Elf_Internal_Shdr *internal, unsigned int i,
 	{
 	  warn (_("Ignore the out of range sh_link value of %u for "
 		  "section %u\n"), internal->sh_link, i);
+	  /* Save the original section header values before garbage
+	     values are cleared.  */
+	  save_original_section_header_values (internal, orig_internal,
+					       NULL, i);
 	  internal->sh_link = 0;
 	}
 
@@ -7888,6 +7924,8 @@  validate_section_info (Elf_Internal_Shdr *internal, unsigned int i,
 	{
 	  warn (_("Ignore the out of range sh_info value of %u for "
 		  "section %u\n"), internal->sh_info, i);
+	  save_original_section_header_values (internal, orig_internal,
+					       NULL, i);
 	  internal->sh_info = 0;
 	}
     }
@@ -7902,6 +7940,8 @@  validate_section_info (Elf_Internal_Shdr *internal, unsigned int i,
 	warn (_("Ignore the out of range sh_entsize value of %"
 		PRIu64 " for section %u\n"),
 	      (uint64_t) internal->sh_entsize, i);
+      save_original_section_header_values (internal, orig_internal,
+					   dynamicent_tag, i);
       internal->sh_entsize = 0;
     }
 
@@ -7916,6 +7956,8 @@  validate_section_info (Elf_Internal_Shdr *internal, unsigned int i,
 	  else
 	    warn (_("Ignore the out of range sh_offset value of %"
 		    PRId64 " for section %u\n"), sh_offset, i);
+	  save_original_section_header_values (internal, orig_internal,
+					       dynamic_tag, i);
 	  internal->sh_offset = 0;
 	}
 
@@ -7930,6 +7972,8 @@  validate_section_info (Elf_Internal_Shdr *internal, unsigned int i,
 		    PRIu64 " for section %u with sh_offset value of %"
 		    PRId64 "\n"), (uint64_t) internal->sh_size, i,
 		  sh_offset);
+	  save_original_section_header_values (internal, orig_internal,
+					       dynamicsz_tag, i);
 	  internal->sh_size = 0;
 	}
     }
@@ -7944,6 +7988,7 @@  get_32bit_section_headers (Filedata * filedata, bool probe)
 {
   Elf32_External_Shdr * shdrs;
   Elf_Internal_Shdr *   internal;
+  Elf_Internal_Shdr **  orig_internal;
   unsigned int          i;
   unsigned int          size = filedata->file_header.e_shentsize;
   unsigned int          num = probe ? 1 : filedata->file_header.e_shnum;
@@ -7983,9 +8028,24 @@  get_32bit_section_headers (Filedata * filedata, bool probe)
       return false;
     }
 
+  filedata->orig_section_headers = (Elf_Internal_Shdr **)
+    cmalloc (num, sizeof (Elf_Internal_Shdr *));
+  if (filedata->orig_section_headers == NULL)
+    {
+      if (!probe)
+	error (_("Out of memory reading %u section headers\n"), num);
+      free (shdrs);
+      free (filedata->section_headers);
+      filedata->section_headers = NULL;
+      return false;
+    }
+  memset (filedata->orig_section_headers, 0,
+	  num * sizeof (Elf_Internal_Shdr *));
+
+  orig_internal = filedata->orig_section_headers;
   for (i = 0, internal = filedata->section_headers;
        i < num;
-       i++, internal++)
+       i++, internal++, orig_internal++)
     {
       internal->sh_name      = BYTE_GET (shdrs[i].sh_name);
       internal->sh_type      = BYTE_GET (shdrs[i].sh_type);
@@ -7997,7 +8057,8 @@  get_32bit_section_headers (Filedata * filedata, bool probe)
       internal->sh_info      = BYTE_GET (shdrs[i].sh_info);
       internal->sh_addralign = BYTE_GET (shdrs[i].sh_addralign);
       internal->sh_entsize   = BYTE_GET (shdrs[i].sh_entsize);
-      validate_section_info (internal, i, filedata, false, probe);
+      validate_section_info (internal, orig_internal, i, filedata,
+			     false, probe);
     }
 
   free (shdrs);
@@ -8011,6 +8072,7 @@  get_64bit_section_headers (Filedata * filedata, bool probe)
 {
   Elf64_External_Shdr *  shdrs;
   Elf_Internal_Shdr *    internal;
+  Elf_Internal_Shdr **   orig_internal;
   unsigned int           i;
   unsigned int           size = filedata->file_header.e_shentsize;
   unsigned int           num = probe ? 1 : filedata->file_header.e_shnum;
@@ -8052,9 +8114,24 @@  get_64bit_section_headers (Filedata * filedata, bool probe)
       return false;
     }
 
+  filedata->orig_section_headers = (Elf_Internal_Shdr **)
+    cmalloc (num, sizeof (Elf_Internal_Shdr *));
+  if (filedata->orig_section_headers == NULL)
+    {
+      if (!probe)
+	error (_("Out of memory reading %u section headers\n"), num);
+      free (shdrs);
+      free (filedata->section_headers);
+      filedata->section_headers = NULL;
+      return false;
+    }
+  memset (filedata->orig_section_headers, 0,
+	  num * sizeof (Elf_Internal_Shdr *));
+
+  orig_internal = filedata->orig_section_headers;
   for (i = 0, internal = filedata->section_headers;
        i < num;
-       i++, internal++)
+       i++, internal++, orig_internal++)
     {
       internal->sh_name      = BYTE_GET (shdrs[i].sh_name);
       internal->sh_type      = BYTE_GET (shdrs[i].sh_type);
@@ -8066,7 +8143,8 @@  get_64bit_section_headers (Filedata * filedata, bool probe)
       internal->sh_info      = BYTE_GET (shdrs[i].sh_info);
       internal->sh_offset    = BYTE_GET (shdrs[i].sh_offset);
       internal->sh_addralign = BYTE_GET (shdrs[i].sh_addralign);
-      validate_section_info (internal, i, filedata, false, probe);
+      validate_section_info (internal, orig_internal, i, filedata,
+			     false, probe);
     }
 
   free (shdrs);
@@ -9016,10 +9094,18 @@  process_section_headers (Filedata * filedata)
   if (do_section_details)
     printf (_("       Flags\n"));
 
-  for (i = 0, section = filedata->section_headers;
+  Elf_Internal_Shdr **orig_section = filedata->orig_section_headers;
+  Elf_Internal_Shdr *sec;
+  for (i = 0, sec = filedata->section_headers;
        i < filedata->file_header.e_shnum;
-       i++, section++)
+       i++, sec++, orig_section++)
     {
+      /* Dump the original section header values if they exist.  */
+      if (*orig_section)
+	section = *orig_section;
+      else
+	section = sec;
+
       /* Run some sanity checks on the section header.  */
 
       /* Check the sh_link field.  */
@@ -10197,16 +10283,18 @@  process_relocs (Filedata * filedata)
 		  uint64_t num_reloc;
 		  uint64_t *relrs = NULL;
 		  Elf_Internal_Shdr section = {};
+		  Elf_Internal_Shdr *orig_section = NULL;
 		  section.sh_offset
 		    = filedata->dynamic_info[DT_RELR];
 		  section.sh_size = rel_size;
 		  section.sh_entsize = rel_entsz;
 		  section.sh_type = SHT_RELR;
-		  validate_section_info (&section, DT_RELR, filedata,
-					 true, false);
+		  validate_section_info (&section, &orig_section, DT_RELR,
+					 filedata, true, false);
 		  num_reloc = count_relr_relocations (filedata,
 						      &section,
 						      &relrs);
+		  free (orig_section);
 		  free (relrs);
 		  if (num_reloc == 0)
 		    continue;
@@ -24847,6 +24935,12 @@  free_filedata (Filedata *filedata)
   free (filedata->program_interpreter);
   free (filedata->program_headers);
   free (filedata->section_headers);
+  if (filedata->orig_section_headers)
+    {
+      for (unsigned int i = 0; i < filedata->file_header.e_shnum; i++)
+	free (filedata->orig_section_headers[i]);
+      free (filedata->orig_section_headers);
+    }
   free (filedata->string_table);
   free (filedata->dump.dump_sects);
   free (filedata->dynamic_strings);
diff --git a/binutils/testsuite/binutils-all/corrupt-1.elf.bz2 b/binutils/testsuite/binutils-all/corrupt-1.elf.bz2
new file mode 100644
index 0000000000000000000000000000000000000000..e9c9f44343a4aadd90457df5e49923830881d194
GIT binary patch
literal 81
zcmV-X0IvT+T4*^jL0KkKS<!e4e*gd?Wun|A06@b80RR91fItKQfCwM}Ata_F&>`e0
nlM^7w$`z$$Gahz<VUjiOf0D@DL^^qw1A^{IrwS4pF9C1h02mvG

literal 0
HcmV?d00001

diff --git a/binutils/testsuite/binutils-all/corrupt-1.r b/binutils/testsuite/binutils-all/corrupt-1.r
new file mode 100644
index 00000000000..ae6438703e2
--- /dev/null
+++ b/binutils/testsuite/binutils-all/corrupt-1.r
@@ -0,0 +1,3 @@ 
+#...
+  \[10\] <no-strings>      REL             0000000000000000 000000 6666666666666600 00      0   0  0
+#pass
diff --git a/binutils/testsuite/binutils-all/readelf.exp b/binutils/testsuite/binutils-all/readelf.exp
index 0f3d75090b2..6c5e63fb9e8 100644
--- a/binutils/testsuite/binutils-all/readelf.exp
+++ b/binutils/testsuite/binutils-all/readelf.exp
@@ -612,6 +612,18 @@  if ![is_remote host] {
     } else {
 	readelf_test {-wi} $tempfile pr26160.r
     }
+
+    set test $srcdir/$subdir/corrupt-1.elf.bz2
+    # We need to strip the ".bz2", but can leave the dirname.
+    set t $subdir/[file tail $test]
+    set testname [file rootname $t]
+    verbose $testname
+    set tempfile tmpdir/corrupt-1.elf
+    if {[catch "system \"bzip2 -dc $test > $tempfile\""] != 0} {
+	untested "bzip2 -dc ($testname)"
+    } else {
+	readelf_test {-SW} $tempfile corrupt-1.r
+    }
 }
 
 # Check dwarf-5 support for DW_OP_addrx.
-- 
2.55.0