/* * Copyright (c) 2000-2020 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@ */ /* * @OSF_COPYRIGHT@ */ /* * Mach Operating System * Copyright (c) 1991,1990,1989 Carnegie Mellon University * All Rights Reserved. * * Permission to use, copy, modify and distribute this software and its * documentation is hereby granted, provided that both the copyright * notice and this permission notice appear in all copies of the * software, derivative works or modified versions, and any portions * thereof, and that both notices appear in supporting documentation. * * CARNEGIE MELLON ALLOWS FREE USE OF THIS SOFTWARE IN ITS "AS IS" * CONDITION. CARNEGIE MELLON DISCLAIMS ANY LIABILITY OF ANY KIND FOR * ANY DAMAGES WHATSOEVER RESULTING FROM THE USE OF THIS SOFTWARE. * * Carnegie Mellon requests users of this software to return to * * Software Distribution Coordinator or Software.Distribution@CS.CMU.EDU * School of Computer Science * Carnegie Mellon University * Pittsburgh PA 15213-3890 * * any improvements or extensions that they make and grant Carnegie Mellon * the rights to redistribute these changes. */ /* * NOTICE: This file was modified by McAfee Research in 2004 to introduce * support for mandatory and extensible security protections. This notice * is included in support of clause 2.2 (b) of the Apple Public License, * Version 2.0. * Copyright (c) 2005 SPARTA, Inc. */ /* */ /* * File: kern/ipc_kobject.c * Author: Rich Draves * Date: 1989 * * Functions for letting a port represent a kernel object. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #ifdef VM32_SUPPORT #include #endif #include #include #include #include #include #include #include #if CONFIG_USER_NOTIFICATION #include #endif #if CONFIG_ARCADE #include #endif #if CONFIG_AUDIT #include #endif #include #include #include #include #include #include #include #if CONFIG_ARCADE #include #endif /* CONFIG_ARCADE */ #include #include #include #include #include #include #include #if HYPERVISOR #include #endif #include #include extern char *proc_name_address(void *p); struct proc; extern int proc_pid(struct proc *p); typedef struct { mach_msg_id_t num; int kobjidx; mig_kern_routine_t kroutine; /* Kernel server routine */ unsigned int kreply_size; /* Size of kernel reply msg */ unsigned int kreply_desc_cnt; /* Number of descs in kernel reply msg */ } mig_hash_t; IPC_KOBJECT_DEFINE(IKOT_MEMORY_OBJECT); /* vestigial, no real instance */ #define MAX_MIG_ENTRIES 1031 #define MIG_HASH(x) (x) #define KOBJ_IDX_NOT_SET (-1) static SECURITY_READ_ONLY_LATE(mig_hash_t) mig_buckets[MAX_MIG_ENTRIES]; static SECURITY_READ_ONLY_LATE(int) mig_table_max_displ; SECURITY_READ_ONLY_LATE(int) mach_kobj_count; /* count of total number of kobjects */ ZONE_DEFINE_TYPE(ipc_kobject_label_zone, "ipc kobject labels", struct ipc_kobject_label, ZC_ZFREE_CLEARMEM); __startup_const static struct mig_kern_subsystem *mig_e[] = { (const struct mig_kern_subsystem *)&mach_vm_subsystem, (const struct mig_kern_subsystem *)&mach_port_subsystem, (const struct mig_kern_subsystem *)&mach_host_subsystem, (const struct mig_kern_subsystem *)&host_priv_subsystem, (const struct mig_kern_subsystem *)&clock_subsystem, (const struct mig_kern_subsystem *)&processor_subsystem, (const struct mig_kern_subsystem *)&processor_set_subsystem, (const struct mig_kern_subsystem *)&is_iokit_subsystem, (const struct mig_kern_subsystem *)&task_subsystem, (const struct mig_kern_subsystem *)&thread_act_subsystem, #ifdef VM32_SUPPORT (const struct mig_kern_subsystem *)&vm32_map_subsystem, #endif #if CONFIG_USER_NOTIFICATION (const struct mig_kern_subsystem *)&UNDReply_subsystem, #endif (const struct mig_kern_subsystem *)&mach_voucher_subsystem, (const struct mig_kern_subsystem *)&memory_entry_subsystem, (const struct mig_kern_subsystem *)&task_restartable_subsystem, (const struct mig_kern_subsystem *)&catch_exc_subsystem, (const struct mig_kern_subsystem *)&catch_mach_exc_subsystem, #if CONFIG_ARCADE (const struct mig_kern_subsystem *)&arcade_register_subsystem, #endif (const struct mig_kern_subsystem *)&mach_eventlink_subsystem, }; __startup_func static void mig_init(void) { unsigned int i, n = sizeof(mig_e) / sizeof(const struct mig_kern_subsystem *); int howmany; mach_msg_id_t j, pos, nentry, range; for (i = 0; i < n; i++) { range = mig_e[i]->end - mig_e[i]->start; if (!mig_e[i]->start || range < 0) { panic("the msgh_ids in mig_e[] aren't valid!"); } if (mig_e[i]->maxsize > KALLOC_SAFE_ALLOC_SIZE - MAX_TRAILER_SIZE) { panic("mig subsystem %d (%p) replies are too large (%d > %d)", mig_e[i]->start, mig_e[i], mig_e[i]->maxsize, KALLOC_SAFE_ALLOC_SIZE - MAX_TRAILER_SIZE); } for (j = 0; j < range; j++) { if (mig_e[i]->kroutine[j].kstub_routine) { /* Only put real entries in the table */ nentry = j + mig_e[i]->start; for (pos = MIG_HASH(nentry) % MAX_MIG_ENTRIES, howmany = 1; mig_buckets[pos].num; pos++, pos = pos % MAX_MIG_ENTRIES, howmany++) { if (mig_buckets[pos].num == nentry) { printf("message id = %d\n", nentry); panic("multiple entries with the same msgh_id"); } if (howmany == MAX_MIG_ENTRIES) { panic("the mig dispatch table is too small"); } } mig_buckets[pos].num = nentry; mig_buckets[pos].kroutine = mig_e[i]->kroutine[j].kstub_routine; if (mig_e[i]->kroutine[j].max_reply_msg) { mig_buckets[pos].kreply_size = mig_e[i]->kroutine[j].max_reply_msg; mig_buckets[pos].kreply_desc_cnt = mig_e[i]->kroutine[j].reply_descr_count; assert3u(mig_e[i]->kroutine[j].descr_count, <=, IPC_KOBJECT_DESC_MAX); assert3u(mig_e[i]->kroutine[j].reply_descr_count, <=, IPC_KOBJECT_RDESC_MAX); } else { /* * Allocating a larger-than-needed kmsg creates hole for * inlined kmsgs (IKM_TYPE_ALL_INLINED) during copyout. * Disallow that. */ panic("kroutine must have precise size %d %d", mig_e[i]->start, j); } mig_buckets[pos].kobjidx = KOBJ_IDX_NOT_SET; if (mig_table_max_displ < howmany) { mig_table_max_displ = howmany; } mach_kobj_count++; } } } /* 77417305: pad to allow for MIG routines removals/cleanups */ mach_kobj_count += 32; printf("mig_table_max_displ = %d mach_kobj_count = %d\n", mig_table_max_displ, mach_kobj_count); } STARTUP(MACH_IPC, STARTUP_RANK_FIRST, mig_init); /* * Do a hash table lookup for given msgh_id. Return 0 * if not found. */ static mig_hash_t * find_mig_hash_entry(int msgh_id) { unsigned int i = (unsigned int)MIG_HASH(msgh_id); int max_iter = mig_table_max_displ; mig_hash_t *ptr; do { ptr = &mig_buckets[i++ % MAX_MIG_ENTRIES]; } while (msgh_id != ptr->num && ptr->num && --max_iter); if (!ptr->kroutine || msgh_id != ptr->num) { ptr = (mig_hash_t *)0; } return ptr; } /* * Routine: ipc_kobject_reply_status * * Returns the error/success status from a given kobject call reply message. * * Contract for KernelServer MIG routines is as follows: * * (1) If reply header has complex bit set, kernel server implementation routine * must have implicitly returned KERN_SUCCESS. * * (2) Otherwise we can always read RetCode from after the header. This is not * obvious to see, and is discussed below by case. * * MIG can return three types of replies from KernelServer routines. * * (A) Complex Reply (i.e. with Descriptors) * * E.g.: thread_get_exception_ports() * * If complex bit is set, we can deduce the call is successful since the bit * is set at the very end. * If complex bit is not set, we must have returned from MIG_RETURN_ERROR. * MIG writes RetCode to immediately after the header, and we know this is * safe to do for all kmsg layouts. (See discussion in ipc_kmsg_server_internal()). * * (B) Simple Reply with Out Params * * E.g.: thread_get_states() * * If the call failed, we return from MIG_RETURN_ERROR, which writes RetCode * to immediately after the header. * If the call succeeded, MIG writes RetCode as KERN_SUCCESS to USER DATA * buffer. *BUT* since the region after header is always initialized with * KERN_SUCCESS, reading from there gives us the same result. We rely on * this behavior to not make a special case. * * (C) Simple Reply without Out Params * * E.g.: thread_set_states() * * For this type of MIG routines we always allocate a mig_reply_error_t * as reply kmsg, which fits inline in kmsg. RetCode can be found after * header, and can be KERN_SUCCESS or otherwise a failure code. */ static kern_return_t ipc_kobject_reply_status(ipc_kmsg_t reply) { mach_msg_header_t *hdr = ikm_header(reply); if (hdr->msgh_bits & MACH_MSGH_BITS_COMPLEX) { return KERN_SUCCESS; } return ((mig_reply_error_t *)hdr)->RetCode; } static void ipc_kobject_set_reply_error_status( ipc_kmsg_t reply, kern_return_t kr) { mig_reply_error_t *error = (mig_reply_error_t *)ikm_header(reply); assert(!(error->Head.msgh_bits & MACH_MSGH_BITS_COMPLEX)); error->RetCode = kr; } /* * Routine: ipc_kobject_set_kobjidx * Purpose: * Set the index for the kobject filter * mask for a given message ID. */ kern_return_t ipc_kobject_set_kobjidx( int msgh_id, int index) { mig_hash_t *ptr = find_mig_hash_entry(msgh_id); if (ptr == (mig_hash_t *)0) { return KERN_INVALID_ARGUMENT; } assert(index < mach_kobj_count); ptr->kobjidx = index; return KERN_SUCCESS; } static void ipc_kobject_init_reply( ipc_kmsg_t reply, const ipc_kmsg_t request, kern_return_t kr) { mach_msg_header_t *req_hdr = ikm_header(request); mach_msg_header_t *reply_hdr = ikm_header(reply); #define InP ((mach_msg_header_t *) req_hdr) #define OutP ((mig_reply_error_t *) reply_hdr) OutP->Head.msgh_size = sizeof(mig_reply_error_t); OutP->Head.msgh_bits = MACH_MSGH_BITS_SET(MACH_MSGH_BITS_LOCAL(InP->msgh_bits), 0, 0, 0); OutP->Head.msgh_remote_port = InP->msgh_local_port; OutP->Head.msgh_local_port = MACH_PORT_NULL; OutP->Head.msgh_voucher_port = MACH_PORT_NULL; OutP->Head.msgh_id = InP->msgh_id + 100; OutP->NDR = NDR_record; OutP->RetCode = kr; #undef InP #undef OutP } static void ipc_kobject_init_new_reply( ipc_kmsg_t new_reply, const ipc_kmsg_t old_reply, kern_return_t kr) { mach_msg_header_t *new_hdr = ikm_header(new_reply); mach_msg_header_t *old_hdr = ikm_header(old_reply); #define InP ((mig_reply_error_t *) old_hdr) #define OutP ((mig_reply_error_t *) new_hdr) OutP->Head.msgh_size = sizeof(mig_reply_error_t); OutP->Head.msgh_bits = InP->Head.msgh_bits & ~MACH_MSGH_BITS_COMPLEX; OutP->Head.msgh_remote_port = InP->Head.msgh_remote_port; OutP->Head.msgh_local_port = MACH_PORT_NULL; OutP->Head.msgh_voucher_port = MACH_PORT_NULL; OutP->Head.msgh_id = InP->Head.msgh_id; OutP->NDR = InP->NDR; OutP->RetCode = kr; #undef InP #undef OutP } static ipc_kmsg_t ipc_kobject_alloc_mig_error(void) { ipc_kmsg_alloc_flags_t flags = IPC_KMSG_ALLOC_KERNEL | IPC_KMSG_ALLOC_ZERO | IPC_KMSG_ALLOC_ALL_INLINE | IPC_KMSG_ALLOC_NOFAIL; return ipc_kmsg_alloc(sizeof(mig_reply_error_t), 0, 0, flags); } /* * Routine: ipc_kobject_server_internal * Purpose: * Handle a message sent to the kernel. * Generates a reply message. * Version for Untyped IPC. * Conditions: * Nothing locked. */ static kern_return_t ipc_kobject_server_internal( __unused ipc_port_t port, ipc_kmsg_t request, ipc_kmsg_t *replyp) { int request_msgh_id; ipc_kmsg_t reply = IKM_NULL; mach_msg_size_t reply_size, reply_desc_cnt; mig_hash_t *ptr; mach_msg_header_t *req_hdr, *reply_hdr; void *req_data, *reply_data; mach_msg_max_trailer_t *req_trailer; thread_ro_t tro = current_thread_ro(); task_t curtask = tro->tro_task; struct proc *curproc = tro->tro_proc; req_hdr = ikm_header(request); req_data = ikm_udata_from_header(request); req_trailer = ipc_kmsg_get_trailer(request); request_msgh_id = req_hdr->msgh_id; /* Find corresponding mig_hash entry, if any */ ptr = find_mig_hash_entry(request_msgh_id); /* Get the reply_size. */ if (ptr == (mig_hash_t *)0) { reply_size = sizeof(mig_reply_error_t); reply_desc_cnt = 0; } else { reply_size = ptr->kreply_size; reply_desc_cnt = ptr->kreply_desc_cnt; } assert(reply_size >= sizeof(mig_reply_error_t)); /* * MIG should really assure no data leakage - * but until it does, pessimistically zero the * whole reply buffer. */ reply = ipc_kmsg_alloc(reply_size, 0, reply_desc_cnt, IPC_KMSG_ALLOC_KERNEL | IPC_KMSG_ALLOC_ZERO | IPC_KMSG_ALLOC_NOFAIL); /* reply can be non-linear */ if (ptr == (mig_hash_t *)0) { #if DEVELOPMENT || DEBUG printf("ipc_kobject_server: bogus kernel message, id=%d\n", req_hdr->msgh_id); #endif /* DEVELOPMENT || DEBUG */ _MIG_MSGID_INVALID(req_hdr->msgh_id); ipc_kobject_init_reply(reply, request, MIG_BAD_ID); *replyp = reply; return KERN_SUCCESS; } /* * We found the routine to call. Call it to perform the kernel function. */ assert(ptr != (mig_hash_t *)0); reply_hdr = ikm_header(reply); /* reply is allocated by kernel. non-zero desc count means complex msg */ reply_data = ikm_udata(reply, reply_desc_cnt, (reply_desc_cnt > 0)); /* * Reply can be of layout IKM_TYPE_ALL_INLINED, IKM_TYPE_UDATA_OOL, * or IKM_TYPE_ALL_OOL, each of which guarantees kernel/user data segregation. * * Here is the trick: In each case, there _must_ be enough space in * the kdata (header) buffer in `reply` to hold a mig_reply_error_t. */ assert(reply->ikm_type != IKM_TYPE_KDATA_OOL); assert((vm_offset_t)reply_hdr + sizeof(mig_reply_error_t) <= ikm_kdata_end(reply)); /* * Discussion by case: * * (1) IKM_TYPE_ALL_INLINED * - IKM_BIG_MSG_SIZE is large enough for mig_reply_error_t * (2) IKM_TYPE_UDATA_OOL * - IKM_SMALL_MSG_SIZE is large enough for mig_reply_error_t * (3) IKM_TYPE_ALL_OOL * - This layout is only possible if kdata (header + descs) doesn't fit * in IKM_SMALL_MSG_SIZE. So we must have at least one descriptor * following the header, which is enough to fit mig_reply_error_t. */ static_assert(sizeof(mig_reply_error_t) < IKM_BIG_MSG_SIZE); static_assert(sizeof(mig_reply_error_t) < sizeof(mach_msg_base_t) + 1 * sizeof(mach_msg_kdescriptor_t)); /* * Therefore, we can temporarily treat `reply` as a *simple* message that * contains NDR Record + RetCode immediately after the header (which overlaps * with descriptors, if the reply msg is supposed to be complex). * * In doing so we save having a separate allocation specifically for errors. */ ipc_kobject_init_reply(reply, request, KERN_SUCCESS); /* Check if the kobject call should be filtered */ #if CONFIG_MACF int idx = ptr->kobjidx; uint8_t *filter_mask = task_get_mach_kobj_filter_mask(curtask); /* Check kobject mig filter mask, if exists. */ if (filter_mask != NULL && (idx == KOBJ_IDX_NOT_SET || !bitstr_test(filter_mask, idx)) && mac_task_kobj_msg_evaluate != NULL) { /* No index registered by Sandbox, or not in filter mask: evaluate policy. */ kern_return_t kr = mac_task_kobj_msg_evaluate(curproc, request_msgh_id, idx); if (kr != KERN_SUCCESS) { ipc_kobject_set_reply_error_status(reply, kr); goto skip_kobjcall; } } #endif /* CONFIG_MACF */ __BeforeKobjectServerTrace(idx); /* See contract in header doc for ipc_kobject_reply_status() */ (*ptr->kroutine)(req_hdr, req_data, req_trailer, reply_hdr, reply_data); __AfterKobjectServerTrace(idx); #if CONFIG_MACF skip_kobjcall: #endif counter_inc(&kernel_task->messages_received); kern_return_t reply_status = ipc_kobject_reply_status(reply); if (reply_status == MIG_NO_REPLY) { /* * The server function will send a reply message * using the reply port right, which it has saved. */ ipc_kmsg_free(reply); reply = IKM_NULL; } else if (reply_status != KERN_SUCCESS && reply_size > sizeof(mig_reply_error_t)) { assert(ikm_header(reply)->msgh_size == sizeof(mig_reply_error_t)); /* * MIG returned an error, and the original kmsg we allocated for reply * is oversized. Deallocate it and allocate a smaller, proper kmsg * that fits mig_reply_error_t snuggly. * * We must do so because we used the trick mentioned above which (depending * on the kmsg layout) may cause payload in mig_reply_error_t to overlap * with kdata buffer meant for descriptors. * * This will mess with ikm_kdata_size() calculation down the line so * reallocate a new buffer immediately here. */ ipc_kmsg_t new_reply = ipc_kobject_alloc_mig_error(); ipc_kobject_init_new_reply(new_reply, reply, reply_status); /* MIG contract: If status is not KERN_SUCCESS, reply must be simple. */ assert(!(ikm_header(reply)->msgh_bits & MACH_MSGH_BITS_COMPLEX)); assert(ikm_header(reply)->msgh_local_port == MACH_PORT_NULL); assert(ikm_header(reply)->msgh_voucher_port == MACH_PORT_NULL); /* So we can simply free the original reply message. */ ipc_kmsg_free(reply); reply = new_reply; } *replyp = reply; return KERN_SUCCESS; } /* * Routine: ipc_kobject_server * Purpose: * Handle a message sent to the kernel. * Generates a reply message. * Version for Untyped IPC. * * Ownership of the incoming rights (from the request) * are transferred on success (wether a reply is made or not). * * Conditions: * Nothing locked. */ ipc_kmsg_t ipc_kobject_server( ipc_port_t port, ipc_kmsg_t request, mach_msg_option64_t option __unused) { mach_msg_header_t *req_hdr = ikm_header(request); #if DEVELOPMENT || DEBUG const int request_msgh_id = req_hdr->msgh_id; #endif ipc_port_t request_voucher_port; ipc_kmsg_t reply = IKM_NULL; mach_msg_header_t *reply_hdr; kern_return_t kr; ipc_kmsg_trace_send(request, option); if (ip_type(port) == IKOT_UEXT_OBJECT) { kr = uext_server(port, request, &reply); } else { kr = ipc_kobject_server_internal(port, request, &reply); assert(kr == KERN_SUCCESS); } if (kr != KERN_SUCCESS) { assert(kr != MACH_SEND_TIMED_OUT && kr != MACH_SEND_INTERRUPTED && kr != MACH_SEND_INVALID_DEST); assert(reply == IKM_NULL); /* convert the server error into a MIG error */ reply = ipc_kobject_alloc_mig_error(); ipc_kobject_init_reply(reply, request, kr); } counter_inc(&kernel_task->messages_sent); /* * Destroy destination. The following code differs from * ipc_object_destroy in that we release the send-once * right instead of generating a send-once notification * (which would bring us here again, creating a loop). * It also differs in that we only expect send or * send-once rights, never receive rights. */ switch (MACH_MSGH_BITS_REMOTE(req_hdr->msgh_bits)) { case MACH_MSG_TYPE_PORT_SEND: ipc_port_release_send(req_hdr->msgh_remote_port); break; case MACH_MSG_TYPE_PORT_SEND_ONCE: ipc_port_release_sonce(req_hdr->msgh_remote_port); break; default: panic("ipc_kobject_server: strange destination rights"); } /* * Destroy voucher. The kernel MIG servers never take ownership * of vouchers sent in messages. Swallow any such rights here. */ request_voucher_port = ipc_kmsg_get_voucher_port(request); if (IP_VALID(request_voucher_port)) { assert(MACH_MSG_TYPE_PORT_SEND == MACH_MSGH_BITS_VOUCHER(req_hdr->msgh_bits)); ipc_port_release_send(request_voucher_port); ipc_kmsg_clear_voucher_port(request); } if (reply == IKM_NULL || ipc_kobject_reply_status(reply) == KERN_SUCCESS) { /* * The server function is responsible for the contents * of the message. The reply port right is moved * to the reply message, and we have deallocated * the destination port right, so we just need * to free the kmsg. */ ipc_kmsg_free(request); } else { /* * The message contents of the request are intact. * Remote port has been released above. Do not destroy * the reply port right either, which is needed in the reply message. */ ipc_kmsg_destroy(request, IPC_KMSG_DESTROY_SKIP_LOCAL | IPC_KMSG_DESTROY_SKIP_REMOTE); } if (reply != IKM_NULL) { reply_hdr = ikm_header(reply); ipc_port_t reply_port = reply_hdr->msgh_remote_port; if (!IP_VALID(reply_port)) { /* * Can't queue the reply message if the destination * (the reply port) isn't valid. */ ipc_kmsg_destroy(reply, IPC_KMSG_DESTROY_NOT_SIGNED); reply = IKM_NULL; } else if (ip_in_space_noauth(reply_port, ipc_space_kernel)) { /* do not lock reply port, use raw pointer comparison */ /* * Don't send replies to kobject kernel ports. */ #if DEVELOPMENT || DEBUG printf("%s: refusing to send reply to kobject %d port (id:%d)\n", __func__, ip_type(reply_port), request_msgh_id); #endif /* DEVELOPMENT || DEBUG */ ipc_kmsg_destroy(reply, IPC_KMSG_DESTROY_NOT_SIGNED); reply = IKM_NULL; } } return reply; } static inline void ipc_kobject_set_raw( ipc_port_t port, ipc_kobject_type_t type, ipc_kobject_t kobject) { uintptr_t *store = &port->ip_kobject; #if __has_feature(ptrauth_calls) type ^= OS_PTRAUTH_DISCRIMINATOR("ipc_port.ip_kobject"); kobject = ptrauth_sign_unauthenticated(kobject, ptrauth_key_process_independent_data, ptrauth_blend_discriminator(store, type)); #else (void)type; #endif // __has_feature(ptrauth_calls) *store = (uintptr_t)kobject; } /* * Routine: ipc_kobject_get_raw * Purpose: * Returns the kobject pointer of a specified port. * * This returns the current value of the kobject pointer, * without any validation (the caller is expected to do * the validation it needs). * * Conditions: * The port is a kobject of the proper type. */ __header_always_inline ipc_kobject_t ipc_kobject_get_raw( ipc_port_t port, ipc_kobject_type_t type) { uintptr_t *store = &port->ip_kobject; ipc_kobject_t kobject = (ipc_kobject_t)*store; #if __has_feature(ptrauth_calls) type ^= OS_PTRAUTH_DISCRIMINATOR("ipc_port.ip_kobject"); kobject = ptrauth_auth_data(kobject, ptrauth_key_process_independent_data, ptrauth_blend_discriminator(store, type)); #else (void)type; #endif // __has_feature(ptrauth_calls) return kobject; } __abortlike static void ipc_kobject_require_panic( ipc_port_t port, ipc_kobject_t kobject, ipc_kobject_type_t kotype) { if (ip_type(port) != kotype) { panic("port %p: invalid kobject type, got %d wanted %d", port, ip_type(port), kotype); } panic("port %p: invalid kobject, got %p wanted %p", port, ipc_kobject_get_raw(port, kotype), kobject); } __header_always_inline void ipc_kobject_require( ipc_port_t port, ipc_kobject_t kobject, ipc_kobject_type_t kotype) { ipc_kobject_t cur; if (ip_type(port) != kotype) { ipc_kobject_require_panic(port, kobject, kotype); } cur = ipc_kobject_get_raw(port, kotype); if (cur && cur != kobject) { ipc_kobject_require_panic(port, kobject, kotype); } } /* * Routine: ipc_kobject_get_locked * Purpose: * Returns the kobject pointer of a specified port, * for an expected type. * * Returns IKO_NULL if the port isn't active. * * This function may be used when: * - the port lock is held * - the kobject association stays while there * are any outstanding rights. * * Conditions: * The port is a kobject of the proper type. */ ipc_kobject_t ipc_kobject_get_locked(ipc_port_t port, ipc_kobject_type_t type) { ipc_kobject_t kobject = IKO_NULL; if (ip_active(port) && ip_type(port) == type) { kobject = ipc_kobject_get_raw(port, type); } return kobject; } /* * Routine: ipc_kobject_get_stable * Purpose: * Returns the kobject pointer of a specified port, * for an expected type, for types where the port/kobject * association is permanent. * * Returns IKO_NULL if the port isn't active. * * Conditions: * The port is a kobject of the proper type. */ ipc_kobject_t ipc_kobject_get_stable(ipc_port_t port, ipc_kobject_type_t type) { assert(ipc_policy(type)->pol_kobject_stable); return ipc_kobject_get_locked(port, type); } ipc_object_label_t ipc_kobject_label_alloc( ipc_object_type_t otype, ipc_label_t label_tag, ipc_port_t alt_port) { ipc_kobject_label_t kolabel; kolabel = zalloc_flags(ipc_kobject_label_zone, Z_WAITOK | Z_ZERO | Z_NOFAIL); kolabel->ikol_label = label_tag; kolabel->ikol_alt_port = alt_port; return IPC_OBJECT_LABEL(otype, .iol_kobject = kolabel); } void ipc_kobject_label_free(ipc_object_label_t label) { assert(label.iol_kobject->ikol_alt_port == IP_NULL); zfree(ipc_kobject_label_zone, label.iol_kobject); } /* * Routine: ipc_kobject_alloc_port * Purpose: * Allocate a kobject port in the kernel space of the specified type. * * This function never fails. * * Conditions: * No locks held (memory is allocated) */ ipc_port_t ipc_kobject_alloc_port( ipc_kobject_t kobject, ipc_object_label_t label, ipc_kobject_alloc_options_t options) { ipc_port_t port; port = ipc_port_alloc_special(ipc_space_kernel, label, IP_INIT_NONE); if (options & IPC_KOBJECT_ALLOC_MAKE_SEND) { ipc_port_make_send_any_locked(port); } ipc_kobject_set_raw(port, label.io_type, kobject); ip_mq_unlock(port); return port; } bool ipc_kobject_make_send_lazy_alloc_port( ipc_port_t *port_store, ipc_kobject_t kobject, ipc_kobject_type_t type) { ipc_port_t port, previous; bool was_armed = false; assert(ipc_policy(type)->pol_kobject_no_senders && ipc_policy(type)->pol_kobject_stable); port = os_atomic_load(port_store, dependency); if (!IP_VALID(port)) { port = ipc_kobject_alloc_port(kobject, type, IPC_KOBJECT_ALLOC_MAKE_SEND); if (os_atomic_cmpxchgv(port_store, IP_NULL, port, &previous, release)) { return true; } /* * undo IPC_KOBJECT_ALLOC_MAKE_SEND */ port->ip_mscount = 0; port->ip_srights = 0; ip_release_live(port); ipc_kobject_dealloc_port(port, 0, type); port = previous; } ip_mq_lock(port); ipc_port_make_send_any_locked(port); was_armed = (port->ip_srights == 1); ip_mq_unlock(port); return was_armed; } bool ipc_kobject_is_mscount_current_locked(ipc_port_t port, mach_port_mscount_t mscount) { return ip_active(port) && port->ip_srights == 0 && port->ip_mscount == mscount; } bool ipc_kobject_is_mscount_current(ipc_port_t port, mach_port_mscount_t mscount) { bool is_last; ip_mq_lock(port); is_last = ipc_kobject_is_mscount_current_locked(port, mscount); ip_mq_unlock(port); return is_last; } kern_return_t ipc_typed_port_copyin_send( ipc_space_t space, mach_port_name_t name, ipc_kobject_type_t kotype, ipc_port_t *portp) { kern_return_t kr; kr = ipc_object_copyin(space, name, MACH_MSG_TYPE_COPY_SEND, IPC_OBJECT_COPYIN_FLAGS_ALLOW_IMMOVABLE_SEND, IPC_COPYIN_KERNEL_DESTINATION, NULL, portp); if (kr != KERN_SUCCESS) { *portp = IP_NULL; return kr; } if (kotype != IOT_ANY && IP_VALID(*portp) && ip_type(*portp) != kotype) { ipc_port_release_send(*portp); *portp = IP_NULL; return KERN_INVALID_CAPABILITY; } return KERN_SUCCESS; } ipc_port_t ipc_kobject_copy_send( ipc_port_t port, ipc_kobject_t kobject, ipc_kobject_type_t kotype) { ipc_port_t sright = port; if (IP_VALID(port)) { ip_mq_lock(port); if (ip_active(port)) { ipc_kobject_require(port, kobject, kotype); ipc_port_copy_send_any_locked(port); } else { sright = IP_DEAD; } ip_mq_unlock(port); } return sright; } ipc_port_t ipc_kobject_make_send( ipc_port_t port, ipc_kobject_t kobject, ipc_kobject_type_t kotype) { ipc_port_t sright = port; if (IP_VALID(port)) { ip_mq_lock(port); if (ip_active(port)) { ipc_kobject_require(port, kobject, kotype); ipc_port_make_send_any_locked(port); } else { sright = IP_DEAD; } ip_mq_unlock(port); } return sright; } void ipc_typed_port_release_send( ipc_port_t port, ipc_kobject_type_t kotype) { if (kotype != IOT_ANY && IP_VALID(port) && ip_type(port) != kotype) { ipc_kobject_require_panic(port, IKO_NULL, kotype); } ipc_port_release_send(port); } static inline ipc_kobject_t ipc_kobject_disable_internal( ipc_port_t port, ipc_kobject_label_t kolabel, ipc_kobject_type_t type) { ipc_kobject_t kobject = ipc_kobject_get_raw(port, type); ipc_kobject_set_raw(port, type, IKO_NULL); if (kolabel) { kolabel->ikol_alt_port = IP_NULL; } return kobject; } /* * Routine: ipc_kobject_dealloc_port_and_unlock * Purpose: * Destroys a port allocated with any of the ipc_kobject_alloc* * functions. * * This will atomically: * - make the port inactive, * - optionally check the make send count * - disable (nil-out) the kobject pointer for kobjects without * a destroy callback. * * The port will retain its kobject-ness and kobject type. * * * Returns: * The kobject pointer that was set prior to this call * (possibly NULL if the kobject was already disabled). * * Conditions: * The port is active and locked. * On return the port is inactive and unlocked. */ __abortlike static void __ipc_kobject_bad_type_panic(ipc_port_t port, ipc_kobject_type_t type) { panic("port %p of type %d, expecting %d", port, ip_type(port), type); } __abortlike static void __ipc_kobject_dealloc_bad_mscount_panic( ipc_port_t port, uint64_t mscount, ipc_kobject_type_t type) { panic("unexpected make-send count: %p[%d], %d, %lld", port, type, port->ip_mscount, mscount); } __abortlike static void __ipc_kobject_dealloc_bad_srights_panic( ipc_port_t port, ipc_kobject_type_t type) { panic("unexpected send right count: %p[%d], %d", port, type, port->ip_srights); } ipc_kobject_t ipc_kobject_dealloc_port_and_unlock( ipc_port_t port, uint64_t mscount, ipc_kobject_type_t type) { ipc_kobject_t kobject = IKO_NULL; ipc_object_policy_t pol = ipc_policy(type); ipc_object_label_t label = ip_label_get(port, type); ipc_release_assert(io_state_active(label.io_state)); if (label.io_type != type) { __ipc_kobject_bad_type_panic(port, type); } if (mscount != IPC_KOBJECT_NO_MSCOUNT && port->ip_mscount != mscount) { __ipc_kobject_dealloc_bad_mscount_panic(port, mscount, type); } if (port->ip_srights && (mscount != IPC_KOBJECT_NO_MSCOUNT || pol->pol_kobject_stable)) { __ipc_kobject_dealloc_bad_srights_panic(port, type); } kobject = ipc_kobject_disable_internal(port, label.iol_kobject, type); ip_label_put(port, &label); ipc_port_destroy(port); return kobject; } /* * Routine: ipc_kobject_dealloc_port * Purpose: * Destroys a port allocated with any of the ipc_kobject_alloc* * functions. * * This will atomically: * - make the port inactive, * - optionally check the make send count * - disable (nil-out) the kobject pointer for kobjects without * a destroy callback. * * The port will retain its kobject-ness and kobject type. * * * Returns: * The kobject pointer that was set prior to this call * (possibly NULL if the kobject was already disabled). * * Conditions: * Nothing is locked. * The port is active. * On return the port is inactive. */ ipc_kobject_t ipc_kobject_dealloc_port( ipc_port_t port, uint64_t mscount, ipc_kobject_type_t type) { ip_mq_lock(port); return ipc_kobject_dealloc_port_and_unlock(port, mscount, type); } /* * Routine: ipc_kobject_enable * Purpose: * Make a port represent a kernel object of the given type. * The caller is responsible for handling refs for the * kernel object, if necessary. * Conditions: * Nothing locked. * The port must be active. */ void ipc_kobject_enable( ipc_port_t port, ipc_kobject_t kobject, ipc_kobject_type_t type) { assert(!ipc_policy(type)->pol_kobject_stable); ip_mq_lock(port); require_ip_active(port); if (ip_type(port) != type) { __ipc_kobject_bad_type_panic(port, type); } ipc_kobject_set_raw(port, type, kobject); ip_mq_unlock(port); } /* * Routine: ipc_kobject_disable_locked * Purpose: * Clear the kobject pointer for a port. * Conditions: * port is locked. * Returns the current kobject pointer. */ ipc_kobject_t ipc_kobject_disable_locked(ipc_port_t port, ipc_kobject_type_t type) { ipc_object_label_t label; ipc_kobject_t kobject; label = ip_label_get(port); if (io_state_active(label.io_state)) { assert(!ipc_policy(type)->pol_kobject_stable); } if (label.io_type != type) { __ipc_kobject_bad_type_panic(port, type); } kobject = ipc_kobject_disable_internal(port, label.iol_kobject, type); ip_label_put(port, &label); return kobject; } /* * Routine: ipc_kobject_disable * Purpose: * Clear the kobject pointer for a port. * Conditions: * Nothing locked. * Returns the current kobject pointer. */ ipc_kobject_t ipc_kobject_disable(ipc_port_t port, ipc_kobject_type_t type) { ipc_kobject_t kobject; ip_mq_lock(port); kobject = ipc_kobject_disable_locked(port, type); ip_mq_unlock(port); return kobject; } /* * Routine: ipc_kobject_notify_send_once_and_unlock * Purpose: * Handles a send once notifications * sent to a kobject. * * A send-once port reference is consumed. * * Conditions: * Port is locked. */ void ipc_kobject_notify_send_once_and_unlock( ipc_port_t port) { /* * drop the send once right while we hold the port lock. * we will keep a port reference while we run the possible * callouts to kobjects. * * This a simplified version of ipc_port_release_sonce() * since kobjects can't be special reply ports. */ assert(!ip_is_special_reply_port(port)); ip_sorights_dec(port); ip_mq_unlock(port); /* * because there's very few consumers, * the code here isn't generic as it's really not worth it. */ switch (ip_type(port)) { case IKOT_TASK_RESUME: task_suspension_send_once(port); break; default: break; } ip_release(port); } /* * Routine: ipc_kobject_label_substitute_task_read * Purpose: * Substitute a task read port for its immovable * control equivalent when the receiver is that task. * Conditions: * Space is write locked and active. * Port is locked and active. * Returns: * - IP_NULL port if no substitution is to be done * - a valid port if a substitution needs to happen */ static ipc_port_t ipc_kobject_label_substitute_task_read( ipc_space_t space, ipc_kobject_label_t kolabel, ipc_port_t port) { ipc_port_t subst = IP_NULL; task_t task = ipc_kobject_get_raw(port, IKOT_TASK_READ); if (task != TASK_NULL && task == space->is_task) { if ((subst = kolabel->ikol_alt_port)) { return subst; } } return IP_NULL; } /* * Routine: ipc_kobject_label_substitute_thread_read * Purpose: * Substitute a thread read port for its immovable * control equivalent when it belongs to the receiver task. * Conditions: * Space is write locked and active. * Port is locked and active. * Returns: * - IP_NULL port if no substitution is to be done * - a valid port if a substitution needs to happen */ static ipc_port_t ipc_kobject_label_substitute_thread_read( ipc_space_t space, ipc_kobject_label_t kolabel, ipc_port_t port) { ipc_port_t subst = IP_NULL; thread_t thread = ipc_kobject_get_raw(port, IKOT_THREAD_READ); if (thread != THREAD_NULL && space->is_task == get_threadtask(thread)) { if ((subst = kolabel->ikol_alt_port) != IP_NULL) { return subst; } } return IP_NULL; } /* * Routine: ipc_kobject_label_check_or_substitute * Purpose: * Check to see if the space is allowed to possess * a right for the given port. In order to qualify, * the space label must contain all the privileges * listed in the port/kobject label. * * Conditions: * Space is write locked and active. * Port is locked and active. * * Returns: * Whether the copyout is authorized. * * If a port substitution is requested, the space is unlocked, * the port is unlocked and its "right" consumed. * * As of now, substituted ports only happen for send rights. */ bool ipc_kobject_label_check_or_substitute( ipc_space_t space, ipc_port_t port, ipc_object_label_t *label, mach_msg_type_name_t msgt_name, ipc_port_t *subst_portp) { ipc_kobject_label_t kolabel = label->iol_kobject; ipc_label_t label_tag = kolabel->ikol_label; assert(is_active(space)); assert(ip_active(port)); *subst_portp = IP_NULL; /* Never OK to copyout the receive right for a labeled kobject */ if (msgt_name == MACH_MSG_TYPE_PORT_RECEIVE) { panic("attempted receive right copyout for labeled kobject"); } if ((label_tag & IPC_LABEL_SUBST_MASK)) { ipc_port_t subst = IP_NULL; if (msgt_name != MACH_MSG_TYPE_PORT_SEND) { return false; } switch (label_tag & IPC_LABEL_SUBST_MASK) { case IPC_LABEL_SUBST_TASK_READ: subst = ipc_kobject_label_substitute_task_read(space, kolabel, port); break; case IPC_LABEL_SUBST_THREAD_READ: subst = ipc_kobject_label_substitute_thread_read(space, kolabel, port); break; default: panic("unexpected label tag: %llx", label_tag); } if (subst != IP_NULL) { ip_reference(subst); is_write_unlock(space); /* * We do not hold a proper send right on `subst`, * only a reference. * * Because of how thread/task termination works, * there is no guarantee copy_send() would work, * so we need to make_send(). * * We can do that because ports tagged with * IPC_LABEL_SUBST_{THREAD,TASK} do not use * the no-senders notification. */ ip_label_put(port, label); ipc_port_release_send_and_unlock(port); /* no check: dPAC integrity */ port = ipc_port_make_send_any(subst); ip_release(subst); *subst_portp = port; return true; } } return (label_tag & space->is_label & IPC_LABEL_SPACE_MASK) == (label_tag & IPC_LABEL_SPACE_MASK); }