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
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 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
317 umount:
318 dst[mntlen] = '\0';
319 if (unmount(dst, MNT_FORCE) == -1)
320 err(1, "unmount('%s') failed", dst);
321
322 rmdir:
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
334 static int
write_firmware(const char * root,const char * mnt)335 write_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 */
397 static int
gpt_chk_mbr(struct dos_partition * dp,u_int64_t dsize)398 gpt_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
423 int
findgptefisys(int devfd,struct disklabel * dl)424 findgptefisys(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
522 int
findmbrfat(int devfd,struct disklabel * dl)523 findmbrfat(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