1/*      $OpenBSD: copy_elf.c,v 1.7 2020/04/28 04:17:42 deraadt Exp $       */
2
3/*
4 * Copyright (c) 2013 Miodrag Vallat.
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 <stdio.h>
20#include <err.h>
21#include <unistd.h>
22#include <stdlib.h>
23#include <string.h>
24
25#include <elf.h>
26
27#if defined(ELFSIZE) && (ELFSIZE == 32)
28#define elfoff2h(x) letoh32(x)
29#define h2elfoff(x) htole32(x)
30#elif defined(ELFSIZE) && (ELFSIZE == 64)
31#define elfoff2h(x) letoh64(x)
32#define h2elfoff(x) htole64(x)
33#else
34#error "unknown elf size"
35#endif
36
37struct image_header;
38
39#define        roundup(x, y)   ((((x)+((y)-1))/(y))*(y))
40
41extern u_long copy_data(int, const char *, int, const char *, u_long,
42	    struct image_header *, Elf_Word);
43u_long	copy_mem(void *, int, const char *, u_long, struct image_header *,
44	    Elf_Word);
45extern u_long fill_zeroes(int, const char *, u_long, struct image_header *,
46	    Elf_Word);
47
48u_long
49ELFNAME(copy_elf)(int ifd, const char *iname, int ofd, const char *oname,
50    u_long crc, struct image_header *ih)
51{
52	ssize_t nbytes;
53	Elf_Ehdr ehdr, elf;
54	Elf_Phdr phdr;
55	Elf_Addr vaddr, ovaddr, svaddr, off, ssym;
56	Elf_Shdr *shp, *wshp;
57	Elf_Addr esym = 0, esymval;
58	int i, sz, havesyms;
59
60	nbytes = read(ifd, &ehdr, sizeof ehdr);
61	if (nbytes == -1)
62		err(1, "%s", iname);
63	if (nbytes != sizeof ehdr)
64		return 0;
65
66	elf = ehdr;
67
68	if (lseek(ifd, (off_t)elfoff2h(elf.e_shoff), SEEK_SET) == -1)
69		err(1, "%s unable to seek to section header", iname);
70
71	sz = letoh16(elf.e_shnum) * sizeof(Elf_Shdr);
72	shp = calloc(sz, 1);
73	if (read(ifd, shp, sz) != sz)
74		err(1, "%s: read section headers", iname);
75
76	wshp = calloc(sz, 1);
77	memcpy(wshp, shp, sz);
78
79	/* first walk the load sections to find the kernel addresses */
80	/* next we walk the sections to find the
81	 * location of esym (first address of data space
82	 */
83	for (i = 0; i < letoh16(ehdr.e_phnum); i++) {
84		if (lseek(ifd, elfoff2h(ehdr.e_phoff) + i *
85		    letoh16(ehdr.e_phentsize), SEEK_SET) == (off_t)-1)
86			err(1, "%s", iname);
87		if (read(ifd, &phdr, sizeof phdr) != sizeof(phdr))
88			err(1, "%s", iname);
89		/* assumes it loads in incrementing address order */
90		if (letoh32(phdr.p_type) == PT_LOAD)
91			vaddr = elfoff2h(phdr.p_vaddr) +
92			    elfoff2h(phdr.p_memsz);
93	}
94
95	/* ok, we need to write the elf header and section header
96	 * which contains info about the not yet written section data
97	 * however due to crc the data all has to be written in order
98	 * which means walking the structures twice once to precompute
99	 * the data, once to write the data.
100	 */
101	ssym = vaddr;
102	vaddr += roundup((sizeof(Elf_Ehdr) + sz), sizeof(Elf_Addr));
103	off = roundup((sizeof(Elf_Ehdr) + sz), sizeof(Elf_Addr));
104	for (i = 0; i < letoh16(elf.e_shnum); i++) {
105		if (esym == 0 && elfoff2h(shp[i].sh_flags) & SHF_WRITE &&
106		    elfoff2h(shp[i].sh_flags) & SHF_ALLOC)
107			esym = elfoff2h(shp[i].sh_addr);
108
109		if (letoh32(shp[i].sh_type) == SHT_SYMTAB ||
110		    letoh32(shp[i].sh_type) == SHT_STRTAB) {
111#ifdef DEBUG
112		fprintf(stderr, "shdr %d %d/%d off %lx\n", i,
113		    letoh32(shp[i].sh_type), roundup(elfoff2h(shp[i].sh_size),
114		    sizeof(Elf_Addr)), off);
115#endif
116			/* data is at shp[i].sh_offset of len shp[i].sh_size */
117			wshp[i].sh_offset = h2elfoff(off);
118			off += roundup(elfoff2h(shp[i].sh_size),
119			    sizeof(Elf_Addr));
120			vaddr += roundup(elfoff2h(shp[i].sh_size),
121			    sizeof(Elf_Addr));
122		}
123	}
124	esymval = vaddr;
125#ifdef DEBUG
126	fprintf(stderr, "esymval %lx size %ld\n", esymval, esymval - ssym);
127#endif
128
129	for (i = 0; i < letoh16(ehdr.e_phnum); i++) {
130#ifdef DEBUG
131		fprintf(stderr, "phdr %d/%d\n", i, letoh16(ehdr.e_phnum));
132#endif
133		if (lseek(ifd, elfoff2h(ehdr.e_phoff) + i *
134		    letoh16(ehdr.e_phentsize), SEEK_SET) == (off_t)-1)
135			err(1, "%s", iname);
136		if (read(ifd, &phdr, sizeof phdr) != sizeof(phdr))
137			err(1, "%s", iname);
138
139#ifdef DEBUG
140		fprintf(stderr,
141		    "vaddr %p type %#x offset %p filesz %p memsz %p\n",
142		    elfoff2h(phdr.p_vaddr), letoh32(phdr.p_type),
143	            elfoff2h(phdr.p_offset), elfoff2h(phdr.p_filesz),
144                    elfoff2h(phdr.p_memsz));
145#endif
146
147		switch (letoh32(phdr.p_type)) {
148		case PT_LOAD:
149			break;
150		case PT_NULL:
151		case PT_NOTE:
152		case PT_OPENBSD_RANDOMIZE:
153#ifdef DEBUG
154			fprintf(stderr, "skipping segment type %#x\n",
155			    letoh32(phdr.p_type));
156#endif
157			continue;
158		default:
159			errx(1, "unexpected segment type %#x",
160			    letoh32(phdr.p_type));
161		}
162
163		if (i == 0)
164			vaddr = elfoff2h(phdr.p_vaddr);
165		else if (vaddr != elfoff2h(phdr.p_vaddr)) {
166#ifdef DEBUG
167			fprintf(stderr, "gap %p->%p\n", vaddr,
168			    elfoff2h(phdr.p_vaddr));
169#endif
170			/* fill the gap between the previous phdr if any */
171			crc = fill_zeroes(ofd, oname, crc, ih,
172			    elfoff2h(phdr.p_vaddr) - vaddr);
173			vaddr = elfoff2h(phdr.p_vaddr);
174		}
175
176		if (elfoff2h(phdr.p_filesz) != 0) {
177#ifdef DEBUG
178			fprintf(stderr, "copying %p from infile %p\n",
179			   elfoff2h(phdr.p_filesz), elfoff2h(phdr.p_offset));
180#endif
181			/* esym will be in the data portion of a region */
182			if (esym >= elfoff2h(phdr.p_vaddr) &&
183			    esym < elfoff2h(phdr.p_vaddr) +
184			    elfoff2h(phdr.p_filesz)) {
185				/* load the region up to the esym
186				 * (may be empty)
187				 */
188				Elf_Addr loadlen = esym -
189				    elfoff2h(phdr.p_vaddr);
190
191				if (lseek(ifd, elfoff2h(phdr.p_offset),
192				    SEEK_SET) == (off_t)-1)
193					err(1, "%s", iname);
194				crc = copy_data(ifd, iname, ofd, oname, crc,
195				    ih, loadlen);
196
197				crc = copy_mem(&esymval, ofd, oname, crc, ih,
198				    sizeof(esymval));
199
200				if (lseek(ifd, elfoff2h(phdr.p_offset) +
201				    loadlen + sizeof(esymval), SEEK_SET) ==
202				    (off_t)-1)
203					err(1, "%s", iname);
204				crc = copy_data(ifd, iname, ofd, oname, crc,
205				    ih, elfoff2h(phdr.p_filesz) - loadlen -
206				    sizeof(esymval));
207			} else {
208
209				if (lseek(ifd, elfoff2h(phdr.p_offset),
210				    SEEK_SET) == (off_t)-1)
211					err(1, "%s", iname);
212				crc = copy_data(ifd, iname, ofd, oname, crc,
213				    ih, elfoff2h(phdr.p_filesz));
214			}
215			if (elfoff2h(phdr.p_memsz) - elfoff2h(phdr.p_filesz)
216			    != 0) {
217#ifdef DEBUG
218				fprintf(stderr, "zeroing %p\n",
219				    elfoff2h(phdr.p_memsz) -
220				    elfoff2h(phdr.p_filesz));
221#endif
222				crc = fill_zeroes(ofd, oname, crc, ih,
223				    elfoff2h(phdr.p_memsz) -
224				    elfoff2h(phdr.p_filesz));
225			}
226			ovaddr = vaddr + elfoff2h(phdr.p_memsz);
227		} else {
228			ovaddr = vaddr;
229		}
230		/*
231		 * If p_filesz == 0, this is likely .bss, which we do not
232		 * need to provide. If it's not the last phdr, the gap
233		 * filling code will output the necessary zeroes anyway.
234		 */
235		vaddr += elfoff2h(phdr.p_memsz);
236	}
237
238	vaddr = roundup(vaddr, sizeof(Elf_Addr));
239	if (vaddr != ovaddr) {
240#ifdef DEBUG
241		fprintf(stderr, "gap %p->%p\n", vaddr, elfoff2h(phdr.p_vaddr));
242#endif
243		/* fill the gap between the previous phdr if not aligned */
244		crc = fill_zeroes(ofd, oname, crc, ih, vaddr - ovaddr);
245	}
246
247	for (havesyms = i = 0; i < letoh16(elf.e_shnum); i++)
248		if (letoh32(shp[i].sh_type) == SHT_SYMTAB)
249			havesyms = 1;
250
251	if (havesyms == 0)
252		return crc;
253
254	elf.e_phoff = 0;
255	elf.e_shoff = h2elfoff(sizeof(Elf_Ehdr));
256	elf.e_phentsize = 0;
257	elf.e_phnum = 0;
258	crc = copy_mem(&elf, ofd, oname, crc, ih, sizeof(elf));
259	crc = copy_mem(wshp, ofd, oname, crc, ih, sz);
260	off = sizeof(elf) + sz;
261	vaddr += sizeof(elf) + sz;
262
263	off = roundup((sizeof(Elf_Ehdr) + sz), sizeof(Elf_Addr));
264	for (i = 0; i < letoh16(elf.e_shnum); i++) {
265		if (letoh32(shp[i].sh_type) == SHT_SYMTAB ||
266		    letoh32(shp[i].sh_type) == SHT_STRTAB) {
267			Elf_Addr align;
268			/* data is at shp[i].sh_offset of len shp[i].sh_size */
269			if (lseek(ifd, elfoff2h(shp[i].sh_offset), SEEK_SET)
270			    == -1)
271				err(1, "%s", iname);
272
273			off += elfoff2h(shp[i].sh_size);
274			vaddr += elfoff2h(shp[i].sh_size);
275			crc = copy_data(ifd, iname, ofd, oname, crc, ih,
276			    elfoff2h(shp[i].sh_size));
277			align = roundup(elfoff2h(shp[i].sh_size),
278			    sizeof(Elf_Addr)) - elfoff2h(shp[i].sh_size);
279			if (align != 0) {
280				vaddr += align;
281				crc = fill_zeroes(ofd, oname, crc, ih, align);
282			}
283		}
284	}
285
286	if (vaddr != esymval)
287		warnx("esymval and vaddr mismatch %llx %llx\n",
288		    (long long)esymval, (long long)vaddr);
289
290	return crc;
291}
292