From: Alexander Graf <graf@amazon.com>
To: <linux-kernel@vger.kernel.org>,
Greg Kroah-Hartman <gregkh@linuxfoundation.org>
Cc: Arnd Bergmann <arnd@arndb.de>, <nh-open-source@amazon.com>,
"The AWS Nitro Enclaves Team"
<aws-nitro-enclaves-devel@amazon.com>
Subject: [PATCH 07/19] nitro_enclaves: Wait for file operations before unbinding the PCI device
Date: Wed, 11 Nov 2026 13:49:56 +0000 [thread overview]
Message-ID: <5f80be8bc20324938d6455407afed074a0620253.1794393487.git.graf@amazon.com> (raw)
In-Reply-To: <cover.1794300000.git.graf@amazon.com>
User space may hold enclave fds and /dev/nitro_enclaves fds across a
sysfs unbind or a hot-unplug of the NE PCI device. Their ioctls and
their release then use ne_devs.ne_pci_dev and ne_misc_dev.this_device
after ne_pci_remove() freed both, or oops on the NULL pointer it
leaves behind.
Add ne_devs.lock, a rw_semaphore that probe and remove hold for writing
and the file operations hold for reading, so that remove waits for the
ones in flight. A release or a remove can hold it for minutes while the
device frees a slot, so the ioctls take it with down_read_killable().
Remove then marks every enclave detached, which wakes its poll like an
out-of-band event, and an ioctl on a detached enclave, or on the misc
device while no device is bound, fails with -ENODEV. A detached enclave
keeps its memory and CPUs on release, because the device that could
confirm that they are free is gone.
A second NE PCI function would overwrite ne_devs.ne_pci_dev at probe, so
refuse it with -EBUSY.
Kiro, an AI coding assistant, found this while checking device removal
with enclave fds still open.
Fixes: 38907e124088 ("nitro_enclaves: Add logic for creating an enclave VM")
Signed-off-by: Alexander Graf <graf@amazon.com>
---
drivers/virt/nitro_enclaves/ne_misc_dev.c | 93 +++++++++++++++++++----
drivers/virt/nitro_enclaves/ne_misc_dev.h | 8 ++
drivers/virt/nitro_enclaves/ne_pci_dev.c | 62 +++++++++++++++
3 files changed, 148 insertions(+), 15 deletions(-)
diff --git a/drivers/virt/nitro_enclaves/ne_misc_dev.c b/drivers/virt/nitro_enclaves/ne_misc_dev.c
index 964cb21b27c77..821624c93795b 100644
--- a/drivers/virt/nitro_enclaves/ne_misc_dev.c
+++ b/drivers/virt/nitro_enclaves/ne_misc_dev.c
@@ -78,6 +78,7 @@ static struct miscdevice ne_misc_dev = {
struct ne_devs ne_devs = {
.ne_misc_dev = &ne_misc_dev,
+ .lock = __RWSEM_INITIALIZER(ne_devs.lock),
};
/*
@@ -147,18 +148,22 @@ struct ne_phys_contig_mem_regions {
*/
static bool ne_check_enclaves_created(void)
{
- struct ne_pci_dev *ne_pci_dev = ne_devs.ne_pci_dev;
+ struct ne_pci_dev *ne_pci_dev = NULL;
bool ret = false;
- if (!ne_pci_dev)
- return ret;
+ down_read(&ne_devs.lock);
- mutex_lock(&ne_pci_dev->enclaves_list_mutex);
+ ne_pci_dev = ne_devs.ne_pci_dev;
+ if (ne_pci_dev) {
+ mutex_lock(&ne_pci_dev->enclaves_list_mutex);
- if (!list_empty(&ne_pci_dev->enclaves_list))
- ret = true;
+ if (!list_empty(&ne_pci_dev->enclaves_list))
+ ret = true;
- mutex_unlock(&ne_pci_dev->enclaves_list_mutex);
+ mutex_unlock(&ne_pci_dev->enclaves_list_mutex);
+ }
+
+ up_read(&ne_devs.lock);
return ret;
}
@@ -1133,17 +1138,18 @@ static int ne_start_enclave_ioctl(struct ne_enclave *ne_enclave,
}
/**
- * ne_enclave_ioctl() - Ioctl function provided by the enclave file.
+ * ne_enclave_do_ioctl() - Ioctl function provided by the enclave file.
* @file: File associated with this ioctl function.
* @cmd: The command that is set for the ioctl call.
* @arg: The argument that is provided for the ioctl call.
*
- * Context: Process context.
+ * Context: Process context. This function is called with ne_devs.lock held
+ * for reading, on an enclave that is not detached.
* Return:
* * 0 on success.
* * Negative return value on failure.
*/
-static long ne_enclave_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
+static long ne_enclave_do_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
{
struct ne_enclave *ne_enclave = file->private_data;
@@ -1390,6 +1396,36 @@ static long ne_enclave_ioctl(struct file *file, unsigned int cmd, unsigned long
return 0;
}
+/**
+ * ne_enclave_ioctl() - Ioctl function provided by the enclave file.
+ * @file: File associated with this ioctl function.
+ * @cmd: The command that is set for the ioctl call.
+ * @arg: The argument that is provided for the ioctl call.
+ *
+ * Context: Process context.
+ * Return:
+ * * 0 on success.
+ * * Negative return value on failure.
+ */
+static long ne_enclave_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
+{
+ struct ne_enclave *ne_enclave = file->private_data;
+ long rc;
+
+ rc = down_read_killable(&ne_devs.lock);
+ if (rc)
+ return rc;
+
+ if (ne_enclave->detached)
+ rc = -ENODEV;
+ else
+ rc = ne_enclave_do_ioctl(file, cmd, arg);
+
+ up_read(&ne_devs.lock);
+
+ return rc;
+}
+
/**
* ne_enclave_remove_all_mem_region_entries() - Remove all memory region entries
* from the enclave data structure.
@@ -1542,7 +1578,7 @@ static void ne_enclave_free(struct ne_enclave *ne_enclave)
static int ne_enclave_release(struct inode *inode, struct file *file)
{
struct ne_enclave *ne_enclave = file->private_data;
- struct ne_pci_dev *ne_pci_dev = ne_devs.ne_pci_dev;
+ struct ne_pci_dev *ne_pci_dev = NULL;
int rc;
if (!ne_enclave)
@@ -1555,6 +1591,21 @@ static int ne_enclave_release(struct inode *inode, struct file *file)
if (!ne_enclave->slot_uid)
return 0;
+ down_read(&ne_devs.lock);
+
+ /*
+ * The device the enclave was created on is gone, and with it the only
+ * party that can confirm that the slot no longer uses its memory and
+ * its CPUs, so the enclave keeps both.
+ */
+ if (ne_enclave->detached) {
+ rc = -ENODEV;
+
+ goto unlock_devs;
+ }
+
+ ne_pci_dev = ne_devs.ne_pci_dev;
+
/*
* Acquire the enclave list mutex before the enclave mutex
* in order to avoid deadlocks with @ref ne_event_work_handler.
@@ -1577,6 +1628,9 @@ static int ne_enclave_release(struct inode *inode, struct file *file)
mutex_unlock(&ne_pci_dev->enclaves_list_mutex);
+unlock_devs:
+ up_read(&ne_devs.lock);
+
return rc;
}
@@ -1774,16 +1828,25 @@ static long ne_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
{
switch (cmd) {
case NE_CREATE_VM: {
- struct ne_pci_dev *ne_pci_dev = ne_devs.ne_pci_dev;
+ struct ne_pci_dev *ne_pci_dev = NULL;
u64 __user *slot_uid = (void __user *)arg;
int rc;
- rc = mutex_lock_killable(&ne_pci_dev->enclaves_list_mutex);
+ rc = down_read_killable(&ne_devs.lock);
if (rc)
return rc;
- rc = ne_create_vm_ioctl(ne_pci_dev, slot_uid);
- mutex_unlock(&ne_pci_dev->enclaves_list_mutex);
+ rc = -ENODEV;
+ ne_pci_dev = ne_devs.ne_pci_dev;
+ if (ne_pci_dev) {
+ rc = mutex_lock_killable(&ne_pci_dev->enclaves_list_mutex);
+ if (!rc) {
+ rc = ne_create_vm_ioctl(ne_pci_dev, slot_uid);
+ mutex_unlock(&ne_pci_dev->enclaves_list_mutex);
+ }
+ }
+
+ up_read(&ne_devs.lock);
return rc;
}
diff --git a/drivers/virt/nitro_enclaves/ne_misc_dev.h b/drivers/virt/nitro_enclaves/ne_misc_dev.h
index 2a4d2224babac..4031d5d23eb00 100644
--- a/drivers/virt/nitro_enclaves/ne_misc_dev.h
+++ b/drivers/virt/nitro_enclaves/ne_misc_dev.h
@@ -12,6 +12,7 @@
#include <linux/mm.h>
#include <linux/mutex.h>
#include <linux/pci.h>
+#include <linux/rwsem.h>
#include <linux/wait.h>
#include "ne_pci_dev.h"
@@ -35,6 +36,8 @@ struct ne_mem_region {
/**
* struct ne_enclave - Per-enclave data used for enclave lifetime management.
* @enclave_info_mutex : Mutex for accessing this internal state.
+ * @detached: The NE PCI device the enclave was created on is
+ * gone. Written with ne_devs.lock held for writing.
* @enclave_list_entry : Entry in the list of created enclaves.
* @eventq: Wait queue used for out-of-band event notifications
* triggered from the PCI device event handler to
@@ -63,6 +66,7 @@ struct ne_mem_region {
*/
struct ne_enclave {
struct mutex enclave_info_mutex;
+ bool detached;
struct list_head enclave_list_entry;
wait_queue_head_t eventq;
bool has_event;
@@ -97,10 +101,14 @@ enum ne_state {
* struct ne_devs - Data structure to keep refs to the NE misc and PCI devices.
* @ne_misc_dev: Nitro Enclaves misc device.
* @ne_pci_dev : Nitro Enclaves PCI device.
+ * @lock: Held for writing while the NE PCI device binds or unbinds, and for
+ * reading by the file operations that reach it or log through the
+ * misc device, which goes away with it.
*/
struct ne_devs {
struct miscdevice *ne_misc_dev;
struct ne_pci_dev *ne_pci_dev;
+ struct rw_semaphore lock;
};
/* Nitro Enclaves (NE) data structure for keeping refs to the NE misc and PCI devices. */
diff --git a/drivers/virt/nitro_enclaves/ne_pci_dev.c b/drivers/virt/nitro_enclaves/ne_pci_dev.c
index d2c430d10758f..cd8e934e968c3 100644
--- a/drivers/virt/nitro_enclaves/ne_pci_dev.c
+++ b/drivers/virt/nitro_enclaves/ne_pci_dev.c
@@ -574,6 +574,38 @@ static void ne_pci_dev_disable(struct pci_dev *pdev)
dev_err(&pdev->dev, "Error in pci dev disable cmd\n");
}
+/**
+ * ne_pci_dev_detach_enclaves() - Cut the enclaves off the NE PCI device that
+ * goes away.
+ * @ne_pci_dev: Private data associated with the PCI device.
+ *
+ * Context: Process context. This function is called with ne_devs.lock held
+ * for writing.
+ */
+static void ne_pci_dev_detach_enclaves(struct ne_pci_dev *ne_pci_dev)
+{
+ struct ne_enclave *ne_enclave = NULL;
+ struct ne_enclave *ne_enclave_tmp = NULL;
+
+ mutex_lock(&ne_pci_dev->enclaves_list_mutex);
+
+ list_for_each_entry_safe(ne_enclave, ne_enclave_tmp,
+ &ne_pci_dev->enclaves_list, enclave_list_entry) {
+ list_del(&ne_enclave->enclave_list_entry);
+
+ mutex_lock(&ne_enclave->enclave_info_mutex);
+
+ ne_enclave->detached = true;
+ ne_enclave->has_event = true;
+
+ wake_up_interruptible(&ne_enclave->eventq);
+
+ mutex_unlock(&ne_enclave->enclave_info_mutex);
+ }
+
+ mutex_unlock(&ne_pci_dev->enclaves_list_mutex);
+}
+
/**
* ne_pci_probe() - Probe function for the NE PCI device.
* @pdev: PCI device to match with the NE PCI driver.
@@ -643,6 +675,20 @@ static int ne_pci_probe(struct pci_dev *pdev, const struct pci_device_id *id)
goto teardown_msix;
}
+ down_write(&ne_devs.lock);
+
+ /*
+ * The driver keeps a single device in ne_devs, and the enclaves and the
+ * misc device belong to it.
+ */
+ if (ne_devs.ne_pci_dev) {
+ dev_err(&pdev->dev, "Another NE PCI device is bound already\n");
+
+ rc = -EBUSY;
+
+ goto unlock_ne_devs;
+ }
+
ne_devs.ne_pci_dev = ne_pci_dev;
rc = misc_register(ne_devs.ne_misc_dev);
@@ -652,10 +698,14 @@ static int ne_pci_probe(struct pci_dev *pdev, const struct pci_device_id *id)
goto disable_ne_pci_dev;
}
+ up_write(&ne_devs.lock);
+
return 0;
disable_ne_pci_dev:
ne_devs.ne_pci_dev = NULL;
+unlock_ne_devs:
+ up_write(&ne_devs.lock);
ne_pci_dev_disable(pdev);
teardown_msix:
ne_teardown_msix(pdev);
@@ -682,6 +732,10 @@ static void ne_pci_remove(struct pci_dev *pdev)
{
struct ne_pci_dev *ne_pci_dev = pci_get_drvdata(pdev);
+ down_write(&ne_devs.lock);
+
+ ne_pci_dev_detach_enclaves(ne_pci_dev);
+
misc_deregister(ne_devs.ne_misc_dev);
ne_devs.ne_pci_dev = NULL;
@@ -699,6 +753,8 @@ static void ne_pci_remove(struct pci_dev *pdev)
pci_disable_device(pdev);
kfree(ne_pci_dev);
+
+ up_write(&ne_devs.lock);
}
/**
@@ -714,6 +770,10 @@ static void ne_pci_shutdown(struct pci_dev *pdev)
if (!ne_pci_dev)
return;
+ down_write(&ne_devs.lock);
+
+ ne_pci_dev_detach_enclaves(ne_pci_dev);
+
misc_deregister(ne_devs.ne_misc_dev);
ne_devs.ne_pci_dev = NULL;
@@ -731,6 +791,8 @@ static void ne_pci_shutdown(struct pci_dev *pdev)
pci_disable_device(pdev);
kfree(ne_pci_dev);
+
+ up_write(&ne_devs.lock);
}
/*
--
2.47.1
prev parent reply other threads:[~2026-11-11 13:51 UTC|newest]
Thread overview: 8+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-11-11 13:49 [PATCH 00/19] nitro_enclaves: Fix enclave release, command retries and the CPU pool Alexander Graf
2026-11-11 13:49 ` [PATCH 01/19] nitro_enclaves: Release the reference on a rejected region page Alexander Graf
2026-11-11 13:49 ` [PATCH 02/19] nitro_enclaves: Split freeing the slot from freeing the enclave Alexander Graf
2026-11-11 13:49 ` [PATCH 03/19] nitro_enclaves: Ignore the reply of a failed slot info command Alexander Graf
2026-11-11 13:49 ` [PATCH 04/19] nitro_enclaves: Retry a command the device asks us to retry Alexander Graf
2026-11-11 13:49 ` [PATCH 05/19] nitro_enclaves: Stop sending commands to a broken device Alexander Graf
2026-11-11 13:49 ` [PATCH 06/19] nitro_enclaves: Set up the device state before requesting its interrupts Alexander Graf
2026-11-11 13:49 ` Alexander Graf [this message]
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=5f80be8bc20324938d6455407afed074a0620253.1794393487.git.graf@amazon.com \
--to=graf@amazon.com \
--cc=arnd@arndb.de \
--cc=aws-nitro-enclaves-devel@amazon.com \
--cc=gregkh@linuxfoundation.org \
--cc=linux-kernel@vger.kernel.org \
--cc=nh-open-source@amazon.com \
/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®