xref: /openbsd/usr.sbin/mkuboot/copy_elf.c (revision d415bd75)
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 
37 struct image_header;
38 
39 #define        roundup(x, y)   ((((x)+((y)-1))/(y))*(y))
40 
41 extern u_long copy_data(int, const char *, int, const char *, u_long,
42 	    struct image_header *, Elf_Word);
43 u_long	copy_mem(void *, int, const char *, u_long, struct image_header *,
44 	    Elf_Word);
45 extern u_long fill_zeroes(int, const char *, u_long, struct image_header *,
46 	    Elf_Word);
47 
48 u_long
49 ELFNAME(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