|
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 |
}
|