+
+
+int
+UBCINFOEXISTS(const struct vnode * vp)
+{
+ return((vp) && ((vp)->v_type == VREG) && ((vp)->v_ubcinfo != UBC_INFO_NULL));
+}
+
+
+void
+ubc_upl_range_needed(
+ upl_t upl,
+ int index,
+ int count)
+{
+ upl_range_needed(upl, index, count);
+}
+
+boolean_t ubc_is_mapped(const struct vnode *vp, boolean_t *writable)
+{
+ if (!UBCINFOEXISTS(vp) || !ISSET(vp->v_ubcinfo->ui_flags, UI_ISMAPPED))
+ return FALSE;
+ if (writable)
+ *writable = ISSET(vp->v_ubcinfo->ui_flags, UI_MAPPEDWRITE);
+ return TRUE;
+}
+
+boolean_t ubc_is_mapped_writable(const struct vnode *vp)
+{
+ boolean_t writable;
+ return ubc_is_mapped(vp, &writable) && writable;
+}
+
+
+/*
+ * CODE SIGNING
+ */
+#define CS_BLOB_PAGEABLE 0
+static volatile SInt32 cs_blob_size = 0;
+static volatile SInt32 cs_blob_count = 0;
+static SInt32 cs_blob_size_peak = 0;
+static UInt32 cs_blob_size_max = 0;
+static SInt32 cs_blob_count_peak = 0;
+
+SYSCTL_INT(_vm, OID_AUTO, cs_blob_count, CTLFLAG_RD | CTLFLAG_LOCKED, (int *)(uintptr_t)&cs_blob_count, 0, "Current number of code signature blobs");
+SYSCTL_INT(_vm, OID_AUTO, cs_blob_size, CTLFLAG_RD | CTLFLAG_LOCKED, (int *)(uintptr_t)&cs_blob_size, 0, "Current size of all code signature blobs");
+SYSCTL_INT(_vm, OID_AUTO, cs_blob_count_peak, CTLFLAG_RD | CTLFLAG_LOCKED, &cs_blob_count_peak, 0, "Peak number of code signature blobs");
+SYSCTL_INT(_vm, OID_AUTO, cs_blob_size_peak, CTLFLAG_RD | CTLFLAG_LOCKED, &cs_blob_size_peak, 0, "Peak size of code signature blobs");
+SYSCTL_INT(_vm, OID_AUTO, cs_blob_size_max, CTLFLAG_RD | CTLFLAG_LOCKED, &cs_blob_size_max, 0, "Size of biggest code signature blob");
+
+/*
+ * Function: csblob_parse_teamid
+ *
+ * Description: This function returns a pointer to the team id
+ stored within the codedirectory of the csblob.
+ If the codedirectory predates team-ids, it returns
+ NULL.
+ This does not copy the name but returns a pointer to
+ it within the CD. Subsequently, the CD must be
+ available when this is used.
+*/
+
+static const char *
+csblob_parse_teamid(struct cs_blob *csblob)
+{
+ const CS_CodeDirectory *cd;
+
+ cd = csblob->csb_cd;
+
+ if (ntohl(cd->version) < CS_SUPPORTSTEAMID)
+ return NULL;
+
+ if (cd->teamOffset == 0)
+ return NULL;
+
+ const char *name = ((const char *)cd) + ntohl(cd->teamOffset);
+ if (cs_debug > 1)
+ printf("found team-id %s in cdblob\n", name);
+
+ return name;
+}
+
+
+kern_return_t
+ubc_cs_blob_allocate(
+ vm_offset_t *blob_addr_p,
+ vm_size_t *blob_size_p)
+{
+ kern_return_t kr;
+
+#if CS_BLOB_PAGEABLE
+ *blob_size_p = round_page(*blob_size_p);
+ kr = kmem_alloc(kernel_map, blob_addr_p, *blob_size_p, VM_KERN_MEMORY_SECURITY);
+#else /* CS_BLOB_PAGEABLE */
+ *blob_addr_p = (vm_offset_t) kalloc_tag(*blob_size_p, VM_KERN_MEMORY_SECURITY);
+ if (*blob_addr_p == 0) {
+ kr = KERN_NO_SPACE;
+ } else {
+ kr = KERN_SUCCESS;
+ }
+#endif /* CS_BLOB_PAGEABLE */
+ return kr;
+}
+
+void
+ubc_cs_blob_deallocate(
+ vm_offset_t blob_addr,
+ vm_size_t blob_size)
+{
+#if CS_BLOB_PAGEABLE
+ kmem_free(kernel_map, blob_addr, blob_size);
+#else /* CS_BLOB_PAGEABLE */
+ kfree((void *) blob_addr, blob_size);
+#endif /* CS_BLOB_PAGEABLE */
+}
+
+int
+ubc_cs_blob_add(
+ struct vnode *vp,
+ cpu_type_t cputype,
+ off_t base_offset,
+ vm_address_t addr,
+ vm_size_t size,
+ __unused int flags,
+ struct cs_blob **ret_blob)
+{
+ kern_return_t kr;
+ struct ubc_info *uip;
+ struct cs_blob *blob, *oblob;
+ int error;
+ ipc_port_t blob_handle;
+ memory_object_size_t blob_size;
+ const CS_CodeDirectory *cd;
+ off_t blob_start_offset, blob_end_offset;
+ union cs_hash_union mdctx;
+ boolean_t record_mtime;
+ int cs_flags;
+
+ record_mtime = FALSE;
+ cs_flags = 0;
+ if (ret_blob)
+ *ret_blob = NULL;
+
+ blob_handle = IPC_PORT_NULL;
+
+ blob = (struct cs_blob *) kalloc(sizeof (struct cs_blob));
+ if (blob == NULL) {
+ return ENOMEM;
+ }
+
+#if CS_BLOB_PAGEABLE
+ /* get a memory entry on the blob */
+ blob_size = (memory_object_size_t) size;
+ kr = mach_make_memory_entry_64(kernel_map,
+ &blob_size,
+ addr,
+ VM_PROT_READ,
+ &blob_handle,
+ IPC_PORT_NULL);
+ if (kr != KERN_SUCCESS) {
+ error = ENOMEM;
+ goto out;
+ }
+ if (memory_object_round_page(blob_size) !=
+ (memory_object_size_t) round_page(size)) {
+ printf("ubc_cs_blob_add: size mismatch 0x%llx 0x%lx !?\n",
+ blob_size, (size_t)size);
+ panic("XXX FBDP size mismatch 0x%llx 0x%lx\n", blob_size, (size_t)size);
+ error = EINVAL;
+ goto out;
+ }
+#else
+ blob_size = (memory_object_size_t) size;
+ blob_handle = IPC_PORT_NULL;
+#endif
+
+ /* 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_handle = blob_handle;
+ blob->csb_mem_kaddr = addr;
+ blob->csb_flags = 0;
+ blob->csb_platform_binary = 0;
+ blob->csb_platform_path = 0;
+ blob->csb_teamid = NULL;
+
+ /*
+ * Validate the blob's contents
+ */
+
+ error = cs_validate_csblob((const uint8_t *)addr, size, &cd);
+ 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;
+
+#if CS_BLOB_PAGEABLE
+#error "cd might move under CS_BLOB_PAGEABLE; reconsider this code"
+#endif
+ blob->csb_cd = cd;
+ 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_flags = (ntohl(cd->flags) & CS_ALLOWED_MACHO) | CS_VALID;
+ blob->csb_end_offset = round_page_4K(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 = ntohl(scatter->base) * PAGE_SIZE_4K;
+ } 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
+ error = mac_vnode_check_signature(vp,
+ base_offset,
+ blob->csb_cdhash,
+ (const void*)addr, size,
+ 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) && !(cs_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 (cs_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 = !!(cs_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;
+ }
+
+ 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) {
+ kfree(blob, sizeof (*blob));
+ blob = NULL;
+ }
+ if (blob_handle != IPC_PORT_NULL) {
+ mach_memory_entry_port_release(blob_handle);
+ blob_handle = IPC_PORT_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;
+ /*
+ * Since we're not failing, consume the data we received.
+ */
+ ubc_cs_blob_deallocate(addr, size);
+ }
+
+ return error;
+}
+
+void
+csvnode_print_debug(struct vnode *vp)
+{
+ const char *name = NULL;
+ struct ubc_info *uip;
+ struct cs_blob *blob;
+
+ name = vnode_getname_printable(vp);
+ if (name) {
+ printf("csvnode: name: %s\n", name);
+ vnode_putname_printable(name);
+ }
+
+ vnode_lock_spin(vp);
+
+ if (! UBCINFOEXISTS(vp)) {
+ blob = NULL;
+ goto out;
+ }
+
+ uip = vp->v_ubcinfo;
+ for (blob = uip->cs_blobs; blob != NULL; blob = blob->csb_next) {
+ printf("csvnode: range: %lu -> %lu flags: 0x%08x platform: %s path: %s team: %s\n",
+ (unsigned long)blob->csb_start_offset,
+ (unsigned long)blob->csb_end_offset,
+ blob->csb_flags,
+ blob->csb_platform_binary ? "yes" : "no",
+ blob->csb_platform_path ? "yes" : "no",
+ blob->csb_teamid ? blob->csb_teamid : "<NO-TEAM>");
+ }
+
+out:
+ vnode_unlock(vp);
+
+}
+
+struct cs_blob *
+ubc_cs_blob_get(
+ struct vnode *vp,
+ cpu_type_t cputype,
+ off_t offset)
+{
+ struct ubc_info *uip;
+ struct cs_blob *blob;
+ off_t offset_in_blob;
+
+ vnode_lock_spin(vp);
+
+ if (! UBCINFOEXISTS(vp)) {
+ blob = NULL;
+ goto out;
+ }
+
+ uip = vp->v_ubcinfo;
+ for (blob = uip->cs_blobs;
+ blob != NULL;
+ blob = blob->csb_next) {
+ if (cputype != -1 && blob->csb_cpu_type == cputype) {
+ break;
+ }
+ if (offset != -1) {
+ offset_in_blob = offset - blob->csb_base_offset;
+ if (offset_in_blob >= blob->csb_start_offset &&
+ offset_in_blob < blob->csb_end_offset) {
+ /* our offset is covered by this blob */
+ break;
+ }
+ }
+ }
+
+out:
+ vnode_unlock(vp);
+
+ return blob;
+}
+
+static void
+ubc_cs_free(
+ struct ubc_info *uip)
+{
+ struct cs_blob *blob, *next_blob;
+
+ for (blob = uip->cs_blobs;
+ blob != NULL;
+ blob = next_blob) {
+ next_blob = blob->csb_next;
+ if (blob->csb_mem_kaddr != 0) {
+ ubc_cs_blob_deallocate(blob->csb_mem_kaddr,
+ blob->csb_mem_size);
+ blob->csb_mem_kaddr = 0;
+ }
+ if (blob->csb_mem_handle != IPC_PORT_NULL) {
+ mach_memory_entry_port_release(blob->csb_mem_handle);
+ }
+ blob->csb_mem_handle = IPC_PORT_NULL;
+ OSAddAtomic(-1, &cs_blob_count);
+ OSAddAtomic((SInt32) -blob->csb_mem_size, &cs_blob_size);
+ kfree(blob, sizeof (*blob));
+ }
+#if CHECK_CS_VALIDATION_BITMAP
+ ubc_cs_validation_bitmap_deallocate( uip->ui_vnode );
+#endif
+ uip->cs_blobs = NULL;
+}
+
+/* check cs blob generation on vnode
+ * returns:
+ * 0 : Success, the cs_blob attached is current
+ * ENEEDAUTH : Generation count mismatch. Needs authentication again.
+ */
+int
+ubc_cs_generation_check(
+ struct vnode *vp)
+{
+ int retval = ENEEDAUTH;
+
+ vnode_lock_spin(vp);
+
+ if (UBCINFOEXISTS(vp) && vp->v_ubcinfo->cs_add_gen == cs_blob_generation_count) {
+ retval = 0;
+ }
+
+ vnode_unlock(vp);
+ return retval;
+}
+
+int
+ubc_cs_blob_revalidate(
+ struct vnode *vp,
+ struct cs_blob *blob,
+ __unused int flags
+ )
+{
+ int error = 0;
+#if CONFIG_MACF
+ int cs_flags = 0;
+#endif
+ const CS_CodeDirectory *cd = NULL;
+
+ assert(vp != NULL);
+ assert(blob != NULL);
+
+ error = cs_validate_csblob((const uint8_t *)blob->csb_mem_kaddr, blob->csb_mem_size, &cd);
+ if (error) {
+ if (cs_debug) {
+ printf("CODESIGNING: csblob invalid: %d\n", error);
+ }
+ goto out;
+ }
+
+ /* callout to mac_vnode_check_signature */
+#if CONFIG_MACF
+ error = mac_vnode_check_signature(vp, blob->csb_base_offset, blob->csb_cdhash,
+ (const void*)blob->csb_mem_kaddr, (int)blob->csb_mem_size,
+ flags, &cs_flags);
+ if (cs_debug && error) {
+ printf("revalidate: check_signature[pid: %d], error = %d\n", current_proc()->p_pid, error);
+ }
+#endif
+
+ /* update generation number if success */
+ vnode_lock_spin(vp);
+ if (UBCINFOEXISTS(vp)) {
+ if (error == 0)
+ vp->v_ubcinfo->cs_add_gen = cs_blob_generation_count;
+ else
+ vp->v_ubcinfo->cs_add_gen = 0;
+ }
+
+ vnode_unlock(vp);
+
+out:
+ return error;
+}
+
+void
+cs_blob_reset_cache()
+{
+ /* incrementing odd no by 2 makes sure '0' is never reached. */
+ OSAddAtomic(+2, &cs_blob_generation_count);
+ printf("Reseting cs_blob cache from all vnodes. \n");
+}
+
+struct cs_blob *
+ubc_get_cs_blobs(
+ struct vnode *vp)
+{
+ struct ubc_info *uip;
+ struct cs_blob *blobs;
+
+ /*
+ * No need to take the vnode lock here. The caller must be holding
+ * a reference on the vnode (via a VM mapping or open file descriptor),
+ * so the vnode will not go away. The ubc_info stays until the vnode
+ * goes away. And we only modify "blobs" by adding to the head of the
+ * list.
+ * The ubc_info could go away entirely if the vnode gets reclaimed as
+ * part of a forced unmount. In the case of a code-signature validation
+ * during a page fault, the "paging_in_progress" reference on the VM
+ * object guarantess that the vnode pager (and the ubc_info) won't go
+ * away during the fault.
+ * Other callers need to protect against vnode reclaim by holding the
+ * vnode lock, for example.
+ */
+
+ if (! UBCINFOEXISTS(vp)) {
+ blobs = NULL;
+ goto out;
+ }
+
+ uip = vp->v_ubcinfo;
+ blobs = uip->cs_blobs;
+
+out:
+ return blobs;
+}
+
+void
+ubc_get_cs_mtime(
+ struct vnode *vp,
+ struct timespec *cs_mtime)
+{
+ struct ubc_info *uip;
+
+ if (! UBCINFOEXISTS(vp)) {
+ cs_mtime->tv_sec = 0;
+ cs_mtime->tv_nsec = 0;
+ return;
+ }
+
+ uip = vp->v_ubcinfo;
+ cs_mtime->tv_sec = uip->cs_mtime.tv_sec;
+ cs_mtime->tv_nsec = uip->cs_mtime.tv_nsec;
+}
+
+unsigned long cs_validate_page_no_hash = 0;
+unsigned long cs_validate_page_bad_hash = 0;
+boolean_t
+cs_validate_page(
+ void *_blobs,
+ memory_object_t pager,
+ memory_object_offset_t page_offset,
+ const void *data,
+ unsigned *tainted)
+{
+ union cs_hash_union mdctx;
+ struct cs_hash *hashtype = NULL;
+ unsigned char actual_hash[CS_HASH_MAX_SIZE];
+ unsigned char expected_hash[CS_HASH_MAX_SIZE];
+ boolean_t found_hash;
+ struct cs_blob *blobs, *blob;
+ const CS_CodeDirectory *cd;
+ const unsigned char *hash;
+ boolean_t validated;
+ off_t offset; /* page offset in the file */
+ size_t size;
+ off_t codeLimit = 0;
+ const char *lower_bound, *upper_bound;
+ vm_offset_t kaddr, blob_addr;
+ vm_size_t ksize;
+ kern_return_t kr;
+
+ offset = page_offset;
+
+ /* retrieve the expected hash */
+ found_hash = FALSE;
+ blobs = (struct cs_blob *) _blobs;
+
+ for (blob = blobs;
+ blob != NULL;
+ blob = blob->csb_next) {
+ offset = page_offset - blob->csb_base_offset;
+ if (offset < blob->csb_start_offset ||
+ offset >= blob->csb_end_offset) {
+ /* our page is not covered by this blob */
+ continue;
+ }
+
+ /* map the blob in the kernel address space */
+ kaddr = blob->csb_mem_kaddr;
+ if (kaddr == 0) {
+ ksize = (vm_size_t) (blob->csb_mem_size +
+ blob->csb_mem_offset);
+ kr = vm_map(kernel_map,
+ &kaddr,
+ ksize,
+ 0,
+ VM_FLAGS_ANYWHERE,
+ blob->csb_mem_handle,
+ 0,
+ TRUE,
+ VM_PROT_READ,
+ VM_PROT_READ,
+ VM_INHERIT_NONE);
+ if (kr != KERN_SUCCESS) {
+ /* XXX FBDP what to do !? */
+ printf("cs_validate_page: failed to map blob, "
+ "size=0x%lx kr=0x%x\n",
+ (size_t)blob->csb_mem_size, kr);
+ break;
+ }
+ }
+
+ blob_addr = kaddr + blob->csb_mem_offset;
+ lower_bound = CAST_DOWN(char *, blob_addr);
+ upper_bound = lower_bound + blob->csb_mem_size;
+
+ cd = blob->csb_cd;
+ if (cd != NULL) {
+ /* all CD's that have been injected is already validated */
+
+ offset = page_offset - blob->csb_base_offset;
+ if (offset < blob->csb_start_offset ||
+ offset >= blob->csb_end_offset) {
+ /* our page is not covered by this blob */
+ continue;
+ }
+
+ hashtype = blob->csb_hashtype;
+ if (hashtype == NULL)
+ panic("unknown hash type ?");
+ if (hashtype->cs_digest_size > sizeof(actual_hash))
+ panic("hash size too large");
+
+ codeLimit = ntohl(cd->codeLimit);
+
+ hash = hashes(cd, (uint32_t)(offset>>PAGE_SHIFT_4K),
+ hashtype->cs_size,
+ lower_bound, upper_bound);
+ if (hash != NULL) {
+ bcopy(hash, expected_hash, hashtype->cs_size);
+ found_hash = TRUE;
+ }
+
+ break;
+ }
+ }
+
+ if (found_hash == FALSE) {
+ /*
+ * We can't verify this page because there is no signature
+ * for it (yet). It's possible that this part of the object
+ * is not signed, or that signatures for that part have not
+ * been loaded yet.
+ * Report that the page has not been validated and let the
+ * caller decide if it wants to accept it or not.
+ */
+ cs_validate_page_no_hash++;
+ if (cs_debug > 1) {
+ printf("CODE SIGNING: cs_validate_page: "
+ "mobj %p off 0x%llx: no hash to validate !?\n",
+ pager, page_offset);
+ }
+ validated = FALSE;
+ *tainted = 0;
+ } else {
+
+ *tainted = 0;
+
+ size = PAGE_SIZE_4K;
+ const uint32_t *asha1, *esha1;
+ if ((off_t)(offset + size) > codeLimit) {
+ /* partial page at end of segment */
+ assert(offset < codeLimit);
+ size = (size_t) (codeLimit & PAGE_MASK_4K);
+ *tainted |= CS_VALIDATE_NX;
+ }
+
+ hashtype->cs_init(&mdctx);
+ hashtype->cs_update(&mdctx, data, size);
+ hashtype->cs_final(actual_hash, &mdctx);
+
+ asha1 = (const uint32_t *) actual_hash;
+ esha1 = (const uint32_t *) expected_hash;
+
+ if (bcmp(expected_hash, actual_hash, hashtype->cs_size) != 0) {
+ if (cs_debug) {
+ printf("CODE SIGNING: cs_validate_page: "
+ "mobj %p off 0x%llx size 0x%lx: "
+ "actual [0x%x 0x%x 0x%x 0x%x 0x%x] != "
+ "expected [0x%x 0x%x 0x%x 0x%x 0x%x]\n",
+ pager, page_offset, size,
+ asha1[0], asha1[1], asha1[2],
+ asha1[3], asha1[4],
+ esha1[0], esha1[1], esha1[2],
+ esha1[3], esha1[4]);
+ }
+ cs_validate_page_bad_hash++;
+ *tainted |= CS_VALIDATE_TAINTED;
+ } else {
+ if (cs_debug > 10) {
+ printf("CODE SIGNING: cs_validate_page: "
+ "mobj %p off 0x%llx size 0x%lx: "
+ "SHA1 OK\n",
+ pager, page_offset, size);
+ }
+ }
+ validated = TRUE;
+ }
+
+ return validated;
+}
+
+int
+ubc_cs_getcdhash(
+ vnode_t vp,
+ off_t offset,
+ unsigned char *cdhash)
+{
+ struct cs_blob *blobs, *blob;
+ off_t rel_offset;
+ int ret;
+
+ vnode_lock(vp);
+
+ blobs = ubc_get_cs_blobs(vp);
+ for (blob = blobs;
+ blob != NULL;
+ blob = blob->csb_next) {
+ /* compute offset relative to this blob */
+ rel_offset = offset - blob->csb_base_offset;
+ if (rel_offset >= blob->csb_start_offset &&
+ rel_offset < blob->csb_end_offset) {
+ /* this blob does cover our "offset" ! */
+ break;
+ }
+ }
+
+ if (blob == NULL) {
+ /* we didn't find a blob covering "offset" */
+ ret = EBADEXEC; /* XXX any better error ? */
+ } else {
+ /* get the SHA1 hash of that blob */
+ bcopy(blob->csb_cdhash, cdhash, sizeof (blob->csb_cdhash));
+ ret = 0;
+ }
+
+ vnode_unlock(vp);
+
+ return ret;
+}
+
+#if CHECK_CS_VALIDATION_BITMAP
+#define stob(s) ((atop_64((s)) + 07) >> 3)
+extern boolean_t root_fs_upgrade_try;
+
+/*
+ * Should we use the code-sign bitmap to avoid repeated code-sign validation?
+ * Depends:
+ * a) Is the target vnode on the root filesystem?
+ * b) Has someone tried to mount the root filesystem read-write?
+ * If answers are (a) yes AND (b) no, then we can use the bitmap.
+ */
+#define USE_CODE_SIGN_BITMAP(vp) ( (vp != NULL) && (vp->v_mount != NULL) && (vp->v_mount->mnt_flag & MNT_ROOTFS) && !root_fs_upgrade_try)
+kern_return_t
+ubc_cs_validation_bitmap_allocate(
+ vnode_t vp)
+{
+ kern_return_t kr = KERN_SUCCESS;
+ struct ubc_info *uip;
+ char *target_bitmap;
+ vm_object_size_t bitmap_size;
+
+ if ( ! USE_CODE_SIGN_BITMAP(vp) || (! UBCINFOEXISTS(vp))) {
+ kr = KERN_INVALID_ARGUMENT;
+ } else {
+ uip = vp->v_ubcinfo;
+
+ if ( uip->cs_valid_bitmap == NULL ) {
+ bitmap_size = stob(uip->ui_size);
+ target_bitmap = (char*) kalloc( (vm_size_t)bitmap_size );
+ if (target_bitmap == 0) {
+ kr = KERN_NO_SPACE;
+ } else {
+ kr = KERN_SUCCESS;
+ }
+ if( kr == KERN_SUCCESS ) {
+ memset( target_bitmap, 0, (size_t)bitmap_size);
+ uip->cs_valid_bitmap = (void*)target_bitmap;
+ uip->cs_valid_bitmap_size = bitmap_size;
+ }
+ }
+ }
+ return kr;
+}
+
+kern_return_t
+ubc_cs_check_validation_bitmap (
+ vnode_t vp,
+ memory_object_offset_t offset,
+ int optype)
+{
+ kern_return_t kr = KERN_SUCCESS;
+
+ if ( ! USE_CODE_SIGN_BITMAP(vp) || ! UBCINFOEXISTS(vp)) {
+ kr = KERN_INVALID_ARGUMENT;
+ } else {
+ struct ubc_info *uip = vp->v_ubcinfo;
+ char *target_bitmap = uip->cs_valid_bitmap;
+
+ if ( target_bitmap == NULL ) {
+ kr = KERN_INVALID_ARGUMENT;
+ } else {
+ uint64_t bit, byte;
+ bit = atop_64( offset );
+ byte = bit >> 3;
+
+ if ( byte > uip->cs_valid_bitmap_size ) {
+ kr = KERN_INVALID_ARGUMENT;
+ } else {
+
+ if (optype == CS_BITMAP_SET) {
+ target_bitmap[byte] |= (1 << (bit & 07));
+ kr = KERN_SUCCESS;
+ } else if (optype == CS_BITMAP_CLEAR) {
+ target_bitmap[byte] &= ~(1 << (bit & 07));
+ kr = KERN_SUCCESS;
+ } else if (optype == CS_BITMAP_CHECK) {
+ if ( target_bitmap[byte] & (1 << (bit & 07))) {
+ kr = KERN_SUCCESS;
+ } else {
+ kr = KERN_FAILURE;
+ }
+ }
+ }
+ }
+ }
+ return kr;
+}
+
+void
+ubc_cs_validation_bitmap_deallocate(
+ vnode_t vp)
+{
+ struct ubc_info *uip;
+ void *target_bitmap;
+ vm_object_size_t bitmap_size;
+
+ if ( UBCINFOEXISTS(vp)) {
+ uip = vp->v_ubcinfo;
+
+ if ( (target_bitmap = uip->cs_valid_bitmap) != NULL ) {
+ bitmap_size = uip->cs_valid_bitmap_size;
+ kfree( target_bitmap, (vm_size_t) bitmap_size );
+ uip->cs_valid_bitmap = NULL;
+ }
+ }
+}
+#else
+kern_return_t ubc_cs_validation_bitmap_allocate(__unused vnode_t vp){
+ return KERN_INVALID_ARGUMENT;
+}
+
+kern_return_t ubc_cs_check_validation_bitmap(
+ __unused struct vnode *vp,
+ __unused memory_object_offset_t offset,
+ __unused int optype){
+
+ return KERN_INVALID_ARGUMENT;
+}
+
+void ubc_cs_validation_bitmap_deallocate(__unused vnode_t vp){
+ return;
+}
+#endif /* CHECK_CS_VALIDATION_BITMAP */