]> git.saurik.com Git - apple/xnu.git/blobdiff - bsd/kern/imageboot.c
xnu-3789.70.16.tar.gz
[apple/xnu.git] / bsd / kern / imageboot.c
index 9fa89e44315a16552add539388182f767683de18..6ba54a69fa4da9c8549d0555249f69bda4c1c0e8 100644 (file)
 #include <sys/imageboot.h>
 #include <kern/assert.h>
 
+#include <sys/namei.h>
+#include <sys/fcntl.h>
+#include <sys/vnode.h>
+#include <sys/sysproto.h>
+#include <sys/csr.h>
+#include <libkern/crypto/sha2.h>
+#include <libkern/crypto/rsa.h>
+#include <libkern/OSKextLibPrivate.h>
+
+#include <kern/kalloc.h>
+
 #include <pexpert/pexpert.h>
+#include <kern/chunklist.h>
 
 extern struct filedesc filedesc0;
 
@@ -85,12 +97,13 @@ imageboot_needed(void)
                panic("%s: M_NAMEI zone exhausted", __FUNCTION__);
 
        /* Check for first layer */
-       if (!(PE_parse_boot_argn("rp0", root_path, MAXPATHLEN) || 
+       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_ROOT_ARG, root_path, MAXPATHLEN) ||
+                       PE_parse_boot_argn(IMAGEBOOT_AUTHROOT_ARG, root_path, MAXPATHLEN))) {
                goto out;
        }
-       
+
        /* Sanity-check first layer */
        if (imageboot_format_is_valid(root_path)) {
                DBG_TRACE("%s: Found %s\n", __FUNCTION__, root_path);
@@ -202,18 +215,635 @@ imageboot_mount_image(const char *root_path, int height)
        return 0;
 }
 
-static boolean_t 
+
+/*
+ * Authenticated root-dmg support
+ */
+
+#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 kfree_safe(x) do { if ((x)) { kfree_addr((x)); (x) = NULL; } } while (0)
+
+enum {
+       MISSING_SIG = -1,
+       INVALID_SIG = -2
+};
+
+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;
+
+       assert(len % sizeof(uint32_t) == 0);
+
+       len = len / sizeof(uint32_t);
+       for (size_t i = 0; i < len; i++) {
+               dst[len-i-1] = OSSwapInt32(src[i]);
+       }
+}
+
+static int
+read_file(const char *path, void **bufp, size_t *bufszp)
+{
+       int err = 0;
+       struct nameidata ndp = {};
+       struct vnode *vp = NULL;
+       off_t fsize = 0;
+       int resid = 0;
+       char *buf = NULL;
+       bool doclose = false;
+
+       vfs_context_t ctx = vfs_context_kernel();
+       proc_t p = vfs_context_proc(ctx);
+       kauth_cred_t kerncred = vfs_context_ucred(ctx);
+
+       NDINIT(&ndp, LOOKUP, OP_OPEN, LOCKLEAF, UIO_SYSSPACE, path, ctx);
+       if ((err = namei(&ndp)) != 0) {
+               AUTHPRNT("namei failed (%s)", path);
+               goto out;
+       }
+       nameidone(&ndp);
+       vp = ndp.ni_vp;
+
+       if ((err = vnode_size(vp, &fsize, ctx)) != 0) {
+               AUTHPRNT("failed to get vnode size");
+               goto out;
+       }
+       if (fsize < 0) {
+               panic("negative file size");
+       }
+
+       if ((err = VNOP_OPEN(vp, FREAD, ctx)) != 0) {
+               AUTHPRNT("failed to open vnode");
+               goto out;
+       }
+       doclose = true;
+
+       /* if bufsz is non-zero, cap the read at bufsz bytes */
+       if (*bufszp && *bufszp < (size_t)fsize) {
+               fsize = *bufszp;
+       }
+
+       buf = kalloc(fsize);
+       if (buf == NULL) {
+               err = ENOMEM;
+               goto out;
+       }
+
+       if ((err = vn_rdwr(UIO_READ, vp, (caddr_t)buf, fsize, 0, UIO_SYSSPACE, IO_NODELOCKED, kerncred, &resid, p)) != 0) {
+               AUTHPRNT("vn_rdwr() failed");
+               goto out;
+       }
+
+       if (resid) {
+               /* didnt get everything we wanted */
+               AUTHPRNT("vn_rdwr resid = %d", resid);
+               err = EINVAL;
+               goto out;
+       }
+
+out:
+       if (doclose) {
+               VNOP_CLOSE(vp, FREAD, ctx);
+       }
+       if (vp) {
+               vnode_put(vp);
+               vp = NULL;
+       }
+
+       if (err) {
+               kfree_safe(buf);
+       } else {
+               *bufp = buf;
+               *bufszp = fsize;
+       }
+
+       return err;
+}
+
+static int
+validate_signature(const uint8_t *key_msb, size_t keylen, uint8_t *sig_msb, size_t siglen, uint8_t *digest)
+{
+       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;
+       }
+}
+
+static int
+validate_chunklist(void *buf, size_t len)
+{
+       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;
+
+       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);
+
+       /*
+        * Open the DMG
+        */
+       NDINIT(&ndp, LOOKUP, OP_OPEN, LOCKLEAF, UIO_SYSSPACE, root_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;
+               }
+
+               if (os_add_overflow(offset, chk->chunk_size, &offset)) {
+                       err = EINVAL;
+                       goto out;
+               }
+               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;
+       }
+       return err;
+}
+
+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");
+       }
+
+       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;
+       }
+
+       /* 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)
+{
+       const struct uuid_command *cmd = NULL;
+       const kernel_mach_header_t *mh = buf;
+
+       /* 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;
+       }
+
+       /* 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;
+               }
+       }
+
+       return NULL;
+}
+
+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)
+{
+       int err = 0;
+       void *buf = NULL;
+       size_t bufsz = 4*1024*1024UL;
+
+       /* get the UUID of the libkern in /S/L/E */
+
+       err = read_file(libkern_path, &buf, &bufsz);
+       if (err) {
+               goto out;
+       }
+
+       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;
+       }
+
+       /* 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;
+       }
+
+       /* ... and compare them */
+       if (bcmp(live_uuid, img_uuid, uuidsz) != 0) {
+               AUTHPRNT("UUID of running libkern does not match %s", libkern_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;
+       }
+
+       /* 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;
+       }
+
+       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);
+       }
+
+       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);
+       }
+
+       if (ret < 0) {
+               return EINVAL; /* negative return values have special meaning */
+       }
+       return ret;
+}
+#endif
+
+static boolean_t
 imageboot_setup_new()
 {
        int error;
        char *root_path = NULL;
        int height = 0;
        boolean_t done = FALSE;
+       boolean_t auth_root = FALSE;
 
        MALLOC_ZONE(root_path, caddr_t, MAXPATHLEN, M_NAMEI, M_WAITOK);
        assert(root_path != NULL);
 
-       if(PE_parse_boot_argn(IMAGEBOOT_CONTAINER_ARG, root_path, MAXPATHLEN) == 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);
                if (error != 0) {
@@ -223,21 +853,67 @@ imageboot_setup_new()
                height++;
        }
 
-       if (PE_parse_boot_argn(IMAGEBOOT_ROOT_ARG, root_path, MAXPATHLEN) == FALSE) {
+       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 (height > 0) {
                        panic("%s specified without %s?\n", IMAGEBOOT_CONTAINER_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", __FUNCTION__, root_path);
+       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);
+
+               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);
+               }
+
+               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);
+       }
 
        error = imageboot_mount_image(root_path, height);
        if (error != 0) {
                panic("Failed to mount root image.");
        }
 
+       if (auth_root) {
+               /* check that the image version matches the running kernel */
+               AUTHDBG("checking root image version");
+               error = auth_version_check();
+               if (error) {
+                       panic("root image version check failed");
+               } else {
+                       AUTHDBG("root image version matches kernel");
+               }
+       }
+
        done = TRUE;
 
 out:
@@ -253,19 +929,20 @@ imageboot_setup()
 
        DBG_TRACE("%s: entry\n", __FUNCTION__);
 
-       if (rootvnode == NULL) {        
+       if (rootvnode == NULL) {
                panic("imageboot_setup: rootvnode is NULL.");
        }
 
        /*
         * 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.
         */
        if (imageboot_setup_new()) {
                return;
        }
-       
+
        MALLOC_ZONE(root_path, caddr_t, MAXPATHLEN, M_NAMEI, M_WAITOK);
        assert(root_path != NULL);