linux-gbmc: peci: Systemic fixes for UAF and memory corruption

Consolidates the following fixes into a single patch:
- peci: core: Check xfer and refcount in peci_command
- peci: core: Relocate IDR removal to release callback
- peci: npcm: Fix use-after-free and ordering in teardown
- peci: dev: Fix use-after-free on adapter detach
- peci: core: Fix sysfs new_device race condition

Tested: Survived 60x unbind/bind iterations on a machine.
Fusion-Link: https://fusion2.corp.google.com/d7994c55-f939-35a7-b32f-0c54d54e61dc (platform11)
Fusion-Link: https://fusion2.corp.google.com/165543ab-bc9b-3723-b4f2-9ceeded9c6b8 (platform11-emr)
Fusion-Link: https://fusion2.corp.google.com/cc558fb5-4d87-3874-bad0-3b11c1c29715 (platform15)
Fusion-Link: https://fusion2.corp.google.com/58873016-06b4-3059-bd4f-91b1875591de (platform17)
Fusion-Link: https://fusion2.corp.google.com/61cccd02-779d-3b8a-9910-551ab3a2122b (platform5)

Platforms-Affected: peci platforms
Google-Bug-Id: 498991349
Change-Id: I9fe6305b83730cb7f33aa1aa1f5b2396268e375e
Signed-off-by: William A. Kennington III <wak@google.com>
(cherry picked from commit c230fa15710917237f26a6e16be4aeb51ab82457)
diff --git a/recipes-kernel/linux/files/0001-peci-core-Check-xfer-and-refcount-in-peci_command.patch b/recipes-kernel/linux/files/0001-peci-core-Check-xfer-and-refcount-in-peci_command.patch
new file mode 100644
index 0000000..f3a832e
--- /dev/null
+++ b/recipes-kernel/linux/files/0001-peci-core-Check-xfer-and-refcount-in-peci_command.patch
@@ -0,0 +1,60 @@
+From 2b8b81232c6d71192a53a988417a16d396166233 Mon Sep 17 00:00:00 2001
+From: OpenEmbedded <oe.patch@oe>
+Date: Fri, 10 Apr 2026 01:21:40 +0000
+Subject: [PATCH] peci: core: Check xfer and refcount in peci_command
+
+The peci_command function lacked proper synchronization with the teardown of
+the adapter. This allowed transfers to continue or start while the adapter was
+being removed, leading to NULL pointer dereferences when adapter->xfer is
+cleared or when the adapter memory is freed.
+
+This patch adds a call to get_device to take a reference on the adapter
+during the transfer, guaranteeing the adapter memory is not freed mid-transfer.
+It also checks if adapter->xfer is still valid under bus_lock.
+---
+ drivers/peci/peci-core.c | 18 ++++++++++++++++--
+ 1 file changed, 16 insertions(+), 2 deletions(-)
+
+diff --git a/drivers/peci/peci-core.c b/drivers/peci/peci-core.c
+index 44c2669..36aaa78 100644
+--- a/drivers/peci/peci-core.c
++++ b/drivers/peci/peci-core.c
+@@ -1451,19 +1451,33 @@ int peci_command(struct peci_adapter *adapter, enum peci_cmd cmd, uint msg_len,
+ 	if (cmd >= PECI_CMD_MAX || cmd < PECI_CMD_XFER)
+ 		return -ENOTTY;
+ 
++	if (!get_device(&adapter->dev))
++		return -ENODEV;
++
+ 	dev_dbg(&adapter->dev, "%s, cmd=0x%02x\n", __func__, cmd);
+ 
+-	if (!peci_cmd_fn[cmd])
+-		return -EINVAL;
++	if (!peci_cmd_fn[cmd]) {
++		ret = -EINVAL;
++		goto out;
++	}
+ 
+ 	mutex_lock(&adapter->bus_lock);
+ 
++	if (!adapter->xfer) {
++		mutex_unlock(&adapter->bus_lock);
++		ret = -ENODEV;
++		goto out;
++	}
++
+ 	ret = peci_check_cmd_support(adapter, cmd);
+ 	if (!ret)
+ 		ret = peci_cmd_fn[cmd](adapter, msg_len, vmsg);
+ 
+ 	mutex_unlock(&adapter->bus_lock);
+ 
++out:
++	put_device(&adapter->dev);
++
+ 	return ret;
+ }
+ EXPORT_SYMBOL_GPL(peci_command);
+-- 
+2.53.0.1213.gd9a14994de-goog
+
diff --git a/recipes-kernel/linux/files/0002-peci-core-Relocate-IDR-removal-to-release-callback.patch b/recipes-kernel/linux/files/0002-peci-core-Relocate-IDR-removal-to-release-callback.patch
new file mode 100644
index 0000000..301d07b
--- /dev/null
+++ b/recipes-kernel/linux/files/0002-peci-core-Relocate-IDR-removal-to-release-callback.patch
@@ -0,0 +1,64 @@
+From 495d4c4c91994152446e0ae1f9c9786e33a4a4de Mon Sep 17 00:00:00 2001
+From: OpenEmbedded <oe.patch@oe>
+Date: Fri, 10 Apr 2026 01:22:02 +0000
+Subject: [PATCH] peci: core: Relocate IDR removal to release callback
+
+The adapter ID was being freed in peci_del_adapter prematurely, before the
+peci_adapter was fully released via its release callback. This allowed a new
+adapter to be allocated the same ID while the old adapter was still in the
+device model (and waiting for async release), leading to ID reuse and slab
+corruption.
+
+This patch moves idr_remove to the peci_adapter_dev_release callback,
+ensuring the ID is only reused once the device is completely torn down.
+It also clears adapter->xfer under bus_lock in peci_del_adapter to
+block new transfers during teardown.
+---
+ drivers/peci/peci-core.c | 16 ++++++++++------
+ 1 file changed, 10 insertions(+), 6 deletions(-)
+
+diff --git a/drivers/peci/peci-core.c b/drivers/peci/peci-core.c
+index 36aaa78..9ca259d 100644
+--- a/drivers/peci/peci-core.c
++++ b/drivers/peci/peci-core.c
+@@ -1765,6 +1765,11 @@ static void peci_adapter_dev_release(struct device *dev)
+ 	struct peci_adapter *adapter = to_peci_adapter(dev);
+ 
+ 	dev_dbg(dev, "%s: %s\n", __func__, adapter->name);
++
++	mutex_lock(&core_lock);
++	idr_remove(&peci_adapter_idr, adapter->nr);
++	mutex_unlock(&core_lock);
++
+ 	mutex_destroy(&adapter->userspace_clients_lock);
+ 	mutex_destroy(&adapter->bus_lock);
+ 	kfree(adapter);
+@@ -2259,6 +2264,11 @@ void peci_del_adapter(struct peci_adapter *adapter)
+ 	if (found != adapter)
+ 		return;
+ 
++	/* Synchronize with and block any new transfers */
++	mutex_lock(&adapter->bus_lock);
++	adapter->xfer = NULL;
++	mutex_unlock(&adapter->bus_lock);
++
+ 	/* Remove devices instantiated from sysfs */
+ 	mutex_lock(&adapter->userspace_clients_lock);
+ 	list_for_each_entry_safe(client, next, &adapter->userspace_clients,
+@@ -2280,13 +2290,7 @@ void peci_del_adapter(struct peci_adapter *adapter)
+ 	dev_dbg(&adapter->dev, "adapter [%s] unregistered\n", adapter->name);
+ 
+ 	pm_runtime_disable(&adapter->dev);
+-	nr = adapter->nr;
+ 	device_unregister(&adapter->dev);
+-
+-	/* free bus id */
+-	mutex_lock(&core_lock);
+-	idr_remove(&peci_adapter_idr, nr);
+-	mutex_unlock(&core_lock);
+ }
+ EXPORT_SYMBOL_GPL(peci_del_adapter);
+ 
+-- 
+2.53.0.1213.gd9a14994de-goog
+
diff --git a/recipes-kernel/linux/files/0003-peci-npcm-Fix-use-after-free-and-ordering-in-teardow.patch b/recipes-kernel/linux/files/0003-peci-npcm-Fix-use-after-free-and-ordering-in-teardow.patch
new file mode 100644
index 0000000..97c2943
--- /dev/null
+++ b/recipes-kernel/linux/files/0003-peci-npcm-Fix-use-after-free-and-ordering-in-teardow.patch
@@ -0,0 +1,54 @@
+From 8694c685fb6c49c26d363317989764898e6e3de8 Mon Sep 17 00:00:00 2001
+From: OpenEmbedded <oe.patch@oe>
+Date: Fri, 10 Apr 2026 01:22:27 +0000
+Subject: [PATCH] peci: npcm: Fix use-after-free and ordering in teardown
+
+The teardown ordering in the peci-npcm driver npcm_peci_remove function
+caused use-after-free bugs. It invoked peci_del_adapter(priv->adapter), which
+drops the final reference and frees priv (as it is devm-managed by the adapter).
+Any access to priv->clk or priv->adapter->dev.of_node afterwards resulted in
+UAF kernel crashes.
+
+This patch fixes the issue by caching clk and of_node in local variables
+prior to deletion, explicitly disabling hardware interrupts, and waking up any
+waiters via complete_all before peci_del_adapter is called.
+---
+ drivers/peci/busses/peci-npcm.c | 20 ++++++++++++++++++--
+ 1 file changed, 18 insertions(+), 2 deletions(-)
+
+diff --git a/drivers/peci/busses/peci-npcm.c b/drivers/peci/busses/peci-npcm.c
+index 95079cb..3349ff4 100644
+--- a/drivers/peci/busses/peci-npcm.c
++++ b/drivers/peci/busses/peci-npcm.c
+@@ -385,10 +385,26 @@ static int npcm_peci_probe(struct platform_device *pdev)
+ static int npcm_peci_remove(struct platform_device *pdev)
+ {
+ 	struct npcm_peci *priv = dev_get_drvdata(&pdev->dev);
++	struct device_node *of_node = priv->adapter->dev.of_node;
++	struct clk *clk = priv->clk;
+ 
+-	clk_disable_unprepare(priv->clk);
++	/* 1. Disable hardware interrupts */
++	regmap_update_bits(priv->regmap, NPCM_PECI_CTL_STS,
++			   NPCM_PECI_CTRL_DONE_INT_EN, 0);
++
++	/* 2. Synchronize with IRQ handler and free it explicitly */
++	synchronize_irq(priv->irq);
++	devm_free_irq(&pdev->dev, priv->irq, priv);
++
++	/* 3. Wake up any waiters (they will see -ENODEV or timeout) */
++	complete_all(&priv->xfer_complete);
++
++	/* 4. Delete adapter (waits for active transfers via bus_lock) */
+ 	peci_del_adapter(priv->adapter);
+-	of_node_put(priv->adapter->dev.of_node);
++
++	/* 5. Disable clocks and release other resources */
++	clk_disable_unprepare(clk);
++	of_node_put(of_node);
+ 
+ 	return 0;
+ }
+-- 
+2.53.0.1213.gd9a14994de-goog
+
diff --git a/recipes-kernel/linux/files/0004-peci-dev-Fix-use-after-free-on-adapter-detach.patch b/recipes-kernel/linux/files/0004-peci-dev-Fix-use-after-free-on-adapter-detach.patch
new file mode 100644
index 0000000..0b64fbf
--- /dev/null
+++ b/recipes-kernel/linux/files/0004-peci-dev-Fix-use-after-free-on-adapter-detach.patch
@@ -0,0 +1,163 @@
+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
+
diff --git a/recipes-kernel/linux/files/0005-peci-core-Fix-sysfs-new_device-race-condition.patch b/recipes-kernel/linux/files/0005-peci-core-Fix-sysfs-new_device-race-condition.patch
new file mode 100644
index 0000000..d871e0f
--- /dev/null
+++ b/recipes-kernel/linux/files/0005-peci-core-Fix-sysfs-new_device-race-condition.patch
@@ -0,0 +1,45 @@
+From 4bd67c67f83241f7526c31c973ba92c2fc82031a Mon Sep 17 00:00:00 2001
+From: OpenEmbedded <oe.patch@oe>
+Date: Fri, 10 Apr 2026 01:33:24 +0000
+Subject: [PATCH] peci: core: Fix sysfs new_device race condition
+
+Concurrent writes to the 'new_device' sysfs attribute could result in
+multiple processes executing peci_new_device() for the same address at
+the same time. The peci_check_client_busy() check inside peci_new_device()
+would pass for both before either completed device_register(), resulting in
+a duplicate sysfs device creation warning and failure (-EEXIST).
+
+This patch moves the peci_new_device() call inside the userspace_clients_lock
+mutex section, synchronizing it with other new_device and delete_device
+sysfs writes.
+---
+ drivers/peci/peci-core.c | 10 +++++++---
+ 1 file changed, 7 insertions(+), 3 deletions(-)
+
+diff --git a/drivers/peci/peci-core.c b/drivers/peci/peci-core.c
+index 9ca259d..5127a05 100644
+--- a/drivers/peci/peci-core.c
++++ b/drivers/peci/peci-core.c
+@@ -1819,12 +1819,16 @@ static ssize_t peci_sysfs_new_device(struct device *dev,
+ 
+ 	info.addr = addr;
+ 	info.domain_id = domain_id;
+-	client = peci_new_device(adapter, &info);
+-	if (!client)
+-		return -EINVAL;
+ 
+ 	/* Keep track of the added device */
+ 	mutex_lock(&adapter->userspace_clients_lock);
++
++	client = peci_new_device(adapter, &info);
++	if (!client) {
++		mutex_unlock(&adapter->userspace_clients_lock);
++		return -EINVAL;
++	}
++
+ 	list_add_tail(&client->detected, &adapter->userspace_clients);
+ 	mutex_unlock(&adapter->userspace_clients_lock);
+ 	dev_dbg(dev, "%s: Instantiated device %s at 0x%02hx\n", "new_device",
+-- 
+2.53.0.1213.gd9a14994de-goog
+
diff --git a/recipes-kernel/linux/linux-gbmc_5.15.bb b/recipes-kernel/linux/linux-gbmc_5.15.bb
index f49e9b6..3f296e4 100644
--- a/recipes-kernel/linux/linux-gbmc_5.15.bb
+++ b/recipes-kernel/linux/linux-gbmc_5.15.bb
@@ -14,6 +14,11 @@
 SRC_URI:prepend = "git://gbmc.googlesource.com/linux;protocol=https;branch=${KBRANCH} "
 
 SRC_URI:append = " \
+  file://0001-peci-core-Check-xfer-and-refcount-in-peci_command.patch \
+  file://0002-peci-core-Relocate-IDR-removal-to-release-callback.patch \
+  file://0003-peci-npcm-Fix-use-after-free-and-ordering-in-teardow.patch \
+  file://0004-peci-dev-Fix-use-after-free-on-adapter-detach.patch \
+  file://0005-peci-core-Fix-sysfs-new_device-race-condition.patch \
   file://0001-hwmon-Add-driver-for-MPS-MPQ8785-Synchronous-Step-Do.patch \
   file://0001-hwmon-pmbus-Add-ltc4286-driver.patch \
   file://0001-Add-support-for-Winbond-W25Q512NW-IQ-IN.patch \