mirror of https://lore.kernel.org/lkml/
 help / color / mirror / Atom feed
From: Alexander Graf <graf@amazon.com>
To: <linux-kernel@vger.kernel.org>,
	Greg Kroah-Hartman <gregkh@linuxfoundation.org>,
	Arnd Bergmann <arnd@arndb.de>
Cc: The AWS Nitro Enclaves Team <aws-nitro-enclaves-devel@amazon.com>,
	"Shuah Khan" <skhan@linuxfoundation.org>,
	Randy Dunlap <rdunlap@infradead.org>,
	Marek Szyprowski <m.szyprowski@samsung.com>,
	Robin Murphy <robin.murphy@arm.com>, <linux-doc@vger.kernel.org>,
	<iommu@lists.linux.dev>,
	Andrew Morton <akpm@linux-foundation.org>,
	David Hildenbrand <david@kernel.org>,
	Mike Rapoport <rppt@kernel.org>, <nh-open-source@amazon.com>,
	<linux-mm@kvack.org>, Jonathan Corbet <corbet@lwn.net>
Subject: [PATCH 14/40] nitro_enclaves: Add NE_SET_INFO_PAGE
Date: Wed, 11 Nov 2026 22:56:38 +0000	[thread overview]
Message-ID: <502b33c4d212373d67006e47bf1c4882d9a4ad95.1794393490.git.graf@amazon.com> (raw)
In-Reply-To: <cover.1794300002.git.graf@amazon.com>

Nitro Sandboxes introduce the info page, a page of parent memory that
the device writes and the parent reads, to share various information about
the state of devices inside the slot.

Add the NE_SET_INFO_PAGE ioctl to register a NE_MEMORY_SHARED allocation
as info page. This page is always read-only to user space.

Signed-off-by: Alexander Graf <graf@amazon.com>
---
 drivers/virt/nitro_enclaves/ne_misc_dev.c | 276 +++++++++++++++++++++-
 drivers/virt/nitro_enclaves/ne_misc_dev.h |  35 ++-
 drivers/virt/nitro_enclaves/ne_pci_dev.c  |  25 ++
 drivers/virt/nitro_enclaves/ne_pci_dev.h  |  21 ++
 include/uapi/linux/nitro_enclaves.h       | 106 ++++++++-
 5 files changed, 435 insertions(+), 28 deletions(-)

diff --git a/drivers/virt/nitro_enclaves/ne_misc_dev.c b/drivers/virt/nitro_enclaves/ne_misc_dev.c
index 0bf62f703806e..610d98264e5a1 100644
--- a/drivers/virt/nitro_enclaves/ne_misc_dev.c
+++ b/drivers/virt/nitro_enclaves/ne_misc_dev.c
@@ -123,6 +123,8 @@ static int ne_add_memory_region_ioctl(struct ne_enclave *ne_enclave,
 				      struct ne_add_memory_region_args *args);
 static int ne_alloc_memory_ioctl(struct ne_enclave *ne_enclave,
 				 struct ne_alloc_memory_args *args);
+static int ne_set_info_page_ioctl(struct ne_enclave *ne_enclave,
+				  struct ne_set_info_page_args *args);
 static const struct file_operations ne_fops = {
 	.owner		= THIS_MODULE,
 	.llseek		= noop_llseek,
@@ -2137,6 +2139,39 @@ static long ne_enclave_do_ioctl(struct file *file, unsigned int cmd, unsigned lo
 		return rc;
 	}
 
+	case NE_EA_IOCTL(NE_SET_INFO_PAGE): {
+		struct ne_set_info_page_args __user *uargs = (void __user *)arg;
+		unsigned int usize = _IOC_SIZE(cmd);
+		struct ne_set_info_page_args args;
+		int rc;
+
+		if (usize < offsetofend(struct ne_set_info_page_args, flags))
+			return -EINVAL;
+
+		rc = copy_struct_from_user(&args, sizeof(args), uargs, usize);
+		if (rc)
+			return rc;
+
+		rc = mutex_lock_killable(&ne_enclave->enclave_info_mutex);
+		if (rc)
+			return rc;
+
+		if (ne_enclave->state != NE_STATE_INIT) {
+			dev_err_ratelimited(ne_misc_dev.this_device,
+					    "Enclave is not in init state\n");
+
+			mutex_unlock(&ne_enclave->enclave_info_mutex);
+
+			return -NE_ERR_NOT_IN_INIT_STATE;
+		}
+
+		rc = ne_set_info_page_ioctl(ne_enclave, &args);
+
+		mutex_unlock(&ne_enclave->enclave_info_mutex);
+
+		return rc;
+	}
+
 	default:
 		return -ENOTTY;
 	}
@@ -2344,6 +2379,9 @@ static void ne_enclave_free(struct ne_enclave *ne_enclave)
 	ne_enclave_remove_all_mem_region_entries(ne_enclave);
 	ne_enclave_remove_all_vcpu_id_entries(ne_enclave);
 
+	if (ne_enclave->info_file)
+		fput(ne_enclave->info_file);
+
 	kfree(ne_enclave);
 }
 
@@ -3152,6 +3190,9 @@ static bool ne_contig_supports_mmap(struct file *file)
  * asks for VM_HUGEPAGE because a fault on a VM_PFNMAP mapping without it only
  * reaches ->huge_fault where the transparent_hugepage policy is "always".
  *
+ * An allocation that is a slot's info page is mapped without write access, and
+ * without the right to gain it with mprotect().
+ *
  * Return: 0 on success, negative return value on failure.
  */
 static int ne_contig_mmap_prepare(struct vm_area_desc *desc)
@@ -3177,6 +3218,13 @@ static int ne_contig_mmap_prepare(struct vm_area_desc *desc)
 	if (offset > mem->size || size > mem->size - offset)
 		return -EINVAL;
 
+	if (READ_ONCE(mem->read_only)) {
+		if (vma_desc_test(desc, VMA_WRITE_BIT))
+			return -EACCES;
+
+		vma_desc_clear_flags(desc, VMA_MAYWRITE_BIT);
+	}
+
 	desc->vm_ops = &ne_contig_vm_ops;
 	vma_desc_set_flags(desc, VMA_IO_BIT, VMA_PFNMAP_BIT, VMA_DONTEXPAND_BIT,
 			   VMA_DONTDUMP_BIT, VMA_DONTCOPY_BIT, VMA_HUGEPAGE_BIT);
@@ -3242,11 +3290,12 @@ static int ne_write_span(void *arg, struct page *page, u64 off, u64 len)
  * refusal is -EFBIG, the same as generic_write_check_limits() gives for a write
  * beyond s_maxbytes. There is no RLIMIT_FSIZE in it, so no SIGXFSZ either.
  *
- * Donating an allocation closes this path, and @lock is what makes the two
- * orderly. It is taken here and in ne_add_memory_region_ioctl() and nowhere
- * else; setting a mapping up and faulting one in both read only fields that
- * never change, so nothing takes it under mmap_write_lock or in a fault, and
- * copying from user space under it cannot invert against the fault path.
+ * Donating an allocation closes this path, and so does making it an info page.
+ * This function, ne_add_memory_region_ioctl(), ne_contig_make_read_only() and
+ * ne_contig_allow_writes() take @lock, which orders those state changes against
+ * a write. Setting a mapping up and faulting one in do not take it, so nothing
+ * takes it under mmap_write_lock or in a fault, and copying from user space
+ * under it cannot invert against the fault path.
  *
  * Return: the number of bytes written, or a negative return value on failure.
  */
@@ -3266,7 +3315,7 @@ static ssize_t ne_contig_write_iter(struct kiocb *iocb, struct iov_iter *from)
 
 	guard(mutex)(&mem->lock);
 
-	if (mem->donated)
+	if (mem->donated || mem->read_only)
 		return -EPERM;
 
 	rc = ne_walk_allocs(mem, pos, count, NE_WRITE_SPAN_PAGES, ne_write_span,
@@ -3281,6 +3330,60 @@ static ssize_t ne_contig_write_iter(struct kiocb *iocb, struct iov_iter *from)
 	return rc;
 }
 
+/**
+ * ne_contig_make_read_only() - Take write access to an allocation away from
+ *				user space.
+ * @file:	The allocation's file.
+ *
+ * From here on a write to the descriptor fails and a new mapping has no write
+ * access, so only the device and the kernel change the memory. The descriptor
+ * is open for writing, so a mapping made before could be made writable with
+ * mprotect(), and one that exists makes this fail.
+ *
+ * mmap_region() raises i_mmap_writable with full ordering before it calls
+ * ne_contig_mmap_prepare() for a mapping that may write, and the mapping keeps
+ * it raised for as long as it exists. @read_only is set before the count is
+ * read, so a mapping that ne_contig_mmap_prepare() set up without seeing it is
+ * counted here. A mapping that is being set up as this runs counts too, even
+ * one that ends up without write access, so a caller sees -EBUSY for it and
+ * can try again.
+ *
+ * Return: 0 on success, -EBUSY if the allocation is mapped.
+ */
+static int ne_contig_make_read_only(struct file *file)
+{
+	struct ne_contig_mem *mem = file->private_data;
+
+	guard(mutex)(&mem->lock);
+
+	WRITE_ONCE(mem->read_only, true);
+
+	/* Pairs with the full ordering of mapping_map_writable(). */
+	smp_mb();
+
+	if (mapping_writably_mapped(file->f_mapping)) {
+		WRITE_ONCE(mem->read_only, false);
+
+		return -EBUSY;
+	}
+
+	return 0;
+}
+
+/**
+ * ne_contig_allow_writes() - Give write access to an allocation back to user
+ *			      space.
+ * @file:	The allocation's file.
+ */
+static void ne_contig_allow_writes(struct file *file)
+{
+	struct ne_contig_mem *mem = file->private_data;
+
+	guard(mutex)(&mem->lock);
+
+	WRITE_ONCE(mem->read_only, false);
+}
+
 /**
  * ne_contig_release() - Free an allocation once nothing holds it any more.
  * @inode:	The allocation's inode, unused.
@@ -3612,6 +3715,13 @@ static int ne_add_memory_region_ioctl(struct ne_enclave *ne_enclave,
 		return -EINVAL;
 	}
 
+	if (fd_file(f) == ne_enclave->info_file) {
+		dev_err_ratelimited(ne_misc_dev.this_device,
+				    "The allocation is the enclave's info page\n");
+
+		return -NE_ERR_MEM_REGION_ALREADY_USED;
+	}
+
 	list_for_each_entry(held, &ne_enclave->contig_regions_list, list) {
 		if (held->file == fd_file(f)) {
 			dev_err_ratelimited(ne_misc_dev.this_device,
@@ -3714,6 +3824,160 @@ static int ne_add_memory_region_ioctl(struct ne_enclave *ne_enclave,
 	return rc;
 }
 
+/**
+ * ne_set_info_page_ioctl() - Give the slot associated with the current enclave
+ *			      the info page.
+ * @ne_enclave:	Private data associated with the current enclave.
+ * @args:	Descriptor for the allocation, and flags.
+ *
+ * The device writes the info page, so the run behind it has to stay where the
+ * device was told it is for as long as the enclave lives. The runs belong to the
+ * file, so a reference on it keeps them whatever user space does with the
+ * descriptor and its mappings, and ne_enclave_free() drops that reference
+ * after SLOT_FREE and after the enclave's memory regions. The reference is taken
+ * before the request, since a request that timed out may still be completed by
+ * the device. A rejected request drops it again.
+ *
+ * Only the device writes the page, and the parent reads back from it what the
+ * device reports about the slot, so user space loses write access to the
+ * allocation before the device is told about it and gets it back only if the
+ * device rejects the request.
+ *
+ * Context: Process context. This function is called with the ne_enclave mutex held.
+ * Return:
+ * * 0 on success.
+ * * Negative return value on failure.
+ * * -ETIMEDOUT if the device did not answer, in which case the slot keeps the
+ *   allocation until the enclave goes away.
+ */
+static int ne_set_info_page_ioctl(struct ne_enclave *ne_enclave,
+				  struct ne_set_info_page_args *args)
+{
+	struct ne_pci_dev_cmd_reply cmd_reply = {};
+	struct slot_add_info_page_req req = {};
+	struct pci_dev *pdev = ne_devs.ne_pci_dev->pdev;
+	struct ne_contig_mem *mem = NULL;
+	int rc = -EINVAL;
+
+	if (ne_enclave->mm != current->mm)
+		return -EIO;
+
+	if (args->flags) {
+		dev_err_ratelimited(ne_misc_dev.this_device,
+				    "Incorrect flag for info page\n");
+
+		return -NE_ERR_INVALID_FLAG_VALUE;
+	}
+
+	if (ne_enclave->info_file) {
+		dev_err_ratelimited(ne_misc_dev.this_device,
+				    "The slot already has an info page\n");
+
+		return -EEXIST;
+	}
+
+	CLASS(fd, f)(args->fd);
+
+	if (fd_empty(f))
+		return -EBADF;
+
+	if (fd_file(f)->f_op != &ne_contig_fops) {
+		dev_err_ratelimited(ne_misc_dev.this_device,
+				    "Not an enclave memory allocation\n");
+
+		return -EINVAL;
+	}
+
+	mem = fd_file(f)->private_data;
+
+	if (mem->enclave_id != ne_enclave->id) {
+		dev_err_ratelimited(ne_misc_dev.this_device,
+				    "Allocation was made for another enclave\n");
+
+		return -EINVAL;
+	}
+
+	if (!mem->shared) {
+		dev_err_ratelimited(ne_misc_dev.this_device,
+				    "The info page allocation is not shared\n");
+
+		return -EINVAL;
+	}
+
+	/*
+	 * The device takes one address and one length, so an allocation the
+	 * allocator had to satisfy in several runs has no single address to
+	 * name. A request of NE_MIN_MEM_REGION_SIZE cannot be split, since the
+	 * allocator has nothing smaller to fall back to.
+	 */
+	if (mem->nr_allocs != 1) {
+		dev_err_ratelimited(ne_misc_dev.this_device,
+				    "The info page allocation is not one run\n");
+
+		return -EINVAL;
+	}
+
+	/*
+	 * An allocation the enclave already has as memory is not available to
+	 * be its info page as well, since the device would be handed the same
+	 * run twice in two roles. @donated is only ever set from
+	 * ne_add_memory_region_ioctl(), which runs under the same enclave mutex
+	 * as this function for the one enclave the allocation was made for, so
+	 * the read needs no lock of its own.
+	 */
+	if (mem->donated) {
+		dev_err_ratelimited(ne_misc_dev.this_device,
+				    "The info page allocation was donated\n");
+
+		return -NE_ERR_MEM_REGION_ALREADY_USED;
+	}
+
+	rc = ne_contig_make_read_only(fd_file(f));
+	if (rc < 0) {
+		dev_err_ratelimited(ne_misc_dev.this_device,
+				    "The info page allocation is mapped\n");
+
+		return rc;
+	}
+
+	req.slot_uid = ne_enclave->slot_uid;
+	req.info_phys = page_to_phys(mem->allocs[0].page);
+	req.info_size = mem->size;
+
+	/*
+	 * Take the reference before the request, so that the run stays where the
+	 * device is told it is from the moment the device is told, as the memory
+	 * region path does.
+	 */
+	ne_enclave->info_file = get_file(fd_file(f));
+
+	rc = ne_do_request(pdev, SLOT_ADD_INFO_PAGE,
+			   &req, sizeof(req), &cmd_reply, sizeof(cmd_reply));
+	if (rc < 0) {
+		dev_err_ratelimited(ne_misc_dev.this_device,
+				    "Error in slot add info page [rc=%d]\n", rc);
+
+		/*
+		 * A request the device never answered was submitted with the
+		 * run's physical address in it, so the device may write the page
+		 * yet. Keep the reference and let ne_enclave_free() drop it
+		 * after SLOT_FREE. Every other error means the device either
+		 * never saw the request or answered to reject it, and the run is
+		 * the parent's again. ne_request_unanswered() tells the two
+		 * apart.
+		 */
+		if (!ne_request_unanswered(rc)) {
+			ne_contig_allow_writes(ne_enclave->info_file);
+			fput(ne_enclave->info_file);
+			ne_enclave->info_file = NULL;
+		}
+
+		return rc;
+	}
+
+	return 0;
+}
+
 #if defined(CONFIG_NITRO_ENCLAVES_MISC_DEV_TEST)
 #include "ne_misc_dev_test.c"
 #endif
diff --git a/drivers/virt/nitro_enclaves/ne_misc_dev.h b/drivers/virt/nitro_enclaves/ne_misc_dev.h
index 2502cca3f3f64..d0d1973bfbe9d 100644
--- a/drivers/virt/nitro_enclaves/ne_misc_dev.h
+++ b/drivers/virt/nitro_enclaves/ne_misc_dev.h
@@ -38,27 +38,37 @@ struct ne_contig_alloc {
  * @allocs:	Contiguous runs backing the allocation, ordered by offset.
  * @nr_allocs:	Number of runs in @allocs.
  * @size:	Size of the allocation in bytes, mirrored in i_size.
- * @shared:	Whether the allocation may be mapped by the caller.
+ * @shared:	Whether the caller asked for a shared allocation. A shared one may
+ *		be mapped by the caller and may be a slot's info page; a private
+ *		one is neither.
  * @enclave_id:	Id of the enclave the allocation was made for.
  * @mm:		The mm charged for the pinned pages, held via mmgrab().
  * @nr_pinned:	Pages charged to @mm->pinned_vm.
- * @lock:	Serialises a write against the donation that ends writing.
+ * @lock:	Serialises a write against the donation or the info page that
+ *		ends writing.
  * @donated:	Whether the allocation has been given to its enclave.
+ * @read_only:	Whether user space has lost write access to the allocation,
+ *		which it does when the allocation becomes a slot's info page.
  *
  * The runs hang off the file, so that closing the last descriptor does not free
  * memory an enclave is still using: a donated region holds the file with
- * get_file(). That is also what keeps a mapping's page table entries from
- * outliving the runs, since a VMA holds vm_file for as long as it exists.
+ * get_file(), and so does the enclave the allocation is the info page of. That
+ * is also what keeps a mapping's page table entries from outliving the runs,
+ * since a VMA holds vm_file for as long as it exists.
  *
  * The array is built before the file is published and not mutated afterwards, so
  * any holder of a reference can read it without a lock of its own, which is what
  * lets a page fault resolve an offset to a run. @shared and @size are set in the
- * same place and are read the same way. @donated is the one field that changes
- * after publication, and @lock covers it.
+ * same place and are read the same way. @donated and @read_only are the fields
+ * that change after publication. @lock covers both on the write path;
+ * ne_set_info_page_ioctl() reads @donated under the enclave mutex its writer also
+ * holds, and ne_contig_mmap_prepare() reads @read_only without a lock, which
+ * ne_contig_make_read_only() orders against.
  *
  * @enclave_id is compared, never dereferenced, and the counter it comes from is
- * 64 bits wide, so an allocation cannot be donated to a later enclave that
- * happens to have been placed at the address of the one it was made for.
+ * 64 bits wide, so an allocation cannot be donated to, or become the info page
+ * of, a later enclave that happens to have been placed at the address of the one
+ * it was made for.
  */
 struct ne_contig_mem {
 	struct ne_contig_alloc	*allocs;
@@ -68,9 +78,10 @@ struct ne_contig_mem {
 	u64			enclave_id;
 	struct mm_struct	*mm;
 	unsigned long		nr_pinned;
-	/* Serialises a write against the donation that ends writing. */
+	/* Serialises a write against what ends writing. */
 	struct mutex		lock;
 	bool			donated;
+	bool			read_only;
 };
 
 /**
@@ -116,6 +127,11 @@ struct ne_mem_region {
  * @id:				Id of this enclave, unique for the lifetime of the
  *				driver, recorded in an %NE_ALLOC_MEMORY allocation
  *				as the enclave it was made for.
+ * @info_file:			File of the %NE_ALLOC_MEMORY allocation the slot
+ *				was given as its info page, held with get_file()
+ *				so that the device keeps the page after user space
+ *				has closed the descriptor. NULL until
+ *				%NE_SET_INFO_PAGE is called.
  * @max_mem_regions:		The maximum number of memory regions that can be
  *				handled by the hypervisor.
  * @mem_regions_list:		Enclave user space memory regions list.
@@ -146,6 +162,7 @@ struct ne_enclave {
 	wait_queue_head_t	eventq;
 	bool			has_event;
 	u64			id;
+	struct file		*info_file;
 	u64			max_mem_regions;
 	struct list_head	mem_regions_list;
 	u64			mem_size;
diff --git a/drivers/virt/nitro_enclaves/ne_pci_dev.c b/drivers/virt/nitro_enclaves/ne_pci_dev.c
index f258e04ee9e47..a07dbc6d8a9b7 100644
--- a/drivers/virt/nitro_enclaves/ne_pci_dev.c
+++ b/drivers/virt/nitro_enclaves/ne_pci_dev.c
@@ -128,6 +128,31 @@ static int ne_wait_for_reply(struct pci_dev *pdev, unsigned int timeout_msecs)
 	return 0;
 }
 
+/**
+ * ne_request_unanswered() - Whether a failure left a submitted request in
+ *			     flight.
+ * @rc:		Failure a command request came back with.
+ *
+ * A request the device never answered was submitted with whatever the caller
+ * put in it, and there is nothing to cancel it with, so a resource whose
+ * address is in it may still be written by the device. A caller that has to
+ * decide who owns such a resource asks here instead of naming an errno of its
+ * own.
+ *
+ * Every code the wait for a reply can fail a submitted request with belongs
+ * here. ne_wait_for_reply() times out, so -ETIMEDOUT is named. A code raised
+ * before ne_submit_request(), and a code the device answered with, do not:
+ * nothing is in flight in either case. A
+ * device that answers one of the codes named here of its own accord lands here
+ * too and costs one held resource, which is the side to be wrong on.
+ *
+ * Return: true if the request may still be in flight.
+ */
+bool ne_request_unanswered(int rc)
+{
+	return rc == -ETIMEDOUT;
+}
+
 /**
  * ne_cmd_frees_slot() - Whether a command is one of the two that free a slot.
  * @cmd_type:	Command type of the request.
diff --git a/drivers/virt/nitro_enclaves/ne_pci_dev.h b/drivers/virt/nitro_enclaves/ne_pci_dev.h
index b0957e2606ec3..4f670c53dd3c0 100644
--- a/drivers/virt/nitro_enclaves/ne_pci_dev.h
+++ b/drivers/virt/nitro_enclaves/ne_pci_dev.h
@@ -123,6 +123,8 @@
  * @NEXT_SLOT:			Get the next slot in the list of allocated slots.
  * @SLOT_INFO:			Get the info for a slot e.g. slot uid, vCPUs count.
  * @SLOT_ADD_BULK_VCPUS:	Add a number of vCPUs, not providing CPU ids.
+ * @SLOT_ADD_INFO_PAGE:		Give a slot the info page, a memory region the
+ *				device writes.
  * @MAX_CMD:			A gatekeeper for max possible command type.
  */
 enum ne_pci_dev_cmd_type {
@@ -138,6 +140,8 @@ enum ne_pci_dev_cmd_type {
 	NEXT_SLOT		= 9,
 	SLOT_INFO		= 10,
 	SLOT_ADD_BULK_VCPUS	= 11,
+	/* 12 is a command this driver does not implement. */
+	SLOT_ADD_INFO_PAGE	= 13,
 	MAX_CMD,
 };
 
@@ -266,6 +270,20 @@ struct slot_add_bulk_vcpus_req {
 	u64	nr_vcpus;
 };
 
+/**
+ * struct slot_add_info_page_req - SLOT_ADD_INFO_PAGE request.
+ * @slot_uid:	Slot unique id mapped to the slot to give the info page to.
+ * @flags:	Flags for the info page. Zero is the only value the driver sends.
+ * @info_phys:	Physical address of the info page.
+ * @info_size:	Size, in bytes, of the info page.
+ */
+struct slot_add_info_page_req {
+	u64	slot_uid;
+	u64	flags;
+	u64	info_phys;
+	u64	info_size;
+};
+
 /**
  * struct ne_pci_dev_cmd_reply - NE PCI device command reply.
  * @rc :		Return code of the logic that processed the request.
@@ -327,6 +345,9 @@ struct ne_pci_dev {
 	struct pci_dev		*pdev;
 };
 
+/* Whether a failure left a submitted request in flight. */
+bool ne_request_unanswered(int rc);
+
 /**
  * ne_do_request() - Submit command request to the PCI device based on the command
  *		     type and retrieve the associated reply.
diff --git a/include/uapi/linux/nitro_enclaves.h b/include/uapi/linux/nitro_enclaves.h
index 6dd00c41c07d5..4f604712983f1 100644
--- a/include/uapi/linux/nitro_enclaves.h
+++ b/include/uapi/linux/nitro_enclaves.h
@@ -287,13 +287,15 @@ struct ne_alloc_memory_args {
 
 /**
  * NE_ALLOC_MEMORY - The command is used to allocate physically contiguous
- *		     memory to give to an enclave with %NE_ADD_MEMORY_REGION,
- *		     drawn from one host NUMA node. The argument is input only;
+ *		     memory to give to an enclave with %NE_ADD_MEMORY_REGION or
+ *		     to provide to the device with %NE_SET_INFO_PAGE, drawn from
+ *		     one host NUMA node. The argument is input only;
  *		     on success the return value is a file descriptor for the
  *		     allocation.
  *		     Writing to the descriptor places bytes in the allocation,
  *		     which is how an enclave image gets there, and stops
- *		     working once the allocation has been donated.
+ *		     working once the allocation has been donated or provided
+ *		     with %NE_SET_INFO_PAGE.
  *		     O_APPEND has no effect. There is no read operation, so
  *		     read() and pread() both fail with EINVAL and a caller that
  *		     wants to check what it wrote keeps its own copy.
@@ -308,16 +310,18 @@ struct ne_alloc_memory_args {
  *		     allocation over, including once the allocation has been
  *		     donated and writing to the descriptor has stopped working.
  *		     Such a mapping is readable too, which is the only way to
- *		     read allocated memory back. The mapping is not inherited
- *		     across fork(); a child that needs one maps the descriptor
- *		     itself.
+ *		     read allocated memory back, and is how a parent reads an
+ *		     info page provided with %NE_SET_INFO_PAGE, which it maps
+ *		     without %PROT_WRITE. The mapping is not inherited across
+ *		     fork(); a child that needs one maps the descriptor itself.
  *		     Donating does not take a mapping away, so a parent that
  *		     mapped a shared allocation can still write to it through
  *		     that mapping afterwards.
  *		     The allocation lives until the last of the descriptor, its
- *		     mappings and the enclave regions donated from it is gone,
- *		     so a caller can close the descriptor as soon as it has
- *		     donated.
+ *		     mappings, the enclave regions donated from it and the
+ *		     enclave it is the info page of is gone, so a caller can
+ *		     close the descriptor as soon as it has donated it or
+ *		     provided it to the device.
  *		     The ioctl can be invoked on the enclave fd, before an
  *		     enclave is started.
  *
@@ -351,7 +355,8 @@ struct ne_alloc_memory_args {
  *
  * Writing to the descriptor sets errno to:
  * * EPERM			- The allocation has been donated with
- *				  %NE_ADD_MEMORY_REGION.
+ *				  %NE_ADD_MEMORY_REGION or provided with
+ *				  %NE_SET_INFO_PAGE.
  * * EFBIG			- The write does not fit inside the allocation.
  * * EFAULT			- Part of the buffer is not readable. Any bytes
  *				  copied before that are reported as a short
@@ -361,6 +366,9 @@ struct ne_alloc_memory_args {
  *
  * An mmap() of the descriptor sets errno to:
  * * ENODEV			- The allocation is %NE_MEMORY_PRIVATE.
+ * * EACCES			- The mapping asks for %PROT_WRITE and the
+ *				  allocation has been provided with
+ *				  %NE_SET_INFO_PAGE.
  * * EINVAL			- The mapping is %MAP_PRIVATE, or its offset or
  *				  length is not a multiple of 2 MiB, or it does
  *				  not fit inside the allocation.
@@ -388,7 +396,8 @@ struct ne_add_memory_region_args {
  *			  contiguous run it is made of, or fewer where runs turn
  *			  out to be physically adjacent. The argument is input
  *			  only. The allocation has to have been made for this
- *			  enclave, and can be given away once.
+ *			  enclave, cannot be its info page, and can be given away
+ *			  once.
  *			  Writing to the descriptor stops working here, so an
  *			  enclave image goes in before this call. The allocation
  *			  has to be %NE_MEMORY_PRIVATE, since memory the parent
@@ -425,13 +434,82 @@ struct ne_add_memory_region_args {
  *					  before being started).
  * * NE_ERR_INVALID_FLAG_VALUE		- The value of the provided flag is invalid.
  * * NE_ERR_MEM_REGION_ALREADY_USED	- The allocation was already given to the
- *					  enclave.
+ *					  enclave, or is its info page.
  * * NE_ERR_MEM_MAX_REGIONS		- The number of memory regions set for the
  *					  enclave reached maximum.
  * * Error codes from the NE PCI device request.
  */
 #define NE_ADD_MEMORY_REGION		_IOW(0xAE, 0x2F, struct ne_add_memory_region_args)
 
+/**
+ * struct ne_set_info_page_args - Allocation to provide to the device (in).
+ * @fd:		Descriptor from %NE_ALLOC_MEMORY, made for this enclave, asked
+ *		for as %NE_MEMORY_SHARED, and made of one contiguous run.
+ * @flags:	Must be zero.
+ */
+struct ne_set_info_page_args {
+	__s32	fd;
+	__u32	flags;
+};
+
+/**
+ * NE_SET_INFO_PAGE - The command is used to give an enclave's slot the info
+ *		      page, a %NE_ALLOC_MEMORY allocation the device writes. The
+ *		      argument is input only. The allocation has to be
+ *		      %NE_MEMORY_SHARED, since a mapping is the only way for
+ *		      the parent to read an allocation back. It also has to be
+ *		      one contiguous run, since the device is given one address
+ *		      and one length for it, and a request of 2 MiB is the one
+ *		      size the allocator cannot come back from in several runs.
+ *		      The info page is not enclave memory: it does not count
+ *		      towards the memory regions %NE_ADD_MEMORY_REGION fills.
+ *		      Only the device writes the page, so from this call on the
+ *		      allocation is read-only to user space: writing to the
+ *		      descriptor fails, and the descriptor is mapped without
+ *		      %PROT_WRITE. A mapping made before the call could still be
+ *		      made writable, so the call fails while one exists, and a
+ *		      caller maps the page after the call. A call the device
+ *		      rejects leaves the allocation writable again.
+ *		      A slot takes one info page, and an allocation is either an
+ *		      enclave's memory or its info page. The driver holds the
+ *		      allocation until the enclave is gone, so closing the
+ *		      descriptor does not free the run the device was given.
+ *		      The ioctl can be invoked on the enclave fd, before an
+ *		      enclave is started.
+ *
+ * Context: Process context.
+ * Return:
+ * * 0					- Logic successfully completed.
+ * * -1					- There was a failure in the ioctl logic.
+ * On failure, errno is set to:
+ * * ENODEV				- The NE PCI device is not available.
+ * * EFAULT				- Argument read failure.
+ * * EBADF				- @fd is not an open file descriptor.
+ * * EINVAL				- The argument is shorter than @flags, or @fd
+ *					  is not a descriptor from %NE_ALLOC_MEMORY,
+ *					  or the allocation was made for another
+ *					  enclave, or it is %NE_MEMORY_PRIVATE, or it
+ *					  is made of more than one contiguous run.
+ * * EIO				- Current task mm is not the same as the one
+ *					  that created the enclave.
+ * * EEXIST				- The slot already has an info page.
+ * * EBUSY				- The allocation has a mapping made before the
+ *					  call, or one is being set up.
+ * * E2BIG				- The argument is longer than this kernel knows
+ *					  about and the excess is not all zero.
+ * * NE_ERR_NOT_IN_INIT_STATE		- The enclave is not in init state (init =
+ *					  before being started).
+ * * NE_ERR_INVALID_FLAG_VALUE		- The value of the provided flag is invalid.
+ * * NE_ERR_MEM_REGION_ALREADY_USED	- The allocation was already given to the
+ *					  enclave with %NE_ADD_MEMORY_REGION.
+ * * ETIMEDOUT				- The device did not answer. The slot keeps
+ *					  the allocation, read-only, until the
+ *					  enclave is gone, since the device may
+ *					  still write the page.
+ * * Error codes from the NE PCI device request.
+ */
+#define NE_SET_INFO_PAGE		_IOW(0xAE, 0x25, struct ne_set_info_page_args)
+
 /**
  * NE_START_ENCLAVE - The command is used to trigger enclave start after the
  *		      enclave resources, such as memory and CPU, have been set.
@@ -775,7 +853,9 @@ struct ne_user_memory_region {
  *		      it has been donated. It costs page tables for the part of
  *		      it a caller touches, and it leaves the memory reachable
  *		      from the parent for as long as it lives, so an allocation
- *		      that is only being handed over does not ask for one.
+ *		      that is only being handed over does not ask for one. It is
+ *		      also what an allocation given to %NE_SET_INFO_PAGE has to
+ *		      be.
  */
 #define NE_MEMORY_SHARED		(0x01)
 
-- 
2.47.1


  parent reply	other threads:[~2026-11-11 22:56 UTC|newest]

Thread overview: 41+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-11-11 22:41 [PATCH 00/40] nitro_enclaves: Add support for Nitro Sandboxes Alexander Graf
2026-11-11 22:41 ` [PATCH 01/40] nitro_enclaves: Allow the CPU pool to span NUMA nodes Alexander Graf
2026-11-11 22:41 ` [PATCH 02/40] nitro_enclaves: Add NE_ADD_ANY_VCPU Alexander Graf
2026-11-11 22:41 ` [PATCH 03/40] nitro_enclaves: Expose CPU pool state under sysfs Alexander Graf
2026-11-11 22:41 ` [PATCH 04/40] nitro_enclaves: Document multi-NUMA CPU pools Alexander Graf
2026-11-11 22:41 ` [PATCH 05/40] dma-contiguous: add dma_get_numa_cma_area() accessor Alexander Graf
2026-11-11 22:41 ` [PATCH 06/40] nitro_enclaves: Add NE_ALLOC_MEMORY ioctl Alexander Graf
2026-11-11 22:41 ` [PATCH 07/40] nitro_enclaves: Add NE_MEMORY_SHARED Alexander Graf
2026-11-11 22:41 ` [PATCH 08/40] nitro_enclaves: Add NE_ADD_MEMORY_REGION Alexander Graf
2026-11-11 22:41 ` [PATCH 09/40] nitro_enclaves: Document the memory pool Alexander Graf
2026-11-11 22:41 ` [PATCH 10/40] nitro_enclaves: Add NE_CREATE_VM2 with a launch flag word Alexander Graf
2026-11-11 22:41 ` [PATCH 11/40] nitro_enclaves: Add NE_ENCLAVE_PCIE_MODE launch flag Alexander Graf
2026-11-11 22:41 ` [PATCH 12/40] nitro_enclaves: Name the device's error codes in the UAPI header Alexander Graf
2026-11-11 22:41 ` [PATCH 13/40] nitro_enclaves: Log a reached device limit at info level Alexander Graf
2026-11-11 22:56 ` Alexander Graf [this message]
2026-11-11 22:59 ` [PATCH 15/40] nitro_enclaves: Document the info page in the memory pool Alexander Graf
2026-11-11 22:59 ` [PATCH 16/40] nitro_enclaves: Turn the command reply into a per-command union Alexander Graf
2026-11-11 22:59 ` [PATCH 17/40] nitro_enclaves: Add the slot pool donation commands Alexander Graf
2026-11-11 22:59 ` [PATCH 18/40] nitro_enclaves: Donate memory for the slot pool Alexander Graf
2026-11-11 22:59 ` [PATCH 19/40] nitro_enclaves: Add the slot pool growth commands Alexander Graf
2026-11-11 22:59 ` [PATCH 20/40] nitro_enclaves: Add a helper to grow the slot pool Alexander Graf
2026-11-11 22:59 ` [PATCH 21/40] nitro_enclaves: Document " Alexander Graf
2026-11-11 22:59 ` [PATCH 22/40] nitro_enclaves: Expose slot pool state under sysfs Alexander Graf
2026-11-11 22:59 ` [PATCH 23/40] nitro_enclaves: Add the device add command Alexander Graf
2026-11-11 22:59 ` [PATCH 24/40] nitro_enclaves: Add NE_ADD_DEVICE Alexander Graf
2026-11-11 22:59 ` [PATCH 25/40] nitro_enclaves: Read the interrupt multiplexer geometry at probe Alexander Graf
2026-11-11 22:59 ` [PATCH 26/40] nitro_enclaves: Donate a bitmap page per multiplexed vector Alexander Graf
2026-11-11 22:59 ` [PATCH 27/40] nitro_enclaves: Dispatch multiplexed interrupts to per-queue bindings Alexander Graf
2026-11-11 22:59 ` [PATCH 28/40] nitro_enclaves: Add NE_SET_NOTIFY_EVENTFD Alexander Graf
2026-11-11 22:59 ` [PATCH 29/40] nitro_enclaves: Report the device's info notification Alexander Graf
2026-11-11 22:59 ` [PATCH 30/40] nitro_enclaves: Count multiplexed interrupts under debugfs Alexander Graf
2026-11-11 22:59 ` [PATCH 31/40] nitro_enclaves: Map the doorbell window at probe Alexander Graf
2026-11-11 22:59 ` [PATCH 32/40] nitro_enclaves: Add NE_SET_VRING_CALL Alexander Graf
2026-11-11 22:59 ` [PATCH 33/40] nitro_enclaves: Assert the layout of the ioctl argument structs Alexander Graf
2026-11-11 22:59 ` [PATCH 34/40] nitro_enclaves: Pass on the Nitro Sandbox launch flags Alexander Graf
2026-11-11 22:59 ` [PATCH 35/40] nitro_enclaves: Add NE_ENCLAVE_DIRECT_MODE Alexander Graf
2026-11-11 22:59 ` [PATCH 36/40] nitro_enclaves: Add NE_ENCLAVE_PCIE_HOTPLUG_MODE Alexander Graf
2026-11-11 22:59 ` [PATCH 37/40] nitro_enclaves: Add NE_ENCLAVE_CPU_OVERCOMMIT_MODE Alexander Graf
2026-11-11 22:59 ` [PATCH 38/40] nitro_enclaves: Add NE_GET_CAPS Alexander Graf
2026-11-11 22:59 ` [PATCH 39/40] nitro_enclaves: Add NE_UPDATE_DEVICE_CONFIG Alexander Graf
2026-11-11 22:59 ` [PATCH 40/40] nitro_enclaves: Document the driver side of Nitro Sandboxes Alexander Graf

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=502b33c4d212373d67006e47bf1c4882d9a4ad95.1794393490.git.graf@amazon.com \
    --to=graf@amazon.com \
    --cc=akpm@linux-foundation.org \
    --cc=arnd@arndb.de \
    --cc=aws-nitro-enclaves-devel@amazon.com \
    --cc=corbet@lwn.net \
    --cc=david@kernel.org \
    --cc=gregkh@linuxfoundation.org \
    --cc=iommu@lists.linux.dev \
    --cc=linux-doc@vger.kernel.org \
    --cc=linux-kernel@vger.kernel.org \
    --cc=linux-mm@kvack.org \
    --cc=m.szyprowski@samsung.com \
    --cc=nh-open-source@amazon.com \
    --cc=rdunlap@infradead.org \
    --cc=robin.murphy@arm.com \
    --cc=rppt@kernel.org \
    --cc=skhan@linuxfoundation.org \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox

all inboxes | Powered by JetHome®