feat(memory share): handle GPF in FFA_MEM_FRAG_RX
In handling the FFA_MEM_FRAG_RX ABI the SPMC copies
memory sharing descriptor fragments to the NWd
RX buffer.
The RX buffer could inadvertently or advertently be put
in the wrong PAS space. This would result in a GPF,
which the SPMC should handle and smoothly return
FFA_ERROR_ABORTED.
Signed-off-by: J-Alves <joao.alves@arm.com>
Change-Id: I63735d5f8cb6a1344323ef768828de053c250ee2
diff --git a/src/api.c b/src/api.c
index f8c3d69..db7c538 100644
--- a/src/api.c
+++ b/src/api.c
@@ -3988,8 +3988,21 @@
goto out;
}
- ret = ffa_memory_retrieve_continue(to_locked, handle, fragment_offset,
- sender_vm_id, &api_page_pool);
+ /*
+ * Whilst copying the fragments, initialize the remaining constituents
+ * in the CPU's internal structure, and later copy from the CPU buffer
+ * into the partition's RX buffer. In the case SPMC is doing a retrieve
+ * request for a VM/Hypervisor in an RME enabled system, there is no
+ * guarantee the RX buffer is in the NS PAS. Accessing the buffer with
+ * the wrong security state attribute would then result in an GPF.
+ * The fragment is initialized in an internal buffer, and is later
+ * copied to the RX buffer using the 'memcpy_trapped' which allows to
+ * smoothly terminate the operation if the access has been preempted by
+ * a GPF exception.
+ */
+ ret = ffa_memory_retrieve_continue(
+ to_locked, handle, fragment_offset, sender_vm_id,
+ cpu_get_buffer(current->cpu), &api_page_pool);
out:
vm_unlock(&to_locked);
return ret;
diff --git a/src/ffa_memory.c b/src/ffa_memory.c
index d45d2b3..056fd86 100644
--- a/src/ffa_memory.c
+++ b/src/ffa_memory.c
@@ -3653,6 +3653,7 @@
ffa_memory_handle_t handle,
uint32_t fragment_offset,
ffa_id_t sender_vm_id,
+ void *retrieve_continue_page,
struct mpool *page_pool)
{
struct ffa_memory_region *memory_region;
@@ -3694,7 +3695,7 @@
/*
* If retrieve request from the hypervisor has been initiated in the
* given share_state, continue it, else assume it is a continuation of
- * retrieve request from a NWd VM.
+ * retrieve request from a partition.
*/
continue_ffa_hyp_mem_retrieve_req =
(to_locked.vm->id == HF_HYPERVISOR_VM_ID) &&
@@ -3715,10 +3716,11 @@
goto out;
}
- if (share_state->retrieved_fragment_count[receiver_index] ==
- 0 ||
- share_state->retrieved_fragment_count[receiver_index] >=
- share_state->fragment_count) {
+ fragment_index =
+ share_state->retrieved_fragment_count[receiver_index];
+
+ if (fragment_index == 0 ||
+ fragment_index >= share_state->fragment_count) {
dlog_verbose(
"Retrieval of memory with handle %#lx not yet "
"started or already completed (%d/%d fragments "
@@ -3730,13 +3732,11 @@
ret = ffa_error(FFA_INVALID_PARAMETERS);
goto out;
}
-
- fragment_index =
- share_state->retrieved_fragment_count[receiver_index];
} else {
- if (share_state->hypervisor_fragment_count == 0 ||
- share_state->hypervisor_fragment_count >=
- share_state->fragment_count) {
+ fragment_index = share_state->hypervisor_fragment_count;
+
+ if (fragment_index == 0 ||
+ fragment_index >= share_state->fragment_count) {
dlog_verbose(
"Retrieve of memory with handle %lx not "
"started from hypervisor.\n",
@@ -3754,8 +3754,6 @@
goto out;
}
- fragment_index = share_state->hypervisor_fragment_count;
-
receiver_index = 0;
}
@@ -3790,12 +3788,27 @@
assert(plat_ffa_acquire_receiver_rx(to_locked, &ret));
remaining_constituent_count = ffa_memory_fragment_init(
- to_locked.vm->mailbox.recv, HF_MAILBOX_SIZE,
- share_state->fragments[fragment_index],
+ (struct ffa_memory_region_constituent *)retrieve_continue_page,
+ HF_MAILBOX_SIZE, share_state->fragments[fragment_index],
share_state->fragment_constituent_counts[fragment_index],
&fragment_length);
CHECK(remaining_constituent_count == 0);
+ /*
+ * Return FFA_ERROR(FFA_ABORTED) in case the access to the partition's
+ * RX buffer results in a GPF exception. Could happen if the retrieve
+ * request is for a VM or the Hypervisor retrieve request, if the PAS
+ * has been changed externally.
+ */
+ if (!memcpy_trapped(to_locked.vm->mailbox.recv, HF_MAILBOX_SIZE,
+ retrieve_continue_page, fragment_length)) {
+ dlog_error(
+ "%s: aborted copying fragment to RX buffer of %#x.\n",
+ __func__, to_locked.vm->id);
+ ret = ffa_error(FFA_ABORTED);
+ goto out;
+ }
+
to_locked.vm->mailbox.recv_func = FFA_MEM_FRAG_TX_32;
to_locked.vm->mailbox.state = MAILBOX_STATE_FULL;