/* Copyright (c) 2017-2018 ARM Limited * * SPDX-License-Identifier: Apache-2.0 * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #include <string.h> #include "mbed_toolchain.h" #include "spm_server.h" #include "spm_messages.h" #include "spm_internal.h" #include "spm_panic.h" #include "handles_manager.h" #include "cmsis.h" extern spm_db_t g_spm; static inline spm_partition_t *get_partition_by_pid(int32_t partition_id) { for (uint32_t i = 0; i < g_spm.partition_count; ++i) { if (g_spm.partitions[i].partition_id == partition_id) { return &(g_spm.partitions[i]); } } return NULL; } static inline psa_handle_t create_msg_handle(void *handle_mem, int32_t friend_pid) { return psa_hndl_mgr_handle_create(&(g_spm.messages_handle_mgr), handle_mem, friend_pid); } static inline spm_active_msg_t *get_msg_from_handle(psa_handle_t handle) { return (spm_active_msg_t *)psa_hndl_mgr_handle_get_mem(&(g_spm.messages_handle_mgr), handle); } static inline void destroy_msg_handle(psa_handle_t handle) { psa_hndl_mgr_handle_destroy(&(g_spm.messages_handle_mgr), handle); } static inline void destroy_channel_handle(psa_handle_t handle) { psa_hndl_mgr_handle_destroy(&(g_spm.channels_handle_mgr), handle); } static inline spm_rot_service_t *get_rot_service(spm_partition_t *prt, psa_signal_t signal) { for (size_t i = 0; i < prt->rot_services_count; i++) { if (prt->rot_services[i].mask == signal) { return &prt->rot_services[i]; } } return NULL; } /* * This function validates the parameters sent from the user and copies it to an active message * * Function assumptions: * * channel is allocated from SPM Core memory * * channel->msg_ptr potentially allocated from nonsecure memory * * user_msg allocated from secure partition memory - not trusted by SPM Core */ static void copy_message_to_spm(spm_ipc_channel_t *channel, psa_msg_t *user_msg) { // Memory allocated from MemoryPool isn't zeroed - thus a temporary variable will make sure we will start from a clear state. spm_active_msg_t temp_active_message = { .channel = channel, .iovecs = {{.in = {0}}}, .out_index = 0, }; if (channel->msg_type == PSA_IPC_CALL) { if (!is_buffer_accessible(channel->msg_ptr, sizeof(spm_pending_call_msg_t), channel->src_partition)) { SPM_PANIC("message data is inaccessible\n"); } spm_pending_call_msg_t *call_msg_data = (spm_pending_call_msg_t *)channel->msg_ptr; // Copy pointers and sizes to secure memory to prevent TOCTOU const psa_invec *temp_invec = call_msg_data->in_vec; const uint32_t temp_invec_size = call_msg_data->in_vec_size; const psa_outvec *temp_outvec = call_msg_data->out_vec; const uint32_t temp_outvec_size = call_msg_data->out_vec_size; validate_iovec(temp_invec, temp_invec_size, temp_outvec, temp_outvec_size); temp_active_message.out_index = (uint8_t)temp_invec_size; if (temp_invec_size > 0) { if (!is_buffer_accessible(temp_invec, temp_invec_size * sizeof(*temp_invec), channel->src_partition)) { SPM_PANIC("in_vec is inaccessible\n"); } for (uint32_t i = 0; i < temp_invec_size; ++i) { if (temp_invec[i].len == 0) { continue; } // Copy struct temp_active_message.iovecs[i].in = temp_invec[i]; user_msg->in_size[i] = temp_invec[i].len; // Copy then check to prevent TOCTOU if (!is_buffer_accessible( temp_active_message.iovecs[i].in.base, temp_active_message.iovecs[i].in.len, channel->src_partition)) { SPM_PANIC("in_vec[%ld] is inaccessible\n", i); } } } if (temp_outvec_size > 0) { if (!is_buffer_accessible(temp_outvec, temp_outvec_size * sizeof(*temp_outvec), channel->src_partition)) { SPM_PANIC("out_vec is inaccessible\n"); } for (uint32_t i = 0; i < temp_outvec_size; ++i) { if (temp_outvec[i].len == 0) { continue; } // Copy struct temp_active_message.iovecs[temp_invec_size + i].out = temp_outvec[i]; user_msg->out_size[i] = temp_outvec[i].len; // Copy then check to prevent TOCTOU if (!is_buffer_accessible( temp_active_message.iovecs[temp_invec_size + i].out.base, temp_active_message.iovecs[temp_invec_size + i].out.len, channel->src_partition)) { SPM_PANIC("out_vec[%lu] is inaccessible\n", i); } } } } // Allocating from SPM-Core internal memory spm_active_msg_t *active_msg = (spm_active_msg_t *)osMemoryPoolAlloc(g_spm.active_messages_mem_pool, osWaitForever); if (NULL == active_msg) { SPM_PANIC("Could not allocate active message"); } // Copy struct *active_msg = temp_active_message; psa_handle_t handle = create_msg_handle(active_msg, PSA_HANDLE_MGR_INVALID_FRIEND_OWNER); user_msg->type = channel->msg_type; user_msg->rhandle = channel->rhandle; user_msg->handle = handle; if (channel->src_partition == NULL) { user_msg->client_id = PSA_NSPE_IDENTIFIER; } else { user_msg->client_id = channel->src_partition->partition_id; } } static spm_ipc_channel_t *spm_rot_service_queue_dequeue(spm_rot_service_t *rot_service) { osStatus_t os_status = osMutexAcquire(rot_service->partition->mutex, osWaitForever); SPM_ASSERT(osOK == os_status); PSA_UNUSED(os_status); spm_ipc_channel_t *ret = rot_service->queue.head; if (ret == NULL) { SPM_PANIC("Dequeue from empty queue"); } rot_service->queue.head = ret->next; ret->next = NULL; if (rot_service->queue.head == NULL) { rot_service->queue.tail = NULL; uint32_t flags = osThreadFlagsClear(rot_service->mask); // osThreadFlagsClear() sets the msb on failure SPM_ASSERT((flags & SPM_CMSIS_RTOS_ERROR_BIT_MSK) == 0); SPM_ASSERT(flags & rot_service->mask); PSA_UNUSED(flags); } os_status = osMutexRelease(rot_service->partition->mutex); SPM_ASSERT(osOK == os_status); return ret; } psa_signal_t psa_wait(psa_signal_t signal_mask, uint32_t timeout) { spm_partition_t *curr_partition = get_active_partition(); SPM_ASSERT(NULL != curr_partition); // active thread in SPM must be in partition DB psa_signal_t flags_all = curr_partition->flags | PSA_DOORBELL; if (signal_mask == PSA_WAIT_ANY) { signal_mask = flags_all; } else { if ((~flags_all) & signal_mask) { SPM_PANIC("signal mask 0x%lx must have only bits from 0x%lx!\n", signal_mask, flags_all); } } psa_signal_t asserted_signals = osThreadFlagsWait( signal_mask, osFlagsWaitAny | osFlagsNoClear, (PSA_BLOCK & timeout) ? osWaitForever : 0 ); // Asserted_signals must be a subset of the supported ROT_SRV and interrupt signals. SPM_ASSERT((asserted_signals == (asserted_signals & flags_all)) || ((PSA_BLOCK != timeout) && (osFlagsErrorTimeout == asserted_signals))); return (osFlagsErrorTimeout == asserted_signals) ? 0 : asserted_signals; } psa_status_t psa_get(psa_signal_t signal, psa_msg_t *msg) { spm_partition_t *curr_partition = get_active_partition(); SPM_ASSERT(NULL != curr_partition); // active thread in SPM must be in partition DB if (!is_buffer_accessible(msg, sizeof(*msg), curr_partition)) { SPM_PANIC("msg is inaccessible\n"); } memset(msg, 0, sizeof(*msg)); // signal must be ONLY ONE of the bits of curr_partition->flags_rot_srv bool is_one_bit = ((signal != 0) && !(signal & (signal - 1))); if (!is_one_bit || !(signal & curr_partition->flags)) { SPM_PANIC( "signal 0x%lx must have only 1 bit ON and must be a subset of 0x%lx!\n", signal, curr_partition->flags ); } uint32_t active_flags = osThreadFlagsGet(); if (0 == (signal & active_flags)) { SPM_PANIC("flag is not active!\n"); } spm_rot_service_t *curr_rot_service = get_rot_service(curr_partition, signal); if (curr_rot_service == NULL) { SPM_PANIC("Received signal (0x%08lx) that does not match any root of trust service", signal); } spm_ipc_channel_t *curr_channel = spm_rot_service_queue_dequeue(curr_rot_service); switch (curr_channel->msg_type) { case PSA_IPC_CONNECT: channel_state_assert( &curr_channel->state, SPM_CHANNEL_STATE_CONNECTING ); break; case PSA_IPC_CALL: channel_state_switch( &curr_channel->state, SPM_CHANNEL_STATE_PENDING, SPM_CHANNEL_STATE_ACTIVE ); break; case PSA_IPC_DISCONNECT: { channel_state_assert( &curr_channel->state, SPM_CHANNEL_STATE_IDLE ); break; } default: SPM_PANIC("psa_get - unexpected message type=0x%08hhX", curr_channel->msg_type); break; } copy_message_to_spm(curr_channel, msg); return PSA_SUCCESS; } static size_t read_or_skip(psa_handle_t msg_handle, uint32_t invec_idx, void *buf, size_t num_bytes) { spm_active_msg_t *active_msg = get_msg_from_handle(msg_handle); channel_state_assert(&active_msg->channel->state, SPM_CHANNEL_STATE_ACTIVE); if (invec_idx >= PSA_MAX_IOVEC) { SPM_PANIC("Invalid invec_idx\n"); } if (invec_idx >= active_msg->out_index) { return 0; } psa_invec *active_iovec = &active_msg->iovecs[invec_idx].in; if (num_bytes > active_iovec->len) { num_bytes = active_iovec->len; } if (num_bytes > 0) { if (buf) { memcpy(buf, active_iovec->base, num_bytes); } active_iovec->base = (void *)((uint8_t *)active_iovec->base + num_bytes); active_iovec->len -= num_bytes; } return num_bytes; } size_t psa_read(psa_handle_t msg_handle, uint32_t invec_idx, void *buf, size_t num_bytes) { spm_partition_t *curr_partition = get_active_partition(); SPM_ASSERT(NULL != curr_partition); // active thread in SPM must be in partition DB if (!is_buffer_accessible(buf, num_bytes, curr_partition)) { SPM_PANIC("buffer is inaccessible\n"); } return read_or_skip(msg_handle, invec_idx, buf, num_bytes); } size_t psa_skip(psa_handle_t msg_handle, uint32_t invec_idx, size_t num_bytes) { return read_or_skip(msg_handle, invec_idx, NULL, num_bytes); } void psa_write(psa_handle_t msg_handle, uint32_t outvec_idx, const void *buffer, size_t num_bytes) { if (0 == num_bytes) { return; } spm_partition_t *curr_partition = get_active_partition(); SPM_ASSERT(NULL != curr_partition); // active thread in S if (!is_buffer_accessible(buffer, num_bytes, curr_partition)) { SPM_PANIC("buffer is inaccessible\n"); } spm_active_msg_t *active_msg = get_msg_from_handle(msg_handle); channel_state_assert(&active_msg->channel->state, SPM_CHANNEL_STATE_ACTIVE); outvec_idx += active_msg->out_index; if (outvec_idx >= PSA_MAX_IOVEC) { SPM_PANIC("Invalid outvec_idx\n"); } psa_outvec *active_iovec = &active_msg->iovecs[outvec_idx].out; if (num_bytes > active_iovec->len) { SPM_PANIC("Invalid write operation (Requested %zu, Avialable %zu)\n", num_bytes, active_iovec->len); } memcpy((uint8_t *)(active_iovec->base), buffer, num_bytes); active_iovec->base = (void *)((uint8_t *)active_iovec->base + num_bytes); active_iovec->len -= num_bytes; return; } void psa_reply(psa_handle_t msg_handle, psa_status_t status) { spm_active_msg_t *active_msg = get_msg_from_handle(msg_handle); spm_ipc_channel_t *active_channel = active_msg->channel; SPM_ASSERT(active_channel != NULL); SPM_ASSERT(active_channel->msg_ptr != NULL); // !!!!!NOTE!!!!! handles must be destroyed before osMemoryPoolFree(). // Message creation blocked on resource exhaustion and handle will be created // only after a successful memory allocation and is not expected to fail. { destroy_msg_handle(msg_handle); memset(active_msg, 0, sizeof(*active_msg)); osStatus_t os_status = osMemoryPoolFree(g_spm.active_messages_mem_pool, active_msg); SPM_ASSERT(osOK == os_status); PSA_UNUSED(os_status); } osSemaphoreId_t completion_sem_id = NULL; bool nspe_call = (active_channel->src_partition == NULL); switch (active_channel->msg_type) { case PSA_IPC_CONNECT: { if ((status != PSA_SUCCESS) && (status != PSA_CONNECTION_REFUSED)) { SPM_PANIC("status (0X%08lx) is not allowed for PSA_IPC_CONNECT", status); } spm_pending_connect_msg_t *connect_msg_data = (spm_pending_connect_msg_t *)(active_channel->msg_ptr); completion_sem_id = connect_msg_data->completion_sem_id; if (status == PSA_CONNECTION_REFUSED) { channel_state_assert(&active_channel->state, SPM_CHANNEL_STATE_CONNECTING); // !!!!!NOTE!!!!! handles must be destroyed before osMemoryPoolFree(). // Channel creation fails on resource exhaustion and handle will be created // only after a successful memory allocation and is not expected to fail. { // In psa_connect the handle was written to user's memory before // the connection was established. // Channel state machine and ACL will prevent attacker from // doing bad stuff before we overwrite it with a negative return code. destroy_channel_handle((psa_handle_t)connect_msg_data->rc); memset(active_channel, 0, sizeof(*active_channel)); osStatus_t os_status = osMemoryPoolFree(g_spm.channel_mem_pool, active_channel); SPM_ASSERT(osOK == os_status); PSA_UNUSED(os_status); } // Replace the handle we created in the user's memory with the error code connect_msg_data->rc = status; active_channel = NULL; } else { channel_state_switch(&active_channel->state, SPM_CHANNEL_STATE_CONNECTING, SPM_CHANNEL_STATE_IDLE); } break; } case PSA_IPC_CALL: { if ((status >= PSA_RESERVED_ERROR_MIN) && (status <= PSA_RESERVED_ERROR_MAX)) { SPM_PANIC("status (0X%08lx) is not allowed for PSA_IPC_CALL", status); } channel_state_switch(&active_channel->state, SPM_CHANNEL_STATE_ACTIVE, SPM_CHANNEL_STATE_IDLE); if (status == PSA_DROP_CONNECTION) { active_channel->is_dropped = TRUE; } spm_pending_call_msg_t *call_msg_data = (spm_pending_call_msg_t *)(active_channel->msg_ptr); call_msg_data->rc = status; completion_sem_id = call_msg_data->completion_sem_id; break; } case PSA_IPC_DISCONNECT: { spm_pending_close_msg_t *close_msg_data = (spm_pending_close_msg_t *)(active_channel->msg_ptr); completion_sem_id = close_msg_data->completion_sem_id; channel_state_switch(&(active_channel->state), SPM_CHANNEL_STATE_IDLE, SPM_CHANNEL_STATE_INVALID ); // !!!!!NOTE!!!!! handles must be destroyed before osMemoryPoolFree(). // Channel creation fails on resource exhaustion and handle will be created // only after a successful memory allocation and is not expected to fail. { destroy_channel_handle(close_msg_data->handle); memset(active_channel, 0, sizeof(*active_channel)); osStatus_t os_status = osMemoryPoolFree(g_spm.channel_mem_pool, active_channel); active_channel = NULL; SPM_ASSERT(osOK == os_status); PSA_UNUSED(os_status); } break; // Note: The status code is ignored for PSA_IPC_DISCONNECT message type } default: SPM_PANIC("psa_reply() - Unexpected message type=0x%08hhX", active_channel->msg_type); break; } if (active_channel != NULL) { active_channel->msg_ptr = NULL; active_channel->msg_type = 0; // uninitialized } if (nspe_call) { nspe_done(completion_sem_id); } else { osStatus_t os_status = osSemaphoreRelease(completion_sem_id); SPM_ASSERT(osOK == os_status); PSA_UNUSED(os_status); } return; } void psa_notify(int32_t partition_id) { spm_partition_t *target_partition = get_partition_by_pid(partition_id); if (NULL == target_partition) { SPM_PANIC("Could not find partition (partition_id = %ld)\n", partition_id ); } uint32_t flags = osThreadFlagsSet(target_partition->thread_id, PSA_DOORBELL); // osThreadFlagsSet() sets the msb on failure // flags is allowed to be 0 since by the time osThreadFlagsSet() returns // the flag could have been cleared by another thread SPM_ASSERT((flags & SPM_CMSIS_RTOS_ERROR_BIT_MSK) == 0); PSA_UNUSED(flags); } void psa_clear(void) { uint32_t flags = osThreadFlagsClear(PSA_DOORBELL); // osThreadFlagsClear() sets the msb on failure SPM_ASSERT((flags & SPM_CMSIS_RTOS_ERROR_BIT_MSK) == 0); // psa_clear() must not be called when doorbell signal is not currently asserted if ((flags & PSA_DOORBELL) != PSA_DOORBELL) { SPM_PANIC("psa_call() called without signaled doorbell\n"); } } void psa_set_rhandle(psa_handle_t msg_handle, void *rhandle) { spm_active_msg_t *active_msg = get_msg_from_handle(msg_handle); active_msg->channel->rhandle = rhandle; } void psa_eoi(uint32_t irq_signal) { spm_partition_t *curr_partition = get_active_partition(); SPM_ASSERT(NULL != curr_partition); if (curr_partition->irq_mapper == NULL) { SPM_PANIC("Try to clear an interrupt flag without declaring IRQ"); } if (0 == (curr_partition->flags & irq_signal)) { SPM_PANIC("Signal %lu not in irq range\n", irq_signal); } bool is_one_bit = ((irq_signal != 0) && !(irq_signal & (irq_signal - 1))); if (!is_one_bit) { SPM_PANIC("signal 0x%lx must have only 1 bit ON!\n", irq_signal); } IRQn_Type irq_line = curr_partition->irq_mapper(irq_signal); uint32_t flags = osThreadFlagsClear(irq_signal); // osThreadFlagsClear() sets the msb on failure SPM_ASSERT((flags & SPM_CMSIS_RTOS_ERROR_BIT_MSK) == 0); // psa_eoi() must not be called with an unasserted flag. if ((flags & irq_signal) != irq_signal) { SPM_PANIC("psa_eoi() called without signaled IRQ\n"); } NVIC_EnableIRQ(irq_line); }