]> git.saurik.com Git - apple/xnu.git/blobdiff - bsd/kern/imageboot.c
xnu-4903.270.47.tar.gz
[apple/xnu.git] / bsd / kern / imageboot.c
index 6ba54a69fa4da9c8549d0555249f69bda4c1c0e8..96c0a1e734b57c462f8734663f2772ac651791e6 100644 (file)
@@ -2,7 +2,7 @@
  * Copyright (c) 2006 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
  * 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@
  */
 
@@ -42,6 +42,7 @@
 #include <sys/vnode.h>
 #include <sys/sysproto.h>
 #include <sys/csr.h>
+#include <miscfs/devfs/devfsdefs.h>
 #include <libkern/crypto/sha2.h>
 #include <libkern/crypto/rsa.h>
 #include <libkern/OSKextLibPrivate.h>
@@ -54,7 +55,7 @@
 extern struct filedesc filedesc0;
 
 extern int (*mountroot)(void);
-extern char rootdevice[];
+extern char rootdevice[DEVMAXNAMESIZE];
 
 #define DEBUG_IMAGEBOOT 0
 
@@ -64,7 +65,9 @@ extern char rootdevice[];
 #define DBG_TRACE(...) do {} while(0)
 #endif
 
-extern int di_root_image(const char *path, char devname[], dev_t *dev_p);
+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);
 
 #define kIBFilePrefix "file://"
@@ -72,12 +75,12 @@ static boolean_t imageboot_setup_new(void);
 __private_extern__ int
 imageboot_format_is_valid(const char *root_path)
 {
-       return (strncmp(root_path, kIBFilePrefix,
-                               strlen(kIBFilePrefix)) == 0);
+       return strncmp(root_path, kIBFilePrefix,
+                  strlen(kIBFilePrefix)) == 0;
 }
 
 static void
-vnode_get_and_drop_always(vnode_t vp) 
+vnode_get_and_drop_always(vnode_t vp)
 {
        vnode_getalways(vp);
        vnode_rele(vp);
@@ -89,18 +92,19 @@ imageboot_needed(void)
 {
        int result = 0;
        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)
+       if (root_path == NULL) {
                panic("%s: M_NAMEI zone exhausted", __FUNCTION__);
+       }
 
        /* Check for first layer */
        if (!(PE_parse_boot_argn("rp0", root_path, MAXPATHLEN) ||
-                       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))) {
+           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;
        }
 
@@ -115,7 +119,7 @@ imageboot_needed(void)
 
        /* Check for second layer */
        if (!(PE_parse_boot_argn("rp1", root_path, MAXPATHLEN) ||
-                       PE_parse_boot_argn(IMAGEBOOT_CONTAINER_ARG, root_path, MAXPATHLEN))) {
+           PE_parse_boot_argn(IMAGEBOOT_CONTAINER_ARG, root_path, MAXPATHLEN))) {
                goto out;
        }
 
@@ -124,33 +128,33 @@ imageboot_needed(void)
                DBG_TRACE("%s: Found %s\n", __FUNCTION__, root_path);
        } else {
                panic("%s: Invalid URL scheme for %s\n",
-                               __FUNCTION__, root_path);
+                   __FUNCTION__, root_path);
        }
 
 out:
        FREE_ZONE(root_path, MAXPATHLEN, M_NAMEI);
 
-       return (result);
+       return result;
 }
 
 
 /*
  * 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 
+ * 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).
  */
 __private_extern__ int
 imageboot_mount_image(const char *root_path, int height)
 {
-       dev_t           dev;
-       int             error;
-       vnode_t         old_rootvnode = NULL;
-       vnode_t         newdp;
-       mount_t         new_rootfs;
+       dev_t           dev;
+       int             error;
+       vnode_t         old_rootvnode = NULL;
+       vnode_t         newdp;
+       mount_t         new_rootfs;
 
-       error = di_root_image(root_path, rootdevice, &dev);
+       error = di_root_image(root_path, rootdevice, DEVMAXNAMESIZE, &dev);
        if (error) {
                panic("%s: di_root_image failed: %d\n", __FUNCTION__, error);
        }
@@ -167,18 +171,19 @@ imageboot_mount_image(const char *root_path, int height)
         * Get the vnode for '/'.
         * Set fdp->fd_fd.fd_cdir to reference it.
         */
-       if (VFS_ROOT(TAILQ_LAST(&mountlist,mntlist), &newdp, vfs_context_kernel()))
+       if (VFS_ROOT(TAILQ_LAST(&mountlist, mntlist), &newdp, vfs_context_kernel())) {
                panic("%s: cannot find root vnode", __FUNCTION__);
+       }
 
        if (rootvnode != NULL) {
                /* remember the old rootvnode, but remove it from mountlist */
-               mount_t         old_rootfs;
+               mount_t         old_rootfs;
 
                old_rootvnode = rootvnode;
                old_rootfs = rootvnode->v_mount;
-       
+
                mount_list_remove(old_rootfs);
-       
+
                mount_lock(old_rootfs);
 #ifdef CONFIG_IMGSRC_ACCESS
                old_rootfs->mnt_kern_flag |= MNTK_BACKS_ROOT;
@@ -202,14 +207,14 @@ imageboot_mount_image(const char *root_path, int height)
 
        if (old_rootvnode != NULL) {
 #ifdef CONFIG_IMGSRC_ACCESS
-           if (height >= 0 && PE_imgsrc_mount_supported()) {
-               imgsrc_rootvnodes[height] = old_rootvnode;
-           } else {
+               if (height >= 0 && PE_imgsrc_mount_supported()) {
+                       imgsrc_rootvnodes[height] = old_rootvnode;
+               } else {
+                       vnode_get_and_drop_always(old_rootvnode);
+               }
+#else
+               height = 0; /* keep the compiler from complaining */
                vnode_get_and_drop_always(old_rootvnode);
-           }
-#else 
-           height = 0; /* keep the compiler from complaining */
-           vnode_get_and_drop_always(old_rootvnode);
 #endif /* CONFIG_IMGSRC_ACCESS */
        }
        return 0;
@@ -240,7 +245,7 @@ key_byteswap(void *_dst, const void *_src, size_t len)
 
        len = len / sizeof(uint32_t);
        for (size_t i = 0; i < len; i++) {
-               dst[len-i-1] = OSSwapInt32(src[i]);
+               dst[len - i - 1] = OSSwapInt32(src[i]);
        }
 }
 
@@ -259,7 +264,7 @@ read_file(const char *path, void **bufp, size_t *bufszp)
        proc_t p = vfs_context_proc(ctx);
        kauth_cred_t kerncred = vfs_context_ucred(ctx);
 
-       NDINIT(&ndp, LOOKUP, OP_OPEN, LOCKLEAF, UIO_SYSSPACE, 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)", path);
                goto out;
@@ -345,17 +350,17 @@ validate_signature(const uint8_t *key_msb, size_t keylen, uint8_t *sig_msb, size
        key_byteswap(sig, sig_msb, siglen);
 
        err = rsa_make_pub(rsa_ctx,
-                       sizeof(exponent), exponent,
-                       CHUNKLIST_PUBKEY_LEN, modulus);
+           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);
+           SHA256_DIGEST_LENGTH, digest,
+           siglen, sig,
+           &sig_valid);
        if (err) {
                sig_valid = false;
                AUTHPRNT("rsa_verify() failed");
@@ -394,26 +399,26 @@ validate_chunklist(void *buf, size_t len)
 
        /* 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) {
+           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) {
+           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);
+                   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) {
+           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;
        }
@@ -436,7 +441,7 @@ validate_chunklist(void *buf, size_t len)
        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);
+                   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;
@@ -493,7 +498,7 @@ validate_root_image(const char *root_path, void *chunklist)
        /*
         * Open the DMG
         */
-       NDINIT(&ndp, LOOKUP, OP_OPEN, LOCKLEAF, UIO_SYSSPACE, root_path, ctx);
+       NDINIT(&ndp, LOOKUP, OP_OPEN, LOCKLEAF, UIO_SYSSPACE, CAST_USER_ADDR_T(root_path), ctx);
        if ((err = namei(&ndp)) != 0) {
                AUTHPRNT("namei failed (%s)", root_path);
                goto out;
@@ -643,7 +648,7 @@ authenticate_root(const char *root_path)
 {
        char *chunklist_path = NULL;
        void *chunklist_buf = NULL;
-       size_t chunklist_len = 32*1024*1024UL;
+       size_t chunklist_len = 32 * 1024 * 1024UL;
        int err = 0;
 
        err = construct_chunklist_path(root_path, &chunklist_path);
@@ -688,7 +693,7 @@ authenticate_root(const char *root_path)
        /* everything checked out - go ahead and mount this */
        AUTHDBG("root image authenticated");
 
- out:
+out:
        kfree_safe(chunklist_buf);
        kfree_safe(chunklist_path);
        return err;
@@ -723,7 +728,7 @@ getuuidfromheader_safe(const void *buf, size_t bufsz, size_t *uuidsz)
                }
 
                if (os_add_overflow(cmd->cmdsize, offset, &offset) ||
-                               offset > bufsz - sizeof(struct uuid_command)) {
+                   offset > bufsz - sizeof(struct uuid_command)) {
                        return NULL;
                }
        }
@@ -742,7 +747,7 @@ auth_version_check(void)
 {
        int err = 0;
        void *buf = NULL;
-       size_t bufsz = 4*1024*1024UL;
+       size_t bufsz = 4 * 1024 * 1024UL;
 
        /* get the UUID of the libkern in /S/L/E */
 
@@ -831,6 +836,96 @@ auth_imgboot_test(proc_t __unused ap, struct auth_imgboot_test_args *uap, int32_
 }
 #endif
 
+/*
+ * 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;
+}
+
 static boolean_t
 imageboot_setup_new()
 {
@@ -839,10 +934,16 @@ imageboot_setup_new()
        int height = 0;
        boolean_t done = FALSE;
        boolean_t auth_root = FALSE;
+       boolean_t ramdisk_root = FALSE;
 
        MALLOC_ZONE(root_path, caddr_t, MAXPATHLEN, M_NAMEI, M_WAITOK);
        assert(root_path != NULL);
 
+       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);
@@ -871,36 +972,41 @@ imageboot_setup_new()
        }
 #endif
 
-       if (auth_root) {
-               /* Copy the path to use locally */
-               char *path_alloc = kalloc(MAXPATHLEN);
-               if (path_alloc == NULL) {
-                       panic("imageboot path allocation failed\n");
-               }
-
-               char *path = path_alloc;
-               strlcpy(path, root_path, MAXPATHLEN);
+       /* 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);
-               if (strncmp(kIBFilePrefix, path, len) == 0) {
-                       /* its a URL - remove the file:// prefix and percent-decode */
-                       path += len;
-                       url_decode(path);
-               }
+       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);
+       }
 
+       if (auth_root) {
                AUTHDBG("authenticating root image at %s", path);
                error = authenticate_root(path);
                if (error) {
                        panic("root image authentication failed (err = %d)\n", error);
                }
                AUTHDBG("successfully authenticated %s", path);
+       }
 
-               kfree_safe(path_alloc);
+       if (ramdisk_root) {
+               error = imageboot_mount_ramdisk(path);
+       } else {
+               error = imageboot_mount_image(root_path, height);
        }
 
-       error = imageboot_mount_image(root_path, height);
-       if (error != 0) {
-               panic("Failed to mount root image.");
+       kfree_safe(path_alloc);
+
+       if (error) {
+               panic("Failed to mount root image (err=%d, auth=%d, ramdisk=%d)\n",
+                   error, auth_root, ramdisk_root);
        }
 
        if (auth_root) {
@@ -935,9 +1041,9 @@ 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.
-        *      container-dmg : an optional dmg that contains the root-dmg.
+        *      root-dmg : the dmg that will be the root filesystem.
+        *      auth-root-dmg : same as root-dmg but with image authentication.
+        *      container-dmg : an optional dmg that contains the root-dmg.
         */
        if (imageboot_setup_new()) {
                return;
@@ -950,16 +1056,16 @@ imageboot_setup()
         * 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 
+        * 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((PE_parse_boot_argn("rp", root_path, MAXPATHLEN) == FALSE) &&
-               (PE_parse_boot_argn("rp0", root_path, MAXPATHLEN) == FALSE)) {
+       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);
-       
+
        error = imageboot_mount_image(root_path, 0);
        if (error) {
                panic("Failed on first stage of imageboot.");
@@ -968,19 +1074,19 @@ imageboot_setup()
        /*
         * See if we are rooting from a nested image
         */
-       if(PE_parse_boot_argn("rp1", root_path, MAXPATHLEN) == FALSE) {
+       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.  
+        * can safely root off the first.
         */
        error = imageboot_mount_image(root_path, 1);
        if (error) {
-               panic("Failed on second stage of imageboot.");  
+               panic("Failed on second stage of imageboot.");
        }
 
 done: