+ return BOOTSTRAP_NO_MEMORY;
+ }
+
+ *subsetportp = jmr->jm_port;
+ jmr->properties |= BOOTSTRAP_PROPERTY_EXPLICITSUBSET;
+
+ /* A job could create multiple subsets, so only add a reference the first time
+ * it does so we don't have to keep a count.
+ */
+ if (j->anonymous && !j->holds_ref) {
+ j->holds_ref = true;
+ runtime_add_ref();
+ }
+
+ job_log(j, LOG_DEBUG, "Job created a subset named \"%s\"", jmr->name);
+ return BOOTSTRAP_SUCCESS;
+}
+
+#ifndef __LAUNCH_DISABLE_XPC_SUPPORT__
+job_t
+xpc_domain_import_service(jobmgr_t jm, launch_data_t pload)
+{
+ jobmgr_t where2put = NULL;
+
+ launch_data_t destname = launch_data_dict_lookup(pload, LAUNCH_JOBKEY_XPCDOMAIN);
+ if (destname) {
+ if (launch_data_get_type(destname) == LAUNCH_DATA_STRING) {
+ const char *str = launch_data_get_string(destname);
+ if (strcmp(str, XPC_DOMAIN_TYPE_SYSTEM) == 0) {
+ where2put = _s_xpc_system_domain;
+ } else if (strcmp(str, XPC_DOMAIN_TYPE_PERUSER) == 0) {
+ where2put = jobmgr_find_xpc_per_user_domain(jm, jm->req_euid);
+ } else if (strcmp(str, XPC_DOMAIN_TYPE_PERSESSION) == 0) {
+ where2put = jobmgr_find_xpc_per_session_domain(jm, jm->req_asid);
+ } else {
+ jobmgr_log(jm, LOG_ERR, "Invalid XPC domain type: %s", str);
+ errno = EINVAL;
+ }
+ } else {
+ jobmgr_log(jm, LOG_ERR, "XPC domain type is not a string.");
+ errno = EINVAL;
+ }
+
+ if (where2put) {
+ launch_data_t mi = NULL;
+ if ((mi = launch_data_dict_lookup(pload, LAUNCH_JOBKEY_MULTIPLEINSTANCES))) {
+ if (launch_data_get_type(mi) == LAUNCH_DATA_BOOL && launch_data_get_bool(mi)) {
+ jobmgr_log(where2put, LOG_ERR, "Multiple-instance services are not supported in this domain.");
+ where2put = NULL;
+ errno = EINVAL;
+ }
+ }
+ }
+ } else {
+ where2put = jm;
+ }
+
+ job_t j = NULL;
+ if (where2put) {
+ jobmgr_log(where2put, LOG_DEBUG, "Importing service...");
+ j = jobmgr_import2(where2put, pload);
+ if (j) {
+ j->xpc_service = true;
+ if (where2put->xpc_singleton) {
+ /* If the service was destined for one of the global domains,
+ * then we have to alias it into our local domain to reserve the
+ * name.
+ */
+ job_t ja = job_new_alias(jm, j);
+ if (!ja) {
+ /* If we failed to alias the job because of a conflict over
+ * the label, then we remove it from the global domain. We
+ * don't want to risk having imported a malicious job into
+ * one of the global domains.
+ */
+ if (errno != EEXIST) {
+ job_assumes(j, errno == 0);
+ } else {
+ job_log(j, LOG_ERR, "Failed to alias job into: %s", where2put->name);
+ }
+
+ job_remove(j);
+ } else {
+ ja->xpc_service = true;
+ j = ja;
+ }
+ }
+ }
+ }
+
+ return j;
+}
+
+kern_return_t
+xpc_domain_import2(job_t j, mach_port_t reqport, mach_port_t dport)
+{
+ if (unlikely(!pid1_magic)) {
+ job_log(j, LOG_ERR, "XPC domains may only reside in PID 1.");
+ return BOOTSTRAP_NOT_PRIVILEGED;
+ }
+ if (!j || !MACH_PORT_VALID(reqport)) {
+ return BOOTSTRAP_UNKNOWN_SERVICE;
+ }
+ if (root_jobmgr->shutting_down) {
+ jobmgr_log(root_jobmgr, LOG_ERR, "Attempt to create new domain while shutting down.");
+ return BOOTSTRAP_NOT_PRIVILEGED;
+ }
+
+ kern_return_t kr = BOOTSTRAP_NO_MEMORY;
+ /* All XPC domains are children of the root job manager. What we're creating
+ * here is really just a skeleton. By creating it, we're adding reqp to our
+ * port set. It will have two messages on it. The first specifies the
+ * environment of the originator. This is so we can cache it and hand it to
+ * xpcproxy to bootstrap our services. The second is the set of jobs that is
+ * to be bootstrapped in.
+ */
+ jobmgr_t jm = jobmgr_new(root_jobmgr, reqport, dport, false, NULL, true, MACH_PORT_NULL);
+ if (job_assumes(j, jm != NULL)) {
+ jm->properties |= BOOTSTRAP_PROPERTY_XPC_DOMAIN;
+ jm->shortdesc = "private";
+ kr = BOOTSTRAP_SUCCESS;
+ }
+
+ return kr;
+}
+
+kern_return_t
+xpc_domain_set_environment(job_t j, mach_port_t rp, mach_port_t bsport, mach_port_t excport, vm_offset_t ctx, mach_msg_type_number_t ctx_sz)
+{
+ if (!j) {
+ /* Due to the whacky nature of XPC service bootstrapping, we can end up
+ * getting this message long after the requesting process has gone away.
+ * See <rdar://problem/8593143>.
+ */
+ return BOOTSTRAP_UNKNOWN_SERVICE;
+ }
+
+ jobmgr_t jm = j->mgr;
+ if (!(jm->properties & BOOTSTRAP_PROPERTY_XPC_DOMAIN)) {
+ return BOOTSTRAP_NOT_PRIVILEGED;
+ }
+
+ if (jm->req_asport != MACH_PORT_NULL) {
+ return BOOTSTRAP_NOT_PRIVILEGED;
+ }
+
+ struct ldcred *ldc = runtime_get_caller_creds();
+ struct proc_bsdshortinfo proc;
+ if (proc_pidinfo(ldc->pid, PROC_PIDT_SHORTBSDINFO, 1, &proc, PROC_PIDT_SHORTBSDINFO_SIZE) == 0) {
+ if (errno != ESRCH) {
+ jobmgr_assumes(jm, errno == 0);
+ }
+
+ jm->error = errno;
+ jobmgr_remove(jm);
+ return BOOTSTRAP_NO_MEMORY;
+ }
+
+ if (!jobmgr_assumes(jm, audit_session_port(ldc->asid, &jm->req_asport) == 0)) {
+ jm->error = EPERM;
+ jobmgr_remove(jm);
+ job_log(j, LOG_ERR, "Failed to get port for ASID: %u", ldc->asid);
+ return BOOTSTRAP_NOT_PRIVILEGED;
+ }
+
+ (void)snprintf(jm->name_init, NAME_MAX, "com.apple.xpc.domain.%s[%i]", proc.pbsi_comm, ldc->pid);
+ strlcpy(jm->owner, proc.pbsi_comm, sizeof(jm->owner));
+ jm->req_bsport = bsport;
+ jm->req_excport = excport;
+ jm->req_rport = rp;
+ jm->req_ctx = ctx;
+ jm->req_ctx_sz = ctx_sz;
+ jm->req_pid = ldc->pid;
+ jm->req_euid = ldc->euid;
+ jm->req_egid = ldc->egid;
+ jm->req_asid = ldc->asid;
+
+ return KERN_SUCCESS;
+}
+
+kern_return_t
+xpc_domain_load_services(job_t j, vm_offset_t services_buff, mach_msg_type_number_t services_sz)
+{
+ if (!j) {
+ return BOOTSTRAP_UNKNOWN_SERVICE;
+ }
+
+ /* This is just for XPC domains (for now). */
+ if (!(j->mgr->properties & BOOTSTRAP_PROPERTY_XPC_DOMAIN)) {
+ return BOOTSTRAP_NOT_PRIVILEGED;
+ }
+ if (j->mgr->session_initialized) {
+ jobmgr_log(j->mgr, LOG_ERR, "Attempt to initialize an already-initialized XPC domain.");
+ return BOOTSTRAP_NOT_PRIVILEGED;
+ }
+
+ size_t offset = 0;
+ launch_data_t services = launch_data_unpack((void *)services_buff, services_sz, NULL, 0, &offset, NULL);
+ if (!jobmgr_assumes(j->mgr, services != NULL)) {
+ return BOOTSTRAP_NO_MEMORY;
+ }
+
+ size_t i = 0;
+ size_t c = launch_data_array_get_count(services);
+ for (i = 0; i < c; i++) {
+ job_t nj = NULL;
+ launch_data_t ploadi = launch_data_array_get_index(services, i);
+ if (!(nj = xpc_domain_import_service(j->mgr, ploadi))) {
+ /* If loading one job fails, just fail the whole thing. At this
+ * point, xpchelper should receive the failure and then just refuse
+ * to launch the application, since its XPC services could not be
+ * fully bootstrapped.
+ *
+ * Take care to not reference the job or its manager after this
+ * point.
+ */
+ if (errno == EINVAL) {
+ jobmgr_log(j->mgr, LOG_ERR, "Service at index is not valid: %lu", i);
+ } else if (errno == EEXIST) {
+ /* If we get back EEXIST, we know that the payload was a
+ * dictionary with a label. But, well, I guess it never hurts to
+ * check.
+ */
+ char *label = "(bogus)";
+ if (launch_data_get_type(ploadi) == LAUNCH_DATA_DICTIONARY) {
+ launch_data_t llabel = launch_data_dict_lookup(ploadi, LAUNCH_JOBKEY_LABEL);
+ if (launch_data_get_type(llabel) == LAUNCH_DATA_STRING) {
+ label = (char *)launch_data_get_string(llabel);
+ }
+ }
+ jobmgr_log(j->mgr, LOG_ERR, "Service name conflict: %s", label);
+ }
+
+ j->mgr->error = errno;
+ jobmgr_log(j->mgr, LOG_ERR, "Obliterating domain.");
+ jobmgr_remove(j->mgr);
+ break;
+ } else {
+ jobmgr_log(j->mgr, LOG_DEBUG, "Imported service %s", nj->label);
+ job_dispatch(nj, false);
+ }
+ }
+
+ kern_return_t result = BOOTSTRAP_NO_MEMORY;
+ if (i == c) {
+ j->mgr->session_initialized = true;
+ (void)jobmgr_assumes(j->mgr, xpc_call_wakeup(j->mgr->req_rport, BOOTSTRAP_SUCCESS) == KERN_SUCCESS);
+ j->mgr->req_rport = MACH_PORT_NULL;
+
+ /* Returning a failure code will destroy the message, whereas returning
+ * success will not, so we need to clean up here.
+ */
+ mig_deallocate(services_buff, services_sz);
+ result = BOOTSTRAP_SUCCESS;
+ }
+
+ return result;
+}
+
+kern_return_t
+xpc_domain_check_in(job_t j, mach_port_t *bsport, mach_port_t *sbsport, mach_port_t *excport, mach_port_t *asport, uint32_t *uid, uint32_t *gid, int32_t *asid, vm_offset_t *ctx, mach_msg_type_number_t *ctx_sz)
+{
+ if (!jobmgr_assumes(root_jobmgr, j != NULL)) {
+ return BOOTSTRAP_UNKNOWN_SERVICE;
+ }
+ jobmgr_t jm = j->mgr;
+ if (!(jm->properties & BOOTSTRAP_PROPERTY_XPC_DOMAIN)) {
+ return BOOTSTRAP_NOT_PRIVILEGED;
+ }
+
+ if (jm->req_asport == MACH_PORT_NULL) {
+ return BOOTSTRAP_NOT_PRIVILEGED;
+ }
+
+ *bsport = jm->req_bsport;
+ *sbsport = root_jobmgr->jm_port;
+ *excport = jm->req_excport;
+ *asport = jm->req_asport;
+ *uid = jm->req_euid;
+ *gid = jm->req_egid;
+ *asid = jm->req_asid;
+
+ *ctx = jm->req_ctx;
+ *ctx_sz = jm->req_ctx_sz;
+
+ return KERN_SUCCESS;
+}
+
+kern_return_t
+xpc_domain_get_service_name(job_t j, event_name_t name)
+{
+ if (!j) {
+ return BOOTSTRAP_NO_MEMORY;
+ }
+ if (!j->xpc_service) {
+ jobmgr_log(j->mgr, LOG_ERR, "Attempt to get service name by non-XPC service: %s", j->label);
+ return BOOTSTRAP_NOT_PRIVILEGED;
+ }
+
+ struct machservice * ms = SLIST_FIRST(&j->machservices);
+ if (!ms) {
+ jobmgr_log(j->mgr, LOG_ERR, "Attempt to get service name of job with no machservices: %s", j->label);
+ return BOOTSTRAP_UNKNOWN_SERVICE;
+ }
+
+ (void)strlcpy(name, ms->name, sizeof(event_name_t));
+ return BOOTSTRAP_SUCCESS;
+}
+#endif /* __LAUNCH_DISABLE_XPC_SUPPORT__ */
+
+kern_return_t
+xpc_events_get_channel_name(job_t j __attribute__((unused)), event_name_t stream __attribute__((unused)), uint64_t token __attribute__((unused)), event_name_t name __attribute__((unused)))
+{
+ return KERN_FAILURE;
+}
+
+kern_return_t
+xpc_events_get_event_name(job_t j, event_name_t stream, uint64_t token, event_name_t name)
+{
+ struct externalevent *event = externalevent_find(stream, token);
+ if (event && j->event_monitor) {
+ (void)strcpy(name, event->name);
+ } else {
+ event = NULL;
+ }
+
+ return event ? BOOTSTRAP_SUCCESS : BOOTSTRAP_UNKNOWN_SERVICE;
+}
+
+kern_return_t
+xpc_events_set_event(job_t j, event_name_t stream, event_name_t key, vm_offset_t event, mach_msg_type_number_t eventCnt)
+{
+ if (j->anonymous) {
+ return BOOTSTRAP_NOT_PRIVILEGED;
+ }
+
+ struct externalevent *eei = NULL;
+ LIST_FOREACH(eei, &j->events, job_le) {
+ if (strcmp(eei->name, key) == 0 && strcmp(eei->sys->name, stream) == 0) {
+ externalevent_delete(eei);
+ eventsystem_ping();
+ break;
+ }
+ }
+
+ bool success = false;
+ struct eventsystem *es = eventsystem_find(stream);
+ if (!es) {
+ es = eventsystem_new(stream);
+ (void)job_assumes(j, es != NULL);
+ }
+
+ if (es) {
+ size_t offset = 0;
+ launch_data_t unpacked = launch_data_unpack((void *)event, eventCnt, NULL, 0, &offset, 0);
+ if (unpacked && launch_data_get_type(unpacked) == LAUNCH_DATA_DICTIONARY) {
+ success = externalevent_new(j, es, key, unpacked);
+ }
+ }
+
+ if (!success) {
+ mig_deallocate(event, eventCnt);
+ }
+
+ return KERN_SUCCESS;
+}
+
+kern_return_t
+xpc_events_get_event(job_t j, event_name_t stream, event_name_t key, vm_offset_t *event, mach_msg_type_number_t *eventCnt)
+{
+ struct externalevent *eei = NULL;
+ LIST_FOREACH(eei, &j->events, job_le) {
+ if (strcmp(eei->name, key) == 0 && strcmp(eei->sys->name, stream) == 0) {
+ /* Big enough. */
+ *eventCnt = 10 * 1024;
+ mig_allocate(event, *eventCnt);
+
+ size_t sz = launch_data_pack(eei->event, (void *)*event, *eventCnt, NULL, NULL);
+ if (!job_assumes(j, sz != 0)) {
+ mig_deallocate(*event, *eventCnt);
+ return BOOTSTRAP_NO_MEMORY;
+ }
+
+ return BOOTSTRAP_SUCCESS;
+ }
+ }
+
+ return BOOTSTRAP_UNKNOWN_SERVICE;
+}
+
+struct machservice *
+xpc_events_find_channel(job_t j, event_name_t stream, mach_port_t *p)
+{
+ struct machservice *msi = NULL;
+ SLIST_FOREACH(msi, &j->machservices, sle) {
+ if (strcmp(stream, msi->name) == 0) {
+ break;
+ }
+ }
+
+ if (!msi) {
+ mach_port_t sp = MACH_PORT_NULL;
+ msi = machservice_new(j, stream, &sp, false);
+ if (job_assumes(j, msi)) {
+ /* Hack to keep this from being publicly accessible through
+ * bootstrap_look_up().
+ */
+ LIST_REMOVE(msi, name_hash_sle);
+ msi->event_channel = true;
+ *p = sp;
+
+ (void)job_dispatch(j, false);
+ } else {
+ errno = BOOTSTRAP_NO_MEMORY;
+ }
+ } else {
+ if (!msi->event_channel) {
+ job_log(j, LOG_ERR, "This job registered a MachService name identical to the requested event channel name: %s", stream);
+ msi = NULL;
+ errno = BOOTSTRAP_NAME_IN_USE;
+ } else {
+ *p = msi->port;
+ }
+ }
+
+ return msi;
+}
+
+kern_return_t
+xpc_events_channel_check_in(job_t j, event_name_t stream, uint64_t flags __attribute__((unused)), mach_port_t *p)
+{
+ struct machservice *ms = xpc_events_find_channel(j, stream, p);
+ if (ms) {
+ if (ms->isActive) {
+ job_log(j, LOG_ERR, "Attempt to check in on event channel multiple times: %s", stream);
+ *p = MACH_PORT_NULL;
+ errno = BOOTSTRAP_SERVICE_ACTIVE;
+ } else {
+ job_checkin(j);
+ machservice_request_notifications(ms);
+ errno = BOOTSTRAP_SUCCESS;
+ }