X-Git-Url: https://git.saurik.com/apple/xnu.git/blobdiff_plain/378393581903b274cb7a4d18e0d978071a6b592d..4d15aeb193b2c68f1d38666c317f8d3734f5f083:/osfmk/ipc/ipc_right.c?ds=inline diff --git a/osfmk/ipc/ipc_right.c b/osfmk/ipc/ipc_right.c index 9f228a43a..7a937f5be 100644 --- a/osfmk/ipc/ipc_right.c +++ b/osfmk/ipc/ipc_right.c @@ -1,23 +1,29 @@ /* - * Copyright (c) 2000-2004 Apple Computer, Inc. All rights reserved. + * Copyright (c) 2000-2007 Apple Inc. All rights reserved. * - * @APPLE_LICENSE_HEADER_START@ + * @APPLE_OSREFERENCE_LICENSE_HEADER_START@ * - * The contents of this file constitute Original Code as defined in and - * are subject to the Apple Public Source License Version 1.1 (the - * "License"). You may not use this file except in compliance with the - * License. Please obtain a copy of the License at - * http://www.apple.com/publicsource and read it before using this file. + * This file contains Original Code and/or Modifications of Original Code + * as defined in and that are subject to the Apple Public Source License + * Version 2.0 (the 'License'). You may not use this file except in + * compliance with the License. The rights granted to you under the License + * may not be used to create, or enable the creation or redistribution of, + * unlawful or unlicensed copies of an Apple operating system, or to + * circumvent, violate, or enable the circumvention or violation of, any + * terms of an Apple operating system software license agreement. * - * This Original Code and all software distributed under the License are - * distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, EITHER + * Please obtain a copy of the License at + * http://www.opensource.apple.com/apsl/ and read it before using this file. + * + * The Original Code and all software distributed under the License are + * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE OR NON-INFRINGEMENT. Please see the - * License for the specific language governing rights and limitations - * under the License. + * 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_LICENSE_HEADER_END@ + * @APPLE_OSREFERENCE_LICENSE_HEADER_END@ */ /* * @OSF_FREE_COPYRIGHT@ @@ -47,6 +53,13 @@ * any improvements or extensions that they make and grant Carnegie Mellon * the rights to redistribute these changes. */ +/* + * NOTICE: This file was modified by McAfee Research in 2004 to introduce + * support for mandatory and extensible security protections. This notice + * is included in support of clause 2.2 (b) of the Apple Public License, + * Version 2.0. + * Copyright (c) 2005-2006 SPARTA, Inc. + */ /* */ /* @@ -62,6 +75,7 @@ #include #include #include +#include #include #include #include @@ -73,7 +87,16 @@ #include #include #include +#include +#include +/* Allow IPC to generate mach port guard exceptions */ +extern kern_return_t +mach_port_guard_exception( + mach_port_name_t name, + uint64_t inguard, + uint64_t portguard, + unsigned reason); /* * Routine: ipc_right_lookup_write * Purpose: @@ -98,7 +121,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; } @@ -140,7 +163,7 @@ 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; } @@ -183,7 +206,7 @@ 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; @@ -229,12 +252,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. @@ -245,34 +262,48 @@ 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_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) 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; + + 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 = (ipc_port_t) entry->ie_object; assert(port != IP_NULL); @@ -280,90 +311,116 @@ ipc_right_dnrequest( if (!ipc_right_check(space, port, name, entry)) { /* 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); - previous = ipc_right_dncancel_macro(space, - port, name, entry); + ipc_notify_send_possible(notify, name); + break; + } - kr = ipc_port_dnrequest(port, name, notify, - &request); + /* + * 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); + +#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) 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; } + /* entry may have changed to dead-name by ipc_right_check() */ - assert(bits & MACH_PORT_TYPE_DEAD_NAME); } - 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; @@ -371,9 +428,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. @@ -381,21 +438,24 @@ ipc_right_dnrequest( */ ipc_port_t -ipc_right_dncancel( +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); - 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; } /* @@ -429,8 +489,10 @@ ipc_right_inuse( * 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. + * Otherwise, entry is converted to a dead name. + * + * Caller is responsible for a reference to port if it + * had died (returns TRUE). */ boolean_t @@ -442,13 +504,12 @@ ipc_right_check( { ipc_entry_bits_t bits; - assert(space->is_active); + assert(is_active(space)); assert(port == (ipc_port_t) entry->ie_object); ip_lock(port); if (ip_active(port)) return FALSE; - ip_unlock(port); /* this was either a pure send right or a send-once right */ @@ -456,53 +517,64 @@ ipc_right_check( assert((bits & MACH_PORT_TYPE_RECEIVE) == 0); assert(IE_BITS_UREFS(bits) > 0); - if (bits & MACH_PORT_TYPE_SEND) { + 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); - - /* convert entry to dead name */ - - if ((bits & MACH_PORT_TYPE_SEND) && !(bits & MACH_PORT_TYPE_RECEIVE)) + if ((bits & MACH_PORT_TYPE_SEND) != 0) { ipc_hash_delete(space, (ipc_object_t)port, name, entry); + } + /* convert entry to dead name */ 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: @@ -510,7 +582,7 @@ ipc_right_check( */ void -ipc_right_clean( +ipc_right_terminate( ipc_space_t space, mach_port_name_t name, ipc_entry_t entry) @@ -521,7 +593,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 @@ -534,19 +606,18 @@ ipc_right_clean( switch (type) { case MACH_PORT_TYPE_DEAD_NAME: - assert(entry->ie_request == 0); + 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; - 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 */ break; } @@ -556,7 +627,7 @@ ipc_right_clean( 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; + ipc_port_t request; ipc_port_t nsrequest = IP_NULL; mach_port_mscount_t mscount = 0; @@ -564,12 +635,12 @@ 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, + request = ipc_right_request_cancel_macro(space, port, name, entry); if (type & MACH_PORT_TYPE_SEND) { @@ -588,8 +659,8 @@ 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); ip_unlock(port); @@ -598,20 +669,20 @@ ipc_right_clean( } else { assert(port->ip_receiver != space); - ip_release(port); - ip_unlock(port); /* port is active */ + ip_unlock(port); + ip_release(port); } 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"); + panic("ipc_right_terminate: strange type - 0x%x", type); } } @@ -620,7 +691,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. @@ -630,7 +701,9 @@ kern_return_t ipc_right_destroy( ipc_space_t space, mach_port_name_t name, - ipc_entry_t entry) + ipc_entry_t entry, + boolean_t check_guard, + uint64_t guard) { ipc_entry_bits_t bits; mach_port_type_t type; @@ -639,28 +712,30 @@ 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); + 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; - 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); + assert(ips_active(pset)); ipc_pset_destroy(pset); /* consumes ref, unlocks */ break; } @@ -672,7 +747,7 @@ ipc_right_destroy( ipc_port_t port = (ipc_port_t) 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); @@ -684,20 +759,34 @@ ipc_right_destroy( 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); @@ -714,8 +803,8 @@ ipc_right_destroy( assert(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); ip_unlock(port); @@ -724,15 +813,17 @@ ipc_right_destroy( } else { assert(port->ip_receiver != space); - ip_release(port); ip_unlock(port); + ip_release(port); } 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; } @@ -763,7 +854,7 @@ ipc_right_dealloc( 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; @@ -771,28 +862,34 @@ 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: 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; + ipc_port_t request; assert(IE_BITS_UREFS(bits) == 1); @@ -803,13 +900,13 @@ ipc_right_dealloc( 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); + request = ipc_right_request_cancel_macro(space, port, name, entry); ip_unlock(port); entry->ie_object = IO_NULL; @@ -819,14 +916,13 @@ 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; + ipc_port_t request = IP_NULL; ipc_port_t nsrequest = IP_NULL; mach_port_mscount_t mscount = 0; @@ -839,7 +935,7 @@ ipc_right_dealloc( if (ipc_right_check(space, port, name, entry)) { 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 */ @@ -854,32 +950,35 @@ ipc_right_dealloc( } } - dnrequest = ipc_right_dncancel_macro(space, port, + request = ipc_right_request_cancel_macro(space, port, name, entry); ipc_hash_delete(space, (ipc_object_t) 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 - entry->ie_bits = bits-1; /* decrement urefs */ - - /* even if dropped a ref, port is active */ - ip_unlock(port); - is_write_unlock(space); + } 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); + } 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; ipc_port_t nsrequest = IP_NULL; mach_port_mscount_t mscount = 0; @@ -905,10 +1004,15 @@ ipc_right_dealloc( entry->ie_bits = bits &~ (IE_BITS_UREFS_MASK | MACH_PORT_TYPE_SEND); - } else - entry->ie_bits = bits-1; /* decrement urefs */ - + } 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) @@ -936,7 +1040,6 @@ 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 @@ -947,8 +1050,9 @@ ipc_right_delta( 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; @@ -960,7 +1064,7 @@ 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. */ @@ -974,7 +1078,7 @@ ipc_right_delta( 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) goto success; @@ -985,12 +1089,9 @@ ipc_right_delta( pset = (ipc_pset_t) 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); @@ -1000,8 +1101,7 @@ ipc_right_delta( } case MACH_PORT_RIGHT_RECEIVE: { - ipc_port_t port; - ipc_port_t dnrequest = IP_NULL; + ipc_port_t request = IP_NULL; if ((bits & MACH_PORT_TYPE_RECEIVE) == 0) goto invalid_right; @@ -1025,50 +1125,79 @@ ipc_right_delta( assert(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); 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; + ipc_hash_insert(space, (ipc_object_t) 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; + 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, + 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; + ipc_port_t request; if ((bits & MACH_PORT_TYPE_SEND_ONCE) == 0) goto invalid_right; @@ -1097,7 +1226,7 @@ ipc_right_delta( goto success; } - 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; @@ -1107,16 +1236,16 @@ 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: { + 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; assert(port != IP_NULL); @@ -1124,45 +1253,71 @@ ipc_right_delta( if (!ipc_right_check(space, port, name, entry)) { /* port is locked and active */ ip_unlock(port); + port = IP_NULL; 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) { 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: { 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) 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; assert(port != IP_NULL); @@ -1175,14 +1330,31 @@ 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) { @@ -1197,37 +1369,46 @@ 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); 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); - dnrequest = ipc_right_dncancel_macro(space, port, + request = ipc_right_request_cancel_macro(space, port, name, entry); ipc_hash_delete(space, (ipc_object_t) 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 (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; } @@ -1243,26 +1424,208 @@ ipc_right_delta( invalid_right: is_write_unlock(space); + if (port != IP_NULL) + ip_release(port); return KERN_INVALID_RIGHT; invalid_value: is_write_unlock(space); 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) || + (srdelta && ((bits & MACH_PORT_TYPE_SEND) == 0))) { + is_write_unlock(space); + return KERN_INVALID_RIGHT; + } + + if (srdelta > 0) + goto invalid_value; + + port = (ipc_port_t) 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); + + /* 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; + ipc_hash_insert(space, (ipc_object_t) 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; + 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); - return KERN_UREFS_OVERFLOW; + + 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_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 @@ -1273,27 +1636,48 @@ ipc_right_info( 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 = (ipc_port_t) 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); + assert(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)) { + 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); @@ -1316,24 +1700,32 @@ ipc_right_copyin_check( mach_msg_type_name_t msgt_name) { ipc_entry_bits_t bits; + ipc_port_t port; bits= entry->ie_bits; - assert(space->is_active); + assert(is_active(space)); switch (msgt_name) { case MACH_MSG_TYPE_MAKE_SEND: + if ((bits & MACH_PORT_TYPE_RECEIVE) == 0) + return FALSE; + break; + case MACH_MSG_TYPE_MAKE_SEND_ONCE: - case MACH_MSG_TYPE_MOVE_RECEIVE: if ((bits & MACH_PORT_TYPE_RECEIVE) == 0) return FALSE; + break; + case MACH_MSG_TYPE_MOVE_RECEIVE: + if ((bits & MACH_PORT_TYPE_RECEIVE) == 0) + return FALSE; + if (io_kotype(entry->ie_object) != IKOT_NONE) + return FALSE; break; case MACH_MSG_TYPE_COPY_SEND: case MACH_MSG_TYPE_MOVE_SEND: case MACH_MSG_TYPE_MOVE_SEND_ONCE: { - ipc_port_t port; - boolean_t active; if (bits & MACH_PORT_TYPE_DEAD_NAME) break; @@ -1344,11 +1736,12 @@ ipc_right_copyin_check( port = (ipc_port_t) entry->ie_object; assert(port != IP_NULL); - ip_lock(port); - active = ip_active(port); - ip_unlock(port); - - if (!active) { + /* + * 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(port)) { break; } @@ -1401,17 +1794,22 @@ ipc_right_copyin( mach_msg_type_name_t msgt_name, boolean_t deadok, ipc_object_t *objectp, - ipc_port_t *sorightp) + ipc_port_t *sorightp, + ipc_port_t *releasep, + int *assertcntp) { ipc_entry_bits_t bits; - + ipc_port_t port; + + *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) goto invalid_right; @@ -1435,7 +1833,6 @@ ipc_right_copyin( } case MACH_MSG_TYPE_MAKE_SEND_ONCE: { - ipc_port_t port; if ((bits & MACH_PORT_TYPE_RECEIVE) == 0) goto invalid_right; @@ -1458,12 +1855,28 @@ ipc_right_copyin( } case MACH_MSG_TYPE_MOVE_RECEIVE: { - ipc_port_t port; - ipc_port_t dnrequest = IP_NULL; + ipc_port_t request = IP_NULL; 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." + */ + return KERN_INVALID_CAPABILITY; + } + port = (ipc_port_t) entry->ie_object; assert(port != IP_NULL); @@ -1485,25 +1898,44 @@ ipc_right_copyin( assert(IE_BITS_TYPE(bits) == MACH_PORT_TYPE_RECEIVE); assert(IE_BITS_UREFS(bits) == 0); - dnrequest = ipc_right_dncancel_macro(space, port, + request = ipc_right_request_cancel_macro(space, port, name, entry); entry->ie_object = IO_NULL; } entry->ie_bits = bits &~ MACH_PORT_TYPE_RECEIVE; + ipc_entry_modified(space, name, entry); - ipc_port_clear_receiver(port); - + (void)ipc_port_clear_receiver(port, FALSE); /* don't destroy the port/mqueue */ port->ip_receiver_name = MACH_PORT_NULL; port->ip_destination = IP_NULL; + +#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; - *sorightp = dnrequest; + *sorightp = request; break; } case MACH_MSG_TYPE_COPY_SEND: { - ipc_port_t port; if (bits & MACH_PORT_TYPE_DEAD_NAME) goto copy_dead; @@ -1520,6 +1952,7 @@ ipc_right_copyin( if (ipc_right_check(space, port, name, entry)) { bits = entry->ie_bits; + *releasep = port; goto copy_dead; } /* port is locked and active */ @@ -1544,8 +1977,7 @@ ipc_right_copyin( } case MACH_MSG_TYPE_MOVE_SEND: { - ipc_port_t port; - ipc_port_t dnrequest = IP_NULL; + ipc_port_t request = IP_NULL; if (bits & MACH_PORT_TYPE_DEAD_NAME) goto move_dead; @@ -1562,6 +1994,7 @@ ipc_right_copyin( if (ipc_right_check(space, port, name, entry)) { bits = entry->ie_bits; + *releasep = port; goto move_dead; } /* port is locked and active */ @@ -1588,30 +2021,33 @@ ipc_right_copyin( assert(IE_BITS_TYPE(bits) == MACH_PORT_TYPE_SEND); - dnrequest = ipc_right_dncancel_macro(space, port, + request = ipc_right_request_cancel_macro(space, port, name, entry); ipc_hash_delete(space, (ipc_object_t) 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); } else { port->ip_srights++; ip_reference(port); - 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); ip_unlock(port); *objectp = (ipc_object_t) port; - *sorightp = dnrequest; + *sorightp = request; break; } case MACH_MSG_TYPE_MOVE_SEND_ONCE: { - ipc_port_t port; - ipc_port_t dnrequest; + ipc_port_t request; if (bits & MACH_PORT_TYPE_DEAD_NAME) goto move_dead; @@ -1628,6 +2064,7 @@ ipc_right_copyin( if (ipc_right_check(space, port, name, entry)) { bits = entry->ie_bits; + *releasep = port; goto move_dead; } /* port is locked and active */ @@ -1644,15 +2081,15 @@ ipc_right_copyin( 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); - + ipc_entry_modified(space, name, entry); *objectp = (ipc_object_t) port; - *sorightp = dnrequest; + *sorightp = request; break; } @@ -1666,7 +2103,7 @@ ipc_right_copyin( 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) @@ -1679,7 +2116,7 @@ ipc_right_copyin( 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) @@ -1688,8 +2125,11 @@ ipc_right_copyin( 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; @@ -1719,7 +2159,7 @@ ipc_right_copyin_undo( bits = entry->ie_bits; - assert(space->is_active); + assert(is_active(space)); assert((msgt_name == MACH_MSG_TYPE_MOVE_SEND) || (msgt_name == MACH_MSG_TYPE_COPY_SEND) || @@ -1745,8 +2185,10 @@ ipc_right_copyin_undo( 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 */ + assert(IE_BITS_UREFS(bits) <= MACH_PORT_UREFS_MAX); + /* if urefs are pegged due to overflow, leave them pegged */ + if (IE_BITS_UREFS(bits) < MACH_PORT_UREFS_MAX) + entry->ie_bits = bits+1; /* increment urefs */ } } else { assert((msgt_name == MACH_MSG_TYPE_MOVE_SEND) || @@ -1757,8 +2199,10 @@ ipc_right_copyin_undo( 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 */ + assert(IE_BITS_UREFS(bits) <= MACH_PORT_UREFS_MAX); + /* if urefs are pegged due to overflow, leave them pegged */ + if (IE_BITS_UREFS(bits) < MACH_PORT_UREFS_MAX) + entry->ie_bits = bits+1; /* increment urefs */ } /* @@ -1770,15 +2214,15 @@ ipc_right_copyin_undo( 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) - ipc_object_release(object); + 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 @@ -1790,21 +2234,24 @@ 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_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 *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; + ipc_port_t request = IP_NULL; + + *releasep = IP_NULL; - assert(space->is_active); + assert(is_active(space)); bits = entry->ie_bits; @@ -1819,6 +2266,7 @@ ipc_right_copyin_two( assert(port != IP_NULL); if (ipc_right_check(space, port, name, entry)) { + *releasep = port; goto invalid_right; } /* port is locked and active */ @@ -1838,7 +2286,7 @@ ipc_right_copyin_two( } else { assert(IE_BITS_TYPE(bits) == MACH_PORT_TYPE_SEND); - dnrequest = ipc_right_dncancel_macro(space, port, + request = ipc_right_request_cancel_macro(space, port, name, entry); port->ip_srights++; @@ -1852,18 +2300,174 @@ ipc_right_copyin_two( port->ip_srights += 2; ip_reference(port); ip_reference(port); - entry->ie_bits = bits-2; /* 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-2; /* decrement urefs */ } + ipc_entry_modified(space, name, entry); + ip_unlock(port); *objectp = (ipc_object_t) port; - *sorightp = dnrequest; + *sorightp = request; return KERN_SUCCESS; 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. + * 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)); + + + /* + * Pre-validate the second disposition is possible all by itself. + */ + if (!ipc_right_copyin_check(space, name, entry, msgt_two)) { + return KERN_INVALID_CAPABILITY; + } + + /* + * 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, FALSE, + objectp, sorightp, releasep, + &assertcnt); + 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, FALSE, + &object_two, sorightp, releasep, + &assertcnt); + 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, FALSE, + objectp, sorightp, releasep, + &assertcnt); + 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((ipc_port_t)*objectp); + } + + return KERN_SUCCESS; +} + + /* * Routine: ipc_right_copyout * Purpose: @@ -1883,9 +2487,6 @@ ipc_right_copyin_two( * 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 @@ -1894,7 +2495,7 @@ ipc_right_copyout( mach_port_name_t name, ipc_entry_t entry, mach_msg_type_name_t msgt_name, - boolean_t overflow, + __unused boolean_t overflow, ipc_object_t object) { ipc_entry_bits_t bits; @@ -1911,14 +2512,16 @@ ipc_right_copyout( switch (msgt_name) { 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); /* 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: @@ -1929,32 +2532,33 @@ ipc_right_copyout( assert(port->ip_srights > 1); assert(urefs > 0); - assert(urefs < MACH_PORT_UREFS_MAX); + assert(urefs <= MACH_PORT_UREFS_MAX); - if (urefs+1 == MACH_PORT_UREFS_MAX) { - if (overflow) { - /* leave urefs pegged to maximum */ - - 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); @@ -1968,12 +2572,17 @@ ipc_right_copyout( 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: { 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; @@ -1988,8 +2597,8 @@ ipc_right_copyout( assert(IE_BITS_UREFS(bits) > 0); assert(port->ip_srights > 0); - ip_release(port); ip_unlock(port); + ip_release(port); /* entry is locked holding ref, so can use port */ @@ -2003,16 +2612,28 @@ ipc_right_copyout( ip_unlock(port); } entry->ie_bits = bits | MACH_PORT_TYPE_RECEIVE; + ipc_entry_modified(space, name, entry); - if (dest != IP_NULL) - ipc_port_release(dest); + 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 */ + ip_release(dest); + } break; } default: panic("ipc_right_copyout: strange rights"); } - return KERN_SUCCESS; } @@ -2040,8 +2661,9 @@ ipc_right_rename( 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(space->is_active); + assert(is_active(space)); assert(oname != nname); /* @@ -2051,7 +2673,7 @@ ipc_right_rename( * Note IE_BITS_COMPAT implies ie_request != 0. */ - if (request != 0) { + if (request != IE_REQ_NONE) { ipc_port_t port; assert(bits & MACH_PORT_TYPE_PORT_RIGHTS); @@ -2059,17 +2681,18 @@ ipc_right_rename( assert(port != IP_NULL); if (ipc_right_check(space, port, oname, oentry)) { - request = 0; + 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 == 0); + assert(oentry->ie_request == IE_REQ_NONE); } else { /* port is locked and active */ - ipc_port_dnrename(port, request, oname, nname); + ipc_port_request_rename(port, request, oname, nname); ip_unlock(port); - oentry->ie_request = 0; + oentry->ie_request = IE_REQ_NONE; } } @@ -2120,9 +2743,7 @@ ipc_right_rename( ips_lock(pset); assert(ips_active(pset)); - assert(pset->ips_local_name == oname); - pset->ips_local_name = nname; ips_unlock(pset); break; } @@ -2135,10 +2756,14 @@ ipc_right_rename( panic("ipc_right_rename: strange rights"); } - assert(oentry->ie_request == 0); + 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); + return KERN_SUCCESS; }