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