xref: /freebsd/lib/libkldelf/ef.c (revision bd66c1b43e33540205dbc1187c2f2a15c58b57ba)
1 /*-
2  * SPDX-License-Identifier: BSD-4-Clause
3  *
4  * Copyright (c) 2000, Boris Popov
5  * All rights reserved.
6  *
7  * Redistribution and use in source and binary forms, with or without
8  * modification, are permitted provided that the following conditions
9  * are met:
10  * 1. Redistributions of source code must retain the above copyright
11  *    notice, this list of conditions and the following disclaimer.
12  * 2. Redistributions in binary form must reproduce the above copyright
13  *    notice, this list of conditions and the following disclaimer in the
14  *    documentation and/or other materials provided with the distribution.
15  * 3. All advertising materials mentioning features or use of this software
16  *    must display the following acknowledgement:
17  *    This product includes software developed by Boris Popov.
18  * 4. Neither the name of the author nor the names of any co-contributors
19  *    may be used to endorse or promote products derived from this software
20  *    without specific prior written permission.
21  *
22  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
23  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
24  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
25  * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
26  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
27  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
28  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
29  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
30  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
31  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
32  * SUCH DAMAGE.
33  */
34 
35 #include <sys/param.h>
36 
37 #include <err.h>
38 #include <errno.h>
39 #include <gelf.h>
40 #include <stdio.h>
41 #include <stdlib.h>
42 #include <string.h>
43 
44 #include "kldelf.h"
45 
46 #define	MAXSEGS 16
47 struct ef_file {
48 	char		*ef_name;
49 	struct elf_file *ef_efile;
50 	GElf_Phdr	*ef_ph;
51 	void		*ef_fpage;		/* First block of the file */
52 	int		ef_fplen;		/* length of first block */
53 	GElf_Hashelt	ef_nbuckets;
54 	GElf_Hashelt	ef_nchains;
55 	GElf_Hashelt	*ef_buckets;
56 	GElf_Hashelt	*ef_chains;
57 	GElf_Hashelt	*ef_hashtab;
58 	caddr_t		ef_strtab;
59 	long		ef_strsz;
60 	GElf_Sym	*ef_symtab;
61 	int		ef_nsegs;
62 	GElf_Phdr	*ef_segs[MAXSEGS];
63 	int		ef_verbose;
64 	GElf_Rel	*ef_rel;		/* relocation table */
65 	long		ef_relsz;		/* number of entries */
66 	GElf_Rela	*ef_rela;		/* relocation table */
67 	long		ef_relasz;		/* number of entries */
68 };
69 
70 static void	ef_print_phdr(GElf_Phdr *);
71 static GElf_Off	ef_get_offset(elf_file_t, GElf_Addr);
72 
73 static void	ef_close(elf_file_t ef);
74 
75 static int	ef_seg_read_rel(elf_file_t ef, GElf_Addr address, size_t len,
76 		    void *dest);
77 static int	ef_seg_read_string(elf_file_t ef, GElf_Addr address, size_t len,
78 		    char *dest);
79 
80 static GElf_Addr ef_symaddr(elf_file_t ef, GElf_Size symidx);
81 static int	ef_lookup_set(elf_file_t ef, const char *name,
82 		    GElf_Addr *startp, GElf_Addr *stopp, long *countp);
83 static int	ef_lookup_symbol(elf_file_t ef, const char *name,
84 		    GElf_Sym **sym);
85 
86 static struct elf_file_ops ef_file_ops = {
87 	.close			= ef_close,
88 	.seg_read_rel		= ef_seg_read_rel,
89 	.seg_read_string	= ef_seg_read_string,
90 	.symaddr		= ef_symaddr,
91 	.lookup_set		= ef_lookup_set,
92 	.lookup_symbol		= ef_lookup_symbol,
93 };
94 
95 static void
96 ef_print_phdr(GElf_Phdr *phdr)
97 {
98 
99 	if ((phdr->p_flags & PF_W) == 0) {
100 		printf("text=0x%jx ", (uintmax_t)phdr->p_filesz);
101 	} else {
102 		printf("data=0x%jx", (uintmax_t)phdr->p_filesz);
103 		if (phdr->p_filesz < phdr->p_memsz)
104 			printf("+0x%jx",
105 			    (uintmax_t)(phdr->p_memsz - phdr->p_filesz));
106 		printf(" ");
107 	}
108 }
109 
110 static GElf_Off
111 ef_get_offset(elf_file_t ef, GElf_Addr addr)
112 {
113 	GElf_Phdr *ph;
114 	int i;
115 
116 	for (i = 0; i < ef->ef_nsegs; i++) {
117 		ph = ef->ef_segs[i];
118 		if (addr >= ph->p_vaddr && addr < ph->p_vaddr + ph->p_memsz) {
119 			return (ph->p_offset + (addr - ph->p_vaddr));
120 		}
121 	}
122 	return (0);
123 }
124 
125 /*
126  * next two functions copied from link_elf.c
127  */
128 static int
129 ef_lookup_symbol(elf_file_t ef, const char *name, GElf_Sym **sym)
130 {
131 	unsigned long hash, symnum;
132 	GElf_Sym *symp;
133 	char *strp;
134 
135 	/* First, search hashed global symbols */
136 	hash = elf_hash(name);
137 	symnum = ef->ef_buckets[hash % ef->ef_nbuckets];
138 
139 	while (symnum != STN_UNDEF) {
140 		if (symnum >= ef->ef_nchains) {
141 			warnx("ef_lookup_symbol: file %s have corrupted symbol table\n",
142 			    ef->ef_name);
143 			return (ENOENT);
144 		}
145 
146 		symp = ef->ef_symtab + symnum;
147 		if (symp->st_name == 0) {
148 			warnx("ef_lookup_symbol: file %s have corrupted symbol table\n",
149 			    ef->ef_name);
150 			return (ENOENT);
151 		}
152 
153 		strp = ef->ef_strtab + symp->st_name;
154 
155 		if (strcmp(name, strp) == 0) {
156 			if (symp->st_shndx != SHN_UNDEF ||
157 			    (symp->st_value != 0 &&
158 				GELF_ST_TYPE(symp->st_info) == STT_FUNC)) {
159 				*sym = symp;
160 				return (0);
161 			} else
162 				return (ENOENT);
163 		}
164 
165 		symnum = ef->ef_chains[symnum];
166 	}
167 
168 	return (ENOENT);
169 }
170 
171 static int
172 ef_lookup_set(elf_file_t ef, const char *name, GElf_Addr *startp,
173     GElf_Addr *stopp, long *countp)
174 {
175 	GElf_Sym *sym;
176 	char *setsym;
177 	int error, len;
178 
179 	len = strlen(name) + sizeof("__start_set_"); /* sizeof includes \0 */
180 	setsym = malloc(len);
181 	if (setsym == NULL)
182 		return (errno);
183 
184 	/* get address of first entry */
185 	snprintf(setsym, len, "%s%s", "__start_set_", name);
186 	error = ef_lookup_symbol(ef, setsym, &sym);
187 	if (error != 0)
188 		goto out;
189 	*startp = sym->st_value;
190 
191 	/* get address of last entry */
192 	snprintf(setsym, len, "%s%s", "__stop_set_", name);
193 	error = ef_lookup_symbol(ef, setsym, &sym);
194 	if (error != 0)
195 		goto out;
196 	*stopp = sym->st_value;
197 
198 	/* and the number of entries */
199 	*countp = (*stopp - *startp) / elf_pointer_size(ef->ef_efile);
200 
201 out:
202 	free(setsym);
203 	return (error);
204 }
205 
206 static GElf_Addr
207 ef_symaddr(elf_file_t ef, GElf_Size symidx)
208 {
209 	const GElf_Sym *sym;
210 
211 	if (symidx >= ef->ef_nchains)
212 		return (0);
213 	sym = ef->ef_symtab + symidx;
214 
215 	if (GELF_ST_BIND(sym->st_info) == STB_LOCAL &&
216 	    sym->st_shndx != SHN_UNDEF && sym->st_value != 0)
217 		return (sym->st_value);
218 	return (0);
219 }
220 
221 static int
222 ef_parse_dynamic(elf_file_t ef, const GElf_Phdr *phdyn)
223 {
224 	GElf_Shdr *shdr;
225 	GElf_Dyn *dyn, *dp;
226 	size_t i, ndyn, nshdr, nsym;
227 	int error;
228 	GElf_Off hash_off, sym_off, str_off;
229 	GElf_Off rel_off;
230 	GElf_Off rela_off;
231 	int rel_sz;
232 	int rela_sz;
233 	int dynamic_idx;
234 
235 	/*
236 	 * The kernel linker parses the PT_DYNAMIC segment to find
237 	 * various important tables.  The gelf API of libelf is
238 	 * section-oriented and requires extracting data from sections
239 	 * instead of segments (program headers).  As a result,
240 	 * iterate over section headers to read various tables after
241 	 * parsing values from PT_DYNAMIC.
242 	 */
243 	error = elf_read_shdrs(ef->ef_efile, &nshdr, &shdr);
244 	if (error != 0)
245 		return (EFTYPE);
246 	dyn = NULL;
247 
248 	/* Find section for .dynamic. */
249 	dynamic_idx = -1;
250 	for (i = 0; i < nshdr; i++) {
251 		if (shdr[i].sh_type == SHT_DYNAMIC) {
252 			/*
253 			 * PowerPC kernels contain additional sections
254 			 * beyond .dynamic in PT_DYNAMIC due to a linker
255 			 * script bug.  Permit a section with a smaller
256 			 * size as a workaround.
257 			 */
258 			if (shdr[i].sh_offset != phdyn->p_offset ||
259 			    ((elf_machine(ef->ef_efile) == EM_PPC ||
260 			    elf_machine(ef->ef_efile) == EM_PPC64) ?
261 			    shdr[i].sh_size > phdyn->p_filesz :
262 			    shdr[i].sh_size != phdyn->p_filesz)) {
263 				warnx(".dynamic section doesn't match phdr");
264 				error = EFTYPE;
265 				goto out;
266 			}
267 			if (dynamic_idx != -1) {
268 				warnx("multiple SHT_DYNAMIC sections");
269 				error = EFTYPE;
270 				goto out;
271 			}
272 			dynamic_idx = i;
273 		}
274 	}
275 
276 	error = elf_read_dynamic(ef->ef_efile, dynamic_idx, &ndyn, &dyn);
277 	if (error != 0)
278 		goto out;
279 
280 	hash_off = rel_off = rela_off = sym_off = str_off = 0;
281 	rel_sz = rela_sz = 0;
282 	for (i = 0; i < ndyn; i++) {
283 		dp = &dyn[i];
284 		if (dp->d_tag == DT_NULL)
285 			break;
286 
287 		switch (dp->d_tag) {
288 		case DT_HASH:
289 			if (hash_off != 0)
290 				warnx("second DT_HASH entry ignored");
291 			else
292 				hash_off = ef_get_offset(ef, dp->d_un.d_ptr);
293 			break;
294 		case DT_STRTAB:
295 			if (str_off != 0)
296 				warnx("second DT_STRTAB entry ignored");
297 			else
298 				str_off = ef_get_offset(ef, dp->d_un.d_ptr);
299 			break;
300 		case DT_SYMTAB:
301 			if (sym_off != 0)
302 				warnx("second DT_SYMTAB entry ignored");
303 			else
304 				sym_off = ef_get_offset(ef, dp->d_un.d_ptr);
305 			break;
306 		case DT_SYMENT:
307 			if (dp->d_un.d_val != elf_object_size(ef->ef_efile,
308 			    ELF_T_SYM)) {
309 				error = EFTYPE;
310 				goto out;
311 			}
312 			break;
313 		case DT_REL:
314 			if (rel_off != 0)
315 				warnx("second DT_REL entry ignored");
316 			else
317 				rel_off = ef_get_offset(ef, dp->d_un.d_ptr);
318 			break;
319 		case DT_RELSZ:
320 			if (rel_sz != 0)
321 				warnx("second DT_RELSZ entry ignored");
322 			else
323 				rel_sz = dp->d_un.d_val;
324 			break;
325 		case DT_RELENT:
326 			if (dp->d_un.d_val != elf_object_size(ef->ef_efile,
327 			    ELF_T_REL)) {
328 				error = EFTYPE;
329 				goto out;
330 			}
331 			break;
332 		case DT_RELA:
333 			if (rela_off != 0)
334 				warnx("second DT_RELA entry ignored");
335 			else
336 				rela_off = ef_get_offset(ef, dp->d_un.d_ptr);
337 			break;
338 		case DT_RELASZ:
339 			if (rela_sz != 0)
340 				warnx("second DT_RELSZ entry ignored");
341 			else
342 				rela_sz = dp->d_un.d_val;
343 			break;
344 		case DT_RELAENT:
345 			if (dp->d_un.d_val != elf_object_size(ef->ef_efile,
346 			    ELF_T_RELA)) {
347 				error = EFTYPE;
348 				goto out;
349 			}
350 			break;
351 		}
352 	}
353 	if (hash_off == 0) {
354 		warnx("%s: no .hash section found\n", ef->ef_name);
355 		error = EFTYPE;
356 		goto out;
357 	}
358 	if (sym_off == 0) {
359 		warnx("%s: no .dynsym section found\n", ef->ef_name);
360 		error = EFTYPE;
361 		goto out;
362 	}
363 	if (str_off == 0) {
364 		warnx("%s: no .dynstr section found\n", ef->ef_name);
365 		error = EFTYPE;
366 		goto out;
367 	}
368 
369 	nsym = 0;
370 	for (i = 0; i < nshdr; i++) {
371 		switch (shdr[i].sh_type) {
372 		case SHT_HASH:
373 			if (shdr[i].sh_offset != hash_off) {
374 				warnx("%s: ignoring SHT_HASH at different offset from DT_HASH",
375 				    ef->ef_name);
376 				break;
377 			}
378 
379 			/*
380 			 * libelf(3) mentions ELF_T_HASH, but it is
381 			 * not defined.
382 			 */
383 			if (shdr[i].sh_size < sizeof(*ef->ef_hashtab) * 2) {
384 				warnx("hash section too small");
385 				error = EFTYPE;
386 				goto out;
387 			}
388 			error = elf_read_data(ef->ef_efile, ELF_T_WORD,
389 			    shdr[i].sh_offset, shdr[i].sh_size,
390 			    (void **)&ef->ef_hashtab);
391 			if (error != 0) {
392 				warnc(error, "can't read hash table");
393 				goto out;
394 			}
395 			ef->ef_nbuckets = ef->ef_hashtab[0];
396 			ef->ef_nchains = ef->ef_hashtab[1];
397 			if ((2 + ef->ef_nbuckets + ef->ef_nchains) *
398 			    sizeof(*ef->ef_hashtab) != shdr[i].sh_size) {
399 				warnx("inconsistent hash section size");
400 				error = EFTYPE;
401 				goto out;
402 			}
403 
404 			ef->ef_buckets = ef->ef_hashtab + 2;
405 			ef->ef_chains = ef->ef_buckets + ef->ef_nbuckets;
406 			break;
407 		case SHT_DYNSYM:
408 			if (shdr[i].sh_offset != sym_off) {
409 				warnx("%s: ignoring SHT_DYNSYM at different offset from DT_SYMTAB",
410 				    ef->ef_name);
411 				break;
412 			}
413 			error = elf_read_symbols(ef->ef_efile, i, &nsym,
414 			    &ef->ef_symtab);
415 			if (error != 0) {
416 				if (ef->ef_verbose)
417 					warnx("%s: can't load .dynsym section (0x%jx)",
418 					    ef->ef_name, (uintmax_t)sym_off);
419 				goto out;
420 			}
421 			break;
422 		case SHT_STRTAB:
423 			if (shdr[i].sh_offset != str_off)
424 				break;
425 			error = elf_read_string_table(ef->ef_efile,
426 			    &shdr[i], &ef->ef_strsz, &ef->ef_strtab);
427 			if (error != 0) {
428 				warnx("can't load .dynstr section");
429 				error = EIO;
430 				goto out;
431 			}
432 			break;
433 		case SHT_REL:
434 			if (shdr[i].sh_offset != rel_off)
435 				break;
436 			if (shdr[i].sh_size != rel_sz) {
437 				warnx("%s: size mismatch for DT_REL section",
438 				    ef->ef_name);
439 				error = EFTYPE;
440 				goto out;
441 			}
442 			error = elf_read_rel(ef->ef_efile, i, &ef->ef_relsz,
443 			    &ef->ef_rel);
444 			if (error != 0) {
445 				warnx("%s: cannot load DT_REL section",
446 				    ef->ef_name);
447 				goto out;
448 			}
449 			break;
450 		case SHT_RELA:
451 			if (shdr[i].sh_offset != rela_off)
452 				break;
453 			if (shdr[i].sh_size != rela_sz) {
454 				warnx("%s: size mismatch for DT_RELA section",
455 				    ef->ef_name);
456 				error = EFTYPE;
457 				goto out;
458 			}
459 			error = elf_read_rela(ef->ef_efile, i, &ef->ef_relasz,
460 			    &ef->ef_rela);
461 			if (error != 0) {
462 				warnx("%s: cannot load DT_RELA section",
463 				    ef->ef_name);
464 				goto out;
465 			}
466 			break;
467 		}
468 	}
469 
470 	if (ef->ef_hashtab == NULL) {
471 		warnx("%s: did not find a symbol hash table", ef->ef_name);
472 		error = EFTYPE;
473 		goto out;
474 	}
475 	if (ef->ef_symtab == NULL) {
476 		warnx("%s: did not find a dynamic symbol table", ef->ef_name);
477 		error = EFTYPE;
478 		goto out;
479 	}
480 	if (nsym != ef->ef_nchains) {
481 		warnx("%s: symbol count mismatch", ef->ef_name);
482 		error = EFTYPE;
483 		goto out;
484 	}
485 	if (ef->ef_strtab == NULL) {
486 		warnx("%s: did not find a dynamic string table", ef->ef_name);
487 		error = EFTYPE;
488 		goto out;
489 	}
490 	if (rel_off != 0 && ef->ef_rel == NULL) {
491 		warnx("%s: did not find a DT_REL relocation table",
492 		    ef->ef_name);
493 		error = EFTYPE;
494 		goto out;
495 	}
496 	if (rela_off != 0 && ef->ef_rela == NULL) {
497 		warnx("%s: did not find a DT_RELA relocation table",
498 		    ef->ef_name);
499 		error = EFTYPE;
500 		goto out;
501 	}
502 
503 	error = 0;
504 out:
505 	free(dyn);
506 	free(shdr);
507 	return (error);
508 }
509 
510 static int
511 ef_seg_read_rel(elf_file_t ef, GElf_Addr address, size_t len, void *dest)
512 {
513 	GElf_Off ofs;
514 	const GElf_Rela *a;
515 	const GElf_Rel *r;
516 	int error;
517 
518 	ofs = ef_get_offset(ef, address);
519 	if (ofs == 0) {
520 		if (ef->ef_verbose)
521 			warnx("ef_seg_read_rel(%s): bad address (%jx)",
522 			    ef->ef_name, (uintmax_t)address);
523 		return (EFAULT);
524 	}
525 	error = elf_read_raw_data(ef->ef_efile, ofs, dest, len);
526 	if (error != 0)
527 		return (error);
528 
529 	for (r = ef->ef_rel; r < &ef->ef_rel[ef->ef_relsz]; r++) {
530 		error = elf_reloc(ef->ef_efile, r, ELF_T_REL, 0, address,
531 		    len, dest);
532 		if (error != 0)
533 			return (error);
534 	}
535 	for (a = ef->ef_rela; a < &ef->ef_rela[ef->ef_relasz]; a++) {
536 		error = elf_reloc(ef->ef_efile, a, ELF_T_RELA, 0, address,
537 		    len, dest);
538 		if (error != 0)
539 			return (error);
540 	}
541 	return (0);
542 }
543 
544 static int
545 ef_seg_read_string(elf_file_t ef, GElf_Addr address, size_t len, char *dest)
546 {
547 	GElf_Off ofs;
548 
549 	ofs = ef_get_offset(ef, address);
550 	if (ofs == 0) {
551 		if (ef->ef_verbose)
552 			warnx("ef_seg_read_string(%s): bad offset (%jx:%ju)",
553 			    ef->ef_name, (uintmax_t)address, (uintmax_t)ofs);
554 		return (EFAULT);
555 	}
556 
557 	return (elf_read_raw_string(ef->ef_efile, ofs, dest, len));
558 }
559 
560 int
561 ef_open(struct elf_file *efile, int verbose)
562 {
563 	elf_file_t ef;
564 	GElf_Ehdr *hdr;
565 	size_t i, nphdr, nsegs;
566 	int error;
567 	GElf_Phdr *phdr, *phdyn;
568 
569 	hdr = &efile->ef_hdr;
570 	if (hdr->e_phnum == 0 ||
571 	    hdr->e_phentsize != elf_object_size(efile, ELF_T_PHDR) ||
572 	    hdr->e_shnum == 0 || hdr->e_shoff == 0 ||
573 	    hdr->e_shentsize != elf_object_size(efile, ELF_T_SHDR))
574 		return (EFTYPE);
575 
576 	ef = malloc(sizeof(*ef));
577 	if (ef == NULL)
578 		return (errno);
579 
580 	efile->ef_ef = ef;
581 	efile->ef_ops = &ef_file_ops;
582 
583 	bzero(ef, sizeof(*ef));
584 	ef->ef_verbose = verbose;
585 	ef->ef_name = strdup(efile->ef_filename);
586 	ef->ef_efile = efile;
587 
588 	error = elf_read_phdrs(efile, &nphdr, &ef->ef_ph);
589 	if (error != 0) {
590 		phdr = NULL;
591 		goto out;
592 	}
593 
594 	error = EFTYPE;
595 	nsegs = 0;
596 	phdyn = NULL;
597 	phdr = ef->ef_ph;
598 	for (i = 0; i < nphdr; i++, phdr++) {
599 		if (verbose > 1)
600 			ef_print_phdr(phdr);
601 		switch (phdr->p_type) {
602 		case PT_LOAD:
603 			if (nsegs < MAXSEGS)
604 				ef->ef_segs[nsegs] = phdr;
605 			nsegs++;
606 			break;
607 		case PT_PHDR:
608 			break;
609 		case PT_DYNAMIC:
610 			phdyn = phdr;
611 			break;
612 		}
613 	}
614 	if (verbose > 1)
615 		printf("\n");
616 	if (phdyn == NULL) {
617 		warnx("Skipping %s: not dynamically-linked",
618 		    ef->ef_name);
619 		goto out;
620 	}
621 
622 	if (nsegs > MAXSEGS) {
623 		warnx("%s: too many segments", ef->ef_name);
624 		goto out;
625 	}
626 	ef->ef_nsegs = nsegs;
627 
628 	error = ef_parse_dynamic(ef, phdyn);
629 out:
630 	if (error != 0)
631 		ef_close(ef);
632 	return (error);
633 }
634 
635 static void
636 ef_close(elf_file_t ef)
637 {
638 	free(ef->ef_rela);
639 	free(ef->ef_rel);
640 	free(ef->ef_strtab);
641 	free(ef->ef_symtab);
642 	free(ef->ef_hashtab);
643 	free(ef->ef_ph);
644 	if (ef->ef_name)
645 		free(ef->ef_name);
646 	ef->ef_efile->ef_ops = NULL;
647 	ef->ef_efile->ef_ef = NULL;
648 	free(ef);
649 }
650