blob: b9543c53c1ea9f30eb1040d5dece681d82c6ee95 [file] [log] [blame]
/*
* SPDX-License-Identifier: BSD-3-Clause
* SPDX-FileCopyrightText: Copyright TF-RMM Contributors.
*/
#include <attestation.h>
#include <buffer.h>
#include <debug.h>
#include <granule.h>
#include <measurement.h>
#include <realm.h>
#include <rsi-handler.h>
#include <smc-rsi.h>
#include <smc.h>
#include <string.h>
#include <utils_def.h>
#define MAX_EXTENDED_SIZE (64U)
#define MAX_MEASUREMENT_WORDS (MAX_MEASUREMENT_SIZE / sizeof(unsigned long))
/*
* Return the Realm Personalization Value.
*
* Arguments:
* rd - The Realm descriptor.
* claim_ptr - The start address of the Realm Personalization Value claim
* claim_len - The length of the Realm Personalization Value claim
*/
static void get_rpv(struct rd *rd, void **claim_ptr, size_t *claim_len)
{
*claim_ptr = (uint8_t *)&(rd->rpv[0]);
*claim_len = RPV_SIZE;
}
/*
* Function to continue with the token write operation
*/
static void attest_token_continue_write_state(struct rec *rec,
struct rsi_result *res)
{
struct granule *gr;
uintptr_t realm_att_token;
unsigned long realm_att_token_ipa = rec->regs[1];
unsigned long offset = rec->regs[2];
unsigned long size = rec->regs[3];
enum s2_walk_status walk_status;
struct s2_walk_result walk_res = { 0UL };
size_t attest_token_len, length;
struct rec_attest_data *attest_data = rec->aux_data.attest_data;
uintptr_t cca_token_buf = rec->aux_data.cca_token_buf;
enum attest_token_err_t ret;
/*
* Translate realm granule IPA to PA. If returns with
* WALK_SUCCESS then the last level page table (llt),
* which holds the realm_att_token_buf mapping, is locked.
*/
walk_status = realm_ipa_to_pa(rec, realm_att_token_ipa, &walk_res);
/* Walk parameter validity was checked by RSI_ATTESTATION_TOKEN_INIT */
assert(walk_status != WALK_INVALID_PARAMS);
if (walk_status == WALK_FAIL) {
if (walk_res.ripas_val == RIPAS_EMPTY) {
res->smc_res.x[0] = RSI_ERROR_INPUT;
} else {
/*
* Translation failed, IPA is not mapped.
* Return to NS host to fix the issue.
*/
res->action = STAGE_2_TRANSLATION_FAULT;
res->rtt_level = walk_res.rtt_level;
}
return;
}
/* If size of buffer is 0, then return early. */
if (size == 0UL) {
res->smc_res.x[0] = RSI_INCOMPLETE;
goto out_unlock;
}
/* Map realm data granule to RMM address space */
gr = find_lock_granule(walk_res.pa, GRANULE_STATE_DATA);
realm_att_token = (uintptr_t)buffer_granule_map(gr, SLOT_REALM);
assert(realm_att_token != 0UL);
if (attest_data->rmm_cca_token_copied_len == 0UL) {
ret = attest_cca_token_create(
&attest_data->token_sign_ctx,
(void *)cca_token_buf,
REC_ATTEST_TOKEN_BUF_SIZE,
&attest_data->rmm_realm_token_buf,
attest_data->rmm_realm_token_len,
&attest_token_len);
if (ret != ATTEST_TOKEN_ERR_SUCCESS) {
res->smc_res.x[0] = RSI_ERROR_INPUT;
goto out_unmap;
}
attest_data->rmm_cca_token_len = attest_token_len;
} else {
attest_token_len = attest_data->rmm_cca_token_len;
}
length = (size < attest_token_len) ? size : attest_token_len;
/* Copy attestation token */
(void)memcpy((void *)(realm_att_token + offset),
(void *)(cca_token_buf +
attest_data->rmm_cca_token_copied_len),
length);
attest_token_len -= length;
if (attest_token_len != 0UL) {
attest_data->rmm_cca_token_len = attest_token_len;
attest_data->rmm_cca_token_copied_len += length;
res->smc_res.x[0] = RSI_INCOMPLETE;
} else {
res->smc_res.x[0] = RSI_SUCCESS;
/* Token creation is complete, reset realm token length */
attest_data->rmm_realm_token_len = 0;
}
res->smc_res.x[1] = length;
out_unmap:
/* Unmap realm granule */
buffer_unmap((void *)realm_att_token);
granule_unlock(gr);
out_unlock:
/* Unlock last level page table (walk_res.g_llt) */
granule_unlock(walk_res.llt);
}
void handle_rsi_attest_token_init(struct rec *rec, struct rsi_result *res)
{
struct rd *rd;
struct rec_attest_data *attest_data;
void *rpv_ptr;
size_t rpv_len;
enum attest_token_err_t ret;
int att_ret;
assert(rec != NULL);
attest_data = rec->aux_data.attest_data;
res->action = UPDATE_REC_RETURN_TO_REALM;
/* Initialize length fields in attestation data */
attest_data->rmm_realm_token_len = 0;
attest_data->rmm_cca_token_copied_len = 0;
attest_data->rmm_cca_token_len = 0;
/*
* Calling RSI_ATTESTATION_TOKEN_INIT any time aborts any ongoing
* operation.
*/
ret = attest_token_ctx_init(&attest_data->token_sign_ctx,
rec->aux_data.attest_heap_buf,
REC_HEAP_SIZE,
granule_addr(rec->g_rec));
if (ret != ATTEST_TOKEN_ERR_SUCCESS) {
ERROR("Failed to initialize attestation token context.\n");
res->smc_res.x[0] = RSI_ERROR_UNKNOWN;
return;
}
/*
* rd lock is acquired so that measurement cannot be updated
* simultaneously by another rec
*/
granule_lock(rec->realm_info.g_rd, GRANULE_STATE_RD);
rd = buffer_granule_map(rec->realm_info.g_rd, SLOT_RD);
assert(rd != NULL);
get_rpv(rd, &rpv_ptr, &rpv_len);
att_ret = attest_realm_token_create(rd->algorithm, rd->measurement,
MEASUREMENT_SLOT_NR,
rpv_ptr,
rpv_len,
(const void *)&rec->regs[1],
ATTEST_CHALLENGE_SIZE,
&attest_data->token_sign_ctx,
attest_data->rmm_realm_token_buf,
sizeof(attest_data->rmm_realm_token_buf));
buffer_unmap(rd);
granule_unlock(rec->realm_info.g_rd);
if (att_ret != 0) {
ERROR("Realm token creation failed.\n");
res->smc_res.x[0] = RSI_ERROR_UNKNOWN;
return;
}
res->smc_res.x[0] = RSI_SUCCESS;
res->smc_res.x[1] = REC_ATTEST_TOKEN_BUF_SIZE;
}
/*
* Return 'false' if no IRQ is pending,
* return 'true' if there is an IRQ pending, and need to return to Host.
*/
static bool check_pending_irq(void)
{
return (read_isr_el1() != 0UL);
}
static __unused int write_response_to_rec(struct rec *curr_rec,
uintptr_t resp_granule)
{
struct granule *rec_granule = NULL;
struct rec *rec = NULL;
void *rec_aux = NULL;
struct token_sign_cntxt *ctx = NULL;
struct rec_attest_data *attest_data = NULL;
bool unmap_unlock_needed = false;
int ret = 0;
/*
* Check if the granule is the same as the current REC. If it is, the current
* code path is guaranteed to have a reference on the REC and the REC
* cannot be deleted. It also means that the REC is mapped at the usual
* SLOT_REC, so we can avoid locking and mapping the REC and the AUX
* granules.
*/
if (resp_granule != granule_addr(curr_rec->g_rec)) {
rec_granule = find_lock_granule(
resp_granule, GRANULE_STATE_REC);
if (rec_granule == NULL) {
/*
* REC must have been destroyed, drop the response.
*/
VERBOSE("REC granule %lx not found\n", resp_granule);
return 0;
}
rec = buffer_granule_map(rec_granule, SLOT_EL3_TOKEN_SIGN_REC);
assert(rec != NULL);
/*
* Map auxiliary granules. Note that the aux granules are mapped at a
* different high VA than during realm creation since this function
* may be executing with another rec mapped at the same high VA.
* Any accesses to aux granules via initialized pointers such as
* attest_data, need to be recaluclated at the new high VA.
*/
rec_aux = buffer_rec_aux_granules_map_el3_token_sign_slot(
rec->g_aux,
rec->num_rec_aux);
assert(rec_aux != NULL);
unmap_unlock_needed = true;
attest_data = (struct rec_attest_data *)((uintptr_t)rec_aux + REC_HEAP_SIZE +
REC_PMU_SIZE + REC_SIMD_SIZE);
} else {
rec = curr_rec;
attest_data = rec->aux_data.attest_data;
}
ctx = &attest_data->token_sign_ctx;
if (attest_el3_token_write_response_to_ctx(ctx, resp_granule) != 0) {
ret = -EPERM;
}
if (unmap_unlock_needed) {
buffer_rec_aux_unmap(rec_aux, rec->num_rec_aux);
buffer_unmap(rec);
granule_unlock(rec_granule);
}
return ret;
}
void handle_rsi_attest_token_continue(struct rec *rec,
struct rmi_rec_exit *rec_exit,
struct rsi_result *res)
{
struct rec_attest_data *attest_data;
unsigned long realm_buf_ipa, offset, size;
assert(rec != NULL);
assert(rec_exit != NULL);
attest_data = rec->aux_data.attest_data;
res->action = UPDATE_REC_RETURN_TO_REALM;
realm_buf_ipa = rec->regs[1];
offset = rec->regs[2];
size = rec->regs[3];
if (!GRANULE_ALIGNED(realm_buf_ipa) ||
(offset >= GRANULE_SIZE) ||
((offset + size) > GRANULE_SIZE) ||
((offset + size) < offset)) {
res->smc_res.x[0] = RSI_ERROR_INPUT;
return;
}
if (!addr_in_rec_par(rec, realm_buf_ipa)) {
res->smc_res.x[0] = RSI_ERROR_INPUT;
return;
}
/* Sign the token */
while (attest_data->rmm_realm_token_len == 0U) {
enum attest_token_err_t ret;
ret = attest_realm_token_sign(&(attest_data->token_sign_ctx),
&(attest_data->rmm_realm_token_len));
if (ret == ATTEST_TOKEN_ERR_INVALID_STATE) {
/*
* Before this call the initial attestation token call
* (SMC_RSI_ATTEST_TOKEN_INIT) must have been executed
* successfully.
*/
res->smc_res.x[0] = RSI_ERROR_STATE;
return;
} else if ((ret != ATTEST_TOKEN_ERR_COSE_SIGN_IN_PROGRESS) &&
(ret != ATTEST_TOKEN_ERR_SUCCESS)) {
/* Accessible only in case of failure during token signing */
ERROR("Realm token signing failed.\n");
res->smc_res.x[0] = RSI_ERROR_UNKNOWN;
return;
}
res->smc_res.x[0] = RSI_INCOMPLETE;
/*
* Return to RSI handler function after each iteration
* to check is there anything else to do (pending IRQ)
* or next signing iteration can be executed.
*/
if (check_pending_irq()) {
res->action = UPDATE_REC_EXIT_TO_HOST;
rec_exit->exit_reason = RMI_EXIT_IRQ;
return;
}
#if ATTEST_EL3_TOKEN_SIGN
/*
* Pull response from EL3, find the corresponding rec granule
* and attestation context, and have the attestation library
* write the response to the context.
*/
uintptr_t granule = 0UL;
int el3_token_sign_ret;
el3_token_sign_ret = attest_el3_token_sign_pull_response_from_el3(&granule);
if (el3_token_sign_ret == -EAGAIN) {
continue;
}
if (el3_token_sign_ret != 0) {
ERROR("Failed to pull response from EL3: %d\n", el3_token_sign_ret);
res->smc_res.x[0] = RSI_ERROR_UNKNOWN;
return;
}
el3_token_sign_ret = write_response_to_rec(rec, granule);
if (el3_token_sign_ret != 0) {
ERROR("Failed to write response to REC: %d\n", el3_token_sign_ret);
res->smc_res.x[0] = RSI_ERROR_UNKNOWN;
return;
}
#endif
/*
* If there are no interrupts pending, then continue to pull
* requests from EL3 until this RECs request is done.
*/
}
attest_token_continue_write_state(rec, res);
}
void handle_rsi_measurement_extend(struct rec *rec, struct rsi_result *res)
{
struct granule *g_rd;
struct rd *rd;
unsigned long index;
unsigned long rd_addr;
size_t size;
void *extend_measurement;
unsigned char *current_measurement;
assert(rec != NULL);
res->action = UPDATE_REC_RETURN_TO_REALM;
/*
* rd lock is acquired so that measurement cannot be updated
* simultaneously by another rec
*/
rd_addr = granule_addr(rec->realm_info.g_rd);
g_rd = find_lock_granule(rd_addr, GRANULE_STATE_RD);
assert(g_rd != NULL);
rd = buffer_granule_map(rec->realm_info.g_rd, SLOT_RD);
assert(rd != NULL);
/*
* X1: index
* X2: size
* X3-X10: measurement value
*/
index = rec->regs[1];
if ((index == RIM_MEASUREMENT_SLOT) ||
(index >= MEASUREMENT_SLOT_NR)) {
res->smc_res.x[0] = RSI_ERROR_INPUT;
goto out_unmap_rd;
}
size = rec->regs[2];
if (size > MAX_EXTENDED_SIZE) {
res->smc_res.x[0] = RSI_ERROR_INPUT;
goto out_unmap_rd;
}
extend_measurement = &rec->regs[3];
current_measurement = rd->measurement[index];
measurement_extend(rd->algorithm,
current_measurement,
extend_measurement,
size,
current_measurement,
MAX_MEASUREMENT_SIZE);
res->smc_res.x[0] = RSI_SUCCESS;
out_unmap_rd:
buffer_unmap(rd);
granule_unlock(g_rd);
}
void handle_rsi_measurement_read(struct rec *rec, struct rsi_result *res)
{
struct rd *rd;
unsigned long idx;
unsigned int i, cnt;
unsigned long *measurement_value_part;
assert(rec != NULL);
res->action = UPDATE_REC_RETURN_TO_REALM;
/* X1: Index */
idx = rec->regs[1];
if (idx >= MEASUREMENT_SLOT_NR) {
res->smc_res.x[0] = RSI_ERROR_INPUT;
return;
}
/*
* rd lock is acquired so that measurement cannot be updated
* simultaneously by another rec
*/
granule_lock(rec->realm_info.g_rd, GRANULE_STATE_RD);
rd = buffer_granule_map(rec->realm_info.g_rd, SLOT_RD);
assert(rd != NULL);
/* Number of 8-bytes words in measurement */
cnt = (unsigned int)(measurement_get_size(rd->algorithm) /
sizeof(unsigned long));
assert(cnt >= (SMC_RESULT_REGS - 1U));
assert(cnt < ARRAY_SIZE(rec->regs));
/* Copy the part of the measurement to res->smc_res.x[] */
for (i = 0U; i < (SMC_RESULT_REGS - 1U); i++) {
measurement_value_part = (unsigned long *)
&(rd->measurement[idx][i * sizeof(unsigned long)]);
res->smc_res.x[i + 1U] = *measurement_value_part;
}
/* Copy the rest of the measurement to the rec->regs[] */
for (; i < cnt; i++) {
measurement_value_part = (unsigned long *)
&(rd->measurement[idx][i * sizeof(unsigned long)]);
rec->regs[i + 1U] = *measurement_value_part;
}
/* Zero-initialize unused area */
for (; i < MAX_MEASUREMENT_WORDS; i++) {
rec->regs[i + 1U] = 0UL;
}
buffer_unmap(rd);
granule_unlock(rec->realm_info.g_rd);
res->smc_res.x[0] = RSI_SUCCESS;
}