1 /*	$OpenBSD: efi_installboot.c,v 1.12 2024/11/08 10:43:07 kettenis 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	write_firmware(const char *, const char *);
74 static int	findgptefisys(int, struct disklabel *);
75 static int	findmbrfat(int, struct disklabel *);
76 
77 void
md_init(void)78 md_init(void)
79 {
80 	stages = 1;
81 	stage1 = "/usr/mdec/" BOOTEFI_SRC;
82 }
83 
84 void
md_loadboot(void)85 md_loadboot(void)
86 {
87 }
88 
89 void
md_prepareboot(int devfd,char * dev)90 md_prepareboot(int devfd, char *dev)
91 {
92 	struct disklabel dl;
93 	int part;
94 
95 	/* Get and check disklabel. */
96 	if (ioctl(devfd, DIOCGDINFO, &dl) == -1)
97 		err(1, "disklabel: %s", dev);
98 	if (dl.d_magic != DISKMAGIC)
99 		errx(1, "bad disklabel magic=0x%08x", dl.d_magic);
100 
101 	/* Warn on unknown disklabel types. */
102 	if (dl.d_type == 0)
103 		warnx("disklabel type unknown");
104 
105 	part = findgptefisys(devfd, &dl);
106 	if (part != -1) {
107 		create_filesystem(&dl, (char)part);
108 		return;
109 	}
110 
111 	part = findmbrfat(devfd, &dl);
112 	if (part != -1) {
113 		create_filesystem(&dl, (char)part);
114 		return;
115 	}
116 }
117 
118 void
md_installboot(int devfd,char * dev)119 md_installboot(int devfd, char *dev)
120 {
121 	struct disklabel dl;
122 	int part;
123 
124 	/* Get and check disklabel. */
125 	if (ioctl(devfd, DIOCGDINFO, &dl) == -1)
126 		err(1, "disklabel: %s", dev);
127 	if (dl.d_magic != DISKMAGIC)
128 		errx(1, "bad disklabel magic=0x%08x", dl.d_magic);
129 
130 	/* Warn on unknown disklabel types. */
131 	if (dl.d_type == 0)
132 		warnx("disklabel type unknown");
133 
134 	part = findgptefisys(devfd, &dl);
135 	if (part != -1) {
136 		write_filesystem(&dl, (char)part);
137 		return;
138 	}
139 
140 	part = findmbrfat(devfd, &dl);
141 	if (part != -1) {
142 		write_filesystem(&dl, (char)part);
143 		return;
144 	}
145 }
146 
147 static int
create_filesystem(struct disklabel * dl,char part)148 create_filesystem(struct disklabel *dl, char part)
149 {
150 	static const char *newfsfmt = "/sbin/newfs -t msdos %s >/dev/null";
151 	struct msdosfs_args args;
152 	char cmd[60];
153 	int rslt;
154 
155 	/* Newfs <duid>.<part> as msdos filesystem. */
156 	memset(&args, 0, sizeof(args));
157 	rslt = asprintf(&args.fspec,
158 	    "%02hhx%02hhx%02hhx%02hhx%02hhx%02hhx%02hhx%02hhx.%c",
159             dl->d_uid[0], dl->d_uid[1], dl->d_uid[2], dl->d_uid[3],
160             dl->d_uid[4], dl->d_uid[5], dl->d_uid[6], dl->d_uid[7],
161 	    part);
162 	if (rslt == -1) {
163 		warn("bad special device");
164 		return rslt;
165 	}
166 
167 	rslt = snprintf(cmd, sizeof(cmd), newfsfmt, args.fspec);
168 	if (rslt >= sizeof(cmd)) {
169 		warnx("can't build newfs command");
170 		free(args.fspec);
171 		rslt = -1;
172 		return rslt;
173 	}
174 
175 	if (verbose)
176 		fprintf(stderr, "%s %s\n",
177 		    (nowrite ? "would newfs" : "newfsing"), args.fspec);
178 	if (!nowrite) {
179 		rslt = system(cmd);
180 		if (rslt == -1) {
181 			warn("system('%s') failed", cmd);
182 			free(args.fspec);
183 			return rslt;
184 		}
185 	}
186 
187 	free(args.fspec);
188 	return 0;
189 }
190 
191 static void
write_filesystem(struct disklabel * dl,char part)192 write_filesystem(struct disklabel *dl, char part)
193 {
194 	static const char *fsckfmt = "/sbin/fsck -t msdos %s >/dev/null";
195 	struct msdosfs_args args;
196 	struct statfs sf;
197 	char cmd[60];
198 	char dst[PATH_MAX];
199 	char *src;
200 	size_t mntlen, pathlen, srclen;
201 	int rslt;
202 
203 	src = NULL;
204 
205 	/* Create directory for temporary mount point. */
206 	strlcpy(dst, "/tmp/installboot.XXXXXXXXXX", sizeof(dst));
207 	if (mkdtemp(dst) == NULL)
208 		err(1, "mkdtemp('%s') failed", dst);
209 	mntlen = strlen(dst);
210 
211 	/* Mount <duid>.<part> as msdos filesystem. */
212 	memset(&args, 0, sizeof(args));
213 	rslt = asprintf(&args.fspec,
214 	    "%02hhx%02hhx%02hhx%02hhx%02hhx%02hhx%02hhx%02hhx.%c",
215             dl->d_uid[0], dl->d_uid[1], dl->d_uid[2], dl->d_uid[3],
216             dl->d_uid[4], dl->d_uid[5], dl->d_uid[6], dl->d_uid[7],
217 	    part);
218 	if (rslt == -1) {
219 		warn("bad special device");
220 		goto rmdir;
221 	}
222 
223 	args.export_info.ex_root = -2;
224 	args.export_info.ex_flags = 0;
225 	args.flags = MSDOSFSMNT_LONGNAME;
226 
227 	if (mount(MOUNT_MSDOS, dst, 0, &args) == -1) {
228 		/* Try fsck'ing it. */
229 		rslt = snprintf(cmd, sizeof(cmd), fsckfmt, args.fspec);
230 		if (rslt >= sizeof(cmd)) {
231 			warnx("can't build fsck command");
232 			rslt = -1;
233 			goto rmdir;
234 		}
235 		rslt = system(cmd);
236 		if (rslt == -1) {
237 			warn("system('%s') failed", cmd);
238 			goto rmdir;
239 		}
240 		if (mount(MOUNT_MSDOS, dst, 0, &args) == -1) {
241 			/* Try newfs'ing it. */
242 			rslt = create_filesystem(dl, part);
243 			if (rslt == -1)
244 				goto rmdir;
245 			rslt = mount(MOUNT_MSDOS, dst, 0, &args);
246 			if (rslt == -1) {
247 				warn("unable to mount EFI System partition");
248 				goto rmdir;
249 			}
250 		}
251 	}
252 
253 	/* Create "/efi/boot" directory in <duid>.<part>. */
254 	if (strlcat(dst, "/efi", sizeof(dst)) >= sizeof(dst)) {
255 		rslt = -1;
256 		warn("unable to build /efi directory");
257 		goto umount;
258 	}
259 	rslt = mkdir(dst, 0755);
260 	if (rslt == -1 && errno != EEXIST) {
261 		warn("mkdir('%s') failed", dst);
262 		goto umount;
263 	}
264 	if (strlcat(dst, "/boot", sizeof(dst)) >= sizeof(dst)) {
265 		rslt = -1;
266 		warn("unable to build /boot directory");
267 		goto umount;
268 	}
269 	rslt = mkdir(dst, 0755);
270 	if (rslt == -1 && errno != EEXIST) {
271 		warn("mkdir('%s') failed", dst);
272 		goto umount;
273 	}
274 
275 	/* Copy EFI bootblocks to /efi/boot/. */
276 	pathlen = strlen(dst);
277 	if (strlcat(dst, "/" BOOTEFI_DST, sizeof(dst)) >= sizeof(dst)) {
278 		rslt = -1;
279 		warn("unable to build /%s path", BOOTEFI_DST);
280 		goto umount;
281 	}
282 	src = fileprefix(root, "/usr/mdec/" BOOTEFI_SRC);
283 	if (src == NULL) {
284 		rslt = -1;
285 		goto umount;
286 	}
287 	srclen = strlen(src);
288 	if (verbose)
289 		fprintf(stderr, "%s %s to %s\n",
290 		    (nowrite ? "would copy" : "copying"), src, dst);
291 	if (!nowrite) {
292 		rslt = filecopy(src, dst);
293 		if (rslt == -1)
294 			goto umount;
295 	}
296 
297 	/* Write /efi/boot/startup.nsh. */
298 	dst[pathlen] = '\0';
299 	if (strlcat(dst, "/startup.nsh", sizeof(dst)) >= sizeof(dst)) {
300 		rslt = -1;
301 		warn("unable to build /startup.nsh path");
302 		goto umount;
303 	}
304 	if (verbose)
305 		fprintf(stderr, "%s %s\n",
306 		    (nowrite ? "would write" : "writing"), dst);
307 	if (!nowrite) {
308 		rslt = fileprintf(dst, "%s\n", BOOTEFI_DST);
309 		if (rslt == -1)
310 			goto umount;
311 	}
312 
313 	/* Skip installing a 2nd copy if we have a small filesystem. */
314 	if (statfs(dst, &sf) || sf.f_blocks < 2048) {
315 		rslt = 0;
316 		goto firmware;
317 	}
318 
319 	/* Create "/efi/openbsd" directory in <duid>.<part>. */
320 	dst[mntlen] = '\0';
321 	if (strlcat(dst, "/efi/openbsd", sizeof(dst)) >= sizeof(dst)) {
322 		rslt = -1;
323 		warn("unable to build /efi/openbsd directory");
324 		goto umount;
325 	}
326 	rslt = mkdir(dst, 0755);
327 	if (rslt == -1 && errno != EEXIST) {
328 		warn("mkdir('%s') failed", dst);
329 		goto umount;
330 	}
331 
332 	/* Copy EFI bootblocks to /efi/openbsd/. */
333 	if (strlcat(dst, "/" BOOTEFI_DST, sizeof(dst)) >= sizeof(dst)) {
334 		rslt = -1;
335 		warn("unable to build /%s path", BOOTEFI_DST);
336 		goto umount;
337 	}
338 	src = fileprefix(root, "/usr/mdec/" BOOTEFI_SRC);
339 	if (src == NULL) {
340 		rslt = -1;
341 		goto umount;
342 	}
343 	srclen = strlen(src);
344 	if (verbose)
345 		fprintf(stderr, "%s %s to %s\n",
346 		    (nowrite ? "would copy" : "copying"), src, dst);
347 	if (!nowrite) {
348 		rslt = filecopy(src, dst);
349 		if (rslt == -1)
350 			goto umount;
351 	}
352 
353 firmware:
354 	dst[mntlen] = '\0';
355 	rslt = write_firmware(root, dst);
356 	if (rslt == -1)
357 		warnx("unable to write firmware");
358 
359 umount:
360 	dst[mntlen] = '\0';
361 	if (unmount(dst, MNT_FORCE) == -1)
362 		err(1, "unmount('%s') failed", dst);
363 
364 rmdir:
365 	free(args.fspec);
366 	dst[mntlen] = '\0';
367 	if (rmdir(dst) == -1)
368 		err(1, "rmdir('%s') failed", dst);
369 
370 	free(src);
371 
372 	if (rslt == -1)
373 		exit(1);
374 }
375 
376 static int
write_firmware(const char * root,const char * mnt)377 write_firmware(const char *root, const char *mnt)
378 {
379 	char dst[PATH_MAX];
380 	char fw[PATH_MAX];
381 	char *src;
382 	struct stat st;
383 	int rslt;
384 
385 	strlcpy(dst, mnt, sizeof(dst));
386 
387 	/* Skip if no /etc/firmware exists */
388 	rslt = snprintf(fw, sizeof(fw), "%s/%s", root, "etc/firmware");
389 	if (rslt < 0 || rslt >= PATH_MAX) {
390 		warnx("unable to build /etc/firmware path");
391 		return -1;
392 	}
393 	if ((stat(fw, &st) != 0) || !S_ISDIR(st.st_mode))
394 		return 0;
395 
396 	/* Copy apple-boot firmware to /m1n1/boot.bin if available */
397 	src = fileprefix(fw, "/apple-boot.bin");
398 	if (src == NULL)
399 		return -1;
400 	if (access(src, R_OK) == 0) {
401 		if (strlcat(dst, "/m1n1", sizeof(dst)) >= sizeof(dst)) {
402 			rslt = -1;
403 			warnx("unable to build /m1n1 path");
404 			goto cleanup;
405 		}
406 		if ((stat(dst, &st) != 0) || !S_ISDIR(st.st_mode)) {
407 			rslt = 0;
408 			goto cleanup;
409 		}
410 		if (strlcat(dst, "/boot.bin", sizeof(dst)) >= sizeof(dst)) {
411 			rslt = -1;
412 			warnx("unable to build /m1n1/boot.bin path");
413 			goto cleanup;
414 		}
415 		if (verbose)
416 			fprintf(stderr, "%s %s to %s\n",
417 			    (nowrite ? "would copy" : "copying"), src, dst);
418 		if (!nowrite) {
419 			rslt = filecopy(src, dst);
420 			if (rslt == -1)
421 				goto cleanup;
422 		}
423 	}
424 	rslt = 0;
425 
426  cleanup:
427 	free(src);
428 	return rslt;
429 }
430 
431 /*
432  * Returns 0 if the MBR with the provided partition array is a GPT protective
433  * MBR, and returns 1 otherwise. A GPT protective MBR would have one and only
434  * one MBR partition, an EFI partition that either covers the whole disk or as
435  * much of it as is possible with a 32bit size field.
436  *
437  * NOTE: MS always uses a size of UINT32_MAX for the EFI partition!**
438  */
439 static int
gpt_chk_mbr(struct dos_partition * dp,u_int64_t dsize)440 gpt_chk_mbr(struct dos_partition *dp, u_int64_t dsize)
441 {
442 	struct dos_partition *dp2;
443 	int efi, found, i;
444 	u_int32_t psize;
445 
446 	found = efi = 0;
447 	for (dp2=dp, i=0; i < NDOSPART; i++, dp2++) {
448 		if (dp2->dp_typ == DOSPTYP_UNUSED)
449 			continue;
450 		found++;
451 		if (dp2->dp_typ != DOSPTYP_EFI)
452 			continue;
453 		if (letoh32(dp2->dp_start) != GPTSECTOR)
454 			continue;
455 		psize = letoh32(dp2->dp_size);
456 		if (psize <= (dsize - GPTSECTOR) || psize == UINT32_MAX)
457 			efi++;
458 	}
459 	if (found == 1 && efi == 1)
460 		return (0);
461 
462 	return (1);
463 }
464 
465 int
findgptefisys(int devfd,struct disklabel * dl)466 findgptefisys(int devfd, struct disklabel *dl)
467 {
468 	struct gpt_partition	 gp[NGPTPARTITIONS];
469 	struct gpt_header	 gh;
470 	struct dos_partition	 dp[NDOSPART];
471 	struct uuid		 efisys_uuid;
472 	const char		 efisys_uuid_code[] = GPT_UUID_EFI_SYSTEM;
473 	off_t			 off;
474 	ssize_t			 len;
475 	u_int64_t		 start;
476 	int			 i;
477 	uint32_t		 orig_csum, new_csum;
478 	uint32_t		 ghsize, ghpartsize, ghpartnum, ghpartspersec;
479 	u_int8_t		*secbuf;
480 
481 	/* Prepare EFI System UUID */
482 	uuid_dec_be(efisys_uuid_code, &efisys_uuid);
483 
484 	if ((secbuf = malloc(dl->d_secsize)) == NULL)
485 		err(1, NULL);
486 
487 	/* Check that there is a protective MBR. */
488 	len = pread(devfd, secbuf, dl->d_secsize, 0);
489 	if (len != dl->d_secsize)
490 		err(4, "can't read mbr");
491 	memcpy(dp, &secbuf[DOSPARTOFF], sizeof(dp));
492 	if (gpt_chk_mbr(dp, DL_GETDSIZE(dl))) {
493 		free(secbuf);
494 		return (-1);
495 	}
496 
497 	/* Check GPT Header. */
498 	off = dl->d_secsize;	/* Read header from sector 1. */
499 	len = pread(devfd, secbuf, dl->d_secsize, off);
500 	if (len != dl->d_secsize)
501 		err(4, "can't pread gpt header");
502 
503 	memcpy(&gh, secbuf, sizeof(gh));
504 	free(secbuf);
505 
506 	/* Check signature */
507 	if (letoh64(gh.gh_sig) != GPTSIGNATURE)
508 		return (-1);
509 
510 	if (letoh32(gh.gh_rev) != GPTREVISION)
511 		return (-1);
512 
513 	ghsize = letoh32(gh.gh_size);
514 	if (ghsize < GPTMINHDRSIZE || ghsize > sizeof(struct gpt_header))
515 		return (-1);
516 
517 	/* Check checksum */
518 	orig_csum = gh.gh_csum;
519 	gh.gh_csum = 0;
520 	new_csum = crc32((unsigned char *)&gh, ghsize);
521 	gh.gh_csum = orig_csum;
522 	if (letoh32(orig_csum) != new_csum)
523 		return (-1);
524 
525 	off = letoh64(gh.gh_part_lba) * dl->d_secsize;
526 	ghpartsize = letoh32(gh.gh_part_size);
527 	ghpartspersec = dl->d_secsize / ghpartsize;
528 	ghpartnum = letoh32(gh.gh_part_num);
529 	if ((secbuf = malloc(dl->d_secsize)) == NULL)
530 		err(1, NULL);
531 	for (i = 0; i < (ghpartnum + ghpartspersec - 1) / ghpartspersec; i++) {
532 		len = pread(devfd, secbuf, dl->d_secsize, off);
533 		if (len != dl->d_secsize) {
534 			free(secbuf);
535 			return (-1);
536 		}
537 		memcpy(gp + i * ghpartspersec, secbuf,
538 		    ghpartspersec * sizeof(struct gpt_partition));
539 		off += dl->d_secsize;
540 	}
541 	free(secbuf);
542 	new_csum = crc32((unsigned char *)&gp, ghpartnum * ghpartsize);
543 	if (new_csum != letoh32(gh.gh_part_csum))
544 		return (-1);
545 
546 	start = 0;
547 	for (i = 0; i < ghpartnum && start == 0; i++) {
548 		if (memcmp(&gp[i].gp_type, &efisys_uuid,
549 		    sizeof(struct uuid)) == 0)
550 			start = letoh64(gp[i].gp_lba_start);
551 	}
552 
553 	if (start) {
554 		for (i = 0; i < MAXPARTITIONS; i++) {
555 			if (DL_GETPSIZE(&dl->d_partitions[i]) > 0 &&
556 			    DL_GETPOFFSET(&dl->d_partitions[i]) == start)
557 				return ('a' + i);
558 		}
559 	}
560 
561 	return (-1);
562 }
563 
564 int
findmbrfat(int devfd,struct disklabel * dl)565 findmbrfat(int devfd, struct disklabel *dl)
566 {
567 	struct dos_partition	 dp[NDOSPART];
568 	ssize_t			 len;
569 	u_int64_t		 start = 0;
570 	int			 i;
571 	u_int8_t		*secbuf;
572 
573 	if ((secbuf = malloc(dl->d_secsize)) == NULL)
574 		err(1, NULL);
575 
576 	/* Read MBR. */
577 	len = pread(devfd, secbuf, dl->d_secsize, 0);
578 	if (len != dl->d_secsize)
579 		err(4, "can't read mbr");
580 	memcpy(dp, &secbuf[DOSPARTOFF], sizeof(dp));
581 
582 	for (i = 0; i < NDOSPART; i++) {
583 		if (dp[i].dp_typ == DOSPTYP_UNUSED)
584 			continue;
585 		if (dp[i].dp_typ == DOSPTYP_FAT16L ||
586 		    dp[i].dp_typ == DOSPTYP_FAT32L ||
587 		    dp[i].dp_typ == DOSPTYP_EFISYS)
588 			start = dp[i].dp_start;
589 	}
590 
591 	free(secbuf);
592 
593 	if (start) {
594 		for (i = 0; i < MAXPARTITIONS; i++) {
595 			if (DL_GETPSIZE(&dl->d_partitions[i]) > 0 &&
596 			    DL_GETPOFFSET(&dl->d_partitions[i]) == start)
597 				return ('a' + i);
598 		}
599 	}
600 
601 	return (-1);
602 }
603