fix(ff-a): check receiver's attributes on memory retrieve

Add check on FFA_MEMORY_RETRIEVE ABI to ensure receiver requested
attributes are the same used by Hafnium to map the buffer.
Currently Hafnium maps memory with hardcoded attributes and doesn't
yet support customization.

Change-Id: I808e1eb68c9f714e622ae9a1982b694c0b09174e
Signed-off-by: Federico Recanati <federico.recanati@arm.com>
diff --git a/src/ffa_memory.c b/src/ffa_memory.c
index c51db1d..84b6ae8 100644
--- a/src/ffa_memory.c
+++ b/src/ffa_memory.c
@@ -1345,6 +1345,42 @@
 }
 
 /**
+ * Check that the memory attributes match Hafnium expectations:
+ * Normal Memory, Inner shareable, Write-Back Read-Allocate
+ * Write-Allocate Cacheable.
+ */
+static struct ffa_value ffa_memory_attributes_validate(
+	ffa_memory_access_permissions_t attributes)
+{
+	enum ffa_memory_type memory_type;
+	enum ffa_memory_cacheability cacheability;
+	enum ffa_memory_shareability shareability;
+
+	memory_type = ffa_get_memory_type_attr(attributes);
+	if (memory_type != FFA_MEMORY_NORMAL_MEM) {
+		dlog_verbose("Invalid memory type %#x, expected %#x.\n",
+			     memory_type, FFA_MEMORY_NORMAL_MEM);
+		return ffa_error(FFA_INVALID_PARAMETERS);
+	}
+
+	cacheability = ffa_get_memory_cacheability_attr(attributes);
+	if (cacheability != FFA_MEMORY_CACHE_WRITE_BACK) {
+		dlog_verbose("Invalid cacheability %#x, expected %#x.\n",
+			     cacheability, FFA_MEMORY_CACHE_WRITE_BACK);
+		return ffa_error(FFA_INVALID_PARAMETERS);
+	}
+
+	shareability = ffa_get_memory_shareability_attr(attributes);
+	if (shareability != FFA_MEMORY_INNER_SHAREABLE) {
+		dlog_verbose("Invalid shareability %#x, expected #%x.\n",
+			     shareability, FFA_MEMORY_INNER_SHAREABLE);
+		return ffa_error(FFA_INVALID_PARAMETERS);
+	}
+
+	return (struct ffa_value){.func = FFA_SUCCESS_32};
+}
+
+/**
  * Check that the given `memory_region` represents a valid memory send request
  * of the given `share_func` type, return the clear flag and permissions via the
  * respective output parameters, and update the permissions if necessary.
@@ -1363,10 +1399,7 @@
 	uint32_t constituents_length;
 	enum ffa_data_access data_access;
 	enum ffa_instruction_access instruction_access;
-	ffa_memory_access_permissions_t attributes;
-	enum ffa_memory_type memory_type;
-	enum ffa_memory_cacheability memory_cacheability;
-	enum ffa_memory_shareability memory_shareability;
+	struct ffa_value ret;
 
 	assert(permissions != NULL);
 
@@ -1499,26 +1532,9 @@
 	 * Normal Memory, Inner shareable, Write-Back Read-Allocate
 	 * Write-Allocate Cacheable.
 	 */
-	attributes = memory_region->attributes;
-	memory_type = ffa_get_memory_type_attr(attributes);
-	if (memory_type != FFA_MEMORY_NORMAL_MEM) {
-		dlog_verbose("Invalid memory type %#x, expected %#x.\n",
-			     memory_type, FFA_MEMORY_NORMAL_MEM);
-		return ffa_error(FFA_INVALID_PARAMETERS);
-	}
-
-	memory_cacheability = ffa_get_memory_cacheability_attr(attributes);
-	if (memory_cacheability != FFA_MEMORY_CACHE_WRITE_BACK) {
-		dlog_verbose("Invalid cacheability %#x, expected %#x.\n",
-			     memory_cacheability, FFA_MEMORY_CACHE_WRITE_BACK);
-		return ffa_error(FFA_INVALID_PARAMETERS);
-	}
-
-	memory_shareability = ffa_get_memory_shareability_attr(attributes);
-	if (memory_shareability != FFA_MEMORY_INNER_SHAREABLE) {
-		dlog_verbose("Invalid shareability %#x, expected %#x.\n",
-			     memory_shareability, FFA_MEMORY_INNER_SHAREABLE);
-		return ffa_error(FFA_INVALID_PARAMETERS);
+	ret = ffa_memory_attributes_validate(memory_region->attributes);
+	if (ret.func != FFA_SUCCESS_32) {
+		return ret;
 	}
 
 	return (struct ffa_value){.func = FFA_SUCCESS_32};
@@ -2356,7 +2372,6 @@
 	 * Check permissions from sender against permissions requested by
 	 * receiver.
 	 */
-	/* TODO: Check attributes too. */
 	sent_permissions =
 		memory_region->receivers[0].receiver_permissions.permissions;
 	sent_data_access = ffa_get_data_access_attr(sent_permissions);
@@ -2434,6 +2449,17 @@
 		panic("Got unexpected FFA_INSTRUCTION_ACCESS_RESERVED. Should "
 		      "be checked before this point.");
 	}
+
+	/*
+	 * Ensure receiver's attributes are compatible with how Hafnium maps
+	 * memory: Normal Memory, Inner shareable, Write-Back Read-Allocate
+	 * Write-Allocate Cacheable.
+	 */
+	ret = ffa_memory_attributes_validate(retrieve_request->attributes);
+	if (ret.func != FFA_SUCCESS_32) {
+		goto out;
+	}
+
 	memory_to_attributes = ffa_memory_permissions_to_mode(
 		permissions, share_state->sender_orig_mode);
 	ret = ffa_retrieve_check_update(
diff --git a/test/vmapi/el0_partitions/memory_sharing.c b/test/vmapi/el0_partitions/memory_sharing.c
index d3e4d5f..e08c37d 100644
--- a/test/vmapi/el0_partitions/memory_sharing.c
+++ b/test/vmapi/el0_partitions/memory_sharing.c
@@ -2156,6 +2156,88 @@
 }
 
 /**
+ * Memory can't be shared with arbitrary attributes because Hafnium maps pages
+ * with hardcoded values and doesn't support custom mappings.
+ */
+TEST(memory_sharing, ffa_validate_retrieve_req_attributes)
+{
+	struct ffa_value ret;
+	struct mailbox_buffers mb = set_up_mailbox();
+	uint32_t msg_size;
+	ffa_memory_handle_t handle;
+
+	struct ffa_value (*send_function[])(uint32_t, uint32_t) = {
+		ffa_mem_share,
+		ffa_mem_lend,
+	};
+
+	struct ffa_memory_region_constituent constituents[] = {
+		{.address = (uint64_t)pages, .page_count = 2},
+		{.address = (uint64_t)pages + PAGE_SIZE * 3, .page_count = 1},
+	};
+
+	SERVICE_SELECT(SERVICE_VM1, "ffa_memory_share_fail_invalid_parameters",
+		       mb.send);
+
+	struct {
+		enum ffa_memory_type memory_type;
+		enum ffa_memory_cacheability memory_cacheability;
+		enum ffa_memory_shareability memory_shareability;
+	} invalid_attributes[] = {
+		/* Invalid memory type */
+		{FFA_MEMORY_DEVICE_MEM, FFA_MEMORY_CACHE_WRITE_BACK,
+		 FFA_MEMORY_INNER_SHAREABLE},
+		/* Invalid cacheability */
+		{FFA_MEMORY_NORMAL_MEM, FFA_MEMORY_CACHE_NON_CACHEABLE,
+		 FFA_MEMORY_INNER_SHAREABLE},
+		/* Invalid shareability */
+		{FFA_MEMORY_NORMAL_MEM, FFA_MEMORY_CACHE_WRITE_BACK,
+		 FFA_MEMORY_SHARE_NON_SHAREABLE},
+		{FFA_MEMORY_NORMAL_MEM, FFA_MEMORY_CACHE_WRITE_BACK,
+		 FFA_MEMORY_OUTER_SHAREABLE}};
+
+	for (uint32_t i = 0; i < ARRAY_SIZE(send_function); i++) {
+		/* Prepare memory region, and set all flags */
+		EXPECT_EQ(ffa_memory_region_init(
+				  mb.send, HF_MAILBOX_SIZE, HF_PRIMARY_VM_ID,
+				  SERVICE_VM1, constituents,
+				  ARRAY_SIZE(constituents), 0, 0,
+				  FFA_DATA_ACCESS_RW,
+				  FFA_INSTRUCTION_ACCESS_NOT_SPECIFIED,
+				  FFA_MEMORY_NORMAL_MEM,
+				  FFA_MEMORY_CACHE_WRITE_BACK,
+				  FFA_MEMORY_INNER_SHAREABLE, NULL, &msg_size),
+			  0);
+
+		ret = send_function[i](msg_size, msg_size);
+		EXPECT_EQ(ret.func, FFA_SUCCESS_32);
+
+		handle = ffa_mem_success_handle(ret);
+
+		for (uint32_t j = 0; j < ARRAY_SIZE(invalid_attributes); ++j) {
+			msg_size = ffa_memory_retrieve_request_init(
+				mb.send, handle, HF_PRIMARY_VM_ID, SERVICE_VM1,
+				0, 0, FFA_DATA_ACCESS_RW,
+				FFA_INSTRUCTION_ACCESS_NOT_SPECIFIED,
+				invalid_attributes[j].memory_type,
+				invalid_attributes[j].memory_cacheability,
+				invalid_attributes[j].memory_shareability);
+
+			EXPECT_LE(msg_size, HF_MAILBOX_SIZE);
+
+			EXPECT_EQ(ffa_msg_send(HF_PRIMARY_VM_ID, SERVICE_VM1,
+					       msg_size, 0)
+					  .func,
+				  FFA_SUCCESS_32);
+
+			ffa_run(SERVICE_VM1, 0);
+		}
+
+		EXPECT_EQ(ffa_mem_reclaim(handle, 0).func, FFA_SUCCESS_32);
+	}
+}
+
+/**
  * If memory is shared can't request zeroing of memory at both send and
  * relinquish.
  */
diff --git a/test/vmapi/primary_with_secondaries/memory_sharing.c b/test/vmapi/primary_with_secondaries/memory_sharing.c
index 7b01986..3a31d15 100644
--- a/test/vmapi/primary_with_secondaries/memory_sharing.c
+++ b/test/vmapi/primary_with_secondaries/memory_sharing.c
@@ -2302,6 +2302,88 @@
 }
 
 /**
+ * Memory can't be shared with arbitrary attributes because Hafnium maps pages
+ * with hardcoded values and doesn't support custom mappings.
+ */
+TEST(memory_sharing, ffa_validate_retrieve_req_attributes)
+{
+	struct ffa_value ret;
+	struct mailbox_buffers mb = set_up_mailbox();
+	uint32_t msg_size;
+	ffa_memory_handle_t handle;
+
+	struct ffa_value (*send_function[])(uint32_t, uint32_t) = {
+		ffa_mem_share,
+		ffa_mem_lend,
+	};
+
+	struct ffa_memory_region_constituent constituents[] = {
+		{.address = (uint64_t)pages, .page_count = 2},
+		{.address = (uint64_t)pages + PAGE_SIZE * 3, .page_count = 1},
+	};
+
+	SERVICE_SELECT(SERVICE_VM1, "ffa_memory_share_fail_invalid_parameters",
+		       mb.send);
+
+	struct {
+		enum ffa_memory_type memory_type;
+		enum ffa_memory_cacheability memory_cacheability;
+		enum ffa_memory_shareability memory_shareability;
+	} invalid_attributes[] = {
+		/* Invalid memory type */
+		{FFA_MEMORY_DEVICE_MEM, FFA_MEMORY_CACHE_WRITE_BACK,
+		 FFA_MEMORY_INNER_SHAREABLE},
+		/* Invalid cacheability */
+		{FFA_MEMORY_NORMAL_MEM, FFA_MEMORY_CACHE_NON_CACHEABLE,
+		 FFA_MEMORY_INNER_SHAREABLE},
+		/* Invalid shareability */
+		{FFA_MEMORY_NORMAL_MEM, FFA_MEMORY_CACHE_WRITE_BACK,
+		 FFA_MEMORY_SHARE_NON_SHAREABLE},
+		{FFA_MEMORY_NORMAL_MEM, FFA_MEMORY_CACHE_WRITE_BACK,
+		 FFA_MEMORY_OUTER_SHAREABLE}};
+
+	for (uint32_t i = 0; i < ARRAY_SIZE(send_function); i++) {
+		/* Prepare memory region, and set all flags */
+		EXPECT_EQ(ffa_memory_region_init(
+				  mb.send, HF_MAILBOX_SIZE, HF_PRIMARY_VM_ID,
+				  SERVICE_VM1, constituents,
+				  ARRAY_SIZE(constituents), 0, 0,
+				  FFA_DATA_ACCESS_RW,
+				  FFA_INSTRUCTION_ACCESS_NOT_SPECIFIED,
+				  FFA_MEMORY_NORMAL_MEM,
+				  FFA_MEMORY_CACHE_WRITE_BACK,
+				  FFA_MEMORY_INNER_SHAREABLE, NULL, &msg_size),
+			  0);
+
+		ret = send_function[i](msg_size, msg_size);
+		EXPECT_EQ(ret.func, FFA_SUCCESS_32);
+
+		handle = ffa_mem_success_handle(ret);
+
+		for (uint32_t j = 0; j < ARRAY_SIZE(invalid_attributes); ++j) {
+			msg_size = ffa_memory_retrieve_request_init(
+				mb.send, handle, HF_PRIMARY_VM_ID, SERVICE_VM1,
+				0, 0, FFA_DATA_ACCESS_RW,
+				FFA_INSTRUCTION_ACCESS_NOT_SPECIFIED,
+				invalid_attributes[j].memory_type,
+				invalid_attributes[j].memory_cacheability,
+				invalid_attributes[j].memory_shareability);
+
+			EXPECT_LE(msg_size, HF_MAILBOX_SIZE);
+
+			EXPECT_EQ(ffa_msg_send(HF_PRIMARY_VM_ID, SERVICE_VM1,
+					       msg_size, 0)
+					  .func,
+				  FFA_SUCCESS_32);
+
+			ffa_run(SERVICE_VM1, 0);
+		}
+
+		EXPECT_EQ(ffa_mem_reclaim(handle, 0).func, FFA_SUCCESS_32);
+	}
+}
+
+/**
  * If memory is shared can't request zeroing of memory at both send and
  * relinquish.
  */