#ifdef HAVE_CONFIG_H
# include <config.h>
#endif
#include <libelf.h>
#include "libelfP.h"
#include "common.h"
#include <stddef.h>
#include <stdlib.h>
#include <string.h>
#include <zlib.h>
#ifdef USE_ZSTD
#include <zstd.h>
#endif
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)
static
void *
__libelf_compress_zlib (Elf_Scn *scn, size_t hsize, int ei_data,
size_t *orig_size, size_t *orig_addralign,
size_t *new_size, bool force,
Elf_Data *data, Elf_Data *next_data,
void *out_buf, size_t out_size, size_t block)
{
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;
int flush = Z_NO_FLUSH;
do
{
cdata = *data;
bool convert = ei_data != MY_ELFDATA && data->d_size > 0;
if (convert)
{
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;
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;
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;
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);
if (convert)
{
free (cdata.d_buf);
cdata.d_buf = NULL;
}
}
while (flush != Z_FINISH);
if (zrc != Z_STREAM_END)
{
__libelf_seterrno (ELF_E_COMPRESS_ERROR);
return deflate_cleanup (NULL, NULL);
}
deflateEnd (&z);
*new_size = used;
return out_buf;
}
#ifdef USE_ZSTD_COMPRESS
static void *
do_zstd_cleanup (void *result, ZSTD_CCtx * const cctx, void *out_buf,
Elf_Data *cdatap)
{
ZSTD_freeCCtx (cctx);
free (out_buf);
if (cdatap != NULL)
free (cdatap->d_buf);
return result;
}
#define zstd_cleanup(result, cdata) \
do_zstd_cleanup(result, cctx, out_buf, cdata)
static
void *
__libelf_compress_zstd (Elf_Scn *scn, size_t hsize, int ei_data,
size_t *orig_size, size_t *orig_addralign,
size_t *new_size, bool force,
Elf_Data *data, Elf_Data *next_data,
void *out_buf, size_t out_size, size_t block)
{
size_t used = hsize;
ZSTD_CCtx* const cctx = ZSTD_createCCtx();
Elf_Data cdata;
cdata.d_buf = NULL;
ZSTD_EndDirective mode = ZSTD_e_continue;
do
{
cdata = *data;
bool convert = ei_data != MY_ELFDATA && data->d_size > 0;
if (convert)
{
cdata.d_buf = malloc (data->d_size);
if (cdata.d_buf == NULL)
{
__libelf_seterrno (ELF_E_NOMEM);
return zstd_cleanup (NULL, NULL);
}
if (gelf_xlatetof (scn->elf, &cdata, data, ei_data) == NULL)
return zstd_cleanup (NULL, &cdata);
}
ZSTD_inBuffer ib = { cdata.d_buf, cdata.d_size, 0 };
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
mode = ZSTD_e_end;
for (;;)
{
ZSTD_outBuffer ob = { out_buf + used, out_size - used, 0 };
size_t ret = ZSTD_compressStream2 (cctx, &ob, &ib, mode);
if (ZSTD_isError (ret))
{
__libelf_seterrno (ELF_E_COMPRESS_ERROR);
return zstd_cleanup (NULL, convert ? &cdata : NULL);
}
used += ob.pos;
if (!force && mode == ZSTD_e_end && used >= *orig_size)
return zstd_cleanup ((void *) -1, convert ? &cdata : NULL);
if (ret > 0)
{
void *bigger = realloc (out_buf, out_size + block);
if (bigger == NULL)
{
__libelf_seterrno (ELF_E_NOMEM);
return zstd_cleanup (NULL, convert ? &cdata : NULL);
}
out_buf = bigger;
out_size += block;
}
else
break;
}
if (convert)
{
free (cdata.d_buf);
cdata.d_buf = NULL;
}
}
while (mode != ZSTD_e_end);
ZSTD_freeCCtx (cctx);
*new_size = used;
return out_buf;
}
#endif
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, bool use_zstd)
{
Elf_Data *data = elf_getdata (scn, NULL);
if (data == NULL)
return NULL;
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;
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;
}
if (use_zstd)
{
#ifdef USE_ZSTD_COMPRESS
return __libelf_compress_zstd (scn, hsize, ei_data, orig_size,
orig_addralign, new_size, force,
data, next_data, out_buf, out_size,
block);
#else
__libelf_seterrno (ELF_E_UNKNOWN_COMPRESSION_TYPE);
return NULL;
#endif
}
else
return __libelf_compress_zlib (scn, hsize, ei_data, orig_size,
orig_addralign, new_size, force,
data, next_data, out_buf, out_size,
block);
}
void *
internal_function
__libelf_decompress_zlib (void *buf_in, size_t size_in, size_t size_out)
{
if (unlikely (size_out / 1032 > size_in))
{
__libelf_seterrno (ELF_E_INVALID_DATA);
return NULL;
}
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;
}
#ifdef USE_ZSTD
static void *
__libelf_decompress_zstd (void *buf_in, size_t size_in, size_t size_out)
{
void *buf_out = malloc (size_out ?: 1);
if (unlikely (buf_out == NULL))
{
__libelf_seterrno (ELF_E_NOMEM);
return NULL;
}
size_t ret = ZSTD_decompress (buf_out, size_out, buf_in, size_in);
if (unlikely (ZSTD_isError (ret)) || unlikely (ret != size_out))
{
free (buf_out);
__libelf_seterrno (ELF_E_DECOMPRESS_ERROR);
return NULL;
}
else
return buf_out;
}
#endif
void *
internal_function
__libelf_decompress (int chtype, void *buf_in, size_t size_in, size_t size_out)
{
if (chtype == ELFCOMPRESS_ZLIB)
return __libelf_decompress_zlib (buf_in, size_in, size_out);
else
{
#ifdef USE_ZSTD
return __libelf_decompress_zstd (buf_in, size_in, size_out);
#else
__libelf_seterrno (ELF_E_UNKNOWN_COMPRESSION_TYPE);
return NULL;
#endif
}
}
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;
bool unknown_compression = false;
if (chdr.ch_type != ELFCOMPRESS_ZLIB)
{
if (chdr.ch_type != ELFCOMPRESS_ZSTD)
unknown_compression = true;
#ifndef USE_ZSTD
if (chdr.ch_type == ELFCOMPRESS_ZSTD)
unknown_compression = true;
#endif
}
if (unknown_compression)
{
__libelf_seterrno (ELF_E_UNKNOWN_COMPRESSION_TYPE);
return NULL;
}
if (! powerof2 (chdr.ch_addralign))
{
__libelf_seterrno (ELF_E_INVALID_ALIGN);
return NULL;
}
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 (chdr.ch_type, buf_in, size_in, chdr.ch_size);
*size_out = chdr.ch_size;
*addralign = chdr.ch_addralign;
return buf_out;
}
void
internal_function
__libelf_reset_rawdata (Elf_Scn *scn, void *buf, size_t size, size_t align,
Elf_Type type)
{
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;
scn->data_list_rear = NULL;
if (scn->data_base != scn->rawdata_base)
free (scn->data_base);
scn->data_base = NULL;
if (scn->zdata_base != buf
&& scn->zdata_base != scn->rawdata_base)
{
free (scn->zdata_base);
scn->zdata_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 = NULL;
scn->zdata_base = NULL;
}
scn->rawdata_base = buf;
scn->flags |= ELF_F_MALLOCED;
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 || type == ELFCOMPRESS_ZSTD)
{
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,
type == ELFCOMPRESS_ZSTD);
if (out_buf == (void *) -1)
return 0;
if (out_buf == NULL)
return -1;
if (elfclass == ELFCLASS32)
{
Elf32_Chdr chdr;
chdr.ch_type = type;
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 = type;
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));
}
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);
free (scn->zdata_base);
scn->zdata_base = NULL;
return 1;
}
else if (type == 0)
{
if (compressed == 0)
{
__libelf_seterrno (ELF_E_NOT_COMPRESSED);
return -1;
}
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;
}
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;
}
}