1/*	$OpenBSD: efi_installboot.c,v 1.10 2023/04/26 18:04:21 kn 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
71static int	create_filesystem(struct disklabel *, char);
72static void	write_filesystem(struct disklabel *, char);
73static int	write_firmware(const char *, const char *);
74static int	findgptefisys(int, struct disklabel *);
75static int	findmbrfat(int, struct disklabel *);
76
77void
78md_init(void)
79{
80	stages = 1;
81	stage1 = "/usr/mdec/" BOOTEFI_SRC;
82}
83
84void
85md_loadboot(void)
86{
87}
88
89void
90md_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
118void
119md_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
147static int
148create_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
191static void
192write_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	char cmd[60];
197	char dst[PATH_MAX];
198	char *src;
199	size_t mntlen, pathlen, srclen;
200	int rslt;
201
202	src = NULL;
203
204	/* Create directory for temporary mount point. */
205	strlcpy(dst, "/tmp/installboot.XXXXXXXXXX", sizeof(dst));
206	if (mkdtemp(dst) == NULL)
207		err(1, "mkdtemp('%s') failed", dst);
208	mntlen = strlen(dst);
209
210	/* Mount <duid>.<part> as msdos filesystem. */
211	memset(&args, 0, sizeof(args));
212	rslt = asprintf(&args.fspec,
213	    "%02hhx%02hhx%02hhx%02hhx%02hhx%02hhx%02hhx%02hhx.%c",
214            dl->d_uid[0], dl->d_uid[1], dl->d_uid[2], dl->d_uid[3],
215            dl->d_uid[4], dl->d_uid[5], dl->d_uid[6], dl->d_uid[7],
216	    part);
217	if (rslt == -1) {
218		warn("bad special device");
219		goto rmdir;
220	}
221
222	args.export_info.ex_root = -2;
223	args.export_info.ex_flags = 0;
224	args.flags = MSDOSFSMNT_LONGNAME;
225
226	if (mount(MOUNT_MSDOS, dst, 0, &args) == -1) {
227		/* Try fsck'ing it. */
228		rslt = snprintf(cmd, sizeof(cmd), fsckfmt, args.fspec);
229		if (rslt >= sizeof(cmd)) {
230			warnx("can't build fsck command");
231			rslt = -1;
232			goto rmdir;
233		}
234		rslt = system(cmd);
235		if (rslt == -1) {
236			warn("system('%s') failed", cmd);
237			goto rmdir;
238		}
239		if (mount(MOUNT_MSDOS, dst, 0, &args) == -1) {
240			/* Try newfs'ing it. */
241			rslt = create_filesystem(dl, part);
242			if (rslt == -1)
243				goto rmdir;
244			rslt = mount(MOUNT_MSDOS, dst, 0, &args);
245			if (rslt == -1) {
246				warn("unable to mount EFI System partition");
247				goto rmdir;
248			}
249		}
250	}
251
252	/* Create "/efi/boot" directory in <duid>.<part>. */
253	if (strlcat(dst, "/efi", sizeof(dst)) >= sizeof(dst)) {
254		rslt = -1;
255		warn("unable to build /efi directory");
256		goto umount;
257	}
258	rslt = mkdir(dst, 0755);
259	if (rslt == -1 && errno != EEXIST) {
260		warn("mkdir('%s') failed", dst);
261		goto umount;
262	}
263	if (strlcat(dst, "/boot", sizeof(dst)) >= sizeof(dst)) {
264		rslt = -1;
265		warn("unable to build /boot directory");
266		goto umount;
267	}
268	rslt = mkdir(dst, 0755);
269	if (rslt == -1 && errno != EEXIST) {
270		warn("mkdir('%s') failed", dst);
271		goto umount;
272	}
273
274	/* Copy EFI bootblocks to /efi/boot/. */
275	pathlen = strlen(dst);
276	if (strlcat(dst, "/" BOOTEFI_DST, sizeof(dst)) >= sizeof(dst)) {
277		rslt = -1;
278		warn("unable to build /%s path", BOOTEFI_DST);
279		goto umount;
280	}
281	src = fileprefix(root, "/usr/mdec/" BOOTEFI_SRC);
282	if (src == NULL) {
283		rslt = -1;
284		goto umount;
285	}
286	srclen = strlen(src);
287	if (verbose)
288		fprintf(stderr, "%s %s to %s\n",
289		    (nowrite ? "would copy" : "copying"), src, dst);
290	if (!nowrite) {
291		rslt = filecopy(src, dst);
292		if (rslt == -1)
293			goto umount;
294	}
295
296	/* Write /efi/boot/startup.nsh. */
297	dst[pathlen] = '\0';
298	if (strlcat(dst, "/startup.nsh", sizeof(dst)) >= sizeof(dst)) {
299		rslt = -1;
300		warn("unable to build /startup.nsh path");
301		goto umount;
302	}
303	if (verbose)
304		fprintf(stderr, "%s %s\n",
305		    (nowrite ? "would write" : "writing"), dst);
306	if (!nowrite) {
307		rslt = fileprintf(dst, "%s\n", BOOTEFI_DST);
308		if (rslt == -1)
309			goto umount;
310	}
311
312	dst[mntlen] = '\0';
313	rslt = write_firmware(root, dst);
314	if (rslt == -1)
315		warnx("unable to write firmware");
316
317umount:
318	dst[mntlen] = '\0';
319	if (unmount(dst, MNT_FORCE) == -1)
320		err(1, "unmount('%s') failed", dst);
321
322rmdir:
323	free(args.fspec);
324	dst[mntlen] = '\0';
325	if (rmdir(dst) == -1)
326		err(1, "rmdir('%s') failed", dst);
327
328	free(src);
329
330	if (rslt == -1)
331		exit(1);
332}
333
334static int
335write_firmware(const char *root, const char *mnt)
336{
337	char dst[PATH_MAX];
338	char fw[PATH_MAX];
339	char *src;
340	struct stat st;
341	int rslt;
342
343	strlcpy(dst, mnt, sizeof(dst));
344
345	/* Skip if no /etc/firmware exists */
346	rslt = snprintf(fw, sizeof(fw), "%s/%s", root, "etc/firmware");
347	if (rslt < 0 || rslt >= PATH_MAX) {
348		warnx("unable to build /etc/firmware path");
349		return -1;
350	}
351	if ((stat(fw, &st) != 0) || !S_ISDIR(st.st_mode))
352		return 0;
353
354	/* Copy apple-boot firmware to /m1n1/boot.bin if available */
355	src = fileprefix(fw, "/apple-boot.bin");
356	if (src == NULL)
357		return -1;
358	if (access(src, R_OK) == 0) {
359		if (strlcat(dst, "/m1n1", sizeof(dst)) >= sizeof(dst)) {
360			rslt = -1;
361			warnx("unable to build /m1n1 path");
362			goto cleanup;
363		}
364		if ((stat(dst, &st) != 0) || !S_ISDIR(st.st_mode)) {
365			rslt = 0;
366			goto cleanup;
367		}
368		if (strlcat(dst, "/boot.bin", sizeof(dst)) >= sizeof(dst)) {
369			rslt = -1;
370			warnx("unable to build /m1n1/boot.bin path");
371			goto cleanup;
372		}
373		if (verbose)
374			fprintf(stderr, "%s %s to %s\n",
375			    (nowrite ? "would copy" : "copying"), src, dst);
376		if (!nowrite) {
377			rslt = filecopy(src, dst);
378			if (rslt == -1)
379				goto cleanup;
380		}
381	}
382	rslt = 0;
383
384 cleanup:
385	free(src);
386	return rslt;
387}
388
389/*
390 * Returns 0 if the MBR with the provided partition array is a GPT protective
391 * MBR, and returns 1 otherwise. A GPT protective MBR would have one and only
392 * one MBR partition, an EFI partition that either covers the whole disk or as
393 * much of it as is possible with a 32bit size field.
394 *
395 * NOTE: MS always uses a size of UINT32_MAX for the EFI partition!**
396 */
397static int
398gpt_chk_mbr(struct dos_partition *dp, u_int64_t dsize)
399{
400	struct dos_partition *dp2;
401	int efi, found, i;
402	u_int32_t psize;
403
404	found = efi = 0;
405	for (dp2=dp, i=0; i < NDOSPART; i++, dp2++) {
406		if (dp2->dp_typ == DOSPTYP_UNUSED)
407			continue;
408		found++;
409		if (dp2->dp_typ != DOSPTYP_EFI)
410			continue;
411		if (letoh32(dp2->dp_start) != GPTSECTOR)
412			continue;
413		psize = letoh32(dp2->dp_size);
414		if (psize <= (dsize - GPTSECTOR) || psize == UINT32_MAX)
415			efi++;
416	}
417	if (found == 1 && efi == 1)
418		return (0);
419
420	return (1);
421}
422
423int
424findgptefisys(int devfd, struct disklabel *dl)
425{
426	struct gpt_partition	 gp[NGPTPARTITIONS];
427	struct gpt_header	 gh;
428	struct dos_partition	 dp[NDOSPART];
429	struct uuid		 efisys_uuid;
430	const char		 efisys_uuid_code[] = GPT_UUID_EFI_SYSTEM;
431	off_t			 off;
432	ssize_t			 len;
433	u_int64_t		 start;
434	int			 i;
435	uint32_t		 orig_csum, new_csum;
436	uint32_t		 ghsize, ghpartsize, ghpartnum, ghpartspersec;
437	u_int8_t		*secbuf;
438
439	/* Prepare EFI System UUID */
440	uuid_dec_be(efisys_uuid_code, &efisys_uuid);
441
442	if ((secbuf = malloc(dl->d_secsize)) == NULL)
443		err(1, NULL);
444
445	/* Check that there is a protective MBR. */
446	len = pread(devfd, secbuf, dl->d_secsize, 0);
447	if (len != dl->d_secsize)
448		err(4, "can't read mbr");
449	memcpy(dp, &secbuf[DOSPARTOFF], sizeof(dp));
450	if (gpt_chk_mbr(dp, DL_GETDSIZE(dl))) {
451		free(secbuf);
452		return (-1);
453	}
454
455	/* Check GPT Header. */
456	off = dl->d_secsize;	/* Read header from sector 1. */
457	len = pread(devfd, secbuf, dl->d_secsize, off);
458	if (len != dl->d_secsize)
459		err(4, "can't pread gpt header");
460
461	memcpy(&gh, secbuf, sizeof(gh));
462	free(secbuf);
463
464	/* Check signature */
465	if (letoh64(gh.gh_sig) != GPTSIGNATURE)
466		return (-1);
467
468	if (letoh32(gh.gh_rev) != GPTREVISION)
469		return (-1);
470
471	ghsize = letoh32(gh.gh_size);
472	if (ghsize < GPTMINHDRSIZE || ghsize > sizeof(struct gpt_header))
473		return (-1);
474
475	/* Check checksum */
476	orig_csum = gh.gh_csum;
477	gh.gh_csum = 0;
478	new_csum = crc32((unsigned char *)&gh, ghsize);
479	gh.gh_csum = orig_csum;
480	if (letoh32(orig_csum) != new_csum)
481		return (-1);
482
483	off = letoh64(gh.gh_part_lba) * dl->d_secsize;
484	ghpartsize = letoh32(gh.gh_part_size);
485	ghpartspersec = dl->d_secsize / ghpartsize;
486	ghpartnum = letoh32(gh.gh_part_num);
487	if ((secbuf = malloc(dl->d_secsize)) == NULL)
488		err(1, NULL);
489	for (i = 0; i < (ghpartnum + ghpartspersec - 1) / ghpartspersec; i++) {
490		len = pread(devfd, secbuf, dl->d_secsize, off);
491		if (len != dl->d_secsize) {
492			free(secbuf);
493			return (-1);
494		}
495		memcpy(gp + i * ghpartspersec, secbuf,
496		    ghpartspersec * sizeof(struct gpt_partition));
497		off += dl->d_secsize;
498	}
499	free(secbuf);
500	new_csum = crc32((unsigned char *)&gp, ghpartnum * ghpartsize);
501	if (new_csum != letoh32(gh.gh_part_csum))
502		return (-1);
503
504	start = 0;
505	for (i = 0; i < ghpartnum && start == 0; i++) {
506		if (memcmp(&gp[i].gp_type, &efisys_uuid,
507		    sizeof(struct uuid)) == 0)
508			start = letoh64(gp[i].gp_lba_start);
509	}
510
511	if (start) {
512		for (i = 0; i < MAXPARTITIONS; i++) {
513			if (DL_GETPSIZE(&dl->d_partitions[i]) > 0 &&
514			    DL_GETPOFFSET(&dl->d_partitions[i]) == start)
515				return ('a' + i);
516		}
517	}
518
519	return (-1);
520}
521
522int
523findmbrfat(int devfd, struct disklabel *dl)
524{
525	struct dos_partition	 dp[NDOSPART];
526	ssize_t			 len;
527	u_int64_t		 start = 0;
528	int			 i;
529	u_int8_t		*secbuf;
530
531	if ((secbuf = malloc(dl->d_secsize)) == NULL)
532		err(1, NULL);
533
534	/* Read MBR. */
535	len = pread(devfd, secbuf, dl->d_secsize, 0);
536	if (len != dl->d_secsize)
537		err(4, "can't read mbr");
538	memcpy(dp, &secbuf[DOSPARTOFF], sizeof(dp));
539
540	for (i = 0; i < NDOSPART; i++) {
541		if (dp[i].dp_typ == DOSPTYP_UNUSED)
542			continue;
543		if (dp[i].dp_typ == DOSPTYP_FAT16L ||
544		    dp[i].dp_typ == DOSPTYP_FAT32L ||
545		    dp[i].dp_typ == DOSPTYP_EFISYS)
546			start = dp[i].dp_start;
547	}
548
549	free(secbuf);
550
551	if (start) {
552		for (i = 0; i < MAXPARTITIONS; i++) {
553			if (DL_GETPSIZE(&dl->d_partitions[i]) > 0 &&
554			    DL_GETPOFFSET(&dl->d_partitions[i]) == start)
555				return ('a' + i);
556		}
557	}
558
559	return (-1);
560}
561