darling-xnu/osfmk/bank/bank.c
2023-05-16 21:41:14 -07:00

1859 lines
59 KiB
C

/*
* Copyright (c) 2012-2020 Apple Inc. All rights reserved.
*
* @APPLE_OSREFERENCE_LICENSE_HEADER_START@
*
* This file contains Original Code and/or Modifications of Original Code
* as defined in and that are subject to the Apple Public Source License
* Version 2.0 (the 'License'). You may not use this file except in
* 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@
*/
#include <bank/bank_internal.h>
#include <bank/bank_types.h>
#include <mach/mach_types.h>
#include <mach/kern_return.h>
#include <ipc/ipc_port.h>
#include <mach/mach_vm.h>
#include <mach/vm_map.h>
#include <vm/vm_map.h>
#include <mach/host_priv.h>
#include <mach/host_special_ports.h>
#include <kern/host.h>
#include <kern/ledger.h>
#include <kern/coalition.h>
#include <kern/thread_group.h>
#include <sys/kdebug.h>
#include <IOKit/IOBSD.h>
#include <mach/mach_voucher_attr_control.h>
#include <kern/policy_internal.h>
static ZONE_DECLARE(bank_task_zone, "bank_task",
sizeof(struct bank_task), ZC_NONE);
static ZONE_DECLARE(bank_account_zone, "bank_account",
sizeof(struct bank_account), ZC_NONE);
#define MAX_BANK_TASK (CONFIG_TASK_MAX)
#define MAX_BANK_ACCOUNT (CONFIG_TASK_MAX + CONFIG_THREAD_MAX)
#define BANK_ELEMENT_TO_HANDLE(x) (CAST_DOWN(bank_handle_t, (x)))
#define HANDLE_TO_BANK_ELEMENT(x) (CAST_DOWN(bank_element_t, (x)))
/* Need macro since bank_element_t is 4 byte aligned on release kernel and direct type case gives compilation error */
#define CAST_TO_BANK_ELEMENT(x) ((bank_element_t)((void *)(x)))
#define CAST_TO_BANK_TASK(x) ((bank_task_t)((void *)(x)))
#define CAST_TO_BANK_ACCOUNT(x) ((bank_account_t)((void *)(x)))
ipc_voucher_attr_control_t bank_voucher_attr_control; /* communication channel from ATM to voucher system */
struct persona;
extern struct persona *system_persona, *proxy_system_persona;
uint32_t persona_get_id(struct persona *persona);
extern int unique_persona;
static ledger_template_t bank_ledger_template = NULL;
struct _bank_ledger_indices bank_ledgers = { .cpu_time = -1, .energy = -1 };
static bank_task_t bank_task_alloc_init(task_t task);
static bank_account_t bank_account_alloc_init(bank_task_t bank_holder, bank_task_t bank_merchant,
bank_task_t bank_secureoriginator, bank_task_t bank_proximateprocess, struct thread_group* banktg, uint32_t persona_id);
static bank_task_t get_bank_task_context(task_t task, boolean_t initialize);
static void bank_task_dealloc(bank_task_t bank_task, mach_voucher_attr_value_reference_t sync);
static kern_return_t bank_account_dealloc_with_sync(bank_account_t bank_account, mach_voucher_attr_value_reference_t sync);
static void bank_rollup_chit_to_tasks(ledger_t bill, ledger_t bank_holder_ledger, ledger_t bank_merchant_ledger,
int bank_holder_pid, int bank_merchant_pid);
static ledger_t bank_get_bank_task_ledger_with_ref(bank_task_t bank_task);
static void bank_destroy_bank_task_ledger(bank_task_t bank_task);
static void init_bank_ledgers(void);
static boolean_t bank_task_is_propagate_entitled(task_t t);
static boolean_t bank_task_is_persona_modify_entitled(task_t t);
static struct thread_group *bank_get_bank_task_thread_group(bank_task_t bank_task __unused);
static struct thread_group *bank_get_bank_account_thread_group(bank_account_t bank_account __unused);
static boolean_t bank_verify_persona_id(uint32_t persona_id);
/* lock to protect task->bank_context transition */
static LCK_GRP_DECLARE(bank_lock_grp, "bank_lock");
static LCK_ATTR_DECLARE(bank_lock_attr, 0, 0);
static LCK_SPIN_DECLARE_ATTR(g_bank_task_lock_data, &bank_lock_grp, &bank_lock_attr);
static TUNABLE(bool, disable_persona_propagate_check,
"disable_persona_propagate_check", false);
#define global_bank_task_lock() \
lck_spin_lock_grp(&g_bank_task_lock_data, &bank_lock_grp)
#define global_bank_task_lock_try() \
lck_spin_try_lock_grp(&g_bank_task_lock_data, &bank_lock_grp)
#define global_bank_task_unlock() \
lck_spin_unlock(&g_bank_task_lock_data)
extern uint64_t proc_uniqueid(void *p);
struct proc;
extern int32_t proc_pid(struct proc *p);
extern int32_t proc_pidversion(void *p);
extern uint32_t proc_persona_id(void *p);
extern uint32_t proc_getuid(void *p);
extern uint32_t proc_getgid(void *p);
extern void proc_getexecutableuuid(void *p, unsigned char *uuidbuf, unsigned long size);
extern int kauth_cred_issuser(void *cred);
extern void* kauth_cred_get(void);
extern void* persona_lookup(uint32_t id);
extern void persona_put(void* persona);
kern_return_t
bank_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);
kern_return_t
bank_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_msg_type_number_t __assert_only prev_value_count,
mach_voucher_attr_content_t recipe,
mach_voucher_attr_content_size_t recipe_size,
mach_voucher_attr_value_handle_t *out_value,
mach_voucher_attr_value_flags_t *out_flags,
ipc_voucher_t *out_value_voucher);
kern_return_t
bank_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_msg_type_number_t value_count,
mach_voucher_attr_recipe_command_t *out_command,
mach_voucher_attr_content_t out_recipe,
mach_voucher_attr_content_size_t *in_out_recipe_size);
kern_return_t
bank_command(
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_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 *in_out_content_size);
void
bank_release(ipc_voucher_attr_manager_t __assert_only manager);
/*
* communication channel from voucher system to ATM
*/
const struct ipc_voucher_attr_manager bank_manager = {
.ivam_release_value = bank_release_value,
.ivam_get_value = bank_get_value,
.ivam_extract_content = bank_extract_content,
.ivam_command = bank_command,
.ivam_release = bank_release,
.ivam_flags = (IVAM_FLAGS_SUPPORT_SEND_PREPROCESS | IVAM_FLAGS_SUPPORT_RECEIVE_POSTPROCESS),
};
#if DEVELOPMENT || DEBUG
LCK_GRP_DECLARE(bank_dev_lock_grp, "bank_dev_lock");
LCK_MTX_DECLARE(bank_tasks_list_lock, &bank_dev_lock_grp);
LCK_MTX_DECLARE(bank_accounts_list_lock, &bank_dev_lock_grp);
queue_head_t bank_tasks_list = QUEUE_HEAD_INITIALIZER(bank_tasks_list);
queue_head_t bank_accounts_list = QUEUE_HEAD_INITIALIZER(bank_accounts_list);
#endif
/*
* Routine: bank_init
* Purpose: Initialize the BANK subsystem.
* Returns: None.
*/
void
bank_init()
{
kern_return_t kr = KERN_SUCCESS;
init_bank_ledgers();
/* Register the bank manager with the Vouchers sub system. */
kr = ipc_register_well_known_mach_voucher_attr_manager(
&bank_manager,
0,
MACH_VOUCHER_ATTR_KEY_BANK,
&bank_voucher_attr_control);
if (kr != KERN_SUCCESS) {
panic("BANK subsystem initialization failed");
}
kprintf("BANK subsystem is initialized\n");
}
/*
* BANK Resource Manager Routines.
*/
/*
* Routine: bank_release_value
* Purpose: Release a value, if sync matches the sync count in value.
* Returns: KERN_SUCCESS: on Successful deletion.
* KERN_FAILURE: if sync value does not matches.
*/
kern_return_t
bank_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)
{
bank_task_t bank_task = BANK_TASK_NULL;
bank_element_t bank_element = BANK_ELEMENT_NULL;
bank_account_t bank_account = BANK_ACCOUNT_NULL;
kern_return_t kr = KERN_SUCCESS;
assert(MACH_VOUCHER_ATTR_KEY_BANK == key);
assert(manager == &bank_manager);
bank_element = HANDLE_TO_BANK_ELEMENT(value);
/* Voucher system should never release the default or persistent value */
assert(bank_element != BANK_DEFAULT_VALUE && bank_element != BANK_DEFAULT_TASK_VALUE);
if (bank_element == BANK_DEFAULT_VALUE || bank_element == BANK_DEFAULT_TASK_VALUE) {
/* Return success for default and default task value */
return KERN_SUCCESS;
}
if (bank_element->be_type == BANK_TASK) {
bank_task = CAST_TO_BANK_TASK(bank_element);
/* Checking of the made ref with sync and clearing of voucher ref should be done under a lock */
lck_mtx_lock(&bank_task->bt_acc_to_pay_lock);
if (bank_task->bt_made != sync) {
lck_mtx_unlock(&bank_task->bt_acc_to_pay_lock);
return KERN_FAILURE;
}
bank_task_made_release_num(bank_task, sync);
assert(bank_task->bt_voucher_ref == 1);
bank_task->bt_voucher_ref = 0;
lck_mtx_unlock(&bank_task->bt_acc_to_pay_lock);
bank_task_dealloc(bank_task, 1);
} else if (bank_element->be_type == BANK_ACCOUNT) {
bank_account = CAST_TO_BANK_ACCOUNT(bank_element);
kr = bank_account_dealloc_with_sync(bank_account, sync);
} else {
panic("Bogus bank type: %d passed in get_value\n", bank_element->be_type);
}
return kr;
}
/*
* Routine: bank_get_value
*
* This function uses the recipe to create a bank attribute for a voucher.
*/
kern_return_t
bank_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_msg_type_number_t prev_value_count,
mach_voucher_attr_content_t recipe,
mach_voucher_attr_content_size_t recipe_size,
mach_voucher_attr_value_handle_t *out_value,
mach_voucher_attr_value_flags_t *out_flags,
ipc_voucher_t *out_value_voucher)
{
bank_task_t bank_holder = BANK_TASK_NULL;
bank_task_t bank_merchant = BANK_TASK_NULL;
bank_task_t bank_secureoriginator = BANK_TASK_NULL;
bank_task_t bank_proximateprocess = BANK_TASK_NULL;
bank_element_t bank_element = BANK_ELEMENT_NULL;
bank_account_t bank_account = BANK_ACCOUNT_NULL;
bank_account_t old_bank_account = BANK_ACCOUNT_NULL;
mach_voucher_attr_value_handle_t bank_handle;
task_t task;
kern_return_t kr = KERN_SUCCESS;
mach_msg_type_number_t i;
struct thread_group *thread_group = NULL;
struct thread_group *cur_thread_group = NULL;
uint32_t persona_id = proc_persona_id(NULL);
assert(MACH_VOUCHER_ATTR_KEY_BANK == key);
assert(manager == &bank_manager);
/* never an out voucher */
*out_value_voucher = IPC_VOUCHER_NULL;
*out_flags = MACH_VOUCHER_ATTR_VALUE_FLAGS_NONE;
switch (command) {
case MACH_VOUCHER_ATTR_BANK_CREATE:
/* It returns the default task value. This value is replaced by
* an actual bank task reference, by using a recipe with
* MACH_VOUCHER_ATTR_SEND_PREPROCESS command.
*/
*out_value = BANK_ELEMENT_TO_HANDLE(BANK_DEFAULT_TASK_VALUE);
*out_flags = MACH_VOUCHER_ATTR_VALUE_FLAGS_PERSIST;
break;
case MACH_VOUCHER_ATTR_BANK_MODIFY_PERSONA:
/* It creates a bank account attribute value with a new persona id
* and auto-redeems it on behalf of the bank_holder.
*/
*out_value = BANK_ELEMENT_TO_HANDLE(BANK_DEFAULT_VALUE);
for (i = 0; i < prev_value_count; i++) {
bank_handle = prev_values[i];
bank_element = HANDLE_TO_BANK_ELEMENT(bank_handle);
/* Expect a pre-processed attribute value */
if (bank_element == BANK_DEFAULT_VALUE || bank_element == BANK_DEFAULT_TASK_VALUE) {
continue;
}
if (!bank_task_is_persona_modify_entitled(current_task())) {
return KERN_NO_ACCESS;
}
struct persona_modify_info pmi = {};
if (recipe_size == sizeof(pmi)) {
memcpy((void *)&pmi, recipe, sizeof(pmi));
persona_id = pmi.persona_id;
} else {
return KERN_INVALID_ARGUMENT;
}
/* Verify if the persona id is valid */
if (!bank_verify_persona_id(persona_id)) {
return KERN_INVALID_ARGUMENT;
}
/* Update the persona id only if the bank element is a bank task.
* This ensures that the bank_holder can be trusted.
*/
if (bank_element->be_type == BANK_TASK) {
bank_holder = CAST_TO_BANK_TASK(bank_element);
/* Ensure that the requestor validated by userspace matches
* the bank_holder
*/
if (pmi.unique_pid != bank_holder->bt_unique_pid) {
return KERN_INVALID_CAPABILITY;
}
bank_merchant = bank_holder;
bank_secureoriginator = bank_holder;
bank_proximateprocess = bank_holder;
thread_group = bank_get_bank_task_thread_group(bank_holder);
} else if (bank_element->be_type == BANK_ACCOUNT) {
return KERN_INVALID_ARGUMENT;
} else {
panic("Bogus bank type: %d passed in get_value\n", bank_element->be_type);
}
/* Do not replace persona id if the task is not spawned in system persona */
if (unique_persona &&
bank_merchant->bt_persona_id != persona_get_id(system_persona) &&
bank_merchant->bt_persona_id != persona_get_id(proxy_system_persona) &&
bank_merchant->bt_persona_id != persona_id) {
return KERN_INVALID_ARGUMENT;
}
if (bank_holder->bt_persona_id == persona_id) {
lck_mtx_lock(&bank_holder->bt_acc_to_pay_lock);
bank_task_made_reference(bank_holder);
if (bank_holder->bt_voucher_ref == 0) {
/* Take a ref for voucher system, if voucher system does not have a ref */
bank_task_reference(bank_holder);
bank_holder->bt_voucher_ref = 1;
}
lck_mtx_unlock(&bank_holder->bt_acc_to_pay_lock);
*out_value = BANK_ELEMENT_TO_HANDLE(bank_holder);
return kr;
}
bank_account = bank_account_alloc_init(bank_holder, bank_merchant,
bank_secureoriginator, bank_proximateprocess,
thread_group, persona_id);
if (bank_account == BANK_ACCOUNT_NULL) {
return KERN_RESOURCE_SHORTAGE;
}
*out_value = BANK_ELEMENT_TO_HANDLE(bank_account);
return kr;
}
break;
case MACH_VOUCHER_ATTR_AUTO_REDEEM:
/* It creates a bank account with the bank_merchant set to the current task.
* A bank attribute voucher needs to be redeemed before it can be adopted by
* it's threads.
*/
for (i = 0; i < prev_value_count; i++) {
bank_handle = prev_values[i];
bank_element = HANDLE_TO_BANK_ELEMENT(bank_handle);
/* Should not have received default task value from an IPC */
if (bank_element == BANK_DEFAULT_VALUE || bank_element == BANK_DEFAULT_TASK_VALUE) {
continue;
}
task = current_task();
if (bank_element->be_type == BANK_TASK) {
bank_holder = CAST_TO_BANK_TASK(bank_element);
bank_secureoriginator = bank_holder;
bank_proximateprocess = bank_holder;
thread_group = bank_get_bank_task_thread_group(bank_holder);
persona_id = bank_holder->bt_persona_id;
} else if (bank_element->be_type == BANK_ACCOUNT) {
old_bank_account = CAST_TO_BANK_ACCOUNT(bank_element);
bank_holder = old_bank_account->ba_holder;
bank_secureoriginator = old_bank_account->ba_secureoriginator;
bank_proximateprocess = old_bank_account->ba_proximateprocess;
thread_group = bank_get_bank_account_thread_group(old_bank_account);
persona_id = old_bank_account->ba_so_persona_id;
} else {
panic("Bogus bank type: %d passed in get_value\n", bank_element->be_type);
}
bank_merchant = get_bank_task_context(task, FALSE);
if (bank_merchant == BANK_TASK_NULL) {
return KERN_RESOURCE_SHORTAGE;
}
cur_thread_group = bank_get_bank_task_thread_group(bank_merchant);
/* Change voucher thread group to current thread group for Apps */
if (task_is_app(task)) {
thread_group = cur_thread_group;
}
/* Change the persona-id to current task persona-id if the task is not spawned in system persona */
if (unique_persona &&
bank_merchant->bt_persona_id != persona_get_id(system_persona) &&
bank_merchant->bt_persona_id != persona_get_id(proxy_system_persona)) {
persona_id = bank_merchant->bt_persona_id;
}
/* Check if trying to redeem for self task, return the default bank task */
if (bank_holder == bank_merchant &&
bank_holder == bank_secureoriginator &&
bank_holder == bank_proximateprocess &&
thread_group == cur_thread_group &&
persona_id == bank_holder->bt_persona_id) {
*out_value = BANK_ELEMENT_TO_HANDLE(BANK_DEFAULT_TASK_VALUE);
*out_flags = MACH_VOUCHER_ATTR_VALUE_FLAGS_PERSIST;
return kr;
}
bank_account = bank_account_alloc_init(bank_holder, bank_merchant,
bank_secureoriginator, bank_proximateprocess,
thread_group, persona_id);
if (bank_account == BANK_ACCOUNT_NULL) {
return KERN_RESOURCE_SHORTAGE;
}
*out_value = BANK_ELEMENT_TO_HANDLE(bank_account);
return kr;
}
*out_value = BANK_ELEMENT_TO_HANDLE(BANK_DEFAULT_VALUE);
break;
case MACH_VOUCHER_ATTR_SEND_PREPROCESS:
for (i = 0; i < prev_value_count; i++) {
bank_handle = prev_values[i];
bank_element = HANDLE_TO_BANK_ELEMENT(bank_handle);
if (bank_element == BANK_DEFAULT_VALUE) {
continue;
}
task = current_task();
if (bank_element == BANK_DEFAULT_TASK_VALUE) {
bank_element = CAST_TO_BANK_ELEMENT(get_bank_task_context(task, FALSE));
}
if (bank_element->be_type == BANK_TASK) {
bank_holder = CAST_TO_BANK_TASK(bank_element);
bank_secureoriginator = bank_holder;
thread_group = bank_get_bank_task_thread_group(bank_holder);
persona_id = bank_holder->bt_persona_id;
} else if (bank_element->be_type == BANK_ACCOUNT) {
old_bank_account = CAST_TO_BANK_ACCOUNT(bank_element);
bank_holder = old_bank_account->ba_holder;
bank_secureoriginator = old_bank_account->ba_secureoriginator;
thread_group = bank_get_bank_account_thread_group(old_bank_account);
persona_id = old_bank_account->ba_so_persona_id;
} else {
panic("Bogus bank type: %d passed in get_value\n", bank_element->be_type);
}
bank_merchant = get_bank_task_context(task, FALSE);
if (bank_merchant == BANK_TASK_NULL) {
return KERN_RESOURCE_SHORTAGE;
}
cur_thread_group = bank_get_bank_task_thread_group(bank_merchant);
/*
* If the process doesn't have secure persona entitlement,
* then replace the secure originator to current task.
* Also update the persona_id to match that of the secure originator.
*/
if (bank_merchant->bt_hasentitlement == 0) {
KERNEL_DEBUG_CONSTANT_IST(KDEBUG_TRACE,
(BANK_CODE(BANK_ACCOUNT_INFO, (BANK_SECURE_ORIGINATOR_CHANGED))) | DBG_FUNC_NONE,
bank_secureoriginator->bt_pid, bank_merchant->bt_pid, 0, 0, 0);
bank_secureoriginator = bank_merchant;
persona_id = bank_merchant->bt_persona_id;
}
bank_proximateprocess = bank_merchant;
/* Check if trying to pre-process for self task, return the bank task */
if (bank_holder == bank_merchant &&
bank_holder == bank_secureoriginator &&
bank_holder == bank_proximateprocess &&
thread_group == cur_thread_group &&
persona_id == bank_holder->bt_persona_id) {
lck_mtx_lock(&bank_holder->bt_acc_to_pay_lock);
bank_task_made_reference(bank_holder);
if (bank_holder->bt_voucher_ref == 0) {
/* Take a ref for voucher system, if voucher system does not have a ref */
bank_task_reference(bank_holder);
bank_holder->bt_voucher_ref = 1;
}
lck_mtx_unlock(&bank_holder->bt_acc_to_pay_lock);
*out_value = BANK_ELEMENT_TO_HANDLE(bank_holder);
return kr;
}
bank_account = bank_account_alloc_init(bank_holder, bank_merchant,
bank_secureoriginator, bank_proximateprocess,
thread_group, persona_id);
if (bank_account == BANK_ACCOUNT_NULL) {
return KERN_RESOURCE_SHORTAGE;
}
*out_value = BANK_ELEMENT_TO_HANDLE(bank_account);
return kr;
}
*out_value = BANK_ELEMENT_TO_HANDLE(BANK_DEFAULT_VALUE);
break;
case MACH_VOUCHER_ATTR_REDEEM:
/* This command expects that the bank attribute has been auto-redeemed
* and returns a reference to that bank account value.
*/
for (i = 0; i < prev_value_count; i++) {
bank_handle = prev_values[i];
bank_element = HANDLE_TO_BANK_ELEMENT(bank_handle);
if (bank_element == BANK_DEFAULT_VALUE) {
continue;
}
if (bank_element == BANK_DEFAULT_TASK_VALUE) {
*out_value = BANK_ELEMENT_TO_HANDLE(BANK_DEFAULT_TASK_VALUE);
*out_flags = MACH_VOUCHER_ATTR_VALUE_FLAGS_PERSIST;
return kr;
}
task = current_task();
if (bank_element->be_type == BANK_TASK) {
bank_holder = CAST_TO_BANK_TASK(bank_element);
if (bank_holder == get_bank_task_context(task, FALSE)) {
*out_value = BANK_ELEMENT_TO_HANDLE(BANK_DEFAULT_TASK_VALUE);
*out_flags = MACH_VOUCHER_ATTR_VALUE_FLAGS_PERSIST;
} else {
kr = KERN_INVALID_CAPABILITY;
}
return kr;
} else if (bank_element->be_type == BANK_ACCOUNT) {
bank_account = CAST_TO_BANK_ACCOUNT(bank_element);
bank_merchant = bank_account->ba_merchant;
if (bank_merchant != get_bank_task_context(task, FALSE)) {
/* This error can be used to verify if the task can
* adopt the voucher.
*/
kr = KERN_INVALID_CAPABILITY;
return kr;
}
bank_account_made_reference(bank_account);
*out_value = BANK_ELEMENT_TO_HANDLE(bank_account);
return kr;
} else {
panic("Bogus bank type: %d passed in get_value\n", bank_element->be_type);
}
}
*out_value = BANK_ELEMENT_TO_HANDLE(BANK_DEFAULT_VALUE);
break;
default:
kr = KERN_INVALID_ARGUMENT;
break;
}
return kr;
}
/*
* Routine: bank_extract_content
* Purpose: Extract a set of aid from an array of voucher values.
* Returns: KERN_SUCCESS: on Success.
* KERN_FAILURE: one of the value is not present in the hash.
* KERN_NO_SPACE: insufficeint buffer provided to fill an array of aid.
*/
kern_return_t
bank_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_msg_type_number_t value_count,
mach_voucher_attr_recipe_command_t *out_command,
mach_voucher_attr_content_t out_recipe,
mach_voucher_attr_content_size_t *in_out_recipe_size)
{
bank_task_t bank_task = BANK_TASK_NULL;
bank_element_t bank_element = BANK_ELEMENT_NULL;
bank_account_t bank_account = BANK_ACCOUNT_NULL;
mach_voucher_attr_value_handle_t bank_handle;
char buf[MACH_VOUCHER_BANK_CONTENT_SIZE];
mach_msg_type_number_t i;
assert(MACH_VOUCHER_ATTR_KEY_BANK == key);
assert(manager == &bank_manager);
for (i = 0; i < value_count && *in_out_recipe_size > 0; i++) {
bank_handle = values[i];
bank_element = HANDLE_TO_BANK_ELEMENT(bank_handle);
if (bank_element == BANK_DEFAULT_VALUE) {
continue;
}
if (bank_element == BANK_DEFAULT_TASK_VALUE) {
bank_element = CAST_TO_BANK_ELEMENT(get_bank_task_context(current_task(), FALSE));
}
if (MACH_VOUCHER_BANK_CONTENT_SIZE > *in_out_recipe_size) {
*in_out_recipe_size = 0;
return KERN_NO_SPACE;
}
if (bank_element->be_type == BANK_TASK) {
bank_task = CAST_TO_BANK_TASK(bank_element);
snprintf(buf, MACH_VOUCHER_BANK_CONTENT_SIZE,
" Bank Context for a pid %d\n", bank_task->bt_pid);
} else if (bank_element->be_type == BANK_ACCOUNT) {
bank_account = CAST_TO_BANK_ACCOUNT(bank_element);
snprintf(buf, MACH_VOUCHER_BANK_CONTENT_SIZE,
" Bank Account linking holder pid %d with merchant pid %d, originator PID/persona: %d, %u and proximate PID/persona: %d, %u\n",
bank_account->ba_holder->bt_pid,
bank_account->ba_merchant->bt_pid,
bank_account->ba_secureoriginator->bt_pid,
bank_account->ba_so_persona_id,
bank_account->ba_proximateprocess->bt_pid,
bank_account->ba_proximateprocess->bt_persona_id);
} else {
panic("Bogus bank type: %d passed in get_value\n", bank_element->be_type);
}
memcpy(&out_recipe[0], buf, strlen(buf) + 1);
*out_command = MACH_VOUCHER_ATTR_BANK_NULL;
*in_out_recipe_size = (mach_voucher_attr_content_size_t)strlen(buf) + 1;
return KERN_SUCCESS;
}
return KERN_SUCCESS;
}
/*
* Routine: bank_command
* Purpose: Execute a command against a set of bank values.
* Returns: KERN_SUCCESS: On successful execution of command.
* KERN_FAILURE: On failure.
*/
kern_return_t
bank_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)
{
bank_task_t bank_task = BANK_TASK_NULL;
bank_task_t bank_secureoriginator = BANK_TASK_NULL;
bank_task_t bank_proximateprocess = BANK_TASK_NULL;
struct persona_token *token = NULL;
bank_element_t bank_element = BANK_ELEMENT_NULL;
bank_account_t bank_account = BANK_ACCOUNT_NULL;
mach_voucher_attr_value_handle_t bank_handle;
mach_msg_type_number_t i;
int32_t pid;
uint32_t persona_id;
assert(MACH_VOUCHER_ATTR_KEY_BANK == key);
assert(manager == &bank_manager);
switch (command) {
case BANK_ORIGINATOR_PID:
if ((sizeof(pid)) > *out_content_size) {
*out_content_size = 0;
return KERN_NO_SPACE;
}
for (i = 0; i < value_count; i++) {
bank_handle = values[i];
bank_element = HANDLE_TO_BANK_ELEMENT(bank_handle);
if (bank_element == BANK_DEFAULT_VALUE) {
continue;
}
if (bank_element == BANK_DEFAULT_TASK_VALUE) {
bank_element = CAST_TO_BANK_ELEMENT(get_bank_task_context(current_task(), FALSE));
}
if (bank_element->be_type == BANK_TASK) {
bank_task = CAST_TO_BANK_TASK(bank_element);
} else if (bank_element->be_type == BANK_ACCOUNT) {
bank_account = CAST_TO_BANK_ACCOUNT(bank_element);
bank_task = bank_account->ba_holder;
} else {
panic("Bogus bank type: %d passed in voucher_command\n", bank_element->be_type);
}
pid = bank_task->bt_pid;
memcpy(&out_content[0], &pid, sizeof(pid));
*out_content_size = (mach_voucher_attr_content_size_t)sizeof(pid);
return KERN_SUCCESS;
}
/* In the case of no value, return error KERN_INVALID_VALUE */
*out_content_size = 0;
return KERN_INVALID_VALUE;
case BANK_PERSONA_TOKEN:
if ((sizeof(struct persona_token)) > *out_content_size) {
*out_content_size = 0;
return KERN_NO_SPACE;
}
for (i = 0; i < value_count; i++) {
bank_handle = values[i];
bank_element = HANDLE_TO_BANK_ELEMENT(bank_handle);
if (bank_element == BANK_DEFAULT_VALUE) {
continue;
}
if (bank_element == BANK_DEFAULT_TASK_VALUE) {
bank_element = CAST_TO_BANK_ELEMENT(get_bank_task_context(current_task(), FALSE));
}
if (bank_element->be_type == BANK_TASK) {
*out_content_size = 0;
return KERN_INVALID_OBJECT;
} else if (bank_element->be_type == BANK_ACCOUNT) {
bank_account = CAST_TO_BANK_ACCOUNT(bank_element);
bank_secureoriginator = bank_account->ba_secureoriginator;
bank_proximateprocess = bank_account->ba_proximateprocess;
} else {
panic("Bogus bank type: %d passed in voucher_command\n", bank_element->be_type);
}
token = (struct persona_token *)(void *)&out_content[0];
memcpy(&token->originator, &bank_secureoriginator->bt_proc_persona, sizeof(struct proc_persona_info));
memcpy(&token->proximate, &bank_proximateprocess->bt_proc_persona, sizeof(struct proc_persona_info));
*out_content_size = (mach_voucher_attr_content_size_t)sizeof(*token);
return KERN_SUCCESS;
}
/* In the case of no value, return error KERN_INVALID_VALUE */
*out_content_size = 0;
return KERN_INVALID_VALUE;
case BANK_PERSONA_ID:
if ((sizeof(persona_id)) > *out_content_size) {
*out_content_size = 0;
return KERN_NO_SPACE;
}
for (i = 0; i < value_count; i++) {
bank_handle = values[i];
bank_element = HANDLE_TO_BANK_ELEMENT(bank_handle);
if (bank_element == BANK_DEFAULT_VALUE) {
continue;
}
if (bank_element == BANK_DEFAULT_TASK_VALUE) {
bank_element = CAST_TO_BANK_ELEMENT(get_bank_task_context(current_task(), FALSE));
}
if (bank_element->be_type == BANK_TASK) {
bank_task = CAST_TO_BANK_TASK(bank_element);
persona_id = bank_task->bt_persona_id;
} else if (bank_element->be_type == BANK_ACCOUNT) {
bank_account = CAST_TO_BANK_ACCOUNT(bank_element);
persona_id = bank_account->ba_so_persona_id;
} else {
panic("Bogus bank type: %d passed in voucher_command\n", bank_element->be_type);
}
memcpy(out_content, &persona_id, sizeof(persona_id));
*out_content_size = (mach_voucher_attr_content_size_t)sizeof(persona_id);
return KERN_SUCCESS;
}
/* In the case of no value, return error KERN_INVALID_VALUE */
*out_content_size = 0;
return KERN_INVALID_VALUE;
default:
return KERN_INVALID_ARGUMENT;
}
return KERN_SUCCESS;
}
void
bank_release(
ipc_voucher_attr_manager_t __assert_only manager)
{
assert(manager == &bank_manager);
}
/*
* Bank Internal Routines.
*/
/*
* Routine: bank_task_alloc_init
* Purpose: Allocate and initialize a bank task structure.
* Returns: bank_task_t on Success.
* BANK_TASK_NULL: on Failure.
* Notes: Leaves the task and ledger blank and has only 1 ref,
* needs to take 1 extra ref after the task field is initialized.
*/
static bank_task_t
bank_task_alloc_init(task_t task)
{
bank_task_t new_bank_task;
new_bank_task = (bank_task_t) zalloc(bank_task_zone);
if (new_bank_task == BANK_TASK_NULL) {
return BANK_TASK_NULL;
}
new_bank_task->bt_type = BANK_TASK;
new_bank_task->bt_voucher_ref = 0;
new_bank_task->bt_refs = 1;
new_bank_task->bt_made = 0;
new_bank_task->bt_ledger = LEDGER_NULL;
new_bank_task->bt_hasentitlement = bank_task_is_propagate_entitled(task);
queue_init(&new_bank_task->bt_accounts_to_pay);
queue_init(&new_bank_task->bt_accounts_to_charge);
lck_mtx_init(&new_bank_task->bt_acc_to_pay_lock, &bank_lock_grp, &bank_lock_attr);
lck_mtx_init(&new_bank_task->bt_acc_to_charge_lock, &bank_lock_grp, &bank_lock_attr);
/*
* Initialize the persona_id struct
*/
bzero(&new_bank_task->bt_proc_persona, sizeof(new_bank_task->bt_proc_persona));
new_bank_task->bt_flags = 0;
new_bank_task->bt_unique_pid = proc_uniqueid(task->bsd_info);
new_bank_task->bt_pid = proc_pid(task->bsd_info);
new_bank_task->bt_pidversion = proc_pidversion(task->bsd_info);
new_bank_task->bt_persona_id = proc_persona_id(task->bsd_info);
new_bank_task->bt_uid = proc_getuid(task->bsd_info);
new_bank_task->bt_gid = proc_getgid(task->bsd_info);
#if CONFIG_THREAD_GROUPS
new_bank_task->bt_thread_group = thread_group_retain(task_coalition_get_thread_group(task));
#endif
proc_getexecutableuuid(task->bsd_info, new_bank_task->bt_macho_uuid, sizeof(new_bank_task->bt_macho_uuid));
#if DEVELOPMENT || DEBUG
new_bank_task->bt_task = NULL;
lck_mtx_lock(&bank_tasks_list_lock);
queue_enter(&bank_tasks_list, new_bank_task, bank_task_t, bt_global_elt);
lck_mtx_unlock(&bank_tasks_list_lock);
#endif
return new_bank_task;
}
/*
* Routine: proc_is_propagate_entitled
* Purpose: Check if the process is allowed to propagate secure originator.
* Returns: TRUE if entitled.
* FALSE if not.
*/
static boolean_t
bank_task_is_propagate_entitled(task_t t)
{
/* Check if it has an entitlement which disallows secure originator propagation */
boolean_t entitled = FALSE;
entitled = IOTaskHasEntitlement(t, ENTITLEMENT_PERSONA_NO_PROPAGATE);
if (entitled) {
return FALSE;
}
/* If it's a platform binary, allow propagation by default */
if (disable_persona_propagate_check || (t->t_flags & TF_PLATFORM)) {
return TRUE;
}
return FALSE;
}
/*
* Routine: proc_is_persona_modify_entitled
* Purpose: Check if the process has persona modify entitlement.
* Returns: TRUE if entitled.
* FALSE if not.
*/
static boolean_t
bank_task_is_persona_modify_entitled(task_t t)
{
boolean_t entitled = FALSE;
entitled = IOTaskHasEntitlement(t, ENTITLEMENT_PERSONA_MODIFY);
return entitled;
}
/*
* Routine: bank_account_alloc_init
* Purpose: Allocate and Initialize the bank account struct.
* Returns: bank_account_t : On Success.
* BANK_ACCOUNT_NULL: On Failure.
*/
static bank_account_t
bank_account_alloc_init(
bank_task_t bank_holder,
bank_task_t bank_merchant,
bank_task_t bank_secureoriginator,
bank_task_t bank_proximateprocess,
struct thread_group *thread_group,
uint32_t persona_id)
{
bank_account_t new_bank_account;
bank_account_t bank_account;
boolean_t entry_found = FALSE;
ledger_t new_ledger = ledger_instantiate(bank_ledger_template, LEDGER_CREATE_INACTIVE_ENTRIES);
if (new_ledger == LEDGER_NULL) {
return BANK_ACCOUNT_NULL;
}
ledger_entry_setactive(new_ledger, bank_ledgers.cpu_time);
ledger_entry_setactive(new_ledger, bank_ledgers.energy);
new_bank_account = (bank_account_t) zalloc(bank_account_zone);
if (new_bank_account == BANK_ACCOUNT_NULL) {
ledger_dereference(new_ledger);
return BANK_ACCOUNT_NULL;
}
new_bank_account->ba_type = BANK_ACCOUNT;
new_bank_account->ba_voucher_ref = 0;
new_bank_account->ba_refs = 1;
new_bank_account->ba_made = 1;
new_bank_account->ba_bill = new_ledger;
new_bank_account->ba_merchant = bank_merchant;
new_bank_account->ba_holder = bank_holder;
new_bank_account->ba_secureoriginator = bank_secureoriginator;
new_bank_account->ba_proximateprocess = bank_proximateprocess;
#if CONFIG_THREAD_GROUPS
new_bank_account->ba_thread_group = thread_group;
#endif
new_bank_account->ba_so_persona_id = persona_id;
/* Iterate through accounts need to pay list to find the existing entry */
lck_mtx_lock(&bank_holder->bt_acc_to_pay_lock);
queue_iterate(&bank_holder->bt_accounts_to_pay, bank_account, bank_account_t, ba_next_acc_to_pay) {
if (bank_account->ba_merchant != bank_merchant ||
bank_account->ba_secureoriginator != bank_secureoriginator ||
bank_account->ba_proximateprocess != bank_proximateprocess ||
bank_get_bank_account_thread_group(bank_account) != thread_group ||
bank_account->ba_so_persona_id != persona_id) {
continue;
}
entry_found = TRUE;
/* Take a made ref, since this value would be returned to voucher system. */
bank_account_made_reference(bank_account);
break;
}
if (!entry_found) {
/* Create a linkage between the holder and the merchant task, Grab both the list locks before adding it to the list. */
lck_mtx_lock(&bank_merchant->bt_acc_to_charge_lock);
/* Add the account entry into Accounts need to pay account link list. */
queue_enter(&bank_holder->bt_accounts_to_pay, new_bank_account, bank_account_t, ba_next_acc_to_pay);
/* Add the account entry into Accounts need to charge account link list. */
queue_enter(&bank_merchant->bt_accounts_to_charge, new_bank_account, bank_account_t, ba_next_acc_to_charge);
lck_mtx_unlock(&bank_merchant->bt_acc_to_charge_lock);
}
lck_mtx_unlock(&bank_holder->bt_acc_to_pay_lock);
if (entry_found) {
ledger_dereference(new_ledger);
zfree(bank_account_zone, new_bank_account);
return bank_account;
}
bank_task_reference(bank_holder);
bank_task_reference(bank_merchant);
bank_task_reference(bank_secureoriginator);
bank_task_reference(bank_proximateprocess);
#if CONFIG_THREAD_GROUPS
assert(new_bank_account->ba_thread_group != NULL);
thread_group_retain(new_bank_account->ba_thread_group);
#endif
#if DEVELOPMENT || DEBUG
new_bank_account->ba_task = NULL;
lck_mtx_lock(&bank_accounts_list_lock);
queue_enter(&bank_accounts_list, new_bank_account, bank_account_t, ba_global_elt);
lck_mtx_unlock(&bank_accounts_list_lock);
#endif
return new_bank_account;
}
/*
* Routine: get_bank_task_context
* Purpose: Get the bank context of the given task
* Returns: bank_task_t on Success.
* BANK_TASK_NULL: on Failure.
* Note: Initialize bank context if NULL.
*/
static bank_task_t
get_bank_task_context
(task_t task,
boolean_t initialize)
{
bank_task_t bank_task;
if (task->bank_context || !initialize) {
assert(task->bank_context != NULL);
return task->bank_context;
}
bank_task = bank_task_alloc_init(task);
/* Grab the task lock and check if we won the race. */
task_lock(task);
if (task->bank_context) {
task_unlock(task);
if (bank_task != BANK_TASK_NULL) {
bank_task_dealloc(bank_task, 1);
}
return task->bank_context;
} else if (bank_task == BANK_TASK_NULL) {
task_unlock(task);
return BANK_TASK_NULL;
}
/* We won the race. Take a ref on the ledger and initialize bank task. */
bank_task->bt_ledger = task->ledger;
#if DEVELOPMENT || DEBUG
bank_task->bt_task = task;
#endif
ledger_reference(task->ledger);
/* Grab the global bank task lock before setting the bank context on a task */
global_bank_task_lock();
task->bank_context = bank_task;
global_bank_task_unlock();
task_unlock(task);
return bank_task;
}
/*
* Routine: bank_task_dealloc
* Purpose: Drops the reference on bank task.
* Returns: None.
*/
static void
bank_task_dealloc(
bank_task_t bank_task,
mach_voucher_attr_value_reference_t sync)
{
assert(bank_task->bt_refs >= 0);
if (bank_task_release_num(bank_task, sync) > (int)sync) {
return;
}
assert(bank_task->bt_refs == 0);
assert(queue_empty(&bank_task->bt_accounts_to_pay));
assert(queue_empty(&bank_task->bt_accounts_to_charge));
assert(!LEDGER_VALID(bank_task->bt_ledger));
lck_mtx_destroy(&bank_task->bt_acc_to_pay_lock, &bank_lock_grp);
lck_mtx_destroy(&bank_task->bt_acc_to_charge_lock, &bank_lock_grp);
#if CONFIG_THREAD_GROUPS
thread_group_release(bank_task->bt_thread_group);
#endif
#if DEVELOPMENT || DEBUG
lck_mtx_lock(&bank_tasks_list_lock);
queue_remove(&bank_tasks_list, bank_task, bank_task_t, bt_global_elt);
lck_mtx_unlock(&bank_tasks_list_lock);
#endif
zfree(bank_task_zone, bank_task);
}
/*
* Routine: bank_account_dealloc_with_sync
* Purpose: Drop the reference on bank account if the sync matches.
* Returns: KERN_SUCCESS if sync matches.
* KERN_FAILURE on mismatch.
*/
static kern_return_t
bank_account_dealloc_with_sync(
bank_account_t bank_account,
mach_voucher_attr_value_reference_t sync)
{
bank_task_t bank_holder = bank_account->ba_holder;
bank_task_t bank_merchant = bank_account->ba_merchant;
bank_task_t bank_secureoriginator = bank_account->ba_secureoriginator;
bank_task_t bank_proximateprocess = bank_account->ba_proximateprocess;
ledger_t bank_merchant_ledger = LEDGER_NULL;
/*
* Grab a reference on the bank_merchant_ledger, since we would not be able
* to take bt_acc_to_pay_lock for bank_merchant later.
*/
bank_merchant_ledger = bank_get_bank_task_ledger_with_ref(bank_merchant);
/* Grab the acc to pay list lock and check the sync value */
lck_mtx_lock(&bank_holder->bt_acc_to_pay_lock);
if (bank_account->ba_made != sync) {
lck_mtx_unlock(&bank_holder->bt_acc_to_pay_lock);
if (bank_merchant_ledger) {
ledger_dereference(bank_merchant_ledger);
}
return KERN_FAILURE;
}
bank_account_made_release_num(bank_account, sync);
if (bank_account_release_num(bank_account, 1) > 1) {
panic("Releasing a non zero ref bank account %p\n", bank_account);
}
/* Grab both the acc to pay and acc to charge locks */
lck_mtx_lock(&bank_merchant->bt_acc_to_charge_lock);
/* No need to take ledger reference for bank_holder ledger since bt_acc_to_pay_lock is locked */
bank_rollup_chit_to_tasks(bank_account->ba_bill, bank_holder->bt_ledger, bank_merchant_ledger,
bank_holder->bt_pid, bank_merchant->bt_pid);
/* Remove the account entry from Accounts need to pay account link list. */
queue_remove(&bank_holder->bt_accounts_to_pay, bank_account, bank_account_t, ba_next_acc_to_pay);
/* Remove the account entry from Accounts need to charge account link list. */
queue_remove(&bank_merchant->bt_accounts_to_charge, bank_account, bank_account_t, ba_next_acc_to_charge);
lck_mtx_unlock(&bank_merchant->bt_acc_to_charge_lock);
lck_mtx_unlock(&bank_holder->bt_acc_to_pay_lock);
if (bank_merchant_ledger) {
ledger_dereference(bank_merchant_ledger);
}
ledger_dereference(bank_account->ba_bill);
/* Drop the reference of bank holder and merchant */
bank_task_dealloc(bank_holder, 1);
bank_task_dealloc(bank_merchant, 1);
bank_task_dealloc(bank_secureoriginator, 1);
bank_task_dealloc(bank_proximateprocess, 1);
#if CONFIG_THREAD_GROUPS
assert(bank_account->ba_thread_group != NULL);
thread_group_release(bank_account->ba_thread_group);
#endif
#if DEVELOPMENT || DEBUG
lck_mtx_lock(&bank_accounts_list_lock);
queue_remove(&bank_accounts_list, bank_account, bank_account_t, ba_global_elt);
lck_mtx_unlock(&bank_accounts_list_lock);
#endif
zfree(bank_account_zone, bank_account);
return KERN_SUCCESS;
}
/*
* Routine: bank_rollup_chit_to_tasks
* Purpose: Debit and Credit holder's and merchant's ledgers.
* Returns: None.
*/
static void
bank_rollup_chit_to_tasks(
ledger_t bill,
ledger_t bank_holder_ledger,
ledger_t bank_merchant_ledger,
int bank_holder_pid,
int bank_merchant_pid)
{
ledger_amount_t credit;
ledger_amount_t debit;
kern_return_t ret;
if (bank_holder_ledger == bank_merchant_ledger) {
return;
}
ret = ledger_get_entries(bill, bank_ledgers.cpu_time, &credit, &debit);
if (ret == KERN_SUCCESS) {
KERNEL_DEBUG_CONSTANT_IST(KDEBUG_TRACE,
(BANK_CODE(BANK_ACCOUNT_INFO, (BANK_SETTLE_CPU_TIME))) | DBG_FUNC_NONE,
bank_merchant_pid, bank_holder_pid, credit, debit, 0);
if (bank_holder_ledger) {
ledger_credit(bank_holder_ledger, task_ledgers.cpu_time_billed_to_me, credit);
ledger_debit(bank_holder_ledger, task_ledgers.cpu_time_billed_to_me, debit);
}
if (bank_merchant_ledger) {
ledger_credit(bank_merchant_ledger, task_ledgers.cpu_time_billed_to_others, credit);
ledger_debit(bank_merchant_ledger, task_ledgers.cpu_time_billed_to_others, debit);
}
}
ret = ledger_get_entries(bill, bank_ledgers.energy, &credit, &debit);
if (ret == KERN_SUCCESS) {
KERNEL_DEBUG_CONSTANT_IST(KDEBUG_TRACE,
(BANK_CODE(BANK_ACCOUNT_INFO, (BANK_SETTLE_ENERGY))) | DBG_FUNC_NONE,
bank_merchant_pid, bank_holder_pid, credit, debit, 0);
if (bank_holder_ledger) {
ledger_credit(bank_holder_ledger, task_ledgers.energy_billed_to_me, credit);
ledger_debit(bank_holder_ledger, task_ledgers.energy_billed_to_me, debit);
}
if (bank_merchant_ledger) {
ledger_credit(bank_merchant_ledger, task_ledgers.energy_billed_to_others, credit);
ledger_debit(bank_merchant_ledger, task_ledgers.energy_billed_to_others, debit);
}
}
}
/*
* Routine: bank_task_destroy
* Purpose: Drops reference on bank task.
* Returns: None.
*/
void
bank_task_destroy(task_t task)
{
bank_task_t bank_task;
/* Grab the global bank task lock before dropping the ref on task bank context */
global_bank_task_lock();
bank_task = task->bank_context;
task->bank_context = NULL;
global_bank_task_unlock();
bank_destroy_bank_task_ledger(bank_task);
bank_task_dealloc(bank_task, 1);
}
/*
* Routine: bank_task_initialize
* Purpose: Initialize the bank context of a task.
* Returns: None.
*/
void
bank_task_initialize(task_t task)
{
get_bank_task_context(task, TRUE);
}
/*
* Routine: init_bank_ledgers
* Purpose: Initialize template for bank ledgers.
* Returns: None.
*/
static void
init_bank_ledgers(void)
{
ledger_template_t t;
int idx;
assert(bank_ledger_template == NULL);
if ((t = ledger_template_create("Bank ledger")) == NULL) {
panic("couldn't create bank ledger template");
}
if ((idx = ledger_entry_add(t, "cpu_time", "sched", "ns")) < 0) {
panic("couldn't create cpu_time entry for bank ledger template");
}
bank_ledgers.cpu_time = idx;
if ((idx = ledger_entry_add(t, "energy", "power", "nj")) < 0) {
panic("couldn't create energy entry for bank ledger template");
}
bank_ledgers.energy = idx;
ledger_template_complete(t);
bank_ledger_template = t;
}
/* Routine: bank_billed_balance_safe
* Purpose: Walk through all the bank accounts billed to me by other tasks and get the current billing balance.
* Called from another task. It takes global bank task lock to make sure the bank context is
* not deallocated while accesing it.
* Returns: cpu balance and energy balance in out paremeters.
*/
void
bank_billed_balance_safe(task_t task, uint64_t *cpu_time, uint64_t *energy)
{
bank_task_t bank_task = BANK_TASK_NULL;
ledger_amount_t credit, debit;
uint64_t cpu_balance = 0;
uint64_t energy_balance = 0;
kern_return_t kr;
/* Task might be in exec, grab the global bank task lock before accessing bank context. */
global_bank_task_lock();
/* Grab a reference on bank context */
if (task->bank_context != NULL) {
bank_task = task->bank_context;
bank_task_reference(bank_task);
}
global_bank_task_unlock();
if (bank_task) {
bank_billed_balance(bank_task, &cpu_balance, &energy_balance);
bank_task_dealloc(bank_task, 1);
} else {
kr = ledger_get_entries(task->ledger, task_ledgers.cpu_time_billed_to_me,
&credit, &debit);
if (kr == KERN_SUCCESS) {
cpu_balance = credit - debit;
}
kr = ledger_get_entries(task->ledger, task_ledgers.energy_billed_to_me,
&credit, &debit);
if (kr == KERN_SUCCESS) {
energy_balance = credit - debit;
}
}
*cpu_time = cpu_balance;
*energy = energy_balance;
return;
}
/*
* Routine: bank_billed_time
* Purpose: Walk through the Accounts need to pay account list and get the current billing balance.
* Returns: cpu balance and energy balance in out paremeters.
*/
void
bank_billed_balance(bank_task_t bank_task, uint64_t *cpu_time, uint64_t *energy)
{
int64_t cpu_balance = 0;
int64_t energy_balance = 0;
bank_account_t bank_account;
int64_t temp = 0;
kern_return_t kr;
if (bank_task == BANK_TASK_NULL) {
*cpu_time = 0;
*energy = 0;
return;
}
lck_mtx_lock(&bank_task->bt_acc_to_pay_lock);
/* bt_acc_to_pay_lock locked, no need to take ledger reference for bt_ledger */
if (bank_task->bt_ledger != LEDGER_NULL) {
kr = ledger_get_balance(bank_task->bt_ledger, task_ledgers.cpu_time_billed_to_me, &temp);
if (kr == KERN_SUCCESS && temp >= 0) {
cpu_balance += temp;
}
#if DEVELOPMENT || DEBUG
else {
printf("bank_bill_time: ledger_get_balance failed or negative balance in ledger: %lld\n", temp);
}
#endif /* DEVELOPMENT || DEBUG */
kr = ledger_get_balance(bank_task->bt_ledger, task_ledgers.energy_billed_to_me, &temp);
if (kr == KERN_SUCCESS && temp >= 0) {
energy_balance += temp;
}
}
queue_iterate(&bank_task->bt_accounts_to_pay, bank_account, bank_account_t, ba_next_acc_to_pay) {
temp = 0;
kr = ledger_get_balance(bank_account->ba_bill, bank_ledgers.cpu_time, &temp);
if (kr == KERN_SUCCESS && temp >= 0) {
cpu_balance += temp;
}
#if DEVELOPMENT || DEBUG
else {
printf("bank_bill_time: ledger_get_balance failed or negative balance in ledger: %lld\n", temp);
}
#endif /* DEVELOPMENT || DEBUG */
kr = ledger_get_balance(bank_account->ba_bill, bank_ledgers.energy, &temp);
if (kr == KERN_SUCCESS && temp >= 0) {
energy_balance += temp;
}
}
lck_mtx_unlock(&bank_task->bt_acc_to_pay_lock);
*cpu_time = (uint64_t)cpu_balance;
*energy = (uint64_t)energy_balance;
return;
}
/* Routine: bank_serviced_balance_safe
* Purpose: Walk through the bank accounts billed to other tasks by me and get the current balance to be charged.
* Called from another task. It takes global bank task lock to make sure the bank context is
* not deallocated while accesing it.
* Returns: cpu balance and energy balance in out paremeters.
*/
void
bank_serviced_balance_safe(task_t task, uint64_t *cpu_time, uint64_t *energy)
{
bank_task_t bank_task = BANK_TASK_NULL;
ledger_amount_t credit, debit;
uint64_t cpu_balance = 0;
uint64_t energy_balance = 0;
kern_return_t kr;
/* Task might be in exec, grab the global bank task lock before accessing bank context. */
global_bank_task_lock();
/* Grab a reference on bank context */
if (task->bank_context != NULL) {
bank_task = task->bank_context;
bank_task_reference(bank_task);
}
global_bank_task_unlock();
if (bank_task) {
bank_serviced_balance(bank_task, &cpu_balance, &energy_balance);
bank_task_dealloc(bank_task, 1);
} else {
kr = ledger_get_entries(task->ledger, task_ledgers.cpu_time_billed_to_others,
&credit, &debit);
if (kr == KERN_SUCCESS) {
cpu_balance = credit - debit;
}
kr = ledger_get_entries(task->ledger, task_ledgers.energy_billed_to_others,
&credit, &debit);
if (kr == KERN_SUCCESS) {
energy_balance = credit - debit;
}
}
*cpu_time = cpu_balance;
*energy = energy_balance;
return;
}
/*
* Routine: bank_serviced_balance
* Purpose: Walk through the Account need to charge account list and get the current balance to be charged.
* Returns: cpu balance and energy balance in out paremeters.
*/
void
bank_serviced_balance(bank_task_t bank_task, uint64_t *cpu_time, uint64_t *energy)
{
int64_t cpu_balance = 0;
int64_t energy_balance = 0;
bank_account_t bank_account;
int64_t temp = 0;
kern_return_t kr;
ledger_t ledger = LEDGER_NULL;
if (bank_task == BANK_TASK_NULL) {
*cpu_time = 0;
*energy = 0;
return;
}
/* Grab a ledger reference on bt_ledger for bank_task */
ledger = bank_get_bank_task_ledger_with_ref(bank_task);
lck_mtx_lock(&bank_task->bt_acc_to_charge_lock);
if (ledger) {
kr = ledger_get_balance(ledger, task_ledgers.cpu_time_billed_to_others, &temp);
if (kr == KERN_SUCCESS && temp >= 0) {
cpu_balance += temp;
}
#if DEVELOPMENT || DEBUG
else {
printf("bank_serviced_time: ledger_get_balance failed or negative balance in ledger: %lld\n", temp);
}
#endif /* DEVELOPMENT || DEBUG */
kr = ledger_get_balance(ledger, task_ledgers.energy_billed_to_others, &temp);
if (kr == KERN_SUCCESS && temp >= 0) {
energy_balance += temp;
}
}
queue_iterate(&bank_task->bt_accounts_to_charge, bank_account, bank_account_t, ba_next_acc_to_charge) {
temp = 0;
kr = ledger_get_balance(bank_account->ba_bill, bank_ledgers.cpu_time, &temp);
if (kr == KERN_SUCCESS && temp >= 0) {
cpu_balance += temp;
}
#if DEVELOPMENT || DEBUG
else {
printf("bank_serviced_time: ledger_get_balance failed or negative balance in ledger: %lld\n", temp);
}
#endif /* DEVELOPMENT || DEBUG */
kr = ledger_get_balance(bank_account->ba_bill, bank_ledgers.energy, &temp);
if (kr == KERN_SUCCESS && temp >= 0) {
energy_balance += temp;
}
}
lck_mtx_unlock(&bank_task->bt_acc_to_charge_lock);
if (ledger) {
ledger_dereference(ledger);
}
*cpu_time = (uint64_t)cpu_balance;
*energy = (uint64_t)energy_balance;
return;
}
/*
* Routine: bank_get_voucher_bank_account
* Purpose: Get the bank account from the voucher.
* Returns: bank_account if bank_account attribute present in voucher.
* NULL on no attribute or no bank_element
*/
static bank_account_t
bank_get_voucher_bank_account(ipc_voucher_t voucher)
{
bank_element_t bank_element = BANK_ELEMENT_NULL;
bank_account_t bank_account = BANK_ACCOUNT_NULL;
mach_voucher_attr_value_handle_t vals[MACH_VOUCHER_ATTR_VALUE_MAX_NESTED];
mach_voucher_attr_value_handle_array_size_t val_count;
kern_return_t kr;
val_count = MACH_VOUCHER_ATTR_VALUE_MAX_NESTED;
kr = mach_voucher_attr_control_get_values(bank_voucher_attr_control,
voucher,
vals,
&val_count);
if (kr != KERN_SUCCESS || val_count == 0) {
return BANK_ACCOUNT_NULL;
}
bank_element = HANDLE_TO_BANK_ELEMENT(vals[0]);
if (bank_element == BANK_DEFAULT_VALUE) {
return BANK_ACCOUNT_NULL;
}
if (bank_element == BANK_DEFAULT_TASK_VALUE) {
bank_element = CAST_TO_BANK_ELEMENT(get_bank_task_context(current_task(), FALSE));
}
if (bank_element->be_type == BANK_TASK) {
return BANK_ACCOUNT_NULL;
} else if (bank_element->be_type == BANK_ACCOUNT) {
bank_account = CAST_TO_BANK_ACCOUNT(bank_element);
return bank_account;
} else {
panic("Bogus bank type: %d passed in bank_get_voucher_bank_account\n", bank_element->be_type);
}
return BANK_ACCOUNT_NULL;
}
/*
* Routine: bank_get_bank_task_ledger_with_ref
* Purpose: Get the bank ledger from the bank task and return a reference to it.
*/
static ledger_t
bank_get_bank_task_ledger_with_ref(bank_task_t bank_task)
{
ledger_t ledger = LEDGER_NULL;
lck_mtx_lock(&bank_task->bt_acc_to_pay_lock);
ledger = bank_task->bt_ledger;
if (ledger) {
ledger_reference(ledger);
}
lck_mtx_unlock(&bank_task->bt_acc_to_pay_lock);
return ledger;
}
/*
* Routine: bank_destroy_bank_task_ledger
* Purpose: Drop the bank task reference on the task ledger.
*/
static void
bank_destroy_bank_task_ledger(bank_task_t bank_task)
{
ledger_t ledger;
/* Remove the ledger reference from the bank task */
lck_mtx_lock(&bank_task->bt_acc_to_pay_lock);
assert(LEDGER_VALID(bank_task->bt_ledger));
ledger = bank_task->bt_ledger;
bank_task->bt_ledger = LEDGER_NULL;
lck_mtx_unlock(&bank_task->bt_acc_to_pay_lock);
ledger_dereference(ledger);
}
/*
* Routine: bank_get_bank_account_ledger
* Purpose: Get the bankledger from the bank account if ba_merchant different than ba_holder
*/
static ledger_t
bank_get_bank_account_ledger(bank_account_t bank_account)
{
ledger_t bankledger = LEDGER_NULL;
if (bank_account != BANK_ACCOUNT_NULL &&
bank_account->ba_holder != bank_account->ba_merchant) {
bankledger = bank_account->ba_bill;
}
return bankledger;
}
/*
* Routine: bank_get_bank_task_thread_group
* Purpose: Get the bank task's thread group from the bank task
*/
static struct thread_group *
bank_get_bank_task_thread_group(bank_task_t bank_task __unused)
{
struct thread_group *banktg = NULL;
#if CONFIG_THREAD_GROUPS
if (bank_task != BANK_TASK_NULL) {
banktg = bank_task->bt_thread_group;
}
#endif /* CONFIG_THREAD_GROUPS */
return banktg;
}
/*
* Routine: bank_get_bank_account_thread_group
* Purpose: Get the bank account's thread group from the bank account
*/
static struct thread_group *
bank_get_bank_account_thread_group(bank_account_t bank_account __unused)
{
struct thread_group *banktg = NULL;
#if CONFIG_THREAD_GROUPS
if (bank_account != BANK_ACCOUNT_NULL) {
banktg = bank_account->ba_thread_group;
}
#endif /* CONFIG_THREAD_GROUPS */
return banktg;
}
/*
* Routine: bank_get_bank_ledger_thread_group_and_persona
* Purpose: Get the bankledger (chit), thread group and persona id from the voucher.
* Returns: bankledger, thread group if bank_account attribute present in voucher
* and persona_id
*/
kern_return_t
bank_get_bank_ledger_thread_group_and_persona(
ipc_voucher_t voucher,
ledger_t *bankledger,
struct thread_group **banktg,
uint32_t *persona_id)
{
bank_account_t bank_account;
bank_task_t bank_task;
struct thread_group *thread_group = NULL;
bank_account = bank_get_voucher_bank_account(voucher);
bank_task = get_bank_task_context(current_task(), FALSE);
if (persona_id != NULL) {
if (bank_account != BANK_ACCOUNT_NULL) {
*persona_id = bank_account->ba_so_persona_id;
} else {
*persona_id = bank_task->bt_persona_id;
}
}
/*
* Use BANK_ACCOUNT_NULL if the ba_holder is same as ba_merchant
* and bank account thread group is same as current thread group
* i.e. ba_merchant's thread group.
*
* The bank account might have ba_holder same as ba_merchant but different
* thread group if daemon sends a voucher to an App and then App sends the
* same voucher back to the daemon (IPC code will replace thread group in the
* voucher to App's thread group when it gets auto redeemed by the App).
*/
if ((bank_account != NULL) &&
(bank_account->ba_holder == bank_account->ba_merchant) &&
(bank_get_bank_account_thread_group(bank_account) ==
bank_get_bank_task_thread_group(bank_account->ba_merchant))) {
bank_account = BANK_ACCOUNT_NULL;
}
if (bankledger != NULL) {
*bankledger = bank_get_bank_account_ledger(bank_account);
}
if (banktg != NULL) {
thread_group = bank_get_bank_account_thread_group(bank_account);
/* Return NULL thread group if voucher has current task's thread group */
if (thread_group == bank_get_bank_task_thread_group(bank_task)) {
thread_group = NULL;
}
*banktg = thread_group;
}
return KERN_SUCCESS;
}
/*
* Routine: bank_swap_thread_bank_ledger
* Purpose: swap the bank ledger on the thread.
* Returns: None.
* Note: Should be only called for current thread or thread which is not started.
*/
void
bank_swap_thread_bank_ledger(thread_t thread __unused, ledger_t new_ledger __unused)
{
spl_t s;
processor_t processor;
ledger_t old_ledger = thread->t_bankledger;
int64_t ctime, effective_ledger_time_consumed = 0;
int64_t remainder = 0, consumed = 0;
int64_t effective_energy_consumed = 0;
uint64_t thread_energy;
if (old_ledger == LEDGER_NULL && new_ledger == LEDGER_NULL) {
return;
}
assert((thread == current_thread() || thread->started == 0));
s = splsched();
thread_lock(thread);
/*
* Calculation of time elapsed by the thread before voucher swap.
* Following is the timeline which shows all the variables used in the calculation below.
*
* thread ledger
* cpu_time
* |<- consumed ->|<- remainder ->|
* timeline ----------------------------------------------------------------->
* | | |
* thread_dispatch ctime quantum end
*
* |<-effective_ledger_time -> |
* deduct_bank_ledger_time
*/
ctime = mach_absolute_time();
processor = thread->last_processor;
if (processor != NULL) {
if ((int64_t)processor->quantum_end > ctime) {
remainder = (int64_t)processor->quantum_end - ctime;
}
consumed = thread->quantum_remaining - remainder;
effective_ledger_time_consumed = consumed - thread->t_deduct_bank_ledger_time;
}
thread->t_deduct_bank_ledger_time = consumed;
thread_energy = ml_energy_stat(thread);
effective_energy_consumed =
thread_energy - thread->t_deduct_bank_ledger_energy;
assert(effective_energy_consumed >= 0);
thread->t_deduct_bank_ledger_energy = thread_energy;
thread->t_bankledger = new_ledger;
thread_unlock(thread);
splx(s);
if (old_ledger != LEDGER_NULL) {
ledger_credit(old_ledger,
bank_ledgers.cpu_time,
effective_ledger_time_consumed);
ledger_credit(old_ledger,
bank_ledgers.energy,
effective_energy_consumed);
}
}
/*
* Routine: bank_verify_persona_id
* Purpose: Verifies if the persona id is valid
*
* The caller should check if the task is entitled
* to do the lookup.
*/
static boolean_t
bank_verify_persona_id(uint32_t persona_id)
{
/* A successful lookup implies that the persona id is valid */
void *persona = persona_lookup(persona_id);
if (!persona) {
return FALSE;
}
persona_put(persona);
return TRUE;
}