lists.openwall.net   lists  /  announce  owl-users  owl-dev  john-users  john-dev  passwdqc-users  yescrypt  popa3d-users  /  oss-security  kernel-hardening  musl  sabotage  tlsify  passwords  /  crypt-dev  xvendor  /  Bugtraq  Full-Disclosure  linux-kernel  linux-netdev  linux-ext4  linux-hardening  linux-cve-announce  PHC 
Open Source and information security mailing list archives
 
Hash Suite for Android: free password hash cracker in your pocket
[<prev] [next>] [day] [month] [year] [list]
Message-Id: <20251024010228.1667904-1-danisjiang@gmail.com>
Date: Thu, 23 Oct 2025 20:02:28 -0500
From: Yuhao Jiang <danisjiang@...il.com>
To: Frederic Barrat <fbarrat@...ux.ibm.com>,
	Andrew Donnellan <ajd@...ux.ibm.com>
Cc: Arnd Bergmann <arnd@...db.de>,
	Greg Kroah-Hartman <gregkh@...uxfoundation.org>,
	Alastair D'Silva <alastair@...ilva.org>,
	Michael Ellerman <mpe@...erman.id.au>,
	linuxppc-dev@...ts.ozlabs.org,
	linux-kernel@...r.kernel.org,
	Yuhao Jiang <danisjiang@...il.com>,
	stable@...r.kernel.org
Subject: [PATCH] ocxl: Fix race leading to use-after-free in file operations

The file operations dereference the context pointer after checking
status under ctx->status_mutex, then drop the lock before using the
context. This allows afu_release() running on another CPU to free
the context, leading to a use-after-free vulnerability.

The race window exists in afu_ioctl(), afu_mmap(), afu_poll() and
afu_read() between the status check and context usage. During device
hot-unplug or rapid open/close cycles, this causes kernel crashes.

Introduce reference counting via kref to prevent premature free.
ocxl_context_get() atomically checks status and acquires a reference
under status_mutex. File operations hold this reference for their
duration, ensuring the context remains valid even if another thread
calls afu_release().

ocxl_context_alloc() initializes refcount to 1 for the file's
lifetime. afu_release() drops this reference, with the context freed
when the last reference goes away. Preserve existing -EBUSY behavior
where the context intentionally leaks on detach timeout.

Reported-by: Yuhao Jiang <danisjiang@...il.com>
Fixes: 5ef3166e8a32 ("ocxl: Driver code for 'generic' opencapi devices")
Cc: stable@...r.kernel.org
Signed-off-by: Yuhao Jiang <danisjiang@...il.com>
---
 drivers/misc/ocxl/context.c       |  69 ++++++++++++++----
 drivers/misc/ocxl/file.c          | 113 +++++++++++++++++++++++-------
 drivers/misc/ocxl/ocxl_internal.h |   4 ++
 3 files changed, 144 insertions(+), 42 deletions(-)

diff --git a/drivers/misc/ocxl/context.c b/drivers/misc/ocxl/context.c
index cded7d1caf32..e154adc972a5 100644
--- a/drivers/misc/ocxl/context.c
+++ b/drivers/misc/ocxl/context.c
@@ -28,6 +28,7 @@ int ocxl_context_alloc(struct ocxl_context **context, struct ocxl_afu *afu,
 
 	ctx->pasid = pasid;
 	ctx->status = OPENED;
+	kref_init(&ctx->kref);
 	mutex_init(&ctx->status_mutex);
 	ctx->mapping = mapping;
 	mutex_init(&ctx->mapping_lock);
@@ -47,6 +48,59 @@ int ocxl_context_alloc(struct ocxl_context **context, struct ocxl_afu *afu,
 }
 EXPORT_SYMBOL_GPL(ocxl_context_alloc);
 
+/**
+ * ocxl_context_get() - Get a reference to the context if not closed
+ * @ctx: The context
+ *
+ * Atomically checks if context status is not CLOSED and acquires a reference.
+ * Must be called with ctx->status_mutex held.
+ *
+ * Return: true if reference acquired, false if context is CLOSED
+ */
+bool ocxl_context_get(struct ocxl_context *ctx)
+{
+	lockdep_assert_held(&ctx->status_mutex);
+
+	if (ctx->status == CLOSED)
+		return false;
+
+	kref_get(&ctx->kref);
+	return true;
+}
+EXPORT_SYMBOL_GPL(ocxl_context_get);
+
+/*
+ * kref release callback - called when last reference is dropped
+ */
+static void ocxl_context_release(struct kref *kref)
+{
+	struct ocxl_context *ctx = container_of(kref, struct ocxl_context,
+						 kref);
+
+	mutex_lock(&ctx->afu->contexts_lock);
+	ctx->afu->pasid_count--;
+	idr_remove(&ctx->afu->contexts_idr, ctx->pasid);
+	mutex_unlock(&ctx->afu->contexts_lock);
+
+	ocxl_afu_irq_free_all(ctx);
+	idr_destroy(&ctx->irq_idr);
+	/* reference to the AFU taken in ocxl_context_alloc() */
+	ocxl_afu_put(ctx->afu);
+	kfree(ctx);
+}
+
+/**
+ * ocxl_context_put() - Release a reference to the context
+ * @ctx: The context
+ *
+ * Decrements the reference count. When it reaches zero, the context is freed.
+ */
+void ocxl_context_put(struct ocxl_context *ctx)
+{
+	kref_put(&ctx->kref, ocxl_context_release);
+}
+EXPORT_SYMBOL_GPL(ocxl_context_put);
+
 /*
  * Callback for when a translation fault triggers an error
  * data:	a pointer to the context which triggered the fault
@@ -279,18 +333,3 @@ void ocxl_context_detach_all(struct ocxl_afu *afu)
 	}
 	mutex_unlock(&afu->contexts_lock);
 }
-
-void ocxl_context_free(struct ocxl_context *ctx)
-{
-	mutex_lock(&ctx->afu->contexts_lock);
-	ctx->afu->pasid_count--;
-	idr_remove(&ctx->afu->contexts_idr, ctx->pasid);
-	mutex_unlock(&ctx->afu->contexts_lock);
-
-	ocxl_afu_irq_free_all(ctx);
-	idr_destroy(&ctx->irq_idr);
-	/* reference to the AFU taken in ocxl_context_alloc() */
-	ocxl_afu_put(ctx->afu);
-	kfree(ctx);
-}
-EXPORT_SYMBOL_GPL(ocxl_context_free);
diff --git a/drivers/misc/ocxl/file.c b/drivers/misc/ocxl/file.c
index 7eb74711ac96..c08724e7ff1e 100644
--- a/drivers/misc/ocxl/file.c
+++ b/drivers/misc/ocxl/file.c
@@ -204,17 +204,21 @@ static long afu_ioctl(struct file *file, unsigned int cmd,
 	int irq_id;
 	u64 irq_offset;
 	long rc;
-	bool closed;
-
-	pr_debug("%s for context %d, command %s\n", __func__, ctx->pasid,
-		CMD_STR(cmd));
 
+	/*
+	 * Hold a reference to the context for the duration of this operation.
+	 * We check the status and acquire the reference atomically under the
+	 * status_mutex to ensure the context remains valid.
+	 */
 	mutex_lock(&ctx->status_mutex);
-	closed = (ctx->status == CLOSED);
+	if (!ocxl_context_get(ctx)) {
+		mutex_unlock(&ctx->status_mutex);
+		return -EIO;
+	}
 	mutex_unlock(&ctx->status_mutex);
 
-	if (closed)
-		return -EIO;
+	pr_debug("%s for context %d, command %s\n", __func__, ctx->pasid,
+		CMD_STR(cmd));
 
 	switch (cmd) {
 	case OCXL_IOCTL_ATTACH:
@@ -230,7 +234,7 @@ static long afu_ioctl(struct file *file, unsigned int cmd,
 					sizeof(irq_offset));
 			if (rc) {
 				ocxl_afu_irq_free(ctx, irq_id);
-				return -EFAULT;
+				rc = -EFAULT;
 			}
 		}
 		break;
@@ -238,8 +242,10 @@ static long afu_ioctl(struct file *file, unsigned int cmd,
 	case OCXL_IOCTL_IRQ_FREE:
 		rc = copy_from_user(&irq_offset, (u64 __user *) args,
 				sizeof(irq_offset));
-		if (rc)
-			return -EFAULT;
+		if (rc) {
+			rc = -EFAULT;
+			break;
+		}
 		irq_id = ocxl_irq_offset_to_id(ctx, irq_offset);
 		rc = ocxl_afu_irq_free(ctx, irq_id);
 		break;
@@ -247,14 +253,20 @@ static long afu_ioctl(struct file *file, unsigned int cmd,
 	case OCXL_IOCTL_IRQ_SET_FD:
 		rc = copy_from_user(&irq_fd, (u64 __user *) args,
 				sizeof(irq_fd));
-		if (rc)
-			return -EFAULT;
-		if (irq_fd.reserved)
-			return -EINVAL;
+		if (rc) {
+			rc = -EFAULT;
+			break;
+		}
+		if (irq_fd.reserved) {
+			rc = -EINVAL;
+			break;
+		}
 		irq_id = ocxl_irq_offset_to_id(ctx, irq_fd.irq_offset);
 		ev_ctx = eventfd_ctx_fdget(irq_fd.eventfd);
-		if (IS_ERR(ev_ctx))
-			return PTR_ERR(ev_ctx);
+		if (IS_ERR(ev_ctx)) {
+			rc = PTR_ERR(ev_ctx);
+			break;
+		}
 		rc = ocxl_irq_set_handler(ctx, irq_id, irq_handler, irq_free, ev_ctx);
 		if (rc)
 			eventfd_ctx_put(ev_ctx);
@@ -280,6 +292,8 @@ static long afu_ioctl(struct file *file, unsigned int cmd,
 	default:
 		rc = -EINVAL;
 	}
+
+	ocxl_context_put(ctx);
 	return rc;
 }
 
@@ -292,9 +306,23 @@ static long afu_compat_ioctl(struct file *file, unsigned int cmd,
 static int afu_mmap(struct file *file, struct vm_area_struct *vma)
 {
 	struct ocxl_context *ctx = file->private_data;
+	int rc;
+
+	/*
+	 * Hold a reference during mmap setup to ensure the context
+	 * remains valid.
+	 */
+	mutex_lock(&ctx->status_mutex);
+	if (!ocxl_context_get(ctx)) {
+		mutex_unlock(&ctx->status_mutex);
+		return -EIO;
+	}
+	mutex_unlock(&ctx->status_mutex);
 
 	pr_debug("%s for context %d\n", __func__, ctx->pasid);
-	return ocxl_context_mmap(ctx, vma);
+	rc = ocxl_context_mmap(ctx, vma);
+	ocxl_context_put(ctx);
+	return rc;
 }
 
 static bool has_xsl_error(struct ocxl_context *ctx)
@@ -324,21 +352,31 @@ static unsigned int afu_poll(struct file *file, struct poll_table_struct *wait)
 {
 	struct ocxl_context *ctx = file->private_data;
 	unsigned int mask = 0;
-	bool closed;
+
+	/*
+	 * Hold a reference to the context while checking for events.
+	 */
+	mutex_lock(&ctx->status_mutex);
+	if (!ocxl_context_get(ctx)) {
+		mutex_unlock(&ctx->status_mutex);
+		return EPOLLERR;
+	}
+	mutex_unlock(&ctx->status_mutex);
 
 	pr_debug("%s for context %d\n", __func__, ctx->pasid);
 
 	poll_wait(file, &ctx->events_wq, wait);
 
-	mutex_lock(&ctx->status_mutex);
-	closed = (ctx->status == CLOSED);
-	mutex_unlock(&ctx->status_mutex);
-
 	if (afu_events_pending(ctx))
 		mask = EPOLLIN | EPOLLRDNORM;
-	else if (closed)
-		mask = EPOLLERR;
+	else {
+		mutex_lock(&ctx->status_mutex);
+		if (ctx->status == CLOSED)
+			mask = EPOLLERR;
+		mutex_unlock(&ctx->status_mutex);
+	}
 
+	ocxl_context_put(ctx);
 	return mask;
 }
 
@@ -410,6 +448,16 @@ static ssize_t afu_read(struct file *file, char __user *buf, size_t count,
 			AFU_EVENT_BODY_MAX_SIZE))
 		return -EINVAL;
 
+	/*
+	 * Hold a reference to the context for the duration of the read operation.
+	 */
+	mutex_lock(&ctx->status_mutex);
+	if (!ocxl_context_get(ctx)) {
+		mutex_unlock(&ctx->status_mutex);
+		return -EIO;
+	}
+	mutex_unlock(&ctx->status_mutex);
+
 	for (;;) {
 		prepare_to_wait(&ctx->events_wq, &event_wait,
 				TASK_INTERRUPTIBLE);
@@ -422,11 +470,13 @@ static ssize_t afu_read(struct file *file, char __user *buf, size_t count,
 
 		if (file->f_flags & O_NONBLOCK) {
 			finish_wait(&ctx->events_wq, &event_wait);
+			ocxl_context_put(ctx);
 			return -EAGAIN;
 		}
 
 		if (signal_pending(current)) {
 			finish_wait(&ctx->events_wq, &event_wait);
+			ocxl_context_put(ctx);
 			return -ERESTARTSYS;
 		}
 
@@ -437,19 +487,24 @@ static ssize_t afu_read(struct file *file, char __user *buf, size_t count,
 
 	if (has_xsl_error(ctx)) {
 		used = append_xsl_error(ctx, &header, buf + sizeof(header));
-		if (used < 0)
+		if (used < 0) {
+			ocxl_context_put(ctx);
 			return used;
+		}
 	}
 
 	if (!afu_events_pending(ctx))
 		header.flags |= OCXL_KERNEL_EVENT_FLAG_LAST;
 
-	if (copy_to_user(buf, &header, sizeof(header)))
+	if (copy_to_user(buf, &header, sizeof(header))) {
+		ocxl_context_put(ctx);
 		return -EFAULT;
+	}
 
 	used += sizeof(header);
 
 	rc = used;
+	ocxl_context_put(ctx);
 	return rc;
 }
 
@@ -464,8 +519,12 @@ static int afu_release(struct inode *inode, struct file *file)
 	ctx->mapping = NULL;
 	mutex_unlock(&ctx->mapping_lock);
 	wake_up_all(&ctx->events_wq);
+	/*
+	 * Drop the initial reference from afu_open(). The context will be
+	 * freed when all references are released.
+	 */
 	if (rc != -EBUSY)
-		ocxl_context_free(ctx);
+		ocxl_context_put(ctx);
 	return 0;
 }
 
diff --git a/drivers/misc/ocxl/ocxl_internal.h b/drivers/misc/ocxl/ocxl_internal.h
index d2028d6c6f08..6eab7806b43d 100644
--- a/drivers/misc/ocxl/ocxl_internal.h
+++ b/drivers/misc/ocxl/ocxl_internal.h
@@ -5,6 +5,7 @@
 
 #include <linux/pci.h>
 #include <linux/cdev.h>
+#include <linux/kref.h>
 #include <linux/list.h>
 #include <misc/ocxl.h>
 
@@ -68,6 +69,7 @@ struct ocxl_xsl_error {
 };
 
 struct ocxl_context {
+	struct kref kref;
 	struct ocxl_afu *afu;
 	int pasid;
 	struct mutex status_mutex;
@@ -140,6 +142,8 @@ int ocxl_link_update_pe(void *link_handle, int pasid, __u16 tid);
 
 int ocxl_context_mmap(struct ocxl_context *ctx,
 			struct vm_area_struct *vma);
+bool ocxl_context_get(struct ocxl_context *ctx);
+void ocxl_context_put(struct ocxl_context *ctx);
 void ocxl_context_detach_all(struct ocxl_afu *afu);
 
 int ocxl_sysfs_register_afu(struct ocxl_file_info *info);
-- 
2.34.1


Powered by blists - more mailing lists

Powered by Openwall GNU/*/Linux Powered by OpenVZ