]> git.ipfire.org Git - thirdparty/elfutils.git/commitdiff
elf_getarhdr.c: Return correct header for archive within an archive
authorAaron Merey <amerey@redhat.com>
Fri, 24 Oct 2025 02:25:25 +0000 (22:25 -0400)
committerAaron Merey <amerey@redhat.com>
Fri, 24 Oct 2025 13:51:13 +0000 (09:51 -0400)
If elf_getarhdr is called on a descriptor that refers to an archive
which is itself a member of another archive, it may return the Elf_Arhdr
of the current member (i.e., the member selected by elf_next or elf_rand)
of the inner archive instead of Elf_Arhdr of the inner archive itself.

This also causes a memory leak: elf_end only attempts to free
Elf_Arhdr fields ar_name and ar_rawname for descriptors that are not
ELF_K_AR.

To fix this, replace the state.elf[32|64] field elf_ar_hdr with new
struct Elf field elf_ar_hdr.  This field stores the Elf_Arhdr for all
descriptors of archive members, including those with kind ELF_K_AR.

Also rename the state.ar field elf_ar_hdr to cur_ar_hdr to clarify that
this is the header of an archive's current member.

Signed-off-by: Aaron Merey <amerey@redhat.com>
libdwfl/open.c
libelf/elf_begin.c
libelf/elf_end.c
libelf/elf_getarhdr.c
libelf/elf_next.c
libelf/elf_rand.c
libelf/libelfP.h
tests/.gitignore
tests/Makefile.am
tests/ar-extract-ar.c [new file with mode: 0644]
tests/run-ar.sh

index 03e66dfa5542d6b7b9669a68b8cf10d1c5de483e..e66595fba097669fbd6c378bf20be673597e28fc 100644 (file)
@@ -148,12 +148,12 @@ libdw_open_elf (int *fdp, Elf **elfp, bool close_on_fail, bool archive_ok,
        {
          /* Pure evil.  libelf needs some better interfaces.  */
          elf->kind = ELF_K_AR;
-         elf->state.ar.elf_ar_hdr.ar_name = "libdwfl is faking you out";
-         elf->state.ar.elf_ar_hdr.ar_size = elf->maximum_size - offset;
+         elf->state.ar.cur_ar_hdr.ar_name = "libdwfl is faking you out";
+         elf->state.ar.cur_ar_hdr.ar_size = elf->maximum_size - offset;
          elf->state.ar.offset = offset - sizeof (struct ar_hdr);
          Elf *subelf = elf_begin (-1, elf->cmd, elf);
          elf->kind = ELF_K_NONE;
-         elf->state.ar.elf_ar_hdr.ar_name = NULL;
+         elf->state.ar.cur_ar_hdr.ar_name = NULL;
          if (unlikely (subelf == NULL))
            error = DWFL_E_LIBELF;
          else
index a824893f08379d9bc1b68b44a450abd86bfabe8d..d22f107dd5a0ff1d95f59b4d800b0e28c5db30d6 100644 (file)
@@ -62,7 +62,7 @@ file_read_ar (int fildes, void *map_address, off_t offset, size_t maxsize,
         happen on demand.  */
       elf->state.ar.offset = offset + SARMAG;
 
-      elf->state.ar.elf_ar_hdr.ar_rawname = elf->state.ar.raw_name;
+      elf->state.ar.cur_ar_hdr.ar_rawname = elf->state.ar.raw_name;
     }
 
   return elf;
@@ -821,10 +821,7 @@ copy_arhdr (Elf_Arhdr *dest, Elf *ref)
 {
   Elf_Arhdr *hdr;
 
-  if (ref->kind == ELF_K_AR)
-    hdr = &ref->state.ar.elf_ar_hdr;
-  else
-    hdr = &ref->state.elf.elf_ar_hdr;
+  hdr = &ref->state.ar.cur_ar_hdr;
 
   char *ar_name = hdr->ar_name;
   char *ar_rawname = hdr->ar_rawname;
@@ -909,7 +906,7 @@ __libelf_next_arhdr_wrlock (Elf *elf)
   /* Copy the raw name over to a NUL terminated buffer.  */
   *((char *) mempcpy (elf->state.ar.raw_name, ar_hdr->ar_name, 16)) = '\0';
 
-  elf_ar_hdr = &elf->state.ar.elf_ar_hdr;
+  elf_ar_hdr = &elf->state.ar.cur_ar_hdr;
 
   /* Now convert the `struct ar_hdr' into `Elf_Arhdr'.
      Determine whether this is a special entry.  */
@@ -1102,7 +1099,7 @@ dup_elf (int fildes, Elf_Cmd cmd, Elf *ref)
      member the internal pointer of the archive file descriptor is
      pointing to.  First read the header of the next member if this
      has not happened already.  */
-  if (ref->state.ar.elf_ar_hdr.ar_name == NULL
+  if (ref->state.ar.cur_ar_hdr.ar_name == NULL
       && __libelf_next_arhdr_wrlock (ref) != 0)
     /* Something went wrong.  Maybe there is no member left.  */
     return NULL;
@@ -1114,7 +1111,7 @@ dup_elf (int fildes, Elf_Cmd cmd, Elf *ref)
   size_t max_size = ref->maximum_size;
   size_t offset = (size_t) (ref->state.ar.offset - ref->start_offset);
   size_t hdr_size = sizeof (struct ar_hdr);
-  size_t ar_size = (size_t) ref->state.ar.elf_ar_hdr.ar_size;
+  size_t ar_size = (size_t) ref->state.ar.cur_ar_hdr.ar_size;
   if (max_size < hdr_size || max_size - hdr_size < offset)
     return NULL;
 
@@ -1130,8 +1127,9 @@ dup_elf (int fildes, Elf_Cmd cmd, Elf *ref)
     {
       /* Enlist this new descriptor in the list of children.  */
       result->next = ref->state.ar.children;
-      result->state.elf.elf_ar_hdr = ar_hdr;
       ref->state.ar.children = result;
+
+      result->elf_ar_hdr = ar_hdr;
     }
   else
     {
index 1d3661276df787b6255dd1e3959461bd99492ccd..9df2e165a4f9f0fc6bb9a5dfcb62b5253e778cc6 100644 (file)
@@ -116,14 +116,11 @@ elf_end (Elf *elf)
       rwlock_unlock (parent->lock);
     }
 
-  if (elf->kind != ELF_K_AR)
-    {
-      if (elf->state.elf.elf_ar_hdr.ar_name != NULL)
-       free (elf->state.elf.elf_ar_hdr.ar_name);
+  if (elf->elf_ar_hdr.ar_name != NULL)
+    free (elf->elf_ar_hdr.ar_name);
 
-      if (elf->state.elf.elf_ar_hdr.ar_rawname != NULL)
-       free (elf->state.elf.elf_ar_hdr.ar_rawname);
-    }
+  if (elf->elf_ar_hdr.ar_rawname != NULL)
+    free (elf->elf_ar_hdr.ar_rawname);
 
   /* This was the last activation.  Free all resources.  */
   switch (elf->kind)
index 9211fc2e9ca68582cea57593755b600ed27d8849..dcd5718ed77cf4f09e9912558fbfe7a89bd40b47 100644 (file)
@@ -51,5 +51,5 @@ elf_getarhdr (Elf *elf)
       return NULL;
     }
 
-  return &elf->state.elf.elf_ar_hdr;
+  return &elf->elf_ar_hdr;
 }
index 6edafd2e924d5088d492ae94493af6568d14227c..32d91b517c94edad4003d96a0516e37e8cd00d69 100644 (file)
@@ -56,7 +56,7 @@ elf_next (Elf *elf)
 
   /* Now advance the offset.  */
   parent->state.ar.offset += (sizeof (struct ar_hdr)
-                             + ((parent->state.ar.elf_ar_hdr.ar_size + 1)
+                             + ((parent->state.ar.cur_ar_hdr.ar_size + 1)
                                 & ~1l));
 
   /* Get the next archive header.  */
@@ -64,7 +64,7 @@ elf_next (Elf *elf)
 
   /* If necessary, mark the archive header as unusable.  */
   if (ret == ELF_C_NULL)
-    parent->state.ar.elf_ar_hdr.ar_name = NULL;
+    parent->state.ar.cur_ar_hdr.ar_name = NULL;
 
   rwlock_unlock (parent->lock);
 
index f1850e7bdbbc63bfb6243503f3bedbba4b2df9e2..c08eadc61ef9bf5e46f1560cd279d45c9dc2e2b2 100644 (file)
@@ -53,7 +53,7 @@ elf_rand (Elf *elf, size_t offset)
   if (__libelf_next_arhdr_wrlock (elf) != 0)
     {
       /* Mark the archive header as unusable.  */
-      elf->state.ar.elf_ar_hdr.ar_name = NULL;
+      elf->elf_ar_hdr.ar_name = NULL;
       return 0;
     }
 
index 1b93da88279590d82c126338205477d0dc5c150c..11ef5989774de08954997d123f44fe9fffda7fa4 100644 (file)
@@ -306,6 +306,9 @@ struct Elf
   /* Reference counting for the descriptor.  */
   int ref_count;
 
+  /* Structure returned by 'elf_getarhdr'.  */
+  Elf_Arhdr elf_ar_hdr;
+
   /* Lock to handle multithreaded programs.  */
   rwlock_define (,lock);
 
@@ -323,7 +326,6 @@ struct Elf
                                   read from the file.  */
       search_tree rawchunk_tree;  /* Tree and lock for elf_getdata_rawchunk
                                     results.  */
-      Elf_Arhdr elf_ar_hdr;     /* Structure returned by 'elf_getarhdr'.  */
       unsigned int scnincr;    /* Number of sections allocate the last
                                   time.  */
       int ehdr_flags;          /* Flags (dirty) for ELF header.  */
@@ -344,7 +346,6 @@ struct Elf
                                   read from the file.  */
       search_tree rawchunk_tree;  /* Tree and lock for
                                     elf_getdata_rawchunk results.  */
-      Elf_Arhdr elf_ar_hdr;     /* Structure returned by 'elf_getarhdr'.  */
       unsigned int scnincr;    /* Number of sections allocate the last
                                   time.  */
       int ehdr_flags;          /* Flags (dirty) for ELF header.  */
@@ -371,7 +372,6 @@ struct Elf
                                   read from the file.  */
       search_tree rawchunk_tree;  /* Tree and lock for
                                     elf_getdata_rawchunk results.  */
-      Elf_Arhdr elf_ar_hdr;     /* Structure returned by 'elf_getarhdr'.  */
       unsigned int scnincr;    /* Number of sections allocate the last
                                   time.  */
       int ehdr_flags;          /* Flags (dirty) for ELF header.  */
@@ -397,7 +397,7 @@ struct Elf
       int64_t offset;          /* Offset in file we are currently at.
                                   elf_next() advances this to the next
                                   member of the archive.  */
-      Elf_Arhdr elf_ar_hdr;     /* Copy of current archive member's structure
+      Elf_Arhdr cur_ar_hdr;     /* Copy of current archive member's structure
                                   returned by 'elf_getarhdr'.  */
       struct ar_hdr ar_hdr;    /* Header read from file.  */
       char ar_name[16];                /* NUL terminated ar_name of elf_ar_hdr.  */
index 6ae12adbce114dce835c605b7692f9362d7ab9f1..4681024f35ac4dc33352f7611ac6747c42cafa25 100644 (file)
@@ -11,6 +11,7 @@
 /arextract
 /arls
 /arsymtest
+/ar-extract-ar
 /asm-tst1
 /asm-tst2
 /asm-tst3
index 00ba754d731cdbe4c5a94d32037e4964261b1396..15c32a857fe5dc9dd7792d2f0cbb95b0f6fbf5db 100644 (file)
@@ -68,6 +68,7 @@ check_PROGRAMS = arextract arsymtest newfile saridx scnnames sectiondump \
                  cu-dwp-section-info declfiles test-manyfuncs \
                  eu_search_cfi eu_search_macros \
                  eu_search_lines eu_search_die \
+                 ar-extract-ar \
                  $(asm_TESTS)
 
 asm_TESTS = asm-tst1 asm-tst2 asm-tst3 asm-tst4 asm-tst5 \
@@ -774,6 +775,7 @@ libeu = ../lib/libeu.a
 
 arextract_LDADD = $(libelf)
 arsymtest_LDADD = $(libelf)
+ar_extract_ar_LDADD = $(libelf)
 newfile_LDADD = $(libelf)
 saridx_LDADD = $(libeu) $(libelf)
 scnnames_LDADD = $(libelf)
diff --git a/tests/ar-extract-ar.c b/tests/ar-extract-ar.c
new file mode 100644 (file)
index 0000000..273f62a
--- /dev/null
@@ -0,0 +1,137 @@
+/* Copyright (C) 2025 Red Hat, Inc.
+   This file is part of elfutils.
+
+   This file is free software; you can redistribute it and/or modify
+   it under the terms of the GNU General Public License as published by
+   the Free Software Foundation; either version 3 of the License, or
+   (at your option) any later version.
+
+   elfutils is distributed in the hope that it will be useful, but
+   WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+   GNU General Public License for more details.
+
+   You should have received a copy of the GNU General Public License
+   along with this program.  If not, see <http://www.gnu.org/licenses/>.  */
+
+#ifdef HAVE_CONFIG_H
+# include <config.h>
+#endif
+
+#include <assert.h>
+#include <fcntl.h>
+#include <gelf.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <system.h>
+
+#define printf_indent(n, ...)          \
+  do {                                 \
+    for (int _i = 0; _i < (n); _i++)   \
+      fputs ("    ", stdout);          \
+    printf (__VA_ARGS__);              \
+  } while (0)
+
+static void
+ar_extract (Elf *elf, int indent)
+{
+  Elf *subelf;
+  Elf_Arsym *arsym;
+  size_t narsym;
+  Elf_Cmd cmd = ELF_C_READ;
+
+  assert (elf_kind (elf) == ELF_K_AR);
+
+  arsym = elf_getarsym (elf, &narsym);
+  if (arsym == NULL)
+    {
+      printf_indent (indent, "Cannot get archive index: %s\n", elf_errmsg (-1));
+      exit (1);
+    }
+
+  if (narsym != 4)
+    {
+      printf_indent (indent, "Incorrect number of arsyms\n");
+      exit (1);
+    }
+
+  printf_indent (indent, "== symbol names ==\n");
+
+  /* Print symbol names.  Skip the null entry at the end of arsyms.  */
+  for (size_t i = 0; i < narsym - 1; ++i)
+    printf_indent (indent, "%s\n", arsym[i].as_name);
+  putchar ('\n');
+  printf_indent (indent, "== headers ==\n");
+
+  /* Print header names and recursively call this function on member
+     archives.  */
+  while ((subelf = elf_begin (-1, cmd, elf)) != NULL)
+    {
+      /* The the header for this element.  */
+      Elf_Arhdr *arhdr = elf_getarhdr (subelf);
+
+      if (arhdr == NULL)
+       {
+         printf_indent (indent, "cannot get arhdr: %s\n", elf_errmsg (-1));
+         exit (1);
+       }
+
+      printf_indent (indent, "%s %s\n", arhdr->ar_name, arhdr->ar_rawname);
+
+      if (elf_kind (subelf) == ELF_K_AR)
+       ar_extract (subelf, indent + 1);
+
+      cmd = elf_next (subelf);
+      elf_end (subelf);
+    }
+
+  putchar ('\n');
+}
+
+
+int
+main (int argc, char *argv[])
+{
+  int fd;
+  Elf *elf;
+  Elf_Cmd cmd;
+
+  if (argc < 2)
+    exit (1);
+
+  /* Open the archive.  */
+  fd = open (argv[1], O_RDONLY);
+  if (fd == -1)
+    {
+      printf ("Cannot open input file: %m");
+      exit (1);
+    }
+
+  /* Set the ELF version.  */
+  elf_version (EV_CURRENT);
+
+  /* Create an ELF descriptor.  */
+  cmd = ELF_C_READ;
+  elf = elf_begin (fd, cmd, NULL);
+
+  if (elf == NULL)
+    {
+      printf ("Cannot create ELF descriptor: %s\n", elf_errmsg (-1));
+      exit (1);
+    }
+
+  /* If it is no archive punt.  */
+  if (elf_kind (elf) != ELF_K_AR)
+    {
+      printf ("`%s' is no archive\n", argv[1]);
+      exit (1);
+    }
+
+  ar_extract (elf, 0);
+  elf_end (elf);
+  close (fd);
+
+  return 0;
+}
index 656f1d1ab33b60429771c70485f2c7205dde1bfe..733d810d188cd15d80b0c05c5b7ee9ca6f3f9683 100755 (executable)
@@ -37,4 +37,96 @@ echo Check new ar file is now empty
 testrun_compare ${abs_top_builddir}/src/ar -t test.ar << EOF
 EOF
 
+tempfiles bin* dup* long* inner* outer*
+
+echo Compile files for nested archives.
+
+cat > dup.c <<'EOF'
+int dup_func(void){return 1;}
+EOF
+cat > bin_outer.c <<'EOF'
+int outer_func(void){return 1;}
+EOF
+cat > long_name_long_name_outer.c <<'EOF'
+int long_name_long_name_func(void){return 1;}
+EOF
+cat > bin_inner1.c <<'EOF'
+int inner1_func(void){return 1;}
+EOF
+cat > long_name_long_name_inner1.c <<'EOF'
+int long_name_long_name_func(void){return 1;}
+EOF
+cat > bin_inner2.c <<'EOF'
+int inner2_func(void){return 1;}
+EOF
+cat > long_name_long_name_inner2.c <<'EOF'
+int long_name_long_name_func(void){return 1;}
+EOF
+
+# Compile the source files.
+for src in *.c; do
+  obj=$(echo "$src" | sed 's/\.c$/.o/')
+  gcc -O0 -c "$src" -o "$obj"
+done
+
+echo Create nested archives.
+testrun ${abs_top_builddir}/src/ar -r inner2.ar bin_inner2.o
+testrun ${abs_top_builddir}/src/ar -r inner2.ar dup.o
+testrun ${abs_top_builddir}/src/ar -r inner2.ar long_name_long_name_inner2.o
+
+# inner1.ar contains inner2.ar
+testrun ${abs_top_builddir}/src/ar -r inner1.ar inner2.ar
+testrun ${abs_top_builddir}/src/ar -r inner1.ar bin_inner1.o
+testrun ${abs_top_builddir}/src/ar -r inner1.ar dup.o
+testrun ${abs_top_builddir}/src/ar -r inner1.ar long_name_long_name_inner1.o
+
+# outer.ar contains inner1.ar
+testrun ${abs_top_builddir}/src/ar -r outer.ar inner1.ar
+testrun ${abs_top_builddir}/src/ar -r outer.ar bin_outer.o
+testrun ${abs_top_builddir}/src/ar -r outer.ar dup.o
+testrun ${abs_top_builddir}/src/ar -r outer.ar long_name_long_name_outer.o
+
+echo Check symbol and header names of the nested archives.
+
+testrun_compare ${abs_builddir}/ar-extract-ar outer.ar <<'EOF'
+== symbol names ==
+outer_func
+dup_func
+long_name_long_name_func
+
+== headers ==
+/ /               
+// //              
+inner1.ar inner1.ar/      
+    == symbol names ==
+    inner1_func
+    dup_func
+    long_name_long_name_func
+
+    == headers ==
+    / /               
+    // //              
+    inner2.ar inner2.ar/      
+        == symbol names ==
+        inner2_func
+        dup_func
+        long_name_long_name_func
+
+        == headers ==
+        / /               
+        // //              
+        bin_inner2.o bin_inner2.o/   
+        dup.o dup.o/          
+        long_name_long_name_inner2.o /0              
+
+    bin_inner1.o bin_inner1.o/   
+    dup.o dup.o/          
+    long_name_long_name_inner1.o /0              
+
+bin_outer.o bin_outer.o/    
+dup.o dup.o/          
+long_name_long_name_outer.o /0              
+
+EOF
+
 exit 0