+
+/*
+ * Some codesigned files use a lowest common denominator page size of
+ * 4KiB, but can be used on systems that have a runtime page size of
+ * 16KiB. Since faults will only occur on 16KiB ranges in
+ * cs_validate_range(), we can convert the original Code Directory to
+ * a multi-level scheme where groups of 4 hashes are combined to form
+ * a new hash, which represents 16KiB in the on-disk file. This can
+ * reduce the wired memory requirement for the Code Directory by
+ * 75%. Care must be taken for binaries that use the "fourk" VM pager
+ * for unaligned access, which may still attempt to validate on
+ * non-16KiB multiples for compatibility with 3rd party binaries.
+ */
+static boolean_t
+ubc_cs_supports_multilevel_hash(struct cs_blob *blob)
+{
+ const CS_CodeDirectory *cd;
+
+ /*
+ * Only applies to binaries that ship as part of the OS,
+ * primarily the shared cache.
+ */
+ if (!blob->csb_platform_binary || blob->csb_teamid != NULL) {
+ return FALSE;
+ }
+
+ /*
+ * If the runtime page size matches the code signing page
+ * size, there is no work to do.
+ */
+ if (PAGE_SHIFT <= blob->csb_hash_pageshift) {
+ return FALSE;
+ }
+
+ cd = blob->csb_cd;
+
+ /*
+ * There must be a valid integral multiple of hashes
+ */
+ if (ntohl(cd->nCodeSlots) & (PAGE_MASK >> blob->csb_hash_pageshift)) {
+ return FALSE;
+ }
+
+ /*
+ * Scatter lists must also have ranges that have an integral number of hashes
+ */
+ if ((ntohl(cd->version) >= CS_SUPPORTSSCATTER) && (ntohl(cd->scatterOffset))) {
+
+ const SC_Scatter *scatter = (const SC_Scatter*)
+ ((const char*)cd + ntohl(cd->scatterOffset));
+ /* iterate all scatter structs to make sure they are all aligned */
+ do {
+ uint32_t sbase = ntohl(scatter->base);
+ uint32_t scount = ntohl(scatter->count);
+
+ /* last scatter? */
+ if (scount == 0) {
+ break;
+ }
+
+ if (sbase & (PAGE_MASK >> blob->csb_hash_pageshift)) {
+ return FALSE;
+ }
+
+ if (scount & (PAGE_MASK >> blob->csb_hash_pageshift)) {
+ return FALSE;
+ }
+
+ scatter++;
+ } while(1);
+ }
+
+ /* Covered range must be a multiple of the new page size */
+ if (ntohl(cd->codeLimit) & PAGE_MASK) {
+ return FALSE;
+ }
+
+ /* All checks pass */
+ return TRUE;
+}
+
+/*
+ * All state and preconditions were checked before, so this
+ * function cannot fail.
+ */
+static void
+ubc_cs_convert_to_multilevel_hash(struct cs_blob *blob)
+{
+ const CS_CodeDirectory *old_cd, *cd;
+ CS_CodeDirectory *new_cd;
+ const CS_GenericBlob *entitlements;
+ vm_offset_t new_blob_addr;
+ vm_size_t new_blob_size;
+ vm_size_t new_cdsize;
+ kern_return_t kr;
+ int error;
+ size_t length;
+
+ uint32_t hashes_per_new_hash_shift = (uint32_t)(PAGE_SHIFT - blob->csb_hash_pageshift);
+
+ if (cs_debug > 1) {
+ printf("CODE SIGNING: Attempting to convert Code Directory for %lu -> %lu page shift\n",
+ (unsigned long)blob->csb_hash_pageshift, (unsigned long)PAGE_SHIFT);
+ }
+
+ old_cd = blob->csb_cd;
+
+ /* Up to the hashes, we can copy all data */
+ new_cdsize = ntohl(old_cd->hashOffset);
+ new_cdsize += (ntohl(old_cd->nCodeSlots) >> hashes_per_new_hash_shift) * old_cd->hashSize;
+
+ new_blob_size = sizeof(CS_SuperBlob);
+ new_blob_size += sizeof(CS_BlobIndex);
+ new_blob_size += new_cdsize;
+
+ if (blob->csb_entitlements_blob) {
+ /* We need to add a slot for the entitlements */
+ new_blob_size += sizeof(CS_BlobIndex);
+ new_blob_size += ntohl(blob->csb_entitlements_blob->length);
+ }
+
+ kr = ubc_cs_blob_allocate(&new_blob_addr, &new_blob_size);
+ if (kr != KERN_SUCCESS) {
+ if (cs_debug > 1) {
+ printf("CODE SIGNING: Failed to allocate memory for new Code Signing Blob: %d\n",
+ kr);
+ }
+ return;
+ }
+
+ CS_SuperBlob *new_superblob;
+
+ new_superblob = (CS_SuperBlob *)new_blob_addr;
+ new_superblob->magic = htonl(CSMAGIC_EMBEDDED_SIGNATURE);
+ new_superblob->length = htonl((uint32_t)new_blob_size);
+ if (blob->csb_entitlements_blob) {
+ vm_size_t ent_offset, cd_offset;
+
+ cd_offset = sizeof(CS_SuperBlob) + 2 * sizeof(CS_BlobIndex);
+ ent_offset = cd_offset + new_cdsize;
+
+ new_superblob->count = htonl(2);
+ new_superblob->index[0].type = htonl(CSSLOT_CODEDIRECTORY);
+ new_superblob->index[0].offset = htonl((uint32_t)cd_offset);
+ new_superblob->index[1].type = htonl(CSSLOT_ENTITLEMENTS);
+ new_superblob->index[1].offset = htonl((uint32_t)ent_offset);
+
+ memcpy((void *)(new_blob_addr + ent_offset), blob->csb_entitlements_blob, ntohl(blob->csb_entitlements_blob->length));
+
+ new_cd = (CS_CodeDirectory *)(new_blob_addr + cd_offset);
+ } else {
+ vm_size_t cd_offset;
+
+ cd_offset = sizeof(CS_SuperBlob) + 1 * sizeof(CS_BlobIndex);
+
+ new_superblob->count = htonl(1);
+ new_superblob->index[0].type = htonl(CSSLOT_CODEDIRECTORY);
+ new_superblob->index[0].offset = htonl((uint32_t)cd_offset);
+
+ new_cd = (CS_CodeDirectory *)new_blob_addr;
+ }
+
+ memcpy(new_cd, old_cd, ntohl(old_cd->hashOffset));
+
+ /* Update fields in the Code Directory structure */
+ new_cd->length = htonl((uint32_t)new_cdsize);
+
+ uint32_t nCodeSlots = ntohl(new_cd->nCodeSlots);
+ nCodeSlots >>= hashes_per_new_hash_shift;
+ new_cd->nCodeSlots = htonl(nCodeSlots);
+
+ new_cd->pageSize = PAGE_SHIFT; /* Not byte-swapped */
+
+ if ((ntohl(new_cd->version) >= CS_SUPPORTSSCATTER) && (ntohl(new_cd->scatterOffset))) {
+ SC_Scatter *scatter = (SC_Scatter*)
+ ((char *)new_cd + ntohl(new_cd->scatterOffset));
+ /* iterate all scatter structs to scale their counts */
+ do {
+ uint32_t scount = ntohl(scatter->count);
+ uint32_t sbase = ntohl(scatter->base);
+
+ /* last scatter? */
+ if (scount == 0) {
+ break;
+ }
+
+ scount >>= hashes_per_new_hash_shift;
+ scatter->count = htonl(scount);
+
+ sbase >>= hashes_per_new_hash_shift;
+ scatter->base = htonl(sbase);
+
+ scatter++;
+ } while(1);
+ }
+
+ /* For each group of hashes, hash them together */
+ const unsigned char *src_base = (const unsigned char *)old_cd + ntohl(old_cd->hashOffset);
+ unsigned char *dst_base = (unsigned char *)new_cd + ntohl(new_cd->hashOffset);
+
+ uint32_t hash_index;
+ for (hash_index = 0; hash_index < nCodeSlots; hash_index++) {
+ union cs_hash_union mdctx;
+
+ uint32_t source_hash_len = old_cd->hashSize << hashes_per_new_hash_shift;
+ const unsigned char *src = src_base + hash_index * source_hash_len;
+ unsigned char *dst = dst_base + hash_index * new_cd->hashSize;
+
+ blob->csb_hashtype->cs_init(&mdctx);
+ blob->csb_hashtype->cs_update(&mdctx, src, source_hash_len);
+ blob->csb_hashtype->cs_final(dst, &mdctx);
+ }
+
+ length = new_blob_size;
+ error = cs_validate_csblob((const uint8_t *)new_blob_addr, &length, &cd, &entitlements);
+ assert(length == new_blob_size);
+ if (error) {
+
+ if (cs_debug > 1) {
+ printf("CODE SIGNING: Failed to validate new Code Signing Blob: %d\n",
+ error);
+ }
+
+ ubc_cs_blob_deallocate(new_blob_addr, new_blob_size);
+ return;
+ }
+
+ /* New Code Directory is ready for use, swap it out in the blob structure */
+ ubc_cs_blob_deallocate(blob->csb_mem_kaddr, blob->csb_mem_size);
+
+ blob->csb_mem_size = new_blob_size;
+ blob->csb_mem_kaddr = new_blob_addr;
+ blob->csb_cd = cd;
+ blob->csb_entitlements_blob = entitlements;
+
+ /* The blob has some cached attributes of the Code Directory, so update those */
+
+ blob->csb_hash_firstlevel_pagesize = blob->csb_hash_pagesize; /* Save the original page size */
+
+ blob->csb_hash_pagesize = PAGE_SIZE;
+ blob->csb_hash_pagemask = PAGE_MASK;
+ blob->csb_hash_pageshift = PAGE_SHIFT;
+ blob->csb_end_offset = ntohl(cd->codeLimit);
+ if((ntohl(cd->version) >= CS_SUPPORTSSCATTER) && (ntohl(cd->scatterOffset))) {
+ const SC_Scatter *scatter = (const SC_Scatter*)
+ ((const char*)cd + ntohl(cd->scatterOffset));
+ blob->csb_start_offset = ((off_t)ntohl(scatter->base)) * PAGE_SIZE;
+ } else {
+ blob->csb_start_offset = 0;
+ }
+}
+
+int
+ubc_cs_blob_add(
+ struct vnode *vp,
+ cpu_type_t cputype,
+ off_t base_offset,
+ vm_address_t *addr,
+ vm_size_t size,
+ struct image_params *imgp,
+ __unused int flags,
+ struct cs_blob **ret_blob)
+{
+ kern_return_t kr;
+ struct ubc_info *uip;
+ struct cs_blob *blob, *oblob;
+ int error;
+ const CS_CodeDirectory *cd;
+ const CS_GenericBlob *entitlements;
+ off_t blob_start_offset, blob_end_offset;
+ union cs_hash_union mdctx;
+ boolean_t record_mtime;
+ size_t length;
+
+ record_mtime = FALSE;
+ if (ret_blob)
+ *ret_blob = NULL;
+
+ blob = (struct cs_blob *) kalloc(sizeof (struct cs_blob));
+ if (blob == NULL) {
+ return ENOMEM;
+ }
+
+ /* fill in the new blob */
+ blob->csb_cpu_type = cputype;
+ blob->csb_base_offset = base_offset;
+ blob->csb_mem_size = size;
+ blob->csb_mem_offset = 0;
+ blob->csb_mem_kaddr = *addr;
+ blob->csb_flags = 0;
+ blob->csb_platform_binary = 0;
+ blob->csb_platform_path = 0;
+ blob->csb_teamid = NULL;
+ blob->csb_entitlements_blob = NULL;
+ blob->csb_entitlements = NULL;
+
+ /* Transfer ownership. Even on error, this function will deallocate */
+ *addr = 0;
+
+ /*
+ * Validate the blob's contents
+ */
+ length = (size_t) size;
+ error = cs_validate_csblob((const uint8_t *)blob->csb_mem_kaddr,
+ &length, &cd, &entitlements);
+ if (error) {
+
+ if (cs_debug)
+ printf("CODESIGNING: csblob invalid: %d\n", error);
+ /*
+ * The vnode checker can't make the rest of this function
+ * succeed if csblob validation failed, so bail */
+ goto out;
+
+ } else {
+ const unsigned char *md_base;
+ uint8_t hash[CS_HASH_MAX_SIZE];
+ int md_size;
+
+ size = (vm_size_t) length;
+ assert(size <= blob->csb_mem_size);
+ if (size < blob->csb_mem_size) {
+ vm_address_t new_blob_addr;
+ const CS_CodeDirectory *new_cd;
+ const CS_GenericBlob *new_entitlements;
+
+ kr = ubc_cs_blob_allocate(&new_blob_addr, &size);
+ if (kr != KERN_SUCCESS) {
+ if (cs_debug > 1) {
+ printf("CODE SIGNING: failed to "
+ "re-allocate blob (size "
+ "0x%llx->0x%llx) error 0x%x\n",
+ (uint64_t)blob->csb_mem_size,
+ (uint64_t)size,
+ kr);
+ }
+ } else {
+ memcpy(new_blob_addr, blob->csb_mem_kaddr, size);
+ if (cd == NULL) {
+ new_cd = NULL;
+ } else {
+ new_cd = ((uintptr_t)cd
+ - (uintptr_t)blob->csb_mem_kaddr
+ + (uintptr_t)new_blob_addr);
+ }
+ if (entitlements == NULL) {
+ new_entitlements = NULL;
+ } else {
+ new_entitlements = ((uintptr_t)entitlements
+ - (uintptr_t)blob->csb_mem_kaddr
+ + (uintptr_t)new_blob_addr);
+ }
+// printf("CODE SIGNING: %s:%d kaddr 0x%llx cd %p ents %p -> blob 0x%llx cd %p ents %p\n", __FUNCTION__, __LINE__, (uint64_t)blob->csb_mem_kaddr, cd, entitlements, (uint64_t)new_blob_addr, new_cd, new_entitlements);
+ ubc_cs_blob_deallocate(blob->csb_mem_kaddr,
+ blob->csb_mem_size);
+ blob->csb_mem_kaddr = new_blob_addr;
+ blob->csb_mem_size = size;
+ cd = new_cd;
+ entitlements = new_entitlements;
+ }
+ }
+
+ blob->csb_cd = cd;
+ blob->csb_entitlements_blob = entitlements; /* may be NULL, not yet validated */
+ blob->csb_hashtype = cs_find_md(cd->hashType);
+ if (blob->csb_hashtype == NULL || blob->csb_hashtype->cs_digest_size > sizeof(hash))
+ panic("validated CodeDirectory but unsupported type");
+
+ blob->csb_hash_pageshift = cd->pageSize;
+ blob->csb_hash_pagesize = (1U << cd->pageSize);
+ blob->csb_hash_pagemask = blob->csb_hash_pagesize - 1;
+ blob->csb_hash_firstlevel_pagesize = 0;
+ blob->csb_flags = (ntohl(cd->flags) & CS_ALLOWED_MACHO) | CS_VALID;
+ blob->csb_end_offset = (((vm_offset_t)ntohl(cd->codeLimit) + blob->csb_hash_pagemask) & ~((vm_offset_t)blob->csb_hash_pagemask));
+ if((ntohl(cd->version) >= CS_SUPPORTSSCATTER) && (ntohl(cd->scatterOffset))) {
+ const SC_Scatter *scatter = (const SC_Scatter*)
+ ((const char*)cd + ntohl(cd->scatterOffset));
+ blob->csb_start_offset = ((off_t)ntohl(scatter->base)) * blob->csb_hash_pagesize;
+ } else {
+ blob->csb_start_offset = 0;
+ }
+ /* compute the blob's cdhash */
+ md_base = (const unsigned char *) cd;
+ md_size = ntohl(cd->length);
+
+ blob->csb_hashtype->cs_init(&mdctx);
+ blob->csb_hashtype->cs_update(&mdctx, md_base, md_size);
+ blob->csb_hashtype->cs_final(hash, &mdctx);
+
+ memcpy(blob->csb_cdhash, hash, CS_CDHASH_LEN);
+ }
+
+ /*
+ * Let policy module check whether the blob's signature is accepted.
+ */
+#if CONFIG_MACF
+ unsigned int cs_flags = blob->csb_flags;
+ error = mac_vnode_check_signature(vp, blob, imgp, &cs_flags, flags);
+ blob->csb_flags = cs_flags;
+
+ if (error) {
+ if (cs_debug)
+ printf("check_signature[pid: %d], error = %d\n", current_proc()->p_pid, error);
+ goto out;
+ }
+ if ((flags & MAC_VNODE_CHECK_DYLD_SIM) && !(blob->csb_flags & CS_PLATFORM_BINARY)) {
+ if (cs_debug)
+ printf("check_signature[pid: %d], is not apple signed\n", current_proc()->p_pid);
+ error = EPERM;
+ goto out;
+ }
+#endif
+
+ if (blob->csb_flags & CS_PLATFORM_BINARY) {
+ if (cs_debug > 1)
+ printf("check_signature[pid: %d]: platform binary\n", current_proc()->p_pid);
+ blob->csb_platform_binary = 1;
+ blob->csb_platform_path = !!(blob->csb_flags & CS_PLATFORM_PATH);
+ } else {
+ blob->csb_platform_binary = 0;
+ blob->csb_platform_path = 0;
+ blob->csb_teamid = csblob_parse_teamid(blob);
+ if (cs_debug > 1) {
+ if (blob->csb_teamid)
+ printf("check_signature[pid: %d]: team-id is %s\n", current_proc()->p_pid, blob->csb_teamid);
+ else
+ printf("check_signature[pid: %d]: no team-id\n", current_proc()->p_pid);
+ }
+ }
+
+ /*
+ * Validate the blob's coverage
+ */
+ blob_start_offset = blob->csb_base_offset + blob->csb_start_offset;
+ blob_end_offset = blob->csb_base_offset + blob->csb_end_offset;
+
+ if (blob_start_offset >= blob_end_offset ||
+ blob_start_offset < 0 ||
+ blob_end_offset <= 0) {
+ /* reject empty or backwards blob */
+ error = EINVAL;
+ goto out;
+ }
+
+ if (ubc_cs_supports_multilevel_hash(blob)) {
+ ubc_cs_convert_to_multilevel_hash(blob);
+ }
+
+ vnode_lock(vp);
+ if (! UBCINFOEXISTS(vp)) {
+ vnode_unlock(vp);
+ error = ENOENT;
+ goto out;
+ }
+ uip = vp->v_ubcinfo;
+
+ /* check if this new blob overlaps with an existing blob */
+ for (oblob = uip->cs_blobs;
+ oblob != NULL;
+ oblob = oblob->csb_next) {
+ off_t oblob_start_offset, oblob_end_offset;
+
+ /* check for conflicting teamid */
+ if (blob->csb_platform_binary) { //platform binary needs to be the same for app slices
+ if (!oblob->csb_platform_binary) {
+ vnode_unlock(vp);
+ error = EALREADY;
+ goto out;
+ }
+ } else if (blob->csb_teamid) { //teamid binary needs to be the same for app slices
+ if (oblob->csb_platform_binary ||
+ oblob->csb_teamid == NULL ||
+ strcmp(oblob->csb_teamid, blob->csb_teamid) != 0) {
+ vnode_unlock(vp);
+ error = EALREADY;
+ goto out;
+ }
+ } else { // non teamid binary needs to be the same for app slices
+ if (oblob->csb_platform_binary ||
+ oblob->csb_teamid != NULL) {
+ vnode_unlock(vp);
+ error = EALREADY;
+ goto out;
+ }
+ }
+
+ oblob_start_offset = (oblob->csb_base_offset +
+ oblob->csb_start_offset);
+ oblob_end_offset = (oblob->csb_base_offset +
+ oblob->csb_end_offset);
+ if (blob_start_offset >= oblob_end_offset ||
+ blob_end_offset <= oblob_start_offset) {
+ /* no conflict with this existing blob */
+ } else {
+ /* conflict ! */
+ if (blob_start_offset == oblob_start_offset &&
+ blob_end_offset == oblob_end_offset &&
+ blob->csb_mem_size == oblob->csb_mem_size &&
+ blob->csb_flags == oblob->csb_flags &&
+ (blob->csb_cpu_type == CPU_TYPE_ANY ||
+ oblob->csb_cpu_type == CPU_TYPE_ANY ||
+ blob->csb_cpu_type == oblob->csb_cpu_type) &&
+ !bcmp(blob->csb_cdhash,
+ oblob->csb_cdhash,
+ CS_CDHASH_LEN)) {
+ /*
+ * We already have this blob:
+ * we'll return success but
+ * throw away the new blob.
+ */
+ if (oblob->csb_cpu_type == CPU_TYPE_ANY) {
+ /*
+ * The old blob matches this one
+ * but doesn't have any CPU type.
+ * Update it with whatever the caller
+ * provided this time.
+ */
+ oblob->csb_cpu_type = cputype;
+ }
+ vnode_unlock(vp);
+ if (ret_blob)
+ *ret_blob = oblob;
+ error = EAGAIN;
+ goto out;
+ } else {
+ /* different blob: reject the new one */
+ vnode_unlock(vp);
+ error = EALREADY;
+ goto out;
+ }
+ }
+
+ }
+
+
+ /* mark this vnode's VM object as having "signed pages" */
+ kr = memory_object_signed(uip->ui_control, TRUE);
+ if (kr != KERN_SUCCESS) {
+ vnode_unlock(vp);
+ error = ENOENT;
+ goto out;
+ }
+
+ if (uip->cs_blobs == NULL) {
+ /* loading 1st blob: record the file's current "modify time" */
+ record_mtime = TRUE;
+ }
+
+ /* set the generation count for cs_blobs */
+ uip->cs_add_gen = cs_blob_generation_count;
+
+ /*
+ * Add this blob to the list of blobs for this vnode.
+ * We always add at the front of the list and we never remove a
+ * blob from the list, so ubc_cs_get_blobs() can return whatever
+ * the top of the list was and that list will remain valid
+ * while we validate a page, even after we release the vnode's lock.
+ */
+ blob->csb_next = uip->cs_blobs;
+ uip->cs_blobs = blob;
+
+ OSAddAtomic(+1, &cs_blob_count);
+ if (cs_blob_count > cs_blob_count_peak) {
+ cs_blob_count_peak = cs_blob_count; /* XXX atomic ? */
+ }
+ OSAddAtomic((SInt32) +blob->csb_mem_size, &cs_blob_size);
+ if ((SInt32) cs_blob_size > cs_blob_size_peak) {
+ cs_blob_size_peak = (SInt32) cs_blob_size; /* XXX atomic ? */
+ }
+ if ((UInt32) blob->csb_mem_size > cs_blob_size_max) {
+ cs_blob_size_max = (UInt32) blob->csb_mem_size;
+ }
+
+ if (cs_debug > 1) {
+ proc_t p;
+ const char *name = vnode_getname_printable(vp);
+ p = current_proc();
+ printf("CODE SIGNING: proc %d(%s) "
+ "loaded %s signatures for file (%s) "
+ "range 0x%llx:0x%llx flags 0x%x\n",
+ p->p_pid, p->p_comm,
+ blob->csb_cpu_type == -1 ? "detached" : "embedded",
+ name,
+ blob->csb_base_offset + blob->csb_start_offset,
+ blob->csb_base_offset + blob->csb_end_offset,
+ blob->csb_flags);
+ vnode_putname_printable(name);
+ }
+
+ vnode_unlock(vp);
+
+ if (record_mtime) {
+ vnode_mtime(vp, &uip->cs_mtime, vfs_context_current());
+ }
+
+ if (ret_blob)
+ *ret_blob = blob;
+
+ error = 0; /* success ! */
+
+out:
+ if (error) {
+ if (cs_debug)
+ printf("check_signature[pid: %d]: error = %d\n", current_proc()->p_pid, error);
+
+ /* we failed; release what we allocated */
+ if (blob) {
+ if (blob->csb_mem_kaddr) {
+ ubc_cs_blob_deallocate(blob->csb_mem_kaddr, blob->csb_mem_size);
+ blob->csb_mem_kaddr = 0;
+ }
+ if (blob->csb_entitlements != NULL) {
+ osobject_release(blob->csb_entitlements);
+ blob->csb_entitlements = NULL;
+ }
+ kfree(blob, sizeof (*blob));
+ blob = NULL;
+ }
+ }
+
+ if (error == EAGAIN) {
+ /*
+ * See above: error is EAGAIN if we were asked
+ * to add an existing blob again. We cleaned the new
+ * blob and we want to return success.
+ */
+ error = 0;
+ }
+
+ return error;
+}
+
+void
+csvnode_print_debug(struct vnode *vp)