diff options
Diffstat (limited to 'security/tf_driver/tf_conn.c')
-rw-r--r-- | security/tf_driver/tf_conn.c | 1574 |
1 files changed, 1574 insertions, 0 deletions
diff --git a/security/tf_driver/tf_conn.c b/security/tf_driver/tf_conn.c new file mode 100644 index 000000000000..3148fec46358 --- /dev/null +++ b/security/tf_driver/tf_conn.c @@ -0,0 +1,1574 @@ +/** + * Copyright (c) 2011 Trusted Logic S.A. + * All Rights Reserved. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, + * MA 02111-1307 USA + */ + +#include <linux/atomic.h> +#include <linux/uaccess.h> +#include <linux/delay.h> +#include <linux/errno.h> +#include <linux/list.h> +#include <linux/mm.h> +#include <linux/pagemap.h> +#include <linux/stddef.h> +#include <linux/types.h> + +#include "s_version.h" + +#include "tf_protocol.h" +#include "tf_defs.h" +#include "tf_util.h" +#include "tf_comm.h" +#include "tf_conn.h" + +#ifdef CONFIG_TF_ZEBRA +#include "tf_crypto.h" +#endif + +#ifdef CONFIG_ANDROID +#define TF_PRIVILEGED_UID_GID 1000 /* Android system AID */ +#else +#define TF_PRIVILEGED_UID_GID 0 +#endif + +/*---------------------------------------------------------------------------- + * Management of the shared memory blocks. + * + * Shared memory blocks are the blocks registered through + * the commands REGISTER_SHARED_MEMORY and POWER_MANAGEMENT + *----------------------------------------------------------------------------*/ + +/** + * Unmaps a shared memory + **/ +void tf_unmap_shmem( + struct tf_connection *connection, + struct tf_shmem_desc *shmem_desc, + u32 full_cleanup) +{ + /* check shmem_desc contains a descriptor */ + if (shmem_desc == NULL) + return; + + dprintk(KERN_DEBUG "tf_unmap_shmem(%p)\n", shmem_desc); + +retry: + mutex_lock(&(connection->shmem_mutex)); + if (atomic_read(&shmem_desc->ref_count) > 1) { + /* + * Shared mem still in use, wait for other operations completion + * before actually unmapping it. + */ + dprintk(KERN_INFO "Descriptor in use\n"); + mutex_unlock(&(connection->shmem_mutex)); + schedule(); + goto retry; + } + + tf_cleanup_shared_memory( + &(connection->cpt_alloc_context), + shmem_desc, + full_cleanup); + + list_del(&(shmem_desc->list)); + + if ((shmem_desc->type == TF_SHMEM_TYPE_REGISTERED_SHMEM) || + (full_cleanup != 0)) { + internal_kfree(shmem_desc); + + atomic_dec(&(connection->shmem_count)); + } else { + /* + * This is a preallocated shared memory, add to free list + * Since the device context is unmapped last, it is + * always the first element of the free list if no + * device context has been created + */ + shmem_desc->block_identifier = 0; + list_add(&(shmem_desc->list), &(connection->free_shmem_list)); + } + + mutex_unlock(&(connection->shmem_mutex)); +} + + +/** + * Find the first available slot for a new block of shared memory + * and map the user buffer. + * Update the descriptors to L1 descriptors + * Update the buffer_start_offset and buffer_size fields + * shmem_desc is updated to the mapped shared memory descriptor + **/ +int tf_map_shmem( + struct tf_connection *connection, + u32 buffer, + /* flags for read-write access rights on the memory */ + u32 flags, + bool in_user_space, + u32 descriptors[TF_MAX_COARSE_PAGES], + u32 *buffer_start_offset, + u32 buffer_size, + struct tf_shmem_desc **shmem_desc, + u32 *descriptor_count) +{ + struct tf_shmem_desc *desc = NULL; + int error; + + dprintk(KERN_INFO "tf_map_shmem(%p, %p, flags = 0x%08x)\n", + connection, + (void *) buffer, + flags); + + mutex_lock(&(connection->shmem_mutex)); + + /* + * Check the list of free shared memory + * is not empty + */ + if (list_empty(&(connection->free_shmem_list))) { + if (atomic_read(&(connection->shmem_count)) == + TF_SHMEM_MAX_COUNT) { + printk(KERN_ERR "tf_map_shmem(%p):" + " maximum shared memories already registered\n", + connection); + error = -ENOMEM; + goto error; + } + + /* no descriptor available, allocate a new one */ + + desc = (struct tf_shmem_desc *) internal_kmalloc( + sizeof(*desc), GFP_KERNEL); + if (desc == NULL) { + printk(KERN_ERR "tf_map_shmem(%p):" + " failed to allocate descriptor\n", + connection); + error = -ENOMEM; + goto error; + } + + /* Initialize the structure */ + desc->type = TF_SHMEM_TYPE_REGISTERED_SHMEM; + atomic_set(&desc->ref_count, 1); + INIT_LIST_HEAD(&(desc->list)); + + atomic_inc(&(connection->shmem_count)); + } else { + /* take the first free shared memory descriptor */ + desc = list_first_entry(&(connection->free_shmem_list), + struct tf_shmem_desc, list); + list_del(&(desc->list)); + } + + /* Add the descriptor to the used list */ + list_add(&(desc->list), &(connection->used_shmem_list)); + + error = tf_fill_descriptor_table( + &(connection->cpt_alloc_context), + desc, + buffer, + connection->vmas, + descriptors, + buffer_size, + buffer_start_offset, + in_user_space, + flags, + descriptor_count); + + if (error != 0) { + dprintk(KERN_ERR "tf_map_shmem(%p):" + " tf_fill_descriptor_table failed with error " + "code %d!\n", + connection, + error); + goto error; + } + desc->client_buffer = (u8 *) buffer; + + /* + * Successful completion. + */ + *shmem_desc = desc; + mutex_unlock(&(connection->shmem_mutex)); + dprintk(KERN_DEBUG "tf_map_shmem: success\n"); + return 0; + + + /* + * Error handling. + */ +error: + mutex_unlock(&(connection->shmem_mutex)); + dprintk(KERN_ERR "tf_map_shmem: failure with error code %d\n", + error); + + tf_unmap_shmem( + connection, + desc, + 0); + + return error; +} + + + +/* This function is a copy of the find_vma() function +in linux kernel 2.6.15 version with some fixes : + - memory block may end on vm_end + - check the full memory block is in the memory area + - guarantee NULL is returned if no memory area is found */ +struct vm_area_struct *tf_find_vma(struct mm_struct *mm, + unsigned long addr, unsigned long size) +{ + struct vm_area_struct *vma = NULL; + + dprintk(KERN_INFO + "tf_find_vma addr=0x%lX size=0x%lX\n", addr, size); + + if (mm) { + /* Check the cache first. */ + /* (Cache hit rate is typically around 35%.) */ + vma = mm->mmap_cache; + if (!(vma && vma->vm_end >= (addr+size) && + vma->vm_start <= addr)) { + struct rb_node *rb_node; + + rb_node = mm->mm_rb.rb_node; + vma = NULL; + + while (rb_node) { + struct vm_area_struct *vma_tmp; + + vma_tmp = rb_entry(rb_node, + struct vm_area_struct, vm_rb); + + dprintk(KERN_INFO + "vma_tmp->vm_start=0x%lX" + "vma_tmp->vm_end=0x%lX\n", + vma_tmp->vm_start, + vma_tmp->vm_end); + + if (vma_tmp->vm_end >= (addr+size)) { + vma = vma_tmp; + if (vma_tmp->vm_start <= addr) + break; + + rb_node = rb_node->rb_left; + } else { + rb_node = rb_node->rb_right; + } + } + + if (vma) + mm->mmap_cache = vma; + if (rb_node == NULL) + vma = NULL; + } + } + return vma; +} + +int tf_validate_shmem_and_flags( + u32 shmem, + u32 shmem_size, + u32 flags) +{ + struct vm_area_struct *vma; + u32 chunk; + + if (shmem_size == 0) + /* This is always valid */ + return 0; + + if ((shmem + shmem_size) < shmem) + /* Overflow */ + return -EINVAL; + + down_read(¤t->mm->mmap_sem); + + /* + * When looking for a memory address, split buffer into chunks of + * size=PAGE_SIZE. + */ + chunk = PAGE_SIZE - (shmem & (PAGE_SIZE-1)); + if (chunk > shmem_size) + chunk = shmem_size; + + do { + vma = tf_find_vma(current->mm, shmem, chunk); + + if (vma == NULL) { + dprintk(KERN_ERR "%s: area not found\n", __func__); + goto error; + } + + if (flags & TF_SHMEM_TYPE_READ) + if (!(vma->vm_flags & VM_READ)) { + dprintk(KERN_ERR "%s: no read permission\n", + __func__); + goto error; + } + if (flags & TF_SHMEM_TYPE_WRITE) + if (!(vma->vm_flags & VM_WRITE)) { + dprintk(KERN_ERR "%s: no write permission\n", + __func__); + goto error; + } + + shmem_size -= chunk; + shmem += chunk; + chunk = (shmem_size <= PAGE_SIZE ? + shmem_size : PAGE_SIZE); + } while (shmem_size != 0); + + up_read(¤t->mm->mmap_sem); + return 0; + +error: + up_read(¤t->mm->mmap_sem); + return -EFAULT; +} + + +static int tf_map_temp_shmem(struct tf_connection *connection, + struct tf_command_param_temp_memref *temp_memref, + u32 param_type, + struct tf_shmem_desc **shmem_desc) +{ + u32 flags; + u32 error = S_SUCCESS; + bool in_user_space = connection->owner != TF_CONNECTION_OWNER_KERNEL; + + dprintk(KERN_INFO "tf_map_temp_shmem(%p, " + "0x%08x[size=0x%08x], offset=0x%08x)\n", + connection, + temp_memref->descriptor, + temp_memref->size, + temp_memref->offset); + + switch (param_type) { + case TF_PARAM_TYPE_MEMREF_TEMP_INPUT: + flags = TF_SHMEM_TYPE_READ; + break; + case TF_PARAM_TYPE_MEMREF_TEMP_OUTPUT: + flags = TF_SHMEM_TYPE_WRITE; + break; + case TF_PARAM_TYPE_MEMREF_TEMP_INOUT: + flags = TF_SHMEM_TYPE_WRITE | TF_SHMEM_TYPE_READ; + break; + default: + error = -EINVAL; + goto error; + } + + if (temp_memref->descriptor == 0) { + /* NULL tmpref */ + temp_memref->offset = 0; + *shmem_desc = NULL; + } else if ((temp_memref->descriptor != 0) && + (temp_memref->size == 0)) { + /* Empty tmpref */ + temp_memref->offset = temp_memref->descriptor; + temp_memref->descriptor = 0; + temp_memref->size = 0; + *shmem_desc = NULL; + } else { + /* Map the temp shmem block */ + + u32 shared_mem_descriptors[TF_MAX_COARSE_PAGES]; + u32 descriptor_count; + + if (in_user_space) { + error = tf_validate_shmem_and_flags( + temp_memref->descriptor, + temp_memref->size, + flags); + if (error != 0) + goto error; + } + + error = tf_map_shmem( + connection, + temp_memref->descriptor, + flags, + in_user_space, + shared_mem_descriptors, + &(temp_memref->offset), + temp_memref->size, + shmem_desc, + &descriptor_count); + temp_memref->descriptor = shared_mem_descriptors[0]; + } + +error: + return error; +} + +/* + * Clean up a list of shared memory descriptors. + */ +static void tf_shared_memory_cleanup_list( + struct tf_connection *connection, + struct list_head *shmem_desc_list) +{ + while (!list_empty(shmem_desc_list)) { + struct tf_shmem_desc *shmem_desc; + + shmem_desc = list_first_entry(shmem_desc_list, + struct tf_shmem_desc, list); + + tf_unmap_shmem(connection, shmem_desc, 1); + } +} + + +/* + * Clean up the shared memory information in the connection. + * Releases all allocated pages. + */ +static void tf_cleanup_shared_memories(struct tf_connection *connection) +{ + /* clean up the list of used and free descriptors. + * done outside the mutex, because tf_unmap_shmem already + * mutex()ed + */ + tf_shared_memory_cleanup_list(connection, + &connection->used_shmem_list); + tf_shared_memory_cleanup_list(connection, + &connection->free_shmem_list); + + mutex_lock(&(connection->shmem_mutex)); + + /* Free the Vmas page */ + if (connection->vmas) { + internal_free_page((unsigned long) connection->vmas); + connection->vmas = NULL; + } + + tf_release_coarse_page_table_allocator( + &(connection->cpt_alloc_context)); + + mutex_unlock(&(connection->shmem_mutex)); +} + + +/* + * Initialize the shared memory in a connection. + * Allocates the minimum memory to be provided + * for shared memory management + */ +int tf_init_shared_memory(struct tf_connection *connection) +{ + int error; + int i; + int coarse_page_index; + + /* + * We only need to initialize special elements and attempt to allocate + * the minimum shared memory descriptors we want to support + */ + + mutex_init(&(connection->shmem_mutex)); + INIT_LIST_HEAD(&(connection->free_shmem_list)); + INIT_LIST_HEAD(&(connection->used_shmem_list)); + atomic_set(&(connection->shmem_count), 0); + + tf_init_coarse_page_table_allocator( + &(connection->cpt_alloc_context)); + + + /* + * Preallocate 3 pages to increase the chances that a connection + * succeeds in allocating shared mem + */ + for (i = 0; + i < 3; + i++) { + struct tf_shmem_desc *shmem_desc = + (struct tf_shmem_desc *) internal_kmalloc( + sizeof(*shmem_desc), GFP_KERNEL); + + if (shmem_desc == NULL) { + printk(KERN_ERR "tf_init_shared_memory(%p):" + " failed to pre allocate descriptor %d\n", + connection, + i); + error = -ENOMEM; + goto error; + } + + for (coarse_page_index = 0; + coarse_page_index < TF_MAX_COARSE_PAGES; + coarse_page_index++) { + struct tf_coarse_page_table *coarse_pg_table; + + coarse_pg_table = tf_alloc_coarse_page_table( + &(connection->cpt_alloc_context), + TF_PAGE_DESCRIPTOR_TYPE_PREALLOCATED); + + if (coarse_pg_table == NULL) { + printk(KERN_ERR "tf_init_shared_memory(%p)" + ": descriptor %d coarse page %d - " + "tf_alloc_coarse_page_table() " + "failed\n", + connection, + i, + coarse_page_index); + error = -ENOMEM; + goto error; + } + + shmem_desc->coarse_pg_table[coarse_page_index] = + coarse_pg_table; + } + shmem_desc->coarse_pg_table_count = 0; + + shmem_desc->type = TF_SHMEM_TYPE_PREALLOC_REGISTERED_SHMEM; + atomic_set(&shmem_desc->ref_count, 1); + + /* + * add this preallocated descriptor to the list of free + * descriptors Keep the device context specific one at the + * beginning of the list + */ + INIT_LIST_HEAD(&(shmem_desc->list)); + list_add_tail(&(shmem_desc->list), + &(connection->free_shmem_list)); + } + + /* allocate memory for the vmas structure */ + connection->vmas = + (struct vm_area_struct **) internal_get_zeroed_page(GFP_KERNEL); + if (connection->vmas == NULL) { + printk(KERN_ERR "tf_init_shared_memory(%p):" + " vmas - failed to get_zeroed_page\n", + connection); + error = -ENOMEM; + goto error; + } + + return 0; + +error: + tf_cleanup_shared_memories(connection); + return error; +} + +/*---------------------------------------------------------------------------- + * Connection operations to the Secure World + *----------------------------------------------------------------------------*/ + +int tf_create_device_context( + struct tf_connection *connection) +{ + union tf_command command; + union tf_answer answer; + int error = 0; + + dprintk(KERN_INFO "tf_create_device_context(%p)\n", + connection); + + command.create_device_context.message_type = + TF_MESSAGE_TYPE_CREATE_DEVICE_CONTEXT; + command.create_device_context.message_size = + (sizeof(struct tf_command_create_device_context) + - sizeof(struct tf_command_header))/sizeof(u32); + command.create_device_context.operation_id = (u32) &answer; + command.create_device_context.device_context_id = (u32) connection; + + error = tf_send_receive( + &connection->dev->sm, + &command, + &answer, + connection, + true); + + if ((error != 0) || + (answer.create_device_context.error_code != S_SUCCESS)) + goto error; + + /* + * CREATE_DEVICE_CONTEXT succeeded, + * store device context handler and update connection status + */ + connection->device_context = + answer.create_device_context.device_context; + spin_lock(&(connection->state_lock)); + connection->state = TF_CONN_STATE_VALID_DEVICE_CONTEXT; + spin_unlock(&(connection->state_lock)); + + /* successful completion */ + dprintk(KERN_INFO "tf_create_device_context(%p):" + " device_context=0x%08x\n", + connection, + answer.create_device_context.device_context); + return 0; + +error: + if (error != 0) { + dprintk(KERN_ERR "tf_create_device_context failed with " + "error %d\n", error); + } else { + /* + * We sent a DeviceCreateContext. The state is now + * TF_CONN_STATE_CREATE_DEVICE_CONTEXT_SENT It has to be + * reset if we ever want to send a DeviceCreateContext again + */ + spin_lock(&(connection->state_lock)); + connection->state = TF_CONN_STATE_NO_DEVICE_CONTEXT; + spin_unlock(&(connection->state_lock)); + dprintk(KERN_ERR "tf_create_device_context failed with " + "error_code 0x%08X\n", + answer.create_device_context.error_code); + if (answer.create_device_context.error_code == + S_ERROR_OUT_OF_MEMORY) + error = -ENOMEM; + else + error = -EFAULT; + } + + return error; +} + +/* Check that the current application belongs to the + * requested GID */ +static bool tf_check_gid(gid_t requested_gid) +{ + if (requested_gid == current_egid()) { + return true; + } else { + u32 size; + u32 i; + /* Look in the supplementary GIDs */ + get_group_info(GROUP_INFO); + size = GROUP_INFO->ngroups; + for (i = 0; i < size; i++) + if (requested_gid == GROUP_AT(GROUP_INFO , i)) + return true; + } + return false; +} + +/* + * Opens a client session to the Secure World + */ +int tf_open_client_session( + struct tf_connection *connection, + union tf_command *command, + union tf_answer *answer) +{ + int error = 0; + struct tf_shmem_desc *shmem_desc[4] = {NULL}; + u32 i; + + dprintk(KERN_INFO "tf_open_client_session(%p)\n", connection); + + /* + * Initialize the message size with no login data. This will be later + * adjusted the the cases below + */ + command->open_client_session.message_size = + (sizeof(struct tf_command_open_client_session) - 20 + - sizeof(struct tf_command_header))/4; + + switch (command->open_client_session.login_type) { + case TF_LOGIN_PUBLIC: + /* Nothing to do */ + break; + + case TF_LOGIN_USER: + /* + * Send the EUID of the calling application in the login data. + * Update message size. + */ + *(u32 *) &command->open_client_session.login_data = + current_euid(); +#ifndef CONFIG_ANDROID + command->open_client_session.login_type = + (u32) TF_LOGIN_USER_LINUX_EUID; +#else + command->open_client_session.login_type = + (u32) TF_LOGIN_USER_ANDROID_EUID; +#endif + + /* Added one word */ + command->open_client_session.message_size += 1; + break; + + case TF_LOGIN_GROUP: { + /* Check requested GID */ + gid_t requested_gid = + *(u32 *) command->open_client_session.login_data; + + if (!tf_check_gid(requested_gid)) { + dprintk(KERN_ERR "tf_open_client_session(%p) " + "TF_LOGIN_GROUP: requested GID (0x%x) does " + "not match real eGID (0x%x)" + "or any of the supplementary GIDs\n", + connection, requested_gid, current_egid()); + error = -EACCES; + goto error; + } +#ifndef CONFIG_ANDROID + command->open_client_session.login_type = + TF_LOGIN_GROUP_LINUX_GID; +#else + command->open_client_session.login_type = + TF_LOGIN_GROUP_ANDROID_GID; +#endif + + command->open_client_session.message_size += 1; /* GID */ + break; + } + +#ifndef CONFIG_ANDROID + case TF_LOGIN_APPLICATION: { + /* + * Compute SHA-1 hash of the application fully-qualified path + * name. Truncate the hash to 16 bytes and send it as login + * data. Update message size. + */ + u8 pSHA1Hash[SHA1_DIGEST_SIZE]; + + error = tf_hash_application_path_and_data(pSHA1Hash, + NULL, 0); + if (error != 0) { + dprintk(KERN_ERR "tf_open_client_session: " + "error in tf_hash_application_path_and_data\n"); + goto error; + } + memcpy(&command->open_client_session.login_data, + pSHA1Hash, 16); + command->open_client_session.login_type = + TF_LOGIN_APPLICATION_LINUX_PATH_SHA1_HASH; + /* 16 bytes */ + command->open_client_session.message_size += 4; + break; + } +#else + case TF_LOGIN_APPLICATION: + /* + * Send the real UID of the calling application in the login + * data. Update message size. + */ + *(u32 *) &command->open_client_session.login_data = + current_uid(); + + command->open_client_session.login_type = + (u32) TF_LOGIN_APPLICATION_ANDROID_UID; + + /* Added one word */ + command->open_client_session.message_size += 1; + break; +#endif + +#ifndef CONFIG_ANDROID + case TF_LOGIN_APPLICATION_USER: { + /* + * Compute SHA-1 hash of the concatenation of the application + * fully-qualified path name and the EUID of the calling + * application. Truncate the hash to 16 bytes and send it as + * login data. Update message size. + */ + u8 pSHA1Hash[SHA1_DIGEST_SIZE]; + + error = tf_hash_application_path_and_data(pSHA1Hash, + (u8 *) &(current_euid()), sizeof(current_euid())); + if (error != 0) { + dprintk(KERN_ERR "tf_open_client_session: " + "error in tf_hash_application_path_and_data\n"); + goto error; + } + memcpy(&command->open_client_session.login_data, + pSHA1Hash, 16); + command->open_client_session.login_type = + TF_LOGIN_APPLICATION_USER_LINUX_PATH_EUID_SHA1_HASH; + + /* 16 bytes */ + command->open_client_session.message_size += 4; + + break; + } +#else + case TF_LOGIN_APPLICATION_USER: + /* + * Send the real UID and the EUID of the calling application in + * the login data. Update message size. + */ + *(u32 *) &command->open_client_session.login_data = + current_uid(); + *(u32 *) &command->open_client_session.login_data[4] = + current_euid(); + + command->open_client_session.login_type = + TF_LOGIN_APPLICATION_USER_ANDROID_UID_EUID; + + /* Added two words */ + command->open_client_session.message_size += 2; + break; +#endif + +#ifndef CONFIG_ANDROID + case TF_LOGIN_APPLICATION_GROUP: { + /* + * Check requested GID. Compute SHA-1 hash of the concatenation + * of the application fully-qualified path name and the + * requested GID. Update message size + */ + gid_t requested_gid; + u8 pSHA1Hash[SHA1_DIGEST_SIZE]; + + requested_gid = *(u32 *) &command->open_client_session. + login_data; + + if (!tf_check_gid(requested_gid)) { + dprintk(KERN_ERR "tf_open_client_session(%p) " + "TF_LOGIN_APPLICATION_GROUP: requested GID (0x%x) " + "does not match real eGID (0x%x)" + "or any of the supplementary GIDs\n", + connection, requested_gid, current_egid()); + error = -EACCES; + goto error; + } + + error = tf_hash_application_path_and_data(pSHA1Hash, + &requested_gid, sizeof(u32)); + if (error != 0) { + dprintk(KERN_ERR "tf_open_client_session: " + "error in tf_hash_application_path_and_data\n"); + goto error; + } + + memcpy(&command->open_client_session.login_data, + pSHA1Hash, 16); + command->open_client_session.login_type = + TF_LOGIN_APPLICATION_GROUP_LINUX_PATH_GID_SHA1_HASH; + + /* 16 bytes */ + command->open_client_session.message_size += 4; + break; + } +#else + case TF_LOGIN_APPLICATION_GROUP: { + /* + * Check requested GID. Send the real UID and the requested GID + * in the login data. Update message size. + */ + gid_t requested_gid; + + requested_gid = *(u32 *) &command->open_client_session. + login_data; + + if (!tf_check_gid(requested_gid)) { + dprintk(KERN_ERR "tf_open_client_session(%p) " + "TF_LOGIN_APPLICATION_GROUP: requested GID (0x%x) " + "does not match real eGID (0x%x)" + "or any of the supplementary GIDs\n", + connection, requested_gid, current_egid()); + error = -EACCES; + goto error; + } + + *(u32 *) &command->open_client_session.login_data = + current_uid(); + *(u32 *) &command->open_client_session.login_data[4] = + requested_gid; + + command->open_client_session.login_type = + TF_LOGIN_APPLICATION_GROUP_ANDROID_UID_GID; + + /* Added two words */ + command->open_client_session.message_size += 2; + + break; + } +#endif + + case TF_LOGIN_PRIVILEGED: + /* A privileged login may be performed only on behalf of the + kernel itself or on behalf of a process with euid=0 or + egid=0 or euid=system or egid=system. */ + if (connection->owner == TF_CONNECTION_OWNER_KERNEL) { + dprintk(KERN_DEBUG "tf_open_client_session: " + "TF_LOGIN_PRIVILEGED for kernel API\n"); + } else if ((current_euid() != TF_PRIVILEGED_UID_GID) && + (current_egid() != TF_PRIVILEGED_UID_GID) && + (current_euid() != 0) && (current_egid() != 0)) { + dprintk(KERN_ERR "tf_open_client_session: " + " user %d, group %d not allowed to open " + "session with TF_LOGIN_PRIVILEGED\n", + current_euid(), current_egid()); + error = -EACCES; + goto error; + } else { + dprintk(KERN_DEBUG "tf_open_client_session: " + "TF_LOGIN_PRIVILEGED for %u:%u\n", + current_euid(), current_egid()); + } + command->open_client_session.login_type = + TF_LOGIN_PRIVILEGED; + break; + + case TF_LOGIN_AUTHENTICATION: { + /* + * Compute SHA-1 hash of the application binary + * Send this hash as the login data (20 bytes) + */ + + u8 *hash; + hash = &(command->open_client_session.login_data[0]); + + error = tf_get_current_process_hash(hash); + if (error != 0) { + dprintk(KERN_ERR "tf_open_client_session: " + "error in tf_get_current_process_hash\n"); + goto error; + } + command->open_client_session.login_type = + TF_LOGIN_AUTHENTICATION_BINARY_SHA1_HASH; + + /* 20 bytes */ + command->open_client_session.message_size += 5; + break; + } + + case TF_LOGIN_PRIVILEGED_KERNEL: + /* A kernel login may be performed only on behalf of the + kernel itself. */ + if (connection->owner == TF_CONNECTION_OWNER_KERNEL) { + dprintk(KERN_DEBUG "tf_open_client_session: " + "TF_LOGIN_PRIVILEGED_KERNEL for kernel API\n"); + command->open_client_session.login_type = + TF_LOGIN_PRIVILEGED_KERNEL; + } else { + dprintk(KERN_ERR "tf_open_client_session: " + " user %d, group %d not allowed to open " + "session with TF_LOGIN_PRIVILEGED_KERNEL\n", + current_euid(), current_egid()); + error = -EACCES; + goto error; + } + command->open_client_session.login_type = + TF_LOGIN_PRIVILEGED_KERNEL; + break; + + default: + dprintk(KERN_ERR "tf_open_client_session: " + "unknown login_type(%08X)\n", + command->open_client_session.login_type); + error = -EOPNOTSUPP; + goto error; + } + + /* Map the temporary memory references */ + for (i = 0; i < 4; i++) { + int param_type; + param_type = TF_GET_PARAM_TYPE( + command->open_client_session.param_types, i); + if ((param_type & (TF_PARAM_TYPE_MEMREF_FLAG | + TF_PARAM_TYPE_REGISTERED_MEMREF_FLAG)) + == TF_PARAM_TYPE_MEMREF_FLAG) { + /* Map temp mem ref */ + error = tf_map_temp_shmem(connection, + &command->open_client_session. + params[i].temp_memref, + param_type, + &shmem_desc[i]); + if (error != 0) { + dprintk(KERN_ERR "tf_open_client_session: " + "unable to map temporary memory block " + "(%08X)\n", error); + goto error; + } + } + } + + /* Fill the handle of the Device Context */ + command->open_client_session.device_context = + connection->device_context; + + error = tf_send_receive( + &connection->dev->sm, + command, + answer, + connection, + true); + +error: + /* Unmap the temporary memory references */ + for (i = 0; i < 4; i++) + if (shmem_desc[i] != NULL) + tf_unmap_shmem(connection, shmem_desc[i], 0); + + if (error != 0) + dprintk(KERN_ERR "tf_open_client_session returns %d\n", + error); + else + dprintk(KERN_ERR "tf_open_client_session returns " + "error_code 0x%08X\n", + answer->open_client_session.error_code); + + return error; +} + + +/* + * Closes a client session from the Secure World + */ +int tf_close_client_session( + struct tf_connection *connection, + union tf_command *command, + union tf_answer *answer) +{ + int error = 0; + + dprintk(KERN_DEBUG "tf_close_client_session(%p)\n", connection); + + command->close_client_session.message_size = + (sizeof(struct tf_command_close_client_session) - + sizeof(struct tf_command_header)) / 4; + command->close_client_session.device_context = + connection->device_context; + + error = tf_send_receive( + &connection->dev->sm, + command, + answer, + connection, + true); + + if (error != 0) + dprintk(KERN_ERR "tf_close_client_session returns %d\n", + error); + else + dprintk(KERN_ERR "tf_close_client_session returns " + "error 0x%08X\n", + answer->close_client_session.error_code); + + return error; +} + + +/* + * Registers a shared memory to the Secure World + */ +int tf_register_shared_memory( + struct tf_connection *connection, + union tf_command *command, + union tf_answer *answer) +{ + int error = 0; + struct tf_shmem_desc *shmem_desc = NULL; + bool in_user_space = connection->owner != TF_CONNECTION_OWNER_KERNEL; + struct tf_command_register_shared_memory *msg = + &command->register_shared_memory; + + dprintk(KERN_INFO "tf_register_shared_memory(%p) " + "%p[0x%08X][0x%08x]\n", + connection, + (void *)msg->shared_mem_descriptors[0], + msg->shared_mem_size, + (u32)msg->memory_flags); + + if (in_user_space) { + error = tf_validate_shmem_and_flags( + msg->shared_mem_descriptors[0], + msg->shared_mem_size, + (u32)msg->memory_flags); + if (error != 0) + goto error; + } + + /* Initialize message_size with no descriptors */ + msg->message_size + = (offsetof(struct tf_command_register_shared_memory, + shared_mem_descriptors) - + sizeof(struct tf_command_header)) / 4; + + /* Map the shmem block and update the message */ + if (msg->shared_mem_size == 0) { + /* Empty shared mem */ + msg->shared_mem_start_offset = msg->shared_mem_descriptors[0]; + } else { + u32 descriptor_count; + error = tf_map_shmem( + connection, + msg->shared_mem_descriptors[0], + msg->memory_flags, + in_user_space, + msg->shared_mem_descriptors, + &(msg->shared_mem_start_offset), + msg->shared_mem_size, + &shmem_desc, + &descriptor_count); + if (error != 0) { + dprintk(KERN_ERR "tf_register_shared_memory: " + "unable to map shared memory block\n"); + goto error; + } + msg->message_size += descriptor_count; + } + + /* + * write the correct device context handle and the address of the shared + * memory descriptor in the message + */ + msg->device_context = connection->device_context; + msg->block_id = (u32)shmem_desc; + + /* Send the updated message */ + error = tf_send_receive( + &connection->dev->sm, + command, + answer, + connection, + true); + + if ((error != 0) || + (answer->register_shared_memory.error_code + != S_SUCCESS)) { + dprintk(KERN_ERR "tf_register_shared_memory: " + "operation failed. Unmap block\n"); + goto error; + } + + /* Saves the block handle returned by the secure world */ + if (shmem_desc != NULL) + shmem_desc->block_identifier = + answer->register_shared_memory.block; + + /* successful completion */ + dprintk(KERN_INFO "tf_register_shared_memory(%p):" + " block_id=0x%08x block=0x%08x\n", + connection, msg->block_id, + answer->register_shared_memory.block); + return 0; + + /* error completion */ +error: + tf_unmap_shmem( + connection, + shmem_desc, + 0); + + if (error != 0) + dprintk(KERN_ERR "tf_register_shared_memory returns %d\n", + error); + else + dprintk(KERN_ERR "tf_register_shared_memory returns " + "error_code 0x%08X\n", + answer->register_shared_memory.error_code); + + return error; +} + + +/* + * Releases a shared memory from the Secure World + */ +int tf_release_shared_memory( + struct tf_connection *connection, + union tf_command *command, + union tf_answer *answer) +{ + int error = 0; + + dprintk(KERN_DEBUG "tf_release_shared_memory(%p)\n", connection); + + command->release_shared_memory.message_size = + (sizeof(struct tf_command_release_shared_memory) - + sizeof(struct tf_command_header)) / 4; + command->release_shared_memory.device_context = + connection->device_context; + + error = tf_send_receive( + &connection->dev->sm, + command, + answer, + connection, + true); + + if ((error != 0) || + (answer->release_shared_memory.error_code != S_SUCCESS)) + goto error; + + /* Use block_id to get back the pointer to shmem_desc */ + tf_unmap_shmem( + connection, + (struct tf_shmem_desc *) + answer->release_shared_memory.block_id, + 0); + + /* successful completion */ + dprintk(KERN_INFO "tf_release_shared_memory(%p):" + " block_id=0x%08x block=0x%08x\n", + connection, answer->release_shared_memory.block_id, + command->release_shared_memory.block); + return 0; + + +error: + if (error != 0) + dprintk(KERN_ERR "tf_release_shared_memory returns %d\n", + error); + else + dprintk(KERN_ERR "tf_release_shared_memory returns " + "nChannelStatus 0x%08X\n", + answer->release_shared_memory.error_code); + + return error; + +} + + +/* + * Invokes a client command to the Secure World + */ +int tf_invoke_client_command( + struct tf_connection *connection, + union tf_command *command, + union tf_answer *answer) +{ + int error = 0; + struct tf_shmem_desc *shmem_desc[4] = {NULL}; + int i; + + dprintk(KERN_INFO "tf_invoke_client_command(%p)\n", connection); + + command->release_shared_memory.message_size = + (sizeof(struct tf_command_invoke_client_command) - + sizeof(struct tf_command_header)) / 4; + +#ifdef CONFIG_TF_ZEBRA + error = tf_crypto_try_shortcuted_update(connection, + (struct tf_command_invoke_client_command *) command, + (struct tf_answer_invoke_client_command *) answer); + if (error == 0) + return error; +#endif + + /* Map the tmprefs */ + for (i = 0; i < 4; i++) { + int param_type = TF_GET_PARAM_TYPE( + command->invoke_client_command.param_types, i); + if ((param_type & (TF_PARAM_TYPE_MEMREF_FLAG | + TF_PARAM_TYPE_REGISTERED_MEMREF_FLAG)) + == TF_PARAM_TYPE_MEMREF_FLAG) { + /* A temporary memref: map it */ + error = tf_map_temp_shmem(connection, + &command->invoke_client_command. + params[i].temp_memref, + param_type, &shmem_desc[i]); + if (error != 0) { + dprintk(KERN_ERR + "tf_invoke_client_command: " + "unable to map temporary memory " + "block\n (%08X)", error); + goto error; + } + } + } + + command->invoke_client_command.device_context = + connection->device_context; + + error = tf_send_receive(&connection->dev->sm, command, + answer, connection, true); + +error: + /* Unmap de temp mem refs */ + for (i = 0; i < 4; i++) { + if (shmem_desc[i] != NULL) { + dprintk(KERN_INFO "tf_invoke_client_command: " + "UnMatemp_memref %d\n ", i); + tf_unmap_shmem(connection, shmem_desc[i], 0); + } + } + + if (error != 0) + dprintk(KERN_ERR "tf_invoke_client_command returns %d\n", + error); + else + dprintk(KERN_ERR "tf_invoke_client_command returns " + "error_code 0x%08X\n", + answer->invoke_client_command.error_code); + + return error; +} + + +/* + * Cancels a client command from the Secure World + */ +int tf_cancel_client_command( + struct tf_connection *connection, + union tf_command *command, + union tf_answer *answer) +{ + int error = 0; + + dprintk(KERN_DEBUG "tf_cancel_client_command(%p)\n", connection); + + command->cancel_client_operation.device_context = + connection->device_context; + command->cancel_client_operation.message_size = + (sizeof(struct tf_command_cancel_client_operation) - + sizeof(struct tf_command_header)) / 4; + + error = tf_send_receive( + &connection->dev->sm, + command, + answer, + connection, + true); + + if ((error != 0) || + (answer->cancel_client_operation.error_code != S_SUCCESS)) + goto error; + + + /* successful completion */ + return 0; + +error: + if (error != 0) + dprintk(KERN_ERR "tf_cancel_client_command returns %d\n", + error); + else + dprintk(KERN_ERR "tf_cancel_client_command returns " + "nChannelStatus 0x%08X\n", + answer->cancel_client_operation.error_code); + + return error; +} + + + +/* + * Destroys a device context from the Secure World + */ +int tf_destroy_device_context( + struct tf_connection *connection) +{ + int error; + /* + * AFY: better use the specialized tf_command_destroy_device_context + * structure: this will save stack + */ + union tf_command command; + union tf_answer answer; + + dprintk(KERN_INFO "tf_destroy_device_context(%p)\n", connection); + + BUG_ON(connection == NULL); + + command.header.message_type = TF_MESSAGE_TYPE_DESTROY_DEVICE_CONTEXT; + command.header.message_size = + (sizeof(struct tf_command_destroy_device_context) - + sizeof(struct tf_command_header))/sizeof(u32); + + /* + * fill in the device context handler + * it is guarantied that the first shared memory descriptor describes + * the device context + */ + command.destroy_device_context.device_context = + connection->device_context; + + error = tf_send_receive( + &connection->dev->sm, + &command, + &answer, + connection, + false); + + if ((error != 0) || + (answer.destroy_device_context.error_code != S_SUCCESS)) + goto error; + + spin_lock(&(connection->state_lock)); + connection->state = TF_CONN_STATE_NO_DEVICE_CONTEXT; + spin_unlock(&(connection->state_lock)); + + /* successful completion */ + dprintk(KERN_INFO "tf_destroy_device_context(%p)\n", + connection); + return 0; + +error: + if (error != 0) { + dprintk(KERN_ERR "tf_destroy_device_context failed with " + "error %d\n", error); + } else { + dprintk(KERN_ERR "tf_destroy_device_context failed with " + "error_code 0x%08X\n", + answer.destroy_device_context.error_code); + if (answer.destroy_device_context.error_code == + S_ERROR_OUT_OF_MEMORY) + error = -ENOMEM; + else + error = -EFAULT; + } + + return error; +} + + +/*---------------------------------------------------------------------------- + * Connection initialization and cleanup operations + *----------------------------------------------------------------------------*/ + +/* + * Opens a connection to the specified device. + * + * The placeholder referenced by connection is set to the address of the + * new connection; it is set to NULL upon failure. + * + * Returns zero upon successful completion, or an appropriate error code upon + * failure. + */ +int tf_open(struct tf_device *dev, + struct file *file, + struct tf_connection **connection) +{ + int error; + struct tf_connection *conn = NULL; + + dprintk(KERN_INFO "tf_open(%p, %p)\n", file, connection); + + /* + * Allocate and initialize the conn. + * kmalloc only allocates sizeof(*conn) virtual memory + */ + conn = (struct tf_connection *) internal_kmalloc(sizeof(*conn), + GFP_KERNEL); + if (conn == NULL) { + printk(KERN_ERR "tf_open(): " + "Out of memory for conn!\n"); + error = -ENOMEM; + goto error; + } + + memset(conn, 0, sizeof(*conn)); + + conn->state = TF_CONN_STATE_NO_DEVICE_CONTEXT; + conn->dev = dev; + spin_lock_init(&(conn->state_lock)); + atomic_set(&(conn->pending_op_count), 0); + INIT_LIST_HEAD(&(conn->list)); + + /* + * Initialize the shared memory + */ + error = tf_init_shared_memory(conn); + if (error != 0) + goto error; + +#ifdef CONFIG_TF_ZEBRA + /* + * Initialize CUS specifics + */ + tf_crypto_init_cus(conn); +#endif + + /* + * Attach the conn to the device. + */ + spin_lock(&(dev->connection_list_lock)); + list_add(&(conn->list), &(dev->connection_list)); + spin_unlock(&(dev->connection_list_lock)); + + /* + * Successful completion. + */ + + *connection = conn; + + dprintk(KERN_INFO "tf_open(): Success (conn=%p)\n", conn); + return 0; + + /* + * Error handling. + */ + +error: + dprintk(KERN_ERR "tf_open(): Failure (error %d)\n", error); + /* Deallocate the descriptor pages if necessary */ + internal_kfree(conn); + *connection = NULL; + return error; +} + + +/* + * Closes the specified connection. + * + * Upon return, the connection has been destroyed and cannot be used anymore. + * + * This function does nothing if connection is set to NULL. + */ +void tf_close(struct tf_connection *connection) +{ + int error; + enum TF_CONN_STATE state; + + dprintk(KERN_DEBUG "tf_close(%p)\n", connection); + + if (connection == NULL) + return; + + /* + * Assumption: Linux guarantees that no other operation is in progress + * and that no other operation will be started when close is called + */ + BUG_ON(atomic_read(&(connection->pending_op_count)) != 0); + + /* + * Exchange a Destroy Device Context message if needed. + */ + spin_lock(&(connection->state_lock)); + state = connection->state; + spin_unlock(&(connection->state_lock)); + if (state == TF_CONN_STATE_VALID_DEVICE_CONTEXT) { + /* + * A DestroyDeviceContext operation was not performed. Do it + * now. + */ + error = tf_destroy_device_context(connection); + if (error != 0) + /* avoid cleanup if destroy device context fails */ + goto error; + } + + /* + * Clean up the shared memory + */ + tf_cleanup_shared_memories(connection); + + spin_lock(&(connection->dev->connection_list_lock)); + list_del(&(connection->list)); + spin_unlock(&(connection->dev->connection_list_lock)); + + internal_kfree(connection); + + return; + +error: + dprintk(KERN_DEBUG "tf_close(%p) failed with error code %d\n", + connection, error); +} + |