| From d1c970ee91d9f8c15da7564de4b1824388cd2a57 Mon Sep 17 00:00:00 2001 |
| From: OpenEmbedded <oe.patch@oe> |
| Date: Fri, 10 Apr 2026 01:25:33 +0000 |
| Subject: [PATCH] peci: dev: Fix use-after-free on adapter detach |
| |
| The peci_dev character device wrapper was vulnerable to use-after-free |
| during adapter removal while a userspace process held an open file descriptor. |
| When peci_dev_detach_adapter was called, it directly freed peci_dev, |
| leaving userspace holding dangling pointers when making subsequent ioctl calls. |
| |
| This patch introduces a kref reference counting mechanism for peci_dev and a |
| detached state flag. This guarantees the peci_dev structure and its resources |
| are only freed when all active userspace file descriptors are closed and the |
| adapter is detached. |
| --- |
| drivers/peci/peci-dev.c | 53 ++++++++++++++++++++++++++++++++--------- |
| 1 file changed, 42 insertions(+), 11 deletions(-) |
| |
| diff --git a/drivers/peci/peci-dev.c b/drivers/peci/peci-dev.c |
| index f026669..b7ad231 100644 |
| --- a/drivers/peci/peci-dev.c |
| +++ b/drivers/peci/peci-dev.c |
| @@ -3,6 +3,7 @@ |
| |
| #include <linux/cdev.h> |
| #include <linux/fs.h> |
| +#include <linux/kref.h> |
| #include <linux/list.h> |
| #include <linux/module.h> |
| #include <linux/notifier.h> |
| @@ -23,6 +24,8 @@ struct peci_dev { |
| struct peci_adapter *adapter; |
| struct device *dev; |
| struct cdev cdev; |
| + struct kref ref; |
| + bool detached; |
| }; |
| |
| #define PECI_MINORS MINORMASK |
| @@ -31,14 +34,23 @@ static dev_t peci_devt; |
| static LIST_HEAD(peci_dev_list); |
| static DEFINE_SPINLOCK(peci_dev_list_lock); |
| |
| +static void peci_dev_release_final(struct kref *ref) |
| +{ |
| + struct peci_dev *peci_dev = container_of(ref, struct peci_dev, ref); |
| + |
| + kfree(peci_dev); |
| +} |
| + |
| static struct peci_dev *peci_dev_get_by_minor(uint index) |
| { |
| struct peci_dev *peci_dev; |
| |
| spin_lock(&peci_dev_list_lock); |
| list_for_each_entry(peci_dev, &peci_dev_list, list) { |
| - if (peci_dev->adapter->nr == index) |
| + if (peci_dev->adapter->nr == index) { |
| + kref_get(&peci_dev->ref); |
| goto found; |
| + } |
| } |
| peci_dev = NULL; |
| found: |
| @@ -61,6 +73,7 @@ static struct peci_dev *peci_dev_alloc(struct peci_adapter *adapter) |
| if (!peci_dev) |
| return ERR_PTR(-ENOMEM); |
| peci_dev->adapter = adapter; |
| + kref_init(&peci_dev->ref); |
| |
| spin_lock(&peci_dev_list_lock); |
| list_add_tail(&peci_dev->list, &peci_dev_list); |
| @@ -71,21 +84,22 @@ static struct peci_dev *peci_dev_alloc(struct peci_adapter *adapter) |
| |
| static void peci_dev_put(struct peci_dev *peci_dev) |
| { |
| - spin_lock(&peci_dev_list_lock); |
| - list_del(&peci_dev->list); |
| - spin_unlock(&peci_dev_list_lock); |
| - kfree(peci_dev); |
| + kref_put(&peci_dev->ref, peci_dev_release_final); |
| } |
| |
| static ssize_t name_show(struct device *dev, |
| struct device_attribute *attr, char *buf) |
| { |
| struct peci_dev *peci_dev = peci_dev_get_by_minor(MINOR(dev->devt)); |
| + ssize_t ret; |
| |
| if (!peci_dev) |
| return -ENODEV; |
| |
| - return sprintf(buf, "%s\n", peci_dev->adapter->name); |
| + ret = sprintf(buf, "%s\n", peci_dev->adapter->name); |
| + peci_dev_put(peci_dev); |
| + |
| + return ret; |
| } |
| static DEVICE_ATTR_RO(name); |
| |
| @@ -106,6 +120,9 @@ static long peci_dev_ioctl(struct file *file, uint iocmd, ulong arg) |
| uint msg_len; |
| int ret; |
| |
| + if (peci_dev->detached) |
| + return -ENODEV; |
| + |
| cmd = _IOC_NR(iocmd); |
| msg_len = _IOC_SIZE(iocmd); |
| |
| @@ -188,8 +205,10 @@ static int peci_dev_open(struct inode *inode, struct file *file) |
| return -ENODEV; |
| |
| adapter = peci_get_adapter(peci_dev->adapter->nr); |
| - if (!adapter) |
| + if (!adapter) { |
| + peci_dev_put(peci_dev); |
| return -ENODEV; |
| + } |
| |
| file->private_data = peci_dev; |
| |
| @@ -201,6 +220,7 @@ static int peci_dev_release(struct inode *inode, struct file *file) |
| struct peci_dev *peci_dev = file->private_data; |
| |
| peci_put_adapter(peci_dev->adapter); |
| + peci_dev_put(peci_dev); |
| file->private_data = NULL; |
| |
| return 0; |
| @@ -270,14 +290,25 @@ static int peci_dev_detach_adapter(struct device *dev, void *dummy) |
| return 0; |
| |
| adapter = to_peci_adapter(dev); |
| - peci_dev = peci_dev_get_by_minor(adapter->nr); |
| - if (!peci_dev) |
| - return 0; |
| + |
| + spin_lock(&peci_dev_list_lock); |
| + list_for_each_entry(peci_dev, &peci_dev_list, list) { |
| + if (peci_dev->adapter == adapter) { |
| + peci_dev->detached = true; |
| + list_del(&peci_dev->list); |
| + goto found; |
| + } |
| + } |
| + spin_unlock(&peci_dev_list_lock); |
| + return 0; |
| + |
| +found: |
| + spin_unlock(&peci_dev_list_lock); |
| |
| cdev_del(&peci_dev->cdev); |
| devt = peci_dev->dev->devt; |
| - peci_dev_put(peci_dev); |
| device_destroy(peci_dev_class, devt); |
| + peci_dev_put(peci_dev); |
| |
| dev_info(dev, "cdev of adapter [%s] unregistered\n", adapter->name); |
| |
| -- |
| 2.53.0.1213.gd9a14994de-goog |
| |