xref: /openbsd-src/usr.sbin/installboot/efi_installboot.c (revision c1a45aed656e7d5627c30c92421893a76f370ccb)
1 /*	$OpenBSD: efi_installboot.c,v 1.2 2022/02/03 10:25:14 visa Exp $	*/
2 /*	$NetBSD: installboot.c,v 1.5 1995/11/17 23:23:50 gwr Exp $ */
3 
4 /*
5  * Copyright (c) 2011 Joel Sing <jsing@openbsd.org>
6  * Copyright (c) 2010 Otto Moerbeek <otto@openbsd.org>
7  * Copyright (c) 2003 Tom Cosgrove <tom.cosgrove@arches-consulting.com>
8  * Copyright (c) 1997 Michael Shalayeff
9  * Copyright (c) 1994 Paul Kranenburg
10  * All rights reserved.
11  *
12  * Redistribution and use in source and binary forms, with or without
13  * modification, are permitted provided that the following conditions
14  * are met:
15  * 1. Redistributions of source code must retain the above copyright
16  *    notice, this list of conditions and the following disclaimer.
17  * 2. Redistributions in binary form must reproduce the above copyright
18  *    notice, this list of conditions and the following disclaimer in the
19  *    documentation and/or other materials provided with the distribution.
20  * 3. All advertising materials mentioning features or use of this software
21  *    must display the following acknowledgement:
22  *      This product includes software developed by Paul Kranenburg.
23  * 4. The name of the author may not be used to endorse or promote products
24  *    derived from this software without specific prior written permission
25  *
26  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
27  * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
28  * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
29  * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
30  * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
31  * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
32  * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
33  * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
34  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
35  * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
36  */
37 
38 #include <sys/param.h>	/* DEV_BSIZE */
39 #include <sys/disklabel.h>
40 #include <sys/dkio.h>
41 #include <sys/ioctl.h>
42 #include <sys/mount.h>
43 #include <sys/stat.h>
44 
45 #include <err.h>
46 #include <errno.h>
47 #include <fcntl.h>
48 #include <stdlib.h>
49 #include <stdio.h>
50 #include <stdint.h>
51 #include <string.h>
52 #include <unistd.h>
53 #include <util.h>
54 #include <uuid.h>
55 
56 #include "installboot.h"
57 
58 #if defined(__aarch64__)
59 #define BOOTEFI_SRC	"BOOTAA64.EFI"
60 #define BOOTEFI_DST	"bootaa64.efi"
61 #elif defined(__arm__)
62 #define BOOTEFI_SRC	"BOOTARM.EFI"
63 #define BOOTEFI_DST	"bootarm.efi"
64 #elif defined(__riscv)
65 #define BOOTEFI_SRC	"BOOTRISCV64.EFI"
66 #define BOOTEFI_DST	"bootriscv64.efi"
67 #else
68 #error "unhandled architecture"
69 #endif
70 
71 static int	create_filesystem(struct disklabel *, char);
72 static void	write_filesystem(struct disklabel *, char);
73 static int	findgptefisys(int, struct disklabel *);
74 static int	findmbrfat(int, struct disklabel *);
75 
76 void
77 md_init(void)
78 {
79 }
80 
81 void
82 md_loadboot(void)
83 {
84 }
85 
86 void
87 md_prepareboot(int devfd, char *dev)
88 {
89 	struct disklabel dl;
90 	int part;
91 
92 	/* Get and check disklabel. */
93 	if (ioctl(devfd, DIOCGDINFO, &dl) == -1)
94 		err(1, "disklabel: %s", dev);
95 	if (dl.d_magic != DISKMAGIC)
96 		errx(1, "bad disklabel magic=0x%08x", dl.d_magic);
97 
98 	/* Warn on unknown disklabel types. */
99 	if (dl.d_type == 0)
100 		warnx("disklabel type unknown");
101 
102 	part = findgptefisys(devfd, &dl);
103 	if (part != -1) {
104 		create_filesystem(&dl, (char)part);
105 		return;
106 	}
107 
108 	part = findmbrfat(devfd, &dl);
109 	if (part != -1) {
110 		create_filesystem(&dl, (char)part);
111 		return;
112 	}
113 }
114 
115 void
116 md_installboot(int devfd, char *dev)
117 {
118 	struct disklabel dl;
119 	int part;
120 
121 	/* Get and check disklabel. */
122 	if (ioctl(devfd, DIOCGDINFO, &dl) == -1)
123 		err(1, "disklabel: %s", dev);
124 	if (dl.d_magic != DISKMAGIC)
125 		errx(1, "bad disklabel magic=0x%08x", dl.d_magic);
126 
127 	/* Warn on unknown disklabel types. */
128 	if (dl.d_type == 0)
129 		warnx("disklabel type unknown");
130 
131 	part = findgptefisys(devfd, &dl);
132 	if (part != -1) {
133 		write_filesystem(&dl, (char)part);
134 		return;
135 	}
136 
137 	part = findmbrfat(devfd, &dl);
138 	if (part != -1) {
139 		write_filesystem(&dl, (char)part);
140 		return;
141 	}
142 }
143 
144 static int
145 create_filesystem(struct disklabel *dl, char part)
146 {
147 	static char *newfsfmt ="/sbin/newfs_msdos %s >/dev/null";
148 	struct msdosfs_args args;
149 	char cmd[60];
150 	int rslt;
151 
152 	/* Mount <duid>.<part> as msdos filesystem. */
153 	memset(&args, 0, sizeof(args));
154 	rslt = asprintf(&args.fspec,
155 	    "%02hhx%02hhx%02hhx%02hhx%02hhx%02hhx%02hhx%02hhx.%c",
156             dl->d_uid[0], dl->d_uid[1], dl->d_uid[2], dl->d_uid[3],
157             dl->d_uid[4], dl->d_uid[5], dl->d_uid[6], dl->d_uid[7],
158 	    part);
159 	if (rslt == -1) {
160 		warn("bad special device");
161 		return rslt;
162 	}
163 
164 	rslt = snprintf(cmd, sizeof(cmd), newfsfmt, args.fspec);
165 	if (rslt >= sizeof(cmd)) {
166 		warnx("can't build newfs command");
167 		rslt = -1;
168 		return rslt;
169 	}
170 
171 	if (verbose)
172 		fprintf(stderr, "%s %s\n",
173 		    (nowrite ? "would newfs" : "newfsing"), args.fspec);
174 	if (!nowrite) {
175 		rslt = system(cmd);
176 		if (rslt == -1) {
177 			warn("system('%s') failed", cmd);
178 			return rslt;
179 		}
180 	}
181 
182 	return 0;
183 }
184 
185 static void
186 write_filesystem(struct disklabel *dl, char part)
187 {
188 	static char *fsckfmt = "/sbin/fsck_msdos %s >/dev/null";
189 	struct msdosfs_args args;
190 	char cmd[60];
191 	char dst[PATH_MAX];
192 	char *src;
193 	size_t mntlen, pathlen, srclen;
194 	int rslt;
195 
196 	src = NULL;
197 
198 	/* Create directory for temporary mount point. */
199 	strlcpy(dst, "/tmp/installboot.XXXXXXXXXX", sizeof(dst));
200 	if (mkdtemp(dst) == NULL)
201 		err(1, "mkdtemp('%s') failed", dst);
202 	mntlen = strlen(dst);
203 
204 	/* Mount <duid>.<part> as msdos filesystem. */
205 	memset(&args, 0, sizeof(args));
206 	rslt = asprintf(&args.fspec,
207 	    "%02hhx%02hhx%02hhx%02hhx%02hhx%02hhx%02hhx%02hhx.%c",
208             dl->d_uid[0], dl->d_uid[1], dl->d_uid[2], dl->d_uid[3],
209             dl->d_uid[4], dl->d_uid[5], dl->d_uid[6], dl->d_uid[7],
210 	    part);
211 	if (rslt == -1) {
212 		warn("bad special device");
213 		goto rmdir;
214 	}
215 
216 	args.export_info.ex_root = -2;
217 	args.export_info.ex_flags = 0;
218 	args.flags = MSDOSFSMNT_LONGNAME;
219 
220 	if (mount(MOUNT_MSDOS, dst, 0, &args) == -1) {
221 		/* Try fsck'ing it. */
222 		rslt = snprintf(cmd, sizeof(cmd), fsckfmt, args.fspec);
223 		if (rslt >= sizeof(cmd)) {
224 			warnx("can't build fsck command");
225 			rslt = -1;
226 			goto rmdir;
227 		}
228 		rslt = system(cmd);
229 		if (rslt == -1) {
230 			warn("system('%s') failed", cmd);
231 			goto rmdir;
232 		}
233 		if (mount(MOUNT_MSDOS, dst, 0, &args) == -1) {
234 			/* Try newfs'ing it. */
235 			rslt = create_filesystem(dl, part);
236 			if (rslt == -1)
237 				goto rmdir;
238 			rslt = mount(MOUNT_MSDOS, dst, 0, &args);
239 			if (rslt == -1) {
240 				warn("unable to mount EFI System partition");
241 				goto rmdir;
242 			}
243 		}
244 	}
245 
246 	/* Create "/efi/boot" directory in <duid>.<part>. */
247 	if (strlcat(dst, "/efi", sizeof(dst)) >= sizeof(dst)) {
248 		rslt = -1;
249 		warn("unable to build /efi directory");
250 		goto umount;
251 	}
252 	rslt = mkdir(dst, 0755);
253 	if (rslt == -1 && errno != EEXIST) {
254 		warn("mkdir('%s') failed", dst);
255 		goto umount;
256 	}
257 	if (strlcat(dst, "/boot", sizeof(dst)) >= sizeof(dst)) {
258 		rslt = -1;
259 		warn("unable to build /boot directory");
260 		goto umount;
261 	}
262 	rslt = mkdir(dst, 0755);
263 	if (rslt == -1 && errno != EEXIST) {
264 		warn("mkdir('%s') failed", dst);
265 		goto umount;
266 	}
267 
268 	/* Copy EFI bootblocks to /efi/boot/. */
269 	pathlen = strlen(dst);
270 	if (strlcat(dst, "/" BOOTEFI_DST, sizeof(dst)) >= sizeof(dst)) {
271 		rslt = -1;
272 		warn("unable to build /%s path", BOOTEFI_DST);
273 		goto umount;
274 	}
275 	src = fileprefix(root, "/usr/mdec/" BOOTEFI_SRC);
276 	if (src == NULL) {
277 		rslt = -1;
278 		goto umount;
279 	}
280 	srclen = strlen(src);
281 	if (verbose)
282 		fprintf(stderr, "%s %s to %s\n",
283 		    (nowrite ? "would copy" : "copying"), src, dst);
284 	if (!nowrite) {
285 		rslt = filecopy(src, dst);
286 		if (rslt == -1)
287 			goto umount;
288 	}
289 
290 	/* Write /efi/boot/startup.nsh. */
291 	dst[pathlen] = '\0';
292 	if (strlcat(dst, "/startup.nsh", sizeof(dst)) >= sizeof(dst)) {
293 		rslt = -1;
294 		warn("unable to build /startup.nsh path");
295 		goto umount;
296 	}
297 	if (verbose)
298 		fprintf(stderr, "%s %s\n",
299 		    (nowrite ? "would write" : "writing"), dst);
300 	if (!nowrite) {
301 		rslt = fileprintf(dst, "%s\n", BOOTEFI_DST);
302 		if (rslt == -1)
303 			goto umount;
304 	}
305 
306 	rslt = 0;
307 
308 umount:
309 	dst[mntlen] = '\0';
310 	if (unmount(dst, MNT_FORCE) == -1)
311 		err(1, "unmount('%s') failed", dst);
312 
313 rmdir:
314 	free(args.fspec);
315 	dst[mntlen] = '\0';
316 	if (rmdir(dst) == -1)
317 		err(1, "rmdir('%s') failed", dst);
318 
319 	free(src);
320 
321 	if (rslt == -1)
322 		exit(1);
323 }
324 
325 /*
326  * Returns 0 if the MBR with the provided partition array is a GPT protective
327  * MBR, and returns 1 otherwise. A GPT protective MBR would have one and only
328  * one MBR partition, an EFI partition that either covers the whole disk or as
329  * much of it as is possible with a 32bit size field.
330  *
331  * NOTE: MS always uses a size of UINT32_MAX for the EFI partition!**
332  */
333 static int
334 gpt_chk_mbr(struct dos_partition *dp, u_int64_t dsize)
335 {
336 	struct dos_partition *dp2;
337 	int efi, found, i;
338 	u_int32_t psize;
339 
340 	found = efi = 0;
341 	for (dp2=dp, i=0; i < NDOSPART; i++, dp2++) {
342 		if (dp2->dp_typ == DOSPTYP_UNUSED)
343 			continue;
344 		found++;
345 		if (dp2->dp_typ != DOSPTYP_EFI)
346 			continue;
347 		if (letoh32(dp2->dp_start) != GPTSECTOR)
348 			continue;
349 		psize = letoh32(dp2->dp_size);
350 		if (psize <= (dsize - GPTSECTOR) || psize == UINT32_MAX)
351 			efi++;
352 	}
353 	if (found == 1 && efi == 1)
354 		return (0);
355 
356 	return (1);
357 }
358 
359 int
360 findgptefisys(int devfd, struct disklabel *dl)
361 {
362 	struct gpt_partition	 gp[NGPTPARTITIONS];
363 	struct gpt_header	 gh;
364 	struct dos_partition	 dp[NDOSPART];
365 	struct uuid		 efisys_uuid;
366 	const char		 efisys_uuid_code[] = GPT_UUID_EFI_SYSTEM;
367 	off_t			 off;
368 	ssize_t			 len;
369 	u_int64_t		 start;
370 	int			 i;
371 	uint32_t		 orig_csum, new_csum;
372 	uint32_t		 ghsize, ghpartsize, ghpartnum, ghpartspersec;
373 	u_int8_t		*secbuf;
374 
375 	/* Prepare EFI System UUID */
376 	uuid_dec_be(efisys_uuid_code, &efisys_uuid);
377 
378 	if ((secbuf = malloc(dl->d_secsize)) == NULL)
379 		err(1, NULL);
380 
381 	/* Check that there is a protective MBR. */
382 	len = pread(devfd, secbuf, dl->d_secsize, 0);
383 	if (len != dl->d_secsize)
384 		err(4, "can't read mbr");
385 	memcpy(dp, &secbuf[DOSPARTOFF], sizeof(dp));
386 	if (gpt_chk_mbr(dp, DL_GETDSIZE(dl))) {
387 		free(secbuf);
388 		return (-1);
389 	}
390 
391 	/* Check GPT Header. */
392 	off = dl->d_secsize;	/* Read header from sector 1. */
393 	len = pread(devfd, secbuf, dl->d_secsize, off);
394 	if (len != dl->d_secsize)
395 		err(4, "can't pread gpt header");
396 
397 	memcpy(&gh, secbuf, sizeof(gh));
398 	free(secbuf);
399 
400 	/* Check signature */
401 	if (letoh64(gh.gh_sig) != GPTSIGNATURE)
402 		return (-1);
403 
404 	if (letoh32(gh.gh_rev) != GPTREVISION)
405 		return (-1);
406 
407 	ghsize = letoh32(gh.gh_size);
408 	if (ghsize < GPTMINHDRSIZE || ghsize > sizeof(struct gpt_header))
409 		return (-1);
410 
411 	/* Check checksum */
412 	orig_csum = gh.gh_csum;
413 	gh.gh_csum = 0;
414 	new_csum = crc32((unsigned char *)&gh, ghsize);
415 	gh.gh_csum = orig_csum;
416 	if (letoh32(orig_csum) != new_csum)
417 		return (-1);
418 
419 	off = letoh64(gh.gh_part_lba) * dl->d_secsize;
420 	ghpartsize = letoh32(gh.gh_part_size);
421 	ghpartspersec = dl->d_secsize / ghpartsize;
422 	ghpartnum = letoh32(gh.gh_part_num);
423 	if ((secbuf = malloc(dl->d_secsize)) == NULL)
424 		err(1, NULL);
425 	for (i = 0; i < (ghpartnum + ghpartspersec - 1) / ghpartspersec; i++) {
426 		len = pread(devfd, secbuf, dl->d_secsize, off);
427 		if (len != dl->d_secsize) {
428 			free(secbuf);
429 			return (-1);
430 		}
431 		memcpy(gp + i * ghpartspersec, secbuf,
432 		    ghpartspersec * sizeof(struct gpt_partition));
433 		off += dl->d_secsize;
434 	}
435 	free(secbuf);
436 	new_csum = crc32((unsigned char *)&gp, ghpartnum * ghpartsize);
437 	if (new_csum != letoh32(gh.gh_part_csum))
438 		return (-1);
439 
440 	start = 0;
441 	for (i = 0; i < ghpartnum && start == 0; i++) {
442 		if (memcmp(&gp[i].gp_type, &efisys_uuid,
443 		    sizeof(struct uuid)) == 0)
444 			start = letoh64(gp[i].gp_lba_start);
445 	}
446 
447 	if (start) {
448 		for (i = 0; i < MAXPARTITIONS; i++) {
449 			if (DL_GETPSIZE(&dl->d_partitions[i]) > 0 &&
450 			    DL_GETPOFFSET(&dl->d_partitions[i]) == start)
451 				return ('a' + i);
452 		}
453 	}
454 
455 	return (-1);
456 }
457 
458 int
459 findmbrfat(int devfd, struct disklabel *dl)
460 {
461 	struct dos_partition	 dp[NDOSPART];
462 	ssize_t			 len;
463 	u_int64_t		 start = 0;
464 	int			 i;
465 	u_int8_t		*secbuf;
466 
467 	if ((secbuf = malloc(dl->d_secsize)) == NULL)
468 		err(1, NULL);
469 
470 	/* Read MBR. */
471 	len = pread(devfd, secbuf, dl->d_secsize, 0);
472 	if (len != dl->d_secsize)
473 		err(4, "can't read mbr");
474 	memcpy(dp, &secbuf[DOSPARTOFF], sizeof(dp));
475 
476 	for (i = 0; i < NDOSPART; i++) {
477 		if (dp[i].dp_typ == DOSPTYP_UNUSED)
478 			continue;
479 		if (dp[i].dp_typ == DOSPTYP_FAT16L ||
480 		    dp[i].dp_typ == DOSPTYP_FAT32L ||
481 		    dp[i].dp_typ == DOSPTYP_EFISYS)
482 			start = dp[i].dp_start;
483 	}
484 
485 	free(secbuf);
486 
487 	if (start) {
488 		for (i = 0; i < MAXPARTITIONS; i++) {
489 			if (DL_GETPSIZE(&dl->d_partitions[i]) > 0 &&
490 			    DL_GETPOFFSET(&dl->d_partitions[i]) == start)
491 				return ('a' + i);
492 		}
493 	}
494 
495 	return (-1);
496 }
497