/*
* 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 <mach/mig.h>
#include <mach/port.h>
#include <mach/kern_return.h>
#include <mach/message.h>
#include <mach/mig_errors.h>
#include <mach/mach_notify.h>
#include <mach/ndr.h>
#include <mach/vm_param.h>
#include <mach/mach_vm_server.h>
#include <mach/mach_port_server.h>
#include <mach/mach_host_server.h>
#include <mach/host_priv_server.h>
#include <mach/clock_server.h>
#include <mach/memory_entry_server.h>
#include <mach/processor_server.h>
#include <mach/processor_set_server.h>
#include <mach/task_server.h>
#include <mach/mach_voucher_server.h>
#ifdef VM32_SUPPORT
#include <mach/vm32_map_server.h>
#endif
#include <mach/thread_act_server.h>
#include <mach/restartable_server.h>
#include <mach/exc_server.h>
#include <mach/mach_exc_server.h>
#include <mach/mach_eventlink_server.h>
#include <device/device_types.h>
#include <device/device_server.h>
#if CONFIG_USER_NOTIFICATION
#include <UserNotification/UNDReplyServer.h>
#endif
#if CONFIG_ARCADE
#include <mach/arcade_register_server.h>
#endif
#if CONFIG_AUDIT
#include <kern/audit_sessionport.h>
#endif
#include <kern/counter.h>
#include <kern/ipc_tt.h>
#include <kern/ipc_mig.h>
#include <kern/ipc_misc.h>
#include <kern/ipc_kobject.h>
#include <kern/host_notify.h>
#include <kern/misc_protos.h>
#if CONFIG_ARCADE
#include <kern/arcade.h>
#endif /* CONFIG_ARCADE */
#include <ipc/ipc_kmsg.h>
#include <ipc/ipc_policy.h>
#include <ipc/ipc_port.h>
#include <ipc/ipc_voucher.h>
#include <kern/sync_sema.h>
#include <kern/work_interval.h>
#include <kern/task_ident.h>
#if HYPERVISOR
#include <kern/hv_support.h>
#endif
#include <vm/vm_protos.h>
#include <security/mac_mach_internal.h>
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);
}