[SRU][J][PATCH 12/17] s390/pci: Fix leak of struct zpci_dev when zpci_add_device() fails

Massimiliano Pellizzer massimiliano.pellizzer at canonical.com
Thu Aug 28 09:31:04 UTC 2025


From: Niklas Schnelle <schnelle at linux.ibm.com>

BugLink: https://bugs.launchpad.net/bugs/2119650

Prior to commit 0467cdde8c43 ("s390/pci: Sort PCI functions prior to
creating virtual busses") the IOMMU was initialized and the device was
registered as part of zpci_create_device() with the struct zpci_dev
freed if either resulted in an error. With that commit this was moved
into a separate function called zpci_add_device().

While this new function logs when adding failed, it expects the caller
not to use and to free the struct zpci_dev on error. This difference
between it and zpci_create_device() was missed while changing the
callers and the incompletely initialized struct zpci_dev may get used in
zpci_scan_configured_device in the error path. This then leads to
a crash due to the device not being registered with the zbus. It was
also not freed in this case. Fix this by handling the error return of
zpci_add_device(). Since in this case the zdev was not added to the
zpci_list it can simply be discarded and freed. Also make this more
explicit by moving the kref_init() into zpci_add_device() and document
that zpci_zdev_get()/zpci_zdev_put() must be used after adding.

Cc: stable at vger.kernel.org
Fixes: 0467cdde8c43 ("s390/pci: Sort PCI functions prior to creating virtual busses")
Reviewed-by: Gerd Bayer <gbayer at linux.ibm.com>
Reviewed-by: Matthew Rosato <mjrosato at linux.ibm.com>
Signed-off-by: Niklas Schnelle <schnelle at linux.ibm.com>
Signed-off-by: Heiko Carstens <hca at linux.ibm.com>
(backported from commit 48796104c864cf4dafa80bd8c2ce88f9c92a65ea)
[mpellizzer: context adjusted due to missing commits
 - bcb5d6c769039 s390/pci: introduce lock to synchronize state of zpci_dev's
 - 0d48566d4b58 s390/pci: rename lock member in struct zpci_dev
 which do not affect the patch.]
Signed-off-by: Massimiliano Pellizzer <massimiliano.pellizzer at canonical.com>
---
 arch/s390/pci/pci.c       | 21 +++++++++++++++++----
 arch/s390/pci/pci_event.c | 10 ++++++++--
 2 files changed, 25 insertions(+), 6 deletions(-)

diff --git a/arch/s390/pci/pci.c b/arch/s390/pci/pci.c
index a57b250ff2a0..9548499b3657 100644
--- a/arch/s390/pci/pci.c
+++ b/arch/s390/pci/pci.c
@@ -809,8 +809,9 @@ int zpci_hot_reset_device(struct zpci_dev *zdev)
  * @fh: Current Function Handle of the device to be created
  * @state: Initial state after creation either Standby or Configured
  *
- * Creates a new zpci device and adds it to its, possibly newly created, zbus
- * as well as zpci_list.
+ * Allocates a new struct zpci_dev and queries the platform for its details.
+ * If successful the device can subsequently be added to the zPCI subsystem
+ * using zpci_add_device().
  *
  * Returns: the zdev on success or an error pointer otherwise
  */
@@ -833,7 +834,6 @@ struct zpci_dev *zpci_create_device(u32 fid, u32 fh, enum zpci_state state)
 		goto error;
 	zdev->state =  state;
 
-	kref_init(&zdev->kref);
 	mutex_init(&zdev->lock);
 	mutex_init(&zdev->kzdev_lock);
 
@@ -845,6 +845,17 @@ struct zpci_dev *zpci_create_device(u32 fid, u32 fh, enum zpci_state state)
 	return ERR_PTR(rc);
 }
 
+/**
+ * zpci_add_device() - Add a previously created zPCI device to the zPCI subsystem
+ * @zdev: The zPCI device to be added
+ *
+ * A struct zpci_dev is added to the zPCI subsystem and to a virtual PCI bus creating
+ * a new one as necessary. A hotplug slot is created and events start to be handled.
+ * If successful from this point on zpci_zdev_get() and zpci_zdev_put() must be used.
+ * If adding the struct zpci_dev fails the device was not added and should be freed.
+ *
+ * Return: 0 on success, or an error code otherwise
+ */
 int zpci_add_device(struct zpci_dev *zdev)
 {
 	int rc;
@@ -858,6 +869,7 @@ int zpci_add_device(struct zpci_dev *zdev)
 	if (rc)
 		goto error_destroy_iommu;
 
+	kref_init(&zdev->kref);
 	spin_lock(&zpci_list_lock);
 	list_add_tail(&zdev->entry, &zpci_list);
 	spin_unlock(&zpci_list_lock);
@@ -1152,7 +1164,8 @@ static void zpci_add_devices(struct list_head *scan_list)
 	list_sort(NULL, scan_list, &zpci_cmp_rid);
 	list_for_each_entry_safe(zdev, tmp, scan_list, entry) {
 		list_del_init(&zdev->entry);
-		zpci_add_device(zdev);
+		if (zpci_add_device(zdev))
+			kfree(zdev);
 	}
 }
 
diff --git a/arch/s390/pci/pci_event.c b/arch/s390/pci/pci_event.c
index 22c87fbc8c6c..e9211857a01d 100644
--- a/arch/s390/pci/pci_event.c
+++ b/arch/s390/pci/pci_event.c
@@ -330,7 +330,10 @@ static void __zpci_event_availability(struct zpci_ccdf_avail *ccdf)
 			zdev = zpci_create_device(ccdf->fid, ccdf->fh, ZPCI_FN_STATE_CONFIGURED);
 			if (IS_ERR(zdev))
 				break;
-			zpci_add_device(zdev);
+			if (zpci_add_device(zdev)) {
+				kfree(zdev);
+				break;
+			}
 		} else {
 			/* the configuration request may be stale */
 			if (zdev->state != ZPCI_FN_STATE_STANDBY)
@@ -344,7 +347,10 @@ static void __zpci_event_availability(struct zpci_ccdf_avail *ccdf)
 			zdev = zpci_create_device(ccdf->fid, ccdf->fh, ZPCI_FN_STATE_STANDBY);
 			if (IS_ERR(zdev))
 				break;
-			zpci_add_device(zdev);
+			if (zpci_add_device(zdev)) {
+				kfree(zdev);
+				break;
+			}
 		} else {
 			zpci_update_fh(zdev, ccdf->fh);
 		}
-- 
2.48.1




More information about the kernel-team mailing list