/* * Copyright (c) 2000-2020 Apple Computer, Inc. All rights reserved. * * @APPLE_OSREFERENCE_LICENSE_HEADER_START@ * * This file contains Original Code and/or Modifications of Original Code * as defined in and that are subject to the Apple Public Source License * Version 2.0 (the 'License'). You may not use this file except in * compliance with the License. The rights granted to you under the License * may not be used to create, or enable the creation or redistribution of, * unlawful or unlicensed copies of an Apple operating system, or to * circumvent, violate, or enable the circumvention or violation of, any * terms of an Apple operating system software license agreement. * * Please obtain a copy of the License at * http://www.opensource.apple.com/apsl/ and read it before using this file. * * The Original Code and all software distributed under the License are * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. * Please see the License for the specific language governing rights and * limitations under the License. * * @APPLE_OSREFERENCE_LICENSE_HEADER_END@ */ /* * @OSF_COPYRIGHT@ */ /* * Mach Operating System * Copyright (c) 1991,1990,1989 Carnegie Mellon University * All Rights Reserved. * * Permission to use, copy, modify and distribute this software and its * documentation is hereby granted, provided that both the copyright * notice and this permission notice appear in all copies of the * software, derivative works or modified versions, and any portions * thereof, and that both notices appear in supporting documentation. * * CARNEGIE MELLON ALLOWS FREE USE OF THIS SOFTWARE IN ITS "AS IS" * CONDITION. CARNEGIE MELLON DISCLAIMS ANY LIABILITY OF ANY KIND FOR * ANY DAMAGES WHATSOEVER RESULTING FROM THE USE OF THIS SOFTWARE. * * Carnegie Mellon requests users of this software to return to * * Software Distribution Coordinator or Software.Distribution@CS.CMU.EDU * School of Computer Science * Carnegie Mellon University * Pittsburgh PA 15213-3890 * * any improvements or extensions that they make and grant Carnegie Mellon * the rights to redistribute these changes. */ /* * NOTICE: This file was modified by McAfee Research in 2004 to introduce * support for mandatory and extensible security protections. This notice * is included in support of clause 2.2 (b) of the Apple Public License, * Version 2.0. */ /* */ /* * File: ipc/ipc_space.c * Author: Rich Draves * Date: 1989 * * Functions to manipulate IPC capability spaces. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include /* Remove this in the future so port names are less predictable. */ #define CONFIG_SEMI_RANDOM_ENTRIES #ifdef CONFIG_SEMI_RANDOM_ENTRIES #define NUM_SEQ_ENTRIES 8 #endif os_refgrp_decl(static, is_refgrp, "is", NULL); static ZONE_DEFINE_TYPE(ipc_space_zone, "ipc spaces", struct ipc_space, ZC_ZFREE_CLEARMEM); SECURITY_READ_ONLY_LATE(ipc_space_t) ipc_space_kernel; SECURITY_READ_ONLY_LATE(ipc_space_t) ipc_space_reply; static ipc_space_t ipc_space_alloc(void) { ipc_space_t space; space = zalloc_flags(ipc_space_zone, Z_WAITOK | Z_ZERO | Z_NOFAIL); lck_ticket_init(&space->is_lock, &ipc_lck_grp); return space; } __attribute__((noinline)) static void ipc_space_free(ipc_space_t space) { assert(!is_active(space)); lck_ticket_destroy(&space->is_lock, &ipc_lck_grp); zfree(ipc_space_zone, space); } static void ipc_space_free_table(smr_node_t node) { ipc_entry_t entry = __container_of(node, struct ipc_entry, ie_smr_node); ipc_entry_table_t table = entry->ie_self; ipc_entry_table_free_noclear(table); } void ipc_space_retire_table(ipc_entry_table_t table) { ipc_entry_t base; vm_size_t size; base = ipc_entry_table_base(table); size = ipc_entry_table_size(table); base->ie_self = table; smr_ipc_call(&base->ie_smr_node, size, ipc_space_free_table); } void ipc_space_reference( ipc_space_t space) { os_ref_retain_mask(&space->is_bits, IS_FLAGS_BITS, &is_refgrp); } void ipc_space_release( ipc_space_t space) { if (os_ref_release_mask(&space->is_bits, IS_FLAGS_BITS, &is_refgrp) == 0) { ipc_space_free(space); } } void ipc_space_lock( ipc_space_t space) { lck_ticket_lock(&space->is_lock, &ipc_lck_grp); } void ipc_space_unlock( ipc_space_t space) { lck_ticket_unlock(&space->is_lock); } void ipc_space_lock_sleep( ipc_space_t space) { lck_ticket_sleep_with_inheritor(&space->is_lock, &ipc_lck_grp, LCK_SLEEP_DEFAULT, (event_t)space, space->is_grower, THREAD_UNINT, TIMEOUT_WAIT_FOREVER); } /* Routine: ipc_space_get_rollpoint * Purpose: * Generate a new gencount rollover point from a space's entropy pool */ ipc_entry_bits_t ipc_space_get_rollpoint( ipc_space_t space) { return random_bool_gen_bits( &space->bool_gen, &space->is_entropy[0], IS_ENTROPY_CNT, IE_BITS_ROLL_BITS); } /* * Routine: ipc_entry_rand_freelist * Purpose: * Pseudo-randomly permute the order of entries in an IPC space * Arguments: * space: the ipc space to initialize. * table: the corresponding ipc table to initialize. * the table is 0 initialized. * bottom: the start of the range to initialize (inclusive). * top: the end of the range to initialize (noninclusive). */ void ipc_space_rand_freelist( ipc_space_t space, ipc_entry_t table, mach_port_index_t bottom, mach_port_index_t size) { int at_start = (bottom == 0); #ifdef CONFIG_SEMI_RANDOM_ENTRIES /* * Only make sequential entries at the start of the table, and not when * we're growing the space. */ ipc_entry_num_t total = 0; #endif /* First entry in the free list is always free, and is the start of the free list. */ mach_port_index_t curr = bottom; mach_port_index_t top = size; bottom++; top--; /* * Initialize the free list in the table. * Add the entries in pseudo-random order and randomly set the generation * number, in order to frustrate attacks involving port name reuse. */ while (bottom <= top) { ipc_entry_t entry = &table[curr]; int which; #ifdef CONFIG_SEMI_RANDOM_ENTRIES /* * XXX: This is a horrible hack to make sure that randomizing the port * doesn't break programs that might have (sad) hard-coded values for * certain port names. */ if (at_start && total++ < NUM_SEQ_ENTRIES) { which = 0; } else #endif which = random_bool_gen_bits( &space->bool_gen, &space->is_entropy[0], IS_ENTROPY_CNT, 1); mach_port_index_t next; if (which) { next = top; top--; } else { next = bottom; bottom++; } /* * The entry's gencount will roll over on its first allocation, at which * point a random rollover will be set for the entry. */ entry->ie_bits = IE_BITS_GEN_MASK; entry->ie_next = next; curr = next; } table[curr].ie_bits = IE_BITS_GEN_MASK; } /* * Routine: ipc_space_create * Purpose: * Creates a new IPC space. * * The new space has two references, one for the caller * and one because it is active. * Conditions: * Nothing locked. Allocates memory. * Returns: * KERN_SUCCESS Created a space. * KERN_RESOURCE_SHORTAGE Couldn't allocate memory. */ kern_return_t ipc_space_create( ipc_label_t label, ipc_space_t *spacep) { ipc_space_t space; ipc_entry_table_t table; ipc_entry_num_t count; table = ipc_entry_table_alloc_by_count(IPC_ENTRY_TABLE_MIN, Z_WAITOK | Z_ZERO | Z_NOFAIL); space = ipc_space_alloc(); count = ipc_entry_table_count(table); random_bool_init(&space->bool_gen); ipc_space_rand_freelist(space, ipc_entry_table_base(table), 0, count); os_ref_init_count_mask(&space->is_bits, IS_FLAGS_BITS, &is_refgrp, 2, 0); space->is_table_free = count - 1; space->is_label = label; space->is_low_mod = count; space->is_node_id = HOST_LOCAL_NODE; /* HOST_LOCAL_NODE, except proxy spaces */ smr_init_store(&space->is_table, table); *spacep = space; return KERN_SUCCESS; } /* * Routine: ipc_space_label * Purpose: * Modify the label on a space. The desired * label must be a super-set of the current * label for the space (as rights may already * have been previously copied out under the * old label value. * Conditions: * Nothing locked. * Returns: * KERN_SUCCESS Updated the label * KERN_INVALID_VALUE label not a superset of old */ kern_return_t ipc_space_label( ipc_space_t space, ipc_label_t label) { is_write_lock(space); if (!is_active(space)) { is_write_unlock(space); return KERN_SUCCESS; } if ((space->is_label & label) != space->is_label) { is_write_unlock(space); return KERN_INVALID_VALUE; } space->is_label = label; is_write_unlock(space); return KERN_SUCCESS; } /* * Routine: ipc_space_add_label * Purpose: * Modify the label on a space. The desired * label is added to the labels already set * on the space. * Conditions: * Nothing locked. * Returns: * KERN_SUCCESS Updated the label * KERN_INVALID_VALUE label not a superset of old */ kern_return_t ipc_space_add_label( ipc_space_t space, ipc_label_t label) { is_write_lock(space); if (!is_active(space)) { is_write_unlock(space); return KERN_SUCCESS; } space->is_label |= label; is_write_unlock(space); return KERN_SUCCESS; } /* * Routine: ipc_space_create_special * Purpose: * Create a special space. A special space * doesn't hold rights in the normal way. * Instead it is place-holder for holding * disembodied (naked) receive rights. * See ipc_port_alloc_special/ipc_port_dealloc_special. * Conditions: * Nothing locked. * Returns: * KERN_SUCCESS Created a space. * KERN_RESOURCE_SHORTAGE Couldn't allocate memory. */ kern_return_t ipc_space_create_special( ipc_space_t *spacep) { ipc_space_t space; space = ipc_space_alloc(); os_ref_init_count_mask(&space->is_bits, IS_FLAGS_BITS, &is_refgrp, 1, 0); space->is_label = IPC_LABEL_SPECIAL; space->is_node_id = HOST_LOCAL_NODE; /* HOST_LOCAL_NODE, except proxy spaces */ *spacep = space; return KERN_SUCCESS; } /* * Routine: ipc_space_terminate * Purpose: * Marks the space as dead and cleans up the entries. * Does nothing if the space is already dead. * Conditions: * Nothing locked. */ void ipc_space_terminate( ipc_space_t space) { ipc_entry_table_t table; assert(space != IS_NULL); is_write_lock(space); if (!is_active(space)) { is_write_unlock(space); return; } table = smr_serialized_load(&space->is_table); smr_clear_store(&space->is_table); /* * If somebody is trying to grow the table, * we must wait until they finish and figure * out the space died. */ while (is_growing(space)) { is_write_sleep(space); } is_write_unlock(space); /* * Now we can futz with it unlocked. * * First destroy receive rights, then the rest. * This will cut down the number of notifications * being sent when the notification destination * was a receive right in this space. */ for (mach_port_index_t index = 1; ipc_entry_table_contains(table, index); index++) { ipc_entry_t entry = ipc_entry_table_get_nocheck(table, index); mach_port_type_t type; type = IE_BITS_TYPE(entry->ie_bits); if (type != MACH_PORT_TYPE_NONE) { mach_port_name_t name; name = MACH_PORT_MAKE(index, IE_BITS_GEN(entry->ie_bits)); ipc_right_terminate(space, name, entry); } } ipc_space_retire_table(table); space->is_table_free = 0; /* * Because the space is now dead, * we must release the "active" reference for it. * Our caller still has his reference. */ is_release(space); } #if CONFIG_PROC_RESOURCE_LIMITS /* * ipc_space_set_table_size_limits: * * Set the table size's soft and hard limit. */ kern_return_t ipc_space_set_table_size_limits( ipc_space_t space, ipc_entry_num_t soft_limit, ipc_entry_num_t hard_limit) { if (space == IS_NULL) { return KERN_INVALID_TASK; } is_write_lock(space); if (!is_active(space)) { is_write_unlock(space); return KERN_INVALID_TASK; } if (hard_limit && soft_limit >= hard_limit) { soft_limit = 0; } space->is_table_size_soft_limit = soft_limit; space->is_table_size_hard_limit = hard_limit; is_write_unlock(space); return KERN_SUCCESS; } /* * Check if port space has exceeded its limits. * Should be called with the space write lock held. */ void ipc_space_check_limit_exceeded(ipc_space_t space) { size_t size = ipc_entry_table_count(is_active_table(space)); if (!is_above_soft_limit_notify(space) && space->is_table_size_soft_limit && ((size - space->is_table_free) > space->is_table_size_soft_limit)) { is_above_soft_limit_send_notification(space); act_set_astproc_resource(current_thread()); } else if (!is_above_hard_limit_notify(space) && space->is_table_size_hard_limit && ((size - space->is_table_free) > space->is_table_size_hard_limit)) { is_above_hard_limit_send_notification(space); act_set_astproc_resource(current_thread()); } } #endif /* CONFIG_PROC_RESOURCE_LIMITS */ /* * Routine: ipc_space_check_table_size_limit * Purpose: * Query the current size, soft_limit, and hard_limit for the ipc space. * Returns true if a notification should be sent as a result of the limit * being exceeded, and if we return true but the soft/hard limit values * are zero that indicates the system limit has been exceeded. See * is_at_max_limit_send_notification * Conditions: * Nothing locked on entry. * Nothing locked on exit. * Returns TRUE if a limit has been exceeded. */ bool ipc_space_check_table_size_limit( ipc_space_t space, ipc_entry_num_t *current_size, ipc_entry_num_t *soft_limit, ipc_entry_num_t *hard_limit) { ipc_entry_table_t table; bool should_notify = false; if (space == IS_NULL) { return false; } is_write_lock(space); if (!is_active(space)) { goto exit; } /* space is locked and active */ table = is_active_table(space); *current_size = ipc_entry_table_count(table) - space->is_table_free; if (is_at_max_limit_notify(space)) { if (!is_at_max_limit_already_notified(space)) { *soft_limit = 0; *hard_limit = 0; is_at_max_limit_notified(space); should_notify = true; } goto exit; } #if CONFIG_PROC_RESOURCE_LIMITS *soft_limit = space->is_table_size_soft_limit; *hard_limit = space->is_table_size_hard_limit; if (!*soft_limit && !*hard_limit) { should_notify = false; goto exit; } /* * Check if the thread sending the soft limit notification arrives after * the one that sent the hard limit notification */ if (is_hard_limit_already_notified(space)) { goto exit; } if (*hard_limit > 0 && *current_size >= *hard_limit) { *soft_limit = 0; should_notify = true; is_hard_limit_notified(space); } else { if (is_soft_limit_already_notified(space)) { goto exit; } if (*soft_limit > 0 && *current_size >= *soft_limit) { *hard_limit = 0; should_notify = true; is_soft_limit_notified(space); } } #endif /* CONFIG_PROC_RESOURCE_LIMITS */ exit: is_write_unlock(space); return should_notify; } /* * Set an ast if port space is at its max limit. * Should be called with the space write lock held. */ void ipc_space_set_at_max_limit(ipc_space_t space) { if (!is_at_max_limit_notify(space)) { is_at_max_limit_send_notification(space); act_set_astproc_resource(current_thread()); } }