+}
+
+/*
+ * 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;
+ }