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
37 int
elf_compress_gnu(Elf_Scn * scn,int inflate,unsigned int flags)38 elf_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 /* Allocated sections, or sections that are already are compressed
84 cannot (also) be GNU compressed. */
85 if ((sh_flags & SHF_ALLOC) != 0 || (sh_flags & SHF_COMPRESSED))
86 {
87 __libelf_seterrno (ELF_E_INVALID_SECTION_FLAGS);
88 return -1;
89 }
90
91 if (sh_type == SHT_NULL || sh_type == SHT_NOBITS)
92 {
93 __libelf_seterrno (ELF_E_INVALID_SECTION_TYPE);
94 return -1;
95 }
96
97 /* For GNU compression we cannot really know whether the section is
98 already compressed or not. Just try and see what happens... */
99 // int compressed = (sh_flags & SHF_COMPRESSED);
100 if (inflate == 1)
101 {
102 size_t hsize = 4 + 8; /* GNU "ZLIB" + 8 byte size. */
103 size_t orig_size, new_size, orig_addralign;
104 void *out_buf = __libelf_compress (scn, hsize, elfdata,
105 &orig_size, &orig_addralign,
106 &new_size, force);
107
108 /* Compression would make section larger, don't change anything. */
109 if (out_buf == (void *) -1)
110 return 0;
111
112 /* Compression failed, return error. */
113 if (out_buf == NULL)
114 return -1;
115
116 uint64_t be64_size = htobe64 (orig_size);
117 memmove (out_buf, "ZLIB", 4);
118 memmove (out_buf + 4, &be64_size, sizeof (be64_size));
119
120 /* We don't know anything about sh_entsize, sh_addralign and
121 sh_flags won't have a SHF_COMPRESSED hint in the GNU format.
122 Just adjust the sh_size. */
123 if (elfclass == ELFCLASS32)
124 {
125 Elf32_Shdr *shdr = elf32_getshdr (scn);
126 shdr->sh_size = new_size;
127 }
128 else
129 {
130 Elf64_Shdr *shdr = elf64_getshdr (scn);
131 shdr->sh_size = new_size;
132 }
133
134 __libelf_reset_rawdata (scn, out_buf, new_size, 1, ELF_T_BYTE);
135
136 /* The section is now compressed, we could keep the uncompressed
137 data around, but since that might have been multiple Elf_Data
138 buffers let the user uncompress it explicitly again if they
139 want it to simplify bookkeeping. */
140 scn->zdata_base = NULL;
141
142 return 1;
143 }
144 else if (inflate == 0)
145 {
146 /* In theory the user could have constructed a compressed section
147 by hand. And in practice they do. For example when copying
148 a section from one file to another using elf_newdata. So we
149 have to use elf_getdata (not elf_rawdata). */
150 Elf_Data *data = elf_getdata (scn, NULL);
151 if (data == NULL)
152 return -1;
153
154 size_t hsize = 4 + 8; /* GNU "ZLIB" + 8 byte size. */
155 if (data->d_size < hsize || memcmp (data->d_buf, "ZLIB", 4) != 0)
156 {
157 __libelf_seterrno (ELF_E_NOT_COMPRESSED);
158 return -1;
159 }
160
161 /* There is a 12-byte header of "ZLIB" followed by
162 an 8-byte big-endian size. There is only one type and
163 Alignment isn't preserved separately. */
164 uint64_t gsize;
165 memcpy (&gsize, data->d_buf + 4, sizeof gsize);
166 gsize = be64toh (gsize);
167
168 /* One more sanity check, size should be bigger than original
169 data size plus some overhead (4 chars ZLIB + 8 bytes size + 6
170 bytes zlib stream overhead + 5 bytes overhead max for one 16K
171 block) and should fit into a size_t. */
172 if (gsize + 4 + 8 + 6 + 5 < data->d_size || gsize > SIZE_MAX)
173 {
174 __libelf_seterrno (ELF_E_NOT_COMPRESSED);
175 return -1;
176 }
177
178 size_t size = gsize;
179 size_t size_in = data->d_size - hsize;
180 void *buf_in = data->d_buf + hsize;
181 void *buf_out = __libelf_decompress (buf_in, size_in, size);
182 if (buf_out == NULL)
183 return -1;
184
185 /* We don't know anything about sh_entsize, sh_addralign and
186 sh_flags won't have a SHF_COMPRESSED hint in the GNU format.
187 Just adjust the sh_size. */
188 if (elfclass == ELFCLASS32)
189 {
190 Elf32_Shdr *shdr = elf32_getshdr (scn);
191 shdr->sh_size = size;
192 }
193 else
194 {
195 Elf64_Shdr *shdr = elf64_getshdr (scn);
196 shdr->sh_size = size;
197 }
198
199 __libelf_reset_rawdata (scn, buf_out, size, sh_addralign,
200 __libelf_data_type (&ehdr, sh_type,
201 sh_addralign));
202
203 scn->zdata_base = buf_out;
204
205 return 1;
206 }
207 else
208 {
209 __libelf_seterrno (ELF_E_UNKNOWN_COMPRESSION_TYPE);
210 return -1;
211 }
212 }
213