]> git.saurik.com Git - apple/xnu.git/blobdiff - bsd/kern/sys_coalition.c
xnu-2782.1.97.tar.gz
[apple/xnu.git] / bsd / kern / sys_coalition.c
diff --git a/bsd/kern/sys_coalition.c b/bsd/kern/sys_coalition.c
new file mode 100644 (file)
index 0000000..3255fb0
--- /dev/null
@@ -0,0 +1,281 @@
+#include <kern/kern_types.h>
+#include <mach/mach_types.h>
+#include <mach/boolean.h>
+
+#include <kern/coalition.h>
+
+#include <sys/coalition.h>
+#include <sys/errno.h>
+#include <sys/kernel.h>
+#include <sys/sysproto.h>
+#include <sys/systm.h>
+
+/* Coalitions syscalls */
+
+/*
+ * Create a new, empty coalition and return its ID.
+ *
+ * Returns:
+ * EINVAL      Flags parameter was invalid
+ * ENOMEM      Unable to allocate kernel resources for a new coalition
+ * EFAULT      cidp parameter pointed to invalid memory.
+ *
+ * Returns with reference held for userspace caller.
+ */
+static
+int
+coalition_create_syscall(user_addr_t cidp, uint32_t flags)
+{
+       int error = 0;
+       kern_return_t kr;
+       uint64_t cid;
+       coalition_t coal;
+
+       if ((flags & (~COALITION_CREATE_FLAG_MASK)) != 0) {
+               return EINVAL;
+       }
+
+       boolean_t privileged = flags & COALITION_CREATE_FLAG_PRIVILEGED;
+
+       kr = coalition_create_internal(&coal, privileged);
+       if (kr != KERN_SUCCESS) {
+               /* for now, the only kr is KERN_RESOURCE_SHORTAGE */
+               error = ENOMEM;
+               goto out;
+       }
+
+       cid = coalition_id(coal);
+
+#if COALITION_DEBUG
+       printf("%s(addr, %u) -> %llu\n", __func__, flags, cid);
+#endif
+       error = copyout(&cid, cidp, sizeof(cid));
+out:
+       return error;
+}
+
+/*
+ * Request to terminate the coalition identified by ID.
+ * Attempts to spawn into this coalition using the posix_spawnattr will begin
+ * failing. Processes already within the coalition may still fork.
+ * Arms the 'coalition is empty' notification when the coalition's active
+ * count reaches zero.
+ *
+ * Returns:
+ * ESRCH       No coalition with that ID could be found.
+ * EALREADY    The coalition with that ID has already been terminated.
+ * EFAULT      cidp parameter pointed to invalid memory.
+ * EPERM       Caller doesn't have permission to terminate that coalition.
+ */
+static
+int
+coalition_request_terminate_syscall(user_addr_t cidp, uint32_t flags)
+{
+       kern_return_t kr;
+       int error = 0;
+       uint64_t cid;
+       coalition_t coal;
+
+       if (flags != 0) {
+               return EINVAL;
+       }
+
+       error = copyin(cidp, &cid, sizeof(cid));
+       if (error) {
+               return error;
+       }
+
+       coal = coalition_find_by_id(cid);
+       if (coal == COALITION_NULL) {
+               return ESRCH;
+       }
+
+       kr = coalition_request_terminate_internal(coal);
+       coalition_release(coal);
+
+       switch (kr) {
+       case KERN_SUCCESS:
+               break;
+       case KERN_DEFAULT_SET:
+               error = EPERM;
+       case KERN_TERMINATED:
+               error = EALREADY;
+       case KERN_INVALID_NAME:
+               error = ESRCH;
+       default:
+               error = EIO;
+       }
+
+#if COALITION_DEBUG
+       printf("%s(%llu, %u) -> %d\n", __func__, cid, flags, error);
+#endif
+
+       return error;
+}
+
+/*
+ * Request the kernel to deallocate the coalition identified by ID, which
+ * must be both terminated and empty. This balances the reference taken
+ * in coalition_create.
+ * The memory containig the coalition object may not be freed just yet, if
+ * other kernel operations still hold references to it.
+ *
+ * Returns:
+ * EINVAL      Flags parameter was invalid
+ * ESRCH       Coalition ID refers to a coalition that doesn't exist.
+ * EBUSY       Coalition has not yet been terminated.
+ * EBUSY       Coalition is still active.
+ * EFAULT      cidp parameter pointed to invalid memory.
+ * EPERM       Caller doesn't have permission to terminate that coalition.
+ * Consumes one reference, "held" by caller since coalition_create
+ */
+static
+int
+coalition_reap_syscall(user_addr_t cidp, uint32_t flags)
+{
+       kern_return_t kr;
+       int error = 0;
+       uint64_t cid;
+       coalition_t coal;
+
+       if (flags != 0) {
+               return EINVAL;
+       }
+
+       error = copyin(cidp, &cid, sizeof(cid));
+       if (error) {
+               return error;
+       }
+
+       coal = coalition_find_by_id(cid);
+       if (coal == COALITION_NULL) {
+               return ESRCH;
+       }
+
+       kr = coalition_reap_internal(coal);
+       coalition_release(coal);
+
+       switch (kr) {
+       case KERN_SUCCESS:
+               break;
+       case KERN_DEFAULT_SET:
+               error = EPERM;
+       case KERN_TERMINATED:
+               error = ESRCH;
+       case KERN_FAILURE:
+               error = EBUSY;
+       default:
+               error = EIO;
+       }
+
+#if COALITION_DEBUG
+       printf("%s(%llu, %u) -> %d\n", __func__, cid, flags, error);
+#endif
+
+       return error;
+}
+
+/* Syscall demux.
+ * Returns EPERM if the calling process is not privileged to make this call.
+ */
+int coalition(proc_t p, struct coalition_args *cap, __unused int32_t *retval)
+{
+       uint32_t operation = cap->operation;
+       user_addr_t cidp = cap->cid;
+       uint32_t flags = cap->flags;
+       int error = 0;
+
+       if (!task_is_in_privileged_coalition(p->task)) {
+               return EPERM;
+       }
+
+       switch (operation) {
+       case COALITION_OP_CREATE:
+               error = coalition_create_syscall(cidp, flags);
+               break;
+       case COALITION_OP_REAP:
+               error = coalition_reap_syscall(cidp, flags);
+               break;
+       case COALITION_OP_TERMINATE:
+               error = coalition_request_terminate_syscall(cidp, flags);
+               break;
+       default:
+               error = ENOSYS;
+       }
+       return error;
+}
+
+/* This is a temporary interface, likely to be changed by 15385642. */
+static int __attribute__ ((noinline))
+coalition_info_resource_usage(coalition_t coal, user_addr_t buffer, user_size_t bufsize)
+{
+       kern_return_t kr;
+       struct coalition_resource_usage cru;
+
+       if (bufsize != sizeof(cru)) {
+               return EINVAL;
+       }
+
+       kr = coalition_resource_usage_internal(coal, &cru);
+
+       switch (kr) {
+       case KERN_INVALID_ARGUMENT:
+               return EINVAL;
+       case KERN_RESOURCE_SHORTAGE:
+               return ENOMEM;
+       case KERN_SUCCESS:
+               break;
+       default:
+               return EIO; /* shrug */
+       }
+
+       return copyout(&cru, buffer, bufsize);
+}
+
+int coalition_info(proc_t p, struct coalition_info_args *uap, __unused int32_t *retval)
+{
+       user_addr_t cidp = uap->cid;
+       user_addr_t buffer = uap->buffer;
+       user_addr_t bufsizep = uap->bufsize;
+       user_size_t bufsize;
+       uint32_t flavor = uap->flavor;
+       int error;
+       uint64_t cid;
+       coalition_t coal;
+
+       error = copyin(cidp, &cid, sizeof(cid));
+       if (error) {
+               return error;
+       }
+
+       coal = coalition_find_by_id(cid);
+       if (coal == COALITION_NULL) {
+               return ESRCH;
+       }
+       /* TODO: priv check? EPERM or ESRCH? */
+
+       if (IS_64BIT_PROCESS(p)) {
+               user64_size_t size64;
+               error = copyin(bufsizep, &size64, sizeof(size64));
+               bufsize = (user_size_t)size64;
+       } else {
+               user32_size_t size32;
+               error = copyin(bufsizep, &size32, sizeof(size32));
+               bufsize = (user_size_t)size32;
+       }
+       if (error) {
+               goto bad;
+       }
+
+       switch (flavor) {
+       case COALITION_INFO_RESOURCE_USAGE:
+               error = coalition_info_resource_usage(coal, buffer, bufsize);
+               break;
+       default:
+               error = EINVAL;
+       }
+
+bad:
+       coalition_release(coal);
+       return error;
+}