| 1 | /* |
| 2 | * Copyright (c) 2006-2021 Apple Inc. All rights reserved. |
| 3 | * |
| 4 | * @APPLE_OSREFERENCE_LICENSE_HEADER_START@ |
| 5 | * |
| 6 | * This file contains Original Code and/or Modifications of Original Code |
| 7 | * as defined in and that are subject to the Apple Public Source License |
| 8 | * Version 2.0 (the 'License'). You may not use this file except in |
| 9 | * compliance with the License. The rights granted to you under the License |
| 10 | * may not be used to create, or enable the creation or redistribution of, |
| 11 | * unlawful or unlicensed copies of an Apple operating system, or to |
| 12 | * circumvent, violate, or enable the circumvention or violation of, any |
| 13 | * terms of an Apple operating system software license agreement. |
| 14 | * |
| 15 | * Please obtain a copy of the License at |
| 16 | * http://www.opensource.apple.com/apsl/ and read it before using this file. |
| 17 | * |
| 18 | * The Original Code and all software distributed under the License are |
| 19 | * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER |
| 20 | * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, |
| 21 | * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, |
| 22 | * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. |
| 23 | * Please see the License for the specific language governing rights and |
| 24 | * limitations under the License. |
| 25 | * |
| 26 | * @APPLE_OSREFERENCE_LICENSE_HEADER_END@ |
| 27 | */ |
| 28 | |
| 29 | #include <sys/param.h> |
| 30 | #include <sys/kernel.h> |
| 31 | #include <sys/proc_internal.h> |
| 32 | #include <sys/systm.h> |
| 33 | #include <sys/systm.h> |
| 34 | #include <sys/mount_internal.h> |
| 35 | #include <sys/fsctl.h> |
| 36 | #include <sys/filedesc.h> |
| 37 | #include <sys/vnode_internal.h> |
| 38 | #include <sys/imageboot.h> |
| 39 | #include <kern/assert.h> |
| 40 | |
| 41 | #include <sys/namei.h> |
| 42 | #include <sys/fcntl.h> |
| 43 | #include <sys/vnode.h> |
| 44 | #include <sys/xattr.h> |
| 45 | #include <sys/sysproto.h> |
| 46 | #include <sys/csr.h> |
| 47 | #include <miscfs/devfs/devfsdefs.h> |
| 48 | #include <libkern/crypto/sha2.h> |
| 49 | #include <libkern/crypto/rsa.h> |
| 50 | #include <libkern/OSKextLibPrivate.h> |
| 51 | #include <sys/ubc_internal.h> |
| 52 | |
| 53 | #if CONFIG_IMAGEBOOT_IMG4 |
| 54 | #include <libkern/img4/interface.h> |
| 55 | #include <img4/firmware.h> |
| 56 | #endif |
| 57 | |
| 58 | #include <kern/kalloc.h> |
| 59 | #include <os/overflow.h> |
| 60 | #include <vm/vm_kern.h> |
| 61 | |
| 62 | #include <pexpert/pexpert.h> |
| 63 | #include <kern/chunklist.h> |
| 64 | |
| 65 | extern int (*mountroot)(void); |
| 66 | extern char rootdevice[DEVMAXNAMESIZE]; |
| 67 | |
| 68 | #define DEBUG_IMAGEBOOT 0 |
| 69 | |
| 70 | #if DEBUG_IMAGEBOOT |
| 71 | #define DBG_TRACE(...) printf("imageboot: " __VA_ARGS__) |
| 72 | #else |
| 73 | #define DBG_TRACE(...) do {} while(0) |
| 74 | #endif |
| 75 | |
| 76 | #define AUTHDBG(fmt, args...) do { printf("%s: " fmt "\n", __func__, ##args); } while (0) |
| 77 | #define AUTHPRNT(fmt, args...) do { printf("%s: " fmt "\n", __func__, ##args); } while (0) |
| 78 | |
| 79 | extern int di_root_image_ext(const char *path, char *devname, size_t devsz, dev_t *dev_p, bool removable); |
| 80 | extern int di_root_image(const char *path, char *devname, size_t devsz, dev_t *dev_p); |
| 81 | extern int di_root_ramfile_buf(void *buf, size_t bufsz, char *devname, size_t devsz, dev_t *dev_p); |
| 82 | |
| 83 | static boolean_t imageboot_setup_new(imageboot_type_t type); |
| 84 | |
| 85 | void *ubc_getobject_from_filename(const char *filename, struct vnode **vpp, off_t *file_size); |
| 86 | |
| 87 | extern lck_rw_t rootvnode_rw_lock; |
| 88 | |
| 89 | #define kIBFilePrefix "file://" |
| 90 | |
| 91 | __private_extern__ int |
| 92 | imageboot_format_is_valid(const char *root_path) |
| 93 | { |
| 94 | return strncmp(s1: root_path, kIBFilePrefix, |
| 95 | n: strlen(kIBFilePrefix)) == 0; |
| 96 | } |
| 97 | |
| 98 | static void |
| 99 | vnode_get_and_drop_always(vnode_t vp) |
| 100 | { |
| 101 | vnode_getalways(vp); |
| 102 | vnode_rele(vp); |
| 103 | vnode_put(vp); |
| 104 | } |
| 105 | |
| 106 | __private_extern__ bool |
| 107 | imageboot_desired(void) |
| 108 | { |
| 109 | bool do_imageboot = false; |
| 110 | |
| 111 | char *root_path = NULL; |
| 112 | root_path = zalloc(view: ZV_NAMEI); |
| 113 | /* |
| 114 | * Check for first layer DMG rooting. |
| 115 | * |
| 116 | * Note that here we are principally concerned with whether or not we |
| 117 | * SHOULD try to imageboot, not whether or not we are going to be able to. |
| 118 | * |
| 119 | * If NONE of the boot-args are present, then assume that image-rooting |
| 120 | * is not requested. |
| 121 | * |
| 122 | * [!! Note parens guard the entire logically OR'd set of statements, below. It validates |
| 123 | * that NONE of the below-mentioned boot-args is present...!!] |
| 124 | */ |
| 125 | if (!(PE_parse_boot_argn(arg_string: "rp0" , arg_ptr: root_path, MAXPATHLEN) || |
| 126 | #if CONFIG_IMAGEBOOT_IMG4 |
| 127 | PE_parse_boot_argn("arp0" , root_path, MAXPATHLEN) || |
| 128 | #endif |
| 129 | PE_parse_boot_argn(arg_string: "rp" , arg_ptr: root_path, MAXPATHLEN) || |
| 130 | PE_parse_boot_argn(IMAGEBOOT_ROOT_ARG, arg_ptr: root_path, MAXPATHLEN) || |
| 131 | PE_parse_boot_argn(IMAGEBOOT_AUTHROOT_ARG, arg_ptr: root_path, MAXPATHLEN))) { |
| 132 | /* explicitly set to false */ |
| 133 | do_imageboot = false; |
| 134 | } else { |
| 135 | /* now sanity check the file-path format */ |
| 136 | if (imageboot_format_is_valid(root_path)) { |
| 137 | DBG_TRACE("%s: Found %s\n" , __FUNCTION__, root_path); |
| 138 | /* root_path looks good and we have one of the aforementioned bootargs */ |
| 139 | do_imageboot = true; |
| 140 | } else { |
| 141 | /* explicitly set to false */ |
| 142 | do_imageboot = false; |
| 143 | } |
| 144 | } |
| 145 | |
| 146 | zfree(ZV_NAMEI, root_path); |
| 147 | return do_imageboot; |
| 148 | } |
| 149 | |
| 150 | __private_extern__ imageboot_type_t |
| 151 | imageboot_needed(void) |
| 152 | { |
| 153 | imageboot_type_t result = IMAGEBOOT_NONE; |
| 154 | char *root_path = NULL; |
| 155 | |
| 156 | DBG_TRACE("%s: checking for presence of root path\n" , __FUNCTION__); |
| 157 | |
| 158 | if (!imageboot_desired()) { |
| 159 | goto out; |
| 160 | } |
| 161 | |
| 162 | root_path = zalloc(view: ZV_NAMEI); |
| 163 | result = IMAGEBOOT_DMG; |
| 164 | |
| 165 | /* Check for second layer */ |
| 166 | if (!(PE_parse_boot_argn(arg_string: "rp1" , arg_ptr: root_path, MAXPATHLEN) || |
| 167 | PE_parse_boot_argn(IMAGEBOOT_CONTAINER_ARG, arg_ptr: root_path, MAXPATHLEN))) { |
| 168 | goto out; |
| 169 | } |
| 170 | |
| 171 | /* Sanity-check second layer */ |
| 172 | if (imageboot_format_is_valid(root_path)) { |
| 173 | DBG_TRACE("%s: Found %s\n" , __FUNCTION__, root_path); |
| 174 | } else { |
| 175 | panic("%s: Invalid URL scheme for %s" , |
| 176 | __FUNCTION__, root_path); |
| 177 | } |
| 178 | |
| 179 | out: |
| 180 | if (root_path != NULL) { |
| 181 | zfree(ZV_NAMEI, root_path); |
| 182 | } |
| 183 | return result; |
| 184 | } |
| 185 | |
| 186 | extern bool IOBaseSystemARVRootHashAvailable(void); |
| 187 | |
| 188 | |
| 189 | /* |
| 190 | * Mounts new filesystem based on image path, and pivots it to the root. |
| 191 | * The image to be mounted is located at image_path. |
| 192 | * It will be mounted at mount_path. |
| 193 | * The vfs_switch_root operation will be performed. |
| 194 | * After the pivot, the outgoing root filesystem (the filesystem at root when |
| 195 | * this function begins) will be at outgoing_root_path. If `skip_signature_check` is true, |
| 196 | * then ignore the chunklisted or authAPFS checks on this image |
| 197 | */ |
| 198 | __private_extern__ int |
| 199 | imageboot_pivot_image(const char *image_path, imageboot_type_t type, const char *mount_path, |
| 200 | const char *outgoing_root_path, const bool rooted_dmg, const bool skip_signature_check) |
| 201 | { |
| 202 | int error; |
| 203 | boolean_t authenticated_dmg_chunklist = false; |
| 204 | vnode_t mount_vp = NULLVP; |
| 205 | errno_t rootauth; |
| 206 | |
| 207 | |
| 208 | if (type != IMAGEBOOT_DMG) { |
| 209 | panic("not supported" ); |
| 210 | } |
| 211 | |
| 212 | /* |
| 213 | * Check that the image file actually exists. |
| 214 | * We also need to find the mount it's on, to mark it as backing the |
| 215 | * root. |
| 216 | */ |
| 217 | vnode_t imagevp = NULLVP; |
| 218 | error = vnode_lookup(path: image_path, flags: 0, vpp: &imagevp, ctx: vfs_context_kernel()); |
| 219 | if (error) { |
| 220 | printf("%s: image file not found or couldn't be read: %d\n" , __FUNCTION__, error); |
| 221 | /* |
| 222 | * bail out here to short-circuit out of panic logic below. |
| 223 | * Failure to find the pivot-image should not be a fatal condition (ENOENT) |
| 224 | * since it may result in natural consequences (ergo, cannot unlock filevault prompt). |
| 225 | */ |
| 226 | return error; |
| 227 | } |
| 228 | |
| 229 | /* |
| 230 | * load the disk image and obtain its device. |
| 231 | * di_root_image's name and the names of its arguments suggest it has |
| 232 | * to be mounted at the root, but that's not actually needed. |
| 233 | * We just need to obtain the device info. |
| 234 | */ |
| 235 | |
| 236 | dev_t dev; |
| 237 | char devname[DEVMAXNAMESIZE]; |
| 238 | |
| 239 | error = di_root_image_ext(path: image_path, devname, DEVMAXNAMESIZE, dev_p: &dev, true); |
| 240 | if (error) { |
| 241 | panic("%s: di_root_image failed: %d" , __FUNCTION__, error); |
| 242 | } |
| 243 | |
| 244 | printf("%s: attached disk image %s as %s\n" , __FUNCTION__, image_path, devname); |
| 245 | |
| 246 | |
| 247 | #if CONFIG_IMAGEBOOT_CHUNKLIST |
| 248 | if ((rooted_dmg == false) && !IOBaseSystemARVRootHashAvailable()) { |
| 249 | error = authenticate_root_with_chunklist(image_path, NULL); |
| 250 | if (error == 0) { |
| 251 | printf("authenticated root-dmg via chunklist...\n" ); |
| 252 | authenticated_dmg_chunklist = true; |
| 253 | } else { |
| 254 | /* root hash was not available, and image is NOT chunklisted? */ |
| 255 | printf("failed to chunklist-authenticate root-dmg @ %s\n" , image_path); |
| 256 | } |
| 257 | } |
| 258 | #endif |
| 259 | |
| 260 | char fulldevname[DEVMAXNAMESIZE + 5]; // "/dev/" |
| 261 | strlcpy(dst: fulldevname, src: "/dev/" , n: sizeof(fulldevname)); |
| 262 | strlcat(dst: fulldevname, src: devname, n: sizeof(fulldevname)); |
| 263 | |
| 264 | /* |
| 265 | * mount expects another layer of indirection (because it expects to |
| 266 | * be getting a user_addr_t of a char *. |
| 267 | * Make a pointer-to-pointer on our stack. It won't use this |
| 268 | * address after it returns so this should be safe. |
| 269 | */ |
| 270 | char *fulldevnamep = &(fulldevname[0]); |
| 271 | char **fulldevnamepp = &fulldevnamep; |
| 272 | |
| 273 | #define PIVOTMNT "/System/Volumes/BaseSystem" |
| 274 | |
| 275 | |
| 276 | /* Attempt to mount as HFS; if it fails, then try as APFS */ |
| 277 | printf("%s: attempting to mount as hfs...\n" , __FUNCTION__); |
| 278 | error = kernel_mount("hfs" , NULLVP, NULLVP, PIVOTMNT, fulldevnamepp, 0, (MNT_RDONLY | MNT_DONTBROWSE), (KERNEL_MOUNT_NOAUTH | KERNEL_MOUNT_BASESYSTEMROOT), vfs_context_kernel()); |
| 279 | if (error) { |
| 280 | printf("mount failed: %d\n" , error); |
| 281 | printf("%s: attempting to mount as apfs...\n" , __FUNCTION__); |
| 282 | error = kernel_mount("apfs" , NULLVP, NULLVP, PIVOTMNT, fulldevnamepp, 0, (MNT_RDONLY | MNT_DONTBROWSE), (KERNEL_MOUNT_NOAUTH | KERNEL_MOUNT_BASESYSTEMROOT), vfs_context_kernel()); |
| 283 | } |
| 284 | |
| 285 | /* If we didn't mount as either HFS or APFS, then bail out */ |
| 286 | if (error) { |
| 287 | /* |
| 288 | * Note that for this particular failure case (failure to mount), the disk image |
| 289 | * being attached may have failed to quiesce within the alloted time out (20-30 sec). |
| 290 | * For example, it may be still probing, or APFS container enumeration may have not |
| 291 | * completed. If so, then we may have fallen into this particular error case. However, |
| 292 | * failure to complete matching should be an exceptional case as 30 sec. is quite a |
| 293 | * long time to wait for matching to complete (which would have occurred in |
| 294 | * di_root_image_ext). |
| 295 | */ |
| 296 | #if defined(__arm64__) && XNU_TARGET_OS_OSX |
| 297 | panic("%s: failed to mount pivot image(%d)!" , __FUNCTION__, error); |
| 298 | #endif |
| 299 | printf("%s: failed to mount pivot image(%d) !" , __FUNCTION__, error); |
| 300 | goto done; |
| 301 | } |
| 302 | |
| 303 | /* otherwise, if the mount succeeded, then assert that the DMG is authenticated (either chunklist or authapfs) */ |
| 304 | error = vnode_lookup(PIVOTMNT, flags: 0, vpp: &mount_vp, ctx: vfs_context_kernel()); |
| 305 | if (error) { |
| 306 | #if defined(__arm64__) && XNU_TARGET_OS_OSX |
| 307 | panic("%s: failed to lookup pivot root (%d) !" , __FUNCTION__, error); |
| 308 | #endif |
| 309 | printf("%s: failed to lookup pivot root (%d)!" , __FUNCTION__, error); |
| 310 | goto done; |
| 311 | } |
| 312 | |
| 313 | /* the 0x1 implies base system */ |
| 314 | rootauth = VNOP_IOCTL(vp: mount_vp, FSIOC_KERNEL_ROOTAUTH, data: (caddr_t)0x1, fflag: 0, ctx: vfs_context_kernel()); |
| 315 | if (rootauth) { |
| 316 | printf("BS-DMG failed to authenticate intra-FS \n" ); |
| 317 | /* |
| 318 | * If we are using a custom rooted DMG, or if we have already authenticated |
| 319 | * the DMG via chunklist, then it is permissible to use. |
| 320 | * Or, if CSR_ALLOW_ANY_RECOVERY_OS is set on Development or Debug build variant. |
| 321 | */ |
| 322 | if (rooted_dmg || authenticated_dmg_chunklist || skip_signature_check) { |
| 323 | rootauth = 0; |
| 324 | } |
| 325 | error = rootauth; |
| 326 | } |
| 327 | vnode_put(vp: mount_vp); |
| 328 | mount_vp = NULLVP; |
| 329 | |
| 330 | if (error) { |
| 331 | /* |
| 332 | * Failure here exclusively means that the mount failed to authenticate. |
| 333 | * This means that the disk image either was not sealed (authapfs), or it was |
| 334 | * not hosted on a chunklisted DMG. Both scenarios may be fatal depending |
| 335 | * on the platform. |
| 336 | */ |
| 337 | #if defined(__arm64__) && XNU_TARGET_OS_OSX |
| 338 | panic("%s: could not authenticate the pivot image: %d. giving up." , __FUNCTION__, error); |
| 339 | #endif |
| 340 | printf("%s: could not authenticate the pivot image: %d. giving up.\n" , __FUNCTION__, error); |
| 341 | goto done; |
| 342 | } |
| 343 | |
| 344 | if (rootvnode) { |
| 345 | mount_t root_mp = vnode_mount(vp: rootvnode); |
| 346 | if (root_mp && (root_mp->mnt_kern_flag & MNTK_SSD)) { |
| 347 | rootvp_is_ssd = true; |
| 348 | } |
| 349 | } |
| 350 | /* |
| 351 | * pivot the incoming and outgoing filesystems |
| 352 | */ |
| 353 | error = vfs_switch_root(mount_path, outgoing_root_path, 0); |
| 354 | if (error) { |
| 355 | panic("%s: vfs_switch_root failed: %d" , __FUNCTION__, error); |
| 356 | } |
| 357 | |
| 358 | /* |
| 359 | * Mark the filesystem containing the image as backing root, so it |
| 360 | * won't be unmountable. |
| 361 | * |
| 362 | * vfs_switch_root() clears this flag, so we have to set it after |
| 363 | * the pivot call. |
| 364 | * If the system later pivots out of the image, vfs_switch_root |
| 365 | * will clear it again, so the backing filesystem can be unmounted. |
| 366 | */ |
| 367 | mount_t imagemp = imagevp->v_mount; |
| 368 | lck_rw_lock_exclusive(lck: &imagemp->mnt_rwlock); |
| 369 | imagemp->mnt_kern_flag |= MNTK_BACKS_ROOT; |
| 370 | lck_rw_done(lck: &imagemp->mnt_rwlock); |
| 371 | |
| 372 | error = 0; |
| 373 | |
| 374 | /* |
| 375 | * Note that we do NOT change kern.bootuuid here - |
| 376 | * imageboot_mount_image() does, but imageboot_pivot_image() doesn't. |
| 377 | * imageboot_mount_image() is used when the root volume uuid was |
| 378 | * "always supposed to be" the one inside the dmg. imageboot_pivot_ |
| 379 | * image() is used when the true root volume just needs to be |
| 380 | * obscured for a moment by the dmg. |
| 381 | */ |
| 382 | |
| 383 | done: |
| 384 | if (imagevp != NULLVP) { |
| 385 | vnode_put(vp: imagevp); |
| 386 | } |
| 387 | return error; |
| 388 | } |
| 389 | |
| 390 | /* kern_sysctl.c */ |
| 391 | extern uuid_string_t fake_bootuuid; |
| 392 | |
| 393 | static void |
| 394 | set_fake_bootuuid(mount_t mp) |
| 395 | { |
| 396 | struct vfs_attr va; |
| 397 | VFSATTR_INIT(&va); |
| 398 | VFSATTR_WANTED(&va, f_uuid); |
| 399 | |
| 400 | if (vfs_getattr(mp, vfa: &va, ctx: vfs_context_current()) != 0) { |
| 401 | return; |
| 402 | } |
| 403 | |
| 404 | if (!VFSATTR_IS_SUPPORTED(&va, f_uuid)) { |
| 405 | return; |
| 406 | } |
| 407 | |
| 408 | uuid_unparse(uu: va.f_uuid, out: fake_bootuuid); |
| 409 | } |
| 410 | |
| 411 | /* |
| 412 | * Swaps in new root filesystem based on image path. |
| 413 | * Current root filesystem is removed from mount list and |
| 414 | * tagged MNTK_BACKS_ROOT, MNT_ROOTFS is cleared on it, and |
| 415 | * "rootvnode" is reset. Root vnode of currentroot filesystem |
| 416 | * is returned with usecount (no iocount). |
| 417 | * kern.bootuuid is arranged to return the UUID of the mounted image. (If |
| 418 | * we did nothing here, it would be the UUID of the image source volume.) |
| 419 | */ |
| 420 | __private_extern__ int |
| 421 | imageboot_mount_image(const char *root_path, int height, imageboot_type_t type) |
| 422 | { |
| 423 | dev_t dev; |
| 424 | int error; |
| 425 | /* |
| 426 | * Need to stash this here since we may do a kernel_mount() on /, which will |
| 427 | * automatically update the rootvnode global. Note that vfs_mountroot() does |
| 428 | * not update that global, which is a bit weird. |
| 429 | */ |
| 430 | vnode_t old_rootvnode = rootvnode; |
| 431 | vnode_t newdp; |
| 432 | mount_t new_rootfs; |
| 433 | boolean_t update_rootvnode = FALSE; |
| 434 | |
| 435 | if (type == IMAGEBOOT_DMG) { |
| 436 | error = di_root_image(path: root_path, devname: rootdevice, DEVMAXNAMESIZE, dev_p: &dev); |
| 437 | if (error) { |
| 438 | panic("%s: di_root_image failed: %d" , __FUNCTION__, error); |
| 439 | } |
| 440 | |
| 441 | rootdev = dev; |
| 442 | mountroot = NULL; |
| 443 | printf("%s: root device 0x%x\n" , __FUNCTION__, rootdev); |
| 444 | error = vfs_mountroot(); |
| 445 | if (error != 0) { |
| 446 | panic("vfs_mountroot() failed." ); |
| 447 | } |
| 448 | |
| 449 | update_rootvnode = TRUE; |
| 450 | } else { |
| 451 | panic("invalid imageboot type: %d" , type); |
| 452 | } |
| 453 | |
| 454 | /* |
| 455 | * Get the vnode for '/'. |
| 456 | * Set fdp->fd_fd.fd_cdir to reference it. |
| 457 | */ |
| 458 | if (VFS_ROOT(TAILQ_LAST(&mountlist, mntlist), &newdp, vfs_context_kernel())) { |
| 459 | panic("%s: cannot find root vnode" , __FUNCTION__); |
| 460 | } |
| 461 | DBG_TRACE("%s: old root fsname: %s\n" , __FUNCTION__, old_rootvnode->v_mount->mnt_vtable->vfc_name); |
| 462 | |
| 463 | if (old_rootvnode != NULL) { |
| 464 | /* remember the old rootvnode, but remove it from mountlist */ |
| 465 | mount_t old_rootfs = old_rootvnode->v_mount; |
| 466 | |
| 467 | mount_list_remove(old_rootfs); |
| 468 | mount_lock(old_rootfs); |
| 469 | old_rootfs->mnt_kern_flag |= MNTK_BACKS_ROOT; |
| 470 | old_rootfs->mnt_flag &= ~MNT_ROOTFS; |
| 471 | mount_unlock(old_rootfs); |
| 472 | } |
| 473 | |
| 474 | vnode_ref(vp: newdp); |
| 475 | vnode_put(vp: newdp); |
| 476 | |
| 477 | lck_rw_lock_exclusive(lck: &rootvnode_rw_lock); |
| 478 | /* switch to the new rootvnode */ |
| 479 | if (update_rootvnode) { |
| 480 | rootvnode = newdp; |
| 481 | set_fake_bootuuid(rootvnode->v_mount); |
| 482 | } |
| 483 | |
| 484 | new_rootfs = rootvnode->v_mount; |
| 485 | mount_lock(new_rootfs); |
| 486 | new_rootfs->mnt_flag |= MNT_ROOTFS; |
| 487 | mount_unlock(new_rootfs); |
| 488 | |
| 489 | kernproc->p_fd.fd_cdir = newdp; |
| 490 | lck_rw_unlock_exclusive(lck: &rootvnode_rw_lock); |
| 491 | |
| 492 | DBG_TRACE("%s: root switched\n" , __FUNCTION__); |
| 493 | |
| 494 | if (old_rootvnode != NULL) { |
| 495 | #ifdef CONFIG_IMGSRC_ACCESS |
| 496 | if (height >= 0) { |
| 497 | imgsrc_rootvnodes[height] = old_rootvnode; |
| 498 | } else { |
| 499 | vnode_get_and_drop_always(vp: old_rootvnode); |
| 500 | } |
| 501 | #else |
| 502 | #pragma unused(height) |
| 503 | vnode_get_and_drop_always(old_rootvnode); |
| 504 | #endif /* CONFIG_IMGSRC_ACCESS */ |
| 505 | } |
| 506 | return 0; |
| 507 | } |
| 508 | |
| 509 | /* |
| 510 | * Return a memory object for given file path. |
| 511 | * Also returns a vnode reference for the given file path. |
| 512 | */ |
| 513 | void * |
| 514 | ubc_getobject_from_filename(const char *filename, struct vnode **vpp, off_t *file_size) |
| 515 | { |
| 516 | int err = 0; |
| 517 | struct nameidata ndp = {}; |
| 518 | struct vnode *vp = NULL; |
| 519 | off_t fsize = 0; |
| 520 | vfs_context_t ctx = vfs_context_kernel(); |
| 521 | void *control = NULL; |
| 522 | |
| 523 | NDINIT(&ndp, LOOKUP, OP_OPEN, LOCKLEAF, UIO_SYSSPACE, CAST_USER_ADDR_T(filename), ctx); |
| 524 | if ((err = namei(ndp: &ndp)) != 0) { |
| 525 | goto errorout; |
| 526 | } |
| 527 | nameidone(&ndp); |
| 528 | vp = ndp.ni_vp; |
| 529 | |
| 530 | if ((err = vnode_size(vp, &fsize, ctx)) != 0) { |
| 531 | goto errorout; |
| 532 | } |
| 533 | |
| 534 | if (fsize < 0) { |
| 535 | goto errorout; |
| 536 | } |
| 537 | |
| 538 | control = ubc_getobject(vp, UBC_FLAGS_NONE); |
| 539 | if (control == NULL) { |
| 540 | goto errorout; |
| 541 | } |
| 542 | |
| 543 | *file_size = fsize; |
| 544 | *vpp = vp; |
| 545 | vp = NULL; |
| 546 | |
| 547 | errorout: |
| 548 | if (vp) { |
| 549 | vnode_put(vp); |
| 550 | } |
| 551 | return control; |
| 552 | } |
| 553 | |
| 554 | static int |
| 555 | imageboot_read_file_internal(const char *path, const off_t offset, const bool pageable, void **bufp, size_t *bufszp, off_t *fsizep) |
| 556 | { |
| 557 | int err = 0; |
| 558 | struct nameidata ndp = {}; |
| 559 | struct vnode *vp = NULL; |
| 560 | struct vnode *rsrc_vp = NULL; |
| 561 | char *readbuf = NULL; |
| 562 | off_t readsize = 0; |
| 563 | off_t readoff = 0; |
| 564 | off_t fsize = 0; |
| 565 | size_t maxsize = 0; |
| 566 | char *buf = NULL; |
| 567 | bool doclose = false; |
| 568 | |
| 569 | vfs_context_t ctx = vfs_context_kernel(); |
| 570 | proc_t p = vfs_context_proc(ctx); |
| 571 | kauth_cred_t kerncred = vfs_context_ucred(ctx); |
| 572 | |
| 573 | NDINIT(&ndp, LOOKUP, OP_OPEN, LOCKLEAF | FOLLOW, UIO_SYSSPACE, CAST_USER_ADDR_T(path), ctx); |
| 574 | if ((err = namei(ndp: &ndp)) != 0) { |
| 575 | AUTHPRNT("namei failed (%s) - %d" , path, err); |
| 576 | goto out; |
| 577 | } |
| 578 | nameidone(&ndp); |
| 579 | vp = ndp.ni_vp; |
| 580 | |
| 581 | if ((err = vnode_size(vp, &fsize, ctx)) != 0) { |
| 582 | AUTHPRNT("failed to get vnode size of %s - %d" , path, err); |
| 583 | goto out; |
| 584 | } |
| 585 | if (fsize < 0) { |
| 586 | panic("negative file size" ); |
| 587 | } |
| 588 | if (offset < 0) { |
| 589 | AUTHPRNT("negative file offset" ); |
| 590 | err = EINVAL; |
| 591 | goto out; |
| 592 | } |
| 593 | |
| 594 | if (fsizep) { |
| 595 | *fsizep = fsize; |
| 596 | } |
| 597 | |
| 598 | if ((err = VNOP_OPEN(vp, FREAD, ctx)) != 0) { |
| 599 | AUTHPRNT("failed to open %s - %d" , path, err); |
| 600 | goto out; |
| 601 | } |
| 602 | doclose = true; |
| 603 | |
| 604 | /* cap fsize to the amount that remains after offset */ |
| 605 | if (os_sub_overflow(fsize, offset, &fsize)) { |
| 606 | fsize = 0; |
| 607 | } else if (fsize < 0) { |
| 608 | fsize = 0; |
| 609 | } |
| 610 | |
| 611 | /* if bufsz is non-zero, cap the read at bufsz bytes */ |
| 612 | maxsize = *bufszp; |
| 613 | if (maxsize && (maxsize < (size_t)fsize)) { |
| 614 | fsize = maxsize; |
| 615 | } |
| 616 | |
| 617 | /* if fsize is larger than the specified limit (presently 2.5GB) or a NVRAM-configured limit, fail */ |
| 618 | maxsize = IMAGEBOOT_MAX_FILESIZE; |
| 619 | PE_parse_boot_argn(arg_string: "rootdmg-maxsize" , arg_ptr: &maxsize, max_arg: sizeof(maxsize)); |
| 620 | if (maxsize && (maxsize < (size_t)fsize)) { |
| 621 | AUTHPRNT("file is too large (%lld > %lld)" , (long long) fsize, (long long) maxsize); |
| 622 | err = ENOMEM; |
| 623 | goto out; |
| 624 | } |
| 625 | |
| 626 | if (pageable) { |
| 627 | vm_offset_t addr = 0; |
| 628 | if (kmem_alloc(map: kernel_map, addrp: &addr, size: (vm_size_t)fsize, |
| 629 | flags: KMA_PAGEABLE | KMA_DATA, VM_KERN_MEMORY_FILE) == KERN_SUCCESS) { |
| 630 | buf = (char *)addr; |
| 631 | } else { |
| 632 | buf = NULL; |
| 633 | } |
| 634 | } else { |
| 635 | //limit kalloc data calls to only 2GB. |
| 636 | if (fsize > IMAGEBOOT_MAX_KALLOCSIZE) { |
| 637 | AUTHPRNT("file is too large for non-pageable (%lld)" , (long long) fsize); |
| 638 | err = ENOMEM; |
| 639 | goto out; |
| 640 | } |
| 641 | buf = (char *)kalloc_data((vm_size_t)fsize, Z_WAITOK); |
| 642 | } |
| 643 | if (buf == NULL) { |
| 644 | err = ENOMEM; |
| 645 | goto out; |
| 646 | } |
| 647 | |
| 648 | #if NAMEDSTREAMS |
| 649 | /* find resource fork so we can evict cached decmpfs data */ |
| 650 | if (VNOP_GETNAMEDSTREAM(vp, &rsrc_vp, XATTR_RESOURCEFORK_NAME, NS_OPEN, /*flags*/ 0, ctx) == 0) { |
| 651 | vnode_ref(vp: rsrc_vp); |
| 652 | vnode_put(vp: rsrc_vp); |
| 653 | AUTHDBG("Found resource fork for %s" , path); |
| 654 | } |
| 655 | #endif |
| 656 | |
| 657 | /* read data in chunks to handle (fsize > INT_MAX) */ |
| 658 | readbuf = buf; |
| 659 | readsize = fsize; |
| 660 | readoff = offset; |
| 661 | while (readsize > 0) { |
| 662 | const off_t chunksize_max = 16 * 1024 * 1024; /* 16 MiB */ |
| 663 | const off_t chunksize = MIN(readsize, chunksize_max); |
| 664 | |
| 665 | /* read next chunk, pass IO_NOCACHE to clarify our intent (even if ignored) */ |
| 666 | if ((err = vn_rdwr(rw: UIO_READ, vp, base: (caddr_t)readbuf, len: (int)chunksize, offset: readoff, segflg: UIO_SYSSPACE, IO_NODELOCKED | IO_NOCACHE | IO_RAOFF, cred: kerncred, /*resid*/ NULL, p)) != 0) { |
| 667 | AUTHPRNT("Cannot read %lld bytes at offset %lld from %s - %d" , (long long)chunksize, (long long)readoff, path, err); |
| 668 | goto out; |
| 669 | } |
| 670 | |
| 671 | /* evict cached pages so they don't accumulate during early boot */ |
| 672 | ubc_msync(vp, readoff, readoff + chunksize, NULL, UBC_INVALIDATE | UBC_PUSHALL); |
| 673 | |
| 674 | /* evict potentially-cached decmpfs data if we have a resource fork */ |
| 675 | if (rsrc_vp != NULL) { |
| 676 | if (vnode_getwithref(vp: rsrc_vp) == 0) { |
| 677 | ubc_msync(rsrc_vp, 0, ubc_getsize(rsrc_vp), NULL, UBC_INVALIDATE | UBC_PUSHALL); |
| 678 | vnode_put(vp: rsrc_vp); |
| 679 | } |
| 680 | } |
| 681 | |
| 682 | readbuf = &readbuf[chunksize]; |
| 683 | readsize -= chunksize; |
| 684 | readoff += chunksize; |
| 685 | } |
| 686 | |
| 687 | out: |
| 688 | if (doclose) { |
| 689 | VNOP_CLOSE(vp, FREAD, ctx); |
| 690 | } |
| 691 | if (rsrc_vp) { |
| 692 | vnode_rele(vp: rsrc_vp); |
| 693 | rsrc_vp = NULL; |
| 694 | } |
| 695 | if (vp) { |
| 696 | vnode_put(vp); |
| 697 | vp = NULL; |
| 698 | } |
| 699 | |
| 700 | if (err) { |
| 701 | if (buf == NULL) { |
| 702 | /* nothing to free */ |
| 703 | } else if (pageable) { |
| 704 | kmem_free(map: kernel_map, addr: (vm_offset_t)buf, size: (vm_size_t)fsize); |
| 705 | } else { |
| 706 | kfree_data(buf, (vm_size_t)fsize); |
| 707 | } |
| 708 | } else { |
| 709 | *bufp = buf; |
| 710 | *bufszp = (size_t)fsize; |
| 711 | } |
| 712 | |
| 713 | return err; |
| 714 | } |
| 715 | |
| 716 | int |
| 717 | imageboot_read_file_pageable(const char *path, void **bufp, size_t *bufszp) |
| 718 | { |
| 719 | return imageboot_read_file_internal(path, offset: 0, true, bufp, bufszp, NULL); |
| 720 | } |
| 721 | |
| 722 | int |
| 723 | imageboot_read_file_from_offset(const char *path, const off_t offset, void **bufp, size_t *bufszp) |
| 724 | { |
| 725 | return imageboot_read_file_internal(path, offset, false, bufp, bufszp, NULL); |
| 726 | } |
| 727 | |
| 728 | int |
| 729 | imageboot_read_file(const char *path, void **bufp, size_t *bufszp, off_t *fsizep) |
| 730 | { |
| 731 | return imageboot_read_file_internal(path, offset: 0, false, bufp, bufszp, fsizep); |
| 732 | } |
| 733 | |
| 734 | #if CONFIG_IMAGEBOOT_IMG4 || CONFIG_IMAGEBOOT_CHUNKLIST |
| 735 | vnode_t |
| 736 | imgboot_get_image_file(const char *path, off_t *fsize, int *errp) |
| 737 | { |
| 738 | struct nameidata ndp = {}; |
| 739 | vnode_t vp = NULL; |
| 740 | vfs_context_t ctx = vfs_context_kernel(); |
| 741 | int err; |
| 742 | |
| 743 | NDINIT(&ndp, LOOKUP, OP_OPEN, LOCKLEAF, UIO_SYSSPACE, CAST_USER_ADDR_T(path), ctx); |
| 744 | if ((err = namei(&ndp)) != 0) { |
| 745 | AUTHPRNT("Cannot find %s - error %d" , path, err); |
| 746 | } else { |
| 747 | nameidone(&ndp); |
| 748 | vp = ndp.ni_vp; |
| 749 | |
| 750 | if (vp->v_type != VREG) { |
| 751 | err = EINVAL; |
| 752 | AUTHPRNT("%s it not a regular file" , path); |
| 753 | } else if (fsize) { |
| 754 | if ((err = vnode_size(vp, fsize, ctx)) != 0) { |
| 755 | AUTHPRNT("Cannot get file size of %s - error %d" , path, err); |
| 756 | } |
| 757 | } |
| 758 | } |
| 759 | |
| 760 | if (err) { |
| 761 | if (vp) { |
| 762 | vnode_put(vp); |
| 763 | } |
| 764 | *errp = err; |
| 765 | vp = NULL; |
| 766 | } |
| 767 | return vp; |
| 768 | } |
| 769 | #endif /* CONFIG_IMAGEBOOT_CHUNKLIST || CONFIG_IMAGEBOOT_CHUNKLIST */ |
| 770 | |
| 771 | #if CONFIG_IMAGEBOOT_IMG4 |
| 772 | |
| 773 | #define APTICKET_NAME "apticket.der" |
| 774 | |
| 775 | static char * |
| 776 | imgboot_get_apticket_path(const char *rootpath, size_t *sz) |
| 777 | { |
| 778 | size_t plen = strlen(rootpath) + sizeof(APTICKET_NAME) + 1; |
| 779 | char *path = (char *)kalloc_data(plen, Z_WAITOK); |
| 780 | |
| 781 | if (path) { |
| 782 | char *slash; |
| 783 | |
| 784 | strlcpy(path, rootpath, plen); |
| 785 | slash = strrchr(path, '/'); |
| 786 | if (slash == NULL) { |
| 787 | slash = path; |
| 788 | } else { |
| 789 | slash++; |
| 790 | } |
| 791 | strlcpy(slash, APTICKET_NAME, sizeof(APTICKET_NAME) + 1); |
| 792 | } |
| 793 | |
| 794 | *sz = plen; |
| 795 | return path; |
| 796 | } |
| 797 | |
| 798 | static int |
| 799 | authenticate_root_with_img4(const char *rootpath) |
| 800 | { |
| 801 | errno_t rv; |
| 802 | vnode_t vp = NULLVP; |
| 803 | size_t ticket_pathsz = 0; |
| 804 | char *ticket_path; |
| 805 | img4_buff_t tck = IMG4_BUFF_INIT; |
| 806 | img4_firmware_execution_context_t exec = { |
| 807 | .i4fex_version = IMG4_FIRMWARE_EXECUTION_CONTEXT_STRUCT_VERSION, |
| 808 | .i4fex_execute = NULL, |
| 809 | .i4fex_context = NULL, |
| 810 | }; |
| 811 | img4_firmware_t fw = NULL; |
| 812 | img4_firmware_flags_t fw_flags = IMG4_FIRMWARE_FLAG_BARE | |
| 813 | IMG4_FIRMWARE_FLAG_SUBSEQUENT_STAGE; |
| 814 | |
| 815 | DBG_TRACE("Check %s\n" , rootpath); |
| 816 | |
| 817 | ticket_path = imgboot_get_apticket_path(rootpath, &ticket_pathsz); |
| 818 | if (ticket_path == NULL) { |
| 819 | AUTHPRNT("Cannot construct ticket path - out of memory" ); |
| 820 | return ENOMEM; |
| 821 | } |
| 822 | |
| 823 | rv = imageboot_read_file(ticket_path, (void **)&tck.i4b_bytes, &tck.i4b_len, NULL); |
| 824 | if (rv) { |
| 825 | AUTHPRNT("Cannot get a ticket from %s - %d\n" , ticket_path, rv); |
| 826 | goto out_with_ticket_path; |
| 827 | } |
| 828 | |
| 829 | DBG_TRACE("Got %lu bytes of manifest from %s\n" , tck.i4b_len, ticket_path); |
| 830 | |
| 831 | vp = imgboot_get_image_file(rootpath, NULL, &rv); |
| 832 | if (vp == NULL) { |
| 833 | /* Error message had been printed already */ |
| 834 | rv = EIO; |
| 835 | goto out_with_ticket_bytes; |
| 836 | } |
| 837 | |
| 838 | fw = img4_firmware_new_from_vnode_4xnu(IMG4_RUNTIME_DEFAULT, &exec, 'rosi', |
| 839 | vp, fw_flags); |
| 840 | if (!fw) { |
| 841 | AUTHPRNT("Could not allocate new firmware" ); |
| 842 | rv = ENOMEM; |
| 843 | goto out_with_ticket_bytes; |
| 844 | } |
| 845 | |
| 846 | img4_firmware_attach_manifest(fw, &tck); |
| 847 | rv = img4_firmware_evaluate(fw, img4_chip_select_personalized_ap(), NULL); |
| 848 | |
| 849 | out_with_ticket_bytes: |
| 850 | kfree_data(tck.i4b_bytes, tck.i4b_len); |
| 851 | out_with_ticket_path: |
| 852 | kfree_data(ticket_path, ticket_pathsz); |
| 853 | |
| 854 | img4_firmware_destroy(&fw); |
| 855 | |
| 856 | if (vp) { |
| 857 | vnode_put(vp); |
| 858 | } |
| 859 | return rv; |
| 860 | } |
| 861 | #endif /* CONFIG_IMAGEBOOT_IMG4 */ |
| 862 | |
| 863 | |
| 864 | /* |
| 865 | * Attach the image at 'path' as a ramdisk and mount it as our new rootfs. |
| 866 | * All existing mounts are first umounted. |
| 867 | */ |
| 868 | static int |
| 869 | imageboot_mount_ramdisk(const char *path) |
| 870 | { |
| 871 | int err = 0; |
| 872 | size_t bufsz = 0; |
| 873 | void *buf = NULL; |
| 874 | dev_t dev; |
| 875 | vnode_t newdp; |
| 876 | vnode_t tvp; |
| 877 | mount_t new_rootfs; |
| 878 | |
| 879 | /* Read our target image from disk */ |
| 880 | err = imageboot_read_file_pageable(path, bufp: &buf, bufszp: &bufsz); |
| 881 | if (err) { |
| 882 | printf("%s: failed: imageboot_read_file_pageable() = %d\n" , __func__, err); |
| 883 | goto out; |
| 884 | } |
| 885 | DBG_TRACE("%s: read '%s' sz = %lu\n" , __func__, path, bufsz); |
| 886 | |
| 887 | #if CONFIG_IMGSRC_ACCESS |
| 888 | /* Re-add all root mounts to the mount list in the correct order... */ |
| 889 | mount_list_remove(rootvnode->v_mount); |
| 890 | for (int i = 0; i < MAX_IMAGEBOOT_NESTING; i++) { |
| 891 | struct vnode *vn = imgsrc_rootvnodes[i]; |
| 892 | if (vn) { |
| 893 | vnode_getalways(vn); |
| 894 | imgsrc_rootvnodes[i] = NULLVP; |
| 895 | |
| 896 | mount_t mnt = vn->v_mount; |
| 897 | mount_lock(mnt); |
| 898 | mnt->mnt_flag |= MNT_ROOTFS; |
| 899 | mount_list_add(mnt); |
| 900 | mount_unlock(mnt); |
| 901 | |
| 902 | vnode_rele(vp: vn); |
| 903 | vnode_put(vp: vn); |
| 904 | } |
| 905 | } |
| 906 | mount_list_add(rootvnode->v_mount); |
| 907 | #endif |
| 908 | |
| 909 | /* ... and unmount everything */ |
| 910 | vfs_unmountall(FALSE); |
| 911 | |
| 912 | lck_rw_lock_exclusive(lck: &rootvnode_rw_lock); |
| 913 | kernproc->p_fd.fd_cdir = NULL; |
| 914 | tvp = rootvnode; |
| 915 | rootvnode = NULL; |
| 916 | rootvp = NULLVP; |
| 917 | rootdev = NODEV; |
| 918 | lck_rw_unlock_exclusive(lck: &rootvnode_rw_lock); |
| 919 | vnode_get_and_drop_always(vp: tvp); |
| 920 | |
| 921 | /* Attach the ramfs image ... */ |
| 922 | err = di_root_ramfile_buf(buf, bufsz, devname: rootdevice, DEVMAXNAMESIZE, dev_p: &dev); |
| 923 | if (err) { |
| 924 | printf("%s: failed: di_root_ramfile_buf() = %d\n" , __func__, err); |
| 925 | goto out; |
| 926 | } |
| 927 | |
| 928 | /* ... and mount it */ |
| 929 | rootdev = dev; |
| 930 | mountroot = NULL; |
| 931 | err = vfs_mountroot(); |
| 932 | if (err) { |
| 933 | printf("%s: failed: vfs_mountroot() = %d\n" , __func__, err); |
| 934 | goto out; |
| 935 | } |
| 936 | |
| 937 | /* Switch to new root vnode */ |
| 938 | if (VFS_ROOT(TAILQ_LAST(&mountlist, mntlist), &newdp, vfs_context_kernel())) { |
| 939 | panic("%s: cannot find root vnode" , __func__); |
| 940 | } |
| 941 | vnode_ref(vp: newdp); |
| 942 | |
| 943 | lck_rw_lock_exclusive(lck: &rootvnode_rw_lock); |
| 944 | rootvnode = newdp; |
| 945 | rootvnode->v_flag |= VROOT; |
| 946 | new_rootfs = rootvnode->v_mount; |
| 947 | mount_lock(new_rootfs); |
| 948 | new_rootfs->mnt_flag |= MNT_ROOTFS; |
| 949 | mount_unlock(new_rootfs); |
| 950 | |
| 951 | set_fake_bootuuid(new_rootfs); |
| 952 | |
| 953 | kernproc->p_fd.fd_cdir = newdp; |
| 954 | lck_rw_unlock_exclusive(lck: &rootvnode_rw_lock); |
| 955 | |
| 956 | vnode_put(vp: newdp); |
| 957 | |
| 958 | DBG_TRACE("%s: root switched\n" , __func__); |
| 959 | |
| 960 | out: |
| 961 | if (err && (buf != NULL)) { |
| 962 | kmem_free(map: kernel_map, addr: (vm_offset_t)buf, size: (vm_size_t)bufsz); |
| 963 | } |
| 964 | return err; |
| 965 | } |
| 966 | |
| 967 | /* |
| 968 | * If the path is in <file://> URL format then we allocate memory and decode it, |
| 969 | * otherwise return the same pointer. |
| 970 | * |
| 971 | * Caller is expected to check if the pointers are different. |
| 972 | */ |
| 973 | static char * |
| 974 | url_to_path(char *url_path, size_t *sz) |
| 975 | { |
| 976 | char *path = url_path; |
| 977 | size_t len = strlen(kIBFilePrefix); |
| 978 | |
| 979 | if (strncmp(kIBFilePrefix, s2: url_path, n: len) == 0) { |
| 980 | /* its a URL - remove the file:// prefix and percent-decode */ |
| 981 | url_path += len; |
| 982 | |
| 983 | len = strlen(s: url_path); |
| 984 | if (len) { |
| 985 | /* Make a copy of the path to URL-decode */ |
| 986 | path = (char *)kalloc_data(len + 1, Z_WAITOK); |
| 987 | if (path == NULL) { |
| 988 | panic("imageboot path allocation failed - cannot allocate %d bytes" , (int)len); |
| 989 | } |
| 990 | |
| 991 | strlcpy(dst: path, src: url_path, n: len + 1); |
| 992 | *sz = len + 1; |
| 993 | url_decode(str: path); |
| 994 | } else { |
| 995 | panic("Bogus imageboot path URL - missing path" ); |
| 996 | } |
| 997 | |
| 998 | DBG_TRACE("%s: root image URL <%s> becomes %s\n" , __func__, url_path, path); |
| 999 | } |
| 1000 | |
| 1001 | return path; |
| 1002 | } |
| 1003 | |
| 1004 | static boolean_t |
| 1005 | imageboot_setup_new(imageboot_type_t type) |
| 1006 | { |
| 1007 | int error; |
| 1008 | char *root_path = NULL; |
| 1009 | int height = 0; |
| 1010 | boolean_t done = FALSE; |
| 1011 | boolean_t auth_root = TRUE; |
| 1012 | boolean_t ramdisk_root = FALSE; |
| 1013 | |
| 1014 | root_path = zalloc(view: ZV_NAMEI); |
| 1015 | assert(root_path != NULL); |
| 1016 | |
| 1017 | unsigned imgboot_arg; |
| 1018 | if (PE_parse_boot_argn(arg_string: "-rootdmg-ramdisk" , arg_ptr: &imgboot_arg, max_arg: sizeof(imgboot_arg))) { |
| 1019 | ramdisk_root = TRUE; |
| 1020 | } |
| 1021 | |
| 1022 | if (PE_parse_boot_argn(IMAGEBOOT_CONTAINER_ARG, arg_ptr: root_path, MAXPATHLEN) == TRUE) { |
| 1023 | printf("%s: container image url is %s\n" , __FUNCTION__, root_path); |
| 1024 | error = imageboot_mount_image(root_path, height, type); |
| 1025 | if (error != 0) { |
| 1026 | panic("Failed to mount container image." ); |
| 1027 | } |
| 1028 | |
| 1029 | height++; |
| 1030 | } |
| 1031 | |
| 1032 | if (PE_parse_boot_argn(IMAGEBOOT_AUTHROOT_ARG, arg_ptr: root_path, MAXPATHLEN) == FALSE && |
| 1033 | PE_parse_boot_argn(IMAGEBOOT_ROOT_ARG, arg_ptr: root_path, MAXPATHLEN) == FALSE) { |
| 1034 | if (height > 0) { |
| 1035 | panic("%s specified without %s or %s?" , IMAGEBOOT_CONTAINER_ARG, IMAGEBOOT_AUTHROOT_ARG, IMAGEBOOT_ROOT_ARG); |
| 1036 | } |
| 1037 | goto out; |
| 1038 | } |
| 1039 | |
| 1040 | printf("%s: root image URL is '%s'\n" , __func__, root_path); |
| 1041 | |
| 1042 | /* Make a copy of the path to URL-decode */ |
| 1043 | size_t pathsz; |
| 1044 | char *path = url_to_path(url_path: root_path, sz: &pathsz); |
| 1045 | assert(path); |
| 1046 | |
| 1047 | #if CONFIG_IMAGEBOOT_CHUNKLIST |
| 1048 | if (auth_root) { |
| 1049 | /* |
| 1050 | * This updates auth_root to reflect whether chunklist was |
| 1051 | * actually enforced. In effect, this clears auth_root if |
| 1052 | * CSR_ALLOW_ANY_RECOVERY_OS allowed an invalid image. |
| 1053 | */ |
| 1054 | AUTHDBG("authenticating root image at %s" , path); |
| 1055 | error = authenticate_root_with_chunklist(path, &auth_root); |
| 1056 | if (error) { |
| 1057 | panic("root image authentication failed (err = %d)" , error); |
| 1058 | } |
| 1059 | AUTHDBG("successfully authenticated %s" , path); |
| 1060 | } |
| 1061 | #endif |
| 1062 | |
| 1063 | if (ramdisk_root) { |
| 1064 | error = imageboot_mount_ramdisk(path); |
| 1065 | } else { |
| 1066 | error = imageboot_mount_image(root_path, height, type); |
| 1067 | } |
| 1068 | |
| 1069 | if (path != root_path) { |
| 1070 | kfree_data(path, pathsz); |
| 1071 | } |
| 1072 | |
| 1073 | if (error) { |
| 1074 | panic("Failed to mount root image (err=%d, auth=%d, ramdisk=%d)" , |
| 1075 | error, auth_root, ramdisk_root); |
| 1076 | } |
| 1077 | |
| 1078 | #if CONFIG_IMAGEBOOT_CHUNKLIST |
| 1079 | if (auth_root) { |
| 1080 | /* check that the image version matches the running kernel */ |
| 1081 | AUTHDBG("checking root image version" ); |
| 1082 | error = authenticate_root_version_check(); |
| 1083 | if (error) { |
| 1084 | panic("root image version check failed" ); |
| 1085 | } else { |
| 1086 | AUTHDBG("root image version matches kernel" ); |
| 1087 | } |
| 1088 | } |
| 1089 | #endif |
| 1090 | |
| 1091 | done = TRUE; |
| 1092 | |
| 1093 | out: |
| 1094 | zfree(ZV_NAMEI, root_path); |
| 1095 | return done; |
| 1096 | } |
| 1097 | |
| 1098 | __private_extern__ void |
| 1099 | imageboot_setup(imageboot_type_t type) |
| 1100 | { |
| 1101 | int error = 0; |
| 1102 | char *root_path = NULL; |
| 1103 | |
| 1104 | DBG_TRACE("%s: entry\n" , __FUNCTION__); |
| 1105 | |
| 1106 | if (rootvnode == NULL) { |
| 1107 | panic("imageboot_setup: rootvnode is NULL." ); |
| 1108 | } |
| 1109 | |
| 1110 | /* |
| 1111 | * New boot-arg scheme: |
| 1112 | * root-dmg : the dmg that will be the root filesystem, authenticated by default. |
| 1113 | * auth-root-dmg : same as root-dmg. |
| 1114 | * container-dmg : an optional dmg that contains the root-dmg. |
| 1115 | * locker : the locker that will be the root filesystem -- mutually |
| 1116 | * exclusive with any other boot-arg. |
| 1117 | */ |
| 1118 | if (imageboot_setup_new(type)) { |
| 1119 | return; |
| 1120 | } |
| 1121 | |
| 1122 | root_path = zalloc(view: ZV_NAMEI); |
| 1123 | assert(root_path != NULL); |
| 1124 | |
| 1125 | /* |
| 1126 | * Look for outermost disk image to root from. If we're doing a nested boot, |
| 1127 | * there's some sense in which the outer image never needs to be the root filesystem, |
| 1128 | * but it does need very similar treatment: it must not be unmounted, needs a fake |
| 1129 | * device vnode created for it, and should not show up in getfsstat() until exposed |
| 1130 | * with MNT_IMGSRC. We just make it the temporary root. |
| 1131 | */ |
| 1132 | #if CONFIG_IMAGEBOOT_IMG4 |
| 1133 | if (PE_parse_boot_argn("arp0" , root_path, MAXPATHLEN)) { |
| 1134 | size_t pathsz; |
| 1135 | char *path = url_to_path(root_path, &pathsz); |
| 1136 | |
| 1137 | assert(path); |
| 1138 | |
| 1139 | if (authenticate_root_with_img4(path)) { |
| 1140 | panic("Root image %s does not match the manifest" , root_path); |
| 1141 | } |
| 1142 | if (path != root_path) { |
| 1143 | kfree_data(path, pathsz); |
| 1144 | } |
| 1145 | } else |
| 1146 | #endif /* CONFIG_IMAGEBOOT_IMG4 */ |
| 1147 | if ((PE_parse_boot_argn(arg_string: "rp" , arg_ptr: root_path, MAXPATHLEN) == FALSE) && |
| 1148 | (PE_parse_boot_argn(arg_string: "rp0" , arg_ptr: root_path, MAXPATHLEN) == FALSE)) { |
| 1149 | panic("%s: no valid path to image." , __FUNCTION__); |
| 1150 | } |
| 1151 | |
| 1152 | DBG_TRACE("%s: root image url is %s\n" , __FUNCTION__, root_path); |
| 1153 | |
| 1154 | error = imageboot_mount_image(root_path, height: 0, type); |
| 1155 | if (error) { |
| 1156 | panic("Failed on first stage of imageboot." ); |
| 1157 | } |
| 1158 | |
| 1159 | /* |
| 1160 | * See if we are rooting from a nested image |
| 1161 | */ |
| 1162 | if (PE_parse_boot_argn(arg_string: "rp1" , arg_ptr: root_path, MAXPATHLEN) == FALSE) { |
| 1163 | goto done; |
| 1164 | } |
| 1165 | |
| 1166 | printf("%s: second level root image url is %s\n" , __FUNCTION__, root_path); |
| 1167 | |
| 1168 | /* |
| 1169 | * If we fail to set up second image, it's not a given that we |
| 1170 | * can safely root off the first. |
| 1171 | */ |
| 1172 | error = imageboot_mount_image(root_path, height: 1, type); |
| 1173 | if (error) { |
| 1174 | panic("Failed on second stage of imageboot." ); |
| 1175 | } |
| 1176 | |
| 1177 | done: |
| 1178 | zfree(ZV_NAMEI, root_path); |
| 1179 | |
| 1180 | DBG_TRACE("%s: exit\n" , __FUNCTION__); |
| 1181 | |
| 1182 | return; |
| 1183 | } |
| 1184 | |