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 78 md_init(void) 79 { 80 stages = 1; 81 stage1 = "/usr/mdec/" BOOTEFI_SRC; 82 } 83 84 void 85 md_loadboot(void) 86 { 87 } 88 89 void 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 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 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 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 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 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 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 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