Kernel KVM virtualization development
 help / color / mirror / Atom feed
* [PATCH v7 00/10] s390/vfio_ccw fixes
@ 2026-07-27 19:22 Eric Farman
  2026-07-27 19:22 ` [PATCH v7 01/10] s390/vfio_ccw: free all memory if cp_init() fails Eric Farman
                   ` (9 more replies)
  0 siblings, 10 replies; 21+ messages in thread
From: Eric Farman @ 2026-07-27 19:22 UTC (permalink / raw)
  To: linux-s390, kvm, linux-kernel
  Cc: Matthew Rosato, Halil Pasic, Christian Borntraeger, Eric Farman

This series addresses some pre-existing issues found in the
s390 vfio_ccw (DASD passthrough) driver.

Patches 1-6 are identical to v6.
Patches 7-10 are a continued attempt to improve access to private->cp .

v1: https://lore.kernel.org/r/20260714232208.1683788-1-farman@linux.ibm.com/
v2: https://lore.kernel.org/r/20260720201931.976660-1-farman@linux.ibm.com/
v3: https://lore.kernel.org/r/20260723174751.1180334-1-farman@linux.ibm.com/
v4: https://lore.kernel.org/r/20260725152705.3958100-1-farman@linux.ibm.com/
v5: https://lore.kernel.org/r/20260726040129.2946151-1-farman@linux.ibm.com/
v6: https://lore.kernel.org/r/20260726172153.1818056-1-farman@linux.ibm.com/
v6->v7:
 - [sashiko/EF] Replace the cp_mutex idea (from v3+) with an expanded use of
   io_mutex (as in v1), but with the workqueue idea for the FSM not-operational
   case that was added in v5

Eric Farman (10):
  s390/vfio_ccw: free all memory if cp_init() fails
  s390/vfio_ccw: limit the number of channel program segments
  s390/vfio_ccw: fix out of bounds check on CCW array
  s390/vfio_ccw: ensure first IDAW remains constant
  s390/vfio_ccw: calculate idal length based on idaw type
  s390/vfio_ccw: ensure index for read/write regions are within range
  s390/vfio_ccw: cancel existing workqueues
  s390/vfio_ccw: move cp cleanup out of not operational
  s390/vfio_ccw: selectively expand io_mutex
  s390/vfio_ccw: implement a crw lock

 drivers/s390/cio/vfio_ccw_async.c   | 16 +++++++
 drivers/s390/cio/vfio_ccw_chp.c     | 31 ++++++++++--
 drivers/s390/cio/vfio_ccw_cp.c      | 74 +++++++++++++++++++++--------
 drivers/s390/cio/vfio_ccw_cp.h      | 10 ++++
 drivers/s390/cio/vfio_ccw_drv.c     | 21 +++++++-
 drivers/s390/cio/vfio_ccw_fsm.c     |  8 +++-
 drivers/s390/cio/vfio_ccw_ops.c     | 22 +++++++--
 drivers/s390/cio/vfio_ccw_private.h | 10 +++-
 8 files changed, 159 insertions(+), 33 deletions(-)

-- 
2.53.0


^ permalink raw reply	[flat|nested] 21+ messages in thread

* [PATCH v7 01/10] s390/vfio_ccw: free all memory if cp_init() fails
  2026-07-27 19:22 [PATCH v7 00/10] s390/vfio_ccw fixes Eric Farman
@ 2026-07-27 19:22 ` Eric Farman
  2026-07-27 19:58   ` sashiko-bot
  2026-07-27 19:22 ` [PATCH v7 02/10] s390/vfio_ccw: limit the number of channel program segments Eric Farman
                   ` (8 subsequent siblings)
  9 siblings, 1 reply; 21+ messages in thread
From: Eric Farman @ 2026-07-27 19:22 UTC (permalink / raw)
  To: linux-s390, kvm, linux-kernel
  Cc: Matthew Rosato, Halil Pasic, Christian Borntraeger, Eric Farman,
	stable, Farhan Ali

The routine cp_free() is called to unpin/free any memory once an I/O
is completed successfully, or if cp_prefetch() fails. But if cp_init()
fails, and cp->initialized is not enabled, the same routine cannot be
used to free all the memory.

An attempt to address this exists in ccwchain_handle_ccw(), where a
single call to ccwchain_free() is made for the currently-processed
CCW segment. But this will leak other segments (created as a result
of a Transfer in Channel) that had been allocated as part of the same
channel program.

Address this by performing the cleanup outside of the recursive
ccwchain_handle_ccw()/ccwchain_loop_tic() logic.

Fixes: 8b515be512a2 ("vfio-ccw: Fix memory leak and don't call cp_free in cp_init")
Cc: stable@vger.kernel.org
Reviewed-by: Farhan Ali<alifm@linux.ibm.com>
Reviewed-by: Matthew Rosato <mjrosato@linux.ibm.com>
Signed-off-by: Eric Farman <farman@linux.ibm.com>
---
 drivers/s390/cio/vfio_ccw_cp.c | 22 ++++++++++++++++++----
 1 file changed, 18 insertions(+), 4 deletions(-)

diff --git a/drivers/s390/cio/vfio_ccw_cp.c b/drivers/s390/cio/vfio_ccw_cp.c
index 7561aa7d3e01..086d1b54bdb0 100644
--- a/drivers/s390/cio/vfio_ccw_cp.c
+++ b/drivers/s390/cio/vfio_ccw_cp.c
@@ -455,9 +455,6 @@ static int ccwchain_handle_ccw(dma32_t cda, struct channel_program *cp)
 	/* Loop for tics on this new chain. */
 	ret = ccwchain_loop_tic(chain, cp);
 
-	if (ret)
-		ccwchain_free(chain);
-
 	return ret;
 }
 
@@ -486,6 +483,23 @@ static int ccwchain_loop_tic(struct ccwchain *chain, struct channel_program *cp)
 	return 0;
 }
 
+static int ccwchain_build_ccws(dma32_t cda, struct channel_program *cp)
+{
+	struct ccwchain *chain, *temp;
+	int ret;
+
+	ret = ccwchain_handle_ccw(cda, cp);
+
+	if (ret) {
+		/* Cleanup if an error occurred */
+		list_for_each_entry_safe(chain, temp, &cp->ccwchain_list, next) {
+			ccwchain_free(chain);
+		}
+	}
+
+	return ret;
+}
+
 static int ccwchain_fetch_tic(struct ccw1 *ccw,
 			      struct channel_program *cp)
 {
@@ -735,7 +749,7 @@ int cp_init(struct channel_program *cp, union orb *orb)
 	memcpy(&cp->orb, orb, sizeof(*orb));
 
 	/* Build a ccwchain for the first CCW segment */
-	ret = ccwchain_handle_ccw(orb->cmd.cpa, cp);
+	ret = ccwchain_build_ccws(orb->cmd.cpa, cp);
 
 	if (!ret)
 		cp->initialized = true;
-- 
2.53.0


^ permalink raw reply related	[flat|nested] 21+ messages in thread

* [PATCH v7 02/10] s390/vfio_ccw: limit the number of channel program segments
  2026-07-27 19:22 [PATCH v7 00/10] s390/vfio_ccw fixes Eric Farman
  2026-07-27 19:22 ` [PATCH v7 01/10] s390/vfio_ccw: free all memory if cp_init() fails Eric Farman
@ 2026-07-27 19:22 ` Eric Farman
  2026-07-27 19:52   ` sashiko-bot
  2026-07-27 19:22 ` [PATCH v7 03/10] s390/vfio_ccw: fix out of bounds check on CCW array Eric Farman
                   ` (7 subsequent siblings)
  9 siblings, 1 reply; 21+ messages in thread
From: Eric Farman @ 2026-07-27 19:22 UTC (permalink / raw)
  To: linux-s390, kvm, linux-kernel
  Cc: Matthew Rosato, Halil Pasic, Christian Borntraeger, Eric Farman,
	stable

The processing of channel programs, and the CCWs within them, is done
recursively. As such, there is an arbitrary (but not architectural)
limit to the number of CCWs that can exist in a single channel program.

The vfio-ccw logic breaks these channel programs into segments whenever
it encounters a Transfer-In-Channel (TIC) CCW, and the combined number
of segments count towards the global limit. Impose an equivalent limit
to the number of segments until such logic can be made non-recursive.

Fixes: 0a19e61e6d4c ("vfio: ccw: introduce channel program interfaces")
Cc: stable@vger.kernel.org
Reviewed-by: Matthew Rosato <mjrosato@linux.ibm.com>
Signed-off-by: Eric Farman <farman@linux.ibm.com>
---
 drivers/s390/cio/vfio_ccw_cp.c | 6 ++++++
 drivers/s390/cio/vfio_ccw_cp.h | 8 ++++++++
 2 files changed, 14 insertions(+)

diff --git a/drivers/s390/cio/vfio_ccw_cp.c b/drivers/s390/cio/vfio_ccw_cp.c
index 086d1b54bdb0..1c2890d139c6 100644
--- a/drivers/s390/cio/vfio_ccw_cp.c
+++ b/drivers/s390/cio/vfio_ccw_cp.c
@@ -332,6 +332,7 @@ static struct ccwchain *ccwchain_alloc(struct channel_program *cp, int len)
 		goto out_err;
 
 	list_add_tail(&chain->next, &cp->ccwchain_list);
+	cp->ccwchain_count++;
 
 	return chain;
 
@@ -441,6 +442,10 @@ static int ccwchain_handle_ccw(dma32_t cda, struct channel_program *cp)
 	if (len < 0)
 		return len;
 
+	/* Limit number of chains in a single channel program */
+	if (cp->ccwchain_count >= CCWCHAIN_COUNT_MAX)
+		return -EINVAL;
+
 	/* Need alloc a new chain for this one. */
 	chain = ccwchain_alloc(cp, len);
 	if (!chain)
@@ -745,6 +750,7 @@ int cp_init(struct channel_program *cp, union orb *orb)
 			vdev->dev,
 			"Prefetching channel program even though prefetch not specified in ORB");
 
+	cp->ccwchain_count = 0;
 	INIT_LIST_HEAD(&cp->ccwchain_list);
 	memcpy(&cp->orb, orb, sizeof(*orb));
 
diff --git a/drivers/s390/cio/vfio_ccw_cp.h b/drivers/s390/cio/vfio_ccw_cp.h
index fc31eb699807..a9b1d8dbc6f6 100644
--- a/drivers/s390/cio/vfio_ccw_cp.h
+++ b/drivers/s390/cio/vfio_ccw_cp.h
@@ -23,11 +23,18 @@
  */
 #define CCWCHAIN_LEN_MAX	256
 
+/*
+ * Maximum number of chains
+ */
+#define CCWCHAIN_COUNT_MAX	16
+
 /**
  * struct channel_program - manage information for channel program
  * @ccwchain_list: list head of ccwchains
  * @orb: orb for the currently processed ssch request
  * @initialized: whether this instance is actually initialized
+ * @guest_cp: copy of guest channel program
+ * @ccwchain_count: number of channel program segments (linked by TIC)
  *
  * @ccwchain_list is the head of a ccwchain list, that contents the
  * translated result of the guest channel program that pointed out by
@@ -38,6 +45,7 @@ struct channel_program {
 	union orb orb;
 	bool initialized;
 	struct ccw1 *guest_cp;
+	unsigned int ccwchain_count;
 };
 
 int cp_init(struct channel_program *cp, union orb *orb);
-- 
2.53.0


^ permalink raw reply related	[flat|nested] 21+ messages in thread

* [PATCH v7 03/10] s390/vfio_ccw: fix out of bounds check on CCW array
  2026-07-27 19:22 [PATCH v7 00/10] s390/vfio_ccw fixes Eric Farman
  2026-07-27 19:22 ` [PATCH v7 01/10] s390/vfio_ccw: free all memory if cp_init() fails Eric Farman
  2026-07-27 19:22 ` [PATCH v7 02/10] s390/vfio_ccw: limit the number of channel program segments Eric Farman
@ 2026-07-27 19:22 ` Eric Farman
  2026-07-27 19:53   ` sashiko-bot
  2026-07-27 19:22 ` [PATCH v7 04/10] s390/vfio_ccw: ensure first IDAW remains constant Eric Farman
                   ` (6 subsequent siblings)
  9 siblings, 1 reply; 21+ messages in thread
From: Eric Farman @ 2026-07-27 19:22 UTC (permalink / raw)
  To: linux-s390, kvm, linux-kernel
  Cc: Matthew Rosato, Halil Pasic, Christian Borntraeger, Eric Farman,
	stable

The routine ccwchain_calc_length() counts the number of channel
command words (CCWs) that are chained together in a single channel
program, and rejects anything larger than CCWCHAIN_LEN_MAX (256) CCWs.

The loop itself is "do..while (count < 257)", and while the logic in
is_cpa_within_range() correctly adjusts between the 0-index array of
CCWs and the count of CCWs starting at 1, this means it would look
at a possible 257th CCW before ending the loop and (correctly)
returning an error.

Fix this by restructuring the loop to break as soon as 256 CCWs
(thus indexes 0-255) are examined, without looking at memory
outside the range.

Fixes: 0a19e61e6d4c ("vfio: ccw: introduce channel program interfaces")
Cc: stable@vger.kernel.org
Reviewed-by: Matthew Rosato <mjrosato@linux.ibm.com>
Signed-off-by: Eric Farman <farman@linux.ibm.com>
---
 drivers/s390/cio/vfio_ccw_cp.c | 17 +++++------------
 1 file changed, 5 insertions(+), 12 deletions(-)

diff --git a/drivers/s390/cio/vfio_ccw_cp.c b/drivers/s390/cio/vfio_ccw_cp.c
index 1c2890d139c6..af632f9d5453 100644
--- a/drivers/s390/cio/vfio_ccw_cp.c
+++ b/drivers/s390/cio/vfio_ccw_cp.c
@@ -377,11 +377,9 @@ static void ccwchain_cda_free(struct ccwchain *chain, int idx)
 static int ccwchain_calc_length(u64 iova, struct channel_program *cp)
 {
 	struct ccw1 *ccw = cp->guest_cp;
-	int cnt = 0;
-
-	do {
-		cnt++;
+	int cnt;
 
+	for (cnt = 1; cnt <= CCWCHAIN_LEN_MAX; cnt++, ccw++) {
 		/*
 		 * We want to keep counting if the current CCW has the
 		 * command-chaining flag enabled, or if it is a TIC CCW
@@ -391,15 +389,10 @@ static int ccwchain_calc_length(u64 iova, struct channel_program *cp)
 		 * after the TIC, depending on the results of its operation.
 		 */
 		if (!ccw_is_chain(ccw) && !is_tic_within_range(ccw, iova, cnt))
-			break;
-
-		ccw++;
-	} while (cnt < CCWCHAIN_LEN_MAX + 1);
-
-	if (cnt == CCWCHAIN_LEN_MAX + 1)
-		cnt = -EINVAL;
+			return cnt;
+	}
 
-	return cnt;
+	return -EINVAL;
 }
 
 static int tic_target_chain_exists(struct ccw1 *tic, struct channel_program *cp)
-- 
2.53.0


^ permalink raw reply related	[flat|nested] 21+ messages in thread

* [PATCH v7 04/10] s390/vfio_ccw: ensure first IDAW remains constant
  2026-07-27 19:22 [PATCH v7 00/10] s390/vfio_ccw fixes Eric Farman
                   ` (2 preceding siblings ...)
  2026-07-27 19:22 ` [PATCH v7 03/10] s390/vfio_ccw: fix out of bounds check on CCW array Eric Farman
@ 2026-07-27 19:22 ` Eric Farman
  2026-07-27 19:59   ` sashiko-bot
  2026-07-27 19:22 ` [PATCH v7 05/10] s390/vfio_ccw: calculate idal length based on idaw type Eric Farman
                   ` (5 subsequent siblings)
  9 siblings, 1 reply; 21+ messages in thread
From: Eric Farman @ 2026-07-27 19:22 UTC (permalink / raw)
  To: linux-s390, kvm, linux-kernel
  Cc: Matthew Rosato, Halil Pasic, Christian Borntraeger, Eric Farman,
	stable

The first IDAW in a list does not need to be on a 2K/4K boundary
like all others, and so is read separately to accurately calculate
the size of the buffer needed to read the full IDAL.

Verify that the address found in the first IDAW is unchanged between
reads, to ensure a consistent set of IDAWs being worked with.

Fixes: 01aa26c672c0 ("s390/cio: Combine direct and indirect CCW paths")
Cc: stable@vger.kernel.org
Reviewed-by: Matthew Rosato <mjrosato@linux.ibm.com>
Signed-off-by: Eric Farman <farman@linux.ibm.com>
---
 drivers/s390/cio/vfio_ccw_cp.c | 16 ++++++++++++++++
 drivers/s390/cio/vfio_ccw_cp.h |  2 ++
 2 files changed, 18 insertions(+)

diff --git a/drivers/s390/cio/vfio_ccw_cp.c b/drivers/s390/cio/vfio_ccw_cp.c
index af632f9d5453..6275794751cb 100644
--- a/drivers/s390/cio/vfio_ccw_cp.c
+++ b/drivers/s390/cio/vfio_ccw_cp.c
@@ -523,6 +523,7 @@ static dma64_t *get_guest_idal(struct ccw1 *ccw, struct channel_program *cp, int
 		&container_of(cp, struct vfio_ccw_private, cp)->vdev;
 	dma64_t *idaws;
 	dma32_t *idaws_f1;
+	u64 first_idaw;
 	int idal_len = idaw_nr * sizeof(*idaws);
 	int idaw_size = idal_is_2k(cp) ? PAGE_SIZE / 2 : PAGE_SIZE;
 	int idaw_mask = ~(idaw_size - 1);
@@ -539,6 +540,18 @@ static dma64_t *get_guest_idal(struct ccw1 *ccw, struct channel_program *cp, int
 			kfree(idaws);
 			return ERR_PTR(ret);
 		}
+
+		idaws_f1 = (dma32_t *)idaws;
+		if (cp->orb.cmd.c64)
+			first_idaw = dma64_to_u64(idaws[0]);
+		else
+			first_idaw = dma32_to_u32(idaws_f1[0]);
+
+		/* Unexpected mismatch from earlier read */
+		if (first_idaw != cp->guest_iova) {
+			kfree(idaws);
+			return ERR_PTR(-EINVAL);
+		}
 	} else {
 		/* Fabricate an IDAL based off CCW data address */
 		if (cp->orb.cmd.c64) {
@@ -604,6 +617,9 @@ static int ccw_count_idaws(struct ccw1 *ccw,
 		iova = dma32_to_u32(ccw->cda);
 	}
 
+	/* Save the read address for later */
+	cp->guest_iova = iova;
+
 	/* Format-1 IDAWs operate on 2K each */
 	if (!cp->orb.cmd.c64)
 		return idal_2k_nr_words((void *)iova, bytes);
diff --git a/drivers/s390/cio/vfio_ccw_cp.h b/drivers/s390/cio/vfio_ccw_cp.h
index a9b1d8dbc6f6..9af98ff12d67 100644
--- a/drivers/s390/cio/vfio_ccw_cp.h
+++ b/drivers/s390/cio/vfio_ccw_cp.h
@@ -35,6 +35,7 @@
  * @initialized: whether this instance is actually initialized
  * @guest_cp: copy of guest channel program
  * @ccwchain_count: number of channel program segments (linked by TIC)
+ * @guest_iova: first data address of a guest channel program
  *
  * @ccwchain_list is the head of a ccwchain list, that contents the
  * translated result of the guest channel program that pointed out by
@@ -46,6 +47,7 @@ struct channel_program {
 	bool initialized;
 	struct ccw1 *guest_cp;
 	unsigned int ccwchain_count;
+	u64 guest_iova;
 };
 
 int cp_init(struct channel_program *cp, union orb *orb);
-- 
2.53.0


^ permalink raw reply related	[flat|nested] 21+ messages in thread

* [PATCH v7 05/10] s390/vfio_ccw: calculate idal length based on idaw type
  2026-07-27 19:22 [PATCH v7 00/10] s390/vfio_ccw fixes Eric Farman
                   ` (3 preceding siblings ...)
  2026-07-27 19:22 ` [PATCH v7 04/10] s390/vfio_ccw: ensure first IDAW remains constant Eric Farman
@ 2026-07-27 19:22 ` Eric Farman
  2026-07-27 19:54   ` sashiko-bot
  2026-07-27 19:22 ` [PATCH v7 06/10] s390/vfio_ccw: ensure index for read/write regions are within range Eric Farman
                   ` (4 subsequent siblings)
  9 siblings, 1 reply; 21+ messages in thread
From: Eric Farman @ 2026-07-27 19:22 UTC (permalink / raw)
  To: linux-s390, kvm, linux-kernel
  Cc: Matthew Rosato, Halil Pasic, Christian Borntraeger, Eric Farman,
	sashiko-bot, stable

Sashiko pointed out that get_guest_idal() unconditionally calculates
the length of the IDAL presuming everything is a Format-2 IDAW.

The output of vfio-ccw is always Format-2, but the input can be either
Format-1 (31-bit addresses) or Format-2 (64-bit addresses). As a result,
the size of the guest IDAL may be incorrect and should be trimmed down.

Reported-by: sashiko-bot <sashiko-bot@kernel.org>
Link: https://lore.kernel.org/r/20260720203400.7328E1F000E9@smtp.kernel.org/
Fixes: 1b676fe3d9d3 ("vfio/ccw: handle a guest Format-1 IDAL")
Cc: stable@vger.kernel.org
Reviewed-by: Matthew Rosato <mjrosato@linux.ibm.com>
Signed-off-by: Eric Farman <farman@linux.ibm.com>
---
 drivers/s390/cio/vfio_ccw_cp.c | 5 +++--
 1 file changed, 3 insertions(+), 2 deletions(-)

diff --git a/drivers/s390/cio/vfio_ccw_cp.c b/drivers/s390/cio/vfio_ccw_cp.c
index 6275794751cb..5ef082b8289a 100644
--- a/drivers/s390/cio/vfio_ccw_cp.c
+++ b/drivers/s390/cio/vfio_ccw_cp.c
@@ -233,6 +233,7 @@ static void convert_ccw0_to_ccw1(struct ccw1 *source, unsigned long len)
 }
 
 #define idal_is_2k(_cp) (!(_cp)->orb.cmd.c64 || (_cp)->orb.cmd.i2k)
+#define get_idaw_size(_cp) ((_cp)->orb.cmd.c64 ? sizeof(u64) : sizeof(u32))
 
 /*
  * Helpers to operate ccwchain.
@@ -524,7 +525,7 @@ static dma64_t *get_guest_idal(struct ccw1 *ccw, struct channel_program *cp, int
 	dma64_t *idaws;
 	dma32_t *idaws_f1;
 	u64 first_idaw;
-	int idal_len = idaw_nr * sizeof(*idaws);
+	int idal_len = idaw_nr * get_idaw_size(cp);
 	int idaw_size = idal_is_2k(cp) ? PAGE_SIZE / 2 : PAGE_SIZE;
 	int idaw_mask = ~(idaw_size - 1);
 	int i, ret;
@@ -593,7 +594,7 @@ static int ccw_count_idaws(struct ccw1 *ccw,
 	struct vfio_device *vdev =
 		&container_of(cp, struct vfio_ccw_private, cp)->vdev;
 	u64 iova;
-	int size = cp->orb.cmd.c64 ? sizeof(u64) : sizeof(u32);
+	int size = get_idaw_size(cp);
 	int ret;
 	int bytes = 1;
 
-- 
2.53.0


^ permalink raw reply related	[flat|nested] 21+ messages in thread

* [PATCH v7 06/10] s390/vfio_ccw: ensure index for read/write regions are within range
  2026-07-27 19:22 [PATCH v7 00/10] s390/vfio_ccw fixes Eric Farman
                   ` (4 preceding siblings ...)
  2026-07-27 19:22 ` [PATCH v7 05/10] s390/vfio_ccw: calculate idal length based on idaw type Eric Farman
@ 2026-07-27 19:22 ` Eric Farman
  2026-07-27 20:03   ` sashiko-bot
  2026-07-27 19:22 ` [PATCH v7 07/10] s390/vfio_ccw: cancel existing workqueues Eric Farman
                   ` (3 subsequent siblings)
  9 siblings, 1 reply; 21+ messages in thread
From: Eric Farman @ 2026-07-27 19:22 UTC (permalink / raw)
  To: linux-s390, kvm, linux-kernel
  Cc: Matthew Rosato, Halil Pasic, Christian Borntraeger, Eric Farman,
	stable, Cornelia Huck

The introduction of the capability chain rightly clamped the
region indexes to the range of the capabilities itself, but
neglected to do so for the existing read/write regions which
should also be enforced.

Fixes: db8e5d17ac03 ("vfio-ccw: add capabilities chain")
Cc: stable@vger.kernel.org
Cc: Cornelia Huck <cohuck@redhat.com>
Reviewed-by: Matthew Rosato <mjrosato@linux.ibm.com>
Signed-off-by: Eric Farman <farman@linux.ibm.com>
---
 drivers/s390/cio/vfio_ccw_async.c | 16 ++++++++++++++++
 drivers/s390/cio/vfio_ccw_chp.c   | 15 +++++++++++++++
 drivers/s390/cio/vfio_ccw_ops.c   |  7 +++----
 3 files changed, 34 insertions(+), 4 deletions(-)

diff --git a/drivers/s390/cio/vfio_ccw_async.c b/drivers/s390/cio/vfio_ccw_async.c
index 420d89ba7f83..4aff0b58fa5d 100644
--- a/drivers/s390/cio/vfio_ccw_async.c
+++ b/drivers/s390/cio/vfio_ccw_async.c
@@ -8,6 +8,7 @@
  */
 
 #include <linux/vfio.h>
+#include <linux/nospec.h>
 
 #include "vfio_ccw_private.h"
 
@@ -24,11 +25,20 @@ static ssize_t vfio_ccw_async_region_read(struct vfio_ccw_private *private,
 		return -EINVAL;
 
 	mutex_lock(&private->io_mutex);
+
+	if (i >= private->num_regions) {
+		ret = -EINVAL;
+		goto out_unlock;
+	}
+
+	i = array_index_nospec(i, private->num_regions);
 	region = private->region[i].data;
 	if (copy_to_user(buf, (void *)region + pos, count))
 		ret = -EFAULT;
 	else
 		ret = count;
+
+out_unlock:
 	mutex_unlock(&private->io_mutex);
 	return ret;
 }
@@ -48,6 +58,12 @@ static ssize_t vfio_ccw_async_region_write(struct vfio_ccw_private *private,
 	if (!mutex_trylock(&private->io_mutex))
 		return -EAGAIN;
 
+	if (i >= private->num_regions) {
+		ret = -EINVAL;
+		goto out_unlock;
+	}
+
+	i = array_index_nospec(i, private->num_regions);
 	region = private->region[i].data;
 	if (copy_from_user((void *)region + pos, buf, count)) {
 		ret = -EFAULT;
diff --git a/drivers/s390/cio/vfio_ccw_chp.c b/drivers/s390/cio/vfio_ccw_chp.c
index 38c176cf6295..f3015132d4b5 100644
--- a/drivers/s390/cio/vfio_ccw_chp.c
+++ b/drivers/s390/cio/vfio_ccw_chp.c
@@ -9,6 +9,7 @@
  */
 
 #include <linux/slab.h>
+#include <linux/nospec.h>
 #include <linux/vfio.h>
 #include "vfio_ccw_private.h"
 
@@ -26,6 +27,13 @@ static ssize_t vfio_ccw_schib_region_read(struct vfio_ccw_private *private,
 		return -EINVAL;
 
 	mutex_lock(&private->io_mutex);
+
+	if (i >= private->num_regions) {
+		ret = -EINVAL;
+		goto out;
+	}
+
+	i = array_index_nospec(i, private->num_regions);
 	region = private->region[i].data;
 
 	if (cio_update_schib(sch)) {
@@ -97,6 +105,12 @@ static ssize_t vfio_ccw_crw_region_read(struct vfio_ccw_private *private,
 		list_del(&crw->next);
 
 	mutex_lock(&private->io_mutex);
+	if (i >= private->num_regions) {
+		ret = -EINVAL;
+		goto out;
+	}
+
+	i = array_index_nospec(i, private->num_regions);
 	region = private->region[i].data;
 
 	if (crw)
@@ -109,6 +123,7 @@ static ssize_t vfio_ccw_crw_region_read(struct vfio_ccw_private *private,
 
 	region->crw = 0;
 
+out:
 	mutex_unlock(&private->io_mutex);
 
 	kfree(crw);
diff --git a/drivers/s390/cio/vfio_ccw_ops.c b/drivers/s390/cio/vfio_ccw_ops.c
index 45ec722d25ea..032a1cdf4df7 100644
--- a/drivers/s390/cio/vfio_ccw_ops.c
+++ b/drivers/s390/cio/vfio_ccw_ops.c
@@ -243,6 +243,7 @@ static ssize_t vfio_ccw_mdev_read(struct vfio_device *vdev,
 		return vfio_ccw_mdev_read_io_region(private, buf, count, ppos);
 	default:
 		index -= VFIO_CCW_NUM_REGIONS;
+		index = array_index_nospec(index, private->num_regions);
 		return private->region[index].ops->read(private, buf, count,
 							ppos);
 	}
@@ -295,6 +296,7 @@ static ssize_t vfio_ccw_mdev_write(struct vfio_device *vdev,
 		return vfio_ccw_mdev_write_io_region(private, buf, count, ppos);
 	default:
 		index -= VFIO_CCW_NUM_REGIONS;
+		index = array_index_nospec(index, private->num_regions);
 		return private->region[index].ops->write(private, buf, count,
 							 ppos);
 	}
@@ -338,11 +340,8 @@ static int vfio_ccw_mdev_ioctl_get_region_info(struct vfio_device *vdev,
 		    VFIO_CCW_NUM_REGIONS + private->num_regions)
 			return -EINVAL;
 
-		info->index = array_index_nospec(info->index,
-						 VFIO_CCW_NUM_REGIONS +
-						 private->num_regions);
-
 		i = info->index - VFIO_CCW_NUM_REGIONS;
+		i = array_index_nospec(i, private->num_regions);
 
 		info->offset = VFIO_CCW_INDEX_TO_OFFSET(info->index);
 		info->size = private->region[i].size;
-- 
2.53.0


^ permalink raw reply related	[flat|nested] 21+ messages in thread

* [PATCH v7 07/10] s390/vfio_ccw: cancel existing workqueues
  2026-07-27 19:22 [PATCH v7 00/10] s390/vfio_ccw fixes Eric Farman
                   ` (5 preceding siblings ...)
  2026-07-27 19:22 ` [PATCH v7 06/10] s390/vfio_ccw: ensure index for read/write regions are within range Eric Farman
@ 2026-07-27 19:22 ` Eric Farman
  2026-07-27 20:02   ` sashiko-bot
  2026-07-27 19:22 ` [PATCH v7 08/10] s390/vfio_ccw: move cp cleanup out of not operational Eric Farman
                   ` (2 subsequent siblings)
  9 siblings, 1 reply; 21+ messages in thread
From: Eric Farman @ 2026-07-27 19:22 UTC (permalink / raw)
  To: linux-s390, kvm, linux-kernel
  Cc: Matthew Rosato, Halil Pasic, Christian Borntraeger, Eric Farman,
	stable

The initialization of the io_work and crw_work workqueues begs the
question of whether they should be un-initialized. Add the corresponding
cleanup tags in _release_dev to ensure work isn't dispatched after
the private struct is free'd.

Suggested-by: Matthew Rosato <mjrosato@linux.ibm.com>
Fixes: e5f84dbaea59 ("vfio: ccw: return I/O results asynchronously")
Fixes: 3f02cb2fd9d2 ("vfio-ccw: Wire up the CRW irq and CRW region")
Cc: stable@vger.kernel.org
Signed-off-by: Eric Farman <farman@linux.ibm.com>
---
 drivers/s390/cio/vfio_ccw_ops.c | 8 ++++++++
 1 file changed, 8 insertions(+)

diff --git a/drivers/s390/cio/vfio_ccw_ops.c b/drivers/s390/cio/vfio_ccw_ops.c
index 032a1cdf4df7..bd488e40e153 100644
--- a/drivers/s390/cio/vfio_ccw_ops.c
+++ b/drivers/s390/cio/vfio_ccw_ops.c
@@ -136,6 +136,10 @@ static void vfio_ccw_mdev_release_dev(struct vfio_device *vdev)
 		kfree(crw);
 	}
 
+	/* Should be empty, but just in case */
+	cancel_work_sync(&private->io_work);
+	cancel_work_sync(&private->crw_work);
+
 	kmem_cache_free(vfio_ccw_crw_region, private->crw_region);
 	kmem_cache_free(vfio_ccw_schib_region, private->schib_region);
 	kmem_cache_free(vfio_ccw_cmd_region, private->cmd_region);
@@ -202,6 +206,10 @@ static void vfio_ccw_mdev_close_device(struct vfio_device *vdev)
 		container_of(vdev, struct vfio_ccw_private, vdev);
 
 	vfio_ccw_fsm_event(private, VFIO_CCW_EVENT_CLOSE);
+
+	cancel_work_sync(&private->io_work);
+	cancel_work_sync(&private->crw_work);
+
 	vfio_ccw_unregister_dev_regions(private);
 }
 
-- 
2.53.0


^ permalink raw reply related	[flat|nested] 21+ messages in thread

* [PATCH v7 08/10] s390/vfio_ccw: move cp cleanup out of not operational
  2026-07-27 19:22 [PATCH v7 00/10] s390/vfio_ccw fixes Eric Farman
                   ` (6 preceding siblings ...)
  2026-07-27 19:22 ` [PATCH v7 07/10] s390/vfio_ccw: cancel existing workqueues Eric Farman
@ 2026-07-27 19:22 ` Eric Farman
  2026-07-27 20:04   ` sashiko-bot
  2026-07-27 19:22 ` [PATCH v7 09/10] s390/vfio_ccw: selectively expand io_mutex Eric Farman
  2026-07-27 19:22 ` [PATCH v7 10/10] s390/vfio_ccw: implement a crw lock Eric Farman
  9 siblings, 1 reply; 21+ messages in thread
From: Eric Farman @ 2026-07-27 19:22 UTC (permalink / raw)
  To: linux-s390, kvm, linux-kernel
  Cc: Matthew Rosato, Halil Pasic, Christian Borntraeger, Eric Farman,
	stable

The fsm_notoper() routine is called when the device has been
lost, and is (by definition) no longer operational. Since this
can happen asynchronously from the normal behavior of the
driver, the cleanup may happen when holding other locks
in the calling sequence (notably, the cio subchannel lock).

Push the cleanup of the private->cp resources to a workqueue,
where it can be done out from under that lock sequence and
(soon) under its own serialization mechanism.

Fixes: 204b394a23ad ("vfio/ccw: Move FSM open/close to MDEV open/close")
Cc: stable@vger.kernel.org
Signed-off-by: Eric Farman <farman@linux.ibm.com>
---
 drivers/s390/cio/vfio_ccw_drv.c     | 9 +++++++++
 drivers/s390/cio/vfio_ccw_fsm.c     | 3 +--
 drivers/s390/cio/vfio_ccw_ops.c     | 3 +++
 drivers/s390/cio/vfio_ccw_private.h | 3 +++
 4 files changed, 16 insertions(+), 2 deletions(-)

diff --git a/drivers/s390/cio/vfio_ccw_drv.c b/drivers/s390/cio/vfio_ccw_drv.c
index 1a095085bc72..c197ad5ab580 100644
--- a/drivers/s390/cio/vfio_ccw_drv.c
+++ b/drivers/s390/cio/vfio_ccw_drv.c
@@ -125,6 +125,15 @@ void vfio_ccw_crw_todo(struct work_struct *work)
 		eventfd_signal(private->crw_trigger);
 }
 
+void vfio_ccw_notoper_todo(struct work_struct *work)
+{
+	struct vfio_ccw_private *private;
+
+	private = container_of(work, struct vfio_ccw_private, notoper_work);
+
+	cp_free(&private->cp);
+}
+
 /*
  * Css driver callbacks
  */
diff --git a/drivers/s390/cio/vfio_ccw_fsm.c b/drivers/s390/cio/vfio_ccw_fsm.c
index 4d7988ea47ef..4d47a3c7b9a0 100644
--- a/drivers/s390/cio/vfio_ccw_fsm.c
+++ b/drivers/s390/cio/vfio_ccw_fsm.c
@@ -170,8 +170,7 @@ static void fsm_notoper(struct vfio_ccw_private *private,
 	css_sched_sch_todo(sch, SCH_TODO_UNREG);
 	private->state = VFIO_CCW_STATE_NOT_OPER;
 
-	/* This is usually handled during CLOSE event */
-	cp_free(&private->cp);
+	queue_work(vfio_ccw_work_q, &private->notoper_work);
 }
 
 /*
diff --git a/drivers/s390/cio/vfio_ccw_ops.c b/drivers/s390/cio/vfio_ccw_ops.c
index bd488e40e153..8ec6b175d991 100644
--- a/drivers/s390/cio/vfio_ccw_ops.c
+++ b/drivers/s390/cio/vfio_ccw_ops.c
@@ -54,6 +54,7 @@ static int vfio_ccw_mdev_init_dev(struct vfio_device *vdev)
 	INIT_LIST_HEAD(&private->crw);
 	INIT_WORK(&private->io_work, vfio_ccw_sch_io_todo);
 	INIT_WORK(&private->crw_work, vfio_ccw_crw_todo);
+	INIT_WORK(&private->notoper_work, vfio_ccw_notoper_todo);
 
 	private->cp.guest_cp = kzalloc_objs(struct ccw1, CCWCHAIN_LEN_MAX);
 	if (!private->cp.guest_cp)
@@ -139,6 +140,7 @@ static void vfio_ccw_mdev_release_dev(struct vfio_device *vdev)
 	/* Should be empty, but just in case */
 	cancel_work_sync(&private->io_work);
 	cancel_work_sync(&private->crw_work);
+	cancel_work_sync(&private->notoper_work);
 
 	kmem_cache_free(vfio_ccw_crw_region, private->crw_region);
 	kmem_cache_free(vfio_ccw_schib_region, private->schib_region);
@@ -209,6 +211,7 @@ static void vfio_ccw_mdev_close_device(struct vfio_device *vdev)
 
 	cancel_work_sync(&private->io_work);
 	cancel_work_sync(&private->crw_work);
+	cancel_work_sync(&private->notoper_work);
 
 	vfio_ccw_unregister_dev_regions(private);
 }
diff --git a/drivers/s390/cio/vfio_ccw_private.h b/drivers/s390/cio/vfio_ccw_private.h
index 0501d4bbcdbd..e2256402b089 100644
--- a/drivers/s390/cio/vfio_ccw_private.h
+++ b/drivers/s390/cio/vfio_ccw_private.h
@@ -102,6 +102,7 @@ struct vfio_ccw_parent {
  * @req_trigger: eventfd ctx for signaling userspace to return device
  * @io_work: work for deferral process of I/O handling
  * @crw_work: work for deferral process of CRW handling
+ * @notoper_work: work for deferred processing in not-operational state
  */
 struct vfio_ccw_private {
 	struct vfio_device vdev;
@@ -125,11 +126,13 @@ struct vfio_ccw_private {
 	struct eventfd_ctx	*req_trigger;
 	struct work_struct	io_work;
 	struct work_struct	crw_work;
+	struct work_struct	notoper_work;
 } __aligned(8);
 
 int vfio_ccw_sch_quiesce(struct subchannel *sch);
 void vfio_ccw_sch_io_todo(struct work_struct *work);
 void vfio_ccw_crw_todo(struct work_struct *work);
+void vfio_ccw_notoper_todo(struct work_struct *work);
 
 extern struct mdev_driver vfio_ccw_mdev_driver;
 
-- 
2.53.0


^ permalink raw reply related	[flat|nested] 21+ messages in thread

* [PATCH v7 09/10] s390/vfio_ccw: selectively expand io_mutex
  2026-07-27 19:22 [PATCH v7 00/10] s390/vfio_ccw fixes Eric Farman
                   ` (7 preceding siblings ...)
  2026-07-27 19:22 ` [PATCH v7 08/10] s390/vfio_ccw: move cp cleanup out of not operational Eric Farman
@ 2026-07-27 19:22 ` Eric Farman
  2026-07-27 20:12   ` sashiko-bot
  2026-07-27 19:22 ` [PATCH v7 10/10] s390/vfio_ccw: implement a crw lock Eric Farman
  9 siblings, 1 reply; 21+ messages in thread
From: Eric Farman @ 2026-07-27 19:22 UTC (permalink / raw)
  To: linux-s390, kvm, linux-kernel
  Cc: Matthew Rosato, Halil Pasic, Christian Borntraeger, Eric Farman,
	stable

The io_mutex was defined to serialize the io_regions, but then has
also sort of been associated with the I/O themselves because of
the close relationship they share.

With the handful of races that are possible, the choices are either to:
 A) expand the scope of io_mutex to close these remaining windows, or
 B) reduce the scope of io_mutex to just io_region, and introduce a new
    lock mechanism for the remaining I/O resources

This patch implements A, since B brings with it a lot more interactions
that would need to be tracked and kept in a correct hierarchy.

The biggest functional change is the introduction of a workqueue
element for the cp_free() called out of fsm_notoper(), which is could
be run in an interrupt context and thus cannot be acquiring mutexes.

Fixes: 4f76617378ee ("vfio-ccw: protect the I/O region")
Cc: stable@vger.kernel.org
Signed-off-by: Eric Farman <farman@linux.ibm.com>
---
 drivers/s390/cio/vfio_ccw_chp.c     | 2 +-
 drivers/s390/cio/vfio_ccw_cp.c      | 8 +++++++-
 drivers/s390/cio/vfio_ccw_drv.c     | 6 ++++--
 drivers/s390/cio/vfio_ccw_fsm.c     | 5 +++++
 drivers/s390/cio/vfio_ccw_private.h | 3 ++-
 5 files changed, 19 insertions(+), 5 deletions(-)

diff --git a/drivers/s390/cio/vfio_ccw_chp.c b/drivers/s390/cio/vfio_ccw_chp.c
index f3015132d4b5..9269b54f5cfd 100644
--- a/drivers/s390/cio/vfio_ccw_chp.c
+++ b/drivers/s390/cio/vfio_ccw_chp.c
@@ -98,13 +98,13 @@ static ssize_t vfio_ccw_crw_region_read(struct vfio_ccw_private *private,
 	if (pos + count > sizeof(*region))
 		return -EINVAL;
 
+	mutex_lock(&private->io_mutex);
 	crw = list_first_entry_or_null(&private->crw,
 				       struct vfio_ccw_crw, next);
 
 	if (crw)
 		list_del(&crw->next);
 
-	mutex_lock(&private->io_mutex);
 	if (i >= private->num_regions) {
 		ret = -EINVAL;
 		goto out;
diff --git a/drivers/s390/cio/vfio_ccw_cp.c b/drivers/s390/cio/vfio_ccw_cp.c
index 5ef082b8289a..58722c4baa25 100644
--- a/drivers/s390/cio/vfio_ccw_cp.c
+++ b/drivers/s390/cio/vfio_ccw_cp.c
@@ -977,17 +977,23 @@ void cp_update_scsw(struct channel_program *cp, union scsw *scsw)
  */
 bool cp_iova_pinned(struct channel_program *cp, u64 iova, u64 length)
 {
+	struct vfio_ccw_private *private =
+		container_of(cp, struct vfio_ccw_private, cp);
 	struct ccwchain *chain;
 	int i;
 
 	if (!cp->initialized)
 		return false;
 
+	mutex_lock(&private->io_mutex);
 	list_for_each_entry(chain, &cp->ccwchain_list, next) {
 		for (i = 0; i < chain->ch_len; i++)
-			if (page_array_iova_pinned(&chain->ch_pa[i], iova, length))
+			if (page_array_iova_pinned(&chain->ch_pa[i], iova, length)) {
+				mutex_unlock(&private->io_mutex);
 				return true;
+			}
 	}
+	mutex_unlock(&private->io_mutex);
 
 	return false;
 }
diff --git a/drivers/s390/cio/vfio_ccw_drv.c b/drivers/s390/cio/vfio_ccw_drv.c
index c197ad5ab580..757ff5b2556e 100644
--- a/drivers/s390/cio/vfio_ccw_drv.c
+++ b/drivers/s390/cio/vfio_ccw_drv.c
@@ -91,6 +91,7 @@ void vfio_ccw_sch_io_todo(struct work_struct *work)
 
 	is_final = !(scsw_actl(&irb->scsw) &
 		     (SCSW_ACTL_DEVACT | SCSW_ACTL_SCHACT));
+	mutex_lock(&private->io_mutex);
 	if (scsw_is_solicited(&irb->scsw)) {
 		cp_update_scsw(&private->cp, &irb->scsw);
 		if (is_final && private->state == VFIO_CCW_STATE_CP_PENDING) {
@@ -98,9 +99,7 @@ void vfio_ccw_sch_io_todo(struct work_struct *work)
 			cp_is_finished = true;
 		}
 	}
-	mutex_lock(&private->io_mutex);
 	memcpy(private->io_region->irb_area, irb, sizeof(*irb));
-	mutex_unlock(&private->io_mutex);
 
 	/*
 	 * Reset to IDLE only if processing of a channel program
@@ -110,6 +109,7 @@ void vfio_ccw_sch_io_todo(struct work_struct *work)
 	 */
 	if (cp_is_finished)
 		private->state = VFIO_CCW_STATE_IDLE;
+	mutex_unlock(&private->io_mutex);
 
 	if (private->io_trigger)
 		eventfd_signal(private->io_trigger);
@@ -131,7 +131,9 @@ void vfio_ccw_notoper_todo(struct work_struct *work)
 
 	private = container_of(work, struct vfio_ccw_private, notoper_work);
 
+	mutex_lock(&private->io_mutex);
 	cp_free(&private->cp);
+	mutex_unlock(&private->io_mutex);
 }
 
 /*
diff --git a/drivers/s390/cio/vfio_ccw_fsm.c b/drivers/s390/cio/vfio_ccw_fsm.c
index 4d47a3c7b9a0..5fd94e9d5c61 100644
--- a/drivers/s390/cio/vfio_ccw_fsm.c
+++ b/drivers/s390/cio/vfio_ccw_fsm.c
@@ -170,6 +170,7 @@ static void fsm_notoper(struct vfio_ccw_private *private,
 	css_sched_sch_todo(sch, SCH_TODO_UNREG);
 	private->state = VFIO_CCW_STATE_NOT_OPER;
 
+	/* This routine could be called from IRQ context, so defer */
 	queue_work(vfio_ccw_work_q, &private->notoper_work);
 }
 
@@ -409,7 +410,11 @@ static void fsm_close(struct vfio_ccw_private *private,
 
 	private->state = VFIO_CCW_STATE_STANDBY;
 	spin_unlock_irq(&sch->lock);
+
+	mutex_lock(&private->io_mutex);
 	cp_free(&private->cp);
+	mutex_unlock(&private->io_mutex);
+
 	return;
 
 err_unlock:
diff --git a/drivers/s390/cio/vfio_ccw_private.h b/drivers/s390/cio/vfio_ccw_private.h
index e2256402b089..739121116ab6 100644
--- a/drivers/s390/cio/vfio_ccw_private.h
+++ b/drivers/s390/cio/vfio_ccw_private.h
@@ -88,7 +88,8 @@ struct vfio_ccw_parent {
  * @state: internal state of the device
  * @completion: synchronization helper of the I/O completion
  * @io_region: MMIO region to input/output I/O arguments/results
- * @io_mutex: protect against concurrent update of I/O regions
+ * @io_mutex: protect against concurrent update of I/O resources
+ *            and @cp lifecycle
  * @region: additional regions for other subchannel operations
  * @cmd_region: MMIO region for asynchronous I/O commands other than START
  * @schib_region: MMIO region for SCHIB information
-- 
2.53.0


^ permalink raw reply related	[flat|nested] 21+ messages in thread

* [PATCH v7 10/10] s390/vfio_ccw: implement a crw lock
  2026-07-27 19:22 [PATCH v7 00/10] s390/vfio_ccw fixes Eric Farman
                   ` (8 preceding siblings ...)
  2026-07-27 19:22 ` [PATCH v7 09/10] s390/vfio_ccw: selectively expand io_mutex Eric Farman
@ 2026-07-27 19:22 ` Eric Farman
  2026-07-27 20:07   ` sashiko-bot
  9 siblings, 1 reply; 21+ messages in thread
From: Eric Farman @ 2026-07-27 19:22 UTC (permalink / raw)
  To: linux-s390, kvm, linux-kernel
  Cc: Matthew Rosato, Halil Pasic, Christian Borntraeger, Eric Farman,
	stable, Farhan Ali

Unlike the channel_program struct, which covers synchronous I/O
submissions and asynchronous interrupts, the CRW region relies
exclusively on asynchronous events coming from hardware.

Implement a lock to manage the list of those payloads, to ensure
they are read cohesively.

Fixes: 3f02cb2fd9d2 ("vfio-ccw: Wire up the CRW irq and CRW region")
Cc: stable@vger.kernel.org
Cc: Farhan Ali <alifm@linux.ibm.com>
Reviewed-by: Matthew Rosato <mjrosato@linux.ibm.com>
Signed-off-by: Eric Farman <farman@linux.ibm.com>
---
 drivers/s390/cio/vfio_ccw_chp.c     | 26 ++++++++++++++++----------
 drivers/s390/cio/vfio_ccw_drv.c     |  6 ++++++
 drivers/s390/cio/vfio_ccw_ops.c     |  4 ++++
 drivers/s390/cio/vfio_ccw_private.h |  4 ++++
 4 files changed, 30 insertions(+), 10 deletions(-)

diff --git a/drivers/s390/cio/vfio_ccw_chp.c b/drivers/s390/cio/vfio_ccw_chp.c
index 9269b54f5cfd..7708eb4d6de0 100644
--- a/drivers/s390/cio/vfio_ccw_chp.c
+++ b/drivers/s390/cio/vfio_ccw_chp.c
@@ -93,18 +93,13 @@ static ssize_t vfio_ccw_crw_region_read(struct vfio_ccw_private *private,
 	loff_t pos = *ppos & VFIO_CCW_OFFSET_MASK;
 	struct ccw_crw_region *region;
 	struct vfio_ccw_crw *crw;
+	unsigned long flags;
 	int ret;
 
 	if (pos + count > sizeof(*region))
 		return -EINVAL;
 
 	mutex_lock(&private->io_mutex);
-	crw = list_first_entry_or_null(&private->crw,
-				       struct vfio_ccw_crw, next);
-
-	if (crw)
-		list_del(&crw->next);
-
 	if (i >= private->num_regions) {
 		ret = -EINVAL;
 		goto out;
@@ -113,6 +108,16 @@ static ssize_t vfio_ccw_crw_region_read(struct vfio_ccw_private *private,
 	i = array_index_nospec(i, private->num_regions);
 	region = private->region[i].data;
 
+	spin_lock_irqsave(&private->crw_lock, flags);
+	crw = list_first_entry_or_null(&private->crw,
+				       struct vfio_ccw_crw, next);
+
+	if (crw)
+		list_del(&crw->next);
+
+	/* Drop CRW lock while copying to userspace */
+	spin_unlock_irqrestore(&private->crw_lock, flags);
+
 	if (crw)
 		memcpy(&region->crw, &crw->crw, sizeof(region->crw));
 
@@ -122,15 +127,16 @@ static ssize_t vfio_ccw_crw_region_read(struct vfio_ccw_private *private,
 		ret = count;
 
 	region->crw = 0;
-
-out:
-	mutex_unlock(&private->io_mutex);
-
 	kfree(crw);
 
 	/* Notify the guest if more CRWs are on our queue */
+	spin_lock_irqsave(&private->crw_lock, flags);
 	if (!list_empty(&private->crw) && private->crw_trigger)
 		eventfd_signal(private->crw_trigger);
+	spin_unlock_irqrestore(&private->crw_lock, flags);
+
+out:
+	mutex_unlock(&private->io_mutex);
 
 	return ret;
 }
diff --git a/drivers/s390/cio/vfio_ccw_drv.c b/drivers/s390/cio/vfio_ccw_drv.c
index 757ff5b2556e..ab6b518cc353 100644
--- a/drivers/s390/cio/vfio_ccw_drv.c
+++ b/drivers/s390/cio/vfio_ccw_drv.c
@@ -118,11 +118,14 @@ void vfio_ccw_sch_io_todo(struct work_struct *work)
 void vfio_ccw_crw_todo(struct work_struct *work)
 {
 	struct vfio_ccw_private *private;
+	unsigned long flags;
 
 	private = container_of(work, struct vfio_ccw_private, crw_work);
 
+	spin_lock_irqsave(&private->crw_lock, flags);
 	if (!list_empty(&private->crw) && private->crw_trigger)
 		eventfd_signal(private->crw_trigger);
+	spin_unlock_irqrestore(&private->crw_lock, flags);
 }
 
 void vfio_ccw_notoper_todo(struct work_struct *work)
@@ -286,6 +289,7 @@ static void vfio_ccw_queue_crw(struct vfio_ccw_private *private,
 			       unsigned int rsid)
 {
 	struct vfio_ccw_crw *crw;
+	unsigned long flags;
 
 	/*
 	 * If unable to allocate a CRW, just drop the event and
@@ -303,7 +307,9 @@ static void vfio_ccw_queue_crw(struct vfio_ccw_private *private,
 	crw->crw.erc = erc;
 	crw->crw.rsid = rsid;
 
+	spin_lock_irqsave(&private->crw_lock, flags);
 	list_add_tail(&crw->next, &private->crw);
+	spin_unlock_irqrestore(&private->crw_lock, flags);
 	queue_work(vfio_ccw_work_q, &private->crw_work);
 }
 
diff --git a/drivers/s390/cio/vfio_ccw_ops.c b/drivers/s390/cio/vfio_ccw_ops.c
index 8ec6b175d991..9cbb277b83fb 100644
--- a/drivers/s390/cio/vfio_ccw_ops.c
+++ b/drivers/s390/cio/vfio_ccw_ops.c
@@ -55,6 +55,7 @@ static int vfio_ccw_mdev_init_dev(struct vfio_device *vdev)
 	INIT_WORK(&private->io_work, vfio_ccw_sch_io_todo);
 	INIT_WORK(&private->crw_work, vfio_ccw_crw_todo);
 	INIT_WORK(&private->notoper_work, vfio_ccw_notoper_todo);
+	spin_lock_init(&private->crw_lock);
 
 	private->cp.guest_cp = kzalloc_objs(struct ccw1, CCWCHAIN_LEN_MAX);
 	if (!private->cp.guest_cp)
@@ -131,11 +132,14 @@ static void vfio_ccw_mdev_release_dev(struct vfio_device *vdev)
 	struct vfio_ccw_private *private =
 		container_of(vdev, struct vfio_ccw_private, vdev);
 	struct vfio_ccw_crw *crw, *temp;
+	unsigned long flags;
 
+	spin_lock_irqsave(&private->crw_lock, flags);
 	list_for_each_entry_safe(crw, temp, &private->crw, next) {
 		list_del(&crw->next);
 		kfree(crw);
 	}
+	spin_unlock_irqrestore(&private->crw_lock, flags);
 
 	/* Should be empty, but just in case */
 	cancel_work_sync(&private->io_work);
diff --git a/drivers/s390/cio/vfio_ccw_private.h b/drivers/s390/cio/vfio_ccw_private.h
index 739121116ab6..3bd0171d38d0 100644
--- a/drivers/s390/cio/vfio_ccw_private.h
+++ b/drivers/s390/cio/vfio_ccw_private.h
@@ -98,6 +98,8 @@ struct vfio_ccw_parent {
  * @cp: channel program for the current I/O operation
  * @irb: irb info received from interrupt
  * @scsw: scsw info
+ * @crw_lock: serialization of CRW list information
+ * @crw: list of Channel Report Word elements
  * @io_trigger: eventfd ctx for signaling userspace I/O results
  * @crw_trigger: eventfd ctx for signaling userspace CRW information
  * @req_trigger: eventfd ctx for signaling userspace to return device
@@ -120,6 +122,8 @@ struct vfio_ccw_private {
 	struct channel_program	cp;
 	struct irb		irb;
 	union scsw		scsw;
+
+	spinlock_t		crw_lock;
 	struct list_head	crw;
 
 	struct eventfd_ctx	*io_trigger;
-- 
2.53.0


^ permalink raw reply related	[flat|nested] 21+ messages in thread

* Re: [PATCH v7 02/10] s390/vfio_ccw: limit the number of channel program segments
  2026-07-27 19:22 ` [PATCH v7 02/10] s390/vfio_ccw: limit the number of channel program segments Eric Farman
@ 2026-07-27 19:52   ` sashiko-bot
  0 siblings, 0 replies; 21+ messages in thread
From: sashiko-bot @ 2026-07-27 19:52 UTC (permalink / raw)
  To: Eric Farman
  Cc: linux-s390, Heiko Carstens, kvm, Vasily Gorbik, Alexander Gordeev

> The processing of channel programs, and the CCWs within them, is done
> recursively. As such, there is an arbitrary (but not architectural)
> limit to the number of CCWs that can exist in a single channel program.
> 
> The vfio-ccw logic breaks these channel programs into segments whenever
> it encounters a Transfer-In-Channel (TIC) CCW, and the combined number
> of segments count towards the global limit. Impose an equivalent limit
> to the number of segments until such logic can be made non-recursive.
> 
> Fixes: 0a19e61e6d4c ("vfio: ccw: introduce channel program interfaces")
> Cc: stable@vger.kernel.org
> Reviewed-by: Matthew Rosato <mjrosato@linux.ibm.com>
> Signed-off-by: Eric Farman <farman@linux.ibm.com>

Sashiko has reviewed this patch and found no issues. It looks great!

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20260727192230.2715207-1-farman@linux.ibm.com?part=2


^ permalink raw reply	[flat|nested] 21+ messages in thread

* Re: [PATCH v7 03/10] s390/vfio_ccw: fix out of bounds check on CCW array
  2026-07-27 19:22 ` [PATCH v7 03/10] s390/vfio_ccw: fix out of bounds check on CCW array Eric Farman
@ 2026-07-27 19:53   ` sashiko-bot
  0 siblings, 0 replies; 21+ messages in thread
From: sashiko-bot @ 2026-07-27 19:53 UTC (permalink / raw)
  To: Eric Farman
  Cc: Alexander Gordeev, Heiko Carstens, Vasily Gorbik, linux-s390, kvm

> The routine ccwchain_calc_length() counts the number of channel
> command words (CCWs) that are chained together in a single channel
> program, and rejects anything larger than CCWCHAIN_LEN_MAX (256) CCWs.
> 
> The loop itself is "do..while (count < 257)", and while the logic in
> is_cpa_within_range() correctly adjusts between the 0-index array of
> CCWs and the count of CCWs starting at 1, this means it would look
> at a possible 257th CCW before ending the loop and (correctly)
> returning an error.
> 
> Fix this by restructuring the loop to break as soon as 256 CCWs
> (thus indexes 0-255) are examined, without looking at memory
> outside the range.
> 
> Fixes: 0a19e61e6d4c ("vfio: ccw: introduce channel program interfaces")
> Cc: stable@vger.kernel.org
> Reviewed-by: Matthew Rosato <mjrosato@linux.ibm.com>
> Signed-off-by: Eric Farman <farman@linux.ibm.com>

Sashiko has reviewed this patch and found no issues. It looks great!

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20260727192230.2715207-1-farman@linux.ibm.com?part=3


^ permalink raw reply	[flat|nested] 21+ messages in thread

* Re: [PATCH v7 05/10] s390/vfio_ccw: calculate idal length based on idaw type
  2026-07-27 19:22 ` [PATCH v7 05/10] s390/vfio_ccw: calculate idal length based on idaw type Eric Farman
@ 2026-07-27 19:54   ` sashiko-bot
  0 siblings, 0 replies; 21+ messages in thread
From: sashiko-bot @ 2026-07-27 19:54 UTC (permalink / raw)
  To: Eric Farman
  Cc: linux-s390, Vasily Gorbik, kvm, Alexander Gordeev, Heiko Carstens

> Sashiko pointed out that get_guest_idal() unconditionally calculates
> the length of the IDAL presuming everything is a Format-2 IDAW.
> 
> The output of vfio-ccw is always Format-2, but the input can be either
> Format-1 (31-bit addresses) or Format-2 (64-bit addresses). As a result,
> the size of the guest IDAL may be incorrect and should be trimmed down.
> 
> Reported-by: sashiko-bot <sashiko-bot@kernel.org>
> Link: https://lore.kernel.org/r/20260720203400.7328E1F000E9@smtp.kernel.org/
> Fixes: 1b676fe3d9d3 ("vfio/ccw: handle a guest Format-1 IDAL")
> Cc: stable@vger.kernel.org
> Reviewed-by: Matthew Rosato <mjrosato@linux.ibm.com>
> Signed-off-by: Eric Farman <farman@linux.ibm.com>

Sashiko has reviewed this patch and found no issues. It looks great!

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20260727192230.2715207-1-farman@linux.ibm.com?part=5


^ permalink raw reply	[flat|nested] 21+ messages in thread

* Re: [PATCH v7 01/10] s390/vfio_ccw: free all memory if cp_init() fails
  2026-07-27 19:22 ` [PATCH v7 01/10] s390/vfio_ccw: free all memory if cp_init() fails Eric Farman
@ 2026-07-27 19:58   ` sashiko-bot
  0 siblings, 0 replies; 21+ messages in thread
From: sashiko-bot @ 2026-07-27 19:58 UTC (permalink / raw)
  To: Eric Farman
  Cc: Alexander Gordeev, linux-s390, kvm, Vasily Gorbik, Heiko Carstens

Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:

Pre-existing issues:
- [Critical] Unbounded mutual recursion between `ccwchain_handle_ccw()` and `ccwchain_loop_tic()` allows guest-triggered host kernel stack exhaustion.
--

--- Patch [1]: [PATCH v7 01/10] s390/vfio_ccw: free all memory if cp_init() fails ---
commit 13d15b9ae0c6f03c0097ee35d98be7a7415af06e
Author: Eric Farman <farman@linux.ibm.com>

s390/vfio_ccw: free all memory if cp_init() fails

This commit updates the cleanup logic during channel program initialization
to prevent memory leaks. It extracts the memory freeing into a separate loop
over the allocated segments rather than handling it inside the recursive
ccwchain_handle_ccw() function.

> diff --git a/drivers/s390/cio/vfio_ccw_cp.c b/drivers/s390/cio/vfio_ccw_cp.c
> --- a/drivers/s390/cio/vfio_ccw_cp.c
> +++ b/drivers/s390/cio/vfio_ccw_cp.c
> @@ -455,9 +455,6 @@ static int ccwchain_handle_ccw(dma32_t cda, struct channel_program *cp)
>  	/* Loop for tics on this new chain. */
>  	ret = ccwchain_loop_tic(chain, cp);

[Severity: Critical]
This isn't a bug introduced by this patch, but does this mutual recursion
have a depth limit?

If a guest provides a channel program with a long, nested sequence of
Transfer in Channel (TIC) commands, each pointing to a new CCW chain,
the execution path appears to cycle endlessly between these two functions:

ccwchain_handle_ccw()
  -> ccwchain_loop_tic()
    -> ccwchain_handle_ccw()

Could this unbounded recursion exhaust the host kernel stack?

> -	if (ret)
> -		ccwchain_free(chain);
> -
>  	return ret;
>  }

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20260727192230.2715207-1-farman@linux.ibm.com?part=1

^ permalink raw reply	[flat|nested] 21+ messages in thread

* Re: [PATCH v7 04/10] s390/vfio_ccw: ensure first IDAW remains constant
  2026-07-27 19:22 ` [PATCH v7 04/10] s390/vfio_ccw: ensure first IDAW remains constant Eric Farman
@ 2026-07-27 19:59   ` sashiko-bot
  0 siblings, 0 replies; 21+ messages in thread
From: sashiko-bot @ 2026-07-27 19:59 UTC (permalink / raw)
  To: Eric Farman
  Cc: Heiko Carstens, kvm, Alexander Gordeev, linux-s390, Vasily Gorbik

Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:

Pre-existing issues:
- [High] `get_guest_idal()` calculates the length of the guest IDAL incorrectly for 32-bit (Format-1) IDAWs, resulting in an out-of-bounds DMA read from guest memory that can cause valid channel programs to be rejected.
--

--- Patch [4]: [PATCH v7 04/10] s390/vfio_ccw: ensure first IDAW remains constant ---
commit aa8d48cf11f4cace4871ca597e3aa07dec1573e4
Author: Eric Farman <farman@linux.ibm.com>

s390/vfio_ccw: ensure first IDAW remains constant

This patch ensures the first IDAW in a list is unchanged between reads to
maintain a consistent set of IDAWs.

> diff --git a/drivers/s390/cio/vfio_ccw_cp.c b/drivers/s390/cio/vfio_ccw_cp.c
> index af632f9d54535..6275794751cb4 100644
> --- a/drivers/s390/cio/vfio_ccw_cp.c
> +++ b/drivers/s390/cio/vfio_ccw_cp.c
> @@ -523,6 +523,7 @@ static dma64_t *get_guest_idal(struct ccw1 *ccw, struct channel_program *cp, int
>  		&container_of(cp, struct vfio_ccw_private, cp)->vdev;
>  	dma64_t *idaws;
>  	dma32_t *idaws_f1;
> +	u64 first_idaw;
>  	int idal_len = idaw_nr * sizeof(*idaws);

[Severity: High]
This is a pre-existing issue, but does this calculate the guest IDAL length
incorrectly for 32-bit (Format-1) IDAWs?

Since idaws is a dma64_t pointer, sizeof(*idaws) is 8 bytes. If the guest is
submitting a channel program with Format-1 32-bit IDAWs (cp->orb.cmd.c64 == 0),
this calculates idal_len as idaw_nr * 8 instead of idaw_nr * 4.

When vfio_dma_rw() is later called with this inflated idal_len, could it
perform an out-of-bounds DMA read from guest memory? If the guest's 32-bit
IDAL ends at a memory boundary, the over-read could fail with -EFAULT or
-EINVAL, causing valid channel programs to be unnecessarily rejected.

>  	int idaw_size = idal_is_2k(cp) ? PAGE_SIZE / 2 : PAGE_SIZE;
>  	int idaw_mask = ~(idaw_size - 1);

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20260727192230.2715207-1-farman@linux.ibm.com?part=4

^ permalink raw reply	[flat|nested] 21+ messages in thread

* Re: [PATCH v7 07/10] s390/vfio_ccw: cancel existing workqueues
  2026-07-27 19:22 ` [PATCH v7 07/10] s390/vfio_ccw: cancel existing workqueues Eric Farman
@ 2026-07-27 20:02   ` sashiko-bot
  0 siblings, 0 replies; 21+ messages in thread
From: sashiko-bot @ 2026-07-27 20:02 UTC (permalink / raw)
  To: Eric Farman
  Cc: kvm, Vasily Gorbik, Heiko Carstens, Alexander Gordeev, linux-s390

Thank you for your contribution! Sashiko AI review found 4 potential issue(s) to consider:

New issues:
- [High] Suboptimal ordering of `cancel_work_sync()` and `private->crw` list teardown introduces a memory leak.

Pre-existing issues:
- [Critical] Time-of-Check to Time-of-Use (TOCTOU) race condition leads to Use-After-Free during device removal.
- [High] Lockless modification and traversal of the shared `private->crw` list leads to data races and list corruption.
- [High] Eventfd contexts for device triggers are leaked when the device is closed or released.
--

--- Patch [7]: [PATCH v7 07/10] s390/vfio_ccw: cancel existing workqueues ---
commit e5fd510ce3193c957c167ef6f613d80a32133fe5
Author: Eric Farman <farman@linux.ibm.com>
s390/vfio_ccw: cancel existing workqueues

The patch adds cleanup tags to un-initialize the io_work and crw_work
workqueues in the release and close paths. This ensures work isn't dispatched
after the private structure is freed.

> diff --git a/drivers/s390/cio/vfio_ccw_ops.c b/drivers/s390/cio/vfio_ccw_ops.c
> index 032a1cdf4df70..bd488e40e1531 100644
> --- a/drivers/s390/cio/vfio_ccw_ops.c
> +++ b/drivers/s390/cio/vfio_ccw_ops.c
> @@ -136,6 +136,10 @@ static void vfio_ccw_mdev_release_dev(struct vfio_device *vdev)
>  		kfree(crw);
>  	}
>  
> +	/* Should be empty, but just in case */
> +	cancel_work_sync(&private->io_work);
> +	cancel_work_sync(&private->crw_work);

[Severity: High]
Does this order of operations leave room for a memory leak?

If a concurrent channel path event triggers vfio_ccw_queue_crw() and adds a
new crw element to private->crw right after the teardown loop finishes draining
the list, but before cancel_work_sync() executes, those newly added elements
will be left on the list permanently.

Would reversing the order, so cancel_work_sync() happens first, guarantee
that no concurrently queued background processing remains before the list
drain is final?

[Severity: High]
This is a pre-existing issue, but is there a data race when modifying
the private->crw list?

It looks like elements are added locklessly in vfio_ccw_queue_crw():

drivers/s390/cio/vfio_ccw_drv.c:vfio_ccw_queue_crw() {
    ...
    list_add_tail(&crw->next, &private->crw);
    ...
}

And unlinked concurrently in vfio_ccw_crw_region_read():

drivers/s390/cio/vfio_ccw_chp.c:vfio_ccw_crw_region_read() {
    ...
    list_del(&crw->next);
    ...
}

Could this concurrent access without a lock lead to list corruption if a
userspace process reads the CRW region while a concurrent channel path hardware
event occurs?

[Severity: Critical]
This is a pre-existing issue, but could a use-after-free occur during
device removal?

In vfio_ccw_chp_event(), private is read while handling an event:

drivers/s390/cio/vfio_ccw_drv.c:vfio_ccw_chp_event() {
    ...
    private = dev_get_drvdata(&parent->dev);
    ...
    vfio_ccw_queue_crw(private, ...);
    ...
}

Concurrently, vfio_ccw_mdev_remove() unregisters the device and clears drvdata
locklessly:

drivers/s390/cio/vfio_ccw_ops.c:vfio_ccw_mdev_remove() {
    ...
    dev_set_drvdata(&parent->dev, NULL);
    ...
}

If the event handler resumes after vfio_ccw_mdev_release_dev() frees the
private structure, will it blindly use the freed pointer to queue work?

> +
>  	kmem_cache_free(vfio_ccw_crw_region, private->crw_region);
>  	kmem_cache_free(vfio_ccw_schib_region, private->schib_region);
>  	kmem_cache_free(vfio_ccw_cmd_region, private->cmd_region);
[ ... ]
> @@ -202,6 +206,10 @@ static void vfio_ccw_mdev_close_device(struct vfio_device *vdev)
>  		container_of(vdev, struct vfio_ccw_private, vdev);
>  
>  	vfio_ccw_fsm_event(private, VFIO_CCW_EVENT_CLOSE);
> +
> +	cancel_work_sync(&private->io_work);
> +	cancel_work_sync(&private->crw_work);

[Severity: High]
This is a pre-existing issue, but are eventfd contexts being leaked here?

If userspace sets the eventfd IRQs but closes the device descriptor without
unregistering them, they appear to be left behind.

Unlike other VFIO drivers, vfio_ccw_mdev_close_device() and
vfio_ccw_mdev_release_dev() do not seem to call eventfd_ctx_put() on
io_trigger, crw_trigger, or req_trigger.

Will these contexts leak and pin inodes in memory over time?

> +
>  	vfio_ccw_unregister_dev_regions(private);
>  }

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20260727192230.2715207-1-farman@linux.ibm.com?part=7

^ permalink raw reply	[flat|nested] 21+ messages in thread

* Re: [PATCH v7 06/10] s390/vfio_ccw: ensure index for read/write regions are within range
  2026-07-27 19:22 ` [PATCH v7 06/10] s390/vfio_ccw: ensure index for read/write regions are within range Eric Farman
@ 2026-07-27 20:03   ` sashiko-bot
  0 siblings, 0 replies; 21+ messages in thread
From: sashiko-bot @ 2026-07-27 20:03 UTC (permalink / raw)
  To: Eric Farman
  Cc: linux-s390, Vasily Gorbik, Alexander Gordeev, kvm, Heiko Carstens

Thank you for your contribution! Sashiko AI review found 3 potential issue(s) to consider:

Pre-existing issues:
- [Critical] Concurrent lockless modification and usage of `private->*_trigger` eventfd pointers can lead to use-after-free.
- [High] Concurrent lockless accesses and modifications to the `private->crw` list lead to list corruption, infinite loops, and potential use-after-free.
- [High] Work items `io_work` and `crw_work` are not unconditionally cancelled before freeing the device structure.
--

--- Patch [6]: [PATCH v7 06/10] s390/vfio_ccw: ensure index for read/write regions are within range ---
commit f8eafc3dc61a7fdadf00091fc10c59686336cf60
Author: Eric Farman <farman@linux.ibm.com>

s390/vfio_ccw: ensure index for read/write regions are within range

This patch adds bounds checking to the region indexes in the read/write paths.
The capability chain previously clamped the region indexes, but the existing
read/write regions lacked these checks, which are now enforced.

> diff --git a/drivers/s390/cio/vfio_ccw_chp.c b/drivers/s390/cio/vfio_ccw_chp.c
> index 38c176cf62957..f3015132d4b5f 100644
> --- a/drivers/s390/cio/vfio_ccw_chp.c
> +++ b/drivers/s390/cio/vfio_ccw_chp.c
[ ... ]
> @@ -97,6 +105,12 @@ static ssize_t vfio_ccw_crw_region_read(struct vfio_ccw_private *private,
>  		list_del(&crw->next);
>  
>  	mutex_lock(&private->io_mutex);

[Severity: High]
This is a pre-existing issue, but is the private->crw list modified locklessly
here?

If an asynchronous host event handler queues a CRW using list_add_tail():

drivers/s390/cio/vfio_ccw_drv.c:vfio_ccw_queue_crw() {
	...
	list_add_tail(&crw->next, &private->crw);
	queue_work(vfio_ccw_work_q, &private->crw_work);
	...
}

Could a concurrent read from userspace calling vfio_ccw_crw_region_read()
corrupt the list pointers when calling list_del() since neither operation
appears to hold a lock protecting private->crw?

> +	if (i >= private->num_regions) {
> +		ret = -EINVAL;
> +		goto out;
> +	}
> +
> +	i = array_index_nospec(i, private->num_regions);
>  	region = private->region[i].data;
>  
>  	if (crw)

> diff --git a/drivers/s390/cio/vfio_ccw_ops.c b/drivers/s390/cio/vfio_ccw_ops.c
> index 45ec722d25ea6..032a1cdf4df70 100644
> --- a/drivers/s390/cio/vfio_ccw_ops.c
> +++ b/drivers/s390/cio/vfio_ccw_ops.c
[ ... ]
> @@ -243,6 +243,7 @@ static ssize_t vfio_ccw_mdev_read(struct vfio_device *vdev,
>  		return vfio_ccw_mdev_read_io_region(private, buf, count, ppos);
>  	default:
>  		index -= VFIO_CCW_NUM_REGIONS;

[Severity: High]
This is a pre-existing issue, but are io_work and crw_work unconditionally
cancelled before freeing the device structure?

Looking at vfio_ccw_mdev_release_dev(), the private structure is freed:

drivers/s390/cio/vfio_ccw_ops.c:vfio_ccw_mdev_release_dev() {
	...
	kfree(private->cp.guest_cp);
	mutex_destroy(&private->io_mutex);
}

If a background event queues io_work or crw_work, and the work item is not
flushed before release, could vfio_ccw_crw_todo() dereference the freed
private pointer when accessing private->crw_trigger?

drivers/s390/cio/vfio_ccw_drv.c:vfio_ccw_crw_todo() {
	private = container_of(work, struct vfio_ccw_private, crw_work);

	if (!list_empty(&private->crw) && private->crw_trigger)
	...
}

[Severity: Critical]
This is another pre-existing issue, but can concurrent modification and usage
of the private->*_trigger eventfd pointers lead to a use-after-free?

The VFIO_DEVICE_SET_IRQS ioctl can locklessly drop the reference and clear the
eventfd pointer:

drivers/s390/cio/vfio_ccw_ops.c:vfio_ccw_mdev_set_irqs() {
	...
		if (fd == -1) {
			if (*ctx)
				eventfd_ctx_put(*ctx);
			*ctx = NULL;
	...
}

If background work items like vfio_ccw_crw_todo() check the pointer and then
call eventfd_signal():

drivers/s390/cio/vfio_ccw_drv.c:vfio_ccw_crw_todo() {
	...
	if (!list_empty(&private->crw) && private->crw_trigger)
		eventfd_signal(private->crw_trigger);
	...
}

Could the eventfd context be freed between the check and the signal call,
resulting in a dereference of the freed context?

> +		index = array_index_nospec(index, private->num_regions);
>  		return private->region[index].ops->read(private, buf, count,
>  							ppos);
>  	}

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20260727192230.2715207-1-farman@linux.ibm.com?part=6

^ permalink raw reply	[flat|nested] 21+ messages in thread

* Re: [PATCH v7 08/10] s390/vfio_ccw: move cp cleanup out of not operational
  2026-07-27 19:22 ` [PATCH v7 08/10] s390/vfio_ccw: move cp cleanup out of not operational Eric Farman
@ 2026-07-27 20:04   ` sashiko-bot
  0 siblings, 0 replies; 21+ messages in thread
From: sashiko-bot @ 2026-07-27 20:04 UTC (permalink / raw)
  To: Eric Farman
  Cc: Heiko Carstens, Alexander Gordeev, linux-s390, kvm, Vasily Gorbik

Thank you for your contribution! Sashiko AI review found 3 potential issue(s) to consider:

New issues:
- [Critical] Concurrent execution of `cp_free()` via `notoper_work` without synchronization leads to data races and double-free of channel program resources.
- [High] Canceling `notoper_work` during device release or close permanently leaks channel program resources if the work is pending.

Pre-existing issues:
- [Critical] A TOCTOU race in `vfio_ccw_sch_event()` can queue `notoper_work` on a freed `private` structure during concurrent device removal.
--

--- Patch [8]: [PATCH v7 08/10] s390/vfio_ccw: move cp cleanup out of not operational ---
commit 553f8e3a76925556dafb452c43ea6719ddd97d0b
Author: Eric Farman <farman@linux.ibm.com>

s390/vfio_ccw: move cp cleanup out of not operational

The fsm_notoper() routine is called when the device has been lost, and is
(by definition) no longer operational. Push the cleanup of the private->cp
resources to a workqueue.

Cc: stable@vger.kernel.org

> diff --git a/drivers/s390/cio/vfio_ccw_drv.c b/drivers/s390/cio/vfio_ccw_drv.c
> index 1a095085bc728..c197ad5ab5805 100644
> --- a/drivers/s390/cio/vfio_ccw_drv.c
> +++ b/drivers/s390/cio/vfio_ccw_drv.c
[ ... ]
> @@ -125,6 +125,15 @@ void vfio_ccw_crw_todo(struct work_struct *work)
>  		eventfd_signal(private->crw_trigger);
>  }
>  
> +void vfio_ccw_notoper_todo(struct work_struct *work)
> +{
> +	struct vfio_ccw_private *private;
> +
> +	private = container_of(work, struct vfio_ccw_private, notoper_work);
> +
> +	cp_free(&private->cp);
> +}

[Severity: Critical]
Does this introduce a double-free race condition?  The new workqueue
callback invokes cp_free() asynchronously without acquiring io_mutex or
the subchannel lock.

Concurrently, fsm_close() might also execute and call cp_free(). Since
cp_free() checks and updates the initialized flag locklessly:

drivers/s390/cio/vfio_ccw_cp.c:cp_free() {
    ...
    if (!cp->initialized)
        return;

    cp->initialized = false;
    ...
}

Could both threads proceed to double-free the channel program resources and
double-unpin guest memory pages?

> diff --git a/drivers/s390/cio/vfio_ccw_fsm.c b/drivers/s390/cio/vfio_ccw_fsm.c
> index 4d7988ea47ef0..4d47a3c7b9a06 100644
> --- a/drivers/s390/cio/vfio_ccw_fsm.c
> +++ b/drivers/s390/cio/vfio_ccw_fsm.c
> @@ -170,8 +170,7 @@ static void fsm_notoper(struct vfio_ccw_private *private,
>  	css_sched_sch_todo(sch, SCH_TODO_UNREG);
>  	private->state = VFIO_CCW_STATE_NOT_OPER;
>  
> -	/* This is usually handled during CLOSE event */
> -	cp_free(&private->cp);
> +	queue_work(vfio_ccw_work_q, &private->notoper_work);

[Severity: Critical]
This is a pre-existing issue, but could this queue_work() trigger a
use-after-free due to a race with device removal?

In vfio_ccw_sch_event(), the private structure is fetched locklessly:

drivers/s390/cio/vfio_ccw_drv.c:vfio_ccw_sch_event() {
    struct vfio_ccw_private *private = dev_get_drvdata(&parent->dev);
    ...
}

Concurrently, if userspace initiates device removal, vfio_ccw_mdev_remove()
clears drvdata and frees the private structure via vfio_put_device(). If
vfio_ccw_sch_event() is preempted after fetching the pointer but before
acquiring the lock, could it pass a freed private pointer into
vfio_ccw_fsm_event(), which then queues the notoper_work?

>  }
>  
>  /*
> diff --git a/drivers/s390/cio/vfio_ccw_ops.c b/drivers/s390/cio/vfio_ccw_ops.c
> index bd488e40e1531..8ec6b175d9914 100644
> --- a/drivers/s390/cio/vfio_ccw_ops.c
> +++ b/drivers/s390/cio/vfio_ccw_ops.c
[ ... ]
> @@ -139,6 +140,7 @@ static void vfio_ccw_mdev_release_dev(struct vfio_device *vdev)
>  	/* Should be empty, but just in case */
>  	cancel_work_sync(&private->io_work);
>  	cancel_work_sync(&private->crw_work);
> +	cancel_work_sync(&private->notoper_work);

[Severity: High]
Does canceling the deferred cleanup synchronously here leak the channel
program resources?

If the device transitions to VFIO_CCW_STATE_NOT_OPER, queue_work() schedules
this cleanup. If the device is subsequently closed or released before the
workqueue executes, cancel_work_sync() will abort the pending cleanup.

Furthermore, because the state is VFIO_CCW_STATE_NOT_OPER, the normal close
handler is bypassed:

drivers/s390/cio/vfio_ccw_fsm.c:vfio_ccw_jumptable
    [VFIO_CCW_STATE_NOT_OPER][VFIO_CCW_EVENT_CLOSE] = fsm_nop,

Does this completely bypass cp_free() and permanently leak the allocations
and pinned guest memory pages?

>  
>  	kmem_cache_free(vfio_ccw_crw_region, private->crw_region);

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20260727192230.2715207-1-farman@linux.ibm.com?part=8

^ permalink raw reply	[flat|nested] 21+ messages in thread

* Re: [PATCH v7 10/10] s390/vfio_ccw: implement a crw lock
  2026-07-27 19:22 ` [PATCH v7 10/10] s390/vfio_ccw: implement a crw lock Eric Farman
@ 2026-07-27 20:07   ` sashiko-bot
  0 siblings, 0 replies; 21+ messages in thread
From: sashiko-bot @ 2026-07-27 20:07 UTC (permalink / raw)
  To: Eric Farman
  Cc: Alexander Gordeev, kvm, Heiko Carstens, Vasily Gorbik, linux-s390

Thank you for your contribution! Sashiko AI review found 3 potential issue(s) to consider:

Pre-existing issues:
- [High] Concurrent VFIO_DEVICE_SET_IRQS ioctl can cause a Use-After-Free of eventfd_ctx in vfio_ccw_crw_region_read() and vfio_ccw_crw_todo().
- [High] Race condition between asynchronous channel path events and device removal leads to Use-After-Free of the `private` struct.
- [High] The `crw` list is emptied before `cancel_work_sync()` is called, which can lead to memory leaks of dynamically allocated `crw` objects.
--

--- Patch [10]: [PATCH v7 10/10] s390/vfio_ccw: implement a crw lock ---
commit 32947477038816f52fb49c7c2eb829ec1255cb5d
Author: Eric Farman <farman@linux.ibm.com>

s390/vfio_ccw: implement a crw lock

Unlike the channel_program struct, which covers synchronous I/O
submissions and asynchronous interrupts, the CRW region relies
exclusively on asynchronous events coming from hardware.

Implement a lock to manage the list of those payloads, to ensure
they are read cohesively.

Fixes: 3f02cb2fd9d2 ("vfio-ccw: Wire up the CRW irq and CRW region")
Cc: stable@vger.kernel.org
Cc: Farhan Ali <alifm@linux.ibm.com>

> diff --git a/drivers/s390/cio/vfio_ccw_drv.c b/drivers/s390/cio/vfio_ccw_drv.c
> index 757ff5b2556e3..ab6b518cc353f 100644
> --- a/drivers/s390/cio/vfio_ccw_drv.c
> +++ b/drivers/s390/cio/vfio_ccw_drv.c
> @@ -118,11 +118,14 @@ void vfio_ccw_sch_io_todo(struct work_struct *work)
>  void vfio_ccw_crw_todo(struct work_struct *work)
>  {
>  	struct vfio_ccw_private *private;
> +	unsigned long flags;
>  
>  	private = container_of(work, struct vfio_ccw_private, crw_work);
>  
> +	spin_lock_irqsave(&private->crw_lock, flags);
>  	if (!list_empty(&private->crw) && private->crw_trigger)
>  		eventfd_signal(private->crw_trigger);
> +	spin_unlock_irqrestore(&private->crw_lock, flags);
>  }

[Severity: High]
This isn't a bug introduced by this patch, but can a concurrent
VFIO_DEVICE_SET_IRQS ioctl cause a use-after-free of the eventfd_ctx?

If an unprivileged userspace process calls the VFIO_DEVICE_SET_IRQS ioctl,
vfio_ccw_mdev_set_irqs() releases the eventfd_ctx without acquiring crw_lock
or other serializing locks:

	if (*ctx)
		eventfd_ctx_put(*ctx);
	*ctx = efdctx;

Since private->crw_trigger is read under crw_lock here, but not held by
the writer, could the context be freed before or during eventfd_signal()?

> @@ -286,6 +289,7 @@ static void vfio_ccw_queue_crw(struct vfio_ccw_private *private,
>  			       unsigned int rsid)
>  {
>  	struct vfio_ccw_crw *crw;
> +	unsigned long flags;
>  
>  	/*
>  	 * If unable to allocate a CRW, just drop the event and
> @@ -303,7 +307,9 @@ static void vfio_ccw_queue_crw(struct vfio_ccw_private *private,
>  	crw->crw.erc = erc;
>  	crw->crw.rsid = rsid;
>  
> +	spin_lock_irqsave(&private->crw_lock, flags);
>  	list_add_tail(&crw->next, &private->crw);
> +	spin_unlock_irqrestore(&private->crw_lock, flags);
>  	queue_work(vfio_ccw_work_q, &private->crw_work);
>  }

[Severity: High]
This is a pre-existing issue, but is there a race condition between asynchronous
channel path events and device removal that could lead to a use-after-free of
the private struct?

A hardware event can trigger vfio_ccw_chp_event() which fetches the private
pointer locklessly:

	struct vfio_ccw_private *private = dev_get_drvdata(&parent->dev);
	...
	vfio_ccw_queue_crw(private, CRW_RSC_CPATH, ...);

Concurrently, if userspace writes to the mdev 'remove' sysfs attribute,
vfio_ccw_mdev_remove() clears the driver data and releases the device reference:

	dev_set_drvdata(&parent->dev, NULL);
	vfio_put_device(&private->vdev);

Could the private structure be freed while we are still executing here or in
queue_work()?

> diff --git a/drivers/s390/cio/vfio_ccw_ops.c b/drivers/s390/cio/vfio_ccw_ops.c
> index 8ec6b175d9914..9cbb277b83fb2 100644
> --- a/drivers/s390/cio/vfio_ccw_ops.c
> +++ b/drivers/s390/cio/vfio_ccw_ops.c

[ ... ]

> @@ -131,11 +132,14 @@ static void vfio_ccw_mdev_release_dev(struct vfio_device *vdev)
>  	struct vfio_ccw_private *private =
>  		container_of(vdev, struct vfio_ccw_private, vdev);
>  	struct vfio_ccw_crw *crw, *temp;
> +	unsigned long flags;
>  
> +	spin_lock_irqsave(&private->crw_lock, flags);
>  	list_for_each_entry_safe(crw, temp, &private->crw, next) {
>  		list_del(&crw->next);
>  		kfree(crw);
>  	}
> +	spin_unlock_irqrestore(&private->crw_lock, flags);
>  
>  	/* Should be empty, but just in case */
>  	cancel_work_sync(&private->io_work);

[Severity: High]
This isn't a bug introduced by this patch, but does emptying the crw list before
calling cancel_work_sync() violate the subsystem rule that background tasks must
be explicitly stopped before the queues they operate on are destroyed?

If a concurrent hardware event (like vfio_ccw_chp_event) allocates a crw and
calls queue_work() before or during the cancellation, could the newly allocated
crw be left on the list and leaked?

Additionally, if queue_work() executes after cancel_work_sync() finishes, could
the work execute on a freed private pointer?

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20260727192230.2715207-1-farman@linux.ibm.com?part=10

^ permalink raw reply	[flat|nested] 21+ messages in thread

* Re: [PATCH v7 09/10] s390/vfio_ccw: selectively expand io_mutex
  2026-07-27 19:22 ` [PATCH v7 09/10] s390/vfio_ccw: selectively expand io_mutex Eric Farman
@ 2026-07-27 20:12   ` sashiko-bot
  0 siblings, 0 replies; 21+ messages in thread
From: sashiko-bot @ 2026-07-27 20:12 UTC (permalink / raw)
  To: Eric Farman
  Cc: Vasily Gorbik, kvm, Heiko Carstens, linux-s390, Alexander Gordeev

Thank you for your contribution! Sashiko AI review found 2 potential issue(s) to consider:

Pre-existing issues:
- [High] TOCTOU race in cp_iova_pinned ignores IOMMU unmap requests, leading to hardware DMA into unmapped memory.
- [High] Data race and list corruption on private->crw between process and IRQ contexts.
--

--- Patch [9]: [PATCH v7 09/10] s390/vfio_ccw: selectively expand io_mutex ---
commit e5ee662e08e75a748fbe4dadeba55875910744f0
Author: Eric Farman <farman@linux.ibm.com>

s390/vfio_ccw: selectively expand io_mutex

This patch expands the scope of the io_mutex to serialize I/O regions
alongside I/O operations themselves. It also defers cp_free() to a
workqueue to avoid acquiring mutexes in interrupt context.

> diff --git a/drivers/s390/cio/vfio_ccw_chp.c b/drivers/s390/cio/vfio_ccw_chp.c
> index f3015132d4b5f..9269b54f5cfd4 100644
> --- a/drivers/s390/cio/vfio_ccw_chp.c
> +++ b/drivers/s390/cio/vfio_ccw_chp.c
> @@ -98,13 +98,13 @@ static ssize_t vfio_ccw_crw_region_read(struct vfio_ccw_private *private,
>  	if (pos + count > sizeof(*region))
>  		return -EINVAL;
>  
> +	mutex_lock(&private->io_mutex);
>  	crw = list_first_entry_or_null(&private->crw,
>  				       struct vfio_ccw_crw, next);
>  
>  	if (crw)
>  		list_del(&crw->next);

[Severity: High]
This is a pre-existing issue, but does holding only io_mutex provide enough
protection here?

In vfio_ccw_queue_crw(), which is called from hardware interrupt context,
items appear to be appended to private->crw locklessly:

vfio_ccw_queue_crw() {
        ...
        list_add_tail(&crw->next, &private->crw);
        queue_work(vfio_ccw_work_q, &private->crw_work);
}

Since io_mutex is a sleeping lock and cannot be acquired in IRQ context,
will concurrent list manipulations between the IRQ context and this read
corrupt the doubly-linked list pointers?

> -	mutex_lock(&private->io_mutex);
>  	if (i >= private->num_regions) {
>  		ret = -EINVAL;
>  		goto out;
> diff --git a/drivers/s390/cio/vfio_ccw_cp.c b/drivers/s390/cio/vfio_ccw_cp.c
> index 5ef082b8289af..58722c4baa251 100644
> --- a/drivers/s390/cio/vfio_ccw_cp.c
> +++ b/drivers/s390/cio/vfio_ccw_cp.c
> @@ -977,17 +977,23 @@ void cp_update_scsw(struct channel_program *cp, union scsw *scsw)
>   */
>  bool cp_iova_pinned(struct channel_program *cp, u64 iova, u64 length)
>  {
> +	struct vfio_ccw_private *private =
> +		container_of(cp, struct vfio_ccw_private, cp);
>  	struct ccwchain *chain;
>  	int i;
>  
>  	if (!cp->initialized)
>  		return false;

[Severity: High]
This isn't a bug introduced by this patch, but is it safe to check
cp->initialized without holding io_mutex?

If a VFIO unmap ioctl occurs concurrently while cp_init() is pinning pages
(but before cp_init() sets cp->initialized = true at the very end), could
this lockless check return false, causing the unmap request to be
incorrectly ignored?

If the unmap request is ignored while the I/O is submitted to the hardware,
could this result in the hardware performing DMA into memory the guest
considers unmapped?

> +	mutex_lock(&private->io_mutex);
>  	list_for_each_entry(chain, &cp->ccwchain_list, next) {
>  		for (i = 0; i < chain->ch_len; i++)
> -			if (page_array_iova_pinned(&chain->ch_pa[i], iova, length))
> +			if (page_array_iova_pinned(&chain->ch_pa[i], iova, length)) {
> +				mutex_unlock(&private->io_mutex);
>  				return true;
> +			}
>  	}
> +	mutex_unlock(&private->io_mutex);
>  
>  	return false;
>  }

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20260727192230.2715207-1-farman@linux.ibm.com?part=9

^ permalink raw reply	[flat|nested] 21+ messages in thread

end of thread, other threads:[~2026-07-27 20:12 UTC | newest]

Thread overview: 21+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-07-27 19:22 [PATCH v7 00/10] s390/vfio_ccw fixes Eric Farman
2026-07-27 19:22 ` [PATCH v7 01/10] s390/vfio_ccw: free all memory if cp_init() fails Eric Farman
2026-07-27 19:58   ` sashiko-bot
2026-07-27 19:22 ` [PATCH v7 02/10] s390/vfio_ccw: limit the number of channel program segments Eric Farman
2026-07-27 19:52   ` sashiko-bot
2026-07-27 19:22 ` [PATCH v7 03/10] s390/vfio_ccw: fix out of bounds check on CCW array Eric Farman
2026-07-27 19:53   ` sashiko-bot
2026-07-27 19:22 ` [PATCH v7 04/10] s390/vfio_ccw: ensure first IDAW remains constant Eric Farman
2026-07-27 19:59   ` sashiko-bot
2026-07-27 19:22 ` [PATCH v7 05/10] s390/vfio_ccw: calculate idal length based on idaw type Eric Farman
2026-07-27 19:54   ` sashiko-bot
2026-07-27 19:22 ` [PATCH v7 06/10] s390/vfio_ccw: ensure index for read/write regions are within range Eric Farman
2026-07-27 20:03   ` sashiko-bot
2026-07-27 19:22 ` [PATCH v7 07/10] s390/vfio_ccw: cancel existing workqueues Eric Farman
2026-07-27 20:02   ` sashiko-bot
2026-07-27 19:22 ` [PATCH v7 08/10] s390/vfio_ccw: move cp cleanup out of not operational Eric Farman
2026-07-27 20:04   ` sashiko-bot
2026-07-27 19:22 ` [PATCH v7 09/10] s390/vfio_ccw: selectively expand io_mutex Eric Farman
2026-07-27 20:12   ` sashiko-bot
2026-07-27 19:22 ` [PATCH v7 10/10] s390/vfio_ccw: implement a crw lock Eric Farman
2026-07-27 20:07   ` sashiko-bot

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox