+ 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;
+
+ ubc_cs_blob_adjust_statistics(blob);
+
+ 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);
+ }
+
+ cs_blob_free(blob);
+ }
+
+ 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;
+}
+
+#if CONFIG_SUPPLEMENTAL_SIGNATURES
+int
+ubc_cs_blob_add_supplement(
+ struct vnode *vp,
+ struct vnode *orig_vp,
+ off_t base_offset,
+ vm_address_t *addr,
+ vm_size_t size,
+ struct cs_blob **ret_blob)
+{
+ kern_return_t kr;
+ struct ubc_info *uip, *orig_uip;
+ int error;
+ struct cs_blob *blob, *orig_blob;
+ CS_CodeDirectory const *cd;
+ off_t blob_start_offset, blob_end_offset;
+
+ if (ret_blob) {
+ *ret_blob = NULL;
+ }
+
+ /* Create the struct cs_blob wrapper that will be attached to the vnode.
+ * Validates the passed in blob in the process. */
+ error = cs_blob_create_validated(addr, size, &blob, &cd);
+
+ if (error != 0) {
+ printf("malformed code signature supplement blob: %d\n", error);
+ return error;
+ }
+
+ blob->csb_cpu_type = -1;
+ blob->csb_base_offset = base_offset;
+
+ blob->csb_reconstituted = false;
+
+ vnode_lock(orig_vp);
+ if (!UBCINFOEXISTS(orig_vp)) {
+ vnode_unlock(orig_vp);
+ error = ENOENT;
+ goto out;
+ }
+
+ orig_uip = orig_vp->v_ubcinfo;
+
+ /* check that the supplement's linked cdhash matches a cdhash of
+ * the target image.
+ */
+
+ if (blob->csb_linkage_hashtype == NULL) {
+ proc_t p;
+ const char *iname = vnode_getname_printable(vp);
+ p = current_proc();
+
+ printf("CODE SIGNING: proc %d(%s) supplemental signature for file (%s) "
+ "is not a supplemental.\n",
+ p->p_pid, p->p_comm, iname);
+
+ error = EINVAL;
+
+ vnode_putname_printable(iname);
+ vnode_unlock(orig_vp);
+ goto out;
+ }
+
+ for (orig_blob = orig_uip->cs_blobs; orig_blob != NULL;
+ orig_blob = orig_blob->csb_next) {
+ ptrauth_utils_auth_blob_generic(orig_blob->csb_cdhash,
+ sizeof(orig_blob->csb_cdhash),
+ OS_PTRAUTH_DISCRIMINATOR("cs_blob.csb_cd_signature"),
+ PTRAUTH_ADDR_DIVERSIFY,
+ orig_blob->csb_cdhash_signature);
+ if (orig_blob->csb_hashtype == blob->csb_linkage_hashtype &&
+ memcmp(orig_blob->csb_cdhash, blob->csb_linkage, CS_CDHASH_LEN) == 0) {
+ // Found match!
+ break;
+ }
+ }
+
+ if (orig_blob == NULL) {
+ // Not found.
+
+ proc_t p;
+ const char *iname = vnode_getname_printable(vp);
+ p = current_proc();
+
+ printf("CODE SIGNING: proc %d(%s) supplemental signature for file (%s) "
+ "does not match any attached cdhash.\n",
+ p->p_pid, p->p_comm, iname);
+
+ error = ESRCH;
+
+ vnode_putname_printable(iname);
+ vnode_unlock(orig_vp);
+ goto out;
+ }
+
+ vnode_unlock(orig_vp);
+
+ // validate the signature against policy!
+#if CONFIG_MACF
+ unsigned int signer_type = blob->csb_signer_type;
+ error = mac_vnode_check_supplemental_signature(vp, blob, orig_vp, orig_blob, &signer_type);
+ blob->csb_signer_type = signer_type;
+
+
+ if (error) {
+ if (cs_debug) {
+ printf("check_supplemental_signature[pid: %d], error = %d\n", current_proc()->p_pid, error);
+ }
+ goto out;
+ }
+#endif
+
+ // We allowed the supplemental signature blob so
+ // copy the platform bit or team-id from the linked signature and whether or not the original is developer code
+ blob->csb_platform_binary = 0;
+ blob->csb_platform_path = 0;
+ if (orig_blob->csb_platform_binary == 1) {
+ blob->csb_platform_binary = orig_blob->csb_platform_binary;
+ blob->csb_platform_path = orig_blob->csb_platform_path;
+ } else if (orig_blob->csb_teamid != NULL) {
+ vm_size_t teamid_size = strlen(orig_blob->csb_teamid) + 1;
+ blob->csb_supplement_teamid = kalloc(teamid_size);
+ if (blob->csb_supplement_teamid == NULL) {
+ error = ENOMEM;
+ goto out;
+ }
+ strlcpy(blob->csb_supplement_teamid, orig_blob->csb_teamid, teamid_size);
+ }
+ blob->csb_flags = (orig_blob->csb_flags & CS_DEV_CODE);
+
+ // 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;
+
+ struct cs_blob *existing = uip->cs_blob_supplement;
+ if (existing != NULL) {
+ if (blob->csb_hashtype == existing->csb_hashtype &&
+ memcmp(blob->csb_cdhash, existing->csb_cdhash, CS_CDHASH_LEN) == 0) {
+ error = EAGAIN; // non-fatal
+ } else {
+ error = EALREADY; // fatal
+ }
+
+ vnode_unlock(vp);
+ goto out;
+ }
+
+ /* Unlike regular cs_blobs, we only ever support one supplement. */
+ blob->csb_next = NULL;
+ uip->cs_blob_supplement = blob;
+
+ /* 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;
+ }
+
+ vnode_unlock(vp);
+
+ /* We still adjust statistics even for supplemental blobs, as they
+ * consume memory just the same. */
+ ubc_cs_blob_adjust_statistics(blob);
+
+ if (cs_debug > 1) {
+ proc_t p;
+ const char *name = vnode_getname_printable(vp);
+ p = current_proc();
+ printf("CODE SIGNING: proc %d(%s) "
+ "loaded supplemental signature for file (%s) "
+ "range 0x%llx:0x%llx\n",
+ p->p_pid, p->p_comm,
+ name,
+ blob->csb_base_offset + blob->csb_start_offset,
+ blob->csb_base_offset + blob->csb_end_offset);
+ vnode_putname_printable(name);
+ }
+
+ if (ret_blob) {
+ *ret_blob = blob;
+ }
+
+ error = 0; // Success!
+out:
+ if (error) {
+ if (cs_debug) {
+ printf("ubc_cs_blob_add_supplement[pid: %d]: error = %d\n", current_proc()->p_pid, error);
+ }
+
+ cs_blob_supplement_free(blob);
+ }
+
+ if (error == EAGAIN) {
+ /* We were asked to add an existing blob.
+ * We cleaned up and ignore the attempt. */
+ error = 0;
+ }
+
+ return error;
+}
+#endif
+
+
+
+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>");