X-Git-Url: https://git.saurik.com/apple/xnu.git/blobdiff_plain/2d21ac55c334faf3a56e5634905ed6987fc787d4..a991bd8d3e7fe02dbca0644054bab73c5b75324a:/bsd/kern/imageboot.c?ds=inline diff --git a/bsd/kern/imageboot.c b/bsd/kern/imageboot.c index 8af10ca2c..36a275c68 100644 --- a/bsd/kern/imageboot.c +++ b/bsd/kern/imageboot.c @@ -1,8 +1,8 @@ /* - * Copyright (c) 2006 Apple Computer, Inc. All rights reserved. + * Copyright (c) 2006-2020 Apple Computer, Inc. All rights reserved. * * @APPLE_OSREFERENCE_LICENSE_HEADER_START@ - * + * * This file contains Original Code and/or Modifications of Original Code * as defined in and that are subject to the Apple Public Source License * Version 2.0 (the 'License'). You may not use this file except in @@ -11,10 +11,10 @@ * unlawful or unlicensed copies of an Apple operating system, or to * circumvent, violate, or enable the circumvention or violation of, any * terms of an Apple operating system software license agreement. - * + * * Please obtain a copy of the License at * http://www.opensource.apple.com/apsl/ and read it before using this file. - * + * * The Original Code and all software distributed under the License are * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, @@ -22,7 +22,7 @@ * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. * Please see the License for the specific language governing rights and * limitations under the License. - * + * * @APPLE_OSREFERENCE_LICENSE_HEADER_END@ */ @@ -32,123 +32,1110 @@ #include #include #include +#include #include #include #include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#if CONFIG_IMAGEBOOT_IMG4 +#include +#include +#endif + +#include #include +#include extern struct filedesc filedesc0; extern int (*mountroot)(void); -extern char rootdevice[]; +extern char rootdevice[DEVMAXNAMESIZE]; + +#if CONFIG_LOCKERBOOT +typedef struct _locker_mount_args { + char lmnt_path[PATH_MAX]; + uint16_t lmnt_preferred_hash; +} locker_mount_args_t; +#endif #define DEBUG_IMAGEBOOT 0 #if DEBUG_IMAGEBOOT -#define DBG_TRACE(...) printf(__VA_ARGS__) +#define DBG_TRACE(...) printf("imageboot: " __VA_ARGS__) #else #define DBG_TRACE(...) do {} while(0) #endif -extern int di_root_image(const char *path, char devname[], dev_t *dev_p); +#define AUTHDBG(fmt, args...) do { printf("%s: " fmt "\n", __func__, ##args); } while (0) +#define AUTHPRNT(fmt, args...) do { printf("%s: " fmt "\n", __func__, ##args); } while (0) +#define kheap_free_safe(h, x, l) do { if ((x)) { kheap_free(h, x, l); (x) = NULL; } } while (0) + +extern int di_root_image_ext(const char *path, char *devname, size_t devsz, dev_t *dev_p, bool removable); +extern int di_root_image(const char *path, char *devname, size_t devsz, dev_t *dev_p); +extern int di_root_ramfile_buf(void *buf, size_t bufsz, char *devname, size_t devsz, dev_t *dev_p); + +static boolean_t imageboot_setup_new(imageboot_type_t type); + +void *ubc_getobject_from_filename(const char *filename, struct vnode **vpp, off_t *file_size); + +extern lck_rw_t * rootvnode_rw_lock; #define kIBFilePrefix "file://" -int +__private_extern__ int +imageboot_format_is_valid(const char *root_path) +{ + return strncmp(root_path, kIBFilePrefix, + strlen(kIBFilePrefix)) == 0; +} + +static void +vnode_get_and_drop_always(vnode_t vp) +{ + vnode_getalways(vp); + vnode_rele(vp); + vnode_put(vp); +} + +__private_extern__ bool +imageboot_desired(void) +{ + bool do_imageboot = false; + + char *root_path = NULL; + root_path = zalloc(ZV_NAMEI); + /* + * Check for first layer DMG rooting. + * + * Note that here we are principally concerned with whether or not we + * SHOULD try to imageboot, not whether or not we are going to be able to. + * + * If NONE of the boot-args are present, then assume that image-rooting + * is not requested. + * + * [!! Note parens guard the entire logically OR'd set of statements, below. It validates + * that NONE of the below-mentioned boot-args is present...!!] + */ + if (!(PE_parse_boot_argn("rp0", root_path, MAXPATHLEN) || +#if CONFIG_IMAGEBOOT_IMG4 + PE_parse_boot_argn("arp0", root_path, MAXPATHLEN) || +#endif + PE_parse_boot_argn("rp", root_path, MAXPATHLEN) || + PE_parse_boot_argn(IMAGEBOOT_ROOT_ARG, root_path, MAXPATHLEN) || + PE_parse_boot_argn(IMAGEBOOT_AUTHROOT_ARG, root_path, MAXPATHLEN))) { + /* explicitly set to false */ + do_imageboot = false; + } else { + /* now sanity check the file-path format */ + if (imageboot_format_is_valid(root_path)) { + DBG_TRACE("%s: Found %s\n", __FUNCTION__, root_path); + /* root_path looks good and we have one of the aforementioned bootargs */ + do_imageboot = true; + } else { + /* explicitly set to false */ + do_imageboot = false; + } + } + + zfree(ZV_NAMEI, root_path); + return do_imageboot; +} + +__private_extern__ imageboot_type_t imageboot_needed(void) { - int result = 0; + imageboot_type_t result = IMAGEBOOT_NONE; char *root_path = NULL; DBG_TRACE("%s: checking for presence of root path\n", __FUNCTION__); - MALLOC_ZONE(root_path, caddr_t, MAXPATHLEN, M_NAMEI, M_WAITOK); - if (root_path == NULL) - panic("%s: M_NAMEI zone exhausted", __FUNCTION__); + if (!imageboot_desired()) { + goto out; + } - if(PE_parse_boot_arg("rp", root_path) == TRUE) { - /* Got it, now verify scheme */ + root_path = zalloc(ZV_NAMEI); + result = IMAGEBOOT_DMG; - if (strncmp(root_path, kIBFilePrefix, - strlen(kIBFilePrefix)) == 0) { - DBG_TRACE("%s: Found %s\n", __FUNCTION__, root_path); - result = 1; + /* Check for second layer */ + if (!(PE_parse_boot_argn("rp1", root_path, MAXPATHLEN) || + PE_parse_boot_argn(IMAGEBOOT_CONTAINER_ARG, root_path, MAXPATHLEN))) { + goto out; + } + + /* Sanity-check second layer */ + if (imageboot_format_is_valid(root_path)) { + DBG_TRACE("%s: Found %s\n", __FUNCTION__, root_path); + } else { + panic("%s: Invalid URL scheme for %s\n", + __FUNCTION__, root_path); + } + +out: + if (root_path != NULL) { + zfree(ZV_NAMEI, root_path); + } + return result; +} + +extern bool IOBaseSystemARVRootHashAvailable(void); + + +/* + * Mounts new filesystem based on image path, and pivots it to the root. + * The image to be mounted is located at image_path. + * It will be mounted at mount_path. + * The vfs_switch_root operation will be performed. + * After the pivot, the outgoing root filesystem (the filesystem at root when + * this function begins) will be at outgoing_root_path. If `rooted_dmg` is true, + * then ignore then chunklisted or authAPFS checks on this image + */ +__private_extern__ int +imageboot_pivot_image(const char *image_path, imageboot_type_t type, const char *mount_path, + const char *outgoing_root_path, const bool rooted_dmg) +{ + int error; + boolean_t authenticated_dmg_chunklist = false; + vnode_t mount_vp = NULLVP; + errno_t rootauth; + + + if (type != IMAGEBOOT_DMG) { + panic("not supported"); + } + + /* + * Check that the image file actually exists. + * We also need to find the mount it's on, to mark it as backing the + * root. + */ + vnode_t imagevp = NULLVP; + error = vnode_lookup(image_path, 0, &imagevp, vfs_context_kernel()); + if (error) { + printf("%s: image file not found or couldn't be read: %d\n", __FUNCTION__, error); + /* + * bail out here to short-circuit out of panic logic below. + * Failure to find the pivot-image should not be a fatal condition (ENOENT) + * since it may result in natural consequences (ergo, cannot unlock filevault prompt). + */ + return error; + } + + /* + * load the disk image and obtain its device. + * di_root_image's name and the names of its arguments suggest it has + * to be mounted at the root, but that's not actually needed. + * We just need to obtain the device info. + */ + + dev_t dev; + char devname[DEVMAXNAMESIZE]; + + error = di_root_image_ext(image_path, devname, DEVMAXNAMESIZE, &dev, true); + if (error) { + panic("%s: di_root_image failed: %d\n", __FUNCTION__, error); + } + + printf("%s: attached disk image %s as %s\n", __FUNCTION__, image_path, devname); + + +#if CONFIG_IMAGEBOOT_CHUNKLIST + if ((rooted_dmg == false) && !IOBaseSystemARVRootHashAvailable()) { + error = authenticate_root_with_chunklist(image_path, NULL); + if (error == 0) { + printf("authenticated root-dmg via chunklist...\n"); + authenticated_dmg_chunklist = true; } else { - DBG_TRACE("%s: Invalid URL scheme for %s\n", - __FUNCTION__, root_path); + /* root hash was not available, and image is NOT chunklisted? */ + printf("failed to chunklist-authenticate root-dmg @ %s\n", image_path); } } - FREE_ZONE(root_path, MAXPATHLEN, M_NAMEI); +#endif + + char fulldevname[DEVMAXNAMESIZE + 5]; // "/dev/" + strlcpy(fulldevname, "/dev/", sizeof(fulldevname)); + strlcat(fulldevname, devname, sizeof(fulldevname)); + + /* + * mount expects another layer of indirection (because it expects to + * be getting a user_addr_t of a char *. + * Make a pointer-to-pointer on our stack. It won't use this + * address after it returns so this should be safe. + */ + char *fulldevnamep = &(fulldevname[0]); + char **fulldevnamepp = &fulldevnamep; - return (result); +#define PIVOTMNT "/System/Volumes/BaseSystem" + + + /* Attempt to mount as HFS; if it fails, then try as APFS */ + printf("%s: attempting to mount as hfs...\n", __FUNCTION__); + error = kernel_mount("hfs", NULLVP, NULLVP, PIVOTMNT, fulldevnamepp, 0, (MNT_RDONLY | MNT_DONTBROWSE), (KERNEL_MOUNT_NOAUTH | KERNEL_MOUNT_BASESYSTEMROOT), vfs_context_kernel()); + if (error) { + printf("mount failed: %d\n", error); + printf("%s: attempting to mount as apfs...\n", __FUNCTION__); + error = kernel_mount("apfs", NULLVP, NULLVP, PIVOTMNT, fulldevnamepp, 0, (MNT_RDONLY | MNT_DONTBROWSE), (KERNEL_MOUNT_NOAUTH | KERNEL_MOUNT_BASESYSTEMROOT), vfs_context_kernel()); + } + + /* If we didn't mount as either HFS or APFS, then bail out */ + if (error) { + /* + * Note that for this particular failure case (failure to mount), the disk image + * being attached may have failed to quiesce within the alloted time out (20-30 sec). + * For example, it may be still probing, or APFS container enumeration may have not + * completed. If so, then we may have fallen into this particular error case. However, + * failure to complete matching should be an exceptional case as 30 sec. is quite a + * long time to wait for matching to complete (which would have occurred in + * di_root_image_ext). + */ +#if defined(__arm64__) && XNU_TARGET_OS_OSX + panic("%s: failed to mount pivot image(%d)!", __FUNCTION__, error); +#endif + printf("%s: failed to mount pivot image(%d) !", __FUNCTION__, error); + goto done; + } + + /* otherwise, if the mount succeeded, then assert that the DMG is authenticated (either chunklist or authapfs) */ + error = vnode_lookup(PIVOTMNT, 0, &mount_vp, vfs_context_kernel()); + if (error) { +#if defined(__arm64__) && XNU_TARGET_OS_OSX + panic("%s: failed to lookup pivot root (%d) !", __FUNCTION__, error); +#endif + printf("%s: failed to lookup pivot root (%d)!", __FUNCTION__, error); + goto done; + } + + /* the 0x1 implies base system */ + rootauth = VNOP_IOCTL(mount_vp, FSIOC_KERNEL_ROOTAUTH, (caddr_t)0x1, 0, vfs_context_kernel()); + if (rootauth) { + printf("BS-DMG failed to authenticate intra-FS \n"); + /* + * If we are using a custom rooted DMG, or if we have already authenticated + * the DMG via chunklist, then it is permissible to use. + */ + if (rooted_dmg || authenticated_dmg_chunklist) { + rootauth = 0; + } + error = rootauth; + } + vnode_put(mount_vp); + mount_vp = NULLVP; + + if (error) { + /* + * Failure here exclusively means that the mount failed to authenticate. + * This means that the disk image either was not sealed (authapfs), or it was + * not hosted on a chunklisted DMG. Both scenarios may be fatal depending + * on the platform. + */ +#if defined(__arm64__) && XNU_TARGET_OS_OSX + panic("%s: could not authenticate the pivot image: %d. giving up.\n", __FUNCTION__, error); +#endif + printf("%s: could not authenticate the pivot image: %d. giving up.\n", __FUNCTION__, error); + goto done; + } + + if (rootvnode) { + mount_t root_mp = vnode_mount(rootvnode); + if (root_mp && (root_mp->mnt_kern_flag & MNTK_SSD)) { + rootvp_is_ssd = true; + } + } + /* + * pivot the incoming and outgoing filesystems + */ + error = vfs_switch_root(mount_path, outgoing_root_path, 0); + if (error) { + panic("%s: vfs_switch_root failed: %d\n", __FUNCTION__, error); + } + + /* + * Mark the filesystem containing the image as backing root, so it + * won't be unmountable. + * + * vfs_switch_root() clears this flag, so we have to set it after + * the pivot call. + * If the system later pivots out of the image, vfs_switch_root + * will clear it again, so the backing filesystem can be unmounted. + */ + mount_t imagemp = imagevp->v_mount; + lck_rw_lock_exclusive(&imagemp->mnt_rwlock); + imagemp->mnt_kern_flag |= MNTK_BACKS_ROOT; + lck_rw_done(&imagemp->mnt_rwlock); + + error = 0; + + /* + * Note that we do NOT change kern.bootuuid here - + * imageboot_mount_image() does, but imageboot_pivot_image() doesn't. + * imageboot_mount_image() is used when the root volume uuid was + * "always supposed to be" the one inside the dmg. imageboot_pivot_ + * image() is used when the true root volume just needs to be + * obscured for a moment by the dmg. + */ + +done: + if (imagevp != NULLVP) { + vnode_put(imagevp); + } + return error; } +/* kern_sysctl.c */ +extern uuid_string_t fake_bootuuid; + +static void +set_fake_bootuuid(mount_t mp) +{ + struct vfs_attr va; + VFSATTR_INIT(&va); + VFSATTR_WANTED(&va, f_uuid); + + if (vfs_getattr(mp, &va, vfs_context_current()) != 0) { + return; + } + + if (!VFSATTR_IS_SUPPORTED(&va, f_uuid)) { + return; + } + + uuid_unparse(va.f_uuid, fake_bootuuid); +} /* - * We know there's an image. Attach it, and - * switch over to root off it - * - * NB: p is always kernproc + * Swaps in new root filesystem based on image path. + * Current root filesystem is removed from mount list and + * tagged MNTK_BACKS_ROOT, MNT_ROOTFS is cleared on it, and + * "rootvnode" is reset. Root vnode of currentroot filesystem + * is returned with usecount (no iocount). + * kern.bootuuid is arranged to return the UUID of the mounted image. (If + * we did nothing here, it would be the UUID of the image source volume.) + */ +__private_extern__ int +imageboot_mount_image(const char *root_path, int height, imageboot_type_t type) +{ + dev_t dev; + int error; + /* + * Need to stash this here since we may do a kernel_mount() on /, which will + * automatically update the rootvnode global. Note that vfs_mountroot() does + * not update that global, which is a bit weird. + */ + vnode_t old_rootvnode = rootvnode; + vnode_t newdp; + mount_t new_rootfs; + boolean_t update_rootvnode = FALSE; + + if (type == IMAGEBOOT_DMG) { + error = di_root_image(root_path, rootdevice, DEVMAXNAMESIZE, &dev); + if (error) { + panic("%s: di_root_image failed: %d\n", __FUNCTION__, error); + } + + rootdev = dev; + mountroot = NULL; + printf("%s: root device 0x%x\n", __FUNCTION__, rootdev); + error = vfs_mountroot(); + if (error != 0) { + panic("vfs_mountroot() failed.\n"); + } + + update_rootvnode = TRUE; + } +#if CONFIG_LOCKERBOOT + else if (type == IMAGEBOOT_LOCKER) { + locker_mount_args_t *mntargs = kheap_alloc(KHEAP_TEMP, + sizeof(*mntargs), Z_WAITOK); + if (!mntargs) { + panic("could not alloc mount args"); + } + + strlcpy(mntargs->lmnt_path, root_path, sizeof(mntargs->lmnt_path)); + mntargs->lmnt_preferred_hash = 0; + + DBG_TRACE("%s: mounting locker: %s\n", __FUNCTION__, root_path); + error = kernel_mount(LOCKERFS_NAME, NULLVP, NULLVP, "/", + mntargs, sizeof(*mntargs), 0, 0, vfs_context_kernel()); + if (error) { + panic("failed to mount locker: %d", error); + } + kheap_free(KHEAP_TEMP, mntargs, sizeof(*mntargs)); + + /* Clear the old mount association. */ + old_rootvnode->v_mountedhere = NULL; + rootvnode->v_mount->mnt_vnodecovered = NULL; + } +#endif + else { + panic("invalid imageboot type: %d", type); + } + + /* + * Get the vnode for '/'. + * Set fdp->fd_fd.fd_cdir to reference it. + */ + if (VFS_ROOT(TAILQ_LAST(&mountlist, mntlist), &newdp, vfs_context_kernel())) { + panic("%s: cannot find root vnode", __FUNCTION__); + } + DBG_TRACE("%s: old root fsname: %s\n", __FUNCTION__, old_rootvnode->v_mount->mnt_vtable->vfc_name); + + if (old_rootvnode != NULL) { + /* remember the old rootvnode, but remove it from mountlist */ + mount_t old_rootfs = old_rootvnode->v_mount; + + mount_list_remove(old_rootfs); + mount_lock(old_rootfs); + old_rootfs->mnt_kern_flag |= MNTK_BACKS_ROOT; + old_rootfs->mnt_flag &= ~MNT_ROOTFS; + mount_unlock(old_rootfs); + } + + vnode_ref(newdp); + vnode_put(newdp); + + lck_rw_lock_exclusive(rootvnode_rw_lock); + /* switch to the new rootvnode */ + if (update_rootvnode) { + rootvnode = newdp; + set_fake_bootuuid(rootvnode->v_mount); + } + + new_rootfs = rootvnode->v_mount; + mount_lock(new_rootfs); + new_rootfs->mnt_flag |= MNT_ROOTFS; + mount_unlock(new_rootfs); + + filedesc0.fd_cdir = newdp; + lck_rw_unlock_exclusive(rootvnode_rw_lock); + + DBG_TRACE("%s: root switched\n", __FUNCTION__); + + if (old_rootvnode != NULL) { +#ifdef CONFIG_IMGSRC_ACCESS + if (height >= 0 && PE_imgsrc_mount_supported()) { + imgsrc_rootvnodes[height] = old_rootvnode; + } else { + vnode_get_and_drop_always(old_rootvnode); + } +#else +#pragma unused(height) + vnode_get_and_drop_always(old_rootvnode); +#endif /* CONFIG_IMGSRC_ACCESS */ + } + return 0; +} + +/* + * Return a memory object for given file path. + * Also returns a vnode reference for the given file path. */ +void * +ubc_getobject_from_filename(const char *filename, struct vnode **vpp, off_t *file_size) +{ + int err = 0; + struct nameidata ndp = {}; + struct vnode *vp = NULL; + off_t fsize = 0; + vfs_context_t ctx = vfs_context_kernel(); + void *control = NULL; + + NDINIT(&ndp, LOOKUP, OP_OPEN, LOCKLEAF, UIO_SYSSPACE, CAST_USER_ADDR_T(filename), ctx); + if ((err = namei(&ndp)) != 0) { + goto errorout; + } + nameidone(&ndp); + vp = ndp.ni_vp; + + if ((err = vnode_size(vp, &fsize, ctx)) != 0) { + goto errorout; + } + + if (fsize < 0) { + goto errorout; + } + + control = ubc_getobject(vp, UBC_FLAGS_NONE); + if (control == NULL) { + goto errorout; + } + + *file_size = fsize; + *vpp = vp; + vp = NULL; + +errorout: + if (vp) { + vnode_put(vp); + } + return control; +} int -imageboot_setup() +imageboot_read_file_from_offset(kalloc_heap_t kheap, const char *path, off_t offset, void **bufp, size_t *bufszp) { - dev_t dev; - int error = 0; - char *root_path = NULL; + int err = 0; + struct nameidata ndp = {}; + struct vnode *vp = NULL; + off_t fsize = 0; + int resid = 0; + char *buf = NULL; + bool doclose = false; - DBG_TRACE("%s: entry\n", __FUNCTION__); + vfs_context_t ctx = vfs_context_kernel(); + proc_t p = vfs_context_proc(ctx); + kauth_cred_t kerncred = vfs_context_ucred(ctx); - MALLOC_ZONE(root_path, caddr_t, MAXPATHLEN, M_NAMEI, M_WAITOK); - if (root_path == NULL) - return (ENOMEM); + 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(PE_parse_boot_arg("rp", root_path) == FALSE) { - error = ENOENT; - goto done; + 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"); } - printf("%s: root image url is %s\n", __FUNCTION__, root_path); - error = di_root_image(root_path, rootdevice, &dev); - if(error) { - printf("%s: di_root_image failed: %d\n", __FUNCTION__, error); - goto done; + 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; + } + + fsize = (off_t)MIN((size_t)fsize, INT_MAX); + buf = kheap_alloc(kheap, (size_t)fsize, Z_WAITOK); + if (buf == NULL) { + err = ENOMEM; + goto out; + } + + if ((err = vn_rdwr(UIO_READ, vp, (caddr_t)buf, (int)fsize, offset, UIO_SYSSPACE, IO_NODELOCKED, kerncred, &resid, p)) != 0) { + AUTHPRNT("Cannot read %d bytes at offset %d from %s - %d", (int)fsize, (int)offset, path, err); + goto out; + } + + if (resid) { + /* didnt get everything we wanted */ + AUTHPRNT("Short read of %d bytes at offset %d from %s - %d", (int)fsize, (int)offset, path, resid); + err = EINVAL; + goto out; + } + +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; - printf("%s: root device 0x%x\n", __FUNCTION__, rootdev); - error = vfs_mountroot(); + 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 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"); + } - if (error == 0 && rootvnode != NULL) { - struct vnode *tvp; - struct vnode *newdp; + DBG_TRACE("%s: root fsname: %s\n", __FUNCTION__, rootvnode->v_mount->mnt_vtable->vfc_name); /* - * Get the vnode for '/'. - * Set fdp->fd_fd.fd_cdir to reference it. + * The locker path is a path, not a URL, so just pass it directly to + * imageboot_mount_image(). */ - if (VFS_ROOT(TAILQ_LAST(&mountlist,mntlist), &newdp, vfs_context_kernel())) - panic("%s: cannot find root vnode", __FUNCTION__); - - vnode_ref(newdp); - vnode_put(newdp); - tvp = rootvnode; - vnode_rele(tvp); - filedesc0.fd_cdir = newdp; - rootvnode = newdp; - mount_list_lock(); - TAILQ_REMOVE(&mountlist, TAILQ_FIRST(&mountlist), mnt_list); - mount_list_unlock(); - mountlist.tqh_first->mnt_flag |= MNT_ROOTFS; - DBG_TRACE("%s: root switched\n", __FUNCTION__); + 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."); + } + done: - FREE_ZONE(root_path, MAXPATHLEN, M_NAMEI); + zfree(ZV_NAMEI, root_path); DBG_TRACE("%s: exit\n", __FUNCTION__); - return (error); + return; }