]> git.saurik.com Git - apple/xnu.git/blobdiff - bsd/kern/kern_cs.c
xnu-2422.1.72.tar.gz
[apple/xnu.git] / bsd / kern / kern_cs.c
diff --git a/bsd/kern/kern_cs.c b/bsd/kern/kern_cs.c
new file mode 100644 (file)
index 0000000..0414050
--- /dev/null
@@ -0,0 +1,474 @@
+/*
+ * Copyright (c) 2000-2012 Apple Inc. All rights reserved.
+ *
+ * @APPLE_OSREFERENCE_LICENSE_HEADER_START@
+ * 
+ * This file contains Original Code and/or Modifications of Original Code
+ * as defined in and that are subject to the Apple Public Source License
+ * Version 2.0 (the 'License'). You may not use this file except in
+ * compliance with the License. The rights granted to you under the License
+ * may not be used to create, or enable the creation or redistribution of,
+ * unlawful or unlicensed copies of an Apple operating system, or to
+ * circumvent, violate, or enable the circumvention or violation of, any
+ * terms of an Apple operating system software license agreement.
+ * 
+ * Please obtain a copy of the License at
+ * http://www.opensource.apple.com/apsl/ and read it before using this file.
+ * 
+ * The Original Code and all software distributed under the License are
+ * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER
+ * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES,
+ * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT.
+ * Please see the License for the specific language governing rights and
+ * limitations under the License.
+ * 
+ * @APPLE_OSREFERENCE_LICENSE_HEADER_END@
+ */
+
+#include <sys/types.h>
+#include <sys/param.h>
+#include <sys/systm.h>
+#include <sys/kernel.h>
+#include <sys/proc_internal.h>
+#include <sys/sysctl.h>
+#include <sys/signal.h>
+#include <sys/signalvar.h>
+#include <sys/codesign.h>
+
+#include <sys/fcntl.h>
+#include <sys/file.h>
+#include <sys/kauth.h>
+#include <sys/mount.h>
+#include <sys/msg.h>
+#include <sys/proc.h>
+#include <sys/socketvar.h>
+#include <sys/vnode.h>
+#include <sys/vnode_internal.h>
+
+#include <sys/ubc.h>
+#include <sys/ubc_internal.h>
+
+#include <security/mac.h>
+#include <security/mac_policy.h>
+#include <security/mac_framework.h>
+
+#include <mach/mach_types.h>
+#include <mach/vm_map.h>
+#include <mach/mach_vm.h>
+
+#include <kern/kern_types.h>
+#include <kern/task.h>
+
+#include <vm/vm_map.h>
+#include <vm/vm_kern.h>
+
+#include <sys/kasl.h>
+#include <sys/syslog.h>
+
+#include <kern/assert.h>
+
+#include <pexpert/pexpert.h>
+
+unsigned long cs_procs_killed = 0;
+unsigned long cs_procs_invalidated = 0;
+
+int cs_force_kill = 0;
+int cs_force_hard = 0;
+int cs_debug = 0;
+#if SECURE_KERNEL
+const int cs_enforcement_enable=1;
+#else
+#if CONFIG_ENFORCE_SIGNED_CODE
+int cs_enforcement_enable=1;
+#else
+int cs_enforcement_enable=0;
+#endif
+int cs_enforcement_panic=0;
+#endif
+int cs_all_vnodes = 0;
+
+static lck_grp_t *cs_lockgrp;
+static lck_rw_t * SigPUPLock;
+
+SYSCTL_INT(_vm, OID_AUTO, cs_force_kill, CTLFLAG_RW | CTLFLAG_LOCKED, &cs_force_kill, 0, "");
+SYSCTL_INT(_vm, OID_AUTO, cs_force_hard, CTLFLAG_RW | CTLFLAG_LOCKED, &cs_force_hard, 0, "");
+SYSCTL_INT(_vm, OID_AUTO, cs_debug, CTLFLAG_RW | CTLFLAG_LOCKED, &cs_debug, 0, "");
+
+SYSCTL_INT(_vm, OID_AUTO, cs_all_vnodes, CTLFLAG_RW | CTLFLAG_LOCKED, &cs_all_vnodes, 0, "");
+
+#if !SECURE_KERNEL
+SYSCTL_INT(_vm, OID_AUTO, cs_enforcement, CTLFLAG_RW | CTLFLAG_LOCKED, &cs_enforcement_enable, 0, "");
+SYSCTL_INT(_vm, OID_AUTO, cs_enforcement_panic, CTLFLAG_RW | CTLFLAG_LOCKED, &cs_enforcement_panic, 0, "");
+#endif
+
+void
+cs_init(void)
+{
+#if !SECURE_KERNEL
+       int disable_cs_enforcement = 0;
+       PE_parse_boot_argn("cs_enforcement_disable", &disable_cs_enforcement, 
+                          sizeof (disable_cs_enforcement));
+       if (disable_cs_enforcement) {
+               cs_enforcement_enable = 0;
+       } else {
+               int panic = 0;
+               PE_parse_boot_argn("cs_enforcement_panic", &panic, sizeof(panic));
+               cs_enforcement_panic = (panic != 0);
+       }
+
+       PE_parse_boot_argn("cs_debug", &cs_debug, sizeof (cs_debug));
+#endif
+       lck_grp_attr_t *attr = lck_grp_attr_alloc_init();
+       cs_lockgrp = lck_grp_alloc_init("KERNCS", attr);
+       SigPUPLock = lck_rw_alloc_init(cs_lockgrp, NULL);
+}
+
+int
+cs_allow_invalid(struct proc *p)
+{
+#if MACH_ASSERT
+       lck_mtx_assert(&p->p_mlock, LCK_MTX_ASSERT_NOTOWNED);
+#endif
+#if CONFIG_MACF && CONFIG_ENFORCE_SIGNED_CODE
+       /* There needs to be a MAC policy to implement this hook, or else the
+        * kill bits will be cleared here every time. If we have 
+        * CONFIG_ENFORCE_SIGNED_CODE, we can assume there is a policy
+        * implementing the hook. 
+        */
+       if( 0 != mac_proc_check_run_cs_invalid(p)) {
+               if(cs_debug) printf("CODE SIGNING: cs_allow_invalid() "
+                                   "not allowed: pid %d\n", 
+                                   p->p_pid);
+               return 0;
+       }
+       if(cs_debug) printf("CODE SIGNING: cs_allow_invalid() "
+                           "allowed: pid %d\n", 
+                           p->p_pid);
+       proc_lock(p);
+       p->p_csflags &= ~(CS_KILL | CS_HARD);
+       proc_unlock(p);
+       vm_map_switch_protect(get_task_map(p->task), FALSE);
+#endif
+       return (p->p_csflags & (CS_KILL | CS_HARD)) == 0;
+}
+
+int
+cs_invalid_page(
+       addr64_t vaddr)
+{
+       struct proc     *p;
+       int             send_kill = 0, retval = 0, verbose = cs_debug;
+       uint32_t        csflags;
+
+       p = current_proc();
+
+       /*
+        * XXX revisit locking when proc is no longer protected
+        * by the kernel funnel...
+        */
+
+       if (verbose)
+               printf("CODE SIGNING: cs_invalid_page(0x%llx): p=%d[%s]\n",
+                   vaddr, p->p_pid, p->p_comm);
+
+       proc_lock(p);
+
+       /* XXX for testing */
+       if (cs_force_kill)
+               p->p_csflags |= CS_KILL;
+       if (cs_force_hard)
+               p->p_csflags |= CS_HARD;
+
+       /* CS_KILL triggers a kill signal, and no you can't have the page. Nothing else. */
+       if (p->p_csflags & CS_KILL) {
+               p->p_csflags |= CS_KILLED;
+               cs_procs_killed++;
+               send_kill = 1;
+               retval = 1;
+       }
+       
+       /* CS_HARD means fail the mapping operation so the process stays valid. */
+       if (p->p_csflags & CS_HARD) {
+               retval = 1;
+       } else {
+               if (p->p_csflags & CS_VALID) {
+                       p->p_csflags &= ~CS_VALID;
+                       cs_procs_invalidated++;
+                       verbose = 1;
+               }
+       }
+       csflags = p->p_csflags;
+       proc_unlock(p);
+
+       if (verbose) {
+               char pid_str[10];
+               snprintf(pid_str, sizeof(pid_str), "%d", p->p_pid);
+               kern_asl_msg(LOG_NOTICE, "messagetracer",
+                       5,
+                       "com.apple.message.domain", "com.apple.kernel.cs.invalidate",
+                       "com.apple.message.signature", send_kill ? "kill" : retval ? "deny" : "invalidate",
+                       "com.apple.message.signature4", pid_str,
+                       "com.apple.message.signature3", p->p_comm,
+                       "com.apple.message.summarize", "YES",
+                       NULL
+               );
+               printf("CODE SIGNING: cs_invalid_page(0x%llx): "
+                      "p=%d[%s] final status 0x%x, %sing page%s\n",
+                      vaddr, p->p_pid, p->p_comm, p->p_csflags,
+                      retval ? "deny" : "allow (remove VALID)",
+                      send_kill ? " sending SIGKILL" : "");
+       }
+
+       if (send_kill)
+               psignal(p, SIGKILL);
+
+
+       return retval;
+}
+
+/*
+ * Assumes p (if passed in) is locked with proc_lock().
+ */
+
+int
+cs_enforcement(struct proc *p)
+{
+
+       if (cs_enforcement_enable)
+               return 1;
+       
+       if (p == NULL)
+               p = current_proc();
+
+       if (p != NULL && (p->p_csflags & CS_ENFORCEMENT))
+               return 1;
+
+       return 0;
+}
+
+static struct {
+       struct cscsr_functions *funcs;
+       vm_map_offset_t csr_map_base;
+       vm_map_size_t csr_map_size;
+       int inuse;
+       int disabled;
+} csr_state;
+
+SYSCTL_INT(_vm, OID_AUTO, sigpup_disable, CTLFLAG_RW | CTLFLAG_LOCKED, &csr_state.disabled, 0, "");
+
+static int
+vnsize(vfs_context_t vfs, vnode_t vp, uint64_t *size)
+{
+       struct vnode_attr va;
+       int error;
+
+       VATTR_INIT(&va);
+       VATTR_WANTED(&va, va_data_size);
+
+       error = vnode_getattr(vp, &va, vfs);
+       if (error)
+               return error;
+       *size = va.va_data_size;
+       return 0;
+}
+
+int
+sigpup_install(user_addr_t argsp)
+{
+       struct sigpup_install_table args;
+       memory_object_control_t control;
+       kern_return_t result;
+       vfs_context_t vfs = NULL;
+       struct vnode_attr va;
+       vnode_t vp = NULL;
+        char *buf = NULL;
+       uint64_t size;
+       size_t len = 0;
+       int error = 0;
+       
+       if (!cs_enforcement_enable || csr_state.funcs == NULL)
+               return ENOTSUP;
+
+       lck_rw_lock_exclusive(SigPUPLock);
+
+       if (kauth_cred_issuser(kauth_cred_get()) == 0) {
+               error = EPERM;
+               goto cleanup;
+       }
+
+       if (cs_debug > 10)
+               printf("sigpup install\n");
+
+       if (csr_state.csr_map_base != 0 || csr_state.inuse) {
+               error = EPERM;
+               goto cleanup;
+       }
+
+       if (USER_ADDR_NULL == argsp) {
+               error = EINVAL;
+               goto cleanup;
+       }
+       if ((error = copyin(argsp, &args, sizeof(args))) != 0)
+               goto cleanup;
+
+       if (cs_debug > 10)
+               printf("sigpup install with args\n");
+
+       MALLOC(buf, char *, MAXPATHLEN, M_TEMP, M_WAITOK);
+       if (buf == NULL) {
+               error = ENOMEM;
+               goto cleanup;
+       }
+       if ((error = copyinstr((user_addr_t)args.path, buf, MAXPATHLEN, &len)) != 0)
+               goto cleanup;
+
+       if ((vfs = vfs_context_create(NULL)) == NULL) {
+               error = ENOMEM;
+               goto cleanup;
+       }
+
+       if ((error = vnode_lookup(buf, VNODE_LOOKUP_NOFOLLOW, &vp, vfs)) != 0)
+               goto cleanup;
+
+       if (cs_debug > 10)
+               printf("sigpup found file: %s\n", buf);
+
+       /* make sure vnode is on the process's root volume */
+       if (rootvnode->v_mount != vp->v_mount) {
+               if (cs_debug) printf("sigpup csr no on root volume\n");
+               error = EPERM;
+               goto cleanup;
+       }
+
+       /* make sure vnode is owned by "root" */
+       VATTR_INIT(&va);
+       VATTR_WANTED(&va, va_uid);
+       error = vnode_getattr(vp, &va, vfs);
+       if (error)
+               goto cleanup;
+
+       if (va.va_uid != 0) {
+               if (cs_debug) printf("sigpup: csr file not owned by root\n");
+               error = EPERM;
+               goto cleanup;
+       }
+
+       error = vnsize(vfs, vp, &size);
+       if (error)
+               goto cleanup;
+
+       control = ubc_getobject(vp, 0);
+       if (control == MEMORY_OBJECT_CONTROL_NULL) {
+               error = EINVAL;
+               goto cleanup;
+       }
+
+       csr_state.csr_map_size = mach_vm_round_page(size);
+
+       if (cs_debug > 10)
+               printf("mmap!\n");
+
+       result = vm_map_enter_mem_object_control(kernel_map,
+                                                &csr_state.csr_map_base,
+                                                csr_state.csr_map_size,
+                                                0, VM_FLAGS_ANYWHERE,
+                                                control, 0 /* file offset */,
+                                                0 /* cow */,
+                                                VM_PROT_READ,
+                                                VM_PROT_READ, 
+                                                VM_INHERIT_DEFAULT);
+       if (result != KERN_SUCCESS) {
+               error = EINVAL;
+               goto cleanup;
+       }
+
+       error = csr_state.funcs->csr_validate_header((const uint8_t *)csr_state.csr_map_base,
+           csr_state.csr_map_size);
+       if (error) {
+               if (cs_debug > 10)
+                       printf("sigpup header invalid, dropping mapping");
+               sigpup_drop();
+               goto cleanup;
+       }
+
+       if (cs_debug > 10)
+               printf("table loaded %ld bytes\n", (long)csr_state.csr_map_size);
+
+cleanup:
+       lck_rw_unlock_exclusive(SigPUPLock);
+
+        if (buf)
+                FREE(buf, M_TEMP);
+       if (vp)
+               (void)vnode_put(vp);
+       if (vfs)
+               (void)vfs_context_rele(vfs);
+        
+       if (error)
+               printf("sigpup: load failed with error: %d\n", error);
+
+
+       return error;
+}
+
+int
+sigpup_drop(void)
+{
+
+       if (kauth_cred_issuser(kauth_cred_get()) == 0)
+               return EPERM;
+
+       lck_rw_lock_exclusive(SigPUPLock);
+
+       if (csr_state.csr_map_base == 0 || csr_state.inuse) {
+               printf("failed to unload the sigpup database\n");
+               lck_rw_unlock_exclusive(SigPUPLock);
+               return EINVAL;
+       }
+
+       if (cs_debug > 10)
+               printf("sigpup: unloading\n");
+
+       (void)mach_vm_deallocate(kernel_map,
+           csr_state.csr_map_base, csr_state.csr_map_size);
+
+       csr_state.csr_map_base = 0;
+       csr_state.csr_map_size = 0;
+
+       lck_rw_unlock_exclusive(SigPUPLock);
+
+       return 0;
+}
+
+void   sigpup_attach_vnode(vnode_t); /* XXX */
+
+void
+sigpup_attach_vnode(vnode_t vp)
+{
+       const void *csblob;
+       size_t cslen;
+
+       if (!cs_enforcement_enable || csr_state.funcs == NULL || csr_state.csr_map_base == 0 || csr_state.disabled)
+               return;
+
+       /* if the file is not on the root volumes or already been check, skip */
+       if (vp->v_mount != rootvnode->v_mount || (vp->v_flag & VNOCS))
+               return;
+
+       csblob = csr_state.funcs->csr_find_file_codedirectory(vp, (const uint8_t *)csr_state.csr_map_base,
+           (size_t)csr_state.csr_map_size, &cslen);
+       if (csblob) {
+               ubc_cs_sigpup_add(vp, (vm_address_t)csblob, (vm_size_t)cslen);
+               csr_state.inuse = 1;
+       }
+       vp->v_flag |= VNOCS;
+}
+
+void
+cs_register_cscsr(struct cscsr_functions *funcs)
+{
+       if (csr_state.funcs || funcs->csr_version < CSCSR_VERSION)
+               return;
+       csr_state.funcs = funcs;
+}