Blame libelf/elf_compress_gnu.c

Packit 032894
/* Compress or decompress a section.
Packit 032894
   Copyright (C) 2015 Red Hat, Inc.
Packit 032894
   This file is part of elfutils.
Packit 032894
Packit 032894
   This file is free software; you can redistribute it and/or modify
Packit 032894
   it under the terms of either
Packit 032894
Packit 032894
     * the GNU Lesser General Public License as published by the Free
Packit 032894
       Software Foundation; either version 3 of the License, or (at
Packit 032894
       your option) any later version
Packit 032894
Packit 032894
   or
Packit 032894
Packit 032894
     * the GNU General Public License as published by the Free
Packit 032894
       Software Foundation; either version 2 of the License, or (at
Packit 032894
       your option) any later version
Packit 032894
Packit 032894
   or both in parallel, as here.
Packit 032894
Packit 032894
   elfutils is distributed in the hope that it will be useful, but
Packit 032894
   WITHOUT ANY WARRANTY; without even the implied warranty of
Packit 032894
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
Packit 032894
   General Public License for more details.
Packit 032894
Packit 032894
   You should have received copies of the GNU General Public License and
Packit 032894
   the GNU Lesser General Public License along with this program.  If
Packit 032894
   not, see <http://www.gnu.org/licenses/>.  */
Packit 032894
Packit 032894
#ifdef HAVE_CONFIG_H
Packit 032894
# include <config.h>
Packit 032894
#endif
Packit 032894
Packit 032894
#include <libelf.h>
Packit 032894
#include "libelfP.h"
Packit 032894
#include "common.h"
Packit 032894
Packit 032894
int
Packit 032894
elf_compress_gnu (Elf_Scn *scn, int inflate, unsigned int flags)
Packit 032894
{
Packit 032894
  if (scn == NULL)
Packit 032894
    return -1;
Packit 032894
Packit 032894
  if ((flags & ~ELF_CHF_FORCE) != 0)
Packit 032894
    {
Packit 032894
      __libelf_seterrno (ELF_E_INVALID_OPERAND);
Packit 032894
      return -1;
Packit 032894
    }
Packit 032894
Packit 032894
  bool force = (flags & ELF_CHF_FORCE) != 0;
Packit 032894
Packit 032894
  Elf *elf = scn->elf;
Packit 032894
  GElf_Ehdr ehdr;
Packit 032894
  if (gelf_getehdr (elf, &ehdr) == NULL)
Packit 032894
    return -1;
Packit 032894
Packit 032894
  int elfclass = elf->class;
Packit 032894
  int elfdata = ehdr.e_ident[EI_DATA];
Packit 032894
Packit 032894
  Elf64_Xword sh_flags;
Packit 032894
  Elf64_Word sh_type;
Packit 032894
  Elf64_Xword sh_addralign;
Packit 032894
  if (elfclass == ELFCLASS32)
Packit 032894
    {
Packit 032894
      Elf32_Shdr *shdr = elf32_getshdr (scn);
Packit 032894
      if (shdr == NULL)
Packit 032894
	return -1;
Packit 032894
Packit 032894
      sh_flags = shdr->sh_flags;
Packit 032894
      sh_type = shdr->sh_type;
Packit 032894
      sh_addralign = shdr->sh_addralign;
Packit 032894
    }
Packit 032894
  else
Packit 032894
    {
Packit 032894
      Elf64_Shdr *shdr = elf64_getshdr (scn);
Packit 032894
      if (shdr == NULL)
Packit 032894
	return -1;
Packit 032894
Packit 032894
      sh_flags = shdr->sh_flags;
Packit 032894
      sh_type = shdr->sh_type;
Packit 032894
      sh_addralign = shdr->sh_addralign;
Packit 032894
    }
Packit 032894
Packit 032894
  /* Allocated sections, or sections that are already are compressed
Packit 032894
     cannot (also) be GNU compressed.  */
Packit 032894
  if ((sh_flags & SHF_ALLOC) != 0 || (sh_flags & SHF_COMPRESSED))
Packit 032894
    {
Packit 032894
      __libelf_seterrno (ELF_E_INVALID_SECTION_FLAGS);
Packit 032894
      return -1;
Packit 032894
    }
Packit 032894
Packit 032894
  if (sh_type == SHT_NULL || sh_type == SHT_NOBITS)
Packit 032894
    {
Packit 032894
      __libelf_seterrno (ELF_E_INVALID_SECTION_TYPE);
Packit 032894
      return -1;
Packit 032894
    }
Packit 032894
Packit 032894
  /* For GNU compression we cannot really know whether the section is
Packit 032894
     already compressed or not.  Just try and see what happens...  */
Packit 032894
  // int compressed = (sh_flags & SHF_COMPRESSED);
Packit 032894
  if (inflate == 1)
Packit 032894
    {
Packit 032894
      size_t hsize = 4 + 8; /* GNU "ZLIB" + 8 byte size.  */
Packit 032894
      size_t orig_size, new_size, orig_addralign;
Packit 032894
      void *out_buf = __libelf_compress (scn, hsize, elfdata,
Packit 032894
					 &orig_size, &orig_addralign,
Packit 032894
					 &new_size, force);
Packit 032894
Packit 032894
      /* Compression would make section larger, don't change anything.  */
Packit 032894
      if (out_buf == (void *) -1)
Packit 032894
	return 0;
Packit 032894
Packit 032894
      /* Compression failed, return error.  */
Packit 032894
      if (out_buf == NULL)
Packit 032894
	return -1;
Packit 032894
Packit 032894
      uint64_t be64_size = htobe64 (orig_size);
Packit 032894
      memmove (out_buf, "ZLIB", 4);
Packit 032894
      memmove (out_buf + 4, &be64_size, sizeof (be64_size));
Packit 032894
Packit 032894
      /* We don't know anything about sh_entsize, sh_addralign and
Packit 032894
	 sh_flags won't have a SHF_COMPRESSED hint in the GNU format.
Packit 032894
	 Just adjust the sh_size.  */
Packit 032894
      if (elfclass == ELFCLASS32)
Packit 032894
	{
Packit 032894
	  Elf32_Shdr *shdr = elf32_getshdr (scn);
Packit 032894
	  shdr->sh_size = new_size;
Packit 032894
	}
Packit 032894
      else
Packit 032894
	{
Packit 032894
	  Elf64_Shdr *shdr = elf64_getshdr (scn);
Packit 032894
	  shdr->sh_size = new_size;
Packit 032894
	}
Packit 032894
Packit 032894
      __libelf_reset_rawdata (scn, out_buf, new_size, 1, ELF_T_BYTE);
Packit 032894
Packit 032894
      /* The section is now compressed, we could keep the uncompressed
Packit 032894
	 data around, but since that might have been multiple Elf_Data
Packit 032894
	 buffers let the user uncompress it explicitly again if they
Packit 032894
	 want it to simplify bookkeeping.  */
Packit 032894
      scn->zdata_base = NULL;
Packit 032894
Packit 032894
      return 1;
Packit 032894
    }
Packit 032894
  else if (inflate == 0)
Packit 032894
    {
Packit 032894
      /* In theory the user could have constucted a compressed section
Packit 032894
	 by hand.  And in practice they do. For example when copying
Packit 032894
	 a section from one file to another using elf_newdata. So we
Packit 032894
	 have to use elf_getdata (not elf_rawdata).  */
Packit 032894
      Elf_Data *data = elf_getdata (scn, NULL);
Packit 032894
      if (data == NULL)
Packit 032894
	return -1;
Packit 032894
Packit 032894
      size_t hsize = 4 + 8; /* GNU "ZLIB" + 8 byte size.  */
Packit 032894
      if (data->d_size < hsize || memcmp (data->d_buf, "ZLIB", 4) != 0)
Packit 032894
	{
Packit 032894
          __libelf_seterrno (ELF_E_NOT_COMPRESSED);
Packit 032894
	  return -1;
Packit 032894
	}
Packit 032894
Packit 032894
      /* There is a 12-byte header of "ZLIB" followed by
Packit 032894
	 an 8-byte big-endian size.  There is only one type and
Packit 032894
	 Alignment isn't preserved separately.  */
Packit 032894
      uint64_t gsize;
Packit 032894
      memcpy (&gsize, data->d_buf + 4, sizeof gsize);
Packit 032894
      gsize = be64toh (gsize);
Packit 032894
Packit 032894
      /* One more sanity check, size should be bigger than original
Packit 032894
	 data size plus some overhead (4 chars ZLIB + 8 bytes size + 6
Packit 032894
	 bytes zlib stream overhead + 5 bytes overhead max for one 16K
Packit 032894
	 block) and should fit into a size_t.  */
Packit 032894
      if (gsize + 4 + 8 + 6 + 5 < data->d_size || gsize > SIZE_MAX)
Packit 032894
	{
Packit 032894
	  __libelf_seterrno (ELF_E_NOT_COMPRESSED);
Packit 032894
	  return -1;
Packit 032894
	}
Packit 032894
Packit 032894
      size_t size = gsize;
Packit 032894
      size_t size_in = data->d_size - hsize;
Packit 032894
      void *buf_in = data->d_buf + hsize;
Packit 032894
      void *buf_out = __libelf_decompress (buf_in, size_in, size);
Packit 032894
      if (buf_out == NULL)
Packit 032894
	return -1;
Packit 032894
Packit 032894
      /* We don't know anything about sh_entsize, sh_addralign and
Packit 032894
	 sh_flags won't have a SHF_COMPRESSED hint in the GNU format.
Packit 032894
	 Just adjust the sh_size.  */
Packit 032894
      if (elfclass == ELFCLASS32)
Packit 032894
	{
Packit 032894
	  Elf32_Shdr *shdr = elf32_getshdr (scn);
Packit 032894
	  shdr->sh_size = size;
Packit 032894
	}
Packit 032894
      else
Packit 032894
	{
Packit 032894
	  Elf64_Shdr *shdr = elf64_getshdr (scn);
Packit 032894
	  shdr->sh_size = size;
Packit 032894
	}
Packit 032894
Packit 032894
      __libelf_reset_rawdata (scn, buf_out, size, sh_addralign,
Packit 032894
			      __libelf_data_type (elf, sh_type, sh_addralign));
Packit 032894
Packit 032894
      scn->zdata_base = buf_out;
Packit 032894
Packit 032894
      return 1;
Packit 032894
    }
Packit 032894
  else
Packit 032894
    {
Packit 032894
      __libelf_seterrno (ELF_E_UNKNOWN_COMPRESSION_TYPE);
Packit 032894
      return -1;
Packit 032894
    }
Packit 032894
}