xref: /openbsd/usr.bin/ctfconv/elf.c (revision 8d2c6414)
1 /*	$OpenBSD: elf.c,v 1.10 2022/08/14 14:54:13 millert Exp $ */
2 
3 /*
4  * Copyright (c) 2016 Martin Pieuchot <mpi@openbsd.org>
5  *
6  * Permission to use, copy, modify, and distribute this software for any
7  * purpose with or without fee is hereby granted, provided that the above
8  * copyright notice and this permission notice appear in all copies.
9  *
10  * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
11  * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
12  * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
13  * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
14  * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
15  * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
16  * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
17  */
18 
19 #include <sys/types.h>
20 
21 #include <machine/reloc.h>
22 
23 #include <assert.h>
24 #include <elf.h>
25 #include <err.h>
26 #include <string.h>
27 
28 static int	elf_reloc_size(unsigned long);
29 static void	elf_reloc_apply(const char *, size_t, const char *, size_t,
30 		    ssize_t, char *, size_t);
31 
32 int
iself(const char * p,size_t filesize)33 iself(const char *p, size_t filesize)
34 {
35 	Elf_Ehdr		*eh = (Elf_Ehdr *)p;
36 
37 	if (filesize < sizeof(Elf_Ehdr)) {
38 		warnx("file too small to be ELF");
39 		return 0;
40 	}
41 
42 	if (eh->e_ehsize < sizeof(Elf_Ehdr) || !IS_ELF(*eh))
43 		return 0;
44 
45 	if (eh->e_ident[EI_CLASS] != ELFCLASS) {
46 		warnx("unexpected word size %u", eh->e_ident[EI_CLASS]);
47 		return 0;
48 	}
49 	if (eh->e_ident[EI_VERSION] != ELF_TARG_VER) {
50 		warnx("unexpected version %u", eh->e_ident[EI_VERSION]);
51 		return 0;
52 	}
53 	if (eh->e_ident[EI_DATA] >= ELFDATANUM) {
54 		warnx("unexpected data format %u", eh->e_ident[EI_DATA]);
55 		return 0;
56 	}
57 	if (eh->e_shoff > filesize) {
58 		warnx("bogus section table offset 0x%llx",
59 		    (unsigned long long)eh->e_shoff);
60 		return 0;
61 	}
62 	if (eh->e_shentsize < sizeof(Elf_Shdr)) {
63 		warnx("bogus section header size %u", eh->e_shentsize);
64 		return 0;
65 	}
66 	if (eh->e_shnum > (filesize - eh->e_shoff) / eh->e_shentsize) {
67 		warnx("bogus section header count %u", eh->e_shnum);
68 		return 0;
69 	}
70 	if (eh->e_shstrndx >= eh->e_shnum) {
71 		warnx("bogus string table index %u", eh->e_shstrndx);
72 		return 0;
73 	}
74 
75 	return 1;
76 }
77 
78 int
elf_getshstab(const char * p,size_t filesize,const char ** shstab,size_t * shstabsize)79 elf_getshstab(const char *p, size_t filesize, const char **shstab,
80     size_t *shstabsize)
81 {
82 	Elf_Ehdr		*eh = (Elf_Ehdr *)p;
83 	Elf_Shdr		*sh;
84 	size_t			 shoff;
85 
86 	shoff = eh->e_shoff + eh->e_shstrndx * eh->e_shentsize;
87 	if (shoff > (filesize - sizeof(*sh))) {
88 		warnx("unexpected string table size");
89 		return -1;
90 	}
91 
92 	sh = (Elf_Shdr *)(p + shoff);
93 	if (sh->sh_type != SHT_STRTAB) {
94 		warnx("unexpected string table type");
95 		return -1;
96 	}
97 	if (sh->sh_offset > filesize) {
98 		warnx("bogus string table offset");
99 		return -1;
100 	}
101 	if (sh->sh_size > filesize - sh->sh_offset) {
102 		warnx("bogus string table size");
103 		return -1;
104 	}
105 	if (shstab != NULL)
106 		*shstab = p + sh->sh_offset;
107 	if (shstabsize != NULL)
108 		*shstabsize = sh->sh_size;
109 
110 	return 0;
111 }
112 
113 ssize_t
elf_getsymtab(const char * p,size_t filesize,const char * shstab,size_t shstabsz,const Elf_Sym ** symtab,size_t * nsymb,const char ** strtab,size_t * strtabsz)114 elf_getsymtab(const char *p, size_t filesize, const char *shstab,
115     size_t shstabsz, const Elf_Sym **symtab, size_t *nsymb, const char **strtab,
116     size_t *strtabsz)
117 {
118 	Elf_Ehdr	*eh = (Elf_Ehdr *)p;
119 	Elf_Shdr	*sh, *symsh;
120 	size_t		 snlen, shoff;
121 	ssize_t		 i;
122 
123 	snlen = strlen(ELF_SYMTAB);
124 	symsh = NULL;
125 
126 	for (i = 0; i < eh->e_shnum; i++) {
127 		shoff = eh->e_shoff + i * eh->e_shentsize;
128 		if (shoff > (filesize - sizeof(*sh)))
129 			continue;
130 
131 		sh = (Elf_Shdr *)(p + shoff);
132 		if (sh->sh_type != SHT_SYMTAB)
133 			continue;
134 
135 		if ((sh->sh_link >= eh->e_shnum) || (sh->sh_name >= shstabsz))
136 			continue;
137 
138 		if (sh->sh_offset > filesize)
139 			continue;
140 
141 		if (sh->sh_size > (filesize - sh->sh_offset))
142 			continue;
143 
144 		if (sh->sh_entsize == 0)
145 			continue;
146 
147 		if (strncmp(shstab + sh->sh_name, ELF_SYMTAB, snlen) == 0) {
148 			if (symtab != NULL)
149 				*symtab = (Elf_Sym *)(p + sh->sh_offset);
150 			if (nsymb != NULL)
151 				*nsymb = (sh->sh_size / sh->sh_entsize);
152 			symsh = sh;
153 
154 			break;
155 		}
156 	}
157 
158 	if (symsh == NULL || (symsh->sh_link >= eh->e_shnum))
159 		return -1;
160 
161 	shoff = eh->e_shoff + symsh->sh_link * eh->e_shentsize;
162 	if (shoff > (filesize - sizeof(*sh)))
163 		return -1;
164 
165 	sh = (Elf_Shdr *)(p + shoff);
166 	if ((sh->sh_offset + sh->sh_size) > filesize)
167 		return -1;
168 
169 	if (strtab != NULL)
170 		*strtab = p + sh->sh_offset;
171 	if (strtabsz != NULL)
172 		*strtabsz = sh->sh_size;
173 
174 	return i;
175 }
176 
177 ssize_t
elf_getsection(char * p,size_t filesize,const char * sname,const char * shstab,size_t shstabsz,const char ** psdata,size_t * pssz)178 elf_getsection(char *p, size_t filesize, const char *sname, const char *shstab,
179     size_t shstabsz, const char **psdata, size_t *pssz)
180 {
181 	Elf_Ehdr	*eh = (Elf_Ehdr *)p;
182 	Elf_Shdr	*sh;
183 	char		*sdata = NULL;
184 	size_t		 snlen, shoff, ssz = 0;
185 	ssize_t		 sidx, i;
186 
187 	snlen = strlen(sname);
188 	if (snlen == 0)
189 		return -1;
190 
191 	/* Find the given section. */
192 	for (i = 0; i < eh->e_shnum; i++) {
193 		shoff = eh->e_shoff + i * eh->e_shentsize;
194 		if (shoff > (filesize - sizeof(*sh)))
195 			continue;
196 
197 		sh = (Elf_Shdr *)(p + shoff);
198 		if ((sh->sh_link >= eh->e_shnum) || (sh->sh_name >= shstabsz))
199 			continue;
200 
201 		if (sh->sh_offset > filesize)
202 			continue;
203 
204 		if (sh->sh_size > (filesize - sh->sh_offset))
205 			continue;
206 
207 		if (strncmp(shstab + sh->sh_name, sname, snlen) == 0) {
208 			sidx = i;
209 			sdata = p + sh->sh_offset;
210 			ssz = sh->sh_size;
211 			elf_reloc_apply(p, filesize, shstab, shstabsz, sidx,
212 			    sdata, ssz);
213 			break;
214 		}
215 	}
216 
217 	if (sdata == NULL)
218 		return -1;
219 
220 	if (psdata != NULL)
221 		*psdata = sdata;
222 	if (pssz != NULL)
223 		*pssz = ssz;
224 
225 	return sidx;
226 }
227 
228 static int
elf_reloc_size(unsigned long type)229 elf_reloc_size(unsigned long type)
230 {
231 	switch (type) {
232 #ifdef R_X86_64_64
233 	case R_X86_64_64:
234 		return sizeof(uint64_t);
235 #endif
236 #ifdef R_X86_64_32
237 	case R_X86_64_32:
238 		return sizeof(uint32_t);
239 #endif
240 #ifdef RELOC_32
241 	case RELOC_32:
242 		return sizeof(uint32_t);
243 #endif
244 	default:
245 		break;
246 	}
247 
248 	return -1;
249 }
250 
251 #define ELF_WRITE_RELOC(buf, val, rsize)				\
252 do {									\
253 	if (rsize == 4) {						\
254 		uint32_t v32 = val;					\
255 		memcpy(buf, &v32, sizeof(v32));				\
256 	} else {							\
257 		uint64_t v64 = val;					\
258 		memcpy(buf, &v64, sizeof(v64));				\
259 	}								\
260 } while (0)
261 
262 static void
elf_reloc_apply(const char * p,size_t filesize,const char * shstab,size_t shstabsz,ssize_t sidx,char * sdata,size_t ssz)263 elf_reloc_apply(const char *p, size_t filesize, const char *shstab,
264     size_t shstabsz, ssize_t sidx, char *sdata, size_t ssz)
265 {
266 	Elf_Ehdr	*eh = (Elf_Ehdr *)p;
267 	Elf_Shdr	*sh;
268 	Elf_Rel		*rel = NULL;
269 	Elf_RelA	*rela = NULL;
270 	const Elf_Sym	*symtab, *sym;
271 	ssize_t		 symtabidx;
272 	size_t		 nsymb, rsym, rtyp, roff;
273 	size_t		 shoff, i, j;
274 	uint64_t	 value;
275 	int		 rsize;
276 
277 	/* Find symbol table location and number of symbols. */
278 	symtabidx = elf_getsymtab(p, filesize, shstab, shstabsz, &symtab,
279 	    &nsymb, NULL, NULL);
280 	if (symtabidx == -1) {
281 		warnx("symbol table not found");
282 		return;
283 	}
284 
285 	/* Apply possible relocation. */
286 	for (i = 0; i < eh->e_shnum; i++) {
287 		shoff = eh->e_shoff + i * eh->e_shentsize;
288 		if (shoff > (filesize - sizeof(*sh)))
289 			continue;
290 
291 		sh = (Elf_Shdr *)(p + shoff);
292 		if (sh->sh_size == 0)
293 			continue;
294 
295 		if ((sh->sh_info != sidx) || (sh->sh_link != symtabidx))
296 			continue;
297 
298 		if (sh->sh_offset > filesize)
299 			continue;
300 
301 		if (sh->sh_size > (filesize - sh->sh_offset))
302 			continue;
303 
304 		switch (sh->sh_type) {
305 		case SHT_RELA:
306 			rela = (Elf_RelA *)(p + sh->sh_offset);
307 			for (j = 0; j < (sh->sh_size / sizeof(Elf_RelA)); j++) {
308 				rsym = ELF_R_SYM(rela[j].r_info);
309 				rtyp = ELF_R_TYPE(rela[j].r_info);
310 				roff = rela[j].r_offset;
311 				if (rsym >= nsymb)
312 					continue;
313 				if (roff >= filesize)
314 					continue;
315 				sym = &symtab[rsym];
316 				value = sym->st_value + rela[j].r_addend;
317 
318 				rsize = elf_reloc_size(rtyp);
319 				if (rsize == -1 || roff + rsize >= ssz)
320 					continue;
321 
322 				ELF_WRITE_RELOC(sdata + roff, value, rsize);
323 			}
324 			break;
325 		case SHT_REL:
326 			rel = (Elf_Rel *)(p + sh->sh_offset);
327 			for (j = 0; j < (sh->sh_size / sizeof(Elf_Rel)); j++) {
328 				rsym = ELF_R_SYM(rel[j].r_info);
329 				rtyp = ELF_R_TYPE(rel[j].r_info);
330 				roff = rel[j].r_offset;
331 				if (rsym >= nsymb)
332 					continue;
333 				if (roff >= filesize)
334 					continue;
335 				sym = &symtab[rsym];
336 				value = sym->st_value;
337 
338 				rsize = elf_reloc_size(rtyp);
339 				if (rsize == -1 || roff + rsize >= ssz)
340 					continue;
341 
342 				ELF_WRITE_RELOC(sdata + roff, value, rsize);
343 			}
344 			break;
345 		default:
346 			continue;
347 		}
348 	}
349 }
350