X-Git-Url: https://git.saurik.com/apple/xnu.git/blobdiff_plain/316670eb35587141e969394ae8537d66b9211e80..c3c9b80d004dbbfdf763edeb97968c6997e3b45b:/osfmk/ipc/ipc_right.c diff --git a/osfmk/ipc/ipc_right.c b/osfmk/ipc/ipc_right.c index 46d7f1ec7..49ce92268 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; @@ -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; @@ -161,10 +163,12 @@ ipc_right_lookup_two_write( 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; @@ -178,18 +182,18 @@ ipc_right_lookup_two_write( * Translate (space, object) -> (name, entry). * Only finds send/receive rights. * Returns TRUE if an entry is found; if so, - * the object is locked and active. + * the object active. * Conditions: * The space must be locked (read or write) and active. - * Nothing else locked. + * The port is locked and active */ -boolean_t +bool 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; @@ -200,14 +204,10 @@ ipc_right_reverse( assert(is_active(space)); assert(io_otype(object) == IOT_PORT); - port = (ipc_port_t) object; - - ip_lock(port); - if (!ip_active(port)) { - ip_unlock(port); + port = ip_object_to_port(object); + require_ip_active(port); - return FALSE; - } + ip_lock_held(port); if (port->ip_receiver == space) { name = port->ip_receiver_name; @@ -217,23 +217,22 @@ 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; + 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; + return true; } - ip_unlock(port); - return FALSE; + return false; } /* @@ -253,34 +252,37 @@ 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_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_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_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_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 */ - + prev_request = entry->ie_request; /* if nothing to do or undo, we're done */ @@ -294,16 +296,17 @@ ipc_right_request_alloc( if (entry->ie_bits & MACH_PORT_TYPE_PORT_RIGHTS) { ipc_port_request_index_t new_request; - 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_OBJECT_COPYIN_FLAGS_NONE)) { /* port is locked and active */ /* if no new request, just cancel previous */ if (notify == IP_NULL) { - if (prev_request != IE_REQ_NONE) + 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); @@ -317,9 +320,10 @@ ipc_right_request_alloc( */ 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) + 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); @@ -333,12 +337,19 @@ ipc_right_request_alloc( * If there is a previous request, free it. Any subsequent * allocation cannot fail, thus assuring an atomic swap. */ - if (prev_request != IE_REQ_NONE) + if (prev_request != IE_REQ_NONE) { previous = ipc_port_request_cancel(port, name, prev_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); + send_possible, immediate, + &new_request); +#endif /* IMPORTANCE_INHERITANCE */ if (kr != KERN_SUCCESS) { assert(previous == IP_NULL); is_write_unlock(space); @@ -346,21 +357,31 @@ ipc_right_request_alloc( kr = ipc_port_request_grow(port, ITS_SIZE_NONE); /* port is unlocked */ - if (kr != KERN_SUCCESS) + if (kr != KERN_SUCCESS) { return kr; + } continue; } + assert(new_request != IE_REQ_NONE); - ip_unlock(port); entry->ie_request = new_request; ipc_entry_modified(space, name, entry); is_write_unlock(space); + +#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; } /* entry may have changed to dead-name by ipc_right_check() */ - } /* treat send_possible requests as immediate w.r.t. dead-name */ @@ -370,34 +391,33 @@ ipc_right_request_alloc( assert(urefs > 0); - if (MACH_PORT_UREFS_OVERFLOW(urefs, 1)) { - is_write_unlock(space); - if (port != IP_NULL) - ip_release(port); - return KERN_UREFS_OVERFLOW; + /* leave urefs pegged to maximum if it overflowed */ + if (urefs < MACH_PORT_UREFS_MAX) { + (entry->ie_bits)++; /* increment urefs */ } - - (entry->ie_bits)++; /* increment urefs */ ipc_entry_modified(space, name, entry); + is_write_unlock(space); - if (port != IP_NULL) + 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 (port != IP_NULL) + if (port != IP_NULL) { ip_release(port); + } - if (entry->ie_bits & MACH_PORT_TYPE_PORT_OR_DEAD) - return KERN_INVALID_ARGUMENT; - else - return KERN_INVALID_RIGHT; + return kr; } *previousp = previous; @@ -416,18 +436,19 @@ ipc_right_request_alloc( ipc_port_t ipc_right_request_cancel( - __unused ipc_space_t space, - ipc_port_t port, - mach_port_name_t name, - ipc_entry_t entry) + __unused ipc_space_t space, + ipc_port_t port, + mach_port_name_t name, + ipc_entry_t entry) { 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)); - if (entry->ie_request == IE_REQ_NONE) + if (entry->ie_request == IE_REQ_NONE) { return IP_NULL; + } previous = ipc_port_request_cancel(port, name, entry->ie_request); entry->ie_request = IE_REQ_NONE; @@ -442,30 +463,25 @@ ipc_right_request_cancel( * Returns TRUE if it is. * Conditions: * The space is write-locked and active. - * It is unlocked if the entry is inuse. */ -boolean_t +bool ipc_right_inuse( - ipc_space_t space, - __unused mach_port_name_t name, - ipc_entry_t entry) + ipc_entry_t entry) { - if (IE_BITS_TYPE(entry->ie_bits) != MACH_PORT_TYPE_NONE) { - is_write_unlock(space); - return TRUE; - } - return FALSE; + return IE_BITS_TYPE(entry->ie_bits) != MACH_PORT_TYPE_NONE; } /* * Routine: ipc_right_check * Purpose: * Check if the port has died. If it has, + * and IPC_OBJECT_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. + * 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 @@ -474,20 +490,24 @@ ipc_right_inuse( 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_object_copyin_flags_t flags) { ipc_entry_bits_t bits; assert(is_active(space)); - assert(port == (ipc_port_t) entry->ie_object); + assert(port == ip_object_to_port(entry->ie_object)); ip_lock(port); - if (ip_active(port)) + if (ip_active(port) || + ((flags & IPC_OBJECT_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 */ @@ -495,25 +515,34 @@ 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. + */ + 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 the port went dead, that notification - * must already be on its way up from the port layer. + * must already be on its way up from the port layer. * * Add the reference that the notification carries. It * is done here, and not in the notification delivery, @@ -528,10 +557,12 @@ ipc_right_check( */ if (entry->ie_request != IE_REQ_NONE) { if (ipc_port_request_type(port, name, entry->ie_request) != 0) { - assert(IE_BITS_UREFS(bits) < MACH_PORT_UREFS_MAX); - bits++; + /* 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_request = IE_REQ_NONE; } entry->ie_bits = bits; entry->ie_object = IO_NULL; @@ -551,9 +582,9 @@ ipc_right_check( void ipc_right_terminate( - 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_entry_bits_t bits; mach_port_type_t type; @@ -573,28 +604,28 @@ ipc_right_terminate( */ switch (type) { - case MACH_PORT_TYPE_DEAD_NAME: + 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 == 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; + 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; @@ -608,8 +639,8 @@ ipc_right_terminate( break; } - request = ipc_right_request_cancel_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); @@ -624,23 +655,13 @@ ipc_right_terminate( } if (type & MACH_PORT_TYPE_RECEIVE) { - wait_queue_link_t wql; - queue_head_t links_data; - queue_t links = &links_data; - assert(port->ip_receiver_name == name); assert(port->ip_receiver == space); - queue_init(links); - ipc_port_clear_receiver(port, links); - ipc_port_destroy(port); /* consumes our ref, unlocks */ - while(!queue_empty(links)) { - wql = (wait_queue_link_t) dequeue(links); - wait_queue_link_free(wql); - } - + 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 */ @@ -648,18 +669,20 @@ ipc_right_terminate( assert(port->ip_receiver != space); ip_unlock(port); - ip_release(port); + ip_release(port); } - if (nsrequest != IP_NULL) + if (nsrequest != IP_NULL) { ipc_notify_no_senders(nsrequest, mscount); + } - if (request != IP_NULL) + if (request != IP_NULL) { ipc_notify_port_deleted(request, name); + } break; - } + } - default: + default: panic("ipc_right_terminate: strange type - 0x%x", type); } } @@ -677,9 +700,11 @@ ipc_right_terminate( 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; @@ -691,7 +716,7 @@ ipc_right_destroy( assert(is_active(space)); switch (type) { - case MACH_PORT_TYPE_DEAD_NAME: + case MACH_PORT_TYPE_DEAD_NAME: assert(entry->ie_request == IE_REQ_NONE); assert(entry->ie_object == IO_NULL); @@ -699,8 +724,8 @@ ipc_right_destroy( 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 == IE_REQ_NONE); assert(pset != IPS_NULL); @@ -712,24 +737,24 @@ ipc_right_destroy( is_write_unlock(space); 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; + 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 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); @@ -744,6 +769,20 @@ ipc_right_destroy( break; } + /* 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; @@ -762,23 +801,13 @@ ipc_right_destroy( } if (type & MACH_PORT_TYPE_RECEIVE) { - queue_head_t links_data; - queue_t links = &links_data; - wait_queue_link_t wql; - - assert(ip_active(port)); + require_ip_active(port); assert(port->ip_receiver == space); - queue_init(links); - ipc_port_clear_receiver(port, links); - ipc_port_destroy(port); /* consumes our ref, unlocks */ - while(!queue_empty(links)) { - wql = (wait_queue_link_t) dequeue(links); - wait_queue_link_free(wql); - } - + 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 */ @@ -789,15 +818,19 @@ ipc_right_destroy( ip_release(port); } - if (nsrequest != IP_NULL) + if (nsrequest != IP_NULL) { ipc_notify_no_senders(nsrequest, mscount); + } - if (request != IP_NULL) + if (request != IP_NULL) { ipc_notify_port_deleted(request, name); + } + + break; - } + } - default: + default: panic("ipc_right_destroy: strange type"); } @@ -807,7 +840,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: @@ -816,13 +849,14 @@ ipc_right_destroy( * Returns: * KERN_SUCCESS A user ref was released. * KERN_INVALID_RIGHT Entry has wrong type. + * KERN_INVALID_CAPABILITY Deallocating a pinned right. */ 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; @@ -835,8 +869,28 @@ ipc_right_dealloc( 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 == IE_REQ_NONE); @@ -845,27 +899,30 @@ ipc_right_dealloc( if (IE_BITS_UREFS(bits) == 1) { ipc_entry_dealloc(space, name, entry); } else { - 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); } is_write_unlock(space); /* release any port that got converted to dead name below */ - if (port != IP_NULL) + if (port != IP_NULL) { ip_release(port); + } break; - } + } - case MACH_PORT_TYPE_SEND_ONCE: { + 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_OBJECT_COPYIN_FLAGS_NONE)) { bits = entry->ie_bits; assert(IE_BITS_TYPE(bits) == MACH_PORT_TYPE_DEAD_NAME); goto dead_name; /* it will release port */ @@ -874,6 +931,13 @@ ipc_right_dealloc( assert(port->ip_sorights > 0); + /* + * 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); @@ -884,12 +948,13 @@ ipc_right_dealloc( ipc_notify_send_once(port); - if (request != IP_NULL) + if (request != IP_NULL) { ipc_notify_port_deleted(request, name); + } break; - } + } - case MACH_PORT_TYPE_SEND: { + case MACH_PORT_TYPE_SEND: { ipc_port_t request = IP_NULL; ipc_port_t nsrequest = IP_NULL; mach_port_mscount_t mscount = 0; @@ -897,10 +962,10 @@ ipc_right_dealloc( 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_OBJECT_COPYIN_FLAGS_NONE)) { bits = entry->ie_bits; assert(IE_BITS_TYPE(bits) == MACH_PORT_TYPE_DEAD_NAME); goto dead_name; /* it will release port */ @@ -910,6 +975,14 @@ ipc_right_dealloc( assert(port->ip_srights > 0); if (IE_BITS_UREFS(bits) == 1) { + if (pinned_control_port_enabled && port->ip_pinned != 0) { + ip_unlock(port); + is_write_unlock(space); + mach_port_guard_exception(name, 0, MPG_FLAGS_MOD_REFS_PINNED_DEALLOC, + ipc_control_port_options & IPC_CONTROL_PORT_OPTIONS_PINNED_HARD ? + kGUARD_EXC_MOD_REFS : kGUARD_EXC_MOD_REFS_NON_FATAL); + return KERN_INVALID_CAPABILITY; + } if (--port->ip_srights == 0) { nsrequest = port->ip_nsrequest; if (nsrequest != IP_NULL) { @@ -919,43 +992,45 @@ ipc_right_dealloc( } request = ipc_right_request_cancel_macro(space, port, - name, entry); - ipc_hash_delete(space, (ipc_object_t) port, - name, entry); + name, entry); + ipc_hash_delete(space, ip_to_object(port), name, entry); 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); - entry->ie_bits = bits-1; /* decrement urefs */ + 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); } - - if (nsrequest != IP_NULL) + if (nsrequest != IP_NULL) { ipc_notify_no_senders(nsrequest, mscount); + } - if (request != IP_NULL) + if (request != IP_NULL) { ipc_notify_port_deleted(request, name); + } break; - } + } - case MACH_PORT_TYPE_SEND_RECEIVE: { + 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); @@ -969,23 +1044,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; } @@ -1004,22 +1084,21 @@ 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_INVALID_CAPABILITY Deallocating a pinned right. */ 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 @@ -1035,23 +1114,27 @@ ipc_right_delta( /* 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 == 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; @@ -1061,26 +1144,29 @@ ipc_right_delta( 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: { + case MACH_PORT_RIGHT_RECEIVE: { ipc_port_t request = IP_NULL; - queue_head_t links_data; - queue_t links = &links_data; - wait_queue_link_t wql; - 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); /* @@ -1090,15 +1176,24 @@ 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); if (port->ip_pdrequest != NULL) { @@ -1114,7 +1209,8 @@ ipc_right_delta( */ ipc_entry_modified(space, name, entry); entry->ie_bits &= ~MACH_PORT_TYPE_RECEIVE; - ipc_hash_insert(space, (ipc_object_t) port, + entry->ie_bits |= MACH_PORT_TYPE_EX_RECEIVE; + ipc_hash_insert(space, ip_to_object(port), name, entry); ip_reference(port); } else { @@ -1126,10 +1222,13 @@ ipc_right_delta( * port is destroyed "first". */ bits &= ~IE_BITS_TYPE_MASK; - bits |= MACH_PORT_TYPE_DEAD_NAME; + bits |= (MACH_PORT_TYPE_DEAD_NAME | MACH_PORT_TYPE_EX_RECEIVE); if (entry->ie_request) { entry->ie_request = IE_REQ_NONE; - bits++; + /* 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; @@ -1140,39 +1239,36 @@ ipc_right_delta( assert(IE_BITS_UREFS(bits) == 0); request = ipc_right_request_cancel_macro(space, port, - name, entry); + name, entry); entry->ie_object = IO_NULL; ipc_entry_dealloc(space, name, entry); } is_write_unlock(space); - queue_init(links); - ipc_port_clear_receiver(port, links); - ipc_port_destroy(port); /* consumes ref, unlocks */ - while(!queue_empty(links)) { - wql = (wait_queue_link_t) dequeue(links); - wait_queue_link_free(wql); - } + ipc_port_destroy(port); /* clears receiver, consumes ref, unlocks */ - if (request != IP_NULL) + if (request != IP_NULL) { ipc_notify_port_deleted(request, name); + } break; - } + } - case MACH_PORT_RIGHT_SEND_ONCE: { + 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_OBJECT_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 */ @@ -1189,6 +1285,13 @@ ipc_right_delta( goto success; } + /* + * 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); @@ -1199,66 +1302,111 @@ ipc_right_delta( ipc_notify_send_once(port); - if (request != IP_NULL) + 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) { - - 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_OBJECT_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 == 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 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_OBJECT_COPYIN_FLAGS_NONE)) { assert((entry->ie_bits & MACH_PORT_TYPE_SEND) == 0); goto invalid_right; } @@ -1266,17 +1414,40 @@ 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) { + if (pinned_control_port_enabled && port->ip_pinned != 0) { + ip_unlock(port); + goto pinned_right; + } + if (--port->ip_srights == 0) { nsrequest = port->ip_nsrequest; if (nsrequest != IP_NULL) { @@ -1288,273 +1459,483 @@ ipc_right_delta( if (bits & MACH_PORT_TYPE_RECEIVE) { assert(port->ip_receiver_name == name); assert(port->ip_receiver == space); - ip_unlock(port); + 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); request = ipc_right_request_cancel_macro(space, port, - name, entry); - ipc_hash_delete(space, (ipc_object_t) port, - name, entry); + name, entry); + ipc_hash_delete(space, ip_to_object(port), + name, entry); ip_unlock(port); - ip_release(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); } 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 (request != IP_NULL) + if (request != IP_NULL) { ipc_notify_port_deleted(request, name); + } break; - } + } - default: - panic("ipc_right_delta: strange right"); + case MACH_PORT_RIGHT_LABELH: + goto invalid_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) + if (port != IP_NULL) { ip_release(port); + } return KERN_INVALID_RIGHT; - invalid_value: +pinned_right: + assert(pinned_control_port_enabled); + is_write_unlock(space); - return KERN_INVALID_VALUE; + mach_port_guard_exception(name, 0, MPG_FLAGS_MOD_REFS_PINNED_DEALLOC, + ipc_control_port_options & IPC_CONTROL_PORT_OPTIONS_PINNED_HARD ? + kGUARD_EXC_MOD_REFS : kGUARD_EXC_MOD_REFS_NON_FATAL); + return KERN_INVALID_CAPABILITY; - urefs_overflow: +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; + +guard_failure: + return KERN_INVALID_RIGHT; } /* - * Routine: ipc_right_info + * Routine: ipc_right_destruct * Purpose: - * Retrieves information about the right. + * Deallocates the receive right and modifies the + * user-reference count for the send rights as requested. * Conditions: - * The space is active and write-locked. - * The space is unlocked upon return. + * The space is write-locked, and is unlocked upon return. + * The space must be active. * Returns: - * KERN_SUCCESS Retrieved info + * 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_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_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; + ipc_port_t port = IP_NULL; ipc_entry_bits_t bits; - mach_port_type_t type = 0; - ipc_port_request_index_t request; + + 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; - request = entry->ie_request; - port = (ipc_port_t) entry->ie_object; - if (bits & MACH_PORT_TYPE_RECEIVE) { - assert(IP_VALID(port)); + assert(is_active(space)); - if (request != IE_REQ_NONE) { - ip_lock(port); - assert(ip_active(port)); - type |= ipc_port_request_type(port, name, request); - ip_unlock(port); - } + if ((bits & MACH_PORT_TYPE_RECEIVE) == 0) { 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)) { - 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); - is_write_unlock(space); - ip_release(port); + /* 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); } - } else { + 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; } - type |= IE_BITS_TYPE(bits); + 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); + mach_port_guard_exception(name, 0, 0, kGUARD_EXC_INVALID_VALUE); + return KERN_INVALID_VALUE; +} - *typep = type; - *urefsp = IE_BITS_UREFS(bits); - return KERN_SUCCESS; -} /* - * Routine: ipc_right_copyin_check + * Routine: ipc_right_info * Purpose: - * Check if a subsequent ipc_right_copyin would succeed. + * Retrieves information about the right. * Conditions: - * The space is locked (read or write) and active. + * The space is active and write-locked. + * The space is unlocked upon return. + * Returns: + * KERN_SUCCESS Retrieved info */ -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) +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_entry_bits_t bits; ipc_port_t port; -#if CONFIG_MACF_MACH - task_t self = current_task(); - int rc = 0; -#endif + ipc_entry_bits_t bits; + mach_port_type_t type = 0; + ipc_port_request_index_t request; - bits= entry->ie_bits; - assert(is_active(space)); + bits = entry->ie_bits; + request = entry->ie_request; + port = ip_object_to_port(entry->ie_object); - switch (msgt_name) { - case MACH_MSG_TYPE_MAKE_SEND: - if ((bits & MACH_PORT_TYPE_RECEIVE) == 0) - return FALSE; + if (bits & MACH_PORT_TYPE_RECEIVE) { + assert(IP_VALID(port)); -#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; + 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_OBJECT_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); + is_write_unlock(space); + ip_release(port); + } + } else { + is_write_unlock(space); + } - case MACH_MSG_TYPE_MAKE_SEND_ONCE: - if ((bits & MACH_PORT_TYPE_RECEIVE) == 0) - return FALSE; + type |= IE_BITS_TYPE(bits); -#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) + *typep = type; + *urefsp = IE_BITS_UREFS(bits); + return KERN_SUCCESS; +} + +/* + * Routine: ipc_right_copyin_check_reply + * Purpose: + * 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_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 reply_port; + + bits = reply_entry->ie_bits; + assert(is_active(space)); + + switch (reply_type) { + case MACH_MSG_TYPE_MAKE_SEND: + if ((bits & MACH_PORT_TYPE_RECEIVE) == 0) { 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) { + if (!ip_is_control(reply_port) || immovable_control_port_enabled) { + mach_port_guard_exception_immovable(reply_name, reply_port, MPG_FLAGS_NONE); + 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: @@ -1576,186 +1957,208 @@ 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, + * or moving the last ref of pinned 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_port_t *releasep, - queue_t links) + ipc_space_t space, + mach_port_name_t name, + ipc_entry_t entry, + mach_msg_type_name_t msgt_name, + ipc_object_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; ipc_port_t port; -#if CONFIG_MACF_MACH - task_t self = current_task(); - int rc; -#endif - + kern_return_t kr; + boolean_t deadok = !!(flags & IPC_OBJECT_COPYIN_FLAGS_DEADOK); + boolean_t allow_imm_send = !!(flags & IPC_OBJECT_COPYIN_FLAGS_ALLOW_IMMOVABLE_SEND); + boolean_t soft_fail_imm_send = !!(flags & IPC_OBJECT_COPYIN_FLAGS_SOFT_FAIL_IMMOVABLE_SEND); + *releasep = IP_NULL; + *assertcntp = 0; bits = entry->ie_bits; assert(is_active(space)); switch (msgt_name) { - case MACH_MSG_TYPE_MAKE_SEND: { - - 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: { + } - 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: { + 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; + } - port = (ipc_port_t) entry->ie_object; + /* + * Disallow moving receive-right kobjects/kolabel, 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_is_kobject(entry->ie_object) || + io_is_kolabeled(entry->ie_object)) { + /* + * 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 = 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 || port->ip_specialreply) { + 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); request = ipc_right_request_cancel_macro(space, port, - name, entry); + 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(port, links); - port->ip_receiver_name = MACH_PORT_NULL; - port->ip_destination = IP_NULL; + /* 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; + } + +#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 */ + ip_unlock(port); - *objectp = (ipc_object_t) port; + *objectp = ip_to_object(port); *sorightp = request; break; - } - - case MACH_MSG_TYPE_COPY_SEND: { + } - 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_OBJECT_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); @@ -1764,126 +2167,134 @@ ipc_right_copyin( goto invalid_right; } - assert(port->ip_srights > 0); + if (!allow_imm_send && port->ip_immovable_send) { + if (!ip_is_control(port) || immovable_control_port_enabled) { + ip_unlock(port); + if (!soft_fail_imm_send) { + mach_port_guard_exception_immovable(name, port, MPG_FLAGS_NONE); + } + 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: { + 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_OBJECT_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); - ip_unlock(port); goto invalid_right; } - assert(port->ip_srights > 0); + if (!allow_imm_send && port->ip_immovable_send) { + if (!ip_is_control(port) || immovable_control_port_enabled) { + ip_unlock(port); + if (!soft_fail_imm_send) { + mach_port_guard_exception_immovable(name, port, MPG_FLAGS_NONE); + } + 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); + assert(port->ip_pinned == 0); ip_reference(port); } else { assert(IE_BITS_TYPE(bits) == - MACH_PORT_TYPE_SEND); + MACH_PORT_TYPE_SEND); request = ipc_right_request_cancel_macro(space, port, - name, entry); - ipc_hash_delete(space, (ipc_object_t) port, - name, entry); + 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; + *objectp = ip_to_object(port); *sorightp = request; break; - } + } - case MACH_MSG_TYPE_MOVE_SEND_ONCE: { + 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); @@ -1893,6 +2304,16 @@ ipc_right_copyin( goto invalid_right; } + if (!allow_imm_send && port->ip_immovable_send) { + if (!ip_is_control(port) || immovable_control_port_enabled) { + ip_unlock(port); + if (!soft_fail_imm_send) { + mach_port_guard_exception_immovable(name, port, MPG_FLAGS_NONE); + } + 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); @@ -1901,137 +2322,60 @@ ipc_right_copyin( ip_unlock(port); entry->ie_object = IO_NULL; - entry->ie_bits = bits &~ - (IE_BITS_UREFS_MASK | MACH_PORT_TYPE_SEND_ONCE); + entry->ie_bits = bits & ~ + (IE_BITS_UREFS_MASK | MACH_PORT_TYPE_SEND_ONCE); ipc_entry_modified(space, name, entry); - *objectp = (ipc_object_t) port; + *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 == 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 == 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(is_active(space)); - - 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 */ - } - ipc_entry_modified(space, name, entry); - /* release the reference acquired by copyin */ - - if (object != IO_DEAD) - io_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 @@ -2043,24 +2387,20 @@ 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_port_t *releasep) +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 request = IP_NULL; -#if CONFIG_MACF_MACH - task_t self = current_task(); - int rc; -#endif *releasep = IP_NULL; @@ -2068,75 +2408,225 @@ ipc_right_copyin_two( 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_OBJECT_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); + /* ... that we steal from the entry when it dies */ request = ipc_right_request_cancel_macro(space, port, - name, entry); + 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; + *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_OBJECT_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_OBJECT_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_OBJECT_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: @@ -2147,34 +2637,30 @@ 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, + ipc_object_copyout_flags_t flags, + 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; @@ -2183,83 +2669,66 @@ ipc_right_copyout( assert(io_active(object)); assert(entry->ie_object == object); - port = (ipc_port_t) object; + port = ip_object_to_port(object); + + if (pinned_control_port_enabled && (flags & IPC_OBJECT_COPYOUT_FLAGS_PINNED)) { + assert(!port->ip_pinned); + assert(port->ip_immovable_send); + port->ip_pinned = 1; + } 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_unlock(port); - ip_release(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_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 */ + /* 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); @@ -2269,203 +2738,102 @@ 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); - 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); - - if (rc) { - ip_unlock(port); - return KERN_NO_ACCESS; - } + /* + * Don't copyout kobjects or kolabels as receive right + */ + if (io_is_kobject(entry->ie_object) || + io_is_kolabeled(entry->ie_object)) { + panic("ipc_right_copyout: Copyout kobject/kolabel as receive right"); } -#endif + + imq_lock(&port->ip_messages); + dest = port->ip_destination; port->ip_receiver_name = name; port->ip_receiver = space; - assert((bits & MACH_PORT_TYPE_RECEIVE) == 0); + 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; + } + 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_unlock(port); - ip_release(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); + boolean_t sync_bootstrap_checkin = FALSE; + if (kn != ITH_KNOTE_PSEUDO && port->ip_sync_bootstrap_checkin) { + sync_bootstrap_checkin = TRUE; + } + 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 */ + + if (bits & MACH_PORT_TYPE_SEND) { + ip_release(port); + + /* 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); - if (dest != IP_NULL) + 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_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; - ipc_port_t release_port = IP_NULL; - - assert(is_active(space)); - 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 != IE_REQ_NONE) { - 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 = IE_REQ_NONE; - object = IO_NULL; - bits = oentry->ie_bits; - release_port = port; - assert(IE_BITS_TYPE(bits) == MACH_PORT_TYPE_DEAD_NAME); - assert(oentry->ie_request == IE_REQ_NONE); - } else { - /* port is locked and active */ - - ipc_port_request_rename(port, request, oname, nname); - ip_unlock(port); - oentry->ie_request = IE_REQ_NONE; } - } - - /* 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); - - ips_lock(pset); - assert(ips_active(pset)); - assert(pset->ips_local_name == oname); - - pset->ips_local_name = nname; - ips_unlock(pset); break; - } - - case MACH_PORT_TYPE_SEND_ONCE: - case MACH_PORT_TYPE_DEAD_NAME: - break; - - default: - panic("ipc_right_rename: strange rights"); } - assert(oentry->ie_request == IE_REQ_NONE); - oentry->ie_object = IO_NULL; - ipc_entry_dealloc(space, oname, oentry); - ipc_entry_modified(space, nname, nentry); - is_write_unlock(space); - - if (release_port != IP_NULL) - ip_release(release_port); - + default: + panic("ipc_right_copyout: strange rights"); + } return KERN_SUCCESS; }