+int
+read_file(const char *path, void **bufp, size_t *bufszp)
+{
+ int err = 0;
+ struct nameidata ndp = {};
+ struct vnode *vp = NULL;
+ off_t fsize = 0;
+ int resid = 0;
+ char *buf = NULL;
+ bool doclose = false;
+
+ vfs_context_t ctx = vfs_context_kernel();
+ proc_t p = vfs_context_proc(ctx);
+ kauth_cred_t kerncred = vfs_context_ucred(ctx);
+
+ NDINIT(&ndp, LOOKUP, OP_OPEN, LOCKLEAF, UIO_SYSSPACE, CAST_USER_ADDR_T(path), ctx);
+ if ((err = namei(&ndp)) != 0) {
+ AUTHPRNT("namei failed (%s) - %d", path, err);
+ goto out;
+ }
+ nameidone(&ndp);
+ vp = ndp.ni_vp;
+
+ if ((err = vnode_size(vp, &fsize, ctx)) != 0) {
+ AUTHPRNT("failed to get vnode size of %s - %d", path, err);
+ goto out;
+ }
+ if (fsize < 0) {
+ panic("negative file size");
+ }
+
+ if ((err = VNOP_OPEN(vp, FREAD, ctx)) != 0) {
+ AUTHPRNT("failed to open %s - %d", path, err);
+ goto out;
+ }
+ doclose = true;
+
+ /* if bufsz is non-zero, cap the read at bufsz bytes */
+ if (*bufszp && *bufszp < (size_t)fsize) {
+ fsize = *bufszp;
+ }
+
+ buf = kalloc(fsize);
+ if (buf == NULL) {
+ err = ENOMEM;
+ goto out;
+ }
+
+ if ((err = vn_rdwr(UIO_READ, vp, (caddr_t)buf, fsize, 0, UIO_SYSSPACE, IO_NODELOCKED, kerncred, &resid, p)) != 0) {
+ AUTHPRNT("Cannot read %d bytes from %s - %d", (int)fsize, path, err);
+ goto out;
+ }
+
+ if (resid) {
+ /* didnt get everything we wanted */
+ AUTHPRNT("Short read of %d bytes from %s - %d", (int)fsize, path, resid);
+ err = EINVAL;
+ goto out;
+ }
+
+out:
+ if (doclose) {
+ VNOP_CLOSE(vp, FREAD, ctx);
+ }
+ if (vp) {
+ vnode_put(vp);
+ vp = NULL;
+ }
+
+ if (err) {
+ kfree_safe(buf);
+ } else {
+ *bufp = buf;
+ *bufszp = fsize;
+ }
+
+ return err;
+}
+
+#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 plen = strlen(rootpath) + sizeof(APTICKET_NAME);
+ char *path = kalloc(plen);
+
+ 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);
+ }
+ return path;
+}
+
+static int
+authenticate_root_with_img4(const char *rootpath)
+{
+ errno_t rv;
+ img4_t i4;
+ img4_payload_t i4pl;
+ vnode_t vp;
+ char *ticket_path;
+ size_t tcksz = 0;
+ void *tckbuf = NULL;
+
+ DBG_TRACE("Check %s\n", rootpath);
+
+ if (img4if == NULL) {
+ AUTHPRNT("AppleImage4 is not ready");
+ return EAGAIN;
+ }
+
+ ticket_path = imgboot_get_apticket_path(rootpath);
+ if (ticket_path == NULL) {
+ AUTHPRNT("Cannot construct ticket path - out of memory");
+ return ENOMEM;
+ }
+
+ rv = read_file(ticket_path, &tckbuf, &tcksz);
+ if (rv) {
+ AUTHPRNT("Cannot get a ticket from %s - %d\n", ticket_path, rv);
+ goto out_with_ticket_path;
+ }
+
+ DBG_TRACE("Got %d bytes of manifest from %s\n", (int)tcksz, ticket_path);
+
+ rv = img4_init(&i4, 0, tckbuf, tcksz, NULL);
+ if (rv) {
+ AUTHPRNT("Cannot initialise verification handle - error %d", rv);
+ goto out_with_ticket_bytes;
+ }
+
+ vp = imgboot_get_image_file(rootpath, NULL, &rv);
+ if (vp == NULL) {
+ /* Error message had been printed already */
+ goto out;
+ }
+
+ rv = img4_payload_init_with_vnode_4xnu(&i4pl, 'rosi', vp, I4PLF_UNWRAPPED);
+ if (rv) {
+ AUTHPRNT("failed to init payload: %d", rv);
+ goto out;
+ }
+
+ rv = img4_get_trusted_external_payload(&i4, &i4pl, IMG4_ENVIRONMENT_PPL, NULL, NULL);
+ if (rv) {
+ AUTHPRNT("failed to validate root image %s: %d", rootpath, rv);
+ }
+
+ img4_payload_destroy(&i4pl);
+out:
+ img4_destroy(&i4);
+out_with_ticket_bytes:
+ kfree_safe(tckbuf);
+out_with_ticket_path:
+ kfree_safe(ticket_path);
+ 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;
+ mount_t new_rootfs;
+
+ /* Read our target image from disk */
+ err = read_file(path, &buf, &bufsz);
+ if (err) {
+ printf("%s: failed: 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 */
+ vnode_get_and_drop_always(rootvnode);
+ filedesc0.fd_cdir = NULL;
+ rootvnode = NULL;
+ vfs_unmountall();
+
+ /* 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__);
+ }
+ 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);
+
+ vnode_ref(newdp);
+ vnode_put(newdp);
+ filedesc0.fd_cdir = newdp;
+
+ DBG_TRACE("%s: root switched\n", __func__);
+
+out:
+ if (err) {
+ kfree_safe(buf);
+ }
+ 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)
+{
+ 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 = kalloc(len + 1);
+ if (path == NULL) {
+ panic("imageboot path allocation failed - cannot allocate %d bytes\n", (int)len);
+ }
+
+ strlcpy(path, url_path, 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)