1/* Compress or decompress a section.
2   Copyright (C) 2015 Red Hat, Inc.
3   This file is part of elfutils.
4
5   This file is free software; you can redistribute it and/or modify
6   it under the terms of either
7
8     * the GNU Lesser General Public License as published by the Free
9       Software Foundation; either version 3 of the License, or (at
10       your option) any later version
11
12   or
13
14     * the GNU General Public License as published by the Free
15       Software Foundation; either version 2 of the License, or (at
16       your option) any later version
17
18   or both in parallel, as here.
19
20   elfutils is distributed in the hope that it will be useful, but
21   WITHOUT ANY WARRANTY; without even the implied warranty of
22   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
23   General Public License for more details.
24
25   You should have received copies of the GNU General Public License and
26   the GNU Lesser General Public License along with this program.  If
27   not, see <http://www.gnu.org/licenses/>.  */
28
29#ifdef HAVE_CONFIG_H
30# include <config.h>
31#endif
32
33#include <libelf.h>
34#include "libelfP.h"
35#include "common.h"
36
37int
38elf_compress_gnu (Elf_Scn *scn, int inflate, unsigned int flags)
39{
40  if (scn == NULL)
41    return -1;
42
43  if ((flags & ~ELF_CHF_FORCE) != 0)
44    {
45      __libelf_seterrno (ELF_E_INVALID_OPERAND);
46      return -1;
47    }
48
49  bool force = (flags & ELF_CHF_FORCE) != 0;
50
51  Elf *elf = scn->elf;
52  GElf_Ehdr ehdr;
53  if (gelf_getehdr (elf, &ehdr) == NULL)
54    return -1;
55
56  int elfclass = elf->class;
57  int elfdata = ehdr.e_ident[EI_DATA];
58
59  Elf64_Xword sh_flags;
60  Elf64_Word sh_type;
61  Elf64_Xword sh_addralign;
62  if (elfclass == ELFCLASS32)
63    {
64      Elf32_Shdr *shdr = elf32_getshdr (scn);
65      if (shdr == NULL)
66	return -1;
67
68      sh_flags = shdr->sh_flags;
69      sh_type = shdr->sh_type;
70      sh_addralign = shdr->sh_addralign;
71    }
72  else
73    {
74      Elf64_Shdr *shdr = elf64_getshdr (scn);
75      if (shdr == NULL)
76	return -1;
77
78      sh_flags = shdr->sh_flags;
79      sh_type = shdr->sh_type;
80      sh_addralign = shdr->sh_addralign;
81    }
82
83  if ((sh_flags & SHF_ALLOC) != 0)
84    {
85      __libelf_seterrno (ELF_E_INVALID_SECTION_FLAGS);
86      return -1;
87    }
88
89  if (sh_type == SHT_NULL || sh_type == SHT_NOBITS)
90    {
91      __libelf_seterrno (ELF_E_INVALID_SECTION_TYPE);
92      return -1;
93    }
94
95  /* For GNU compression we cannot really know whether the section is
96     already compressed or not.  Just try and see what happens...  */
97  // int compressed = (sh_flags & SHF_COMPRESSED);
98  if (inflate == 1)
99    {
100      size_t hsize = 4 + 8; /* GNU "ZLIB" + 8 byte size.  */
101      size_t orig_size, new_size, orig_addralign;
102      void *out_buf = __libelf_compress (scn, hsize, elfdata,
103					 &orig_size, &orig_addralign,
104					 &new_size, force);
105
106      /* Compression would make section larger, don't change anything.  */
107      if (out_buf == (void *) -1)
108	return 0;
109
110      /* Compression failed, return error.  */
111      if (out_buf == NULL)
112	return -1;
113
114      uint64_t be64_size = htobe64 (orig_size);
115      memmove (out_buf, "ZLIB", 4);
116      memmove (out_buf + 4, &be64_size, sizeof (be64_size));
117
118      /* We don't know anything about sh_entsize, sh_addralign and
119	 sh_flags won't have a SHF_COMPRESSED hint in the GNU format.
120	 Just adjust the sh_size.  */
121      if (elfclass == ELFCLASS32)
122	{
123	  Elf32_Shdr *shdr = elf32_getshdr (scn);
124	  shdr->sh_size = new_size;
125	}
126      else
127	{
128	  Elf64_Shdr *shdr = elf64_getshdr (scn);
129	  shdr->sh_size = new_size;
130	}
131
132      __libelf_reset_rawdata (scn, out_buf, new_size, 1, ELF_T_BYTE);
133
134      /* The section is now compressed, we could keep the uncompressed
135	 data around, but since that might have been multiple Elf_Data
136	 buffers let the user uncompress it explicitly again if they
137	 want it to simplify bookkeeping.  */
138      scn->zdata_base = NULL;
139
140      return 1;
141    }
142  else if (inflate == 0)
143    {
144      /* In theory the user could have constucted a compressed section
145	 by hand.  But we always just take the rawdata directly and
146	 decompress that.  */
147      Elf_Data *data = elf_rawdata (scn, NULL);
148      if (data == NULL)
149	return -1;
150
151      size_t hsize = 4 + 8; /* GNU "ZLIB" + 8 byte size.  */
152      if (data->d_size < hsize || memcmp (data->d_buf, "ZLIB", 4) != 0)
153	{
154          __libelf_seterrno (ELF_E_NOT_COMPRESSED);
155	  return -1;
156	}
157
158      /* There is a 12-byte header of "ZLIB" followed by
159	 an 8-byte big-endian size.  There is only one type and
160	 Alignment isn't preserved separately.  */
161      uint64_t gsize;
162      memcpy (&gsize, data->d_buf + 4, sizeof gsize);
163      gsize = be64toh (gsize);
164
165      /* One more sanity check, size should be bigger than original
166	 data size plus some overhead (4 chars ZLIB + 8 bytes size + 6
167	 bytes zlib stream overhead + 5 bytes overhead max for one 16K
168	 block) and should fit into a size_t.  */
169      if (gsize + 4 + 8 + 6 + 5 < data->d_size || gsize > SIZE_MAX)
170	{
171	  __libelf_seterrno (ELF_E_NOT_COMPRESSED);
172	  return -1;
173	}
174
175      size_t size = gsize;
176      size_t size_in = data->d_size - hsize;
177      void *buf_in = data->d_buf + hsize;
178      void *buf_out = __libelf_decompress (buf_in, size_in, size);
179      if (buf_out == NULL)
180	return -1;
181
182      /* We don't know anything about sh_entsize, sh_addralign and
183	 sh_flags won't have a SHF_COMPRESSED hint in the GNU format.
184	 Just adjust the sh_size.  */
185      if (elfclass == ELFCLASS32)
186	{
187	  Elf32_Shdr *shdr = elf32_getshdr (scn);
188	  shdr->sh_size = size;
189	}
190      else
191	{
192	  Elf64_Shdr *shdr = elf64_getshdr (scn);
193	  shdr->sh_size = size;
194	}
195
196      __libelf_reset_rawdata (scn, buf_out, size, sh_addralign,
197			      __libelf_data_type (elf, sh_type));
198
199      scn->zdata_base = buf_out;
200
201      return 1;
202    }
203  else
204    {
205      __libelf_seterrno (ELF_E_UNKNOWN_COMPRESSION_TYPE);
206      return -1;
207    }
208}
209