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