| /* 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; |
| } |
| } |