]> git.ipfire.org Git - thirdparty/elfutils.git/commitdiff
Support note NT_FILE for locating files. jankratochvil/NT_FILE
authorJan Kratochvil <jan.kratochvil@redhat.com>
Thu, 18 Sep 2014 16:27:03 +0000 (18:27 +0200)
committerJan Kratochvil <jan.kratochvil@redhat.com>
Thu, 18 Sep 2014 16:27:03 +0000 (18:27 +0200)
Martin Milata:
------------------------------------------------------------------------------
RFE: dwfl_core_file_report: use NT_FILE core note if the link_map chain is broken
https://bugzilla.redhat.com/show_bug.cgi?id=1129777

The dwfl_core_file_report function follows dynamic linker's link_map chain in
order to determine the shared libraries used by the executable. As this data
structure is located in writable memory it can be overwritten by garbage, which
is sometimes the case.
https://github.com/abrt/satyr/issues/127#issuecomment-46957546

Since version 3.7 (commit 2aa362c49), Linux kernel adds NT_FILE note to core
files which contains the files mapped by the process, including shared
libraries.
------------------------------------------------------------------------------

dwfl_core_file_report now tries to fall back on NT_FILE if the link_map chain
is broken.

elfutils would already find the appropriate binary file from
/usr/lib/debug/.build-id/ symbolic links.  But those symbolic links do not have
to be present on the system while NT_FILE still points to the correct binaries.

Filenames from the note NT_FILE are used only if link_map filenames failed to
locate matching binaries.

tests/test-core.core.bz2 had to have its NT_FILE disabled as run-unstrip-n.sh
otherwise FAILs:
FAIL: 0x7f67f2aaf000+0x202000 - . - /home/jkratoch/redhat/elfutils-libregr/test-core-lib.so
PASS: 0x7f67f2aaf000+0x202000 - . - test-core-lib.so
As test-core-lib.so is found in link_map but it is not present on the disk
elfutils now chooses the more reliable filename from NT_FILE (although that
filename is also not found on the disk).  Updating the expected text would be
also sufficient.

libdwfl/
2014-09-18  Jan Kratochvil  <jan.kratochvil@redhat.com>

Support NT_FILE for locating files.
* core-file.c (dwfl_core_file_report): New variables note_file and
note_file_size, set them and pass them to dwfl_segment_report_module.
* dwfl_segment_report_module.c: Include common.h and fcntl.h.
(buf_has_data, buf_read_ulong, handle_file_note): New functions.
(invalid_elf): New function from code of dwfl_segment_report_module.
(dwfl_segment_report_module): Add parameters note_file and
note_file_size.  New variables elf and fd, clean them up in finish.
Move some code to invalid_elf.  Call handle_file_note, if it found
a name verify the file by invalid_elf.  Protect elf and fd against
cleanup by finish if we found the file for new Dwfl_Module.
* libdwflP.h (dwfl_segment_report_module): Add parameters note_file and
note_file_size.

tests/
2014-09-18  Jan Kratochvil  <jan.kratochvil@redhat.com>

Support NT_FILE for locating files.
* Makefile.am (TESTS): Add run-linkmap-cut.sh.
(EXTRA_DIST): Add run-linkmap-cut.sh, linkmap-cut-lib.so.bz2,
linkmap-cut.bz2 and linkmap-cut.core.bz2 .
* linkmap-cut-lib.so.bz2: New file.
* linkmap-cut.bz2: New file.
* linkmap-cut.core.bz2: New file.
* run-linkmap-cut.sh: New file.
* test-core.core.bz2: Disable its NT_FILE note.

Signed-off-by: Jan Kratochvil <jan.kratochvil@redhat.com>
libdwfl/ChangeLog
libdwfl/core-file.c
libdwfl/dwfl_segment_report_module.c
libdwfl/libdwflP.h
tests/ChangeLog
tests/Makefile.am
tests/linkmap-cut-lib.so.bz2 [new file with mode: 0644]
tests/linkmap-cut.bz2 [new file with mode: 0644]
tests/linkmap-cut.core.bz2 [new file with mode: 0644]
tests/run-linkmap-cut.sh [new file with mode: 0755]
tests/test-core.core.bz2

index bfbc1f77fa7579364e0443f59e17219c648a95cf..99963f8de76888b4c3defcbc31a40f2b7fd728cf 100644 (file)
@@ -1,3 +1,19 @@
+2014-09-18  Jan Kratochvil  <jan.kratochvil@redhat.com>
+
+       Support NT_FILE for locating files.
+       * core-file.c (dwfl_core_file_report): New variables note_file and
+       note_file_size, set them and pass them to dwfl_segment_report_module.
+       * dwfl_segment_report_module.c: Include common.h and fcntl.h.
+       (buf_has_data, buf_read_ulong, handle_file_note): New functions.
+       (invalid_elf): New function from code of dwfl_segment_report_module.
+       (dwfl_segment_report_module): Add parameters note_file and
+       note_file_size.  New variables elf and fd, clean them up in finish.
+       Move some code to invalid_elf.  Call handle_file_note, if it found
+       a name verify the file by invalid_elf.  Protect elf and fd against
+       cleanup by finish if we found the file for new Dwfl_Module.
+       * libdwflP.h (dwfl_segment_report_module): Add parameters note_file and
+       note_file_size.
+
 2014-09-18  Jan Kratochvil  <jan.kratochvil@redhat.com>
 
        * dwfl_build_id_find_elf.c (dwfl_build_id_find_elf): Use IS_EXECUTABLE.
index 4ce63c4e64c837bbcf2023aec6819801d31d540a..50031aed62162099cc470e3fd2527efd4026db54 100644 (file)
@@ -451,7 +451,9 @@ dwfl_core_file_report (Dwfl *dwfl, Elf *elf, const char *executable)
   /* Next, we should follow the chain from DT_DEBUG.  */
 
   const void *auxv = NULL;
+  const void *note_file = NULL;
   size_t auxv_size = 0;
+  size_t note_file_size = 0;
   if (likely (notes_phdr.p_type == PT_NOTE))
     {
       /* PT_NOTE -> NT_AUXV -> AT_PHDR -> PT_DYNAMIC -> DT_DEBUG */
@@ -468,13 +470,19 @@ dwfl_core_file_report (Dwfl *dwfl, Elf *elf, const char *executable)
          size_t desc_pos;
          while ((pos = gelf_getnote (notes, pos, &nhdr,
                                      &name_pos, &desc_pos)) > 0)
-           if (nhdr.n_type == NT_AUXV
-               && nhdr.n_namesz == sizeof "CORE"
+           if (nhdr.n_namesz == sizeof "CORE"
                && !memcmp (notes->d_buf + name_pos, "CORE", sizeof "CORE"))
              {
-               auxv = notes->d_buf + desc_pos;
-               auxv_size = nhdr.n_descsz;
-               break;
+               if (nhdr.n_type == NT_AUXV)
+                 {
+                   auxv = notes->d_buf + desc_pos;
+                   auxv_size = nhdr.n_descsz;
+                 }
+               if (nhdr.n_type == NT_FILE)
+                 {
+                   note_file = notes->d_buf + desc_pos;
+                   note_file_size = nhdr.n_descsz;
+                 }
              }
        }
     }
@@ -498,6 +506,7 @@ dwfl_core_file_report (Dwfl *dwfl, Elf *elf, const char *executable)
       int seg = dwfl_segment_report_module (dwfl, ndx, NULL,
                                            &dwfl_elf_phdr_memory_callback, elf,
                                            core_file_read_eagerly, elf,
+                                           note_file, note_file_size,
                                            &r_debug_info);
       if (unlikely (seg < 0))
        {
index 3393b08733347c0c25a1da225edee6c0e1fc09c0..3822e32353ec49646d2687f17efe3988cd6a807f 100644 (file)
@@ -30,6 +30,7 @@
 #include "../libelf/libelfP.h" /* For NOTE_ALIGN.  */
 #undef _
 #include "libdwflP.h"
+#include "common.h"
 
 #include <elf.h>
 #include <gelf.h>
@@ -38,6 +39,7 @@
 #include <alloca.h>
 #include <endian.h>
 #include <unistd.h>
+#include <fcntl.h>
 
 
 /* A good size for the initial read from memory, if it's not too costly.
@@ -79,12 +81,161 @@ addr_segndx (Dwfl *dwfl, size_t segment, GElf_Addr addr, bool next)
   return ndx;
 }
 
+/* Return whether there is SZ bytes available at PTR till END.
+   Function comes from src/readelf.c .  */
+
+static bool
+buf_has_data (const void *ptr, const void *end, size_t sz)
+{
+  return ptr < end && (size_t) (end - ptr) >= sz;
+}
+
+/* Read SZ bytes into *RETP from *PTRP (limited by END) in format E32.
+   Function comes from src/readelf.c .  */
+
+static bool
+buf_read_ulong (const Elf32_Ehdr *e32, size_t sz,
+               const void **ptrp, const void *end, uint64_t *retp)
+{
+  if (! buf_has_data (*ptrp, end, sz))
+    return false;
+
+  union
+  {
+    uint64_t u64;
+    uint32_t u32;
+  } u;
+
+  memcpy (&u, *ptrp, sz);
+  (*ptrp) += sz;
+
+  if (retp == NULL)
+    return true;
+
+  if (MY_ELFDATA != e32->e_ident[EI_DATA])
+    {
+      if (sz == 4)
+       CONVERT (u.u32);
+      else
+       CONVERT (u.u64);
+    }
+  if (sz == 4)
+    *retp = u.u32;
+  else
+    *retp = u.u64;
+  return true;
+}
+
+/* Try to find matching entry for module from address MODULE_START to
+   MODULE_END in NT_FILE note located at NOTE_FILE of NOTE_FILE_SIZE
+   bytes in format E32.  */
+
+static const char *
+handle_file_note (GElf_Addr module_start, GElf_Addr module_end,
+                 const Elf32_Ehdr *e32,
+                 const void *note_file, size_t note_file_size)
+{
+  if (note_file == NULL)
+    return NULL;
+
+  size_t sz;
+  switch (e32->e_ident[EI_CLASS])
+    {
+    case ELFCLASS32:
+      sz = 4;
+      break;
+    case ELFCLASS64:
+      sz = 8;
+      break;
+    default:
+      return NULL;
+    }
+
+  const void *ptr = note_file;
+  const void *end = note_file + note_file_size;
+  uint64_t count;
+  if (! buf_read_ulong (e32, sz, &ptr, end, &count))
+    return NULL;
+  if (! buf_read_ulong (e32, sz, &ptr, end, NULL)) // page_size
+    return NULL;
+
+  /* Where file names are stored.  */
+  const char *fptr = ptr + 3 * count * sz;
+
+  ssize_t firstix = -1;
+  ssize_t lastix = -1;
+  for (size_t mix = 0; mix < count; mix++)
+    {
+      uint64_t mstart, mend, moffset;
+      if (! buf_read_ulong (e32, sz, &ptr, fptr, &mstart)
+         || ! buf_read_ulong (e32, sz, &ptr, fptr, &mend)
+         || ! buf_read_ulong (e32, sz, &ptr, fptr, &moffset))
+       return NULL;
+      if (mstart == module_start && moffset == 0)
+       firstix = lastix = mix;
+      if (firstix != -1 && mstart < module_end)
+       lastix = mix;
+      if (mend >= module_end)
+       break;
+    }
+  if (firstix == -1)
+    return NULL;
+
+  const char *retval = NULL;
+  for (ssize_t mix = 0; mix <= lastix; mix++)
+    {
+      const char *fnext = memchr (fptr, 0, (const char *) end - fptr);
+      if (fnext == NULL)
+       return NULL;
+      if (mix == firstix)
+       retval = fptr;
+      if (firstix < mix && mix <= lastix && strcmp (fptr, retval) != 0)
+       return NULL;
+      fptr = fnext + 1;
+    }
+  return retval;
+}
+
+/* Return true iff we are certain ELF cannot match BUILD_ID of
+   BUILD_ID_LEN bytes.  Pass DISK_FILE_HAS_BUILD_ID as false if it is
+   certain ELF does not contain build-id (it is only a performance hit
+   to pass it always as true).  */
+
+static bool
+invalid_elf (Elf *elf, bool disk_file_has_build_id,
+            const void *build_id, size_t build_id_len)
+{
+  if (! disk_file_has_build_id && build_id_len > 0)
+    {
+      /* Module found in segments with build-id is more reliable
+        than a module found via DT_DEBUG on disk without any
+        build-id.   */
+      return true;
+    }
+  if (disk_file_has_build_id && build_id_len > 0)
+    {
+      const void *elf_build_id;
+      ssize_t elf_build_id_len;
+
+      /* If there is a build id in the elf file, check it.  */
+      elf_build_id_len = INTUSE(dwelf_elf_gnu_build_id) (elf, &elf_build_id);
+      if (elf_build_id_len > 0)
+       {
+         if (build_id_len != (size_t) elf_build_id_len
+             || memcmp (build_id, elf_build_id, build_id_len) != 0)
+           return true;
+       }
+    }
+  return false;
+}
+
 int
 dwfl_segment_report_module (Dwfl *dwfl, int ndx, const char *name,
                            Dwfl_Memory_Callback *memory_callback,
                            void *memory_callback_arg,
                            Dwfl_Module_Callback *read_eagerly,
                            void *read_eagerly_arg,
+                           const void *note_file, size_t note_file_size,
                            const struct r_debug_info *r_debug_info)
 {
   size_t segment = ndx;
@@ -121,10 +272,16 @@ dwfl_segment_report_module (Dwfl *dwfl, int ndx, const char *name,
 
   void *buffer = NULL;
   size_t buffer_available = INITIAL_READ;
+  Elf *elf = NULL;
+  int fd = -1;
 
   inline int finish (void)
   {
     release_buffer (&buffer, &buffer_available);
+    if (elf != NULL)
+      elf_end (elf);
+    if (fd != -1)
+      close (fd);
     return ndx;
   }
 
@@ -481,32 +638,9 @@ dwfl_segment_report_module (Dwfl *dwfl, int ndx, const char *name,
        if ((module_end > module->start && module_start < module->end)
            || dyn_vaddr == module->l_ld)
          {
-           bool close_elf = false;
-           if (! module->disk_file_has_build_id && build_id_len > 0)
-             {
-               /* Module found in segments with build-id is more reliable
-                  than a module found via DT_DEBUG on disk without any
-                  build-id.   */
-               if (module->elf != NULL)
-                 close_elf = true;
-             }
            if (module->elf != NULL
-               && module->disk_file_has_build_id && build_id_len > 0)
-             {
-               const void *elf_build_id;
-               ssize_t elf_build_id_len;
-
-               /* If there is a build id in the elf file, check it.  */
-               elf_build_id_len = INTUSE(dwelf_elf_gnu_build_id) (module->elf,
-                                                               &elf_build_id);
-               if (elf_build_id_len > 0)
-                 {
-                   if (build_id_len != (size_t) elf_build_id_len
-                       || memcmp (build_id, elf_build_id, build_id_len) != 0)
-                     close_elf = true;
-                 }
-             }
-           if (close_elf)
+               && invalid_elf (module->elf, module->disk_file_has_build_id,
+                               build_id, build_id_len))
              {
                elf_end (module->elf);
                close (module->fd);
@@ -527,6 +661,29 @@ dwfl_segment_report_module (Dwfl *dwfl, int ndx, const char *name,
        }
     }
 
+  const char *file_note_name = handle_file_note (module_start, module_end,
+                                                &ehdr.e32,
+                                                note_file, note_file_size);
+  if (file_note_name)
+    {
+      name = file_note_name;
+      name_is_final = true;
+      bool invalid = false;
+      fd = open64 (name, O_RDONLY);
+      if (fd >= 0)
+       {
+         Dwfl_Error error = __libdw_open_file (&fd, &elf, true, false);
+         if (error == DWFL_E_NOERROR)
+           invalid = invalid_elf (elf, true /* disk_file_has_build_id */,
+                                  build_id, build_id_len);
+       }
+      if (invalid)
+       {
+         free (build_id);
+         return finish ();
+       }
+    }
+
   /* Our return value now says to skip the segments contained
      within the module.  */
   ndx = addr_segndx (dwfl, segment, module_end, true);
@@ -683,10 +840,10 @@ dwfl_segment_report_module (Dwfl *dwfl, int ndx, const char *name,
                               : dynstr_vaddr + dynstrsz - start);
   const GElf_Off whole = MAX (file_trimmed_end, shdrs_end);
 
-  Elf *elf = NULL;
-  if ((*read_eagerly) (MODCB_ARGS (mod), &buffer, &buffer_available,
-                      cost, worthwhile, whole, contiguous,
-                      read_eagerly_arg, &elf)
+  if (elf == NULL
+      && (*read_eagerly) (MODCB_ARGS (mod), &buffer, &buffer_available,
+                         cost, worthwhile, whole, contiguous,
+                         read_eagerly_arg, &elf)
       && elf == NULL)
     {
       /* The caller wants to read the whole file in right now, but hasn't
@@ -748,6 +905,8 @@ dwfl_segment_report_module (Dwfl *dwfl, int ndx, const char *name,
     {
       /* Install the file in the module.  */
       mod->main.elf = elf;
+      elf = NULL;
+      fd = -1;
       mod->main.vaddr = module_start - bias;
       mod->main.address_sync = module_address_sync;
       mod->main_bias = bias;
index 735b920a0d14ce9d66df0fedbae400a32b4dba4c..12ee116e6e65350a0c2690cc5ad33e4f86f688cc 100644 (file)
@@ -660,6 +660,8 @@ extern int dwfl_segment_report_module (Dwfl *dwfl, int ndx, const char *name,
                                       void *memory_callback_arg,
                                       Dwfl_Module_Callback *read_eagerly,
                                       void *read_eagerly_arg,
+                                      const void *note_file,
+                                      size_t note_file_size,
                                       const struct r_debug_info *r_debug_info);
 
 /* Report a module for entry in the dynamic linker's struct link_map list.
index 3f1272850fb8c1765b7aad28ef52bbfd2bc00c8a..71163ee3a411c6c3c4716dc2abac5df7c7d841b8 100644 (file)
@@ -1,3 +1,15 @@
+2014-09-18  Jan Kratochvil  <jan.kratochvil@redhat.com>
+
+       Support NT_FILE for locating files.
+       * Makefile.am (TESTS): Add run-linkmap-cut.sh.
+       (EXTRA_DIST): Add run-linkmap-cut.sh, linkmap-cut-lib.so.bz2,
+       linkmap-cut.bz2 and linkmap-cut.core.bz2 .
+       * linkmap-cut-lib.so.bz2: New file.
+       * linkmap-cut.bz2: New file.
+       * linkmap-cut.core.bz2: New file.
+       * run-linkmap-cut.sh: New file.
+       * test-core.core.bz2: Disable its NT_FILE note.
+
 2014-08-28  Jan Kratochvil  <jan.kratochvil@redhat.com>
 
        * Makefile.am (check_PROGRAMS): Add deleted and deleted-lib.so.
index be3494daba97bcb841acf3f840b7d7ac0269a30d..ebf4f71f4fe9aec051d7038477ae1c74ba073e83 100644 (file)
@@ -109,7 +109,8 @@ TESTS = run-arextract.sh run-arsymtest.sh newfile test-nlist \
        run-backtrace-core-s390x.sh run-backtrace-core-s390.sh \
        run-backtrace-core-aarch64.sh \
        run-backtrace-demangle.sh run-stack-d-test.sh run-stack-i-test.sh \
-       run-readelf-dwz-multi.sh run-allfcts-multi.sh run-deleted.sh
+       run-readelf-dwz-multi.sh run-allfcts-multi.sh run-deleted.sh \
+       run-linkmap-cut.sh
 
 if !BIARCH
 export ELFUTILS_DISABLE_BIARCH = 1
@@ -272,7 +273,8 @@ EXTRA_DIST = run-arextract.sh run-arsymtest.sh \
             run-stack-d-test.sh run-stack-i-test.sh \
             testfiledwarfinlines.bz2 testfiledwarfinlines.core.bz2 \
             run-readelf-zdebug.sh testfile-debug.bz2 testfile-zdebug.bz2 \
-            run-deleted.sh
+            run-deleted.sh run-linkmap-cut.sh linkmap-cut-lib.so.bz2 \
+            linkmap-cut.bz2 linkmap-cut.core.bz2 
 
 if USE_VALGRIND
 valgrind_cmd='valgrind -q --error-exitcode=1 --run-libc-freeres=no'
diff --git a/tests/linkmap-cut-lib.so.bz2 b/tests/linkmap-cut-lib.so.bz2
new file mode 100644 (file)
index 0000000..a1bda5c
Binary files /dev/null and b/tests/linkmap-cut-lib.so.bz2 differ
diff --git a/tests/linkmap-cut.bz2 b/tests/linkmap-cut.bz2
new file mode 100644 (file)
index 0000000..f2ccd7c
Binary files /dev/null and b/tests/linkmap-cut.bz2 differ
diff --git a/tests/linkmap-cut.core.bz2 b/tests/linkmap-cut.core.bz2
new file mode 100644 (file)
index 0000000..b55b2f2
Binary files /dev/null and b/tests/linkmap-cut.core.bz2 differ
diff --git a/tests/run-linkmap-cut.sh b/tests/run-linkmap-cut.sh
new file mode 100755 (executable)
index 0000000..7b1e797
--- /dev/null
@@ -0,0 +1,27 @@
+#! /bin/bash
+# Copyright (C) 2014 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/>.
+
+. $srcdir/test-subr.sh
+
+testfiles linkmap-cut-lib.so linkmap-cut linkmap-cut.core
+tempfiles bt
+# It may have non-zero exit code with:
+# .../elfutils/src/stack: dwfl_thread_getframes tid 3130 at 0x3fdf821d64 in /usr/lib64/libc-2.18.so: no matching address range
+testrun ${abs_top_builddir}/src/stack --core=linkmap-cut.core -e linkmap-cut >bt || true
+cat bt
+grep -qw libfunc bt
+grep -qw main bt
index 4d4346b9cf7e2ffb995ca80b1549ee28a39d4d4f..dfaecc75b696e4a8c1debca22ff5cd879486167b 100644 (file)
Binary files a/tests/test-core.core.bz2 and b/tests/test-core.core.bz2 differ