feat(memory share): relinquish use `memcpy_trapped`

The handling of FFA_MEM_RELINQUISH copies the relinquish descriptor
from the TX buffer of the NWd for calls made from NWd VMs.
To safely do so, refactored the code copying the descriptor and
used `memcpy_trapped` to recover from a potential GPF. The copying
was split in two steps: first the relinquish descriptor and then
the rest of the message. The first step's intent is to safely
validate the content of the header before copying the whole descriptor.

Signed-off-by: J-Alves <joao.alves@arm.com>
Change-Id: Iad29ccfbb9df8e663a0f98aff41eea200a2a1a0d
diff --git a/src/api.c b/src/api.c
index 0eddcef..f5ff325 100644
--- a/src/api.c
+++ b/src/api.c
@@ -3682,15 +3682,118 @@
 	return ret;
 }
 
+/**
+ * Copies the memory relinquish descriptor from the partition's TX buffer, to
+ * the buffer of the local CPU. Do it safely, and return error if:
+ * - FFA_ABORTED: if the `memcpy_trapped` fails.
+ * - FFA_INVALID_PARAMETERS: if the size of the full memory relinquish
+ * descriptor doesn't fit the local CPU buffer.
+ *
+ * Returns FFA_SUCCESS if copying goes well, and sets 'out_relinquish'
+ * to the address of the cpu buffer with the relinquish descriptor.
+ */
+static struct ffa_value api_get_ffa_mem_relinquish_descriptor(
+	struct vcpu *current, const void *from_msg,
+	struct ffa_mem_relinquish **out_relinquish)
+{
+	struct ffa_mem_relinquish *relinquish_request;
+	uint32_t from_msg_size;
+	uint32_t total_from_msg_size;
+	uint32_t dst_size;
+	vaddr_t dst;
+	vaddr_t src;
+
+	assert(from_msg != NULL);
+	assert(out_relinquish != NULL);
+
+	/*
+	 * Copy the relinquish descriptor to an internal buffer, so that the
+	 * caller can't change it underneath us.
+	 */
+	relinquish_request =
+		(struct ffa_mem_relinquish *)cpu_get_buffer(current->cpu);
+
+	/* Set the destination for the copy. */
+	dst = va_from_ptr(relinquish_request);
+	src = va_from_ptr(from_msg);
+
+	dst_size = cpu_get_buffer_size(current->cpu);
+
+	/* Only copy the size to start with. */
+	from_msg_size = sizeof(struct ffa_mem_relinquish);
+	total_from_msg_size = from_msg_size;
+
+	if (!memcpy_trapped(ptr_from_va(dst), dst_size, ptr_from_va(src),
+			    from_msg_size)) {
+		dlog_error(
+			"%s: Failed to copy FF-A memory relinquish "
+			"descriptor.\n",
+			__func__);
+		return ffa_error(FFA_ABORTED);
+	}
+
+	if (relinquish_request->endpoint_count != 1) {
+		dlog_error("%s: relinquish descriptor must have 1 endpoint\n",
+			   __func__);
+		return ffa_error(FFA_INVALID_PARAMETERS);
+	}
+
+	/*
+	 * Increment the `dst` to the position right after the copied header.
+	 * Increment the `src` to point at the list of endpoints.
+	 *
+	 * Calculate the new `dst_size` which is the size of the allocated cpu
+	 * buffer, minus the size of the copied memory relinquish header.
+	 *
+	 * Only after the above: determine the new `from_msg_size` in accordance
+	 * to the endpoint count.
+	 */
+	dst = va_add(dst, from_msg_size);
+	src = va_add(src, from_msg_size);
+
+	/*
+	 * Check if it is safe to copy the rest of the message.
+	 * This also serves as a santiy check to 'endpoint_count'.
+	 * The size of what is left in the descriptor, based on endpoint_count,
+	 * shall not be bigger than the size of the mailbox minus the size of
+	 * the header which was previously copied in this function.
+	 */
+	dst_size -= from_msg_size;
+	from_msg_size = relinquish_request->endpoint_count * sizeof(ffa_id_t);
+	total_from_msg_size += from_msg_size;
+
+	if (total_from_msg_size > HF_MAILBOX_SIZE ||
+	    total_from_msg_size > dst_size) {
+		dlog_verbose(
+			"Relinquish message too long. Endpoint count: %u\n",
+			relinquish_request->endpoint_count);
+		return ffa_error(FFA_INVALID_PARAMETERS);
+	}
+
+	/* Copy the remaining fragment. */
+	if (!memcpy_trapped(ptr_from_va(dst), dst_size, ptr_from_va(src),
+			    from_msg_size)) {
+		dlog_error("%s: Failed to copy FF-A relinquish request.\n",
+			   __func__);
+		return ffa_error(FFA_ABORTED);
+	}
+
+	/*
+	 * Set the output address for the relinquish descriptor to the current
+	 * cpu's buffer.
+	 */
+	*out_relinquish = relinquish_request;
+
+	return (struct ffa_value){.func = FFA_SUCCESS_32};
+}
+
 struct ffa_value api_ffa_mem_relinquish(struct vcpu *current)
 {
 	struct vm *from = current->vm;
 	struct vm_locked from_locked;
 	const void *from_msg;
-	struct ffa_mem_relinquish *relinquish_request;
-	uint32_t message_buffer_size;
 	struct ffa_value ret;
-	uint32_t length;
+	struct ffa_mem_relinquish *relinquish_request;
 
 	from_locked = vm_lock(from);
 	from_msg = from->mailbox.send;
@@ -3701,47 +3804,18 @@
 		goto out;
 	}
 
+	ret = api_get_ffa_mem_relinquish_descriptor(current, from_msg,
+						    &relinquish_request);
+
 	/*
-	 * Calculate length from relinquish descriptor before copying. We will
-	 * check again later to make sure it hasn't changed.
+	 * If the descriptor was safely copied, continue with the handling of
+	 * the retrieve request.
 	 */
-	length = sizeof(struct ffa_mem_relinquish) +
-		 ((struct ffa_mem_relinquish *)from_msg)->endpoint_count *
-			 sizeof(ffa_id_t);
-	/*
-	 * Copy the relinquish descriptor to an internal buffer, so that the
-	 * caller can't change it underneath us.
-	 */
-	relinquish_request =
-		(struct ffa_mem_relinquish *)cpu_get_buffer(current->cpu);
-	message_buffer_size = cpu_get_buffer_size(current->cpu);
-	if (length > HF_MAILBOX_SIZE || length > message_buffer_size) {
-		dlog_verbose("Relinquish message too long.\n");
-		ret = ffa_error(FFA_INVALID_PARAMETERS);
-		goto out;
+	if (ret.func == FFA_SUCCESS_32) {
+		ret = ffa_memory_relinquish(from_locked, relinquish_request,
+					    &api_page_pool);
 	}
 
-	if (!memcpy_trapped(relinquish_request, message_buffer_size, from_msg,
-			    length)) {
-		dlog_error("%s: Failed to copy FF-A relinquish request.\n",
-			   __func__);
-		ret = ffa_error(FFA_ABORTED);
-		goto out;
-	}
-
-	if (sizeof(struct ffa_mem_relinquish) +
-		    relinquish_request->endpoint_count * sizeof(ffa_id_t) !=
-	    length) {
-		dlog_verbose(
-			"Endpoint count changed while copying to internal "
-			"buffer.\n");
-		ret = ffa_error(FFA_INVALID_PARAMETERS);
-		goto out;
-	}
-
-	ret = ffa_memory_relinquish(from_locked, relinquish_request,
-				    &api_page_pool);
-
 out:
 	vm_unlock(&from_locked);
 	return ret;