X-Git-Url: https://git.saurik.com/apple/xnu.git/blobdiff_plain/c910b4d9d2451126ae3917b931cd4390c11e1d52..4ba76501152d51ccb5647018f3192c6096367d48:/osfmk/ipc/ipc_right.c diff --git a/osfmk/ipc/ipc_right.c b/osfmk/ipc/ipc_right.c index e7ffd94ec..00a256e17 100644 --- a/osfmk/ipc/ipc_right.c +++ b/osfmk/ipc/ipc_right.c @@ -2,7 +2,7 @@ * Copyright (c) 2000-2007 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 @@ -11,10 +11,10 @@ * 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, @@ -22,34 +22,34 @@ * 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_FREE_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. */ @@ -75,6 +75,7 @@ #include #include #include +#include #include #include #include @@ -86,6 +87,7 @@ #include #include #include +#include #include /* @@ -102,9 +104,9 @@ kern_return_t ipc_right_lookup_write( - ipc_space_t space, - mach_port_name_t name, - ipc_entry_t *entryp) + ipc_space_t space, + mach_port_name_t name, + ipc_entry_t *entryp) { ipc_entry_t entry; @@ -112,7 +114,7 @@ ipc_right_lookup_write( is_write_lock(space); - if (!space->is_active) { + if (!is_active(space)) { is_write_unlock(space); return KERN_INVALID_TASK; } @@ -141,11 +143,11 @@ ipc_right_lookup_write( kern_return_t ipc_right_lookup_two_write( - ipc_space_t space, - mach_port_name_t name1, - ipc_entry_t *entryp1, - mach_port_name_t name2, - ipc_entry_t *entryp2) + ipc_space_t space, + mach_port_name_t name1, + ipc_entry_t *entryp1, + mach_port_name_t name2, + ipc_entry_t *entryp2) { ipc_entry_t entry1; ipc_entry_t entry2; @@ -154,17 +156,19 @@ ipc_right_lookup_two_write( is_write_lock(space); - if (!space->is_active) { + if (!is_active(space)) { is_write_unlock(space); return KERN_INVALID_TASK; } if ((entry1 = ipc_entry_lookup(space, name1)) == IE_NULL) { is_write_unlock(space); + mach_port_guard_exception(name1, 0, 0, kGUARD_EXC_INVALID_NAME); return KERN_INVALID_NAME; } if ((entry2 = ipc_entry_lookup(space, name2)) == IE_NULL) { is_write_unlock(space); + mach_port_guard_exception(name2, 0, 0, kGUARD_EXC_INVALID_NAME); return KERN_INVALID_NAME; } *entryp1 = entry1; @@ -186,10 +190,10 @@ ipc_right_lookup_two_write( boolean_t ipc_right_reverse( - ipc_space_t space, - ipc_object_t object, - mach_port_name_t *namep, - ipc_entry_t *entryp) + ipc_space_t space, + ipc_object_t object, + mach_port_name_t *namep, + ipc_entry_t *entryp) { ipc_port_t port; mach_port_name_t name; @@ -197,10 +201,10 @@ ipc_right_reverse( /* would switch on io_otype to handle multiple types of object */ - assert(space->is_active); + assert(is_active(space)); assert(io_otype(object) == IOT_PORT); - port = (ipc_port_t) object; + port = ip_object_to_port(object); ip_lock(port); if (!ip_active(port)) { @@ -217,17 +221,17 @@ ipc_right_reverse( assert(entry != IE_NULL); assert(entry->ie_bits & MACH_PORT_TYPE_RECEIVE); - assert(port == (ipc_port_t) entry->ie_object); + assert(port == ip_object_to_port(entry->ie_object)); *namep = name; *entryp = entry; return TRUE; } - if (ipc_hash_lookup(space, (ipc_object_t) port, namep, entryp)) { + if (ipc_hash_lookup(space, ip_to_object(port), namep, entryp)) { assert((entry = *entryp) != IE_NULL); assert(IE_BITS_TYPE(entry->ie_bits) == MACH_PORT_TYPE_SEND); - assert(port == (ipc_port_t) entry->ie_object); + assert(port == ip_object_to_port(entry->ie_object)); return TRUE; } @@ -243,12 +247,6 @@ ipc_right_reverse( * registered send-once right. If notify is IP_NULL, * just cancels the previously registered request. * - * This interacts with the IE_BITS_COMPAT, because they - * both use ie_request. If this is a compat entry, then - * previous always gets IP_NULL. If notify is IP_NULL, - * then the entry remains a compat entry. Otherwise - * the real dead-name request is registered and the entry - * is no longer a compat entry. * Conditions: * Nothing locked. May allocate memory. * Only consumes/returns refs if successful. @@ -259,125 +257,172 @@ ipc_right_reverse( * KERN_INVALID_RIGHT Name doesn't denote port/dead rights. * KERN_INVALID_ARGUMENT Name denotes dead name, but * immediate is FALSE or notify is IP_NULL. - * KERN_UREFS_OVERFLOW Name denotes dead name, but - * generating immediate notif. would overflow urefs. * KERN_RESOURCE_SHORTAGE Couldn't allocate memory. */ kern_return_t -ipc_right_dnrequest( - ipc_space_t space, - mach_port_name_t name, - boolean_t immediate, - ipc_port_t notify, - ipc_port_t *previousp) +ipc_right_request_alloc( + ipc_space_t space, + mach_port_name_t name, + boolean_t immediate, + boolean_t send_possible, + ipc_port_t notify, + ipc_port_t *previousp) { - ipc_port_t previous; + ipc_port_request_index_t prev_request; + ipc_port_t previous = IP_NULL; + ipc_entry_t entry; + kern_return_t kr; + +#if IMPORTANCE_INHERITANCE + boolean_t needboost = FALSE; +#endif /* IMPORTANCE_INHERITANCE */ for (;;) { - ipc_entry_t entry; - ipc_entry_bits_t bits; - kern_return_t kr; + ipc_port_t port = IP_NULL; kr = ipc_right_lookup_write(space, name, &entry); - if (kr != KERN_SUCCESS) + if (kr != KERN_SUCCESS) { return kr; + } + /* space is write-locked and active */ - bits = entry->ie_bits; - if (bits & MACH_PORT_TYPE_PORT_RIGHTS) { - ipc_port_t port; - ipc_port_request_index_t request; - port = (ipc_port_t) entry->ie_object; + prev_request = entry->ie_request; + + /* if nothing to do or undo, we're done */ + if (notify == IP_NULL && prev_request == IE_REQ_NONE) { + is_write_unlock(space); + *previousp = IP_NULL; + return KERN_SUCCESS; + } + + /* see if the entry is of proper type for requests */ + if (entry->ie_bits & MACH_PORT_TYPE_PORT_RIGHTS) { + ipc_port_request_index_t new_request; + + port = ip_object_to_port(entry->ie_object); assert(port != IP_NULL); - if (!ipc_right_check(space, port, name, entry)) { + if (!ipc_right_check(space, port, name, entry, IPC_RIGHT_COPYIN_FLAGS_NONE)) { /* port is locked and active */ + /* if no new request, just cancel previous */ if (notify == IP_NULL) { - previous = ipc_right_dncancel_macro( - space, port, name, entry); - + if (prev_request != IE_REQ_NONE) { + previous = ipc_port_request_cancel(port, name, prev_request); + } ip_unlock(port); + entry->ie_request = IE_REQ_NONE; + ipc_entry_modified(space, name, entry); is_write_unlock(space); break; } /* - * If a registered soright exists, - * want to atomically switch with it. - * If ipc_port_dncancel finds us a - * soright, then the following - * ipc_port_dnrequest will reuse - * that slot, so we are guaranteed - * not to unlock and retry. + * send-once rights, kernel objects, and non-full other queues + * fire immediately (if immediate specified). */ + if (send_possible && immediate && + ((entry->ie_bits & MACH_PORT_TYPE_SEND_ONCE) || + port->ip_receiver == ipc_space_kernel || !ip_full(port))) { + if (prev_request != IE_REQ_NONE) { + previous = ipc_port_request_cancel(port, name, prev_request); + } + ip_unlock(port); + entry->ie_request = IE_REQ_NONE; + ipc_entry_modified(space, name, entry); + is_write_unlock(space); + + ipc_notify_send_possible(notify, name); + break; + } - previous = ipc_right_dncancel_macro(space, - port, name, entry); + /* + * If there is a previous request, free it. Any subsequent + * allocation cannot fail, thus assuring an atomic swap. + */ + if (prev_request != IE_REQ_NONE) { + previous = ipc_port_request_cancel(port, name, prev_request); + } - kr = ipc_port_dnrequest(port, name, notify, - &request); +#if IMPORTANCE_INHERITANCE + kr = ipc_port_request_alloc(port, name, notify, + send_possible, immediate, + &new_request, &needboost); +#else + kr = ipc_port_request_alloc(port, name, notify, + send_possible, immediate, + &new_request); +#endif /* IMPORTANCE_INHERITANCE */ if (kr != KERN_SUCCESS) { assert(previous == IP_NULL); is_write_unlock(space); - kr = ipc_port_dngrow(port, - ITS_SIZE_NONE); + kr = ipc_port_request_grow(port, ITS_SIZE_NONE); /* port is unlocked */ - if (kr != KERN_SUCCESS) + + if (kr != KERN_SUCCESS) { return kr; + } continue; } - assert(request != 0); - ip_unlock(port); - entry->ie_request = request; + assert(new_request != IE_REQ_NONE); + entry->ie_request = new_request; + ipc_entry_modified(space, name, entry); is_write_unlock(space); - break; - } else { - /* - * Our capability bits were changed by ipc_right_check - * because it found an inactive port and removed our - * references to it (converting our entry into a dead - * one). Reload the bits (and obviously we can't use - * the port name anymore). - */ - bits = entry->ie_bits; +#if IMPORTANCE_INHERITANCE + if (needboost == TRUE) { + if (ipc_port_importance_delta(port, IPID_OPTION_SENDPOSSIBLE, 1) == FALSE) { + ip_unlock(port); + } + } else +#endif /* IMPORTANCE_INHERITANCE */ + ip_unlock(port); + break; } - - assert(bits & MACH_PORT_TYPE_DEAD_NAME); + /* entry may have changed to dead-name by ipc_right_check() */ } - if ((bits & MACH_PORT_TYPE_DEAD_NAME) && - immediate && (notify != IP_NULL)) { - mach_port_urefs_t urefs = IE_BITS_UREFS(bits); + /* treat send_possible requests as immediate w.r.t. dead-name */ + if ((send_possible || immediate) && notify != IP_NULL && + (entry->ie_bits & MACH_PORT_TYPE_DEAD_NAME)) { + mach_port_urefs_t urefs = IE_BITS_UREFS(entry->ie_bits); - assert(IE_BITS_TYPE(bits) == MACH_PORT_TYPE_DEAD_NAME); assert(urefs > 0); - if (MACH_PORT_UREFS_OVERFLOW(urefs, 1)) { - is_write_unlock(space); - return KERN_UREFS_OVERFLOW; + /* leave urefs pegged to maximum if it overflowed */ + if (urefs < MACH_PORT_UREFS_MAX) { + (entry->ie_bits)++; /* increment urefs */ } + ipc_entry_modified(space, name, entry); - (entry->ie_bits)++; /* increment urefs */ is_write_unlock(space); + if (port != IP_NULL) { + ip_release(port); + } + ipc_notify_dead_name(notify, name); previous = IP_NULL; break; } + kr = (entry->ie_bits & MACH_PORT_TYPE_PORT_OR_DEAD) ? + KERN_INVALID_ARGUMENT : KERN_INVALID_RIGHT; + is_write_unlock(space); - if (bits & MACH_PORT_TYPE_PORT_OR_DEAD) - return KERN_INVALID_ARGUMENT; - else - return KERN_INVALID_RIGHT; + + if (port != IP_NULL) { + ip_release(port); + } + + return kr; } *previousp = previous; @@ -385,9 +430,9 @@ ipc_right_dnrequest( } /* - * Routine: ipc_right_dncancel + * Routine: ipc_right_request_cancel * Purpose: - * Cancel a dead-name request and return the send-once right. + * Cancel a notification request and return the send-once right. * Afterwards, entry->ie_request == 0. * Conditions: * The space must be write-locked; the port must be locked. @@ -395,21 +440,25 @@ ipc_right_dnrequest( */ ipc_port_t -ipc_right_dncancel( - __unused ipc_space_t space, - ipc_port_t port, - mach_port_name_t name, - ipc_entry_t entry) +ipc_right_request_cancel( + __unused ipc_space_t space, + ipc_port_t port, + mach_port_name_t name, + ipc_entry_t entry) { - ipc_port_t dnrequest; + ipc_port_t previous; - assert(ip_active(port)); - assert(port == (ipc_port_t) entry->ie_object); + require_ip_active(port); + assert(port == ip_object_to_port(entry->ie_object)); - dnrequest = ipc_port_dncancel(port, name, entry->ie_request); - entry->ie_request = 0; + if (entry->ie_request == IE_REQ_NONE) { + return IP_NULL; + } - return dnrequest; + previous = ipc_port_request_cancel(port, name, entry->ie_request); + entry->ie_request = IE_REQ_NONE; + ipc_entry_modified(space, name, entry); + return previous; } /* @@ -424,9 +473,9 @@ ipc_right_dncancel( boolean_t ipc_right_inuse( - ipc_space_t space, - __unused mach_port_name_t name, - ipc_entry_t entry) + ipc_space_t space, + __unused mach_port_name_t name, + ipc_entry_t entry) { if (IE_BITS_TYPE(entry->ie_bits) != MACH_PORT_TYPE_NONE) { is_write_unlock(space); @@ -439,30 +488,38 @@ ipc_right_inuse( * Routine: ipc_right_check * Purpose: * Check if the port has died. If it has, + * and IPC_RIGHT_COPYIN_FLAGS_ALLOW_DEAD_SEND_ONCE is not + * passed and it is not a send once right then * clean up the entry and return TRUE. * Conditions: * The space is write-locked; the port is not locked. - * If returns FALSE, the port is also locked and active. - * Otherwise, entry is converted to a dead name, freeing - * a reference to port. + * If returns FALSE, the port is also locked. + * Otherwise, entry is converted to a dead name. + * + * Caller is responsible for a reference to port if it + * had died (returns TRUE). */ boolean_t ipc_right_check( - ipc_space_t space, - ipc_port_t port, - mach_port_name_t name, - ipc_entry_t entry) + ipc_space_t space, + ipc_port_t port, + mach_port_name_t name, + ipc_entry_t entry, + ipc_right_copyin_flags_t flags) { ipc_entry_bits_t bits; - assert(space->is_active); - assert(port == (ipc_port_t) entry->ie_object); + assert(is_active(space)); + assert(port == ip_object_to_port(entry->ie_object)); ip_lock(port); - if (ip_active(port)) + if (ip_active(port) || + ((flags & IPC_RIGHT_COPYIN_FLAGS_ALLOW_DEAD_SEND_ONCE) && + entry->ie_request == IE_REQ_NONE && + (entry->ie_bits & MACH_PORT_TYPE_SEND_ONCE))) { return FALSE; - ip_unlock(port); + } /* this was either a pure send right or a send-once right */ @@ -470,53 +527,65 @@ ipc_right_check( assert((bits & MACH_PORT_TYPE_RECEIVE) == 0); assert(IE_BITS_UREFS(bits) > 0); - if (bits & MACH_PORT_TYPE_SEND) { - assert(IE_BITS_TYPE(bits) == MACH_PORT_TYPE_SEND); - } else { - assert(IE_BITS_TYPE(bits) == MACH_PORT_TYPE_SEND_ONCE); - assert(IE_BITS_UREFS(bits) == 1); - } + if (bits & MACH_PORT_TYPE_SEND) { + assert(IE_BITS_TYPE(bits) == MACH_PORT_TYPE_SEND); + assert(IE_BITS_UREFS(bits) > 0); + assert(port->ip_srights > 0); + port->ip_srights--; + } else { + assert(IE_BITS_TYPE(bits) == MACH_PORT_TYPE_SEND_ONCE); + assert(IE_BITS_UREFS(bits) == 1); + assert(port->ip_sorights > 0); + port->ip_sorights--; + } + ip_unlock(port); + /* + * delete SEND rights from ipc hash. + */ - ipc_port_release(port); + if ((bits & MACH_PORT_TYPE_SEND) != 0) { + ipc_hash_delete(space, ip_to_object(port), name, entry); + } /* convert entry to dead name */ + bits = (bits & ~IE_BITS_TYPE_MASK) | MACH_PORT_TYPE_DEAD_NAME; - if ((bits & MACH_PORT_TYPE_SEND) && !(bits & MACH_PORT_TYPE_RECEIVE)) - ipc_hash_delete(space, (ipc_object_t)port, name, entry); - - bits = (bits &~ IE_BITS_TYPE_MASK) | MACH_PORT_TYPE_DEAD_NAME; - /* * If there was a notification request outstanding on this - * name, and since the port went dead, that notification - * must already be on its way up from the port layer. We - * don't need the index of the notification port anymore. + * name, and the port went dead, that notification + * must already be on its way up from the port layer. * - * JMM - We also add a reference to the entry since the - * notification only carries the name and NOT a reference - * (or right). This makes for pretty loose reference - * counting, since it is only happenstance that we - * detected the notification in progress like this. - * But most (all?) calls that try to deal with this entry - * will also come through here, so the reference gets added - * before the entry gets used eventually (I would rather it - * be explicit in the notification generation, though) + * Add the reference that the notification carries. It + * is done here, and not in the notification delivery, + * because the latter doesn't have a space reference and + * trying to actually move a send-right reference would + * get short-circuited into a MACH_PORT_DEAD by IPC. Since + * all calls that deal with the right eventually come + * through here, it has the same result. + * + * Once done, clear the request index so we only account + * for it once. */ - if (entry->ie_request != 0) { - assert(IE_BITS_UREFS(bits) < MACH_PORT_UREFS_MAX); - entry->ie_request = 0; - bits++; + if (entry->ie_request != IE_REQ_NONE) { + if (ipc_port_request_type(port, name, entry->ie_request) != 0) { + /* if urefs are pegged due to overflow, leave them pegged */ + if (IE_BITS_UREFS(bits) < MACH_PORT_UREFS_MAX) { + bits++; /* increment urefs */ + } + } + entry->ie_request = IE_REQ_NONE; } entry->ie_bits = bits; entry->ie_object = IO_NULL; + ipc_entry_modified(space, name, entry); return TRUE; } /* - * Routine: ipc_right_clean + * Routine: ipc_right_terminate * Purpose: - * Cleans up an entry in a dead space. + * Cleans up an entry in a terminated space. * The entry isn't deallocated or removed * from reverse hash tables. * Conditions: @@ -524,10 +593,10 @@ ipc_right_check( */ void -ipc_right_clean( - ipc_space_t space, - mach_port_name_t name, - ipc_entry_t entry) +ipc_right_terminate( + ipc_space_t space, + mach_port_name_t name, + ipc_entry_t entry) { ipc_entry_bits_t bits; mach_port_type_t type; @@ -535,7 +604,7 @@ ipc_right_clean( bits = entry->ie_bits; type = IE_BITS_TYPE(bits); - assert(!space->is_active); + assert(!is_active(space)); /* * IE_BITS_COMPAT/ipc_right_dncancel doesn't have this @@ -547,30 +616,29 @@ ipc_right_clean( */ switch (type) { - case MACH_PORT_TYPE_DEAD_NAME: - assert(entry->ie_request == 0); + case MACH_PORT_TYPE_DEAD_NAME: + assert(entry->ie_request == IE_REQ_NONE); assert(entry->ie_object == IO_NULL); break; - case MACH_PORT_TYPE_PORT_SET: { - ipc_pset_t pset = (ipc_pset_t) entry->ie_object; + case MACH_PORT_TYPE_PORT_SET: { + ipc_pset_t pset = ips_object_to_pset(entry->ie_object); - assert(entry->ie_request == 0); + assert(entry->ie_request == IE_REQ_NONE); assert(pset != IPS_NULL); ips_lock(pset); assert(ips_active(pset)); - - ipc_pset_destroy(pset); /* consumes ref, unlocks */ + ipc_pset_destroy(space, pset); /* consumes ref, unlocks */ break; - } - - case MACH_PORT_TYPE_SEND: - case MACH_PORT_TYPE_RECEIVE: - case MACH_PORT_TYPE_SEND_RECEIVE: - case MACH_PORT_TYPE_SEND_ONCE: { - ipc_port_t port = (ipc_port_t) entry->ie_object; - ipc_port_t dnrequest; + } + + case MACH_PORT_TYPE_SEND: + case MACH_PORT_TYPE_RECEIVE: + case MACH_PORT_TYPE_SEND_RECEIVE: + case MACH_PORT_TYPE_SEND_ONCE: { + ipc_port_t port = ip_object_to_port(entry->ie_object); + ipc_port_t request; ipc_port_t nsrequest = IP_NULL; mach_port_mscount_t mscount = 0; @@ -578,13 +646,13 @@ ipc_right_clean( ip_lock(port); if (!ip_active(port)) { + ip_unlock(port); ip_release(port); - ip_check_unlock(port); break; } - dnrequest = ipc_right_dncancel_macro(space, port, - name, entry); + request = ipc_right_request_cancel_macro(space, port, + name, entry); if (type & MACH_PORT_TYPE_SEND) { assert(port->ip_srights > 0); @@ -602,30 +670,32 @@ ipc_right_clean( assert(port->ip_receiver_name == name); assert(port->ip_receiver == space); - ipc_port_clear_receiver(port); - ipc_port_destroy(port); /* consumes our ref, unlocks */ + ipc_port_destroy(port); /* clears receiver, consumes our ref, unlocks */ } else if (type & MACH_PORT_TYPE_SEND_ONCE) { assert(port->ip_sorights > 0); + port->ip_reply_context = 0; ip_unlock(port); ipc_notify_send_once(port); /* consumes our ref */ } else { assert(port->ip_receiver != space); + ip_unlock(port); ip_release(port); - ip_unlock(port); /* port is active */ } - if (nsrequest != IP_NULL) + if (nsrequest != IP_NULL) { ipc_notify_no_senders(nsrequest, mscount); + } - if (dnrequest != IP_NULL) - ipc_notify_port_deleted(dnrequest, name); + if (request != IP_NULL) { + ipc_notify_port_deleted(request, name); + } break; - } + } - default: - panic("ipc_right_clean: strange type - 0x%x", type); + default: + panic("ipc_right_terminate: strange type - 0x%x", type); } } @@ -634,7 +704,7 @@ ipc_right_clean( * Purpose: * Destroys an entry in a space. * Conditions: - * The space is write-locked. + * The space is write-locked (returns unlocked). * The space must be active. * Returns: * KERN_SUCCESS The entry was destroyed. @@ -642,9 +712,11 @@ ipc_right_clean( kern_return_t ipc_right_destroy( - ipc_space_t space, - mach_port_name_t name, - ipc_entry_t entry) + ipc_space_t space, + mach_port_name_t name, + ipc_entry_t entry, + boolean_t check_guard, + uint64_t guard) { ipc_entry_bits_t bits; mach_port_type_t type; @@ -653,65 +725,81 @@ ipc_right_destroy( entry->ie_bits &= ~IE_BITS_TYPE_MASK; type = IE_BITS_TYPE(bits); - assert(space->is_active); + assert(is_active(space)); switch (type) { - case MACH_PORT_TYPE_DEAD_NAME: - assert(entry->ie_request == 0); + case MACH_PORT_TYPE_DEAD_NAME: + assert(entry->ie_request == IE_REQ_NONE); assert(entry->ie_object == IO_NULL); ipc_entry_dealloc(space, name, entry); + is_write_unlock(space); break; - case MACH_PORT_TYPE_PORT_SET: { - ipc_pset_t pset = (ipc_pset_t) entry->ie_object; + case MACH_PORT_TYPE_PORT_SET: { + ipc_pset_t pset = ips_object_to_pset(entry->ie_object); - assert(entry->ie_request == 0); + assert(entry->ie_request == IE_REQ_NONE); assert(pset != IPS_NULL); entry->ie_object = IO_NULL; ipc_entry_dealloc(space, name, entry); ips_lock(pset); - assert(ips_active(pset)); + is_write_unlock(space); - ipc_pset_destroy(pset); /* consumes ref, unlocks */ + assert(ips_active(pset)); + ipc_pset_destroy(space, pset); /* consumes ref, unlocks */ break; - } + } - case MACH_PORT_TYPE_SEND: - case MACH_PORT_TYPE_RECEIVE: - case MACH_PORT_TYPE_SEND_RECEIVE: - case MACH_PORT_TYPE_SEND_ONCE: { - ipc_port_t port = (ipc_port_t) entry->ie_object; + case MACH_PORT_TYPE_SEND: + case MACH_PORT_TYPE_RECEIVE: + case MACH_PORT_TYPE_SEND_RECEIVE: + case MACH_PORT_TYPE_SEND_ONCE: { + ipc_port_t port = ip_object_to_port(entry->ie_object); ipc_port_t nsrequest = IP_NULL; mach_port_mscount_t mscount = 0; - ipc_port_t dnrequest; + ipc_port_t request; assert(port != IP_NULL); - if (type == MACH_PORT_TYPE_SEND) - ipc_hash_delete(space, (ipc_object_t) port, - name, entry); + if (type == MACH_PORT_TYPE_SEND) { + ipc_hash_delete(space, ip_to_object(port), name, entry); + } ip_lock(port); if (!ip_active(port)) { assert((type & MACH_PORT_TYPE_RECEIVE) == 0); - ip_release(port); - ip_check_unlock(port); - - entry->ie_request = 0; + ip_unlock(port); + entry->ie_request = IE_REQ_NONE; entry->ie_object = IO_NULL; ipc_entry_dealloc(space, name, entry); - + is_write_unlock(space); + ip_release(port); break; } - dnrequest = ipc_right_dncancel_macro(space, port, name, entry); + /* For receive rights, check for guarding */ + if ((type & MACH_PORT_TYPE_RECEIVE) && + (check_guard) && (port->ip_guarded) && + (guard != port->ip_context)) { + /* Guard Violation */ + uint64_t portguard = port->ip_context; + ip_unlock(port); + is_write_unlock(space); + /* Raise mach port guard exception */ + mach_port_guard_exception(name, 0, portguard, kGUARD_EXC_DESTROY); + return KERN_INVALID_RIGHT; + } + + + request = ipc_right_request_cancel_macro(space, port, name, entry); entry->ie_object = IO_NULL; ipc_entry_dealloc(space, name, entry); + is_write_unlock(space); if (type & MACH_PORT_TYPE_SEND) { assert(port->ip_srights > 0); @@ -725,32 +813,36 @@ ipc_right_destroy( } if (type & MACH_PORT_TYPE_RECEIVE) { - assert(ip_active(port)); + require_ip_active(port); assert(port->ip_receiver == space); - ipc_port_clear_receiver(port); - ipc_port_destroy(port); /* consumes our ref, unlocks */ + ipc_port_destroy(port); /* clears receiver, consumes our ref, unlocks */ } else if (type & MACH_PORT_TYPE_SEND_ONCE) { assert(port->ip_sorights > 0); + port->ip_reply_context = 0; ip_unlock(port); ipc_notify_send_once(port); /* consumes our ref */ } else { assert(port->ip_receiver != space); - ip_release(port); ip_unlock(port); + ip_release(port); } - if (nsrequest != IP_NULL) + if (nsrequest != IP_NULL) { ipc_notify_no_senders(nsrequest, mscount); + } + + if (request != IP_NULL) { + ipc_notify_port_deleted(request, name); + } + - if (dnrequest != IP_NULL) - ipc_notify_port_deleted(dnrequest, name); break; - } + } - default: + default: panic("ipc_right_destroy: strange type"); } @@ -760,7 +852,7 @@ ipc_right_destroy( /* * Routine: ipc_right_dealloc * Purpose: - * Releases a send/send-once/dead-name user ref. + * Releases a send/send-once/dead-name/port_set user ref. * Like ipc_right_delta with a delta of -1, * but looks at the entry to determine the right. * Conditions: @@ -773,11 +865,11 @@ ipc_right_destroy( kern_return_t ipc_right_dealloc( - ipc_space_t space, - mach_port_name_t name, - ipc_entry_t entry) + ipc_space_t space, + mach_port_name_t name, + ipc_entry_t entry) { - + ipc_port_t port = IP_NULL; ipc_entry_bits_t bits; mach_port_type_t type; @@ -785,45 +877,79 @@ ipc_right_dealloc( type = IE_BITS_TYPE(bits); - assert(space->is_active); + assert(is_active(space)); switch (type) { - case MACH_PORT_TYPE_DEAD_NAME: { - dead_name: + case MACH_PORT_TYPE_PORT_SET: { + ipc_pset_t pset; + + assert(IE_BITS_UREFS(bits) == 0); + assert(entry->ie_request == IE_REQ_NONE); + + pset = ips_object_to_pset(entry->ie_object); + assert(pset != IPS_NULL); + + entry->ie_object = IO_NULL; + ipc_entry_dealloc(space, name, entry); + + ips_lock(pset); + assert(ips_active(pset)); + is_write_unlock(space); + + ipc_pset_destroy(space, pset); /* consumes ref, unlocks */ + break; + } + + case MACH_PORT_TYPE_DEAD_NAME: { +dead_name: assert(IE_BITS_UREFS(bits) > 0); - assert(entry->ie_request == 0); + assert(entry->ie_request == IE_REQ_NONE); assert(entry->ie_object == IO_NULL); if (IE_BITS_UREFS(bits) == 1) { ipc_entry_dealloc(space, name, entry); + } else { + /* if urefs are pegged due to overflow, leave them pegged */ + if (IE_BITS_UREFS(bits) < MACH_PORT_UREFS_MAX) { + entry->ie_bits = bits - 1; /* decrement urefs */ + } + ipc_entry_modified(space, name, entry); } - else - entry->ie_bits = bits-1; /* decrement urefs */ - is_write_unlock(space); + + /* release any port that got converted to dead name below */ + if (port != IP_NULL) { + ip_release(port); + } break; - } + } - case MACH_PORT_TYPE_SEND_ONCE: { - ipc_port_t port, dnrequest; + case MACH_PORT_TYPE_SEND_ONCE: { + ipc_port_t request; assert(IE_BITS_UREFS(bits) == 1); - port = (ipc_port_t) entry->ie_object; + port = ip_object_to_port(entry->ie_object); assert(port != IP_NULL); - if (ipc_right_check(space, port, name, entry)) { - + if (ipc_right_check(space, port, name, entry, IPC_RIGHT_COPYIN_FLAGS_NONE)) { bits = entry->ie_bits; assert(IE_BITS_TYPE(bits) == MACH_PORT_TYPE_DEAD_NAME); - goto dead_name; + goto dead_name; /* it will release port */ } /* port is locked and active */ assert(port->ip_sorights > 0); - dnrequest = ipc_right_dncancel_macro(space, port, name, entry); + /* + * clear any reply context: + * no one will be sending the response b/c we are destroying + * the single, outstanding send once right. + */ + port->ip_reply_context = 0; + + request = ipc_right_request_cancel_macro(space, port, name, entry); ip_unlock(port); entry->ie_object = IO_NULL; @@ -833,27 +959,27 @@ ipc_right_dealloc( ipc_notify_send_once(port); - if (dnrequest != IP_NULL) - ipc_notify_port_deleted(dnrequest, name); + if (request != IP_NULL) { + ipc_notify_port_deleted(request, name); + } break; - } + } - case MACH_PORT_TYPE_SEND: { - ipc_port_t port; - ipc_port_t dnrequest = IP_NULL; + case MACH_PORT_TYPE_SEND: { + ipc_port_t request = IP_NULL; ipc_port_t nsrequest = IP_NULL; mach_port_mscount_t mscount = 0; assert(IE_BITS_UREFS(bits) > 0); - port = (ipc_port_t) entry->ie_object; + port = ip_object_to_port(entry->ie_object); assert(port != IP_NULL); - if (ipc_right_check(space, port, name, entry)) { + if (ipc_right_check(space, port, name, entry, IPC_RIGHT_COPYIN_FLAGS_NONE)) { bits = entry->ie_bits; assert(IE_BITS_TYPE(bits) == MACH_PORT_TYPE_DEAD_NAME); - goto dead_name; + goto dead_name; /* it will release port */ } /* port is locked and active */ @@ -868,42 +994,46 @@ ipc_right_dealloc( } } - dnrequest = ipc_right_dncancel_macro(space, port, - name, entry); - ipc_hash_delete(space, (ipc_object_t) port, - name, entry); + request = ipc_right_request_cancel_macro(space, port, + name, entry); + ipc_hash_delete(space, ip_to_object(port), name, entry); - ip_release(port); + ip_unlock(port); entry->ie_object = IO_NULL; ipc_entry_dealloc(space, name, entry); + is_write_unlock(space); + ip_release(port); + } else { + ip_unlock(port); + /* if urefs are pegged due to overflow, leave them pegged */ + if (IE_BITS_UREFS(bits) < MACH_PORT_UREFS_MAX) { + entry->ie_bits = bits - 1; /* decrement urefs */ + } + ipc_entry_modified(space, name, entry); + is_write_unlock(space); + } - } else - entry->ie_bits = bits-1; /* decrement urefs */ - - /* even if dropped a ref, port is active */ - ip_unlock(port); - is_write_unlock(space); - - if (nsrequest != IP_NULL) + if (nsrequest != IP_NULL) { ipc_notify_no_senders(nsrequest, mscount); + } - if (dnrequest != IP_NULL) - ipc_notify_port_deleted(dnrequest, name); + if (request != IP_NULL) { + ipc_notify_port_deleted(request, name); + } break; - } + } - case MACH_PORT_TYPE_SEND_RECEIVE: { - ipc_port_t port; + case MACH_PORT_TYPE_SEND_RECEIVE: { ipc_port_t nsrequest = IP_NULL; mach_port_mscount_t mscount = 0; assert(IE_BITS_UREFS(bits) > 0); - port = (ipc_port_t) entry->ie_object; + port = ip_object_to_port(entry->ie_object); assert(port != IP_NULL); ip_lock(port); - assert(ip_active(port)); + require_ip_active(port); assert(port->ip_receiver_name == name); assert(port->ip_receiver == space); assert(port->ip_srights > 0); @@ -917,21 +1047,28 @@ ipc_right_dealloc( } } - entry->ie_bits = bits &~ (IE_BITS_UREFS_MASK | - MACH_PORT_TYPE_SEND); - } else - entry->ie_bits = bits-1; /* decrement urefs */ - + entry->ie_bits = bits & ~(IE_BITS_UREFS_MASK | + MACH_PORT_TYPE_SEND); + } else { + /* if urefs are pegged due to overflow, leave them pegged */ + if (IE_BITS_UREFS(bits) < MACH_PORT_UREFS_MAX) { + entry->ie_bits = bits - 1; /* decrement urefs */ + } + } ip_unlock(port); + + ipc_entry_modified(space, name, entry); is_write_unlock(space); - if (nsrequest != IP_NULL) + if (nsrequest != IP_NULL) { ipc_notify_no_senders(nsrequest, mscount); + } break; - } + } - default: + default: is_write_unlock(space); + mach_port_guard_exception(name, 0, 0, kGUARD_EXC_INVALID_RIGHT); return KERN_INVALID_RIGHT; } @@ -950,21 +1087,20 @@ ipc_right_dealloc( * KERN_SUCCESS Count was modified. * KERN_INVALID_RIGHT Entry has wrong type. * KERN_INVALID_VALUE Bad delta for the right. - * KERN_UREFS_OVERFLOW OK delta, except would overflow. */ kern_return_t ipc_right_delta( - ipc_space_t space, - mach_port_name_t name, - ipc_entry_t entry, - mach_port_right_t right, - mach_port_delta_t delta) + ipc_space_t space, + mach_port_name_t name, + ipc_entry_t entry, + mach_port_right_t right, + mach_port_delta_t delta) { + ipc_port_t port = IP_NULL; ipc_entry_bits_t bits; - - bits = entry->ie_bits; + bits = entry->ie_bits; /* * The following is used (for case MACH_PORT_RIGHT_DEAD_NAME) in the @@ -974,59 +1110,65 @@ ipc_right_delta( * we postpone doing so when we are holding the space lock. */ - assert(space->is_active); + assert(is_active(space)); assert(right < MACH_PORT_RIGHT_NUMBER); /* Rights-specific restrictions and operations. */ switch (right) { - case MACH_PORT_RIGHT_PORT_SET: { + case MACH_PORT_RIGHT_PORT_SET: { ipc_pset_t pset; - if ((bits & MACH_PORT_TYPE_PORT_SET) == 0) + if ((bits & MACH_PORT_TYPE_PORT_SET) == 0) { + mach_port_guard_exception(name, 0, 0, kGUARD_EXC_INVALID_RIGHT); goto invalid_right; + } assert(IE_BITS_TYPE(bits) == MACH_PORT_TYPE_PORT_SET); assert(IE_BITS_UREFS(bits) == 0); - assert(entry->ie_request == 0); + assert(entry->ie_request == IE_REQ_NONE); - if (delta == 0) + if (delta == 0) { goto success; + } - if (delta != -1) + if (delta != -1) { goto invalid_value; + } - pset = (ipc_pset_t) entry->ie_object; + pset = ips_object_to_pset(entry->ie_object); assert(pset != IPS_NULL); - - entry->ie_object = IO_NULL; ipc_entry_dealloc(space, name, entry); - ips_lock(pset); assert(ips_active(pset)); is_write_unlock(space); - ipc_pset_destroy(pset); /* consumes ref, unlocks */ + ipc_pset_destroy(space, pset); /* consumes ref, unlocks */ break; - } + } - case MACH_PORT_RIGHT_RECEIVE: { - ipc_port_t port; - ipc_port_t dnrequest = IP_NULL; + case MACH_PORT_RIGHT_RECEIVE: { + ipc_port_t request = IP_NULL; - if ((bits & MACH_PORT_TYPE_RECEIVE) == 0) + if ((bits & MACH_PORT_TYPE_RECEIVE) == 0) { + if ((bits & MACH_PORT_TYPE_EX_RECEIVE) == 0) { + mach_port_guard_exception(name, 0, 0, kGUARD_EXC_INVALID_RIGHT); + } goto invalid_right; + } - if (delta == 0) + if (delta == 0) { goto success; + } - if (delta != -1) + if (delta != -1) { goto invalid_value; + } - port = (ipc_port_t) entry->ie_object; + port = ip_object_to_port(entry->ie_object); assert(port != IP_NULL); /* @@ -1036,65 +1178,99 @@ ipc_right_delta( */ ip_lock(port); - assert(ip_active(port)); + require_ip_active(port); assert(port->ip_receiver_name == name); assert(port->ip_receiver == space); + /* Mach Port Guard Checking */ + if (port->ip_guarded) { + uint64_t portguard = port->ip_context; + ip_unlock(port); + is_write_unlock(space); + /* Raise mach port guard exception */ + mach_port_guard_exception(name, 0, portguard, kGUARD_EXC_MOD_REFS); + goto guard_failure; + } + if (bits & MACH_PORT_TYPE_SEND) { assert(IE_BITS_TYPE(bits) == - MACH_PORT_TYPE_SEND_RECEIVE); + MACH_PORT_TYPE_SEND_RECEIVE); assert(IE_BITS_UREFS(bits) > 0); - assert(IE_BITS_UREFS(bits) < MACH_PORT_UREFS_MAX); assert(port->ip_srights > 0); - /* - * The remaining send right turns into a - * dead name. Notice we don't decrement - * ip_srights, generate a no-senders notif, - * or use ipc_right_dncancel, because the - * port is destroyed "first". - */ - bits &= ~IE_BITS_TYPE_MASK; - bits |= MACH_PORT_TYPE_DEAD_NAME; - if (entry->ie_request) { - entry->ie_request = 0; - bits++; + if (port->ip_pdrequest != NULL) { + /* + * Since another task has requested a + * destroy notification for this port, it + * isn't actually being destroyed - the receive + * right is just being moved to another task. + * Since we still have one or more send rights, + * we need to record the loss of the receive + * right and enter the remaining send right + * into the hash table. + */ + ipc_entry_modified(space, name, entry); + entry->ie_bits &= ~MACH_PORT_TYPE_RECEIVE; + entry->ie_bits |= MACH_PORT_TYPE_EX_RECEIVE; + ipc_hash_insert(space, ip_to_object(port), + name, entry); + ip_reference(port); + } else { + /* + * The remaining send right turns into a + * dead name. Notice we don't decrement + * ip_srights, generate a no-senders notif, + * or use ipc_right_dncancel, because the + * port is destroyed "first". + */ + bits &= ~IE_BITS_TYPE_MASK; + bits |= (MACH_PORT_TYPE_DEAD_NAME | MACH_PORT_TYPE_EX_RECEIVE); + if (entry->ie_request) { + entry->ie_request = IE_REQ_NONE; + /* if urefs are pegged due to overflow, leave them pegged */ + if (IE_BITS_UREFS(bits) < MACH_PORT_UREFS_MAX) { + bits++; /* increment urefs */ + } + } + entry->ie_bits = bits; + entry->ie_object = IO_NULL; + ipc_entry_modified(space, name, entry); } - entry->ie_bits = bits; - entry->ie_object = IO_NULL; } else { assert(IE_BITS_TYPE(bits) == MACH_PORT_TYPE_RECEIVE); assert(IE_BITS_UREFS(bits) == 0); - dnrequest = ipc_right_dncancel_macro(space, port, - name, entry); + request = ipc_right_request_cancel_macro(space, port, + name, entry); entry->ie_object = IO_NULL; ipc_entry_dealloc(space, name, entry); } is_write_unlock(space); - ipc_port_clear_receiver(port); - ipc_port_destroy(port); /* consumes ref, unlocks */ + ipc_port_destroy(port); /* clears receiver, consumes ref, unlocks */ - if (dnrequest != IP_NULL) - ipc_notify_port_deleted(dnrequest, name); + if (request != IP_NULL) { + ipc_notify_port_deleted(request, name); + } break; - } + } - case MACH_PORT_RIGHT_SEND_ONCE: { - ipc_port_t port, dnrequest; + case MACH_PORT_RIGHT_SEND_ONCE: { + ipc_port_t request; - if ((bits & MACH_PORT_TYPE_SEND_ONCE) == 0) + if ((bits & MACH_PORT_TYPE_SEND_ONCE) == 0) { goto invalid_right; + } assert(IE_BITS_TYPE(bits) == MACH_PORT_TYPE_SEND_ONCE); assert(IE_BITS_UREFS(bits) == 1); - port = (ipc_port_t) entry->ie_object; + port = ip_object_to_port(entry->ie_object); assert(port != IP_NULL); - if (ipc_right_check(space, port, name, entry)) { + if (ipc_right_check(space, port, name, entry, IPC_RIGHT_COPYIN_FLAGS_NONE)) { assert(!(entry->ie_bits & MACH_PORT_TYPE_SEND_ONCE)); + mach_port_guard_exception(name, 0, 0, kGUARD_EXC_INVALID_RIGHT); goto invalid_right; } /* port is locked and active */ @@ -1111,7 +1287,14 @@ ipc_right_delta( goto success; } - dnrequest = ipc_right_dncancel_macro(space, port, name, entry); + /* + * clear any reply context: + * no one will be sending the response b/c we are destroying + * the single, outstanding send once right. + */ + port->ip_reply_context = 0; + + request = ipc_right_request_cancel_macro(space, port, name, entry); ip_unlock(port); entry->ie_object = IO_NULL; @@ -1121,67 +1304,111 @@ ipc_right_delta( ipc_notify_send_once(port); - if (dnrequest != IP_NULL) - ipc_notify_port_deleted(dnrequest, name); + if (request != IP_NULL) { + ipc_notify_port_deleted(request, name); + } break; - } + } - case MACH_PORT_RIGHT_DEAD_NAME: { + case MACH_PORT_RIGHT_DEAD_NAME: { + ipc_port_t relport = IP_NULL; mach_port_urefs_t urefs; if (bits & MACH_PORT_TYPE_SEND_RIGHTS) { - ipc_port_t port; - - port = (ipc_port_t) entry->ie_object; + port = ip_object_to_port(entry->ie_object); assert(port != IP_NULL); - if (!ipc_right_check(space, port, name, entry)) { + if (!ipc_right_check(space, port, name, entry, IPC_RIGHT_COPYIN_FLAGS_NONE)) { /* port is locked and active */ ip_unlock(port); + port = IP_NULL; + mach_port_guard_exception(name, 0, 0, kGUARD_EXC_INVALID_RIGHT); goto invalid_right; } bits = entry->ie_bits; - } else if ((bits & MACH_PORT_TYPE_DEAD_NAME) == 0) + relport = port; + port = IP_NULL; + } else if ((bits & MACH_PORT_TYPE_DEAD_NAME) == 0) { + mach_port_guard_exception(name, 0, 0, kGUARD_EXC_INVALID_RIGHT); goto invalid_right; + } assert(IE_BITS_TYPE(bits) == MACH_PORT_TYPE_DEAD_NAME); assert(IE_BITS_UREFS(bits) > 0); assert(entry->ie_object == IO_NULL); - assert(entry->ie_request == 0); + assert(entry->ie_request == IE_REQ_NONE); - urefs = IE_BITS_UREFS(bits); - if (MACH_PORT_UREFS_UNDERFLOW(urefs, delta)) + if (delta > ((mach_port_delta_t)MACH_PORT_UREFS_MAX) || + delta < (-((mach_port_delta_t)MACH_PORT_UREFS_MAX))) { goto invalid_value; - if (MACH_PORT_UREFS_OVERFLOW(urefs, delta)) - goto urefs_overflow; + } + + urefs = IE_BITS_UREFS(bits); + + if (urefs == MACH_PORT_UREFS_MAX) { + /* + * urefs are pegged due to an overflow + * only a delta removing all refs at once can change it + */ + + if (delta != (-((mach_port_delta_t)MACH_PORT_UREFS_MAX))) { + delta = 0; + } + } else { + if (MACH_PORT_UREFS_UNDERFLOW(urefs, delta)) { + goto invalid_value; + } + if (MACH_PORT_UREFS_OVERFLOW(urefs, delta)) { + /* leave urefs pegged to maximum if it overflowed */ + delta = MACH_PORT_UREFS_MAX - urefs; + } + } if ((urefs + delta) == 0) { ipc_entry_dealloc(space, name, entry); - } - else + } else if (delta != 0) { entry->ie_bits = bits + delta; + ipc_entry_modified(space, name, entry); + } is_write_unlock(space); + if (relport != IP_NULL) { + ip_release(relport); + } + break; - } + } - case MACH_PORT_RIGHT_SEND: { + case MACH_PORT_RIGHT_SEND: { mach_port_urefs_t urefs; - ipc_port_t port; - ipc_port_t dnrequest = IP_NULL; + ipc_port_t request = IP_NULL; ipc_port_t nsrequest = IP_NULL; + ipc_port_t port_to_release = IP_NULL; mach_port_mscount_t mscount = 0; - if ((bits & MACH_PORT_TYPE_SEND) == 0) + if ((bits & MACH_PORT_TYPE_SEND) == 0) { + /* invalid right exception only when not live/dead confusion */ + if ((bits & MACH_PORT_TYPE_DEAD_NAME) == 0 +#if !defined(AE_MAKESENDRIGHT_FIXED) + /* + * AE tries to add single send right without knowing if it already owns one. + * But if it doesn't, it should own the receive right and delta should be 1. + */ + && (((bits & MACH_PORT_TYPE_RECEIVE) == 0) || (delta != 1)) +#endif + ) { + mach_port_guard_exception(name, 0, 0, kGUARD_EXC_INVALID_RIGHT); + } goto invalid_right; + } - /* maximum urefs for send is MACH_PORT_UREFS_MAX-1 */ + /* maximum urefs for send is MACH_PORT_UREFS_MAX */ - port = (ipc_port_t) entry->ie_object; + port = ip_object_to_port(entry->ie_object); assert(port != IP_NULL); - if (ipc_right_check(space, port, name, entry)) { + if (ipc_right_check(space, port, name, entry, IPC_RIGHT_COPYIN_FLAGS_NONE)) { assert((entry->ie_bits & MACH_PORT_TYPE_SEND) == 0); goto invalid_right; } @@ -1189,14 +1416,32 @@ ipc_right_delta( assert(port->ip_srights > 0); - urefs = IE_BITS_UREFS(bits); - if (MACH_PORT_UREFS_UNDERFLOW(urefs, delta)) { + if (delta > ((mach_port_delta_t)MACH_PORT_UREFS_MAX) || + delta < (-((mach_port_delta_t)MACH_PORT_UREFS_MAX))) { ip_unlock(port); goto invalid_value; } - if (MACH_PORT_UREFS_OVERFLOW(urefs+1, delta)) { - ip_unlock(port); - goto urefs_overflow; + + urefs = IE_BITS_UREFS(bits); + + if (urefs == MACH_PORT_UREFS_MAX) { + /* + * urefs are pegged due to an overflow + * only a delta removing all refs at once can change it + */ + + if (delta != (-((mach_port_delta_t)MACH_PORT_UREFS_MAX))) { + delta = 0; + } + } else { + if (MACH_PORT_UREFS_UNDERFLOW(urefs, delta)) { + ip_unlock(port); + goto invalid_value; + } + if (MACH_PORT_UREFS_OVERFLOW(urefs, delta)) { + /* leave urefs pegged to maximum if it overflowed */ + delta = MACH_PORT_UREFS_MAX - urefs; + } } if ((urefs + delta) == 0) { @@ -1211,103 +1456,339 @@ ipc_right_delta( if (bits & MACH_PORT_TYPE_RECEIVE) { assert(port->ip_receiver_name == name); assert(port->ip_receiver == space); + ip_unlock(port); assert(IE_BITS_TYPE(bits) == - MACH_PORT_TYPE_SEND_RECEIVE); + MACH_PORT_TYPE_SEND_RECEIVE); - entry->ie_bits = bits &~ (IE_BITS_UREFS_MASK| - MACH_PORT_TYPE_SEND); + entry->ie_bits = bits & ~(IE_BITS_UREFS_MASK | + MACH_PORT_TYPE_SEND); + ipc_entry_modified(space, name, entry); } else { assert(IE_BITS_TYPE(bits) == - MACH_PORT_TYPE_SEND); + MACH_PORT_TYPE_SEND); - dnrequest = ipc_right_dncancel_macro(space, port, - name, entry); - ipc_hash_delete(space, (ipc_object_t) port, - name, entry); + request = ipc_right_request_cancel_macro(space, port, + name, entry); + ipc_hash_delete(space, ip_to_object(port), + name, entry); - ip_release(port); + ip_unlock(port); + port_to_release = port; entry->ie_object = IO_NULL; ipc_entry_dealloc(space, name, entry); } - } else + } else if (delta != 0) { + ip_unlock(port); entry->ie_bits = bits + delta; + ipc_entry_modified(space, name, entry); + } else { + ip_unlock(port); + } - /* even if dropped a ref, port is active */ - ip_unlock(port); is_write_unlock(space); - if (nsrequest != IP_NULL) + if (port_to_release != IP_NULL) { + ip_release(port_to_release); + } + + if (nsrequest != IP_NULL) { ipc_notify_no_senders(nsrequest, mscount); + } - if (dnrequest != IP_NULL) - ipc_notify_port_deleted(dnrequest, name); + if (request != IP_NULL) { + ipc_notify_port_deleted(request, name); + } break; - } + } + + case MACH_PORT_RIGHT_LABELH: + goto invalid_right; - default: - panic("ipc_right_delta: strange right"); + default: + panic("ipc_right_delta: strange right %d for 0x%x (%p) in space:%p", + right, name, (void *)entry, (void *)space); } return KERN_SUCCESS; - success: +success: is_write_unlock(space); return KERN_SUCCESS; - invalid_right: +invalid_right: is_write_unlock(space); + if (port != IP_NULL) { + ip_release(port); + } return KERN_INVALID_RIGHT; - invalid_value: +invalid_value: is_write_unlock(space); + mach_port_guard_exception(name, 0, 0, kGUARD_EXC_INVALID_VALUE); return KERN_INVALID_VALUE; - urefs_overflow: +guard_failure: + return KERN_INVALID_RIGHT; +} + +/* + * Routine: ipc_right_destruct + * Purpose: + * Deallocates the receive right and modifies the + * user-reference count for the send rights as requested. + * Conditions: + * The space is write-locked, and is unlocked upon return. + * The space must be active. + * Returns: + * KERN_SUCCESS Count was modified. + * KERN_INVALID_RIGHT Entry has wrong type. + * KERN_INVALID_VALUE Bad delta for the right. + */ + +kern_return_t +ipc_right_destruct( + ipc_space_t space, + mach_port_name_t name, + ipc_entry_t entry, + mach_port_delta_t srdelta, + uint64_t guard) +{ + ipc_port_t port = IP_NULL; + ipc_entry_bits_t bits; + + mach_port_urefs_t urefs; + ipc_port_t request = IP_NULL; + ipc_port_t nsrequest = IP_NULL; + mach_port_mscount_t mscount = 0; + + bits = entry->ie_bits; + + assert(is_active(space)); + + if ((bits & MACH_PORT_TYPE_RECEIVE) == 0) { + is_write_unlock(space); + + /* No exception if we used to have receive and held entry since */ + if ((bits & MACH_PORT_TYPE_EX_RECEIVE) == 0) { + mach_port_guard_exception(name, 0, 0, kGUARD_EXC_INVALID_RIGHT); + } + return KERN_INVALID_RIGHT; + } + + if (srdelta && (bits & MACH_PORT_TYPE_SEND) == 0) { + is_write_unlock(space); + mach_port_guard_exception(name, 0, 0, kGUARD_EXC_INVALID_RIGHT); + return KERN_INVALID_RIGHT; + } + + if (srdelta > 0) { + goto invalid_value; + } + + port = ip_object_to_port(entry->ie_object); + assert(port != IP_NULL); + + ip_lock(port); + require_ip_active(port); + assert(port->ip_receiver_name == name); + assert(port->ip_receiver == space); + + /* Mach Port Guard Checking */ + if (port->ip_guarded && (guard != port->ip_context)) { + uint64_t portguard = port->ip_context; + ip_unlock(port); + is_write_unlock(space); + mach_port_guard_exception(name, 0, portguard, kGUARD_EXC_DESTROY); + return KERN_INVALID_ARGUMENT; + } + + /* + * First reduce the send rights as requested and + * adjust the entry->ie_bits accordingly. The + * ipc_entry_modified() call is made once the receive + * right is destroyed too. + */ + + if (srdelta) { + assert(port->ip_srights > 0); + + urefs = IE_BITS_UREFS(bits); + + /* + * Since we made sure that srdelta is negative, + * the check for urefs overflow is not required. + */ + if (MACH_PORT_UREFS_UNDERFLOW(urefs, srdelta)) { + ip_unlock(port); + goto invalid_value; + } + + if (urefs == MACH_PORT_UREFS_MAX) { + /* + * urefs are pegged due to an overflow + * only a delta removing all refs at once can change it + */ + if (srdelta != (-((mach_port_delta_t)MACH_PORT_UREFS_MAX))) { + srdelta = 0; + } + } + + if ((urefs + srdelta) == 0) { + if (--port->ip_srights == 0) { + nsrequest = port->ip_nsrequest; + if (nsrequest != IP_NULL) { + port->ip_nsrequest = IP_NULL; + mscount = port->ip_mscount; + } + } + assert(IE_BITS_TYPE(bits) == MACH_PORT_TYPE_SEND_RECEIVE); + entry->ie_bits = bits & ~(IE_BITS_UREFS_MASK | + MACH_PORT_TYPE_SEND); + } else { + entry->ie_bits = bits + srdelta; + } + } + + /* + * Now destroy the receive right. Update space and + * entry accordingly. + */ + + bits = entry->ie_bits; + if (bits & MACH_PORT_TYPE_SEND) { + assert(IE_BITS_UREFS(bits) > 0); + assert(IE_BITS_UREFS(bits) <= MACH_PORT_UREFS_MAX); + + if (port->ip_pdrequest != NULL) { + /* + * Since another task has requested a + * destroy notification for this port, it + * isn't actually being destroyed - the receive + * right is just being moved to another task. + * Since we still have one or more send rights, + * we need to record the loss of the receive + * right and enter the remaining send right + * into the hash table. + */ + ipc_entry_modified(space, name, entry); + entry->ie_bits &= ~MACH_PORT_TYPE_RECEIVE; + entry->ie_bits |= MACH_PORT_TYPE_EX_RECEIVE; + ipc_hash_insert(space, ip_to_object(port), + name, entry); + ip_reference(port); + } else { + /* + * The remaining send right turns into a + * dead name. Notice we don't decrement + * ip_srights, generate a no-senders notif, + * or use ipc_right_dncancel, because the + * port is destroyed "first". + */ + bits &= ~IE_BITS_TYPE_MASK; + bits |= (MACH_PORT_TYPE_DEAD_NAME | MACH_PORT_TYPE_EX_RECEIVE); + if (entry->ie_request) { + entry->ie_request = IE_REQ_NONE; + if (IE_BITS_UREFS(bits) < MACH_PORT_UREFS_MAX) { + bits++; /* increment urefs */ + } + } + entry->ie_bits = bits; + entry->ie_object = IO_NULL; + ipc_entry_modified(space, name, entry); + } + } else { + assert(IE_BITS_TYPE(bits) == MACH_PORT_TYPE_RECEIVE); + assert(IE_BITS_UREFS(bits) == 0); + request = ipc_right_request_cancel_macro(space, port, + name, entry); + entry->ie_object = IO_NULL; + ipc_entry_dealloc(space, name, entry); + } + + /* Unlock space */ + is_write_unlock(space); + + if (nsrequest != IP_NULL) { + ipc_notify_no_senders(nsrequest, mscount); + } + + ipc_port_destroy(port); /* clears receiver, consumes ref, unlocks */ + + if (request != IP_NULL) { + ipc_notify_port_deleted(request, name); + } + + return KERN_SUCCESS; + +invalid_value: is_write_unlock(space); - return KERN_UREFS_OVERFLOW; + mach_port_guard_exception(name, 0, 0, kGUARD_EXC_INVALID_VALUE); + return KERN_INVALID_VALUE; } + /* * Routine: ipc_right_info * Purpose: * Retrieves information about the right. * Conditions: - * The space is write-locked, and is unlocked upon return - * if the call is unsuccessful. The space must be active. + * The space is active and write-locked. + * The space is unlocked upon return. * Returns: - * KERN_SUCCESS Retrieved info; space still locked. + * KERN_SUCCESS Retrieved info */ kern_return_t ipc_right_info( - ipc_space_t space, - mach_port_name_t name, - ipc_entry_t entry, - mach_port_type_t *typep, - mach_port_urefs_t *urefsp) + ipc_space_t space, + mach_port_name_t name, + ipc_entry_t entry, + mach_port_type_t *typep, + mach_port_urefs_t *urefsp) { + ipc_port_t port; ipc_entry_bits_t bits; - mach_port_type_t type; + mach_port_type_t type = 0; ipc_port_request_index_t request; bits = entry->ie_bits; + request = entry->ie_request; + port = ip_object_to_port(entry->ie_object); - if (bits & MACH_PORT_TYPE_SEND_RIGHTS) { - ipc_port_t port = (ipc_port_t) entry->ie_object; + if (bits & MACH_PORT_TYPE_RECEIVE) { + assert(IP_VALID(port)); - if (ipc_right_check(space, port, name, entry)) { + if (request != IE_REQ_NONE) { + ip_lock(port); + require_ip_active(port); + type |= ipc_port_request_type(port, name, request); + ip_unlock(port); + } + is_write_unlock(space); + } else if (bits & MACH_PORT_TYPE_SEND_RIGHTS) { + /* + * validate port is still alive - if so, get request + * types while we still have it locked. Otherwise, + * recapture the (now dead) bits. + */ + if (!ipc_right_check(space, port, name, entry, IPC_RIGHT_COPYIN_FLAGS_NONE)) { + if (request != IE_REQ_NONE) { + type |= ipc_port_request_type(port, name, request); + } + ip_unlock(port); + is_write_unlock(space); + } else { bits = entry->ie_bits; assert(IE_BITS_TYPE(bits) == MACH_PORT_TYPE_DEAD_NAME); - } else - ip_unlock(port); + is_write_unlock(space); + ip_release(port); + } + } else { + is_write_unlock(space); } - type = IE_BITS_TYPE(bits); - request = entry->ie_request; - - if (request != 0) - type |= MACH_PORT_TYPE_DNREQUEST; + type |= IE_BITS_TYPE(bits); *typep = type; *urefsp = IE_BITS_UREFS(bits); @@ -1315,142 +1796,132 @@ ipc_right_info( } /* - * Routine: ipc_right_copyin_check + * Routine: ipc_right_copyin_check_reply * Purpose: - * Check if a subsequent ipc_right_copyin would succeed. + * Check if a subsequent ipc_right_copyin would succeed. Used only + * by ipc_kmsg_copyin_header to check if reply_port can be copied in. + * If the reply port is an immovable send right, it errors out. * Conditions: * The space is locked (read or write) and active. */ boolean_t -ipc_right_copyin_check( - __assert_only ipc_space_t space, - __unused mach_port_name_t name, - ipc_entry_t entry, - mach_msg_type_name_t msgt_name) +ipc_right_copyin_check_reply( + __assert_only ipc_space_t space, + mach_port_name_t reply_name, + ipc_entry_t reply_entry, + mach_msg_type_name_t reply_type) { ipc_entry_bits_t bits; - ipc_port_t port; -#if CONFIG_MACF_MACH - task_t self = current_task(); - int rc = 0; -#endif + ipc_port_t reply_port; - bits= entry->ie_bits; - assert(space->is_active); + bits = reply_entry->ie_bits; + assert(is_active(space)); - switch (msgt_name) { - case MACH_MSG_TYPE_MAKE_SEND: - if ((bits & MACH_PORT_TYPE_RECEIVE) == 0) + switch (reply_type) { + case MACH_MSG_TYPE_MAKE_SEND: + if ((bits & MACH_PORT_TYPE_RECEIVE) == 0) { return FALSE; - -#if CONFIG_MACF_MACH - port = (ipc_port_t) entry->ie_object; - ip_lock(port); - tasklabel_lock(self); - rc = mac_port_check_make_send(&self->maclabel, &port->ip_label); tasklabel_unlock(self); - ip_unlock(port); - if (rc) - return FALSE; -#endif - break; - - case MACH_MSG_TYPE_MAKE_SEND_ONCE: - if ((bits & MACH_PORT_TYPE_RECEIVE) == 0) - return FALSE; - -#if CONFIG_MACF_MACH - port = (ipc_port_t) entry->ie_object; - ip_lock(port); - tasklabel_lock(self); - rc = mac_port_check_make_send_once(&self->maclabel, &port->ip_label); - tasklabel_unlock(self); - ip_unlock(port); - if (rc) - return FALSE; -#endif + } break; - case MACH_MSG_TYPE_MOVE_RECEIVE: - if ((bits & MACH_PORT_TYPE_RECEIVE) == 0) + case MACH_MSG_TYPE_MAKE_SEND_ONCE: + if ((bits & MACH_PORT_TYPE_RECEIVE) == 0) { return FALSE; - -#if CONFIG_MACF_MACH - port = (ipc_port_t) entry->ie_object; - ip_lock(port); - tasklabel_lock(self); - rc = mac_port_check_move_receive(&self->maclabel, &port->ip_label); - tasklabel_unlock(self); - ip_unlock(port); - if (rc) - return FALSE; -#endif + } break; - case MACH_MSG_TYPE_COPY_SEND: - case MACH_MSG_TYPE_MOVE_SEND: - case MACH_MSG_TYPE_MOVE_SEND_ONCE: { - boolean_t active; + case MACH_MSG_TYPE_MOVE_RECEIVE: + /* ipc_kmsg_copyin_header already filters it out */ + return FALSE; - if (bits & MACH_PORT_TYPE_DEAD_NAME) + case MACH_MSG_TYPE_COPY_SEND: + case MACH_MSG_TYPE_MOVE_SEND: + case MACH_MSG_TYPE_MOVE_SEND_ONCE: { + if (bits & MACH_PORT_TYPE_DEAD_NAME) { break; + } - if ((bits & MACH_PORT_TYPE_SEND_RIGHTS) == 0) + if ((bits & MACH_PORT_TYPE_SEND_RIGHTS) == 0) { return FALSE; + } - port = (ipc_port_t) entry->ie_object; - assert(port != IP_NULL); + reply_port = ip_object_to_port(reply_entry->ie_object); + assert(reply_port != IP_NULL); - ip_lock(port); - active = ip_active(port); -#if CONFIG_MACF_MACH - tasklabel_lock(self); - switch (msgt_name) { - case MACH_MSG_TYPE_COPY_SEND: - rc = mac_port_check_copy_send(&self->maclabel, - &port->ip_label); - break; - case MACH_MSG_TYPE_MOVE_SEND: - rc = mac_port_check_move_send(&self->maclabel, - &port->ip_label); - break; - case MACH_MSG_TYPE_MOVE_SEND_ONCE: - rc = mac_port_check_move_send_once(&self->maclabel, - &port->ip_label); + /* + * active status peek to avoid checks that will be skipped + * on copyin for dead ports. Lock not held, so will not be + * atomic (but once dead, there's no going back). + */ + if (!ip_active(reply_port)) { break; - default: - panic("ipc_right_copyin_check: strange rights"); - } - tasklabel_unlock(self); - if (rc) { - ip_unlock(port); - return FALSE; } -#endif - ip_unlock(port); - if (!active) { - break; + /* + * Can't copyin a send right that is marked immovable. This bit + * is set only during port creation and never unset. So it can + * be read without a lock. + */ + if (reply_port->ip_immovable_send) { + mach_port_guard_exception(reply_name, 0, 0, kGUARD_EXC_IMMOVABLE); + return FALSE; } - if (msgt_name == MACH_MSG_TYPE_MOVE_SEND_ONCE) { - if ((bits & MACH_PORT_TYPE_SEND_ONCE) == 0) + if (reply_type == MACH_MSG_TYPE_MOVE_SEND_ONCE) { + if ((bits & MACH_PORT_TYPE_SEND_ONCE) == 0) { return FALSE; + } } else { - if ((bits & MACH_PORT_TYPE_SEND) == 0) + if ((bits & MACH_PORT_TYPE_SEND) == 0) { return FALSE; + } } break; - } + } - default: + default: panic("ipc_right_copyin_check: strange rights"); } return TRUE; } +/* + * Routine: ipc_right_copyin_check_guard_locked + * Purpose: + * Check if the port is guarded and the guard + * value matches the one passed in the arguments. + * If MACH_MSG_GUARD_FLAGS_UNGUARDED_ON_SEND is set, + * check if the port is unguarded. + * Conditions: + * The port is locked. + * Returns: + * KERN_SUCCESS Port is either unguarded + * or guarded with expected value + * KERN_INVALID_ARGUMENT Port is either unguarded already or guard mismatch. + * This also raises a EXC_GUARD exception. + */ +static kern_return_t +ipc_right_copyin_check_guard_locked( + mach_port_name_t name, + ipc_port_t port, + mach_port_context_t context, + mach_msg_guard_flags_t *guard_flags) +{ + mach_msg_guard_flags_t flags = *guard_flags; + if ((flags & MACH_MSG_GUARD_FLAGS_UNGUARDED_ON_SEND) && !port->ip_guarded && !context) { + return KERN_SUCCESS; + } else if (port->ip_guarded && (port->ip_context == context)) { + return KERN_SUCCESS; + } + + /* Incorrect guard; Raise exception */ + mach_port_guard_exception(name, context, port->ip_context, kGUARD_EXC_INCORRECT_GUARD); + return KERN_INVALID_ARGUMENT; +} + /* * Routine: ipc_right_copyin * Purpose: @@ -1472,184 +1943,205 @@ ipc_right_copyin_check( * Returns: * KERN_SUCCESS Acquired an object, possibly IO_DEAD. * KERN_INVALID_RIGHT Name doesn't denote correct right. + * KERN_INVALID_CAPABILITY Trying to move an kobject port or an immovable right + * KERN_INVALID_ARGUMENT Port is unguarded or guard mismatch */ kern_return_t ipc_right_copyin( - ipc_space_t space, - mach_port_name_t name, - ipc_entry_t entry, - mach_msg_type_name_t msgt_name, - boolean_t deadok, - ipc_object_t *objectp, - ipc_port_t *sorightp) + ipc_space_t space, + mach_port_name_t name, + ipc_entry_t entry, + mach_msg_type_name_t msgt_name, + ipc_right_copyin_flags_t flags, + ipc_object_t *objectp, + ipc_port_t *sorightp, + ipc_port_t *releasep, + int *assertcntp, + mach_port_context_t context, + mach_msg_guard_flags_t *guard_flags) { ipc_entry_bits_t bits; -#if CONFIG_MACF_MACH - task_t self = current_task(); - int rc; -#endif - + ipc_port_t port; + kern_return_t kr; + boolean_t deadok = flags & IPC_RIGHT_COPYIN_FLAGS_DEADOK? TRUE : FALSE; + boolean_t allow_imm_send = flags & IPC_RIGHT_COPYIN_FLAGS_ALLOW_IMMOVABLE_SEND? TRUE : FALSE; + + *releasep = IP_NULL; + *assertcntp = 0; + bits = entry->ie_bits; - assert(space->is_active); + assert(is_active(space)); switch (msgt_name) { - case MACH_MSG_TYPE_MAKE_SEND: { - ipc_port_t port; - - if ((bits & MACH_PORT_TYPE_RECEIVE) == 0) + case MACH_MSG_TYPE_MAKE_SEND: { + if ((bits & MACH_PORT_TYPE_RECEIVE) == 0) { goto invalid_right; + } - port = (ipc_port_t) entry->ie_object; + port = ip_object_to_port(entry->ie_object); assert(port != IP_NULL); ip_lock(port); - assert(ip_active(port)); assert(port->ip_receiver_name == name); assert(port->ip_receiver == space); -#if CONFIG_MACF_MACH - tasklabel_lock(self); - rc = mac_port_check_make_send(&self->maclabel, &port->ip_label); - tasklabel_unlock(self); - if (rc) { - ip_unlock(port); - return KERN_NO_ACCESS; - } -#endif - - port->ip_mscount++; - port->ip_srights++; - ip_reference(port); + ipc_port_make_send_locked(port); ip_unlock(port); - *objectp = (ipc_object_t) port; + *objectp = ip_to_object(port); *sorightp = IP_NULL; break; - } - - case MACH_MSG_TYPE_MAKE_SEND_ONCE: { - ipc_port_t port; + } - if ((bits & MACH_PORT_TYPE_RECEIVE) == 0) + case MACH_MSG_TYPE_MAKE_SEND_ONCE: { + if ((bits & MACH_PORT_TYPE_RECEIVE) == 0) { goto invalid_right; + } - port = (ipc_port_t) entry->ie_object; + port = ip_object_to_port(entry->ie_object); assert(port != IP_NULL); ip_lock(port); - assert(ip_active(port)); + require_ip_active(port); assert(port->ip_receiver_name == name); assert(port->ip_receiver == space); -#if CONFIG_MACF_MACH - tasklabel_lock(self); - rc = mac_port_check_make_send_once(&self->maclabel, &port->ip_label); - tasklabel_unlock(self); - if (rc) { - ip_unlock(port); - return KERN_NO_ACCESS; - } -#endif - - port->ip_sorights++; - ip_reference(port); + ipc_port_make_sonce_locked(port); ip_unlock(port); - *objectp = (ipc_object_t) port; + *objectp = ip_to_object(port); *sorightp = IP_NULL; break; - } + } - case MACH_MSG_TYPE_MOVE_RECEIVE: { - ipc_port_t port; - ipc_port_t dnrequest = IP_NULL; + case MACH_MSG_TYPE_MOVE_RECEIVE: { + ipc_port_t request = IP_NULL; - if ((bits & MACH_PORT_TYPE_RECEIVE) == 0) + if ((bits & MACH_PORT_TYPE_RECEIVE) == 0) { goto invalid_right; + } + + /* + * Disallow moving receive-right kobjects, e.g. mk_timer ports + * The ipc_port structure uses the kdata union of kobject and + * imp_task exclusively. Thus, general use of a kobject port as + * a receive right can cause type confusion in the importance + * code. + */ + if (io_kotype(entry->ie_object) != IKOT_NONE) { + /* + * Distinguish an invalid right, e.g., trying to move + * a send right as a receive right, from this + * situation which is, "This is a valid receive right, + * but it's also a kobject and you can't move it." + */ + mach_port_guard_exception(name, 0, 0, kGUARD_EXC_IMMOVABLE); + return KERN_INVALID_CAPABILITY; + } - port = (ipc_port_t) entry->ie_object; + port = ip_object_to_port(entry->ie_object); assert(port != IP_NULL); ip_lock(port); - assert(ip_active(port)); + require_ip_active(port); assert(port->ip_receiver_name == name); assert(port->ip_receiver == space); -#if CONFIG_MACF_MACH - tasklabel_lock(self); - rc = mac_port_check_move_receive(&self->maclabel, - &port->ip_label); - tasklabel_unlock(self); - if (rc) { + if (port->ip_immovable_receive) { + assert(port->ip_receiver != ipc_space_kernel); ip_unlock(port); - return KERN_NO_ACCESS; + assert(current_task() != kernel_task); + mach_port_guard_exception(name, 0, 0, kGUARD_EXC_IMMOVABLE); + return KERN_INVALID_CAPABILITY; + } + + if (guard_flags != NULL) { + kr = ipc_right_copyin_check_guard_locked(name, port, context, guard_flags); + if (kr != KERN_SUCCESS) { + ip_unlock(port); + return kr; + } } -#endif if (bits & MACH_PORT_TYPE_SEND) { assert(IE_BITS_TYPE(bits) == - MACH_PORT_TYPE_SEND_RECEIVE); + MACH_PORT_TYPE_SEND_RECEIVE); assert(IE_BITS_UREFS(bits) > 0); assert(port->ip_srights > 0); - ipc_hash_insert(space, (ipc_object_t) port, - name, entry); + ipc_hash_insert(space, ip_to_object(port), + name, entry); ip_reference(port); } else { assert(IE_BITS_TYPE(bits) == MACH_PORT_TYPE_RECEIVE); assert(IE_BITS_UREFS(bits) == 0); - dnrequest = ipc_right_dncancel_macro(space, port, - name, entry); + request = ipc_right_request_cancel_macro(space, port, + name, entry); entry->ie_object = IO_NULL; } - entry->ie_bits = bits &~ MACH_PORT_TYPE_RECEIVE; + entry->ie_bits = bits & ~MACH_PORT_TYPE_RECEIVE; + entry->ie_bits |= MACH_PORT_TYPE_EX_RECEIVE; + ipc_entry_modified(space, name, entry); + + /* ipc_port_clear_receiver unguards the port and clears the ip_immovable_receive bit */ + (void)ipc_port_clear_receiver(port, FALSE); /* don't destroy the port/mqueue */ + if (guard_flags != NULL) { + /* this flag will be cleared during copyout */ + *guard_flags = *guard_flags | MACH_MSG_GUARD_FLAGS_UNGUARDED_ON_SEND; + } - ipc_port_clear_receiver(port); +#if IMPORTANCE_INHERITANCE + /* + * Account for boosts the current task is going to lose when + * copying this right in. Tempowner ports have either not + * been accounting to any task (and therefore are already in + * "limbo" state w.r.t. assertions) or to some other specific + * task. As we have no way to drop the latter task's assertions + * here, We'll deduct those when we enqueue it on its + * destination port (see ipc_port_check_circularity()). + */ + if (port->ip_tempowner == 0) { + assert(IIT_NULL == port->ip_imp_task); + + /* ports in limbo have to be tempowner */ + port->ip_tempowner = 1; + *assertcntp = port->ip_impcount; + } +#endif /* IMPORTANCE_INHERITANCE */ - port->ip_receiver_name = MACH_PORT_NULL; - port->ip_destination = IP_NULL; ip_unlock(port); - *objectp = (ipc_object_t) port; - *sorightp = dnrequest; + *objectp = ip_to_object(port); + *sorightp = request; break; - } - - case MACH_MSG_TYPE_COPY_SEND: { - ipc_port_t port; + } - if (bits & MACH_PORT_TYPE_DEAD_NAME) + case MACH_MSG_TYPE_COPY_SEND: { + if (bits & MACH_PORT_TYPE_DEAD_NAME) { goto copy_dead; + } /* allow for dead send-once rights */ - if ((bits & MACH_PORT_TYPE_SEND_RIGHTS) == 0) + if ((bits & MACH_PORT_TYPE_SEND_RIGHTS) == 0) { goto invalid_right; + } assert(IE_BITS_UREFS(bits) > 0); - port = (ipc_port_t) entry->ie_object; + port = ip_object_to_port(entry->ie_object); assert(port != IP_NULL); - if (ipc_right_check(space, port, name, entry)) { + if (ipc_right_check(space, port, name, entry, IPC_RIGHT_COPYIN_FLAGS_NONE)) { bits = entry->ie_bits; + *releasep = port; goto copy_dead; } /* port is locked and active */ -#if CONFIG_MACF_MACH - tasklabel_lock(self); - rc = mac_port_check_copy_send(&self->maclabel, &port->ip_label); - tasklabel_unlock(self); - if (rc) { - ip_unlock(port); - return KERN_NO_ACCESS; - } -#endif - if ((bits & MACH_PORT_TYPE_SEND) == 0) { assert(IE_BITS_TYPE(bits) == MACH_PORT_TYPE_SEND_ONCE); assert(port->ip_sorights > 0); @@ -1658,51 +2150,45 @@ ipc_right_copyin( goto invalid_right; } - assert(port->ip_srights > 0); + if (!allow_imm_send && port->ip_immovable_send) { + ip_unlock(port); + mach_port_guard_exception(name, 0, 0, kGUARD_EXC_IMMOVABLE); + return KERN_INVALID_CAPABILITY; + } - port->ip_srights++; - ip_reference(port); + ipc_port_copy_send_locked(port); ip_unlock(port); - *objectp = (ipc_object_t) port; + *objectp = ip_to_object(port); *sorightp = IP_NULL; break; - } + } - case MACH_MSG_TYPE_MOVE_SEND: { - ipc_port_t port; - ipc_port_t dnrequest = IP_NULL; + case MACH_MSG_TYPE_MOVE_SEND: { + ipc_port_t request = IP_NULL; - if (bits & MACH_PORT_TYPE_DEAD_NAME) + if (bits & MACH_PORT_TYPE_DEAD_NAME) { goto move_dead; + } /* allow for dead send-once rights */ - if ((bits & MACH_PORT_TYPE_SEND_RIGHTS) == 0) + if ((bits & MACH_PORT_TYPE_SEND_RIGHTS) == 0) { goto invalid_right; + } assert(IE_BITS_UREFS(bits) > 0); - port = (ipc_port_t) entry->ie_object; + port = ip_object_to_port(entry->ie_object); assert(port != IP_NULL); - if (ipc_right_check(space, port, name, entry)) { + if (ipc_right_check(space, port, name, entry, IPC_RIGHT_COPYIN_FLAGS_NONE)) { bits = entry->ie_bits; + *releasep = port; goto move_dead; } /* port is locked and active */ -#if CONFIG_MACF_MACH - tasklabel_lock (self); - rc = mac_port_check_copy_send (&self->maclabel, &port->ip_label); - tasklabel_unlock (self); - if (rc) - { - ip_unlock (port); - return KERN_NO_ACCESS; - } -#endif - if ((bits & MACH_PORT_TYPE_SEND) == 0) { assert(IE_BITS_TYPE(bits) == MACH_PORT_TYPE_SEND_ONCE); assert(port->ip_sorights > 0); @@ -1711,74 +2197,79 @@ ipc_right_copyin( goto invalid_right; } - assert(port->ip_srights > 0); + if (!allow_imm_send && port->ip_immovable_send) { + ip_unlock(port); + mach_port_guard_exception(name, 0, 0, kGUARD_EXC_IMMOVABLE); + return KERN_INVALID_CAPABILITY; + } if (IE_BITS_UREFS(bits) == 1) { + assert(port->ip_srights > 0); if (bits & MACH_PORT_TYPE_RECEIVE) { assert(port->ip_receiver_name == name); assert(port->ip_receiver == space); assert(IE_BITS_TYPE(bits) == - MACH_PORT_TYPE_SEND_RECEIVE); + MACH_PORT_TYPE_SEND_RECEIVE); ip_reference(port); } else { assert(IE_BITS_TYPE(bits) == - MACH_PORT_TYPE_SEND); + MACH_PORT_TYPE_SEND); - dnrequest = ipc_right_dncancel_macro(space, port, - name, entry); - ipc_hash_delete(space, (ipc_object_t) port, - name, entry); + request = ipc_right_request_cancel_macro(space, port, + name, entry); + ipc_hash_delete(space, ip_to_object(port), + name, entry); entry->ie_object = IO_NULL; + /* transfer entry's reference to caller */ } - entry->ie_bits = bits &~ - (IE_BITS_UREFS_MASK|MACH_PORT_TYPE_SEND); + entry->ie_bits = bits & ~ + (IE_BITS_UREFS_MASK | MACH_PORT_TYPE_SEND); } else { - port->ip_srights++; - ip_reference(port); - entry->ie_bits = bits-1; /* decrement urefs */ + ipc_port_copy_send_locked(port); + /* if urefs are pegged due to overflow, leave them pegged */ + if (IE_BITS_UREFS(bits) < MACH_PORT_UREFS_MAX) { + entry->ie_bits = bits - 1; /* decrement urefs */ + } } + ipc_entry_modified(space, name, entry); ip_unlock(port); - *objectp = (ipc_object_t) port; - *sorightp = dnrequest; + *objectp = ip_to_object(port); + *sorightp = request; break; - } + } - case MACH_MSG_TYPE_MOVE_SEND_ONCE: { - ipc_port_t port; - ipc_port_t dnrequest; + case MACH_MSG_TYPE_MOVE_SEND_ONCE: { + ipc_port_t request; - if (bits & MACH_PORT_TYPE_DEAD_NAME) + if (bits & MACH_PORT_TYPE_DEAD_NAME) { goto move_dead; + } /* allow for dead send rights */ - if ((bits & MACH_PORT_TYPE_SEND_RIGHTS) == 0) + if ((bits & MACH_PORT_TYPE_SEND_RIGHTS) == 0) { goto invalid_right; + } assert(IE_BITS_UREFS(bits) > 0); - port = (ipc_port_t) entry->ie_object; + port = ip_object_to_port(entry->ie_object); assert(port != IP_NULL); - if (ipc_right_check(space, port, name, entry)) { + if (ipc_right_check(space, port, name, entry, flags)) { bits = entry->ie_bits; + *releasep = port; goto move_dead; } - /* port is locked and active */ - -#if CONFIG_MACF_MACH - tasklabel_lock (self); - rc = mac_port_check_copy_send (&self->maclabel, &port->ip_label); - tasklabel_unlock (self); - if (rc) - { - ip_unlock (port); - return KERN_NO_ACCESS; - } -#endif + /* + * port is locked, but may not be active: + * Allow copyin of inactive ports with no dead name request and treat it + * as if the copyin of the port was successful and port became inactive + * later. + */ if ((bits & MACH_PORT_TYPE_SEND_ONCE) == 0) { assert(bits & MACH_PORT_TYPE_SEND); @@ -1788,145 +2279,74 @@ ipc_right_copyin( goto invalid_right; } + if (!allow_imm_send && port->ip_immovable_send) { + ip_unlock(port); + mach_port_guard_exception(name, 0, 0, kGUARD_EXC_IMMOVABLE); + return KERN_INVALID_CAPABILITY; + } + assert(IE_BITS_TYPE(bits) == MACH_PORT_TYPE_SEND_ONCE); assert(IE_BITS_UREFS(bits) == 1); assert(port->ip_sorights > 0); - dnrequest = ipc_right_dncancel_macro(space, port, name, entry); + request = ipc_right_request_cancel_macro(space, port, name, entry); ip_unlock(port); entry->ie_object = IO_NULL; - entry->ie_bits = bits &~ - (IE_BITS_UREFS_MASK | MACH_PORT_TYPE_SEND_ONCE); - - *objectp = (ipc_object_t) port; - *sorightp = dnrequest; + entry->ie_bits = bits & ~ + (IE_BITS_UREFS_MASK | MACH_PORT_TYPE_SEND_ONCE); + ipc_entry_modified(space, name, entry); + *objectp = ip_to_object(port); + *sorightp = request; break; - } + } - default: - invalid_right: + default: +invalid_right: return KERN_INVALID_RIGHT; } return KERN_SUCCESS; - copy_dead: +copy_dead: assert(IE_BITS_TYPE(bits) == MACH_PORT_TYPE_DEAD_NAME); assert(IE_BITS_UREFS(bits) > 0); - assert(entry->ie_request == 0); + assert(entry->ie_request == IE_REQ_NONE); assert(entry->ie_object == 0); - if (!deadok) + if (!deadok) { goto invalid_right; + } *objectp = IO_DEAD; *sorightp = IP_NULL; return KERN_SUCCESS; - move_dead: +move_dead: assert(IE_BITS_TYPE(bits) == MACH_PORT_TYPE_DEAD_NAME); assert(IE_BITS_UREFS(bits) > 0); - assert(entry->ie_request == 0); + assert(entry->ie_request == IE_REQ_NONE); assert(entry->ie_object == 0); - if (!deadok) + if (!deadok) { goto invalid_right; + } if (IE_BITS_UREFS(bits) == 1) { bits &= ~MACH_PORT_TYPE_DEAD_NAME; } - entry->ie_bits = bits-1; /* decrement urefs */ - + /* if urefs are pegged due to overflow, leave them pegged */ + if (IE_BITS_UREFS(bits) < MACH_PORT_UREFS_MAX) { + entry->ie_bits = bits - 1; /* decrement urefs */ + } + ipc_entry_modified(space, name, entry); *objectp = IO_DEAD; *sorightp = IP_NULL; return KERN_SUCCESS; - } /* - * Routine: ipc_right_copyin_undo - * Purpose: - * Undoes the effects of an ipc_right_copyin - * of a send/send-once right that is dead. - * (Object is either IO_DEAD or a dead port.) - * Conditions: - * The space is write-locked and active. - */ - -void -ipc_right_copyin_undo( - ipc_space_t space, - mach_port_name_t name, - ipc_entry_t entry, - mach_msg_type_name_t msgt_name, - ipc_object_t object, - ipc_port_t soright) -{ - ipc_entry_bits_t bits; - - bits = entry->ie_bits; - - assert(space->is_active); - - assert((msgt_name == MACH_MSG_TYPE_MOVE_SEND) || - (msgt_name == MACH_MSG_TYPE_COPY_SEND) || - (msgt_name == MACH_MSG_TYPE_MOVE_SEND_ONCE)); - - if (soright != IP_NULL) { - assert((msgt_name == MACH_MSG_TYPE_MOVE_SEND) || - (msgt_name == MACH_MSG_TYPE_MOVE_SEND_ONCE)); - assert(IE_BITS_TYPE(bits) == MACH_PORT_TYPE_NONE); - assert(object != IO_DEAD); - - entry->ie_bits = ((bits &~ IE_BITS_RIGHT_MASK) | - MACH_PORT_TYPE_DEAD_NAME | 2); - - } else if (IE_BITS_TYPE(bits) == MACH_PORT_TYPE_NONE) { - assert((msgt_name == MACH_MSG_TYPE_MOVE_SEND) || - (msgt_name == MACH_MSG_TYPE_MOVE_SEND_ONCE)); - - entry->ie_bits = ((bits &~ IE_BITS_RIGHT_MASK) | - MACH_PORT_TYPE_DEAD_NAME | 1); - } else if (IE_BITS_TYPE(bits) == MACH_PORT_TYPE_DEAD_NAME) { - assert(object == IO_DEAD); - assert(IE_BITS_UREFS(bits) > 0); - - if (msgt_name != MACH_MSG_TYPE_COPY_SEND) { - assert(IE_BITS_UREFS(bits) < MACH_PORT_UREFS_MAX); - entry->ie_bits = bits+1; /* increment urefs */ - } - } else { - assert((msgt_name == MACH_MSG_TYPE_MOVE_SEND) || - (msgt_name == MACH_MSG_TYPE_COPY_SEND)); - assert(IE_BITS_TYPE(bits) == MACH_PORT_TYPE_SEND); - assert(object != IO_DEAD); - assert(entry->ie_object == object); - assert(IE_BITS_UREFS(bits) > 0); - - if (msgt_name != MACH_MSG_TYPE_COPY_SEND) { - assert(IE_BITS_UREFS(bits) < MACH_PORT_UREFS_MAX-1); - entry->ie_bits = bits+1; /* increment urefs */ - } - - /* - * May as well convert the entry to a dead name. - * (Or if it is a compat entry, destroy it.) - */ - - (void) ipc_right_check(space, (ipc_port_t) object, - name, entry); - /* object is dead so it is not locked */ - } - - /* release the reference acquired by copyin */ - - if (object != IO_DEAD) - ipc_object_release(object); -} - -/* - * Routine: ipc_right_copyin_two + * Routine: ipc_right_copyin_two_move_sends * Purpose: * Like ipc_right_copyin with MACH_MSG_TYPE_MOVE_SEND * and deadok == FALSE, except that this moves two @@ -1938,94 +2358,246 @@ ipc_right_copyin_undo( * KERN_SUCCESS Acquired an object. * KERN_INVALID_RIGHT Name doesn't denote correct right. */ - +static kern_return_t -ipc_right_copyin_two( - ipc_space_t space, - mach_port_name_t name, - ipc_entry_t entry, - ipc_object_t *objectp, - ipc_port_t *sorightp) +ipc_right_copyin_two_move_sends( + ipc_space_t space, + mach_port_name_t name, + ipc_entry_t entry, + ipc_object_t *objectp, + ipc_port_t *sorightp, + ipc_port_t *releasep) { ipc_entry_bits_t bits; mach_port_urefs_t urefs; ipc_port_t port; - ipc_port_t dnrequest = IP_NULL; -#if CONFIG_MACF_MACH - task_t self = current_task(); - int rc; -#endif + ipc_port_t request = IP_NULL; + + *releasep = IP_NULL; - assert(space->is_active); + assert(is_active(space)); bits = entry->ie_bits; - if ((bits & MACH_PORT_TYPE_SEND) == 0) + if ((bits & MACH_PORT_TYPE_SEND) == 0) { goto invalid_right; + } urefs = IE_BITS_UREFS(bits); - if (urefs < 2) + if (urefs < 2) { goto invalid_right; + } - port = (ipc_port_t) entry->ie_object; + port = ip_object_to_port(entry->ie_object); assert(port != IP_NULL); - if (ipc_right_check(space, port, name, entry)) { + if (ipc_right_check(space, port, name, entry, IPC_RIGHT_COPYIN_FLAGS_NONE)) { + *releasep = port; goto invalid_right; } /* port is locked and active */ -#if CONFIG_MACF_MACH - tasklabel_lock(self); - rc = mac_port_check_copy_send(&self->maclabel, &port->ip_label); - tasklabel_unlock(self); - if (rc) { - ip_unlock(port); - return KERN_NO_ACCESS; - } -#endif - - assert(port->ip_srights > 0); + if (urefs > 2) { + /* + * We are moving 2 urefs as naked send rights, which is decomposed as: + * - two copy sends (which doesn't affect the make send count) + * - decrementing the local urefs twice. + */ + ipc_port_copy_send_locked(port); + ipc_port_copy_send_locked(port); + /* if urefs are pegged due to overflow, leave them pegged */ + if (IE_BITS_UREFS(bits) < MACH_PORT_UREFS_MAX) { + entry->ie_bits = bits - 2; /* decrement urefs */ + } + } else { + /* + * We have exactly 2 send rights for this port in this space, + * which means that we will liberate the naked send right held + * by this entry. + * + * However refcounting rules around entries are that naked send rights + * on behalf of spaces do not have an associated port reference, + * so we need to donate one ... + */ + ipc_port_copy_send_locked(port); - if (urefs == 2) { if (bits & MACH_PORT_TYPE_RECEIVE) { assert(port->ip_receiver_name == name); assert(port->ip_receiver == space); assert(IE_BITS_TYPE(bits) == - MACH_PORT_TYPE_SEND_RECEIVE); + MACH_PORT_TYPE_SEND_RECEIVE); - port->ip_srights++; - ip_reference(port); + /* ... that we inject manually when the entry stays alive */ ip_reference(port); } else { assert(IE_BITS_TYPE(bits) == MACH_PORT_TYPE_SEND); - dnrequest = ipc_right_dncancel_macro(space, port, - name, entry); + /* ... that we steal from the entry when it dies */ + request = ipc_right_request_cancel_macro(space, port, + name, entry); - port->ip_srights++; - ip_reference(port); - ipc_hash_delete(space, (ipc_object_t) port, - name, entry); + ipc_hash_delete(space, ip_to_object(port), + name, entry); entry->ie_object = IO_NULL; } - entry->ie_bits = bits &~ (IE_BITS_UREFS_MASK|MACH_PORT_TYPE_SEND); - } else { - port->ip_srights += 2; - ip_reference(port); - ip_reference(port); - entry->ie_bits = bits-2; /* decrement urefs */ + + entry->ie_bits = bits & ~(IE_BITS_UREFS_MASK | MACH_PORT_TYPE_SEND); } + ipc_entry_modified(space, name, entry); + ip_unlock(port); - *objectp = (ipc_object_t) port; - *sorightp = dnrequest; + *objectp = ip_to_object(port); + *sorightp = request; return KERN_SUCCESS; - invalid_right: +invalid_right: return KERN_INVALID_RIGHT; } + +/* + * Routine: ipc_right_copyin_two + * Purpose: + * Like ipc_right_copyin with two dispositions, + * each of which results in a send or send-once right, + * and deadok = FALSE. + * Conditions: + * The space is write-locked and active. + * The object is returned with two refs/rights. + * Msgt_one refers to the dest_type + * Returns: + * KERN_SUCCESS Acquired an object. + * KERN_INVALID_RIGHT Name doesn't denote correct right(s). + * KERN_INVALID_CAPABILITY Name doesn't denote correct right for msgt_two. + */ +kern_return_t +ipc_right_copyin_two( + ipc_space_t space, + mach_port_name_t name, + ipc_entry_t entry, + mach_msg_type_name_t msgt_one, + mach_msg_type_name_t msgt_two, + ipc_object_t *objectp, + ipc_port_t *sorightp, + ipc_port_t *releasep) +{ + kern_return_t kr; + int assertcnt = 0; + + assert(MACH_MSG_TYPE_PORT_ANY_SEND(msgt_one)); + assert(MACH_MSG_TYPE_PORT_ANY_SEND(msgt_two)); + + /* + * This is a little tedious to make atomic, because + * there are 25 combinations of valid dispositions. + * However, most are easy. + */ + + /* + * If either is move-sonce, then there must be an error. + */ + if (msgt_one == MACH_MSG_TYPE_MOVE_SEND_ONCE || + msgt_two == MACH_MSG_TYPE_MOVE_SEND_ONCE) { + return KERN_INVALID_RIGHT; + } + + if ((msgt_one == MACH_MSG_TYPE_MAKE_SEND) || + (msgt_one == MACH_MSG_TYPE_MAKE_SEND_ONCE) || + (msgt_two == MACH_MSG_TYPE_MAKE_SEND) || + (msgt_two == MACH_MSG_TYPE_MAKE_SEND_ONCE)) { + /* + * One of the dispositions needs a receive right. + * + * If the copyin below succeeds, we know the receive + * right is there (because the pre-validation of + * the second disposition already succeeded in our + * caller). + * + * Hence the port is not in danger of dying. + */ + ipc_object_t object_two; + + kr = ipc_right_copyin(space, name, entry, + msgt_one, IPC_RIGHT_COPYIN_FLAGS_ALLOW_IMMOVABLE_SEND, + objectp, sorightp, releasep, + &assertcnt, 0, NULL); + assert(assertcnt == 0); + if (kr != KERN_SUCCESS) { + return kr; + } + + assert(IO_VALID(*objectp)); + assert(*sorightp == IP_NULL); + assert(*releasep == IP_NULL); + + /* + * Now copyin the second (previously validated) + * disposition. The result can't be a dead port, + * as no valid disposition can make us lose our + * receive right. + */ + kr = ipc_right_copyin(space, name, entry, + msgt_two, IPC_RIGHT_COPYIN_FLAGS_NONE, + &object_two, sorightp, releasep, + &assertcnt, 0, NULL); + assert(assertcnt == 0); + assert(kr == KERN_SUCCESS); + assert(*sorightp == IP_NULL); + assert(*releasep == IP_NULL); + assert(object_two == *objectp); + assert(entry->ie_bits & MACH_PORT_TYPE_RECEIVE); + } else if ((msgt_one == MACH_MSG_TYPE_MOVE_SEND) && + (msgt_two == MACH_MSG_TYPE_MOVE_SEND)) { + /* + * This is an easy case. Just use our + * handy-dandy special-purpose copyin call + * to get two send rights for the price of one. + */ + kr = ipc_right_copyin_two_move_sends(space, name, entry, + objectp, sorightp, + releasep); + if (kr != KERN_SUCCESS) { + return kr; + } + } else { + mach_msg_type_name_t msgt_name; + + /* + * Must be either a single move-send and a + * copy-send, or two copy-send dispositions. + * Use the disposition with the greatest side + * effects for the actual copyin - then just + * duplicate the send right you get back. + */ + if (msgt_one == MACH_MSG_TYPE_MOVE_SEND || + msgt_two == MACH_MSG_TYPE_MOVE_SEND) { + msgt_name = MACH_MSG_TYPE_MOVE_SEND; + } else { + msgt_name = MACH_MSG_TYPE_COPY_SEND; + } + + kr = ipc_right_copyin(space, name, entry, + msgt_name, IPC_RIGHT_COPYIN_FLAGS_ALLOW_IMMOVABLE_SEND, + objectp, sorightp, releasep, + &assertcnt, 0, NULL); + assert(assertcnt == 0); + if (kr != KERN_SUCCESS) { + return kr; + } + + /* + * Copy the right we got back. If it is dead now, + * that's OK. Neither right will be usable to send + * a message anyway. + */ + (void)ipc_port_copy_send(ip_object_to_port(*objectp)); + } + + return KERN_SUCCESS; +} + + /* * Routine: ipc_right_copyout * Purpose: @@ -2036,34 +2608,29 @@ ipc_right_copyin_two( * because user-reference overflow isn't a possibility. * * If copying out the object would cause the user-reference - * count in the entry to overflow, and overflow is TRUE, - * then instead the user-reference count is left pegged - * to its maximum value and the copyout succeeds anyway. + * count in the entry to overflow, then the user-reference + * count is left pegged to its maximum value and the copyout + * succeeds anyway. * Conditions: * The space is write-locked and active. * The object is locked and active. * The object is unlocked; the space isn't. * Returns: * KERN_SUCCESS Copied out capability. - * KERN_UREFS_OVERFLOW User-refs would overflow; - * guaranteed not to happen with a fresh entry - * or if overflow=TRUE was specified. */ kern_return_t ipc_right_copyout( - ipc_space_t space, - mach_port_name_t name, - ipc_entry_t entry, - mach_msg_type_name_t msgt_name, - boolean_t overflow, - ipc_object_t object) + ipc_space_t space, + mach_port_name_t name, + ipc_entry_t entry, + mach_msg_type_name_t msgt_name, + mach_port_context_t *context, + mach_msg_guard_flags_t *guard_flags, + ipc_object_t object) { ipc_entry_bits_t bits; ipc_port_t port; -#if CONFIG_MACF_MACH - int rc; -#endif bits = entry->ie_bits; @@ -2072,81 +2639,60 @@ ipc_right_copyout( assert(io_active(object)); assert(entry->ie_object == object); - port = (ipc_port_t) object; + port = ip_object_to_port(object); switch (msgt_name) { - case MACH_MSG_TYPE_PORT_SEND_ONCE: - + case MACH_MSG_TYPE_PORT_SEND_ONCE: + assert(IE_BITS_TYPE(bits) == MACH_PORT_TYPE_NONE); + assert(IE_BITS_UREFS(bits) == 0); assert(port->ip_sorights > 0); -#if CONFIG_MACF_MACH - if (space->is_task) { - tasklabel_lock(space->is_task); - rc = mac_port_check_hold_send_once(&space->is_task->maclabel, - &port->ip_label); - tasklabel_unlock(space->is_task); - - if (rc) { - ip_unlock(port); - return KERN_NO_ACCESS; - } + if (port->ip_specialreply) { + ipc_port_adjust_special_reply_port_locked(port, + current_thread()->ith_knote, IPC_PORT_ADJUST_SR_LINK_WORKLOOP, FALSE); + /* port unlocked on return */ + } else { + ip_unlock(port); } -#endif - /* transfer send-once right and ref to entry */ - ip_unlock(port); - entry->ie_bits = bits | (MACH_PORT_TYPE_SEND_ONCE | 1); + entry->ie_bits = bits | (MACH_PORT_TYPE_SEND_ONCE | 1); /* set urefs to 1 */ + ipc_entry_modified(space, name, entry); break; - case MACH_MSG_TYPE_PORT_SEND: + case MACH_MSG_TYPE_PORT_SEND: assert(port->ip_srights > 0); -#if CONFIG_MACF_MACH - if (space->is_task) { - tasklabel_lock(space->is_task); - rc = mac_port_check_hold_send(&space->is_task->maclabel, - &port->ip_label); - tasklabel_unlock(space->is_task); - - if (rc) { - ip_unlock(port); - return KERN_NO_ACCESS; - } - } -#endif - if (bits & MACH_PORT_TYPE_SEND) { mach_port_urefs_t urefs = IE_BITS_UREFS(bits); assert(port->ip_srights > 1); assert(urefs > 0); - assert(urefs < MACH_PORT_UREFS_MAX); - - if (urefs+1 == MACH_PORT_UREFS_MAX) { - if (overflow) { - /* leave urefs pegged to maximum */ + assert(urefs <= MACH_PORT_UREFS_MAX); - port->ip_srights--; - ip_release(port); - ip_unlock(port); - return KERN_SUCCESS; - } + if (urefs == MACH_PORT_UREFS_MAX) { + /* + * leave urefs pegged to maximum, + * consume send right and ref + */ + port->ip_srights--; ip_unlock(port); - return KERN_UREFS_OVERFLOW; + ip_release(port); + return KERN_SUCCESS; } + /* consume send right and ref */ port->ip_srights--; - ip_release(port); ip_unlock(port); + ip_release(port); } else if (bits & MACH_PORT_TYPE_RECEIVE) { assert(IE_BITS_TYPE(bits) == MACH_PORT_TYPE_RECEIVE); assert(IE_BITS_UREFS(bits) == 0); - /* transfer send right to entry */ - ip_release(port); + /* transfer send right to entry, consume ref */ ip_unlock(port); + ip_release(port); } else { assert(IE_BITS_TYPE(bits) == MACH_PORT_TYPE_NONE); assert(IE_BITS_UREFS(bits) == 0); @@ -2156,195 +2702,94 @@ ipc_right_copyout( /* entry is locked holding ref, so can use port */ - ipc_hash_insert(space, (ipc_object_t) port, - name, entry); + ipc_hash_insert(space, ip_to_object(port), name, entry); } - entry->ie_bits = (bits | MACH_PORT_TYPE_SEND) + 1; + entry->ie_bits = (bits | MACH_PORT_TYPE_SEND) + 1; /* increment urefs */ + ipc_entry_modified(space, name, entry); break; - case MACH_MSG_TYPE_PORT_RECEIVE: { + case MACH_MSG_TYPE_PORT_RECEIVE: { ipc_port_t dest; +#if IMPORTANCE_INHERITANCE + natural_t assertcnt = port->ip_impcount; +#endif /* IMPORTANCE_INHERITANCE */ assert(port->ip_mscount == 0); assert(port->ip_receiver_name == MACH_PORT_NULL); + + imq_lock(&port->ip_messages); dest = port->ip_destination; -#if CONFIG_MACF_MACH - if (space->is_task) { - tasklabel_lock(space->is_task); - rc = mac_port_check_hold_receive(&space->is_task->maclabel, - &port->ip_label); - tasklabel_unlock(space->is_task); + port->ip_receiver_name = name; + port->ip_receiver = space; - if (rc) { - ip_unlock(port); - return KERN_NO_ACCESS; + struct knote *kn = current_thread()->ith_knote; + + if ((guard_flags != NULL) && ((*guard_flags & MACH_MSG_GUARD_FLAGS_IMMOVABLE_RECEIVE) != 0)) { + assert(port->ip_immovable_receive == 0); + port->ip_guarded = 1; + port->ip_strict_guard = 0; + /* pseudo receive shouldn't set the receive right as immovable in the sender's space */ + if (kn != ITH_KNOTE_PSEUDO) { + port->ip_immovable_receive = 1; } + port->ip_context = current_thread()->ith_msg_addr; + *context = port->ip_context; + *guard_flags = *guard_flags & ~MACH_MSG_GUARD_FLAGS_UNGUARDED_ON_SEND; } -#endif - - port->ip_receiver_name = name; - port->ip_receiver = space; assert((bits & MACH_PORT_TYPE_RECEIVE) == 0); - if (bits & MACH_PORT_TYPE_SEND) { assert(IE_BITS_TYPE(bits) == MACH_PORT_TYPE_SEND); assert(IE_BITS_UREFS(bits) > 0); assert(port->ip_srights > 0); - - ip_release(port); - ip_unlock(port); - - /* entry is locked holding ref, so can use port */ - - ipc_hash_delete(space, (ipc_object_t) port, - name, entry); } else { assert(IE_BITS_TYPE(bits) == MACH_PORT_TYPE_NONE); assert(IE_BITS_UREFS(bits) == 0); - - /* transfer ref to entry */ - ip_unlock(port); } - entry->ie_bits = bits | MACH_PORT_TYPE_RECEIVE; - if (dest != IP_NULL) - ipc_port_release(dest); - break; - } - - default: - panic("ipc_right_copyout: strange rights"); - } - - return KERN_SUCCESS; -} - -/* - * Routine: ipc_right_rename - * Purpose: - * Transfer an entry from one name to another. - * The old entry is deallocated. - * Conditions: - * The space is write-locked and active. - * The new entry is unused. Upon return, - * the space is unlocked. - * Returns: - * KERN_SUCCESS Moved entry to new name. - */ - -kern_return_t -ipc_right_rename( - ipc_space_t space, - mach_port_name_t oname, - ipc_entry_t oentry, - mach_port_name_t nname, - ipc_entry_t nentry) -{ - ipc_port_request_index_t request = oentry->ie_request; - ipc_entry_bits_t bits = oentry->ie_bits; - ipc_object_t object = oentry->ie_object; - - assert(space->is_active); - assert(oname != nname); - - /* - * If IE_BITS_COMPAT, we can't allow the entry to be renamed - * if the port is dead. (This would foil ipc_port_destroy.) - * Instead we should fail because oentry shouldn't exist. - * Note IE_BITS_COMPAT implies ie_request != 0. - */ - - if (request != 0) { - ipc_port_t port; - - assert(bits & MACH_PORT_TYPE_PORT_RIGHTS); - port = (ipc_port_t) object; - assert(port != IP_NULL); - - if (ipc_right_check(space, port, oname, oentry)) { - request = 0; - object = IO_NULL; - bits = oentry->ie_bits; - assert(IE_BITS_TYPE(bits) == MACH_PORT_TYPE_DEAD_NAME); - assert(oentry->ie_request == 0); - } else { - /* port is locked and active */ - - ipc_port_dnrename(port, request, oname, nname); - ip_unlock(port); - oentry->ie_request = 0; + boolean_t sync_bootstrap_checkin = FALSE; + if (kn != ITH_KNOTE_PSEUDO && port->ip_sync_bootstrap_checkin) { + sync_bootstrap_checkin = TRUE; } - } - - /* initialize nentry before letting ipc_hash_insert see it */ - - assert((nentry->ie_bits & IE_BITS_RIGHT_MASK) == 0); - nentry->ie_bits |= bits & IE_BITS_RIGHT_MASK; - nentry->ie_request = request; - nentry->ie_object = object; - - switch (IE_BITS_TYPE(bits)) { - case MACH_PORT_TYPE_SEND: { - ipc_port_t port; - - port = (ipc_port_t) object; - assert(port != IP_NULL); - - /* remember, there are no other share entries possible */ - /* or we can't do the rename. Therefore we do not need */ - /* to check the other subspaces */ - ipc_hash_delete(space, (ipc_object_t) port, oname, oentry); - ipc_hash_insert(space, (ipc_object_t) port, nname, nentry); - break; - } - - case MACH_PORT_TYPE_RECEIVE: - case MACH_PORT_TYPE_SEND_RECEIVE: { - ipc_port_t port; - - port = (ipc_port_t) object; - assert(port != IP_NULL); - - ip_lock(port); - assert(ip_active(port)); - assert(port->ip_receiver_name == oname); - assert(port->ip_receiver == space); - - port->ip_receiver_name = nname; - ip_unlock(port); - break; - } - - case MACH_PORT_TYPE_PORT_SET: { - ipc_pset_t pset; - - pset = (ipc_pset_t) object; - assert(pset != IPS_NULL); + if (!ITH_KNOTE_VALID(kn, MACH_MSG_TYPE_PORT_RECEIVE)) { + kn = NULL; + } + ipc_port_adjust_port_locked(port, kn, sync_bootstrap_checkin); + /* port & message queue are unlocked */ - ips_lock(pset); - assert(ips_active(pset)); - assert(pset->ips_local_name == oname); + if (bits & MACH_PORT_TYPE_SEND) { + ip_release(port); - pset->ips_local_name = nname; - ips_unlock(pset); - break; - } + /* entry is locked holding ref, so can use port */ + ipc_hash_delete(space, ip_to_object(port), name, entry); + } + entry->ie_bits = bits | MACH_PORT_TYPE_RECEIVE; + ipc_entry_modified(space, name, entry); - case MACH_PORT_TYPE_SEND_ONCE: - case MACH_PORT_TYPE_DEAD_NAME: + if (dest != IP_NULL) { +#if IMPORTANCE_INHERITANCE + /* + * Deduct the assertion counts we contributed to + * the old destination port. They've already + * been reflected into the task as a result of + * getting enqueued. + */ + ip_lock(dest); + ipc_port_impcount_delta(dest, 0 - assertcnt, IP_NULL); + ip_unlock(dest); +#endif /* IMPORTANCE_INHERITANCE */ + + /* Drop turnstile ref on dest */ + ipc_port_send_turnstile_complete(dest); + ip_release(dest); + } break; - - default: - panic("ipc_right_rename: strange rights"); } - assert(oentry->ie_request == 0); - oentry->ie_object = IO_NULL; - ipc_entry_dealloc(space, oname, oentry); - is_write_unlock(space); - + default: + panic("ipc_right_copyout: strange rights"); + } return KERN_SUCCESS; }