[binutils-gdb] readelf: Save and dump the original section header values

"H.J. Lu via Binutils-cvs" <[email protected]> Thu, 9 Jul 2026 10:31:52 +0000 (GMT)
Newsgroups gmane.comp.gnu.binutils.cvs
Message-ID <[email protected]>
https://sourceware.org/git/gitweb.cgi?p=binutils-gdb.git;h=c447183ab0f32d0c2378e72cfd64f5e048b62cb4

commit c447183ab0f32d0c2378e72cfd64f5e048b62cb4
Author: H.J. Lu <[email protected]>
Date:   Thu Jul 2 10:14:10 2026 +0800

    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 <[email protected]>

Diff:
---
 binutils/readelf.c                                |  92 +++++++++++++++++++---
 binutils/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, 97 insertions(+), 10 deletions(-)

diff --git a/binutils/readelf.c b/binutils/readelf.c
index 2826e0cf195..0938fe64b22 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,13 @@ get_32bit_section_headers (Filedata * filedata, bool probe)
       return false;
     }
 
+  filedata->orig_section_headers = (Elf_Internal_Shdr **)
+    xcalloc2 (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 +8046,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 +8061,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 +8103,13 @@ get_64bit_section_headers (Filedata * filedata, bool probe)
       return false;
     }
 
+  filedata->orig_section_headers = (Elf_Internal_Shdr **)
+    xcalloc2 (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 +8121,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 +9072,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 +10261,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 +24913,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 00000000000..e9c9f44343a
Binary files /dev/null and b/binutils/testsuite/binutils-all/corrupt-1.elf.bz2 differ
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.