+
+out:
+ if (doclose) {
+ VNOP_CLOSE(vp, FREAD, ctx);
+ }
+ if (vp) {
+ vnode_put(vp);
+ vp = NULL;
+ }
+
+ if (err) {
+ kheap_free_safe(kheap, buf, (size_t)fsize);
+ } else {
+ *bufp = buf;
+ *bufszp = (size_t)fsize;
+ }
+
+ return err;
+}
+
+int
+imageboot_read_file(kalloc_heap_t kheap, const char *path, void **bufp, size_t *bufszp)
+{
+ return imageboot_read_file_from_offset(kheap, path, 0, bufp, bufszp);
+}
+
+#if CONFIG_IMAGEBOOT_IMG4 || CONFIG_IMAGEBOOT_CHUNKLIST
+vnode_t
+imgboot_get_image_file(const char *path, off_t *fsize, int *errp)
+{
+ struct nameidata ndp = {};
+ vnode_t vp = NULL;
+ vfs_context_t ctx = vfs_context_kernel();
+ int err;
+
+ NDINIT(&ndp, LOOKUP, OP_OPEN, LOCKLEAF, UIO_SYSSPACE, CAST_USER_ADDR_T(path), ctx);
+ if ((err = namei(&ndp)) != 0) {
+ AUTHPRNT("Cannot find %s - error %d", path, err);
+ } else {
+ nameidone(&ndp);
+ vp = ndp.ni_vp;
+
+ if (vp->v_type != VREG) {
+ err = EINVAL;
+ AUTHPRNT("%s it not a regular file", path);
+ } else if (fsize) {
+ if ((err = vnode_size(vp, fsize, ctx)) != 0) {
+ AUTHPRNT("Cannot get file size of %s - error %d", path, err);
+ }
+ }
+ }
+
+ if (err) {
+ *errp = err;
+ vp = NULL;
+ }
+ return vp;
+}
+#endif /* CONFIG_IMAGEBOOT_CHUNKLIST || CONFIG_IMAGEBOOT_CHUNKLIST */
+
+#if CONFIG_IMAGEBOOT_IMG4
+
+#define APTICKET_NAME "apticket.der"
+
+static char *
+imgboot_get_apticket_path(const char *rootpath, size_t *sz)
+{
+ size_t plen = strlen(rootpath) + sizeof(APTICKET_NAME) + 1;
+ char *path = kheap_alloc(KHEAP_TEMP, plen, Z_WAITOK);
+
+ if (path) {
+ char *slash;
+
+ strlcpy(path, rootpath, plen);
+ slash = strrchr(path, '/');
+ if (slash == NULL) {
+ slash = path;
+ } else {
+ slash++;
+ }
+ strlcpy(slash, APTICKET_NAME, sizeof(APTICKET_NAME) + 1);
+ }
+
+ *sz = plen;
+ return path;
+}
+
+static int
+authenticate_root_with_img4(const char *rootpath)
+{
+ errno_t rv;
+ vnode_t vp;
+ size_t ticket_pathsz = 0;
+ char *ticket_path;
+ img4_buff_t tck = IMG4_BUFF_INIT;
+ img4_firmware_execution_context_t exec = {
+ .i4fex_version = IMG4_FIRMWARE_EXECUTION_CONTEXT_STRUCT_VERSION,
+ .i4fex_execute = NULL,
+ .i4fex_context = NULL,
+ };
+ img4_firmware_t fw = NULL;
+ img4_firmware_flags_t fw_flags = IMG4_FIRMWARE_FLAG_BARE |
+ IMG4_FIRMWARE_FLAG_SUBSEQUENT_STAGE;
+
+ DBG_TRACE("Check %s\n", rootpath);
+
+ if (img4if == NULL) {
+ AUTHPRNT("AppleImage4 is not ready");
+ return EAGAIN;
+ }
+
+ ticket_path = imgboot_get_apticket_path(rootpath, &ticket_pathsz);
+ if (ticket_path == NULL) {
+ AUTHPRNT("Cannot construct ticket path - out of memory");
+ return ENOMEM;
+ }
+
+ rv = imageboot_read_file(KHEAP_TEMP, ticket_path, (void **)&tck.i4b_bytes, &tck.i4b_len);
+ if (rv) {
+ AUTHPRNT("Cannot get a ticket from %s - %d\n", ticket_path, rv);
+ goto out_with_ticket_path;
+ }
+
+ DBG_TRACE("Got %lu bytes of manifest from %s\n", tck.i4b_len, ticket_path);
+
+ vp = imgboot_get_image_file(rootpath, NULL, &rv);
+ if (vp == NULL) {
+ /* Error message had been printed already */
+ rv = EIO;
+ goto out_with_ticket_bytes;
+ }
+
+ fw = img4_firmware_new_from_vnode_4xnu(IMG4_RUNTIME_DEFAULT, &exec, 'rosi',
+ vp, fw_flags);
+ if (!fw) {
+ AUTHPRNT("Could not allocate new firmware");
+ rv = ENOMEM;
+ goto out_with_ticket_bytes;
+ }
+
+ img4_firmware_attach_manifest(fw, &tck);
+ rv = img4_firmware_evaluate(fw, img4_chip_select_personalized_ap(), NULL);
+
+out_with_ticket_bytes:
+ kheap_free_safe(KHEAP_TEMP, tck.i4b_bytes, tck.i4b_len);
+out_with_ticket_path:
+ kheap_free_safe(KHEAP_TEMP, ticket_path, ticket_pathsz);
+
+ img4_firmware_destroy(&fw);
+ return rv;
+}
+#endif /* CONFIG_IMAGEBOOT_IMG4 */
+
+
+/*
+ * Attach the image at 'path' as a ramdisk and mount it as our new rootfs.
+ * All existing mounts are first umounted.
+ */
+static int
+imageboot_mount_ramdisk(const char *path)
+{
+ int err = 0;
+ size_t bufsz = 0;
+ void *buf = NULL;
+ dev_t dev;
+ vnode_t newdp;
+ vnode_t tvp;
+ mount_t new_rootfs;
+
+ /* Read our target image from disk */
+ err = imageboot_read_file(KHEAP_DATA_BUFFERS, path, &buf, &bufsz);
+ if (err) {
+ printf("%s: failed: imageboot_read_file() = %d\n", __func__, err);
+ goto out;
+ }
+ DBG_TRACE("%s: read '%s' sz = %lu\n", __func__, path, bufsz);
+
+#if CONFIG_IMGSRC_ACCESS
+ /* Re-add all root mounts to the mount list in the correct order... */
+ mount_list_remove(rootvnode->v_mount);
+ for (int i = 0; i < MAX_IMAGEBOOT_NESTING; i++) {
+ struct vnode *vn = imgsrc_rootvnodes[i];
+ if (vn) {
+ vnode_getalways(vn);
+ imgsrc_rootvnodes[i] = NULLVP;
+
+ mount_t mnt = vn->v_mount;
+ mount_lock(mnt);
+ mnt->mnt_flag |= MNT_ROOTFS;
+ mount_list_add(mnt);
+ mount_unlock(mnt);
+
+ vnode_rele(vn);
+ vnode_put(vn);
+ }
+ }
+ mount_list_add(rootvnode->v_mount);
+#endif
+
+ /* ... and unmount everything */
+ vfs_unmountall();
+
+ lck_rw_lock_exclusive(rootvnode_rw_lock);
+ filedesc0.fd_cdir = NULL;
+ tvp = rootvnode;
+ rootvnode = NULL;
+ rootvp = NULLVP;
+ rootdev = NODEV;
+ lck_rw_unlock_exclusive(rootvnode_rw_lock);
+ vnode_get_and_drop_always(tvp);
+
+ /* Attach the ramfs image ... */
+ err = di_root_ramfile_buf(buf, bufsz, rootdevice, DEVMAXNAMESIZE, &dev);
+ if (err) {
+ printf("%s: failed: di_root_ramfile_buf() = %d\n", __func__, err);
+ goto out;
+ }
+
+ /* ... and mount it */
+ rootdev = dev;
+ mountroot = NULL;
+ err = vfs_mountroot();
+ if (err) {
+ printf("%s: failed: vfs_mountroot() = %d\n", __func__, err);
+ goto out;
+ }
+
+ /* Switch to new root vnode */
+ if (VFS_ROOT(TAILQ_LAST(&mountlist, mntlist), &newdp, vfs_context_kernel())) {
+ panic("%s: cannot find root vnode", __func__);
+ }
+ vnode_ref(newdp);
+
+ lck_rw_lock_exclusive(rootvnode_rw_lock);
+ rootvnode = newdp;
+ rootvnode->v_flag |= VROOT;
+ new_rootfs = rootvnode->v_mount;
+ mount_lock(new_rootfs);
+ new_rootfs->mnt_flag |= MNT_ROOTFS;
+ mount_unlock(new_rootfs);
+
+ set_fake_bootuuid(new_rootfs);
+
+ filedesc0.fd_cdir = newdp;
+ lck_rw_unlock_exclusive(rootvnode_rw_lock);
+
+ vnode_put(newdp);
+
+ DBG_TRACE("%s: root switched\n", __func__);
+
+out:
+ if (err) {
+ kheap_free_safe(KHEAP_DATA_BUFFERS, buf, bufsz);
+ }
+ return err;
+}
+
+/*
+ * If the path is in <file://> URL format then we allocate memory and decode it,
+ * otherwise return the same pointer.
+ *
+ * Caller is expected to check if the pointers are different.
+ */
+static char *
+url_to_path(char *url_path, size_t *sz)
+{
+ char *path = url_path;
+ size_t len = strlen(kIBFilePrefix);
+
+ if (strncmp(kIBFilePrefix, url_path, len) == 0) {
+ /* its a URL - remove the file:// prefix and percent-decode */
+ url_path += len;
+
+ len = strlen(url_path);
+ if (len) {
+ /* Make a copy of the path to URL-decode */
+ path = kheap_alloc(KHEAP_TEMP, len + 1, Z_WAITOK);
+ if (path == NULL) {
+ panic("imageboot path allocation failed - cannot allocate %d bytes\n", (int)len);
+ }
+
+ strlcpy(path, url_path, len + 1);
+ *sz = len + 1;
+ url_decode(path);
+ } else {
+ panic("Bogus imageboot path URL - missing path\n");
+ }
+
+ DBG_TRACE("%s: root image URL <%s> becomes %s\n", __func__, url_path, path);
+ }
+
+ return path;
+}
+
+static boolean_t
+imageboot_setup_new(imageboot_type_t type)
+{
+ int error;
+ char *root_path = NULL;
+ int height = 0;
+ boolean_t done = FALSE;
+ boolean_t auth_root = TRUE;
+ boolean_t ramdisk_root = FALSE;
+
+ root_path = zalloc(ZV_NAMEI);
+ assert(root_path != NULL);
+
+#if CONFIG_LOCKERBOOT
+ if (type == IMAGEBOOT_LOCKER) {
+ if (!PE_parse_boot_argn(IMAGEBOOT_LOCKER_ARG, root_path, MAXPATHLEN)) {
+ panic("locker boot with no locker given");
+ }
+
+ DBG_TRACE("%s: root fsname: %s\n", __FUNCTION__, rootvnode->v_mount->mnt_vtable->vfc_name);
+
+ /*
+ * The locker path is a path, not a URL, so just pass it directly to
+ * imageboot_mount_image().
+ */
+ error = imageboot_mount_image(root_path, 0, type);
+ if (error) {
+ panic("failed to mount system locker: %d", error);
+ }
+
+ done = TRUE;
+ goto out;
+ }
+#endif /* CONFIG_LOCKERBOOT */
+
+ unsigned imgboot_arg;
+ if (PE_parse_boot_argn("-rootdmg-ramdisk", &imgboot_arg, sizeof(imgboot_arg))) {
+ ramdisk_root = TRUE;
+ }
+
+ if (PE_parse_boot_argn(IMAGEBOOT_CONTAINER_ARG, root_path, MAXPATHLEN) == TRUE) {
+ printf("%s: container image url is %s\n", __FUNCTION__, root_path);
+ error = imageboot_mount_image(root_path, height, type);
+ if (error != 0) {
+ panic("Failed to mount container image.");
+ }
+
+ height++;
+ }
+
+ if (PE_parse_boot_argn(IMAGEBOOT_AUTHROOT_ARG, root_path, MAXPATHLEN) == FALSE &&
+ PE_parse_boot_argn(IMAGEBOOT_ROOT_ARG, root_path, MAXPATHLEN) == FALSE) {
+ if (height > 0) {
+ panic("%s specified without %s or %s?\n", IMAGEBOOT_CONTAINER_ARG, IMAGEBOOT_AUTHROOT_ARG, IMAGEBOOT_ROOT_ARG);
+ }
+ goto out;
+ }
+
+ printf("%s: root image URL is '%s'\n", __func__, root_path);
+
+ /* Make a copy of the path to URL-decode */
+ size_t pathsz;
+ char *path = url_to_path(root_path, &pathsz);
+ assert(path);
+
+#if CONFIG_IMAGEBOOT_CHUNKLIST
+ if (auth_root) {
+ /*
+ * This updates auth_root to reflect whether chunklist was
+ * actually enforced. In effect, this clears auth_root if
+ * CSR_ALLOW_ANY_RECOVERY_OS allowed an invalid image.
+ */
+ AUTHDBG("authenticating root image at %s", path);
+ error = authenticate_root_with_chunklist(path, &auth_root);
+ if (error) {
+ panic("root image authentication failed (err = %d)\n", error);
+ }
+ AUTHDBG("successfully authenticated %s", path);
+ }
+#endif
+
+ if (ramdisk_root) {
+ error = imageboot_mount_ramdisk(path);
+ } else {
+ error = imageboot_mount_image(root_path, height, type);
+ }
+
+ if (path != root_path) {
+ kheap_free_safe(KHEAP_TEMP, path, pathsz);
+ }
+
+ if (error) {
+ panic("Failed to mount root image (err=%d, auth=%d, ramdisk=%d)\n",
+ error, auth_root, ramdisk_root);
+ }
+
+#if CONFIG_IMAGEBOOT_CHUNKLIST
+ if (auth_root) {
+ /* check that the image version matches the running kernel */
+ AUTHDBG("checking root image version");
+ error = authenticate_root_version_check();
+ if (error) {
+ panic("root image version check failed");
+ } else {
+ AUTHDBG("root image version matches kernel");
+ }
+ }
+#endif
+
+ done = TRUE;
+
+out:
+ zfree(ZV_NAMEI, root_path);
+ return done;
+}
+
+__private_extern__ void
+imageboot_setup(imageboot_type_t type)
+{
+ int error = 0;
+ char *root_path = NULL;
+
+ DBG_TRACE("%s: entry\n", __FUNCTION__);
+
+ if (rootvnode == NULL) {
+ panic("imageboot_setup: rootvnode is NULL.");
+ }
+
+ /*
+ * New boot-arg scheme:
+ * root-dmg : the dmg that will be the root filesystem, authenticated by default.
+ * auth-root-dmg : same as root-dmg.
+ * container-dmg : an optional dmg that contains the root-dmg.
+ * locker : the locker that will be the root filesystem -- mutually
+ * exclusive with any other boot-arg.
+ */
+ if (imageboot_setup_new(type)) {
+ return;
+ }
+
+ root_path = zalloc(ZV_NAMEI);
+ assert(root_path != NULL);
+
+ /*
+ * Look for outermost disk image to root from. If we're doing a nested boot,
+ * there's some sense in which the outer image never needs to be the root filesystem,
+ * but it does need very similar treatment: it must not be unmounted, needs a fake
+ * device vnode created for it, and should not show up in getfsstat() until exposed
+ * with MNT_IMGSRC. We just make it the temporary root.
+ */
+#if CONFIG_IMAGEBOOT_IMG4
+ if (PE_parse_boot_argn("arp0", root_path, MAXPATHLEN)) {
+ size_t pathsz;
+ char *path = url_to_path(root_path, &pathsz);
+
+ assert(path);
+
+ if (authenticate_root_with_img4(path)) {
+ panic("Root image %s does not match the manifest\n", root_path);
+ }
+ if (path != root_path) {
+ kheap_free_safe(KHEAP_TEMP, path, pathsz);
+ }
+ } else
+#endif /* CONFIG_IMAGEBOOT_IMG4 */
+ if ((PE_parse_boot_argn("rp", root_path, MAXPATHLEN) == FALSE) &&
+ (PE_parse_boot_argn("rp0", root_path, MAXPATHLEN) == FALSE)) {
+ panic("%s: no valid path to image.\n", __FUNCTION__);
+ }
+
+ DBG_TRACE("%s: root image url is %s\n", __FUNCTION__, root_path);
+
+ error = imageboot_mount_image(root_path, 0, type);
+ if (error) {
+ panic("Failed on first stage of imageboot.");
+ }
+
+ /*
+ * See if we are rooting from a nested image
+ */
+ if (PE_parse_boot_argn("rp1", root_path, MAXPATHLEN) == FALSE) {
+ goto done;
+ }
+
+ printf("%s: second level root image url is %s\n", __FUNCTION__, root_path);
+
+ /*
+ * If we fail to set up second image, it's not a given that we
+ * can safely root off the first.
+ */
+ error = imageboot_mount_image(root_path, 1, type);
+ if (error) {
+ panic("Failed on second stage of imageboot.");
+ }
+