+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, CAST_USER_ADDR_T(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";