Rewrite kernel image support: use calculation instead of brute force, support uncompr...
authorRoland McGrath <roland@redhat.com>
Thu, 27 Aug 2009 19:36:47 +0000 (12:36 -0700)
committerRoland McGrath <roland@redhat.com>
Thu, 27 Aug 2009 19:36:47 +0000 (12:36 -0700)
libdwfl/ChangeLog
libdwfl/Makefile.am
libdwfl/gzip.c
libdwfl/image-header.c [new file with mode: 0644]
libdwfl/libdwflP.h
libdwfl/open.c

index 9d2bf8c..39ae70d 100644 (file)
@@ -1,3 +1,17 @@
+2009-08-27  Roland McGrath  <roland@redhat.com>
+
+       * image-header.c: New file.
+       * Makefile.am (libdwfl_a_SOURCES): Add it.
+       * libdwflP.h: Declare __libdw_image_header.
+       * open.c (decompress): Don't consume ELF on failure.
+       (what_kind): New function, broken out of ...
+       (__libdw_open_file): ... here.  Call it.
+       If it fails, try __libdw_image_header and then try what_kind again.
+
+       * gzip.c (unzip): Reuse *WHOLE as first INPUT_BUFFER,
+       leave it behind for next decompressor.
+       * open.c (decompress): Free BUFFER on failure.
+
 2009-08-26  Roland McGrath  <roland@redhat.com>
 
        * gzip.c (find_zImage_payload): New function, broken out of ...
index af83a96..69bef7d 100644 (file)
@@ -74,7 +74,7 @@ libdwfl_a_SOURCES = dwfl_begin.c dwfl_end.c dwfl_error.c dwfl_version.c \
                    dwfl_module_return_value_location.c \
                    dwfl_module_register_names.c \
                    dwfl_segment_report_module.c \
-                   link_map.c core-file.c open.c
+                   link_map.c core-file.c open.c image-header.c
 
 if ZLIB
 libdwfl_a_SOURCES += gzip.c
index daf250b..5604d49 100644 (file)
@@ -57,7 +57,8 @@
 # include <lzma.h>
 # define unzip         __libdw_unlzma
 # define DWFL_E_ZLIB   DWFL_E_LZMA
-# define MAGIC         "\xFD" "7zXZ\0"
+# define MAGIC         "\xFD" "7zXZ\0" /* XZ file format.  */
+# define MAGIC2                "\x5d\0"        /* Raw LZMA format.  */
 # define Z(what)       LZMA_##what
 # define LZMA_ERRNO    LZMA_PROG_ERROR
 # define z_stream      lzma_stream
 # define Z(what)       Z_##what
 #endif
 
-/* We can also handle Linux kernel zImage format in a very hackish way.
-   If it looks like one, we actually just scan the image for the right
-   magic bytes to figure out where the compressed image starts.  */
-
-#define LINUX_MAGIC_OFFSET     514
-#define LINUX_MAGIC            "HdrS"
-#define LINUX_MAX_SCAN         32768
-
-static void *
-find_zImage_payload (void *buffer, size_t size)
-{
-  void *p = memmem (buffer, size, MAGIC, sizeof MAGIC - 1);
-#ifdef LZMA
-  /* The raw LZMA format doesn't have any helpful header magic bytes to
-     match.  So instead we just consider any byte that could possibly be
-     the start of an LZMA header, and try feeding the input to the decoder
-     to see if it likes the data.  */
-  if (p == NULL)
-    for (; size > 0; ++buffer, --size)
-      if (*(uint8_t *) buffer < (9 * 5 * 5))
-       {
-         uint8_t dummy[512];
-         lzma_stream z = { .next_in = buffer, .avail_in = size,
-                           .next_out = dummy, .avail_out = sizeof dummy };
-         int result = lzma_alone_decoder (&z, 1 << 30);
-         if (result != LZMA_OK)
-           break;
-         result = lzma_code (&z, LZMA_RUN);
-         lzma_end (&z);
-         if (result == LZMA_OK)
-           return buffer;
-       }
-#endif
-  return p;
-}
-
-static bool
-mapped_zImage (off64_t *start_offset, void **mapped, size_t *mapped_size)
-{
-  const size_t pos = LINUX_MAGIC_OFFSET + sizeof LINUX_MAGIC;
-  if (*mapped_size > pos
-      && !memcmp (*mapped + LINUX_MAGIC_OFFSET,
-                 LINUX_MAGIC, sizeof LINUX_MAGIC - 1))
-    {
-      size_t scan = *mapped_size - pos;
-      if (scan > LINUX_MAX_SCAN)
-       scan = LINUX_MAX_SCAN;
-      void *p = find_zImage_payload (*mapped + pos, scan);
-      if (p != NULL)
-       {
-         *start_offset += p - *mapped;
-         *mapped_size = *mapped + *mapped_size - p,
-         *mapped = p;
-         return true;
-       }
-    }
-  return false;
-}
-
 #define READ_SIZE              (1 << 20)
 
 /* If this is not a compressed image, return DWFL_E_BADELF.
    If we uncompressed it into *WHOLE, *WHOLE_SIZE, return DWFL_E_NOERROR.
-   Otherwise return an error for bad compressed data or I/O failure.  */
+   Otherwise return an error for bad compressed data or I/O failure.
+   If we return an error after reading the first part of the file,
+   leave that portion malloc'd in *WHOLE, *WHOLE_SIZE.  If *WHOLE
+   is not null on entry, we'll use it in lieu of repeating a read.  */
 
 Dwfl_Error internal_function
 unzip (int fd, off64_t start_offset,
@@ -176,45 +121,66 @@ unzip (int fd, off64_t start_offset,
   }
 
   void *input_buffer = NULL;
-  off_t input_pos;
+  off_t input_pos = 0;
+
+  inline Dwfl_Error fail (Dwfl_Error failure)
+  {
+    if (input_pos == (off_t) mapped_size)
+      *whole = input_buffer;
+    else
+      {
+       free (input_buffer);
+       *whole = NULL;
+      }
+    free (buffer);
+    return failure;
+  }
 
   inline Dwfl_Error zlib_fail (int result)
   {
-    Dwfl_Error failure = DWFL_E_ZLIB;
     switch (result)
       {
       case Z (MEM_ERROR):
-       failure = DWFL_E_NOMEM;
-       break;
+       return fail (DWFL_E_NOMEM);
       case Z (ERRNO):
-       failure = DWFL_E_ERRNO;
-       break;
+       return fail (DWFL_E_ERRNO);
+      default:
+       return fail (DWFL_E_ZLIB);
       }
-    free (buffer);
-    free (input_buffer);
-    *whole = NULL;
-    return failure;
   }
 
   if (mapped == NULL)
     {
-      input_buffer = malloc (READ_SIZE);
-      if (unlikely (input_buffer == NULL))
-       return DWFL_E_NOMEM;
+      if (*whole == NULL)
+       {
+         input_buffer = malloc (READ_SIZE);
+         if (unlikely (input_buffer == NULL))
+           return DWFL_E_NOMEM;
 
-      ssize_t n = pread_retry (fd, input_buffer, READ_SIZE, 0);
-      if (unlikely (n < 0))
-       return zlib_fail (Z (ERRNO));
+         ssize_t n = pread_retry (fd, input_buffer, READ_SIZE, start_offset);
+         if (unlikely (n < 0))
+           return zlib_fail (Z (ERRNO));
 
-      input_pos = n;
-      mapped = input_buffer;
-      mapped_size = n;
+         input_pos = n;
+         mapped = input_buffer;
+         mapped_size = n;
+       }
+      else
+       {
+         input_buffer = *whole;
+         input_pos = mapped_size = *whole_size;
+       }
     }
 
+#define NOMAGIC(magic) \
+  (mapped_size <= sizeof magic || memcmp (mapped, magic, sizeof magic - 1))
+
   /* First, look at the header.  */
-  if ((mapped_size <= sizeof MAGIC
-       || memcmp (mapped, MAGIC, sizeof MAGIC - 1))
-      && !mapped_zImage (&start_offset, &mapped, &mapped_size))
+  if (NOMAGIC (MAGIC)
+#ifdef MAGIC2
+      && NOMAGIC (MAGIC2)
+#endif
+      )
     /* Not a compressed file.  */
     return DWFL_E_BADELF;
 
@@ -236,7 +202,8 @@ unzip (int fd, off64_t start_offset,
     {
       if (z.avail_in == 0 && input_buffer != NULL)
        {
-         ssize_t n = pread_retry (fd, input_buffer, READ_SIZE, input_pos);
+         ssize_t n = pread_retry (fd, input_buffer, READ_SIZE,
+                                  start_offset + input_pos);
          if (unlikely (n < 0))
            {
              inflateEnd (&z);
@@ -308,37 +275,12 @@ unzip (int fd, off64_t start_offset,
 
   if (result == DWFL_E_NOERROR && gzdirect (zf))
     {
-      bool found = false;
-      char buf[sizeof LINUX_MAGIC - 1];
-      gzseek (zf, start_offset + LINUX_MAGIC_OFFSET, SEEK_SET);
-      int n = gzread (zf, buf, sizeof buf);
-      if (n == sizeof buf
-         && !memcmp (buf, LINUX_MAGIC, sizeof LINUX_MAGIC - 1))
-       while (gzread (zf, buf, sizeof MAGIC - 1) == sizeof MAGIC - 1)
-         if (!memcmp (buf, MAGIC, sizeof MAGIC - 1))
-           {
-             start_offset = gztell (zf) - (sizeof MAGIC - 1);
-             found = true;
-             break;
-           }
-         else if (gztell (zf) > LINUX_MAX_SCAN)
-           break;
       gzclose (zf);
-      if (found)
-       {
-         result = open_stream ();
-         if (result == DWFL_E_NOERROR && unlikely (gzdirect (zf)))
-           {
-             gzclose (zf);
-             result = DWFL_E_BADELF;
-           }
-       }
-      else
-       result = DWFL_E_BADELF;
+      return fail (DWFL_E_BADELF);
     }
 
   if (result != DWFL_E_NOERROR)
-    return result;
+    return fail (result);
 
   ptrdiff_t pos = 0;
   while (1)
@@ -365,6 +307,8 @@ unzip (int fd, off64_t start_offset,
   smaller_buffer (pos);
 #endif
 
+  free (input_buffer);
+
   *whole = buffer;
   *whole_size = size;
 
diff --git a/libdwfl/image-header.c b/libdwfl/image-header.c
new file mode 100644 (file)
index 0000000..054fba7
--- /dev/null
@@ -0,0 +1,124 @@
+/* Linux kernel image support for libdwfl.
+   Copyright (C) 2009 Red Hat, Inc.
+   This file is part of Red Hat elfutils.
+
+   Red Hat elfutils 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; version 2 of the License.
+
+   Red Hat 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 Red Hat elfutils; if not, write to the Free Software Foundation,
+   Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301 USA.
+
+   In addition, as a special exception, Red Hat, Inc. gives You the
+   additional right to link the code of Red Hat elfutils with code licensed
+   under any Open Source Initiative certified open source license
+   (http://www.opensource.org/licenses/index.php) which requires the
+   distribution of source code with any binary distribution and to
+   distribute linked combinations of the two.  Non-GPL Code permitted under
+   this exception must only link to the code of Red Hat elfutils through
+   those well defined interfaces identified in the file named EXCEPTION
+   found in the source code files (the "Approved Interfaces").  The files
+   of Non-GPL Code may instantiate templates or use macros or inline
+   functions from the Approved Interfaces without causing the resulting
+   work to be covered by the GNU General Public License.  Only Red Hat,
+   Inc. may make changes or additions to the list of Approved Interfaces.
+   Red Hat's grant of this exception is conditioned upon your not adding
+   any new exceptions.  If you wish to add a new Approved Interface or
+   exception, please contact Red Hat.  You must obey the GNU General Public
+   License in all respects for all of the Red Hat elfutils code and other
+   code used in conjunction with Red Hat elfutils except the Non-GPL Code
+   covered by this exception.  If you modify this file, you may extend this
+   exception to your version of the file, but you are not obligated to do
+   so.  If you do not wish to provide this exception without modification,
+   you must delete this exception statement from your version and license
+   this file solely under the GPL without exception.
+
+   Red Hat elfutils is an included package of the Open Invention Network.
+   An included package of the Open Invention Network is a package for which
+   Open Invention Network licensees cross-license their patents.  No patent
+   license is granted, either expressly or impliedly, by designation as an
+   included package.  Should you wish to participate in the Open Invention
+   Network licensing program, please visit www.openinventionnetwork.com
+   <http://www.openinventionnetwork.com>.  */
+
+#include "libdwflP.h"
+#include "system.h"
+
+#include <unistd.h>
+#include <endian.h>
+
+#if BYTE_ORDER == LITTLE_ENDIAN
+# define LE16(x)       (x)
+# define LE32(x)       (x)
+#else
+# define LE16(x)       bswap_16 (x)
+# define LE32(x)       bswap_32 (x)
+#endif
+
+/* See Documentation/x86/boot.txt in Linux kernel sources
+   for an explanation of these format details.  */
+
+#define MAGIC1                 0xaa55
+#define MAGIC2                 0x53726448 /* "HdrS" little-endian */
+#define MIN_VERSION            0x0208
+
+#define H_START                        (H_SETUP_SECTS & -4)
+#define H_SETUP_SECTS          0x1f1
+#define H_MAGIC1               0x1fe
+#define H_MAGIC2               0x202
+#define H_VERSION              0x206
+#define H_PAYLOAD_OFFSET       0x248
+#define H_PAYLOAD_LENGTH       0x24c
+#define H_END                  0x250
+#define H_READ_SIZE            (H_END - H_START)
+
+Dwfl_Error
+internal_function
+__libdw_image_header (int fd, off64_t *start_offset,
+                     void *mapped, size_t mapped_size)
+{
+  if (likely (mapped_size > H_END))
+    {
+      const void *header = mapped;
+      char header_buffer[H_READ_SIZE];
+      if (header == NULL)
+       {
+         ssize_t n = pread_retry (fd, header_buffer, H_READ_SIZE,
+                                  *start_offset + H_START);
+         if (n < 0)
+           return DWFL_E_ERRNO;
+         if (n < H_READ_SIZE)
+           return DWFL_E_BADELF;
+
+         header = header_buffer - H_START;
+       }
+
+      if (*(uint16_t *) (header + H_MAGIC1) == LE16 (MAGIC1)
+         && *(uint32_t *) (header + H_MAGIC2) == LE32 (MAGIC2)
+         && LE16 (*(uint16_t *) (header + H_VERSION)) >= MIN_VERSION)
+       {
+         /* The magic numbers match and the version field is sufficient.
+            Extract the payload bounds.  */
+
+         uint32_t offset = LE32 (*(uint32_t *) (header + H_PAYLOAD_OFFSET));
+         uint32_t length = LE32 (*(uint32_t *) (header + H_PAYLOAD_LENGTH));
+
+         offset += ((*(uint8_t *) (header + H_SETUP_SECTS) ?: 4) + 1) * 512;
+
+         if (offset > H_END && offset < mapped_size
+             && mapped_size - length >= offset)
+           {
+             /* It looks kosher.  Use it!  */
+             *start_offset += offset;
+             return DWFL_E_NOERROR;
+           }
+       }
+    }
+  return DWFL_E_BADELF;
+}
index 0c52d25..d08d8a7 100644 (file)
@@ -344,6 +344,11 @@ extern Dwfl_Error __libdw_unlzma (int fd, off64_t start_offset,
                                  void **whole, size_t *whole_size)
   internal_function;
 
+/* Skip the image header before a file image: updates *START_OFFSET.  */
+extern Dwfl_Error __libdw_image_header (int fd, off64_t *start_offset,
+                                       void *mapped, size_t mapped_size)
+  internal_function;
+
 /* Open Elf handle on *FDP.  This handles decompression and checks
    elf_kind.  Succeed only for ELF_K_ELF, or also ELF_K_AR if ARCHIVE_OK.
    Returns DWFL_E_NOERROR and sets *ELFP on success, resets *FDP to -1 if
index e78eb21..397af35 100644 (file)
@@ -65,8 +65,7 @@
 # define __libdw_unlzma(...)   false
 #endif
 
-/* Always consumes *ELF, never consumes FD.
-   Replaces *ELF on success.  */
+/* Consumes and replaces *ELF only on success.  */
 static Dwfl_Error
 decompress (int fd __attribute__ ((unused)), Elf **elf)
 {
@@ -77,7 +76,7 @@ decompress (int fd __attribute__ ((unused)), Elf **elf)
 #if USE_ZLIB || USE_BZLIB || USE_LZMA
   const off64_t offset = (*elf)->start_offset;
   void *const mapped = ((*elf)->map_address == NULL ? NULL
-                       : (*elf)->map_address + (*elf)->start_offset);
+                       : (*elf)->map_address + offset);
   const size_t mapped_size = (*elf)->maximum_size;
   if (mapped_size == 0)
     return error;
@@ -89,9 +88,6 @@ decompress (int fd __attribute__ ((unused)), Elf **elf)
     error = __libdw_unlzma (fd, offset, mapped, mapped_size, &buffer, &size);
 #endif
 
-  elf_end (*elf);
-  *elf = NULL;
-
   if (error == DWFL_E_NOERROR)
     {
       if (unlikely (size == 0))
@@ -101,39 +97,86 @@ decompress (int fd __attribute__ ((unused)), Elf **elf)
        }
       else
        {
-         *elf = elf_memory (buffer, size);
-         if (*elf == NULL)
+         Elf *memelf = elf_memory (buffer, size);
+         if (memelf == NULL)
            {
              error = DWFL_E_LIBELF;
              free (buffer);
            }
          else
-           (*elf)->flags |= ELF_F_MALLOCED;
+           {
+             memelf->flags |= ELF_F_MALLOCED;
+             elf_end (*elf);
+             *elf = memelf;
+           }
        }
     }
+  else
+    free (buffer);
 
   return error;
 }
 
+static Dwfl_Error
+what_kind (int fd, Elf **elfp, Elf_Kind *kind, bool *close_fd)
+{
+  Dwfl_Error error = DWFL_E_NOERROR;
+  *kind = elf_kind (*elfp);
+  if (unlikely (*kind == ELF_K_NONE))
+    {
+      if (unlikely (*elfp == NULL))
+       error = DWFL_E_LIBELF;
+      else
+       {
+         error = decompress (fd, elfp);
+         if (error == DWFL_E_NOERROR)
+           {
+             *close_fd = true;
+             *kind = elf_kind (*elfp);
+           }
+       }
+    }
+  return error;
+}
+
 Dwfl_Error internal_function
 __libdw_open_file (int *fdp, Elf **elfp, bool close_on_fail, bool archive_ok)
 {
   bool close_fd = false;
-  Dwfl_Error error = DWFL_E_NOERROR;
 
   Elf *elf = elf_begin (*fdp, ELF_C_READ_MMAP_PRIVATE, NULL);
-  Elf_Kind kind = elf_kind (elf);
-  if (unlikely (kind == ELF_K_NONE))
+
+  Elf_Kind kind;
+  Dwfl_Error error = what_kind (*fdp, &elf, &kind, &close_fd);
+  if (error == DWFL_E_BADELF)
     {
-      if (unlikely (elf == NULL))
-       error = DWFL_E_LIBELF;
-      else
+      /* It's not an ELF file or a compressed file.
+        See if it's an image with a header preceding the real file.  */
+
+      off64_t offset = elf->start_offset;
+      error = __libdw_image_header (*fdp, &offset,
+                                   (elf->map_address == NULL ? NULL
+                                    : elf->map_address + offset),
+                                   elf->maximum_size);
+      if (error == DWFL_E_NOERROR)
        {
-         error = decompress (*fdp, &elf);
-         if (error == DWFL_E_NOERROR)
+         /* 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.offset = offset - sizeof (struct ar_hdr);
+         Elf *subelf = elf_begin (-1, ELF_C_READ_MMAP_PRIVATE, elf);
+         elf->kind = ELF_K_NONE;
+         if (unlikely (subelf == NULL))
+           error = DWFL_E_LIBELF;
+         else
            {
-             close_fd = true;
-             kind = elf_kind (elf);
+             subelf->parent = NULL;
+             subelf->flags |= elf->flags & (ELF_F_MMAPPED | ELF_F_MALLOCED);
+             elf->flags &= ~(ELF_F_MMAPPED | ELF_F_MALLOCED);
+             elf_end (elf);
+             elf = subelf;
+             error = what_kind (*fdp, &elf, &kind, &close_fd);
            }
        }
     }
@@ -141,10 +184,12 @@ __libdw_open_file (int *fdp, Elf **elfp, bool close_on_fail, bool archive_ok)
   if (error == DWFL_E_NOERROR
       && kind != ELF_K_ELF
       && !(archive_ok && kind == ELF_K_AR))
+    error = DWFL_E_BADELF;
+
+  if (error != DWFL_E_NOERROR)
     {
       elf_end (elf);
       elf = NULL;
-      error = DWFL_E_BADELF;
     }
 
   if (error == DWFL_E_NOERROR ? close_fd : close_on_fail)