X-Git-Url: https://git.saurik.com/apple/xnu.git/blobdiff_plain/0a7de7458d150b5d4dffc935ba399be265ef0a1a..a991bd8d3e7fe02dbca0644054bab73c5b75324a:/bsd/kern/imageboot.c diff --git a/bsd/kern/imageboot.c b/bsd/kern/imageboot.c index 96c0a1e73..36a275c68 100644 --- a/bsd/kern/imageboot.c +++ b/bsd/kern/imageboot.c @@ -1,5 +1,5 @@ /* - * Copyright (c) 2006 Apple Computer, Inc. All rights reserved. + * Copyright (c) 2006-2020 Apple Computer, Inc. All rights reserved. * * @APPLE_OSREFERENCE_LICENSE_HEADER_START@ * @@ -32,6 +32,7 @@ #include #include #include +#include #include #include #include @@ -46,6 +47,12 @@ #include #include #include +#include + +#if CONFIG_IMAGEBOOT_IMG4 +#include +#include +#endif #include @@ -57,18 +64,34 @@ extern struct filedesc filedesc0; extern int (*mountroot)(void); 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 +#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(void); +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://" @@ -87,35 +110,64 @@ vnode_get_and_drop_always(vnode_t vp) vnode_put(vp); } -__private_extern__ int -imageboot_needed(void) +__private_extern__ bool +imageboot_desired(void) { - int result = 0; - char *root_path = NULL; - - DBG_TRACE("%s: checking for presence of root path\n", __FUNCTION__); + bool do_imageboot = false; - MALLOC_ZONE(root_path, caddr_t, MAXPATHLEN, M_NAMEI, M_WAITOK); - if (root_path == NULL) { - panic("%s: M_NAMEI zone exhausted", __FUNCTION__); - } - - /* Check for first layer */ + 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))) { - goto out; + /* 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; + } } - /* Sanity-check first layer */ - if (imageboot_format_is_valid(root_path)) { - DBG_TRACE("%s: Found %s\n", __FUNCTION__, root_path); - } else { + zfree(ZV_NAMEI, root_path); + return do_imageboot; +} + +__private_extern__ imageboot_type_t +imageboot_needed(void) +{ + imageboot_type_t result = IMAGEBOOT_NONE; + char *root_path = NULL; + + DBG_TRACE("%s: checking for presence of root path\n", __FUNCTION__); + + if (!imageboot_desired()) { goto out; } - result = 1; + root_path = zalloc(ZV_NAMEI); + result = IMAGEBOOT_DMG; /* Check for second layer */ if (!(PE_parse_boot_argn("rp1", root_path, MAXPATHLEN) || @@ -132,11 +184,235 @@ imageboot_needed(void) } out: - FREE_ZONE(root_path, MAXPATHLEN, M_NAMEI); - + 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 { + /* root hash was not available, and image is NOT chunklisted? */ + printf("failed to chunklist-authenticate root-dmg @ %s\n", image_path); + } + } +#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; + +#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); +} /* * Swaps in new root filesystem based on image path. @@ -144,27 +420,66 @@ out: * 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_mount_image(const char *root_path, int height, imageboot_type_t type) { dev_t dev; int error; - vnode_t old_rootvnode = NULL; + /* + * 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; - error = di_root_image(root_path, rootdevice, DEVMAXNAMESIZE, &dev); - if (error) { - panic("%s: di_root_image failed: %d\n", __FUNCTION__, error); + 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"); + } - rootdev = dev; - mountroot = NULL; - printf("%s: root device 0x%x\n", __FUNCTION__, rootdev); - error = vfs_mountroot(); - if (error != 0) { - panic("vfs_mountroot() failed.\n"); + 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); } /* @@ -174,35 +489,37 @@ imageboot_mount_image(const char *root_path, int height) 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 (rootvnode != NULL) { + if (old_rootvnode != NULL) { /* remember the old rootvnode, but remove it from mountlist */ - mount_t old_rootfs; - - old_rootvnode = rootvnode; - old_rootfs = rootvnode->v_mount; + mount_t old_rootfs = old_rootvnode->v_mount; mount_list_remove(old_rootfs); - mount_lock(old_rootfs); -#ifdef CONFIG_IMGSRC_ACCESS old_rootfs->mnt_kern_flag |= MNTK_BACKS_ROOT; -#endif /* CONFIG_IMGSRC_ACCESS */ 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 */ - rootvnode = newdp; + 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); - vnode_ref(newdp); - vnode_put(newdp); filedesc0.fd_cdir = newdp; + lck_rw_unlock_exclusive(rootvnode_rw_lock); + DBG_TRACE("%s: root switched\n", __FUNCTION__); if (old_rootvnode != NULL) { @@ -213,44 +530,60 @@ imageboot_mount_image(const char *root_path, int height) vnode_get_and_drop_always(old_rootvnode); } #else - height = 0; /* keep the compiler from complaining */ +#pragma unused(height) vnode_get_and_drop_always(old_rootvnode); #endif /* CONFIG_IMGSRC_ACCESS */ } return 0; } - /* - * Authenticated root-dmg support + * 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; -#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) + 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; -#define kfree_safe(x) do { if ((x)) { kfree_addr((x)); (x) = NULL; } } while (0) + if ((err = vnode_size(vp, &fsize, ctx)) != 0) { + goto errorout; + } -enum { - MISSING_SIG = -1, - INVALID_SIG = -2 -}; + if (fsize < 0) { + goto errorout; + } -static void -key_byteswap(void *_dst, const void *_src, size_t len) -{ - uint32_t *dst __attribute__((align_value(1))) = _dst; - const uint32_t *src __attribute__((align_value(1))) = _src; + control = ubc_getobject(vp, UBC_FLAGS_NONE); + if (control == NULL) { + goto errorout; + } - assert(len % sizeof(uint32_t) == 0); + *file_size = fsize; + *vpp = vp; + vp = NULL; - len = len / sizeof(uint32_t); - for (size_t i = 0; i < len; i++) { - dst[len - i - 1] = OSSwapInt32(src[i]); +errorout: + if (vp) { + vnode_put(vp); } + return control; } -static int -read_file(const char *path, void **bufp, size_t *bufszp) +int +imageboot_read_file_from_offset(kalloc_heap_t kheap, const char *path, off_t offset, void **bufp, size_t *bufszp) { int err = 0; struct nameidata ndp = {}; @@ -266,14 +599,14 @@ read_file(const char *path, void **bufp, size_t *bufszp) NDINIT(&ndp, LOOKUP, OP_OPEN, LOCKLEAF, UIO_SYSSPACE, CAST_USER_ADDR_T(path), ctx); if ((err = namei(&ndp)) != 0) { - AUTHPRNT("namei failed (%s)", path); + 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"); + AUTHPRNT("failed to get vnode size of %s - %d", path, err); goto out; } if (fsize < 0) { @@ -281,7 +614,7 @@ read_file(const char *path, void **bufp, size_t *bufszp) } if ((err = VNOP_OPEN(vp, FREAD, ctx)) != 0) { - AUTHPRNT("failed to open vnode"); + AUTHPRNT("failed to open %s - %d", path, err); goto out; } doclose = true; @@ -291,20 +624,22 @@ read_file(const char *path, void **bufp, size_t *bufszp) fsize = *bufszp; } - buf = kalloc(fsize); + 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, fsize, 0, UIO_SYSSPACE, IO_NODELOCKED, kerncred, &resid, p)) != 0) { - AUTHPRNT("vn_rdwr() failed"); + 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("vn_rdwr resid = %d", resid); + AUTHPRNT("Short read of %d bytes at offset %d from %s - %d", (int)fsize, (int)offset, path, resid); err = EINVAL; goto out; } @@ -319,522 +654,148 @@ out: } if (err) { - kfree_safe(buf); + kheap_free_safe(kheap, buf, (size_t)fsize); } else { *bufp = buf; - *bufszp = fsize; + *bufszp = (size_t)fsize; } return err; } -static int -validate_signature(const uint8_t *key_msb, size_t keylen, uint8_t *sig_msb, size_t siglen, uint8_t *digest) +int +imageboot_read_file(kalloc_heap_t kheap, const char *path, void **bufp, size_t *bufszp) { - int err = 0; - bool sig_valid = false; - uint8_t *sig = NULL; - - const uint8_t exponent[] = { 0x01, 0x00, 0x01 }; - uint8_t *modulus = kalloc(keylen); - rsa_pub_ctx *rsa_ctx = kalloc(sizeof(rsa_pub_ctx)); - sig = kalloc(siglen); - - if (modulus == NULL || rsa_ctx == NULL || sig == NULL) { - err = ENOMEM; - goto out; - } - - bzero(rsa_ctx, sizeof(rsa_pub_ctx)); - key_byteswap(modulus, key_msb, keylen); - key_byteswap(sig, sig_msb, siglen); - - err = rsa_make_pub(rsa_ctx, - sizeof(exponent), exponent, - CHUNKLIST_PUBKEY_LEN, modulus); - if (err) { - AUTHPRNT("rsa_make_pub() failed"); - goto out; - } - - err = rsa_verify_pkcs1v15(rsa_ctx, CC_DIGEST_OID_SHA256, - SHA256_DIGEST_LENGTH, digest, - siglen, sig, - &sig_valid); - if (err) { - sig_valid = false; - AUTHPRNT("rsa_verify() failed"); - err = EINVAL; - goto out; - } - -out: - kfree_safe(sig); - kfree_safe(rsa_ctx); - kfree_safe(modulus); - - if (err) { - return err; - } else if (sig_valid == true) { - return 0; /* success */ - } else { - return INVALID_SIG; - } + return imageboot_read_file_from_offset(kheap, path, 0, bufp, bufszp); } -static int -validate_chunklist(void *buf, size_t len) +#if CONFIG_IMAGEBOOT_IMG4 || CONFIG_IMAGEBOOT_CHUNKLIST +vnode_t +imgboot_get_image_file(const char *path, off_t *fsize, int *errp) { - int err = 0; - size_t sigsz = 0; - size_t sig_end = 0; - size_t chunks_end = 0; - bool valid_sig = false; - struct chunklist_hdr *hdr = buf; - - if (len < sizeof(struct chunklist_hdr)) { - AUTHPRNT("no space for header"); - return EINVAL; - } - - /* recognized file format? */ - if (hdr->cl_magic != CHUNKLIST_MAGIC || - hdr->cl_file_ver != CHUNKLIST_FILE_VERSION_10 || - hdr->cl_chunk_method != CHUNKLIST_SIGNATURE_METHOD_10 || - hdr->cl_sig_method != CHUNKLIST_SIGNATURE_METHOD_10) { - AUTHPRNT("unrecognized chunklist format"); - return EINVAL; - } - - /* does the chunk list fall within the bounds of the buffer? */ - if (os_mul_and_add_overflow(hdr->cl_chunk_count, sizeof(struct chunklist_chunk), hdr->cl_chunk_offset, &chunks_end) || - hdr->cl_chunk_offset < sizeof(struct chunklist_hdr) || chunks_end > len) { - AUTHPRNT("invalid chunk_count (%llu) or chunk_offset (%llu)", - hdr->cl_chunk_count, hdr->cl_chunk_offset); - return EINVAL; - } - - /* does the signature fall within the bounds of the buffer? */ - if (os_add_overflow(hdr->cl_sig_offset, sizeof(struct chunklist_sig), &sig_end) || - hdr->cl_sig_offset < sizeof(struct chunklist_hdr) || - hdr->cl_sig_offset < chunks_end || - hdr->cl_sig_offset > len) { - AUTHPRNT("invalid signature offset (%llu)", hdr->cl_sig_offset); - return EINVAL; - } - - if (sig_end > len || os_sub_overflow(len, hdr->cl_sig_offset, &sigsz) || sigsz != CHUNKLIST_SIG_LEN) { - /* missing or incorrect signature size */ - return MISSING_SIG; - } - - AUTHDBG("hashing chunklist"); - - /* hash the chunklist (excluding the signature) */ - uint8_t sha_digest[SHA256_DIGEST_LENGTH]; - SHA256_CTX sha_ctx; - SHA256_Init(&sha_ctx); - SHA256_Update(&sha_ctx, buf, hdr->cl_sig_offset); - SHA256_Final(sha_digest, &sha_ctx); - - AUTHDBG("validating chunklist signature against pub keys"); - for (size_t i = 0; i < CHUNKLIST_NPUBKEYS; i++) { - const struct chunklist_pubkey *key = &chunklist_pubkeys[i]; - err = validate_signature(key->key, CHUNKLIST_PUBKEY_LEN, - buf + hdr->cl_sig_offset, sigsz, sha_digest); - if (err == 0) { - AUTHDBG("validated chunklist signature with key %lu (prod=%d)", i, key->isprod); - valid_sig = key->isprod; -#if IMAGEBOOT_ALLOW_DEVKEYS - if (!key->isprod) { - /* allow dev keys in dev builds only */ - AUTHDBG("*** allowing DEV key: this will fail in customer builds ***"); - valid_sig = true; - } -#endif - goto out; - } else if (err == INVALID_SIG) { - /* try the next key */ - } else { - goto out; /* something bad happened */ - } - } - - /* At this point we tried all the keys: nothing went wrong but none of them - * signed our chunklist. */ - AUTHPRNT("signature did not verify against any known public key"); - -out: - if (err) { - return err; - } else if (valid_sig == true) { - return 0; /* signed, and everything checked out */ - } else { - return EINVAL; - } -} - -static int -validate_root_image(const char *root_path, void *chunklist) -{ - int err = 0; - struct chunklist_hdr *hdr = chunklist; - struct chunklist_chunk *chk = NULL; - size_t ch = 0; struct nameidata ndp = {}; - struct vnode *vp = NULL; - off_t fsize = 0; - off_t offset = 0; - bool doclose = false; - size_t bufsz = 0; - void *buf = NULL; - + vnode_t vp = NULL; vfs_context_t ctx = vfs_context_kernel(); - kauth_cred_t kerncred = vfs_context_ucred(ctx); - proc_t p = vfs_context_proc(ctx); - - AUTHDBG("validating root dmg %s", root_path); + int err; - /* - * Open the DMG - */ - NDINIT(&ndp, LOOKUP, OP_OPEN, LOCKLEAF, UIO_SYSSPACE, CAST_USER_ADDR_T(root_path), ctx); + NDINIT(&ndp, LOOKUP, OP_OPEN, LOCKLEAF, UIO_SYSSPACE, CAST_USER_ADDR_T(path), ctx); if ((err = namei(&ndp)) != 0) { - AUTHPRNT("namei failed (%s)", root_path); - goto out; - } - nameidone(&ndp); - vp = ndp.ni_vp; - - if (vp->v_type != VREG) { - err = EINVAL; - goto out; - } - - if ((err = vnode_size(vp, &fsize, ctx)) != 0) { - AUTHPRNT("failed to get vnode size"); - goto out; - } - - if ((err = VNOP_OPEN(vp, FREAD, ctx)) != 0) { - AUTHPRNT("failed to open vnode"); - goto out; - } - doclose = true; - - /* - * Iterate the chunk list and check each chunk - */ - chk = chunklist + hdr->cl_chunk_offset; - for (ch = 0; ch < hdr->cl_chunk_count; ch++) { - int resid = 0; - - if (!buf) { - /* allocate buffer based on first chunk size */ - buf = kalloc(chk->chunk_size); - if (buf == NULL) { - err = ENOMEM; - goto out; - } - bufsz = chk->chunk_size; - } - - if (chk->chunk_size > bufsz) { - AUTHPRNT("chunk size too big"); - err = EINVAL; - goto out; - } - - err = vn_rdwr(UIO_READ, vp, (caddr_t)buf, chk->chunk_size, offset, UIO_SYSSPACE, IO_NODELOCKED, kerncred, &resid, p); - if (err) { - AUTHPRNT("vn_rdrw fail (err = %d, resid = %d)", err, resid); - goto out; - } - if (resid) { - err = EINVAL; - AUTHPRNT("chunk covered non-existant part of image"); - goto out; - } - - /* calculate the SHA256 of this chunk */ - uint8_t sha_digest[SHA256_DIGEST_LENGTH]; - SHA256_CTX sha_ctx; - SHA256_Init(&sha_ctx); - SHA256_Update(&sha_ctx, buf, chk->chunk_size); - SHA256_Final(sha_digest, &sha_ctx); - - /* Check the calculated SHA matches the chunk list */ - if (bcmp(sha_digest, chk->chunk_sha256, SHA256_DIGEST_LENGTH) != 0) { - AUTHPRNT("SHA mismatch on chunk %lu (offset %lld, size %u)", ch, offset, chk->chunk_size); - err = EINVAL; - goto out; - } + AUTHPRNT("Cannot find %s - error %d", path, err); + } else { + nameidone(&ndp); + vp = ndp.ni_vp; - if (os_add_overflow(offset, chk->chunk_size, &offset)) { + if (vp->v_type != VREG) { err = EINVAL; - goto out; + 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); + } } - chk++; - } - - if (offset != fsize) { - AUTHPRNT("chunklist did not cover entire file (offset = %lld, fsize = %lld)", offset, fsize); - err = EINVAL; - goto out; - } - -out: - kfree_safe(buf); - if (doclose) { - VNOP_CLOSE(vp, FREAD, ctx); - } - if (vp) { - vnode_put(vp); - vp = NULL; - } - - return err; -} - -static int -construct_chunklist_path(const char *root_path, char **bufp) -{ - int err = 0; - char *path = NULL; - size_t len = 0; - - path = kalloc(MAXPATHLEN); - if (path == NULL) { - AUTHPRNT("failed to allocate space for chunklist path"); - err = ENOMEM; - goto out; - } - - len = strnlen(root_path, MAXPATHLEN); - if (len < MAXPATHLEN && len > strlen(".dmg")) { - /* correctly terminated string with space for extension */ - } else { - AUTHPRNT("malformed root path"); - err = EINVAL; - goto out; - } - - len = strlcpy(path, root_path, MAXPATHLEN); - if (len >= MAXPATHLEN) { - AUTHPRNT("root path is too long"); - err = EINVAL; - goto out; } - path[len - strlen(".dmg")] = '\0'; - len = strlcat(path, ".chunklist", MAXPATHLEN); - if (len >= MAXPATHLEN) { - AUTHPRNT("chunklist path is too long"); - err = EINVAL; - goto out; - } - -out: if (err) { - kfree_safe(path); - } else { - *bufp = path; + *errp = err; + vp = NULL; } - return err; + return vp; } +#endif /* CONFIG_IMAGEBOOT_CHUNKLIST || CONFIG_IMAGEBOOT_CHUNKLIST */ -static int -authenticate_root(const char *root_path) -{ - char *chunklist_path = NULL; - void *chunklist_buf = NULL; - size_t chunklist_len = 32 * 1024 * 1024UL; - int err = 0; - - err = construct_chunklist_path(root_path, &chunklist_path); - if (err) { - AUTHPRNT("failed creating chunklist path"); - goto out; - } - - AUTHDBG("validating root against chunklist %s", chunklist_path); - - /* - * Read and authenticate the chunklist, then validate the root image against - * the chunklist. - */ - - AUTHDBG("reading chunklist"); - err = read_file(chunklist_path, &chunklist_buf, &chunklist_len); - if (err) { - AUTHPRNT("failed to read chunklist"); - goto out; - } - - AUTHDBG("validating chunklist"); - err = validate_chunklist(chunklist_buf, chunklist_len); - if (err < 0) { - AUTHDBG("missing or incorrect signature on chunklist"); - goto out; - } else if (err) { - AUTHPRNT("failed to validate chunklist"); - goto out; - } else { - AUTHDBG("successfully validated chunklist"); - } +#if CONFIG_IMAGEBOOT_IMG4 - AUTHDBG("validating root image against chunklist"); - err = validate_root_image(root_path, chunklist_buf); - if (err) { - AUTHPRNT("failed to validate root image against chunklist (%d)", err); - goto out; - } +#define APTICKET_NAME "apticket.der" - /* everything checked out - go ahead and mount this */ - AUTHDBG("root image authenticated"); - -out: - kfree_safe(chunklist_buf); - kfree_safe(chunklist_path); - return err; -} - -static const uuid_t * -getuuidfromheader_safe(const void *buf, size_t bufsz, size_t *uuidsz) +static char * +imgboot_get_apticket_path(const char *rootpath, size_t *sz) { - const struct uuid_command *cmd = NULL; - const kernel_mach_header_t *mh = buf; + size_t plen = strlen(rootpath) + sizeof(APTICKET_NAME) + 1; + char *path = kheap_alloc(KHEAP_TEMP, plen, Z_WAITOK); - /* space for the header and at least one load command? */ - if (bufsz < sizeof(kernel_mach_header_t) + sizeof(struct uuid_command)) { - AUTHPRNT("libkern image too small"); - return NULL; - } + if (path) { + char *slash; - /* validate the mach header */ - if (mh->magic != MH_MAGIC_64 || (mh->sizeofcmds > bufsz - sizeof(kernel_mach_header_t))) { - AUTHPRNT("invalid MachO header"); - return NULL; - } - - /* iterate the load commands */ - size_t offset = sizeof(kernel_mach_header_t); - for (size_t i = 0; i < mh->ncmds; i++) { - cmd = buf + offset; - - if (cmd->cmd == LC_UUID) { - *uuidsz = sizeof(cmd->uuid); - return &cmd->uuid; - } - - if (os_add_overflow(cmd->cmdsize, offset, &offset) || - offset > bufsz - sizeof(struct uuid_command)) { - return NULL; + strlcpy(path, rootpath, plen); + slash = strrchr(path, '/'); + if (slash == NULL) { + slash = path; + } else { + slash++; } + strlcpy(slash, APTICKET_NAME, sizeof(APTICKET_NAME) + 1); } - return NULL; + *sz = plen; + return path; } -static const char *libkern_path = "/System/Library/Extensions/System.kext/PlugIns/Libkern.kext/Libkern"; -static const char *libkern_bundle = "com.apple.kpi.libkern"; - -/* - * Check that the UUID of the libkern currently loaded matches the one on disk. - */ static int -auth_version_check(void) +authenticate_root_with_img4(const char *rootpath) { - int err = 0; - void *buf = NULL; - size_t bufsz = 4 * 1024 * 1024UL; + 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; - /* get the UUID of the libkern in /S/L/E */ + DBG_TRACE("Check %s\n", rootpath); - err = read_file(libkern_path, &buf, &bufsz); - if (err) { - goto out; + if (img4if == NULL) { + AUTHPRNT("AppleImage4 is not ready"); + return EAGAIN; } - unsigned long uuidsz = 0; - const uuid_t *img_uuid = getuuidfromheader_safe(buf, bufsz, &uuidsz); - if (img_uuid == NULL || uuidsz != sizeof(uuid_t)) { - AUTHPRNT("invalid UUID (sz = %lu)", uuidsz); - err = EINVAL; - goto out; + ticket_path = imgboot_get_apticket_path(rootpath, &ticket_pathsz); + if (ticket_path == NULL) { + AUTHPRNT("Cannot construct ticket path - out of memory"); + return ENOMEM; } - /* Get the UUID of the loaded libkern */ - uuid_t live_uuid; - err = OSKextGetUUIDForName(libkern_bundle, live_uuid); - if (err) { - AUTHPRNT("could not find loaded libkern"); - goto out; + 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; } - /* ... and compare them */ - if (bcmp(live_uuid, img_uuid, uuidsz) != 0) { - AUTHPRNT("UUID of running libkern does not match %s", libkern_path); + DBG_TRACE("Got %lu bytes of manifest from %s\n", tck.i4b_len, ticket_path); - uuid_string_t img_uuid_str, live_uuid_str; - uuid_unparse(*img_uuid, img_uuid_str); - uuid_unparse(live_uuid, live_uuid_str); - AUTHPRNT("loaded libkern UUID = %s", live_uuid_str); - AUTHPRNT("on-disk libkern UUID = %s", img_uuid_str); - - err = EINVAL; - goto out; + vp = imgboot_get_image_file(rootpath, NULL, &rv); + if (vp == NULL) { + /* Error message had been printed already */ + rv = EIO; + goto out_with_ticket_bytes; } - /* UUID matches! */ - -out: - kfree_safe(buf); - return err; -} - -#if 0 -int -auth_imgboot_test(proc_t __unused ap, struct auth_imgboot_test_args *uap, int32_t *retval) -{ - int ret = 0; - int err; - char path[MAXPATHLEN]; - vm_size_t len; - *retval = 0; - - err = copyinstr(uap->path, path, MAXPATHLEN, &len); - if (err) { - return err; - } - if (len >= MAXPATHLEN) { - return ENAMETOOLONG; + 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; } - AUTHDBG("authenticating root image at %s", path); - err = authenticate_root(path); - if (err) { - AUTHPRNT("root authentication FAIL (%d)", err); - ret = err; - } else { - AUTHDBG("successfully authenticated %s", path); - } + img4_firmware_attach_manifest(fw, &tck); + rv = img4_firmware_evaluate(fw, img4_chip_select_personalized_ap(), NULL); - AUTHDBG("checking root image version"); - err = auth_version_check(); - if (err) { - AUTHPRNT("root image version check FAIL (%d)", err); - err = err ?: ret; - } else { - AUTHPRNT("root version check success (%d)", err); - } +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); - if (ret < 0) { - return EINVAL; /* negative return values have special meaning */ - } - return ret; + img4_firmware_destroy(&fw); + return rv; } -#endif +#endif /* CONFIG_IMAGEBOOT_IMG4 */ + /* * Attach the image at 'path' as a ramdisk and mount it as our new rootfs. @@ -848,12 +809,13 @@ imageboot_mount_ramdisk(const char *path) void *buf = NULL; dev_t dev; vnode_t newdp; + vnode_t tvp; mount_t new_rootfs; /* Read our target image from disk */ - err = read_file(path, &buf, &bufsz); + err = imageboot_read_file(KHEAP_DATA_BUFFERS, path, &buf, &bufsz); if (err) { - printf("%s: failed: read_file() = %d\n", __func__, err); + printf("%s: failed: imageboot_read_file() = %d\n", __func__, err); goto out; } DBG_TRACE("%s: read '%s' sz = %lu\n", __func__, path, bufsz); @@ -881,10 +843,16 @@ imageboot_mount_ramdisk(const char *path) #endif /* ... and unmount everything */ - vnode_get_and_drop_always(rootvnode); + vfs_unmountall(); + + lck_rw_lock_exclusive(rootvnode_rw_lock); filedesc0.fd_cdir = NULL; + tvp = rootvnode; rootvnode = NULL; - vfs_unmountall(); + 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); @@ -906,6 +874,9 @@ imageboot_mount_ramdisk(const char *path) 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; @@ -913,32 +884,94 @@ imageboot_mount_ramdisk(const char *path) new_rootfs->mnt_flag |= MNT_ROOTFS; mount_unlock(new_rootfs); - vnode_ref(newdp); - vnode_put(newdp); + 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) { - kfree_safe(buf); + 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_setup_new(imageboot_type_t type) { int error; char *root_path = NULL; int height = 0; boolean_t done = FALSE; - boolean_t auth_root = FALSE; + boolean_t auth_root = TRUE; boolean_t ramdisk_root = FALSE; - MALLOC_ZONE(root_path, caddr_t, MAXPATHLEN, M_NAMEI, M_WAITOK); + 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; @@ -946,7 +979,7 @@ imageboot_setup_new() 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); + error = imageboot_mount_image(root_path, height, type); if (error != 0) { panic("Failed to mount container image."); } @@ -954,81 +987,74 @@ imageboot_setup_new() height++; } - if (PE_parse_boot_argn(IMAGEBOOT_AUTHROOT_ARG, root_path, MAXPATHLEN) == TRUE) { - auth_root = TRUE; - } else if (PE_parse_boot_argn(IMAGEBOOT_ROOT_ARG, root_path, MAXPATHLEN) == FALSE) { + 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?\n", IMAGEBOOT_CONTAINER_ARG, IMAGEBOOT_ROOT_ARG); + 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); - -#if CONFIG_CSR - if (auth_root && (csr_check(CSR_ALLOW_ANY_RECOVERY_OS) == 0)) { - AUTHPRNT("CSR_ALLOW_ANY_RECOVERY_OS set, skipping root image authentication"); - auth_root = false; - } -#endif + printf("%s: root image URL is '%s'\n", __func__, root_path); /* Make a copy of the path to URL-decode */ - char *path_alloc = kalloc(MAXPATHLEN); - if (path_alloc == NULL) { - panic("imageboot path allocation failed\n"); - } - char *path = path_alloc; - - size_t len = strlen(kIBFilePrefix); - strlcpy(path, root_path, MAXPATHLEN); - if (strncmp(kIBFilePrefix, path, len) == 0) { - /* its a URL - remove the file:// prefix and percent-decode */ - path += len; - url_decode(path); - } + 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(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); + error = imageboot_mount_image(root_path, height, type); } - kfree_safe(path_alloc); + 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 = auth_version_check(); + error = authenticate_root_version_check(); if (error) { panic("root image version check failed"); } else { AUTHDBG("root image version matches kernel"); } } +#endif done = TRUE; out: - FREE_ZONE(root_path, MAXPATHLEN, M_NAMEI); + zfree(ZV_NAMEI, root_path); return done; } __private_extern__ void -imageboot_setup() +imageboot_setup(imageboot_type_t type) { int error = 0; char *root_path = NULL; @@ -1041,15 +1067,17 @@ imageboot_setup() /* * New boot-arg scheme: - * root-dmg : the dmg that will be the root filesystem. - * auth-root-dmg : same as root-dmg but with image authentication. + * 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()) { + if (imageboot_setup_new(type)) { return; } - MALLOC_ZONE(root_path, caddr_t, MAXPATHLEN, M_NAMEI, M_WAITOK); + root_path = zalloc(ZV_NAMEI); assert(root_path != NULL); /* @@ -1059,14 +1087,29 @@ imageboot_setup() * 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__); } - printf("%s: root image url is %s\n", __FUNCTION__, root_path); + DBG_TRACE("%s: root image url is %s\n", __FUNCTION__, root_path); - error = imageboot_mount_image(root_path, 0); + error = imageboot_mount_image(root_path, 0, type); if (error) { panic("Failed on first stage of imageboot."); } @@ -1084,13 +1127,13 @@ imageboot_setup() * 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); + 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__);