libdwfl: add dwfl_report_offline_memory
authorAleksei Vetrov <vvvvvv@google.com>
Tue, 20 Sep 2022 13:36:37 +0000 (13:36 +0000)
committerMark Wielaard <mark@klomp.org>
Sun, 16 Oct 2022 15:09:42 +0000 (17:09 +0200)
This method allows to read and report ELF from memory instead of opening
a file. That way arbitrary memory can be worked with, e.g. when coming
from a stream without the need to persist.

Another useful application is for fuzzing, because fuzzers might be able
to track accesses to the memory and change the fuzzer input to cover
more edge cases through more targeted input. Hence, add a new function
along with a test case.

Signed-off-by: Aleksei Vetrov <vvvvvv@google.com>
13 files changed:
ChangeLog
NEWS
libdw/ChangeLog
libdw/libdw.map
libdwfl/ChangeLog
libdwfl/libdwfl.h
libdwfl/libdwflP.h
libdwfl/offline.c
libdwfl/open.c
tests/ChangeLog
tests/Makefile.am
tests/dwfl-report-offline-memory.c [new file with mode: 0644]
tests/run-dwfl-report-offline-memory.sh [new file with mode: 0755]

index 1f449d6..6062418 100644 (file)
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,3 +1,7 @@
+2022-09-13  Aleksei Vetrov  <vvvvvv@google.com>
+
+       * NEWS (libdwfl): Add dwfl_report_offline_memory.
+
 2022-09-27  Taketo Kabe  <kabe@sra-tohoku.co.jp>
 
        * debuginfod/debuginfod-client.c: Correctly get timestamp when
diff --git a/NEWS b/NEWS
index 156f78d..6ebd172 100644 (file)
--- a/NEWS
+++ b/NEWS
@@ -6,6 +6,7 @@ debuginfod: Add --disable-source-scan option.
 
 libdwfl: Add new function dwfl_get_debuginfod_client.
          Add new function dwfl_frame_reg.
+         Add new function dwfl_report_offline_memory.
 
 Version 0.187
 
index dd54afc..9a798ff 100644 (file)
@@ -1,3 +1,7 @@
+2022-09-13  Aleksei Vetrov  <vvvvvv@google.com>
+
+       * libdw.map (ELFUTILS_0.188): Add dwfl_report_offline_memory.
+
 2022-08-09  Ulrich Drepper  <drepper@redhat.com>
 
        * dwarf_next_cfi.c (dwarf_next_cfi): Don't skip processing the
index 8f39343..5331ad4 100644 (file)
@@ -371,4 +371,5 @@ ELFUTILS_0.188 {
   global:
     dwfl_get_debuginfod_client;
     dwfl_frame_reg;
+    dwfl_report_offline_memory;
 } ELFUTILS_0.186;
index 30f30b1..942f05d 100644 (file)
@@ -1,3 +1,16 @@
+2022-09-13  Aleksei Vetrov  <vvvvvv@google.com>
+
+       * libdwfl.h (dwfl_report_offline_memory): New function.
+       * libdwflP.h (__libdw_open_elf_memory): New internal function.
+       (dwfl_report_offline_memory): INTDECL.
+       * offline.c (dwfl_report_offline_memory): New function.
+       * open.c (decompress): Return DWFL_E_BADELF when fd is -1.
+       (libdw_open_elf): New argument use_elfp. Adding *elfp to elf if
+       true.
+       (__libdw_open_file): Pass false to libdw_open_elf.
+       (__libdw_open_elf_memory): New function.
+       (__libdw_open_elf): Pass false for libdw_open_elf.
+
 2022-07-28  Di Chen  <dichen@redhat.com>
 
        * libdwfl.h (dwfl_frame_reg): New function.
index edb537c..9114f7f 100644 (file)
@@ -159,6 +159,10 @@ extern Dwfl_Module *dwfl_report_elf (Dwfl *dwfl, const char *name,
 extern Dwfl_Module *dwfl_report_offline (Dwfl *dwfl, const char *name,
                                         const char *file_name, int fd);
 
+/* Similar, but report ELF from memory region.  */
+extern Dwfl_Module *dwfl_report_offline_memory (Dwfl *dwfl, const char *name,
+                                               const char *file_name,
+                                               char *data, size_t size);
 
 /* Finish reporting the current set of modules to the library.
    If REMOVED is not null, it's called for each module that
index a2949e7..011b5de 100644 (file)
@@ -631,6 +631,11 @@ extern Dwfl_Error __libdw_open_file (int *fdp, Elf **elfp,
                                     bool close_on_fail, bool archive_ok)
   internal_function;
 
+/* Same as __libdw_open_file, but opens Elf handle from memory region.  */
+extern Dwfl_Error __libdw_open_elf_memory (char *data, size_t size, Elf **elfp,
+                                          bool archive_ok)
+  internal_function;
+
 /* Same as __libdw_open_file, but never closes the given file
    descriptor and ELF_K_AR is always an acceptable type.  */
 extern Dwfl_Error __libdw_open_elf (int fd, Elf **elfp) internal_function;
@@ -760,6 +765,7 @@ INTDECL (dwfl_report_begin_add)
 INTDECL (dwfl_report_module)
 INTDECL (dwfl_report_segment)
 INTDECL (dwfl_report_offline)
+INTDECL (dwfl_report_offline_memory)
 INTDECL (dwfl_report_end)
 INTDECL (dwfl_build_id_find_elf)
 INTDECL (dwfl_build_id_find_debuginfo)
index 58ba4c3..499663e 100644 (file)
@@ -1,6 +1,7 @@
 /* Recover relocatibility for addresses computed from debug information.
    Copyright (C) 2005-2009, 2012 Red Hat, Inc.
    Copyright (C) 2022 Mark J. Wielaard <mark@klomp.org>
+   Copyright (C) 2022 Google LLC
    This file is part of elfutils.
 
    This file is free software; you can redistribute it and/or modify
@@ -252,6 +253,7 @@ process_archive (Dwfl *dwfl, const char *name, const char *file_name, int fd,
 
 {
   Dwfl_Module *mod = NULL;
+  /* elf_begin supports opening archives even with fd == -1 passed.  */
   Elf *member = elf_begin (fd, ELF_C_READ_MMAP_PRIVATE, archive);
   if (unlikely (member == NULL)) /* Empty archive.  */
     {
@@ -320,3 +322,27 @@ dwfl_report_offline (Dwfl *dwfl, const char *name,
   return __libdwfl_report_offline (dwfl, name, file_name, fd, closefd, NULL);
 }
 INTDEF (dwfl_report_offline)
+
+Dwfl_Module *
+dwfl_report_offline_memory (Dwfl *dwfl, const char *name,
+                           const char *file_name, char *data, size_t size)
+{
+  if (dwfl == NULL)
+    return NULL;
+
+  Elf *elf;
+  Dwfl_Error error = __libdw_open_elf_memory (data, size, &elf, true);
+  if (error != DWFL_E_NOERROR)
+    {
+      __libdwfl_seterrno (error);
+      return NULL;
+    }
+  /* It is ok to pass fd == -1 here, because libelf uses it as a value for
+     "no file opened" and supports working with files without fd, thanks to
+     the existence of the elf_memory function.  */
+  Dwfl_Module *mod = process_file (dwfl, name, file_name, -1, elf, NULL);
+  if (mod == NULL)
+    elf_end (elf);
+  return mod;
+}
+INTDEF (dwfl_report_offline_memory)
index 77bd2bd..da8b59a 100644 (file)
@@ -1,5 +1,6 @@
 /* Decompression support for libdwfl: zlib (gzip), bzlib (bzip2) or lzma (xz).
    Copyright (C) 2009, 2016 Red Hat, Inc.
+   Copyright (C) 2022 Google LLC
    This file is part of elfutils.
 
    This file is free software; you can redistribute it and/or modify
@@ -53,6 +54,9 @@ static Dwfl_Error
 decompress (int fd __attribute__ ((unused)), Elf **elf)
 {
   Dwfl_Error error = DWFL_E_BADELF;
+  /* ELF cannot be decompressed, if there is no file descriptor.  */
+  if (fd == -1)
+    return error;
   void *buffer = NULL;
   size_t size = 0;
 
@@ -124,11 +128,12 @@ what_kind (int fd, Elf **elfp, Elf_Kind *kind, bool *may_close_fd)
 
 static Dwfl_Error
 libdw_open_elf (int *fdp, Elf **elfp, bool close_on_fail, bool archive_ok,
-               bool never_close_fd, bool bad_elf_ok)
+               bool never_close_fd, bool bad_elf_ok, bool use_elfp)
 {
   bool may_close_fd = false;
 
-  Elf *elf = elf_begin (*fdp, ELF_C_READ_MMAP_PRIVATE, NULL);
+  Elf *elf =
+      use_elfp ? *elfp : elf_begin (*fdp, ELF_C_READ_MMAP_PRIVATE, NULL);
 
   Elf_Kind kind;
   Dwfl_Error error = what_kind (*fdp, &elf, &kind, &may_close_fd);
@@ -194,11 +199,28 @@ libdw_open_elf (int *fdp, Elf **elfp, bool close_on_fail, bool archive_ok,
 Dwfl_Error internal_function
 __libdw_open_file (int *fdp, Elf **elfp, bool close_on_fail, bool archive_ok)
 {
-  return libdw_open_elf (fdp, elfp, close_on_fail, archive_ok, false, false);
+  return libdw_open_elf (fdp, elfp, close_on_fail, archive_ok, false, false,
+                        false);
+}
+
+Dwfl_Error internal_function
+__libdw_open_elf_memory (char *data, size_t size, Elf **elfp, bool archive_ok)
+{
+  /* It is ok to use `fd == -1` here, because libelf uses it as a value for
+     "no file opened" and code supports working with this value, and also
+     `never_close_fd == false` is passed to prevent closing non-existant file.
+     The only caveat is in `decompress` method, which doesn't support
+     decompressing from memory, so reading compressed zImage using this method
+     won't work.  */
+  int fd = -1;
+  *elfp = elf_memory (data, size);
+  /* Allow using this ELF as reference for subsequent elf_begin calls.  */
+  (*elfp)->cmd = ELF_C_READ_MMAP_PRIVATE;
+  return libdw_open_elf (&fd, elfp, false, archive_ok, true, false, true);
 }
 
 Dwfl_Error internal_function
 __libdw_open_elf (int fd, Elf **elfp)
 {
-  return libdw_open_elf (&fd, elfp, false, true, true, true);
+  return libdw_open_elf (&fd, elfp, false, true, true, true, false);
 }
index d10acfa..6ac2c1e 100644 (file)
@@ -1,3 +1,12 @@
+2022-09-13  Aleksei Vetrov  <vvvvvv@google.com>
+
+       * Makefile.am (check_PROGRAMS): Add dwfl-report-offline-memory.
+       (TESTS): Add run-dwfl-report-offline-memory.sh.
+       (EXTRA_DIST): Likewise.
+       (dwfl_report_offline_memory_LDADD): New variable.
+       * dwfl-report-offline-memory.c: New file.
+       * run-dwfl-report-offline-memory.sh: Likewise.
+
 2022-09-13  Khem Raj <raj.khem@gmail.com>
 
        * Makefile.am (*_LDADD): Add libeu if needed for error.
index 142b9c3..f680d3e 100644 (file)
@@ -47,6 +47,7 @@ check_PROGRAMS = arextract arsymtest newfile saridx scnnames sectiondump \
                  alldts typeiter typeiter2 low_high_pc \
                  test-elf_cntl_gelf_getshdr dwflsyms dwfllines \
                  dwfl-report-elf-align dwfl-report-segment-contiguous \
+                 dwfl-report-offline-memory \
                  varlocs backtrace backtrace-child \
                  backtrace-data backtrace-dwarf debuglink debugaltlink \
                  buildid deleted deleted-lib.so aggregate_size peel_type \
@@ -149,6 +150,7 @@ TESTS = run-arextract.sh run-arsymtest.sh run-ar.sh newfile test-nlist \
        run-readelf-mixed-corenote.sh run-dwfllines.sh \
        run-readelf-variant.sh run-readelf-fat-lto.sh \
        run-dwfl-report-elf-align.sh run-addr2line-test.sh \
+       run-dwfl-report-offline-memory.sh \
        run-addr2line-i-test.sh run-addr2line-i-lex-test.sh \
        run-addr2line-i-demangle-test.sh run-addr2line-alt-debugpath.sh \
        run-varlocs.sh run-exprlocs.sh run-varlocs-vars.sh run-funcretval.sh \
@@ -413,6 +415,7 @@ EXTRA_DIST = run-arextract.sh run-arsymtest.sh run-ar.sh \
             testfile69.core.bz2 testfile69.so.bz2 \
             testfile70.core.bz2 testfile70.exec.bz2 testfile71.bz2 \
             run-dwfllines.sh run-dwfl-report-elf-align.sh \
+            run-dwfl-report-offline-memory.sh \
             testfile-dwfl-report-elf-align-shlib.so.bz2 \
             testfilenolines.bz2 test-core-lib.so.bz2 test-core.core.bz2 \
             test-core.exec.bz2 run-addr2line-test.sh \
@@ -711,6 +714,7 @@ test_elf_cntl_gelf_getshdr_LDADD = $(libelf)
 dwflsyms_LDADD = $(libdw) $(libelf) $(argp_LDADD)
 dwfllines_LDADD = $(libeu) $(libdw) $(libelf) $(argp_LDADD)
 dwfl_report_elf_align_LDADD = $(libeu) $(libdw)
+dwfl_report_offline_memory_LDADD = $(libeu) $(libdw)
 dwfl_report_segment_contiguous_LDADD = $(libdw) $(libebl) $(libelf)
 varlocs_LDADD = $(libeu) $(libdw) $(libelf) $(argp_LDADD)
 backtrace_LDADD = $(libeu) $(libdw) $(libelf) $(argp_LDADD)
diff --git a/tests/dwfl-report-offline-memory.c b/tests/dwfl-report-offline-memory.c
new file mode 100644 (file)
index 0000000..837aca5
--- /dev/null
@@ -0,0 +1,97 @@
+/* Test program for dwfl_report_offline_memory.
+   Copyright (C) 2022 Google LLC
+   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/>.  */
+
+#include <assert.h>
+#include <config.h>
+#include <fcntl.h>
+#include <locale.h>
+#include <stdio.h>
+#include <stdio_ext.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include ELFUTILS_HEADER(dwfl)
+#include "system.h"
+
+
+static const Dwfl_Callbacks offline_callbacks =
+  {
+    .find_debuginfo = INTUSE(dwfl_standard_find_debuginfo),
+    .section_address = INTUSE(dwfl_offline_section_address),
+  };
+
+static int
+count_modules (Dwfl_Module *mod __attribute__ ((unused)),
+              void **userdata __attribute__ ((unused)),
+              const char *name __attribute__ ((unused)),
+              Dwarf_Addr base __attribute__ ((unused)), void *arg)
+{
+  unsigned long long *counter = arg;
+  ++(*counter);
+  return DWARF_CB_OK;
+}
+
+int
+main (int argc, char **argv)
+{
+  /* We use no threads here which can interfere with handling a stream.  */
+  (void) __fsetlocking (stdout, FSETLOCKING_BYCALLER);
+
+  /* Set locale.  */
+  (void) setlocale (LC_ALL, "");
+
+  if (argc != 3)
+    error (-1, 0,
+          "usage: dwfl_report_offline_memory [filename] "
+          "[expected number of modules]");
+
+  const char *fname = argv[1];
+  int fd = open (fname, O_RDONLY);
+  if (fd < 0)
+    error (-1, 0, "can't open file %s: %s", fname, strerror (errno));
+  size_t size = lseek (fd, 0, SEEK_END);
+  lseek (fd, 0, SEEK_SET);
+  char *data = malloc (size);
+  size_t bytes_read = read (fd, data, size);
+  assert (bytes_read == size);
+  close (fd);
+
+  Dwfl *dwfl = dwfl_begin (&offline_callbacks);
+  assert (dwfl != NULL);
+
+  Dwfl_Module *mod =
+      dwfl_report_offline_memory (dwfl, argv[1], argv[1], data, size);
+  assert (mod != NULL);
+  dwfl_report_end (dwfl, NULL, NULL);
+
+  unsigned long long number_of_modules = 0;
+  ptrdiff_t offset =
+      dwfl_getmodules (dwfl, &count_modules, &number_of_modules, 0);
+  if (offset < 0)
+    error (1, 0, "dwfl_getmodules: %s", dwfl_errmsg (-1));
+  assert (offset == 0);
+
+  char *endptr;
+  unsigned long long expected_number_of_modules =
+      strtoull (argv[2], &endptr, 0);
+  assert (endptr && !*endptr);
+  assert (number_of_modules == expected_number_of_modules);
+
+  dwfl_end (dwfl);
+  free (data);
+
+  return 0;
+}
diff --git a/tests/run-dwfl-report-offline-memory.sh b/tests/run-dwfl-report-offline-memory.sh
new file mode 100755 (executable)
index 0000000..644a45d
--- /dev/null
@@ -0,0 +1,26 @@
+#! /bin/sh
+# Copyright (C) 2022 Google LLC
+# 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 testfile-dwfl-report-elf-align-shlib.so
+testfiles testarchive64.a
+
+testrun ${abs_builddir}/dwfl-report-offline-memory ./testfile-dwfl-report-elf-align-shlib.so 1
+testrun ${abs_builddir}/dwfl-report-offline-memory ./testarchive64.a 3
+
+exit 0