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