You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
536 lines
15 KiB
536 lines
15 KiB
/* Compress or decompress a section.
|
|
Copyright (C) 2015, 2016 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 either
|
|
|
|
* the GNU Lesser General Public License as published by the Free
|
|
Software Foundation; either version 3 of the License, or (at
|
|
your option) any later version
|
|
|
|
or
|
|
|
|
* the GNU General Public License as published by the Free
|
|
Software Foundation; either version 2 of the License, or (at
|
|
your option) any later version
|
|
|
|
or both in parallel, as here.
|
|
|
|
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 copies of the GNU General Public License and
|
|
the GNU Lesser General Public License along with this program. If
|
|
not, see <http://www.gnu.org/licenses/>. */
|
|
|
|
#ifdef HAVE_CONFIG_H
|
|
# include <config.h>
|
|
#endif
|
|
|
|
#include <libelf.h>
|
|
#include <system.h>
|
|
#include "libelfP.h"
|
|
#include "common.h"
|
|
|
|
#include <stddef.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <unistd.h>
|
|
#include <zlib.h>
|
|
|
|
/* Cleanup and return result. Don't leak memory. */
|
|
static void *
|
|
do_deflate_cleanup (void *result, z_stream *z, void *out_buf,
|
|
Elf_Data *cdatap)
|
|
{
|
|
deflateEnd (z);
|
|
free (out_buf);
|
|
if (cdatap != NULL)
|
|
free (cdatap->d_buf);
|
|
return result;
|
|
}
|
|
|
|
#define deflate_cleanup(result, cdata) \
|
|
do_deflate_cleanup(result, &z, out_buf, cdata)
|
|
|
|
/* Given a section, uses the (in-memory) Elf_Data to extract the
|
|
original data size (including the given header size) and data
|
|
alignment. Returns a buffer that has at least hsize bytes (for the
|
|
caller to fill in with a header) plus zlib compressed date. Also
|
|
returns the new buffer size in new_size (hsize + compressed data
|
|
size). Returns (void *) -1 when FORCE is false and the compressed
|
|
data would be bigger than the original data. */
|
|
void *
|
|
internal_function
|
|
__libelf_compress (Elf_Scn *scn, size_t hsize, int ei_data,
|
|
size_t *orig_size, size_t *orig_addralign,
|
|
size_t *new_size, bool force)
|
|
{
|
|
/* The compressed data is the on-disk data. We simplify the
|
|
implementation a bit by asking for the (converted) in-memory
|
|
data (which might be all there is if the user created it with
|
|
elf_newdata) and then convert back to raw if needed before
|
|
compressing. Should be made a bit more clever to directly
|
|
use raw if that is directly available. */
|
|
Elf_Data *data = elf_getdata (scn, NULL);
|
|
if (data == NULL)
|
|
return NULL;
|
|
|
|
/* When not forced and we immediately know we would use more data by
|
|
compressing, because of the header plus zlib overhead (five bytes
|
|
per 16 KB block, plus a one-time overhead of six bytes for the
|
|
entire stream), don't do anything. */
|
|
Elf_Data *next_data = elf_getdata (scn, data);
|
|
if (next_data == NULL && !force
|
|
&& data->d_size <= hsize + 5 + 6)
|
|
return (void *) -1;
|
|
|
|
*orig_addralign = data->d_align;
|
|
*orig_size = data->d_size;
|
|
|
|
/* Guess an output block size. 1/8th of the original Elf_Data plus
|
|
hsize. Make the first chunk twice that size (25%), then increase
|
|
by a block (12.5%) when necessary. */
|
|
size_t block = (data->d_size / 8) + hsize;
|
|
size_t out_size = 2 * block;
|
|
void *out_buf = malloc (out_size);
|
|
if (out_buf == NULL)
|
|
{
|
|
__libelf_seterrno (ELF_E_NOMEM);
|
|
return NULL;
|
|
}
|
|
|
|
/* Caller gets to fill in the header at the start. Just skip it here. */
|
|
size_t used = hsize;
|
|
|
|
z_stream z;
|
|
z.zalloc = Z_NULL;
|
|
z.zfree = Z_NULL;
|
|
z.opaque = Z_NULL;
|
|
int zrc = deflateInit (&z, Z_BEST_COMPRESSION);
|
|
if (zrc != Z_OK)
|
|
{
|
|
__libelf_seterrno (ELF_E_COMPRESS_ERROR);
|
|
return deflate_cleanup(NULL, NULL);
|
|
}
|
|
|
|
Elf_Data cdata;
|
|
cdata.d_buf = NULL;
|
|
|
|
/* Loop over data buffers. */
|
|
int flush = Z_NO_FLUSH;
|
|
do
|
|
{
|
|
/* Convert to raw if different endianness. */
|
|
cdata = *data;
|
|
bool convert = ei_data != MY_ELFDATA && data->d_size > 0;
|
|
if (convert)
|
|
{
|
|
/* Don't do this conversion in place, we might want to keep
|
|
the original data around, caller decides. */
|
|
cdata.d_buf = malloc (data->d_size);
|
|
if (cdata.d_buf == NULL)
|
|
{
|
|
__libelf_seterrno (ELF_E_NOMEM);
|
|
return deflate_cleanup (NULL, NULL);
|
|
}
|
|
if (gelf_xlatetof (scn->elf, &cdata, data, ei_data) == NULL)
|
|
return deflate_cleanup (NULL, &cdata);
|
|
}
|
|
|
|
z.avail_in = cdata.d_size;
|
|
z.next_in = cdata.d_buf;
|
|
|
|
/* Get next buffer to see if this is the last one. */
|
|
data = next_data;
|
|
if (data != NULL)
|
|
{
|
|
*orig_addralign = MAX (*orig_addralign, data->d_align);
|
|
*orig_size += data->d_size;
|
|
next_data = elf_getdata (scn, data);
|
|
}
|
|
else
|
|
flush = Z_FINISH;
|
|
|
|
/* Flush one data buffer. */
|
|
do
|
|
{
|
|
z.avail_out = out_size - used;
|
|
z.next_out = out_buf + used;
|
|
zrc = deflate (&z, flush);
|
|
if (zrc == Z_STREAM_ERROR)
|
|
{
|
|
__libelf_seterrno (ELF_E_COMPRESS_ERROR);
|
|
return deflate_cleanup (NULL, convert ? &cdata : NULL);
|
|
}
|
|
used += (out_size - used) - z.avail_out;
|
|
|
|
/* Bail out if we are sure the user doesn't want the
|
|
compression forced and we are using more compressed data
|
|
than original data. */
|
|
if (!force && flush == Z_FINISH && used >= *orig_size)
|
|
return deflate_cleanup ((void *) -1, convert ? &cdata : NULL);
|
|
|
|
if (z.avail_out == 0)
|
|
{
|
|
void *bigger = realloc (out_buf, out_size + block);
|
|
if (bigger == NULL)
|
|
{
|
|
__libelf_seterrno (ELF_E_NOMEM);
|
|
return deflate_cleanup (NULL, convert ? &cdata : NULL);
|
|
}
|
|
out_buf = bigger;
|
|
out_size += block;
|
|
}
|
|
}
|
|
while (z.avail_out == 0); /* Need more output buffer. */
|
|
|
|
if (convert)
|
|
{
|
|
free (cdata.d_buf);
|
|
cdata.d_buf = NULL;
|
|
}
|
|
}
|
|
while (flush != Z_FINISH); /* More data blocks. */
|
|
|
|
if (zrc != Z_STREAM_END)
|
|
{
|
|
__libelf_seterrno (ELF_E_COMPRESS_ERROR);
|
|
return deflate_cleanup (NULL, NULL);
|
|
}
|
|
|
|
deflateEnd (&z);
|
|
*new_size = used;
|
|
return out_buf;
|
|
}
|
|
|
|
void *
|
|
internal_function
|
|
__libelf_decompress (void *buf_in, size_t size_in, size_t size_out)
|
|
{
|
|
/* Catch highly unlikely compression ratios so we don't allocate
|
|
some giant amount of memory for nothing. The max compression
|
|
factor 1032:1 comes from http://www.zlib.net/zlib_tech.html */
|
|
if (unlikely (size_out / 1032 > size_in))
|
|
{
|
|
__libelf_seterrno (ELF_E_INVALID_DATA);
|
|
return NULL;
|
|
}
|
|
|
|
/* Malloc might return NULL when requestion zero size. This is highly
|
|
unlikely, it would only happen when the compression was forced.
|
|
But we do need a non-NULL buffer to return and set as result.
|
|
Just make sure to always allocate at least 1 byte. */
|
|
void *buf_out = malloc (size_out ?: 1);
|
|
if (unlikely (buf_out == NULL))
|
|
{
|
|
__libelf_seterrno (ELF_E_NOMEM);
|
|
return NULL;
|
|
}
|
|
|
|
z_stream z =
|
|
{
|
|
.next_in = buf_in,
|
|
.avail_in = size_in,
|
|
.next_out = buf_out,
|
|
.avail_out = size_out
|
|
};
|
|
int zrc = inflateInit (&z);
|
|
while (z.avail_in > 0 && likely (zrc == Z_OK))
|
|
{
|
|
z.next_out = buf_out + (size_out - z.avail_out);
|
|
zrc = inflate (&z, Z_FINISH);
|
|
if (unlikely (zrc != Z_STREAM_END))
|
|
{
|
|
zrc = Z_DATA_ERROR;
|
|
break;
|
|
}
|
|
zrc = inflateReset (&z);
|
|
}
|
|
|
|
if (unlikely (zrc != Z_OK) || unlikely (z.avail_out != 0))
|
|
{
|
|
free (buf_out);
|
|
buf_out = NULL;
|
|
__libelf_seterrno (ELF_E_DECOMPRESS_ERROR);
|
|
}
|
|
|
|
inflateEnd(&z);
|
|
return buf_out;
|
|
}
|
|
|
|
void *
|
|
internal_function
|
|
__libelf_decompress_elf (Elf_Scn *scn, size_t *size_out, size_t *addralign)
|
|
{
|
|
GElf_Chdr chdr;
|
|
if (gelf_getchdr (scn, &chdr) == NULL)
|
|
return NULL;
|
|
|
|
if (chdr.ch_type != ELFCOMPRESS_ZLIB)
|
|
{
|
|
__libelf_seterrno (ELF_E_UNKNOWN_COMPRESSION_TYPE);
|
|
return NULL;
|
|
}
|
|
|
|
if (! powerof2 (chdr.ch_addralign))
|
|
{
|
|
__libelf_seterrno (ELF_E_INVALID_ALIGN);
|
|
return NULL;
|
|
}
|
|
|
|
/* Take the in-memory representation, so we can even handle a
|
|
section that has just been constructed (maybe it was copied
|
|
over from some other ELF file first with elf_newdata). This
|
|
is slightly inefficient when the raw data needs to be
|
|
converted since then we'll be converting the whole buffer and
|
|
not just Chdr. */
|
|
Elf_Data *data = elf_getdata (scn, NULL);
|
|
if (data == NULL)
|
|
return NULL;
|
|
|
|
int elfclass = scn->elf->class;
|
|
size_t hsize = (elfclass == ELFCLASS32
|
|
? sizeof (Elf32_Chdr) : sizeof (Elf64_Chdr));
|
|
size_t size_in = data->d_size - hsize;
|
|
void *buf_in = data->d_buf + hsize;
|
|
void *buf_out = __libelf_decompress (buf_in, size_in, chdr.ch_size);
|
|
*size_out = chdr.ch_size;
|
|
*addralign = chdr.ch_addralign;
|
|
return buf_out;
|
|
}
|
|
|
|
/* Assumes buf is a malloced buffer. */
|
|
void
|
|
internal_function
|
|
__libelf_reset_rawdata (Elf_Scn *scn, void *buf, size_t size, size_t align,
|
|
Elf_Type type)
|
|
{
|
|
/* This is the new raw data, replace and possibly free old data. */
|
|
scn->rawdata.d.d_off = 0;
|
|
scn->rawdata.d.d_version = EV_CURRENT;
|
|
scn->rawdata.d.d_buf = buf;
|
|
scn->rawdata.d.d_size = size;
|
|
scn->rawdata.d.d_align = align;
|
|
scn->rawdata.d.d_type = type;
|
|
|
|
/* Existing existing data is no longer valid. */
|
|
scn->data_list_rear = NULL;
|
|
if (scn->data_base != scn->rawdata_base)
|
|
free (scn->data_base);
|
|
scn->data_base = NULL;
|
|
if (scn->elf->map_address == NULL
|
|
|| scn->rawdata_base == scn->zdata_base
|
|
|| (scn->flags & ELF_F_MALLOCED) != 0)
|
|
free (scn->rawdata_base);
|
|
|
|
scn->rawdata_base = buf;
|
|
scn->flags |= ELF_F_MALLOCED;
|
|
|
|
/* Pretend we (tried to) read the data from the file and setup the
|
|
data (might have to convert the Chdr to native format). */
|
|
scn->data_read = 1;
|
|
scn->flags |= ELF_F_FILEDATA;
|
|
__libelf_set_data_list_rdlock (scn, 1);
|
|
}
|
|
|
|
int
|
|
elf_compress (Elf_Scn *scn, int type, unsigned int flags)
|
|
{
|
|
if (scn == NULL)
|
|
return -1;
|
|
|
|
if ((flags & ~ELF_CHF_FORCE) != 0)
|
|
{
|
|
__libelf_seterrno (ELF_E_INVALID_OPERAND);
|
|
return -1;
|
|
}
|
|
|
|
bool force = (flags & ELF_CHF_FORCE) != 0;
|
|
|
|
Elf *elf = scn->elf;
|
|
GElf_Ehdr ehdr;
|
|
if (gelf_getehdr (elf, &ehdr) == NULL)
|
|
return -1;
|
|
|
|
int elfclass = elf->class;
|
|
int elfdata = ehdr.e_ident[EI_DATA];
|
|
|
|
Elf64_Xword sh_flags;
|
|
Elf64_Word sh_type;
|
|
Elf64_Xword sh_addralign;
|
|
if (elfclass == ELFCLASS32)
|
|
{
|
|
Elf32_Shdr *shdr = elf32_getshdr (scn);
|
|
if (shdr == NULL)
|
|
return -1;
|
|
|
|
sh_flags = shdr->sh_flags;
|
|
sh_type = shdr->sh_type;
|
|
sh_addralign = shdr->sh_addralign;
|
|
}
|
|
else
|
|
{
|
|
Elf64_Shdr *shdr = elf64_getshdr (scn);
|
|
if (shdr == NULL)
|
|
return -1;
|
|
|
|
sh_flags = shdr->sh_flags;
|
|
sh_type = shdr->sh_type;
|
|
sh_addralign = shdr->sh_addralign;
|
|
}
|
|
|
|
if ((sh_flags & SHF_ALLOC) != 0)
|
|
{
|
|
__libelf_seterrno (ELF_E_INVALID_SECTION_FLAGS);
|
|
return -1;
|
|
}
|
|
|
|
if (sh_type == SHT_NULL || sh_type == SHT_NOBITS)
|
|
{
|
|
__libelf_seterrno (ELF_E_INVALID_SECTION_TYPE);
|
|
return -1;
|
|
}
|
|
|
|
int compressed = (sh_flags & SHF_COMPRESSED);
|
|
if (type == ELFCOMPRESS_ZLIB)
|
|
{
|
|
/* Compress/Deflate. */
|
|
if (compressed == 1)
|
|
{
|
|
__libelf_seterrno (ELF_E_ALREADY_COMPRESSED);
|
|
return -1;
|
|
}
|
|
|
|
size_t hsize = (elfclass == ELFCLASS32
|
|
? sizeof (Elf32_Chdr) : sizeof (Elf64_Chdr));
|
|
size_t orig_size, orig_addralign, new_size;
|
|
void *out_buf = __libelf_compress (scn, hsize, elfdata,
|
|
&orig_size, &orig_addralign,
|
|
&new_size, force);
|
|
|
|
/* Compression would make section larger, don't change anything. */
|
|
if (out_buf == (void *) -1)
|
|
return 0;
|
|
|
|
/* Compression failed, return error. */
|
|
if (out_buf == NULL)
|
|
return -1;
|
|
|
|
/* Put the header in front of the data. */
|
|
if (elfclass == ELFCLASS32)
|
|
{
|
|
Elf32_Chdr chdr;
|
|
chdr.ch_type = ELFCOMPRESS_ZLIB;
|
|
chdr.ch_size = orig_size;
|
|
chdr.ch_addralign = orig_addralign;
|
|
if (elfdata != MY_ELFDATA)
|
|
{
|
|
CONVERT (chdr.ch_type);
|
|
CONVERT (chdr.ch_size);
|
|
CONVERT (chdr.ch_addralign);
|
|
}
|
|
memcpy (out_buf, &chdr, sizeof (Elf32_Chdr));
|
|
}
|
|
else
|
|
{
|
|
Elf64_Chdr chdr;
|
|
chdr.ch_type = ELFCOMPRESS_ZLIB;
|
|
chdr.ch_reserved = 0;
|
|
chdr.ch_size = orig_size;
|
|
chdr.ch_addralign = sh_addralign;
|
|
if (elfdata != MY_ELFDATA)
|
|
{
|
|
CONVERT (chdr.ch_type);
|
|
CONVERT (chdr.ch_reserved);
|
|
CONVERT (chdr.ch_size);
|
|
CONVERT (chdr.ch_addralign);
|
|
}
|
|
memcpy (out_buf, &chdr, sizeof (Elf64_Chdr));
|
|
}
|
|
|
|
/* Note we keep the sh_entsize as is, we assume it is setup
|
|
correctly and ignored when SHF_COMPRESSED is set. */
|
|
if (elfclass == ELFCLASS32)
|
|
{
|
|
Elf32_Shdr *shdr = elf32_getshdr (scn);
|
|
shdr->sh_size = new_size;
|
|
shdr->sh_addralign = __libelf_type_align (ELFCLASS32, ELF_T_CHDR);
|
|
shdr->sh_flags |= SHF_COMPRESSED;
|
|
}
|
|
else
|
|
{
|
|
Elf64_Shdr *shdr = elf64_getshdr (scn);
|
|
shdr->sh_size = new_size;
|
|
shdr->sh_addralign = __libelf_type_align (ELFCLASS64, ELF_T_CHDR);
|
|
shdr->sh_flags |= SHF_COMPRESSED;
|
|
}
|
|
|
|
__libelf_reset_rawdata (scn, out_buf, new_size, 1, ELF_T_CHDR);
|
|
|
|
/* The section is now compressed, we could keep the uncompressed
|
|
data around, but since that might have been multiple Elf_Data
|
|
buffers let the user uncompress it explicitly again if they
|
|
want it to simplify bookkeeping. */
|
|
scn->zdata_base = NULL;
|
|
|
|
return 1;
|
|
}
|
|
else if (type == 0)
|
|
{
|
|
/* Decompress/Inflate. */
|
|
if (compressed == 0)
|
|
{
|
|
__libelf_seterrno (ELF_E_NOT_COMPRESSED);
|
|
return -1;
|
|
}
|
|
|
|
/* If the data is already decompressed (by elf_strptr), then we
|
|
only need to setup the rawdata and section header. XXX what
|
|
about elf_newdata? */
|
|
if (scn->zdata_base == NULL)
|
|
{
|
|
size_t size_out, addralign;
|
|
void *buf_out = __libelf_decompress_elf (scn, &size_out, &addralign);
|
|
if (buf_out == NULL)
|
|
return -1;
|
|
|
|
scn->zdata_base = buf_out;
|
|
scn->zdata_size = size_out;
|
|
scn->zdata_align = addralign;
|
|
}
|
|
|
|
/* Note we keep the sh_entsize as is, we assume it is setup
|
|
correctly and ignored when SHF_COMPRESSED is set. */
|
|
if (elfclass == ELFCLASS32)
|
|
{
|
|
Elf32_Shdr *shdr = elf32_getshdr (scn);
|
|
shdr->sh_size = scn->zdata_size;
|
|
shdr->sh_addralign = scn->zdata_align;
|
|
shdr->sh_flags &= ~SHF_COMPRESSED;
|
|
}
|
|
else
|
|
{
|
|
Elf64_Shdr *shdr = elf64_getshdr (scn);
|
|
shdr->sh_size = scn->zdata_size;
|
|
shdr->sh_addralign = scn->zdata_align;
|
|
shdr->sh_flags &= ~SHF_COMPRESSED;
|
|
}
|
|
|
|
__libelf_reset_rawdata (scn, scn->zdata_base,
|
|
scn->zdata_size, scn->zdata_align,
|
|
__libelf_data_type (&ehdr, sh_type,
|
|
scn->zdata_align));
|
|
|
|
return 1;
|
|
}
|
|
else
|
|
{
|
|
__libelf_seterrno (ELF_E_UNKNOWN_COMPRESSION_TYPE);
|
|
return -1;
|
|
}
|
|
}
|