#include <mach/mach_types.h>
#include <mach/notify.h>
#include <ipc/ipc_types.h>
#include <ipc/ipc_port.h>
#include <ipc/ipc_voucher.h>
#include <kern/ipc_kobject.h>
#include <kern/ipc_tt.h>
#include <kern/mach_param.h>
#include <kern/kalloc.h>
#include <kern/zalloc.h>
#include <libkern/OSAtomic.h>
#include <mach/mach_voucher_server.h>
#include <mach/mach_voucher_attr_control_server.h>
#include <mach/mach_host_server.h>
uint32_t ipc_voucher_trace_contents = 0;
static zone_t ipc_voucher_zone;
static zone_t ipc_voucher_attr_control_zone;
#define IV_HASH_BUCKETS 127
#define IV_HASH_BUCKET(x) ((x) % IV_HASH_BUCKETS)
static queue_head_t ivht_bucket[IV_HASH_BUCKETS];
static lck_spin_t ivht_lock_data;
static uint32_t ivht_count = 0;
#define ivht_lock_init() \
lck_spin_init(&ivht_lock_data, &ipc_lck_grp, &ipc_lck_attr)
#define ivht_lock_destroy() \
lck_spin_destroy(&ivht_lock_data, &ipc_lck_grp)
#define ivht_lock() \
lck_spin_lock(&ivht_lock_data)
#define ivht_lock_try() \
lck_spin_try_lock(&ivht_lock_data)
#define ivht_unlock() \
lck_spin_unlock(&ivht_lock_data)
static iv_index_t ivgt_keys_in_use = MACH_VOUCHER_ATTR_KEY_NUM_WELL_KNOWN;
static ipc_voucher_global_table_element iv_global_table[MACH_VOUCHER_ATTR_KEY_NUM_WELL_KNOWN];
static lck_spin_t ivgt_lock_data;
#define ivgt_lock_init() \
lck_spin_init(&ivgt_lock_data, &ipc_lck_grp, &ipc_lck_attr)
#define ivgt_lock_destroy() \
lck_spin_destroy(&ivgt_lock_data, &ipc_lck_grp)
#define ivgt_lock() \
lck_spin_lock(&ivgt_lock_data)
#define ivgt_lock_try() \
lck_spin_try_lock(&ivgt_lock_data)
#define ivgt_unlock() \
lck_spin_unlock(&ivgt_lock_data)
ipc_voucher_t iv_alloc(iv_index_t entries);
void iv_dealloc(ipc_voucher_t iv, boolean_t unhash);
static inline iv_refs_t
iv_reference(ipc_voucher_t iv)
{
iv_refs_t refs;
refs = hw_atomic_add(&iv->iv_refs, 1);
return refs;
}
static inline void
iv_release(ipc_voucher_t iv)
{
iv_refs_t refs;
assert(0 < iv->iv_refs);
refs = hw_atomic_sub(&iv->iv_refs, 1);
if (0 == refs)
iv_dealloc(iv, TRUE);
}
#define IV_FREELIST_END ((iv_index_t) 0)
#define IV_HASH_END UINT32_MAX
#define IV_HASH_VAL(sz, val) \
(((val) >> 3) % (sz))
static inline iv_index_t
iv_hash_value(
iv_index_t key_index,
mach_voucher_attr_value_handle_t value)
{
ipc_voucher_attr_control_t ivac;
ivac = iv_global_table[key_index].ivgte_control;
assert(IVAC_NULL != ivac);
return IV_HASH_VAL(ivac->ivac_init_table_size, value);
}
static inline iv_index_t
iv_key_to_index(mach_voucher_attr_key_t key)
{
if (MACH_VOUCHER_ATTR_KEY_ALL == key ||
MACH_VOUCHER_ATTR_KEY_NUM_WELL_KNOWN < key)
return IV_UNUSED_KEYINDEX;
return (iv_index_t)key - 1;
}
static inline mach_voucher_attr_key_t
iv_index_to_key(iv_index_t key_index)
{
if (MACH_VOUCHER_ATTR_KEY_NUM_WELL_KNOWN > key_index)
return iv_global_table[key_index].ivgte_key;
return MACH_VOUCHER_ATTR_KEY_NONE;
}
static void ivace_release(iv_index_t key_index, iv_index_t value_index);
static void ivace_lookup_values(iv_index_t key_index, iv_index_t value_index,
mach_voucher_attr_value_handle_array_t values,
mach_voucher_attr_value_handle_array_size_t *count);
static iv_index_t iv_lookup(ipc_voucher_t, iv_index_t);
static void ivgt_lookup(iv_index_t,
boolean_t,
ipc_voucher_attr_manager_t *,
ipc_voucher_attr_control_t *);
#if defined(MACH_VOUCHER_ATTR_KEY_USER_DATA) || defined(MACH_VOUCHER_ATTR_KEY_TEST)
void user_data_attr_manager_init(void);
#endif
void
ipc_voucher_init(void)
{
natural_t ipc_voucher_max = (task_max + thread_max) * 2;
natural_t attr_manager_max = MACH_VOUCHER_ATTR_KEY_NUM_WELL_KNOWN;
iv_index_t i;
ipc_voucher_zone = zinit(sizeof(struct ipc_voucher),
ipc_voucher_max * sizeof(struct ipc_voucher),
sizeof(struct ipc_voucher),
"ipc vouchers");
zone_change(ipc_voucher_zone, Z_NOENCRYPT, TRUE);
ipc_voucher_attr_control_zone = zinit(sizeof(struct ipc_voucher_attr_control),
attr_manager_max * sizeof(struct ipc_voucher_attr_control),
sizeof(struct ipc_voucher_attr_control),
"ipc voucher attr controls");
zone_change(ipc_voucher_attr_control_zone, Z_NOENCRYPT, TRUE);
ivht_lock_init();
for (i = 0; i < IV_HASH_BUCKETS; i++)
queue_init(&ivht_bucket[i]);
ivgt_lock_init();
#if defined(MACH_VOUCHER_ATTR_KEY_USER_DATA) || defined(MACH_VOUCHER_ATTR_KEY_TEST)
user_data_attr_manager_init();
#endif
}
ipc_voucher_t
iv_alloc(iv_index_t entries)
{
ipc_voucher_t iv;
iv_index_t i;
iv = (ipc_voucher_t)zalloc(ipc_voucher_zone);
if (IV_NULL == iv)
return IV_NULL;
iv->iv_refs = 1;
iv->iv_sum = 0;
iv->iv_hash = 0;
iv->iv_port = IP_NULL;
if (entries > IV_ENTRIES_INLINE) {
iv_entry_t table;
table = (iv_entry_t) kalloc(sizeof(*table) * entries);
if (IVE_NULL == table) {
zfree(ipc_voucher_zone, iv);
return IV_NULL;
}
iv->iv_table = table;
iv->iv_table_size = entries;
} else {
iv->iv_table = iv->iv_inline_table;
iv->iv_table_size = IV_ENTRIES_INLINE;
}
for (i=0; i < iv->iv_table_size; i++)
iv->iv_table[i] = IV_UNUSED_VALINDEX;
return (iv);
}
static void
iv_set(ipc_voucher_t iv,
iv_index_t key_index,
iv_index_t value_index)
{
assert(key_index < iv->iv_table_size);
iv->iv_table[key_index] = value_index;
}
void
iv_dealloc(ipc_voucher_t iv, boolean_t unhash)
{
ipc_port_t port = iv->iv_port;
natural_t i;
if (unhash) {
ivht_lock();
assert(0 == iv->iv_refs);
assert(IV_HASH_BUCKETS > iv->iv_hash);
queue_remove(&ivht_bucket[iv->iv_hash], iv, ipc_voucher_t, iv_hash_link);
ivht_count--;
ivht_unlock();
KERNEL_DEBUG_CONSTANT(MACHDBG_CODE(DBG_MACH_IPC,MACH_IPC_VOUCHER_DESTROY) | DBG_FUNC_NONE,
VM_KERNEL_ADDRPERM((uintptr_t)iv), 0, ivht_count, 0, 0);
} else
assert(0 == --iv->iv_refs);
if (IP_VALID(port)) {
assert(ip_active(port));
assert(port->ip_srights == 0);
ipc_port_dealloc_kernel(port);
}
for (i = 0; i < iv->iv_table_size; i++) {
ivace_release(i, iv->iv_table[i]);
#if MACH_ASSERT
iv_set(iv, i, ~0);
#endif
}
if (iv->iv_table != iv->iv_inline_table)
kfree(iv->iv_table,
iv->iv_table_size * sizeof(*iv->iv_table));
zfree(ipc_voucher_zone, iv);
}
static inline iv_index_t
iv_lookup(ipc_voucher_t iv, iv_index_t key_index)
{
if (key_index < iv->iv_table_size)
return iv->iv_table[key_index];
return IV_UNUSED_VALINDEX;
}
uintptr_t
unsafe_convert_port_to_voucher(
ipc_port_t port)
{
if (IP_VALID(port)) {
uintptr_t voucher = (uintptr_t) port->ip_kobject;
if (ip_kotype(port) == IKOT_VOUCHER)
return (voucher);
}
return (uintptr_t)IV_NULL;
}
ipc_voucher_t
convert_port_to_voucher(
ipc_port_t port)
{
if (IP_VALID(port)) {
ipc_voucher_t voucher = (ipc_voucher_t) port->ip_kobject;
if (ip_kotype(port) != IKOT_VOUCHER)
return IV_NULL;
assert(ip_active(port));
ipc_voucher_reference(voucher);
return (voucher);
}
return IV_NULL;
}
ipc_voucher_t
convert_port_name_to_voucher(
mach_port_name_t voucher_name)
{
ipc_voucher_t iv;
kern_return_t kr;
ipc_port_t port;
if (MACH_PORT_VALID(voucher_name)) {
kr = ipc_port_translate_send(current_space(), voucher_name, &port);
if (KERN_SUCCESS != kr)
return IV_NULL;
iv = convert_port_to_voucher(port);
ip_unlock(port);
return iv;
}
return IV_NULL;
}
void
ipc_voucher_reference(ipc_voucher_t voucher)
{
iv_refs_t refs;
if (IPC_VOUCHER_NULL == voucher)
return;
refs = iv_reference(voucher);
assert(1 < refs);
}
void
ipc_voucher_release(ipc_voucher_t voucher)
{
if (IPC_VOUCHER_NULL != voucher)
iv_release(voucher);
}
void
ipc_voucher_notify(mach_msg_header_t *msg)
{
mach_no_senders_notification_t *notification = (void *)msg;
ipc_port_t port = notification->not_header.msgh_remote_port;
ipc_voucher_t iv;
assert(ip_active(port));
assert(IKOT_VOUCHER == ip_kotype(port));
iv = (ipc_voucher_t)port->ip_kobject;
ipc_voucher_release(iv);
}
ipc_port_t
convert_voucher_to_port(ipc_voucher_t voucher)
{
ipc_port_t port, send;
if (IV_NULL == voucher)
return (IP_NULL);
assert(0 < voucher->iv_refs);
port = voucher->iv_port;
if (!IP_VALID(port)) {
port = ipc_port_alloc_kernel();
assert(IP_VALID(port));
ipc_kobject_set_atomically(port, (ipc_kobject_t) voucher, IKOT_VOUCHER);
if (!OSCompareAndSwapPtr(IP_NULL, port, &voucher->iv_port)) {
ipc_port_dealloc_kernel(port);
port = voucher->iv_port;
assert(ip_kotype(port) == IKOT_VOUCHER);
assert(port->ip_kobject == (ipc_kobject_t)voucher);
}
}
ip_lock(port);
assert(ip_active(port));
send = ipc_port_make_send_locked(port);
if (1 == port->ip_srights) {
ipc_port_t old_notify;
assert(IP_NULL == port->ip_nsrequest);
ipc_port_nsrequest(port, port->ip_mscount, ipc_port_make_sonce_locked(port), &old_notify);
assert(IP_NULL == old_notify);
} else {
ip_unlock(port);
ipc_voucher_release(voucher);
}
return (send);
}
#define ivace_reset_data(ivace_elem, next_index) { \
(ivace_elem)->ivace_value = 0xDEADC0DEDEADC0DE; \
(ivace_elem)->ivace_refs = 0; \
(ivace_elem)->ivace_made = 0; \
(ivace_elem)->ivace_free = TRUE; \
(ivace_elem)->ivace_releasing = FALSE; \
(ivace_elem)->ivace_layered = 0; \
(ivace_elem)->ivace_index = IV_HASH_END; \
(ivace_elem)->ivace_next = (next_index); \
}
#define ivace_copy_data(ivace_src_elem, ivace_dst_elem) { \
(ivace_dst_elem)->ivace_value = (ivace_src_elem)->ivace_value; \
(ivace_dst_elem)->ivace_refs = (ivace_src_elem)->ivace_refs; \
(ivace_dst_elem)->ivace_made = (ivace_src_elem)->ivace_made; \
(ivace_dst_elem)->ivace_free = (ivace_src_elem)->ivace_free; \
(ivace_dst_elem)->ivace_layered = (ivace_src_elem)->ivace_layered; \
(ivace_dst_elem)->ivace_releasing = (ivace_src_elem)->ivace_releasing; \
(ivace_dst_elem)->ivace_index = (ivace_src_elem)->ivace_index; \
(ivace_dst_elem)->ivace_next = (ivace_src_elem)->ivace_next; \
}
ipc_voucher_attr_control_t
ivac_alloc(iv_index_t key_index)
{
ipc_voucher_attr_control_t ivac;
ivac_entry_t table;
natural_t i;
ivac = (ipc_voucher_attr_control_t)zalloc(ipc_voucher_attr_control_zone);
if (IVAC_NULL == ivac)
return IVAC_NULL;
ivac->ivac_refs = 1;
ivac->ivac_is_growing = FALSE;
ivac->ivac_port = IP_NULL;
table = (ivac_entry_t) kalloc(IVAC_ENTRIES_MIN * sizeof(ivac_entry));
ivac->ivac_table = table;
ivac->ivac_table_size = IVAC_ENTRIES_MIN;
ivac->ivac_init_table_size = IVAC_ENTRIES_MIN;
for (i = 0; i < ivac->ivac_table_size; i++) {
ivace_reset_data(&table[i], i+1);
}
table[0].ivace_next = IV_HASH_END;
table[0].ivace_free = FALSE;
table[i-1].ivace_next = IV_FREELIST_END;
ivac->ivac_freelist = 1;
ivac_lock_init(ivac);
ivac->ivac_key_index = key_index;
return (ivac);
}
void
ivac_dealloc(ipc_voucher_attr_control_t ivac)
{
ipc_voucher_attr_manager_t ivam = IVAM_NULL;
iv_index_t key_index = ivac->ivac_key_index;
ipc_port_t port = ivac->ivac_port;
natural_t i;
ivgt_lock();
if (ivac->ivac_refs > 0) {
ivgt_unlock();
return;
}
if (iv_global_table[key_index].ivgte_control == ivac) {
ivam = iv_global_table[key_index].ivgte_manager;
iv_global_table[key_index].ivgte_manager = IVAM_NULL;
iv_global_table[key_index].ivgte_control = IVAC_NULL;
iv_global_table[key_index].ivgte_key = MACH_VOUCHER_ATTR_KEY_NONE;
}
ivgt_unlock();
if (IVAM_NULL != ivam)
(ivam->ivam_release)(ivam);
if (IP_VALID(port)) {
assert(ip_active(port));
assert(port->ip_srights == 0);
ipc_port_dealloc_kernel(port);
}
#ifdef MACH_DEBUG
for (i = 0; i < ivac->ivac_table_size; i++)
if (ivac->ivac_table[i].ivace_refs != 0)
panic("deallocing a resource manager with live refs to its attr values\n");
#endif
kfree(ivac->ivac_table, ivac->ivac_table_size * sizeof(*ivac->ivac_table));
ivac_lock_destroy(ivac);
zfree(ipc_voucher_attr_control_zone, ivac);
}
void
ipc_voucher_attr_control_reference(ipc_voucher_attr_control_t control)
{
ivac_reference(control);
}
void
ipc_voucher_attr_control_release(ipc_voucher_attr_control_t control)
{
ivac_release(control);
}
ipc_voucher_attr_control_t
convert_port_to_voucher_attr_control(
ipc_port_t port)
{
if (IP_VALID(port)) {
ipc_voucher_attr_control_t ivac = (ipc_voucher_attr_control_t) port->ip_kobject;
if (ip_kotype(port) != IKOT_VOUCHER_ATTR_CONTROL)
return IVAC_NULL;
assert(ip_active(port));
ivac_reference(ivac);
return (ivac);
}
return IVAC_NULL;
}
void
ipc_voucher_attr_control_notify(mach_msg_header_t *msg)
{
mach_no_senders_notification_t *notification = (void *)msg;
ipc_port_t port = notification->not_header.msgh_remote_port;
ipc_voucher_attr_control_t ivac;
assert(IKOT_VOUCHER_ATTR_CONTROL == ip_kotype(port));
ip_lock(port);
assert(ip_active(port));
if (port->ip_mscount == notification->not_count) {
ivac = (ipc_voucher_attr_control_t)port->ip_kobject;
ip_unlock(port);
ivac_release(ivac);
}
ip_unlock(port);
}
ipc_port_t
convert_voucher_attr_control_to_port(ipc_voucher_attr_control_t control)
{
ipc_port_t port, send;
if (IVAC_NULL == control)
return (IP_NULL);
port = control->ivac_port;
if (!IP_VALID(port)) {
port = ipc_port_alloc_kernel();
assert(IP_VALID(port));
if (OSCompareAndSwapPtr(IP_NULL, port, &control->ivac_port)) {
ip_lock(port);
ipc_kobject_set_atomically(port, (ipc_kobject_t) control, IKOT_VOUCHER_ATTR_CONTROL);
} else {
ipc_port_dealloc_kernel(port);
port = control->ivac_port;
ip_lock(port);
assert(ip_kotype(port) == IKOT_VOUCHER_ATTR_CONTROL);
assert(port->ip_kobject == (ipc_kobject_t)control);
}
} else
ip_lock(port);
assert(ip_active(port));
send = ipc_port_make_send_locked(port);
if (1 == port->ip_srights) {
ipc_port_t old_notify;
assert(IP_NULL == port->ip_nsrequest);
ipc_port_nsrequest(port, port->ip_mscount, ipc_port_make_sonce_locked(port), &old_notify);
assert(IP_NULL == old_notify);
ip_unlock(port);
} else {
ip_unlock(port);
ivac_release(control);
}
return (send);
}
static void
ivace_lookup_values(
iv_index_t key_index,
iv_index_t value_index,
mach_voucher_attr_value_handle_array_t values,
mach_voucher_attr_value_handle_array_size_t *count)
{
ipc_voucher_attr_control_t ivac;
ivac_entry_t ivace;
if (IV_UNUSED_VALINDEX == value_index ||
MACH_VOUCHER_ATTR_KEY_NUM_WELL_KNOWN <= key_index) {
*count = 0;
return;
}
ivac = iv_global_table[key_index].ivgte_control;
assert(IVAC_NULL != ivac);
ivac_lock(ivac);
assert(value_index < ivac->ivac_table_size);
ivace = &ivac->ivac_table[value_index];
assert(ivace->ivace_refs > 0);
values[0] = ivace->ivace_value;
ivac_unlock(ivac);
*count = 1;
}
static void
ivac_grow_table(ipc_voucher_attr_control_t ivac)
{
iv_index_t i = 0;
ivac_entry_t new_table = NULL, old_table = NULL;
iv_index_t new_size, old_size;
if (ivac->ivac_is_growing) {
ivac_sleep(ivac);
return;
}
ivac->ivac_is_growing = 1;
if (ivac->ivac_table_size >= IVAC_ENTRIES_MAX) {
panic("Cannot grow ipc space beyond IVAC_ENTRIES_MAX. Some process is leaking vouchers");
return;
}
old_size = ivac->ivac_table_size;
ivac_unlock(ivac);
new_size = old_size * 2;
assert(new_size > old_size);
assert(new_size < IVAC_ENTRIES_MAX);
new_table = kalloc(sizeof(ivac_entry) * new_size);
if (!new_table){
panic("Failed to grow ivac table to size %d\n", new_size);
return;
}
for (i = old_size; i < new_size; i++) {
ivace_reset_data(&new_table[i], i+1);
}
ivac_lock(ivac);
for (i = 0; i < ivac->ivac_table_size; i++){
ivace_copy_data(&ivac->ivac_table[i], &new_table[i]);
}
old_table = ivac->ivac_table;
ivac->ivac_table = new_table;
ivac->ivac_table_size = new_size;
ivac->ivac_table[new_size - 1].ivace_next = ivac->ivac_freelist;
ivac->ivac_freelist = old_size;
ivac->ivac_is_growing = 0;
ivac_wakeup(ivac);
if (old_table){
ivac_unlock(ivac);
kfree(old_table, old_size * sizeof(ivac_entry));
ivac_lock(ivac);
}
}
static void
ivace_reference_by_index(
iv_index_t key_index,
iv_index_t val_index)
{
ipc_voucher_attr_control_t ivac;
ivac_entry_t ivace;
if (IV_UNUSED_VALINDEX == val_index)
return;
ivgt_lookup(key_index, FALSE, NULL, &ivac);
assert(IVAC_NULL != ivac);
ivac_lock(ivac);
assert(val_index < ivac->ivac_table_size);
ivace = &ivac->ivac_table[val_index];
assert(0xdeadc0dedeadc0de != ivace->ivace_value);
assert(0 < ivace->ivace_refs);
assert(!ivace->ivace_free);
ivace->ivace_refs++;
ivac_unlock(ivac);
}
static iv_index_t
ivace_reference_by_value(
ipc_voucher_attr_control_t ivac,
mach_voucher_attr_value_handle_t value)
{
ivac_entry_t ivace = IVACE_NULL;
iv_index_t hash_index;
iv_index_t index;
if (IVAC_NULL == ivac) {
return IV_UNUSED_VALINDEX;
}
ivac_lock(ivac);
restart:
hash_index = IV_HASH_VAL(ivac->ivac_init_table_size, value);
index = ivac->ivac_table[hash_index].ivace_index;
while (index != IV_HASH_END) {
assert(index < ivac->ivac_table_size);
ivace = &ivac->ivac_table[index];
assert(!ivace->ivace_free);
if (ivace->ivace_value == value)
break;
assert(ivace->ivace_next != index);
index = ivace->ivace_next;
}
if (index != IV_HASH_END) {
if (IV_UNUSED_VALINDEX != index) {
ivace->ivace_refs++;
ivace->ivace_made++;
}
ivac_unlock(ivac);
ivac_release(ivac);
return index;
}
index = ivac->ivac_freelist;
if (IV_FREELIST_END == index) {
ivac_grow_table(ivac);
goto restart;
}
ivace = &ivac->ivac_table[index];
ivac->ivac_freelist = ivace->ivace_next;
ivace->ivace_value = value;
ivace->ivace_refs = 1;
ivace->ivace_made = 1;
ivace->ivace_free = FALSE;
ivace->ivace_next = ivac->ivac_table[hash_index].ivace_index;
ivac->ivac_table[hash_index].ivace_index = index;
ivac_unlock(ivac);
return index;
}
static void ivace_release(
iv_index_t key_index,
iv_index_t value_index)
{
ipc_voucher_attr_control_t ivac;
ipc_voucher_attr_manager_t ivam;
mach_voucher_attr_value_handle_t value;
mach_voucher_attr_value_reference_t made;
mach_voucher_attr_key_t key;
iv_index_t hash_index;
ivac_entry_t ivace;
kern_return_t kr;
if (IV_UNUSED_VALINDEX == value_index)
return;
ivgt_lookup(key_index, FALSE, &ivam, &ivac);
assert(IVAC_NULL != ivac);
assert(IVAM_NULL != ivam);
ivac_lock(ivac);
assert(value_index < ivac->ivac_table_size);
ivace = &ivac->ivac_table[value_index];
assert(0 < ivace->ivace_refs);
if (0 < --ivace->ivace_refs) {
ivac_unlock(ivac);
return;
}
key = iv_index_to_key(key_index);
assert(MACH_VOUCHER_ATTR_KEY_NONE != key);
if (ivace->ivace_releasing) {
ivac_unlock(ivac);
return;
}
ivace->ivace_releasing = TRUE;
value = ivace->ivace_value;
redrive:
assert(value == ivace->ivace_value);
assert(!ivace->ivace_free);
made = ivace->ivace_made;
ivac_unlock(ivac);
kr = (ivam->ivam_release_value)(ivam, key, value, made);
ivac_lock(ivac);
ivace = &ivac->ivac_table[value_index];
assert(value == ivace->ivace_value);
if (ivace->ivace_made != made) {
assert(made < ivace->ivace_made);
if (KERN_SUCCESS == kr)
ivace->ivace_made -= made;
if (0 == ivace->ivace_refs)
goto redrive;
ivace->ivace_releasing = FALSE;
ivac_unlock(ivac);
return;
} else {
if (KERN_SUCCESS != kr) {
ivace->ivace_releasing = FALSE;
ivac_unlock(ivac);
return;
}
}
assert(0 == ivace->ivace_refs);
hash_index = iv_hash_value(key_index, value);
if (ivac->ivac_table[hash_index].ivace_index == value_index) {
ivac->ivac_table[hash_index].ivace_index = ivace->ivace_next;
} else {
hash_index = ivac->ivac_table[hash_index].ivace_index;
assert(IV_HASH_END != hash_index);
while (ivac->ivac_table[hash_index].ivace_next != value_index) {
hash_index = ivac->ivac_table[hash_index].ivace_next;
assert(IV_HASH_END != hash_index);
}
ivac->ivac_table[hash_index].ivace_next = ivace->ivace_next;
}
ivace->ivace_value = 0xdeadc0dedeadc0de;
ivace->ivace_releasing = FALSE;
ivace->ivace_free = TRUE;
ivace->ivace_made = 0;
ivace->ivace_next = ivac->ivac_freelist;
ivac->ivac_freelist = value_index;
ivac_unlock(ivac);
ivac_release(ivac);
return;
}
static void
ivgt_lookup(iv_index_t key_index,
boolean_t take_reference,
ipc_voucher_attr_manager_t *manager,
ipc_voucher_attr_control_t *control)
{
ipc_voucher_attr_control_t ivac;
if (key_index < MACH_VOUCHER_ATTR_KEY_NUM_WELL_KNOWN) {
ivgt_lock();
if (NULL != manager)
*manager = iv_global_table[key_index].ivgte_manager;
ivac = iv_global_table[key_index].ivgte_control;
if (IVAC_NULL != ivac) {
assert(key_index == ivac->ivac_key_index);
if (take_reference) {
assert(NULL != control);
ivac_reference(ivac);
}
}
ivgt_unlock();
if (NULL != control)
*control = ivac;
} else {
if (NULL != manager)
*manager = IVAM_NULL;
if (NULL != control)
*control = IVAC_NULL;
}
}
static kern_return_t
ipc_replace_voucher_value(
ipc_voucher_t voucher,
mach_voucher_attr_key_t key,
mach_voucher_attr_recipe_command_t command,
ipc_voucher_t prev_voucher,
mach_voucher_attr_content_t content,
mach_voucher_attr_content_size_t content_size)
{
mach_voucher_attr_value_handle_t previous_vals[MACH_VOUCHER_ATTR_VALUE_MAX_NESTED];
mach_voucher_attr_value_handle_array_size_t previous_vals_count;
mach_voucher_attr_value_handle_t new_value;
ipc_voucher_t new_value_voucher;
ipc_voucher_attr_manager_t ivam;
ipc_voucher_attr_control_t ivac;
iv_index_t prev_val_index;
iv_index_t save_val_index;
iv_index_t val_index;
iv_index_t key_index;
kern_return_t kr;
key_index = iv_key_to_index(key);
ivgt_lookup(key_index, TRUE, &ivam, &ivac);
if (IVAM_NULL == ivam)
return KERN_INVALID_ARGUMENT;
save_val_index = iv_lookup(voucher, key_index);
prev_val_index = (IV_NULL != prev_voucher) ?
iv_lookup(prev_voucher, key_index) :
save_val_index;
ivace_lookup_values(key_index, prev_val_index,
previous_vals, &previous_vals_count);
new_value_voucher = IV_NULL;
kr = (ivam->ivam_get_value)(
ivam, key, command,
previous_vals, previous_vals_count,
content, content_size,
&new_value, &new_value_voucher);
if (KERN_SUCCESS != kr) {
ivac_release(ivac);
return kr;
}
if (IV_NULL != new_value_voucher)
iv_release(new_value_voucher);
val_index = ivace_reference_by_value(ivac, new_value);
iv_set(voucher, key_index, val_index);
ivace_release(key_index, save_val_index);
return KERN_SUCCESS;
}
static kern_return_t
ipc_directly_replace_voucher_value(
ipc_voucher_t voucher,
mach_voucher_attr_key_t key,
mach_voucher_attr_value_handle_t new_value)
{
ipc_voucher_attr_manager_t ivam;
ipc_voucher_attr_control_t ivac;
iv_index_t save_val_index;
iv_index_t val_index;
iv_index_t key_index;
key_index = iv_key_to_index(key);
ivgt_lookup(key_index, TRUE, &ivam, &ivac);
if (IVAM_NULL == ivam)
return KERN_INVALID_ARGUMENT;
save_val_index = iv_lookup(voucher, key_index);
val_index = ivace_reference_by_value(ivac, new_value);
iv_set(voucher, key_index, val_index);
ivace_release(key_index, save_val_index);
return KERN_SUCCESS;
}
static kern_return_t
ipc_execute_voucher_recipe_command(
ipc_voucher_t voucher,
mach_voucher_attr_key_t key,
mach_voucher_attr_recipe_command_t command,
ipc_voucher_t prev_iv,
mach_voucher_attr_content_t content,
mach_voucher_attr_content_size_t content_size,
boolean_t key_priv)
{
iv_index_t prev_val_index;
iv_index_t val_index;
kern_return_t kr;
switch (command) {
case MACH_VOUCHER_ATTR_COPY:
if (0 < content_size)
return KERN_INVALID_ARGUMENT;
if (IV_NULL == prev_iv)
return KERN_SUCCESS;
if (MACH_VOUCHER_ATTR_KEY_ALL == key) {
iv_index_t limit, j;
limit = (prev_iv->iv_table_size < voucher->iv_table_size) ?
prev_iv->iv_table_size :
voucher->iv_table_size;
for (j = 0; j < limit; j++) {
val_index = iv_lookup(voucher, j);
ivace_release(j, val_index);
prev_val_index = iv_lookup(prev_iv, j);
ivace_reference_by_index(j, prev_val_index);
iv_set(voucher, j, prev_val_index);
}
} else {
iv_index_t key_index;
key_index = iv_key_to_index(key);
if (ivgt_keys_in_use < key_index)
return KERN_INVALID_ARGUMENT;
val_index = iv_lookup(voucher, key_index);
ivace_release(key_index, val_index);
prev_val_index = iv_lookup(prev_iv, key_index);
ivace_reference_by_index(key_index, prev_val_index);
iv_set(voucher, key_index, prev_val_index);
}
break;
case MACH_VOUCHER_ATTR_REMOVE:
if (0 < content_size)
return KERN_INVALID_ARGUMENT;
if (MACH_VOUCHER_ATTR_KEY_ALL == key) {
iv_index_t limit, j;
limit = (IV_NULL == prev_iv) ? voucher->iv_table_size :
((prev_iv->iv_table_size < voucher->iv_table_size) ?
prev_iv->iv_table_size : voucher->iv_table_size);
for (j = 0; j < limit; j++) {
val_index = iv_lookup(voucher, j);
if (IV_NULL != prev_iv) {
prev_val_index = iv_lookup(prev_iv, j);
if (val_index != prev_val_index)
continue;
}
ivace_release(j, val_index);
iv_set(voucher, j, IV_UNUSED_VALINDEX);
}
} else {
iv_index_t key_index;
key_index = iv_key_to_index(key);
if (ivgt_keys_in_use < key_index)
return KERN_INVALID_ARGUMENT;
val_index = iv_lookup(voucher, key_index);
if (IV_NULL != prev_iv) {
prev_val_index = iv_lookup(prev_iv, key_index);
if (val_index != prev_val_index)
break;
}
ivace_release(key_index, val_index);
iv_set(voucher, key_index, IV_UNUSED_VALINDEX);
}
break;
case MACH_VOUCHER_ATTR_SET_VALUE_HANDLE:
if (key_priv) {
mach_voucher_attr_value_handle_t new_value;
if (sizeof(mach_voucher_attr_value_handle_t) != content_size)
return KERN_INVALID_ARGUMENT;
new_value = *(mach_voucher_attr_value_handle_t *)(void *)content;
kr = ipc_directly_replace_voucher_value(voucher,
key,
new_value);
if (KERN_SUCCESS != kr)
return kr;
} else
return KERN_INVALID_CAPABILITY;
break;
case MACH_VOUCHER_ATTR_REDEEM:
if (MACH_VOUCHER_ATTR_KEY_ALL == key) {
iv_index_t limit, j;
if (IV_NULL != prev_iv)
limit = (prev_iv->iv_table_size < voucher->iv_table_size) ?
prev_iv->iv_table_size :
voucher->iv_table_size;
else
limit = voucher->iv_table_size;
for (j = 0; j < limit; j++) {
mach_voucher_attr_key_t j_key;
j_key = iv_index_to_key(j);
if (MACH_VOUCHER_ATTR_KEY_NONE == j_key)
continue;
kr = ipc_replace_voucher_value(voucher,
j_key,
command,
prev_iv,
content,
content_size);
if (KERN_SUCCESS != kr)
return kr;
}
break;
}
default:
kr = ipc_replace_voucher_value(voucher,
key,
command,
prev_iv,
content,
content_size);
if (KERN_SUCCESS != kr)
return kr;
break;
}
return KERN_SUCCESS;
}
static inline iv_index_t
iv_checksum(ipc_voucher_t voucher, boolean_t *emptyp)
{
iv_index_t c = 0;
boolean_t empty = TRUE;
if (0 < voucher->iv_table_size) {
iv_index_t i = voucher->iv_table_size - 1;
do {
iv_index_t v = voucher->iv_table[i];
c = c << 3 | c >> (32 - 3);
c = ~c;
if (0 < v) {
c += v;
empty = FALSE;
}
} while (0 < i--);
}
*emptyp = empty;
return c;
}
static ipc_voucher_t
iv_dedup(ipc_voucher_t new_iv)
{
boolean_t empty;
iv_index_t sum;
iv_index_t hash;
ipc_voucher_t iv;
sum = iv_checksum(new_iv, &empty);
if (empty) {
iv_dealloc(new_iv, FALSE);
return IV_NULL;
}
hash = IV_HASH_BUCKET(sum);
ivht_lock();
queue_iterate(&ivht_bucket[hash], iv, ipc_voucher_t, iv_hash_link) {
assert(iv->iv_hash == hash);
if (0 < iv->iv_refs && iv->iv_sum == sum) {
iv_refs_t refs;
iv_index_t i;
assert(iv->iv_table_size <= new_iv->iv_table_size);
for (i = 0; i < iv->iv_table_size; i++)
if (iv->iv_table[i] != new_iv->iv_table[i])
break;
if (i < iv->iv_table_size)
continue;
while (i < new_iv->iv_table_size)
if (new_iv->iv_table[i++] != IV_UNUSED_VALINDEX)
break;
if (i < new_iv->iv_table_size)
continue;
refs = iv_reference(iv);
if (1 == refs) {
iv->iv_refs = 0;
continue;
}
ivht_unlock();
iv_dealloc(new_iv, FALSE);
return iv;
}
}
new_iv->iv_sum = sum;
new_iv->iv_hash = hash;
queue_enter(&ivht_bucket[hash], new_iv, ipc_voucher_t, iv_hash_link);
ivht_count++;
ivht_unlock();
#if (KDEBUG_LEVEL >= KDEBUG_LEVEL_STANDARD)
if (kdebug_enable & ~KDEBUG_ENABLE_PPT) {
uintptr_t voucher_addr = VM_KERNEL_ADDRPERM((uintptr_t)new_iv);
uintptr_t attr_tracepoints_needed = 0;
if (ipc_voucher_trace_contents) {
#define PAYLOAD_PER_TRACEPOINT (4 * sizeof(uintptr_t))
#define PAYLOAD_SIZE 1024
_Static_assert(PAYLOAD_SIZE % PAYLOAD_PER_TRACEPOINT == 0, "size invariant violated");
mach_voucher_attr_raw_recipe_array_size_t payload_size = PAYLOAD_SIZE;
uintptr_t payload[PAYLOAD_SIZE / sizeof(uintptr_t)];
kern_return_t kr;
kr = mach_voucher_extract_all_attr_recipes(new_iv, (mach_voucher_attr_raw_recipe_array_t)payload, &payload_size);
if (KERN_SUCCESS == kr) {
attr_tracepoints_needed = (payload_size + PAYLOAD_PER_TRACEPOINT - 1) / PAYLOAD_PER_TRACEPOINT;
size_t remainder = payload_size % PAYLOAD_PER_TRACEPOINT;
if (remainder) {
bzero((uint8_t*)payload + payload_size,
PAYLOAD_PER_TRACEPOINT - remainder);
}
}
KERNEL_DEBUG_CONSTANT(MACHDBG_CODE(DBG_MACH_IPC,MACH_IPC_VOUCHER_CREATE) | DBG_FUNC_NONE,
voucher_addr,
new_iv->iv_table_size, ivht_count, payload_size, 0);
uintptr_t index = 0;
while (attr_tracepoints_needed--) {
KERNEL_DEBUG_CONSTANT1(MACHDBG_CODE(DBG_MACH_IPC,MACH_IPC_VOUCHER_CREATE_ATTR_DATA) | DBG_FUNC_NONE,
payload[index],
payload[index+1],
payload[index+2],
payload[index+3],
voucher_addr);
index += 4;
}
} else {
KERNEL_DEBUG_CONSTANT(MACHDBG_CODE(DBG_MACH_IPC,MACH_IPC_VOUCHER_CREATE) | DBG_FUNC_NONE,
voucher_addr,
new_iv->iv_table_size, ivht_count, 0, 0);
}
}
#endif
return new_iv;
}
kern_return_t
ipc_create_mach_voucher(
ipc_voucher_attr_raw_recipe_array_t recipes,
ipc_voucher_attr_raw_recipe_array_size_t recipe_size,
ipc_voucher_t *new_voucher)
{
ipc_voucher_attr_recipe_t sub_recipe;
ipc_voucher_attr_recipe_size_t recipe_used = 0;
ipc_voucher_t voucher;
kern_return_t kr = KERN_SUCCESS;
if (0 == recipe_size) {
*new_voucher = IV_NULL;
return KERN_SUCCESS;
}
voucher = iv_alloc(ivgt_keys_in_use);
if (IV_NULL == voucher)
return KERN_RESOURCE_SHORTAGE;
while (0 < recipe_size - recipe_used) {
if (recipe_size - recipe_used < sizeof(*sub_recipe)) {
kr = KERN_INVALID_ARGUMENT;
break;
}
sub_recipe = (ipc_voucher_attr_recipe_t)(void *)&recipes[recipe_used];
if (recipe_size - recipe_used - sizeof(*sub_recipe) < sub_recipe->content_size) {
kr = KERN_INVALID_ARGUMENT;
break;
}
recipe_used += sizeof(*sub_recipe) + sub_recipe->content_size;
kr = ipc_execute_voucher_recipe_command(voucher,
sub_recipe->key,
sub_recipe->command,
sub_recipe->previous_voucher,
sub_recipe->content,
sub_recipe->content_size,
FALSE);
if (KERN_SUCCESS != kr)
break;
}
if (KERN_SUCCESS == kr) {
*new_voucher = iv_dedup(voucher);
} else {
iv_dealloc(voucher, FALSE);
*new_voucher = IV_NULL;
}
return kr;
}
kern_return_t
ipc_voucher_attr_control_create_mach_voucher(
ipc_voucher_attr_control_t control,
ipc_voucher_attr_raw_recipe_array_t recipes,
ipc_voucher_attr_raw_recipe_array_size_t recipe_size,
ipc_voucher_t *new_voucher)
{
mach_voucher_attr_key_t control_key;
ipc_voucher_attr_recipe_t sub_recipe;
ipc_voucher_attr_recipe_size_t recipe_used = 0;
ipc_voucher_t voucher = IV_NULL;
kern_return_t kr = KERN_SUCCESS;
if (IPC_VOUCHER_ATTR_CONTROL_NULL == control)
return KERN_INVALID_CAPABILITY;
if (0 == recipe_size) {
*new_voucher = IV_NULL;
return KERN_SUCCESS;
}
voucher = iv_alloc(ivgt_keys_in_use);
if (IV_NULL == voucher)
return KERN_RESOURCE_SHORTAGE;
control_key = iv_index_to_key(control->ivac_key_index);
while (0 < recipe_size - recipe_used) {
if (recipe_size - recipe_used < sizeof(*sub_recipe)) {
kr = KERN_INVALID_ARGUMENT;
break;
}
sub_recipe = (ipc_voucher_attr_recipe_t)(void *)&recipes[recipe_used];
if (recipe_size - recipe_used - sizeof(*sub_recipe) < sub_recipe->content_size) {
kr = KERN_INVALID_ARGUMENT;
break;
}
recipe_used += sizeof(*sub_recipe) + sub_recipe->content_size;
kr = ipc_execute_voucher_recipe_command(voucher,
sub_recipe->key,
sub_recipe->command,
sub_recipe->previous_voucher,
sub_recipe->content,
sub_recipe->content_size,
(sub_recipe->key == control_key));
if (KERN_SUCCESS != kr)
break;
}
if (KERN_SUCCESS == kr) {
*new_voucher = iv_dedup(voucher);
} else {
*new_voucher = IV_NULL;
iv_dealloc(voucher, FALSE);
}
return kr;
}
kern_return_t
ipc_register_well_known_mach_voucher_attr_manager(
ipc_voucher_attr_manager_t manager,
mach_voucher_attr_value_handle_t default_value,
mach_voucher_attr_key_t key,
ipc_voucher_attr_control_t *control)
{
ipc_voucher_attr_control_t new_control;
iv_index_t key_index;
iv_index_t hash_index;
if (IVAM_NULL == manager)
return KERN_INVALID_ARGUMENT;
key_index = iv_key_to_index(key);
if (IV_UNUSED_KEYINDEX == key_index)
return KERN_INVALID_ARGUMENT;
new_control = ivac_alloc(key_index);
if (IVAC_NULL == new_control)
return KERN_RESOURCE_SHORTAGE;
new_control->ivac_table[IV_UNUSED_VALINDEX].ivace_value = default_value;
new_control->ivac_table[IV_UNUSED_VALINDEX].ivace_refs = IVACE_REFS_MAX;
new_control->ivac_table[IV_UNUSED_VALINDEX].ivace_made = IVACE_REFS_MAX;
assert(IV_HASH_END == new_control->ivac_table[IV_UNUSED_VALINDEX].ivace_next);
ivgt_lock();
if (IVAM_NULL != iv_global_table[key_index].ivgte_manager) {
ivgt_unlock();
ivac_release(new_control);
return KERN_INVALID_ARGUMENT;
}
iv_global_table[key_index].ivgte_manager = manager;
iv_global_table[key_index].ivgte_control = new_control;
iv_global_table[key_index].ivgte_key = key;
hash_index = iv_hash_value(key_index, default_value);
assert(IV_HASH_END == new_control->ivac_table[hash_index].ivace_index);
new_control->ivac_table[hash_index].ivace_index = IV_UNUSED_VALINDEX;
ivgt_unlock();
*control = new_control;
return KERN_SUCCESS;
}
kern_return_t
mach_voucher_extract_attr_content(
ipc_voucher_t voucher,
mach_voucher_attr_key_t key,
mach_voucher_attr_content_t content,
mach_voucher_attr_content_size_t *in_out_size)
{
mach_voucher_attr_value_handle_t vals[MACH_VOUCHER_ATTR_VALUE_MAX_NESTED];
mach_voucher_attr_value_handle_array_size_t vals_count;
mach_voucher_attr_recipe_command_t command;
ipc_voucher_attr_manager_t manager;
iv_index_t value_index;
iv_index_t key_index;
kern_return_t kr;
if (IV_NULL == voucher)
return KERN_INVALID_ARGUMENT;
key_index = iv_key_to_index(key);
value_index = iv_lookup(voucher, key_index);
if (IV_UNUSED_VALINDEX == value_index) {
*in_out_size = 0;
return KERN_SUCCESS;
}
ivgt_lookup(key_index, FALSE, &manager, NULL);
assert(IVAM_NULL != manager);
ivace_lookup_values(key_index, value_index,
vals, &vals_count);
assert(0 < vals_count);
kr = (manager->ivam_extract_content)(manager, key,
vals, vals_count,
&command,
content, in_out_size);
return kr;
}
kern_return_t
mach_voucher_extract_attr_recipe(
ipc_voucher_t voucher,
mach_voucher_attr_key_t key,
mach_voucher_attr_raw_recipe_t raw_recipe,
mach_voucher_attr_raw_recipe_size_t *in_out_size)
{
mach_voucher_attr_value_handle_t vals[MACH_VOUCHER_ATTR_VALUE_MAX_NESTED];
mach_voucher_attr_value_handle_array_size_t vals_count;
ipc_voucher_attr_manager_t manager;
mach_voucher_attr_recipe_t recipe;
iv_index_t value_index;
iv_index_t key_index;
kern_return_t kr;
if (IV_NULL == voucher)
return KERN_INVALID_ARGUMENT;
key_index = iv_key_to_index(key);
value_index = iv_lookup(voucher, key_index);
if (IV_UNUSED_VALINDEX == value_index) {
*in_out_size = 0;
return KERN_SUCCESS;
}
if (*in_out_size < sizeof(*recipe))
return KERN_NO_SPACE;
recipe = (mach_voucher_attr_recipe_t)(void *)raw_recipe;
recipe->key = key;
recipe->command = MACH_VOUCHER_ATTR_NOOP;
recipe->previous_voucher = MACH_VOUCHER_NAME_NULL;
recipe->content_size = *in_out_size - sizeof(*recipe);
ivgt_lookup(key_index, FALSE, &manager, NULL);
assert(IVAM_NULL != manager);
ivace_lookup_values(key_index, value_index,
vals, &vals_count);
assert(0 < vals_count);
kr = (manager->ivam_extract_content)(manager, key,
vals, vals_count,
&recipe->command,
recipe->content, &recipe->content_size);
if (KERN_SUCCESS == kr) {
assert(*in_out_size - sizeof(*recipe) >= recipe->content_size);
*in_out_size = sizeof(*recipe) + recipe->content_size;
}
return kr;
}
kern_return_t
mach_voucher_extract_all_attr_recipes(
ipc_voucher_t voucher,
mach_voucher_attr_raw_recipe_array_t recipes,
mach_voucher_attr_raw_recipe_array_size_t *in_out_size)
{
mach_voucher_attr_recipe_size_t recipe_size = *in_out_size;
mach_voucher_attr_recipe_size_t recipe_used = 0;
iv_index_t key_index;
if (IV_NULL == voucher)
return KERN_INVALID_ARGUMENT;
for (key_index = 0; key_index < voucher->iv_table_size; key_index++) {
mach_voucher_attr_value_handle_t vals[MACH_VOUCHER_ATTR_VALUE_MAX_NESTED];
mach_voucher_attr_value_handle_array_size_t vals_count;
mach_voucher_attr_content_size_t content_size;
ipc_voucher_attr_manager_t manager;
mach_voucher_attr_recipe_t recipe;
mach_voucher_attr_key_t key;
iv_index_t value_index;
kern_return_t kr;
value_index = iv_lookup(voucher, key_index);
if (IV_UNUSED_VALINDEX == value_index)
continue;
if (recipe_size - recipe_used < sizeof(*recipe))
return KERN_NO_SPACE;
recipe = (mach_voucher_attr_recipe_t)(void *)&recipes[recipe_used];
content_size = recipe_size - recipe_used - sizeof(*recipe);
ivgt_lookup(key_index, FALSE, &manager, NULL);
assert(IVAM_NULL != manager);
ivace_lookup_values(key_index, value_index,
vals, &vals_count);
assert(0 < vals_count);
key = iv_index_to_key(key_index);
recipe->key = key;
recipe->command = MACH_VOUCHER_ATTR_NOOP;
recipe->content_size = content_size;
kr = (manager->ivam_extract_content)(manager, key,
vals, vals_count,
&recipe->command,
recipe->content, &recipe->content_size);
if (KERN_SUCCESS != kr)
return kr;
assert(recipe->content_size <= content_size);
recipe_used += sizeof(*recipe) + recipe->content_size;
}
*in_out_size = recipe_used;
return KERN_SUCCESS;
}
#if !(DEVELOPMENT || DEBUG)
kern_return_t
mach_voucher_debug_info(
ipc_space_t __unused space,
mach_port_name_t __unused voucher_name,
mach_voucher_attr_raw_recipe_array_t __unused recipes,
mach_voucher_attr_raw_recipe_array_size_t __unused *in_out_size)
{
return KERN_NOT_SUPPORTED;
}
#else
kern_return_t
mach_voucher_debug_info(
ipc_space_t space,
mach_port_name_t voucher_name,
mach_voucher_attr_raw_recipe_array_t recipes,
mach_voucher_attr_raw_recipe_array_size_t *in_out_size)
{
ipc_voucher_t voucher = IPC_VOUCHER_NULL;
kern_return_t kr;
ipc_port_t port = MACH_PORT_NULL;
if (!MACH_PORT_VALID(voucher_name)) {
return KERN_INVALID_ARGUMENT;
}
kr = ipc_port_translate_send(space, voucher_name, &port);
if (KERN_SUCCESS != kr)
return KERN_INVALID_ARGUMENT;
voucher = convert_port_to_voucher(port);
ip_unlock(port);
if (voucher) {
kr = mach_voucher_extract_all_attr_recipes(voucher, recipes, in_out_size);
ipc_voucher_release(voucher);
return kr;
}
return KERN_FAILURE;
}
#endif
kern_return_t
mach_voucher_attr_command(
ipc_voucher_t voucher,
mach_voucher_attr_key_t key,
mach_voucher_attr_command_t command,
mach_voucher_attr_content_t in_content,
mach_voucher_attr_content_size_t in_content_size,
mach_voucher_attr_content_t out_content,
mach_voucher_attr_content_size_t *out_content_size)
{
mach_voucher_attr_value_handle_t vals[MACH_VOUCHER_ATTR_VALUE_MAX_NESTED];
mach_voucher_attr_value_handle_array_size_t vals_count;
ipc_voucher_attr_manager_t manager;
ipc_voucher_attr_control_t control;
iv_index_t value_index;
iv_index_t key_index;
kern_return_t kr;
if (IV_NULL == voucher)
return KERN_INVALID_ARGUMENT;
key_index = iv_key_to_index(key);
ivgt_lookup(key_index, TRUE, &manager, &control);
assert(IVAM_NULL != manager);
value_index = iv_lookup(voucher, key_index);
ivace_lookup_values(key_index, value_index,
vals, &vals_count);
kr = (manager->ivam_command)(manager, key,
vals, vals_count,
command,
in_content, in_content_size,
out_content, out_content_size);
ivac_release(control);
return kr;
}
kern_return_t
mach_voucher_attr_control_get_values(
ipc_voucher_attr_control_t control,
ipc_voucher_t voucher,
mach_voucher_attr_value_handle_array_t out_values,
mach_voucher_attr_value_handle_array_size_t *in_out_size)
{
iv_index_t key_index, value_index;
if (IPC_VOUCHER_ATTR_CONTROL_NULL == control)
return KERN_INVALID_CAPABILITY;
if (IV_NULL == voucher)
return KERN_INVALID_ARGUMENT;
if (0 == *in_out_size)
return KERN_SUCCESS;
key_index = control->ivac_key_index;
assert(0 < voucher->iv_refs);
value_index = iv_lookup(voucher, key_index);
ivace_lookup_values(key_index, value_index,
out_values, in_out_size);
return KERN_SUCCESS;
}
kern_return_t
mach_voucher_attr_control_create_mach_voucher(
ipc_voucher_attr_control_t control,
mach_voucher_attr_raw_recipe_array_t recipes,
mach_voucher_attr_raw_recipe_size_t recipe_size,
ipc_voucher_t *new_voucher)
{
mach_voucher_attr_key_t control_key;
mach_voucher_attr_recipe_t sub_recipe;
mach_voucher_attr_recipe_size_t recipe_used = 0;
ipc_voucher_t voucher = IV_NULL;
kern_return_t kr = KERN_SUCCESS;
if (IPC_VOUCHER_ATTR_CONTROL_NULL == control)
return KERN_INVALID_CAPABILITY;
if (0 == recipe_size) {
*new_voucher = IV_NULL;
return KERN_SUCCESS;
}
voucher = iv_alloc(ivgt_keys_in_use);
if (IV_NULL == voucher)
return KERN_RESOURCE_SHORTAGE;
control_key = iv_index_to_key(control->ivac_key_index);
while (0 < recipe_size - recipe_used) {
ipc_voucher_t prev_iv;
if (recipe_size - recipe_used < sizeof(*sub_recipe)) {
kr = KERN_INVALID_ARGUMENT;
break;
}
sub_recipe = (mach_voucher_attr_recipe_t)(void *)&recipes[recipe_used];
if (recipe_size - recipe_used - sizeof(*sub_recipe) < sub_recipe->content_size) {
kr = KERN_INVALID_ARGUMENT;
break;
}
recipe_used += sizeof(*sub_recipe) + sub_recipe->content_size;
prev_iv = convert_port_name_to_voucher(sub_recipe->previous_voucher);
if (MACH_PORT_NULL != sub_recipe->previous_voucher && IV_NULL == prev_iv) {
kr = KERN_INVALID_CAPABILITY;
break;
}
kr = ipc_execute_voucher_recipe_command(voucher,
sub_recipe->key,
sub_recipe->command,
prev_iv,
sub_recipe->content,
sub_recipe->content_size,
(sub_recipe->key == control_key));
ipc_voucher_release(prev_iv);
if (KERN_SUCCESS != kr)
break;
}
if (KERN_SUCCESS == kr) {
*new_voucher = iv_dedup(voucher);
} else {
*new_voucher = IV_NULL;
iv_dealloc(voucher, FALSE);
}
return kr;
}
kern_return_t
host_create_mach_voucher(
host_t host,
mach_voucher_attr_raw_recipe_array_t recipes,
mach_voucher_attr_raw_recipe_size_t recipe_size,
ipc_voucher_t *new_voucher)
{
mach_voucher_attr_recipe_t sub_recipe;
mach_voucher_attr_recipe_size_t recipe_used = 0;
ipc_voucher_t voucher = IV_NULL;
kern_return_t kr = KERN_SUCCESS;
if (host == HOST_NULL)
return KERN_INVALID_ARGUMENT;
if (0 == recipe_size) {
*new_voucher = IV_NULL;
return KERN_SUCCESS;
}
voucher = iv_alloc(ivgt_keys_in_use);
if (IV_NULL == voucher)
return KERN_RESOURCE_SHORTAGE;
while (0 < recipe_size - recipe_used) {
ipc_voucher_t prev_iv;
if (recipe_size - recipe_used < sizeof(*sub_recipe)) {
kr = KERN_INVALID_ARGUMENT;
break;
}
sub_recipe = (mach_voucher_attr_recipe_t)(void *)&recipes[recipe_used];
if (recipe_size - recipe_used - sizeof(*sub_recipe) < sub_recipe->content_size) {
kr = KERN_INVALID_ARGUMENT;
break;
}
recipe_used += sizeof(*sub_recipe) + sub_recipe->content_size;
prev_iv = convert_port_name_to_voucher(sub_recipe->previous_voucher);
if (MACH_PORT_NULL != sub_recipe->previous_voucher && IV_NULL == prev_iv) {
kr = KERN_INVALID_CAPABILITY;
break;
}
kr = ipc_execute_voucher_recipe_command(voucher,
sub_recipe->key,
sub_recipe->command,
prev_iv,
sub_recipe->content,
sub_recipe->content_size,
FALSE);
ipc_voucher_release(prev_iv);
if (KERN_SUCCESS != kr)
break;
}
if (KERN_SUCCESS == kr) {
*new_voucher = iv_dedup(voucher);
} else {
*new_voucher = IV_NULL;
iv_dealloc(voucher, FALSE);
}
return kr;
}
kern_return_t
host_register_well_known_mach_voucher_attr_manager(
host_t host,
mach_voucher_attr_manager_t __unused manager,
mach_voucher_attr_value_handle_t __unused default_value,
mach_voucher_attr_key_t __unused key,
ipc_voucher_attr_control_t __unused *control)
{
if (HOST_NULL == host)
return KERN_INVALID_HOST;
#if 1
return KERN_NOT_SUPPORTED;
#else
mig_voucher_attr_manager_t proxy;
kern_return_t kr;
proxy = mvam_alloc(manager);
kr = ipc_register_well_known_mach_voucher_attr_manager(&proxy->mvam_manager,
default_value,
key,
control);
if (KERN_SUCCESS != kr)
mvam_release(proxy);
return kr;
#endif
}
kern_return_t
host_register_mach_voucher_attr_manager(
host_t host,
mach_voucher_attr_manager_t __unused manager,
mach_voucher_attr_value_handle_t __unused default_value,
mach_voucher_attr_key_t __unused *key,
ipc_voucher_attr_control_t __unused *control)
{
if (HOST_NULL == host)
return KERN_INVALID_HOST;
return KERN_NOT_SUPPORTED;
}
#if defined(MACH_VOUCHER_ATTR_KEY_USER_DATA) || defined(MACH_VOUCHER_ATTR_KEY_TEST)
#define USER_DATA_MAX_DATA (16*1024)
struct user_data_value_element {
mach_voucher_attr_value_reference_t e_made;
mach_voucher_attr_content_size_t e_size;
iv_index_t e_sum;
iv_index_t e_hash;
queue_chain_t e_hash_link;
uint8_t e_data[];
};
typedef struct user_data_value_element *user_data_element_t;
#define USER_DATA_HASH_BUCKETS 127
#define USER_DATA_HASH_BUCKET(x) ((x) % USER_DATA_HASH_BUCKETS)
static queue_head_t user_data_bucket[USER_DATA_HASH_BUCKETS];
static lck_spin_t user_data_lock_data;
#define user_data_lock_init() \
lck_spin_init(&user_data_lock_data, &ipc_lck_grp, &ipc_lck_attr)
#define user_data_lock_destroy() \
lck_spin_destroy(&user_data_lock_data, &ipc_lck_grp)
#define user_data_lock() \
lck_spin_lock(&user_data_lock_data)
#define user_data_lock_try() \
lck_spin_try_lock(&user_data_lock_data)
#define user_data_unlock() \
lck_spin_unlock(&user_data_lock_data)
static kern_return_t
user_data_release_value(
ipc_voucher_attr_manager_t manager,
mach_voucher_attr_key_t key,
mach_voucher_attr_value_handle_t value,
mach_voucher_attr_value_reference_t sync);
static kern_return_t
user_data_get_value(
ipc_voucher_attr_manager_t manager,
mach_voucher_attr_key_t key,
mach_voucher_attr_recipe_command_t command,
mach_voucher_attr_value_handle_array_t prev_values,
mach_voucher_attr_value_handle_array_size_t prev_value_count,
mach_voucher_attr_content_t content,
mach_voucher_attr_content_size_t content_size,
mach_voucher_attr_value_handle_t *out_value,
ipc_voucher_t *out_value_voucher);
static kern_return_t
user_data_extract_content(
ipc_voucher_attr_manager_t manager,
mach_voucher_attr_key_t key,
mach_voucher_attr_value_handle_array_t values,
mach_voucher_attr_value_handle_array_size_t value_count,
mach_voucher_attr_recipe_command_t *out_command,
mach_voucher_attr_content_t out_content,
mach_voucher_attr_content_size_t *in_out_content_size);
static kern_return_t
user_data_command(
ipc_voucher_attr_manager_t manager,
mach_voucher_attr_key_t key,
mach_voucher_attr_value_handle_array_t values,
mach_msg_type_number_t value_count,
mach_voucher_attr_command_t command,
mach_voucher_attr_content_t in_content,
mach_voucher_attr_content_size_t in_content_size,
mach_voucher_attr_content_t out_content,
mach_voucher_attr_content_size_t *out_content_size);
static void
user_data_release(
ipc_voucher_attr_manager_t manager);
struct ipc_voucher_attr_manager user_data_manager = {
.ivam_release_value = user_data_release_value,
.ivam_get_value = user_data_get_value,
.ivam_extract_content = user_data_extract_content,
.ivam_command = user_data_command,
.ivam_release = user_data_release,
};
ipc_voucher_attr_control_t user_data_control;
ipc_voucher_attr_control_t test_control;
#if defined(MACH_VOUCHER_ATTR_KEY_USER_DATA) && defined(MACH_VOUCHER_ATTR_KEY_TEST)
#define USER_DATA_ASSERT_KEY(key) \
assert(MACH_VOUCHER_ATTR_KEY_USER_DATA == (key) || \
MACH_VOUCHER_ATTR_KEY_TEST == (key));
#elif defined(MACH_VOUCHER_ATTR_KEY_USER_DATA)
#define USER_DATA_ASSERT_KEY(key) assert(MACH_VOUCHER_ATTR_KEY_USER_DATA == (key))
#else
#define USER_DATA_ASSERT_KEY(key) assert(MACH_VOUCHER_ATTR_KEY_TEST == (key))
#endif
static kern_return_t
user_data_release_value(
ipc_voucher_attr_manager_t __assert_only manager,
mach_voucher_attr_key_t __assert_only key,
mach_voucher_attr_value_handle_t value,
mach_voucher_attr_value_reference_t sync)
{
user_data_element_t elem;
iv_index_t hash;
assert (&user_data_manager == manager);
USER_DATA_ASSERT_KEY(key);
elem = (user_data_element_t)value;
hash = elem->e_hash;
user_data_lock();
if (sync == elem->e_made) {
queue_remove(&user_data_bucket[hash], elem, user_data_element_t, e_hash_link);
user_data_unlock();
kfree(elem, sizeof(*elem) + elem->e_size);
return KERN_SUCCESS;
}
assert(sync < elem->e_made);
user_data_unlock();
return KERN_FAILURE;
}
static iv_index_t
user_data_checksum(
mach_voucher_attr_content_t content,
mach_voucher_attr_content_size_t content_size)
{
mach_voucher_attr_content_size_t i;
iv_index_t cksum = 0;
for(i = 0; i < content_size; i++, content++) {
cksum = (cksum << 8) ^ (cksum + *(unsigned char *)content);
}
return (~cksum);
}
static user_data_element_t
user_data_dedup(
mach_voucher_attr_content_t content,
mach_voucher_attr_content_size_t content_size)
{
iv_index_t sum;
iv_index_t hash;
user_data_element_t elem;
user_data_element_t alloc = NULL;
sum = user_data_checksum(content, content_size);
hash = USER_DATA_HASH_BUCKET(sum);
retry:
user_data_lock();
queue_iterate(&user_data_bucket[hash], elem, user_data_element_t, e_hash_link) {
assert(elem->e_hash == hash);
if (elem->e_sum == sum && elem->e_size == content_size) {
iv_index_t i;
for (i = 0; i < content_size; i++)
if (elem->e_data[i] != content[i])
break;
if (i < content_size)
continue;
elem->e_made++;
user_data_unlock();
if (NULL != alloc)
kfree(alloc, sizeof(*alloc) + content_size);
return elem;
}
}
if (NULL == alloc) {
user_data_unlock();
alloc = (user_data_element_t)kalloc(sizeof(*alloc) + content_size);
alloc->e_made = 1;
alloc->e_size = content_size;
alloc->e_sum = sum;
alloc->e_hash = hash;
memcpy(alloc->e_data, content, content_size);
goto retry;
}
queue_enter(&user_data_bucket[hash], alloc, user_data_element_t, e_hash_link);
user_data_unlock();
return alloc;
}
static kern_return_t
user_data_get_value(
ipc_voucher_attr_manager_t __assert_only manager,
mach_voucher_attr_key_t __assert_only key,
mach_voucher_attr_recipe_command_t command,
mach_voucher_attr_value_handle_array_t prev_values,
mach_voucher_attr_value_handle_array_size_t prev_value_count,
mach_voucher_attr_content_t content,
mach_voucher_attr_content_size_t content_size,
mach_voucher_attr_value_handle_t *out_value,
ipc_voucher_t *out_value_voucher)
{
user_data_element_t elem;
assert (&user_data_manager == manager);
USER_DATA_ASSERT_KEY(key);
*out_value_voucher = IPC_VOUCHER_NULL;
switch (command) {
case MACH_VOUCHER_ATTR_REDEEM:
if (0 < prev_value_count) {
elem = (user_data_element_t)prev_values[0];
assert(0 < elem->e_made);
elem->e_made++;
*out_value = prev_values[0];
return KERN_SUCCESS;
}
*out_value = 0;
return KERN_SUCCESS;
case MACH_VOUCHER_ATTR_USER_DATA_STORE:
if (USER_DATA_MAX_DATA < content_size)
return KERN_RESOURCE_SHORTAGE;
if (0 == content_size) {
*out_value = 0;
return KERN_SUCCESS;
}
elem = user_data_dedup(content, content_size);
*out_value = (mach_voucher_attr_value_handle_t)elem;
return KERN_SUCCESS;
default:
return KERN_INVALID_ARGUMENT;
}
}
static kern_return_t
user_data_extract_content(
ipc_voucher_attr_manager_t __assert_only manager,
mach_voucher_attr_key_t __assert_only key,
mach_voucher_attr_value_handle_array_t values,
mach_voucher_attr_value_handle_array_size_t value_count,
mach_voucher_attr_recipe_command_t *out_command,
mach_voucher_attr_content_t out_content,
mach_voucher_attr_content_size_t *in_out_content_size)
{
mach_voucher_attr_content_size_t size = 0;
user_data_element_t elem;
unsigned int i;
assert (&user_data_manager == manager);
USER_DATA_ASSERT_KEY(key);
for (i = 0; i < value_count ; i++) {
elem = (user_data_element_t)values[i];
assert(USER_DATA_MAX_DATA >= elem->e_size);
if (size + elem->e_size > *in_out_content_size)
return KERN_NO_SPACE;
memcpy(&out_content[size], elem->e_data, elem->e_size);
size += elem->e_size;
}
*out_command = MACH_VOUCHER_ATTR_BITS_STORE;
*in_out_content_size = size;
return KERN_SUCCESS;
}
static kern_return_t
user_data_command(
ipc_voucher_attr_manager_t __assert_only manager,
mach_voucher_attr_key_t __assert_only key,
mach_voucher_attr_value_handle_array_t __unused values,
mach_msg_type_number_t __unused value_count,
mach_voucher_attr_command_t __unused command,
mach_voucher_attr_content_t __unused in_content,
mach_voucher_attr_content_size_t __unused in_content_size,
mach_voucher_attr_content_t __unused out_content,
mach_voucher_attr_content_size_t __unused *out_content_size)
{
assert (&user_data_manager == manager);
USER_DATA_ASSERT_KEY(key);
return KERN_FAILURE;
}
static void
user_data_release(
ipc_voucher_attr_manager_t manager)
{
if (manager != &user_data_manager)
return;
panic("Voucher user-data manager released");
}
static int user_data_manager_inited = 0;
void
user_data_attr_manager_init()
{
kern_return_t kr;
#if defined(MACH_VOUCHER_ATTR_KEY_USER_DATA)
if ((user_data_manager_inited & 0x1) != 0x1) {
kr = ipc_register_well_known_mach_voucher_attr_manager(&user_data_manager,
(mach_voucher_attr_value_handle_t)0,
MACH_VOUCHER_ATTR_KEY_USER_DATA,
&user_data_control);
if (KERN_SUCCESS != kr)
printf("Voucher user-data manager register(USER-DATA) returned %d", kr);
else
user_data_manager_inited |= 0x1;
}
#endif
#if defined(MACH_VOUCHER_ATTR_KEY_TEST)
if ((user_data_manager_inited & 0x2) != 0x2) {
kr = ipc_register_well_known_mach_voucher_attr_manager(&user_data_manager,
(mach_voucher_attr_value_handle_t)0,
MACH_VOUCHER_ATTR_KEY_TEST,
&test_control);
if (KERN_SUCCESS != kr)
printf("Voucher user-data manager register(TEST) returned %d", kr);
else
user_data_manager_inited |= 0x2;
}
#endif
#if defined(MACH_VOUCHER_ATTR_KEY_USER_DATA) || defined(MACH_VOUCHER_ATTR_KEY_TEST)
int i;
for (i=0; i < USER_DATA_HASH_BUCKETS; i++)
queue_init(&user_data_bucket[i]);
user_data_lock_init();
#endif
}
#endif