* [PATCH v7 0/6] s390/vfio-ap: Fix pre-existing bugs in vfio_ap device driver
@ 2026-09-04 22:35 Anthony Krowiak
2026-09-04 22:35 ` [PATCH v7 1/6] s390/vfio-ap: Fix leaks of pinned NIB and registered GISC Anthony Krowiak
` (5 more replies)
0 siblings, 6 replies; 13+ messages in thread
From: Anthony Krowiak @ 2026-09-04 22:35 UTC (permalink / raw)
To: linux-s390, linux-kernel, kvm
Cc: jjherne, borntraeger, mjrosato, pasic, alex, kwankhede, fiuczy,
pbonzini, frankja, imbrenda, agordeev, hca, gor
The sashiko AI reported several pre-existing bugs in the vfio_ap device
driver code while reviewing unrelated patches. This series fixes four
such bugs.
Change log v6 => v7:
~~~~~~~~~~~~~~~~~~~
Patch: s390/vfio-ap: fix queue state leakage to guest and host
apq_reset_check()
- Fixed missing closing brace on -EIO early-return block that
made the AP_RESET_MAX_WAIT timeout check unreachable.
- Introduced apq_reset_finalize() helper to copy the final
TAPQ status word to q->reset_status and set
q->reset_status.response_code to AP_RESPONSE_NORMAL only
when zeroization is confirmed (ret == 0). Replaces the
done: label and goto.
- Both pre-timeout and within-timeout completion paths now
call apq_reset_finalize() directly.
apq_status_check()
- Signature changed from (int apqn, ...) to
(struct vfio_ap_queue *q, ...) to support ratelimited
warning reporting.
vfio_ap_mdev_remove_queue()
- Removed post-lock fallback unbounded reset block that had
a use-after-free risk on q->matrix_mdev after
release_update_locks_for_mdev().
Patch: s390/vfio-ap: replace guest-reachable WARNs with
ratelimited warnings and add dmesg visibility to DBF_WARNs
This is a new patch created in response to review comments of the
v6 series.
vfio_ap_wait_for_irqclear()
- Signature changed from (int apqn) to
(struct vfio_ap_queue *q).
- Replaced WARN_ONCE calls with report_tapq_rc() and
report_irqclear_timeout().
vfio_ap_irq_disable()
- Replaced three WARN_ONCE calls with
report_aqic_disable_error().
- Added AP_RESPONSE_STATE_CHANGE_IN_PROGRESS to retry case.
- Added AP_RESPONSE_INVALID_GISA,
AP_RESPONSE_ASSOC_SECRET_NOT_UNIQUE and
AP_RESPONSE_ASSOC_FAILED as explicit end_fail cases.
vfio_ap_irq_enable()
- Added dev_warn_ratelimited() alongside each
VFIO_AP_DBF_WARN() call for dmesg visibility.
handle_pqap()
- Added pr_warn_ratelimited() for AIV facility not installed
and hook not registered cases.
- Added dev_warn_ratelimited() for mdev not in use and queue
not bound cases.
apq_status_check()
- Replaced WARN with report_tapq_rc().
vfio_ap_mdev_reset_queue()
- Replaced WARN with report_zapq_rc().
- Removed AP_RESPONSE_BUSY from async-reset case (not a
valid ZAPQ response code).
New functions introduced:
- apq_reset_finalize()
- report_tapq_rc()
- report_irqclear_timeout()
- report_aqic_disable_error()
- report_zapq_rc()
Anthony Krowiak (6):
s390/vfio-ap: Fix leaks of pinned NIB and registered GISC
s390/vfio-ap: Fix failure to release IRQ notification eventfd contexts
s390/vfio-ap: Fix unbounded loop in apq_reset_check()
s390/vfio-ap: Use AP_DOMAINS for adm_add bitmap size in
vfio_ap_mdev_cfg_add()
s390/vfio-ap: fix queue state leakage to guest and host
s390/vfio-ap: replace guest-reachable WARNs with ratelimited
warnings and add dmesg visibility to DBF_WARNs
drivers/s390/crypto/vfio_ap_ops.c | 539 ++++++++++++++++++++++++------
1 file changed, 441 insertions(+), 98 deletions(-)
--
2.53.0
^ permalink raw reply [flat|nested] 13+ messages in thread
* [PATCH v7 1/6] s390/vfio-ap: Fix leaks of pinned NIB and registered GISC
2026-09-04 22:35 [PATCH v7 0/6] s390/vfio-ap: Fix pre-existing bugs in vfio_ap device driver Anthony Krowiak
@ 2026-09-04 22:35 ` Anthony Krowiak
2026-09-04 22:51 ` sashiko-bot
2026-09-04 22:35 ` [PATCH v7 2/6] s390/vfio-ap: Fix failure to release IRQ notification eventfd contexts Anthony Krowiak
` (4 subsequent siblings)
5 siblings, 1 reply; 13+ messages in thread
From: Anthony Krowiak @ 2026-09-04 22:35 UTC (permalink / raw)
To: linux-s390, linux-kernel, kvm
Cc: jjherne, borntraeger, mjrosato, pasic, alex, kwankhede, fiuczy,
pbonzini, frankja, imbrenda, agordeev, hca, gor, stable
Several code paths in the vfio_ap driver failed to free the AQIC
resources — the pinned guest NIB page and the registered guest ISC
used to enable interrupts for a queue — when a queue became unavailable
or when unexpected response codes were returned. This could cause memory
exhaustion and depletion of KVM interrupt subclass registrations over time
with repeated dynamic AP reconfiguration.
vfio_ap_mdev_reset_queue()
~~~~~~~~~~~~~~~~~~~~~~~~~~
AP_RESPONSE_Q_NOT_AVAIL (0x01) was not handled, causing it to fall
through to the default case which only fired a WARN without calling
vfio_ap_free_aqic_resources(). When ap_zapq() returns this response
code the queue is physically unavailable and can no longer generate
AP interrupts or DMA-write to the NIB. Add AP_RESPONSE_Q_NOT_AVAIL
alongside AP_RESPONSE_DECONFIGURED and AP_RESPONSE_CHECKSTOPPED so
that AQIC resources are freed immediately for all three non-
operational cases.
The default case itself also failed to free AQIC resources. The
only valid response codes for PQAP-ZAPQ are 0x00, 0x01, 0x02, 0x03,
0x04, and 0x0a. Any other code indicates a hardware or firmware bug.
A malfunctioning queue cannot generate AP interrupts or DMA-write to
the NIB, so call vfio_ap_free_aqic_resources() in the default case rather
than leaking the resources.
vfio_ap_mdev_remove_queue()
~~~~~~~~~~~~~~~~~~~~~~~~~~~
When the AP bus scan detects a queue is no longer in the host AP
configuration, vfio_ap_mdev_remove_queue() skips the ZAPQ since
issuing it would return AP_RESPONSE_Q_NOT_AVAIL anyway. However,
vfio_ap_free_aqic_resources() was also never called, leaking the
pinned NIB page and registered guest ISC. Since the hardware is
gone and can no longer DMA-write to the NIB, it is safe to call
vfio_ap_free_aqic_resources() directly. Add an else branch to the
host-config test_bit_inv guard to free AQIC resources when the
queue is not in the host AP configuration. If the queue is not
assigned to an mdev, vfio_ap_free_aqic_resources() is a no-op.
apq_reset_check()
~~~~~~~~~~~~~~~~~
When apq_status_check() returns -EIO - indicating that PQAP(TAPQ)
returned an invalid response code - apq_reset_check() returned
immediately without freeing the AQIC resources. An invalid TAPQ
response code indicates a hardware or firmware bug. A malfunctioning
queue cannot generate AP interrupts or DMA-write to the NIB, so
call vfio_ap_free_aqic_resources() before returning in the -EIO
case.
vfio_ap_irq_disable()
~~~~~~~~~~~~~~~~~~~~~
The valid response codes for PQAP(AQIC) include
AP_RESPONSE_STATE_CHANGE_IN_PROGRESS (0x0a),
AP_RESPONSE_INVALID_GISA (0x08),
AP_RESPONSE_ASSOC_SECRET_NOT_UNIQUE (0x35), and
AP_RESPONSE_ASSOC_FAILED (0x36), none of which were explicitly
handled. All four fell through to the default case and jumped to
end_fail without freeing resources.
AP_RESPONSE_STATE_CHANGE_IN_PROGRESS indicates a transient
condition, analogous to AP_RESPONSE_RESET_IN_PROGRESS and
AP_RESPONSE_BUSY. Add it to the retry case.
AP_RESPONSE_INVALID_GISA indicates the AQIC instruction was
rejected due to an invalid GISA address. The disable did not take
effect and the hardware still holds the NIB address.
AP_RESPONSE_ASSOC_SECRET_NOT_UNIQUE and AP_RESPONSE_ASSOC_FAILED
are asynchronous response codes from a previously executed
association instruction. All subsequent AP instructions, including
the current AQIC, end with the asynchronous response code until
the queue is reset. The AQIC disable was therefore not executed
and the hardware still holds the NIB address.
For all three of the above, end_fail is the correct disposition
since the NIB must not be freed while hardware may still write to
it. Add these as explicit cases above default to document the
intent.
Fixes: ec89b55e3bce7 ("s390: ap: implement PAPQ AQIC interception in kernel")
Cc: stable@vger.kernel.org
Signed-off-by: Anthony Krowiak <akrowiak@linux.ibm.com>
---
drivers/s390/crypto/vfio_ap_ops.c | 275 +++++++++++++++++++++++-------
1 file changed, 216 insertions(+), 59 deletions(-)
diff --git a/drivers/s390/crypto/vfio_ap_ops.c b/drivers/s390/crypto/vfio_ap_ops.c
index 940c0ff668be..9d4f5b9e3301 100644
--- a/drivers/s390/crypto/vfio_ap_ops.c
+++ b/drivers/s390/crypto/vfio_ap_ops.c
@@ -31,6 +31,7 @@
#define AP_QUEUE_IN_USE "in use"
#define AP_RESET_INTERVAL 20 /* Reset sleep interval (20ms) */
+#define AP_RESET_MAX_WAIT 2000 /* Maximum wait for reset (2000ms) */
static int vfio_ap_mdev_reset_queues(struct ap_matrix_mdev *matrix_mdev);
static int vfio_ap_mdev_reset_qlist(struct list_head *qlist);
@@ -226,16 +227,27 @@ static struct vfio_ap_queue *vfio_ap_mdev_get_queue(
}
/**
- * vfio_ap_wait_for_irqclear - clears the IR bit or gives up after 5 tries
- * @apqn: The AP Queue number
- *
- * Checks the IRQ bit for the status of this APQN using ap_tapq.
- * Returns if the ap_tapq function succeeded and the bit is clear.
- * Returns if ap_tapq function failed with invalid, deconfigured or
- * checkstopped AP.
- * Otherwise retries up to 5 times after waiting 20ms.
+ * vfio_ap_wait_for_irqclear - wait for the IR bit to clear after a disable
+ *
+ * @apqn: the APQN of the queue
+ *
+ * Repeatedly polls the AP queue status via PQAP(TAPQ) every 20ms until the IR
+ * bit is clear, the queue becomes non-operational, or 5 retries are exhausted.
+ *
+ * Because PQAP(AQIC) disable initiates an asynchronous process, a
+ * condition-code 0 completion does not guarantee the IR bit has been cleared.
+ * The host must confirm IR=0 before unpinning the NIB page to avoid a wild
+ * DMA write to a freed page.
+ *
+ * Return:
+ * - 0 if the IR bit is clear (i.e., interrupts are disabled).
+ *
+ * - -ENODEV if the PQAP-TAPQ response code indicates the queue is not available,
+ * is deconfigured, or is checkstopped (i.e., not operational).
+ *
+ * - -ETIMEDOUT the function timed out before the IR bit was cleared.
*/
-static void vfio_ap_wait_for_irqclear(int apqn)
+static int vfio_ap_wait_for_irqclear(int apqn)
{
struct ap_queue_status status;
int retry = 5;
@@ -246,7 +258,7 @@ static void vfio_ap_wait_for_irqclear(int apqn)
case AP_RESPONSE_NORMAL:
case AP_RESPONSE_RESET_IN_PROGRESS:
if (!status.irq_enabled)
- return;
+ return 0;
fallthrough;
case AP_RESPONSE_BUSY:
msleep(20);
@@ -257,12 +269,15 @@ static void vfio_ap_wait_for_irqclear(int apqn)
default:
WARN_ONCE(1, "%s: tapq rc %02x: %04x\n", __func__,
status.response_code, apqn);
- return;
+ return -ENODEV;
}
} while (--retry);
- WARN_ONCE(1, "%s: tapq rc %02x: %04x could not clear IR bit\n",
- __func__, status.response_code, apqn);
+ WARN_ONCE(1, "%s: tapq rc %02x: timed out waiting for interrupts disabled for %02x.%04x\n",
+ __func__, status.response_code,
+ AP_QID_CARD(apqn), AP_QID_QUEUE(apqn));
+
+ return -ETIMEDOUT;
}
/**
@@ -289,20 +304,28 @@ static void vfio_ap_free_aqic_resources(struct vfio_ap_queue *q)
}
/**
- * vfio_ap_irq_disable - disables and clears an ap_queue interrupt
- * @q: The vfio_ap_queue
+ * vfio_ap_irq_disable - disable interrupts for an AP queue
+ * @q: the vfio_ap_queue
+ *
+ * Issues PQAP(AQIC) to disable interrupts for the AP queue. On success
+ * (AP_RESPONSE_NORMAL or AP_RESPONSE_OTHERWISE_CHANGED), polls via
+ * vfio_ap_wait_for_irqclear() until the IR bit is confirmed clear before
+ * freeing the pinned NIB page and unregistering the guest ISC. This wait is
+ * necessary because AQIC disable is asynchronous: freeing the NIB before IR=0
+ * is confirmed risks a wild DMA write to a freed host page.
*
- * Uses ap_aqic to disable the interruption and in case of success, reset
- * in progress or IRQ disable command already proceeded: calls
- * vfio_ap_wait_for_irqclear() to check for the IRQ bit to be clear
- * and calls vfio_ap_free_aqic_resources() to free the resources associated
- * with the AP interrupt handling.
+ * Retries up to 5 times (with 20ms sleep) if the queue is busy or a reset is
+ * in progress.
*
- * In the case the AP is busy, or a reset is in progress,
- * retries after 20ms, up to 5 times.
+ * If the IR bit cannot be confirmed clear (timeout), the NIB page and guest
+ * ISC are intentionally leaked. If the page were unpinned and returned to the
+ * allocator, a subsequent hardware DMA write to that physical address would
+ * corrupt memory belonging to a new owner — a wild DMA write that could crash
+ * or compromise the host kernel.
*
- * Returns if ap_aqic function failed with invalid, deconfigured or
- * checkstopped AP.
+ * If the queue is non-operational (deconfigured, checkstopped, not available),
+ * resources are freed immediately since the hardware can no longer write to
+ * the NIB.
*
* Return: &struct ap_queue_status
*/
@@ -310,34 +333,90 @@ static struct ap_queue_status vfio_ap_irq_disable(struct vfio_ap_queue *q)
{
union ap_qirq_ctrl aqic_gisa = { .value = 0 };
struct ap_queue_status status;
- int retries = 5;
+ int retries = 5, ret;
do {
status = ap_aqic(q->apqn, aqic_gisa, 0);
switch (status.response_code) {
case AP_RESPONSE_OTHERWISE_CHANGED:
case AP_RESPONSE_NORMAL:
- vfio_ap_wait_for_irqclear(q->apqn);
- goto end_free;
+ /*
+ * AQIC disable was accepted (NORMAL), or the queue was
+ * already disabled or a prior async request is still
+ * completing (OTHERWISE_CHANGED). In both cases, we must
+ * wait until interrupt processing has been disabled
+ * before proceeding.
+ */
+ ret = vfio_ap_wait_for_irqclear(q->apqn);
+ if (ret == 0 || ret == -ENODEV)
+ goto end_free;
+ /*
+ * Timed out waiting to confirm interrupts are disabled.
+ * If ap_aqic returned NORMAL, the guest would incorrectly
+ * interpret that as a successful disable and may free or
+ * reuse the NIB while hardware can still write to it.
+ * Zero the status word and set OTHERWISE_CHANGED to mimic
+ * what the hardware does for that response code. This
+ * signals to the guest that the reset operation did not
+ * complete.
+ */
+ if (status.response_code == AP_RESPONSE_NORMAL) {
+ memset(&status, 0, sizeof(status));
+ status.response_code = AP_RESPONSE_OTHERWISE_CHANGED;
+ }
+ goto end_fail;
case AP_RESPONSE_RESET_IN_PROGRESS:
case AP_RESPONSE_BUSY:
+ case AP_RESPONSE_STATE_CHANGE_IN_PROGRESS:
msleep(20);
break;
case AP_RESPONSE_Q_NOT_AVAIL:
case AP_RESPONSE_DECONFIGURED:
case AP_RESPONSE_CHECKSTOPPED:
+ /* AP not operational; no further interrupts possible */
+ WARN_ONCE(1, "%s: ap_aqic status %d\n", __func__,
+ status.response_code);
+ goto end_free;
case AP_RESPONSE_INVALID_ADDRESS:
+ case AP_RESPONSE_INVALID_GISA:
+ case AP_RESPONSE_ASSOC_SECRET_NOT_UNIQUE:
+ case AP_RESPONSE_ASSOC_FAILED:
default:
- /* All cases in default means AP not operational */
+ /*
+ * The AQIC disable was rejected; IRQ is still enabled
+ * and the hardware still holds the NIB address. Do not
+ * free resources.
+ */
WARN_ONCE(1, "%s: ap_aqic status %d\n", __func__,
status.response_code);
- goto end_free;
+ goto end_fail;
}
} while (retries--);
WARN_ONCE(1, "%s: ap_aqic status %d\n", __func__,
status.response_code);
+
+end_fail:
+ /*
+ * We are here either because the AQIC instruction failed to disable
+ * interrupts, or because IR=0 could not be confirmed. In either case
+ * the NIB page and guest ISC cannot be freed: hardware may still write
+ * to the NIB, and unpinning the page would allow it to be reallocated
+ * to a new owner. A subsequent hardware DMA write to that physical
+ * address would corrupt the new owner's memory — a wild DMA write that
+ * could crash or compromise the host kernel. The resources are
+ * therefore intentionally leaked.
+ */
+ return status;
+
end_free:
+ /*
+ * This label is reached because the queue was successfully disabled,
+ * or because the queue is not operational or not available, in which case
+ * interrupts can not be processed, so free the AQIC resources - the pinned NIB
+ * page and the registered guest ISC - used to enable interrupts so they will
+ * not be leaked.
+ */
vfio_ap_free_aqic_resources(q);
return status;
}
@@ -401,22 +480,29 @@ static int ensure_nib_shared(unsigned long addr)
}
/**
- * vfio_ap_irq_enable - Enable Interruption for a APQN
+ * vfio_ap_irq_enable - enable interrupts for an AP queue on behalf of a guest
*
- * @q: the vfio_ap_queue holding AQIC parameters
+ * @q: the vfio_ap_queue for which interrupts are to be enabled
* @isc: the guest ISC to register with the GIB interface
- * @vcpu: the vcpu object containing the registers specifying the parameters
- * passed to the PQAP(AQIC) instruction.
+ * @vcpu: the vcpu whose registers contain the PQAP(AQIC) parameters
*
- * Pin the NIB saved in *q
- * Register the guest ISC to GIB interface and retrieve the
- * host ISC to issue the host side PQAP/AQIC
+ * Pins the guest NIB page, registers the guest ISC with the GIB to obtain a
+ * host ISC, and reissues PQAP(AQIC) with the translated host-absolute NIB
+ * address and host ISC on behalf of the guest.
*
- * status.response_code may be set to AP_RESPONSE_INVALID_ADDRESS in case the
- * vfio_pin_pages or kvm_s390_gisc_register failed.
+ * The condition code and AP-queue status word returned by PQAP(AQIC) are
+ * reflected back to the guest as-is. IRQ state verification (polling until
+ * IR=1) is the responsibility of the guest AP bus, not the host.
*
- * Otherwise return the ap_queue_status returned by the ap_aqic(),
- * all retry handling will be done by the guest.
+ * Resource management is based solely on whether hardware accepted the new NIB:
+ * - AP_RESPONSE_NORMAL (CC=0): hardware accepted the new NIB; the old pinned
+ * NIB page and registered guest ISC are freed and the new ones saved.
+ * - All other responses: hardware did not accept the new NIB; the newly pinned
+ * page and registered ISC are freed and the previously saved resources are
+ * left intact.
+ *
+ * AP_RESPONSE_INVALID_ADDRESS is returned if vfio_pin_pages() or
+ * kvm_s390_gisc_register() fails before the AQIC instruction is issued.
*
* Return: &struct ap_queue_status
*/
@@ -428,11 +514,10 @@ static struct ap_queue_status vfio_ap_irq_enable(struct vfio_ap_queue *q,
struct ap_queue_status status = {};
struct kvm_s390_gisa *gisa;
struct page *h_page;
- int nisc;
+ int nisc, ret;
struct kvm *kvm;
phys_addr_t h_nib;
dma_addr_t nib;
- int ret;
/* Verify that the notification indicator byte address is valid */
if (vfio_ap_validate_nib(vcpu, &nib)) {
@@ -489,24 +574,33 @@ static struct ap_queue_status vfio_ap_irq_enable(struct vfio_ap_queue *q,
status = ap_aqic(q->apqn, aqic_gisa, h_nib);
switch (status.response_code) {
case AP_RESPONSE_NORMAL:
- /* See if we did clear older IRQ configuration */
+ /*
+ * Hardware accepted the new NIB address (CC=0). The old NIB and
+ * guest ISC are no longer used by hardware and can be freed.
+ * The new resources are saved for tracking and future teardown.
+ *
+ * IRQ state verification (polling until IR=1) is the
+ * responsibility of the guest AP bus, not the host. The
+ * condition code and status word are reflected back to the
+ * guest to respond to the PQAP-AQIC instruction.
+ */
vfio_ap_free_aqic_resources(q);
q->saved_iova = nib;
q->saved_isc = isc;
break;
- case AP_RESPONSE_OTHERWISE_CHANGED:
- /* We could not modify IRQ settings: clear new configuration */
+ default:
+ /*
+ * Hardware did not accept the new NIB (CC=3 or error). The
+ * previously saved NIB and guest ISC remain active and must
+ * not be freed. Release the newly pinned page and registered
+ * ISC that were prepared for this (rejected) request.
+ */
ret = kvm_s390_gisc_unregister(kvm, isc);
if (ret)
VFIO_AP_DBF_WARN("%s: kvm_s390_gisc_unregister: rc=%d isc=%d, apqn=%#04x\n",
__func__, ret, isc, q->apqn);
vfio_unpin_pages(&q->matrix_mdev->vdev, nib, 1);
break;
- default:
- pr_warn("%s: apqn %04x: response: %02x\n", __func__, q->apqn,
- status.response_code);
- vfio_ap_irq_disable(q);
- break;
}
if (status.response_code != AP_RESPONSE_NORMAL) {
@@ -635,7 +729,6 @@ static int handle_pqap(struct kvm_vcpu *vcpu)
}
status = vcpu->run->s.regs.gprs[1];
-
/* If IR bit(16) is set we enable the interrupt */
if ((status >> (63 - 16)) & 0x01)
qstatus = vfio_ap_irq_enable(q, status & 0x07, vcpu);
@@ -1919,22 +2012,57 @@ static int apq_status_check(int apqn, struct ap_queue_status *status)
{
switch (status->response_code) {
case AP_RESPONSE_NORMAL:
+ /*
+ * This response code only indicates that the PQAP(ZAPQ) has
+ * been initiated. The following bit settings in the status
+ * returned from TAPQ must be verified to confirm that the
+ * asynchronous portion of the queue zeroization has completed.
+ */
+ if (status->queue_empty && !status->replies_waiting &&
+ !status->irq_enabled && !status->async)
+ return 0;
+
+ /* Async zeroization still in progress; keep waiting */
+ return -EBUSY;
+
case AP_RESPONSE_DECONFIGURED:
case AP_RESPONSE_CHECKSTOPPED:
- return 0;
+ /*
+ * The queue is non-operational: interrupts are not possible so
+ * AQIC resources can be safely freed. However, zeroization
+ * cannot be confirmed because all status bits are zeroed when
+ * these response codes are returned — there is no way to
+ * distinguish a zeroized queue from one that has not been
+ * zeroized. Return -ENODEV to signal that AQIC resources should
+ * be freed but that zeroization has not been confirmed.
+ */
+ return -ENODEV;
+
case AP_RESPONSE_RESET_IN_PROGRESS:
- case AP_RESPONSE_BUSY:
+ /*
+ * A reset is in progress. It may be the reset we issued or one
+ * issued prior to ours; either way, once it completes the queue
+ * will be zeroized, so keep waiting.
+ */
return -EBUSY;
+
+ case AP_RESPONSE_BUSY:
case AP_RESPONSE_ASSOC_SECRET_NOT_UNIQUE:
case AP_RESPONSE_ASSOC_FAILED:
/*
+ * AP_RESPONSE_BUSY:
+ * The queue is busy with something unrelated to a reset and our
+ * ZAPQ was rejected outright. Re-issue the ZAPQ.
+ *
+ * AP_RESPONSE_ASSOC_SECRET_NOT_UNIQUE
+ * AP_RESPONSE_ASSOC_FAILED:
* These asynchronous response codes indicate a PQAP(AAPQ)
* instruction to associate a secret with the guest failed. All
* subsequent AP instructions will end with the asynchronous
- * response code until the AP queue is reset; so, let's return
- * a value indicating a reset needs to be performed again.
+ * response code until the AP queue is reset. Re-issue the ZAPQ.
*/
return -EAGAIN;
+
default:
WARN(true,
"failed to verify reset of queue %02x.%04x: TAPQ rc=%u\n",
@@ -1959,8 +2087,16 @@ static void apq_reset_check(struct work_struct *reset_work)
elapsed += AP_RESET_INTERVAL;
status = ap_tapq(q->apqn, NULL);
ret = apq_status_check(q->apqn, &status);
- if (ret == -EIO)
+ if (ret == -EIO) {
+ /*
+ * TAPQ returned an invalid response code. This
+ * indicates a hardware or firmware bug; the queue
+ * cannot generate AP interrupts or DMA-write to the
+ * NIB, so free the AQIC resources rather than leak them.
+ */
+ vfio_ap_free_aqic_resources(q);
return;
+ }
if (ret == -EBUSY) {
pr_notice_ratelimited(WAIT_MSG, elapsed,
AP_QID_CARD(q->apqn),
@@ -1977,8 +2113,12 @@ static void apq_reset_check(struct work_struct *reset_work)
memcpy(&q->reset_status, &status, sizeof(status));
continue;
}
- if (q->saved_isc != VFIO_AP_ISC_INVALID)
- vfio_ap_free_aqic_resources(q);
+ /*
+ * We end up here when the ZAPQ has completed. ZAPQ
+ * disables interrupts, so if the AQIC resources must be
+ * freed; otherwise they may be leaked.
+ */
+ vfio_ap_free_aqic_resources(q);
break;
}
}
@@ -1995,22 +2135,30 @@ static void vfio_ap_mdev_reset_queue(struct vfio_ap_queue *q)
switch (status.response_code) {
case AP_RESPONSE_NORMAL:
case AP_RESPONSE_RESET_IN_PROGRESS:
- case AP_RESPONSE_BUSY:
case AP_RESPONSE_STATE_CHANGE_IN_PROGRESS:
/*
* Let's verify whether the ZAPQ completed successfully on a work queue.
*/
queue_work(system_long_wq, &q->reset_work);
break;
+ case AP_RESPONSE_Q_NOT_AVAIL:
case AP_RESPONSE_DECONFIGURED:
case AP_RESPONSE_CHECKSTOPPED:
vfio_ap_free_aqic_resources(q);
break;
default:
+ /*
+ * The architecture defines only the response codes above as
+ * valid for ZAPQ. Any other response code indicates a hardware
+ * or firmware bug. Since a malfunctioning queue cannot generate
+ * AP interrupts or DMA-write to the NIB, free the AQIC resources
+ * rather than leak them.
+ */
WARN(true,
"PQAP/ZAPQ for %02x.%04x failed with invalid rc=%u\n",
AP_QID_CARD(q->apqn), AP_QID_QUEUE(q->apqn),
status.response_code);
+ vfio_ap_free_aqic_resources(q);
}
}
@@ -2534,6 +2682,15 @@ void vfio_ap_mdev_remove_queue(struct ap_device *apdev)
test_bit_inv(apqi, (unsigned long *)matrix_dev->info.aqm)) {
vfio_ap_mdev_reset_queue(q);
flush_work(&q->reset_work);
+ } else {
+ /*
+ * The queue is no longer in the host's AP configuration.
+ * The hardware cannot DMA-write to the NIB, so it is safe
+ * to free the AQIC resources directly without issuing a
+ * ZAPQ. If the queue is not assigned to an mdev,
+ * vfio_ap_free_aqic_resources() is a no-op.
+ */
+ vfio_ap_free_aqic_resources(q);
}
done:
--
2.53.0
^ permalink raw reply related [flat|nested] 13+ messages in thread
* [PATCH v7 2/6] s390/vfio-ap: Fix failure to release IRQ notification eventfd contexts
2026-09-04 22:35 [PATCH v7 0/6] s390/vfio-ap: Fix pre-existing bugs in vfio_ap device driver Anthony Krowiak
2026-09-04 22:35 ` [PATCH v7 1/6] s390/vfio-ap: Fix leaks of pinned NIB and registered GISC Anthony Krowiak
@ 2026-09-04 22:35 ` Anthony Krowiak
2026-09-04 22:49 ` sashiko-bot
2026-09-04 22:35 ` [PATCH v7 3/6] s390/vfio-ap: Fix unbounded loop in apq_reset_check() Anthony Krowiak
` (3 subsequent siblings)
5 siblings, 1 reply; 13+ messages in thread
From: Anthony Krowiak @ 2026-09-04 22:35 UTC (permalink / raw)
To: linux-s390, linux-kernel, kvm
Cc: jjherne, borntraeger, mjrosato, pasic, alex, kwankhede, fiuczy,
pbonzini, frankja, imbrenda, agordeev, hca, gor, stable
When userspace registers IRQ notification eventfds via the
VFIO_DEVICE_SET_IRQS ioctl, vfio_ap_set_request_irq() and
vfio_ap_set_cfg_change_irq() each call eventfd_ctx_fdget(), which
takes a reference on the eventfd_ctx and stores it in
matrix_mdev->req_trigger and matrix_mdev->cfg_chg_trigger
respectively.
These references are dropped only when userspace explicitly replaces
or clears them via a subsequent SET_IRQS call. If the device is
closed without that explicit teardown - because the guest exits,
the VM process crashes, or the device file is simply closed -
neither vfio_ap_mdev_close_device() nor the remove path releases
these references. The eventfd_ctx backing objects and their
associated file references therefore leak for the lifetime of the
kernel.
Fix this by introducing vfio_ap_mdev_release_eventfds() and calling
it from vfio_ap_mdev_close_device() after vfio_ap_mdev_unset_kvm().
The VFIO core guarantees that close_device is called before
vfio_unregister_group_dev() returns in the remove path, so fixing
close_device is sufficient to cover both teardown paths.
Note:
~~~~
The matrix_dev->mdevs lock must be held during the call to
vfio_ap_mdev_release_eventfds(). There is a small window between the calls
to vfio_ap_mdev_unset_kvm() which gets and releases the update locks
and the acquisition of the matrix_dev->mdevs_lock mutex during which
it is possible - although highly unlikely during normal operation - whereby
a concurrent SET_IRQS call can get in.
Taking matrix_dev->mdevs_lock around vfio_ap_mdev_release_eventfds()
is sufficient to make this race-free. The SET_IRQS ioctl path writes
req_trigger and cfg_chg_trigger only from vfio_ap_mdev_ioctl(), which
holds mdevs_lock for its entire duration and always calls
eventfd_ctx_put() on the previous value before storing the new one.
Any number of concurrent SET_IRQS calls during the window between
vfio_ap_mdev_unset_kvm() and the acquisition of mdevs_lock are
therefore safe: each ioctl invocation puts the reference it found and
installs a new one, leaving exactly one live reference in the field
when it releases the lock. When release_eventfds subsequently acquires
mdevs_lock it finds that single surviving reference and puts it.
Conversely, a SET_IRQS call that loses the race and blocks on
mdevs_lock will find the field NULL after release_eventfds finishes,
take ownership of the reference it just created, and install it into a
field that will never be read again - a transient leak. To close that
final case, callers must ensure no new SET_IRQS ioctls can be issued
after close_device() is called, which the VFIO core guarantees by
releasing the device file before invoking close_device().
Fixes: bf48961f6f48e ("s390/vfio-ap: realize the VFIO_DEVICE_SET_IRQS ioctl")
Cc: stable@vger.kernel.org
Signed-off-by: Anthony Krowiak <akrowiak@linux.ibm.com>
Reviewed-by: Matthew Rosato <mjrosato@linux.ibm.com>
---
drivers/s390/crypto/vfio_ap_ops.c | 16 ++++++++++++++++
1 file changed, 16 insertions(+)
diff --git a/drivers/s390/crypto/vfio_ap_ops.c b/drivers/s390/crypto/vfio_ap_ops.c
index 9d4f5b9e3301..4a31454d725a 100644
--- a/drivers/s390/crypto/vfio_ap_ops.c
+++ b/drivers/s390/crypto/vfio_ap_ops.c
@@ -2209,12 +2209,28 @@ static int vfio_ap_mdev_open_device(struct vfio_device *vdev)
return vfio_ap_mdev_set_kvm(matrix_mdev, vdev->kvm);
}
+static void vfio_ap_mdev_release_eventfds(struct ap_matrix_mdev *matrix_mdev)
+{
+ if (matrix_mdev->req_trigger) {
+ eventfd_ctx_put(matrix_mdev->req_trigger);
+ matrix_mdev->req_trigger = NULL;
+ }
+ if (matrix_mdev->cfg_chg_trigger) {
+ eventfd_ctx_put(matrix_mdev->cfg_chg_trigger);
+ matrix_mdev->cfg_chg_trigger = NULL;
+ }
+}
+
static void vfio_ap_mdev_close_device(struct vfio_device *vdev)
{
struct ap_matrix_mdev *matrix_mdev =
container_of(vdev, struct ap_matrix_mdev, vdev);
vfio_ap_mdev_unset_kvm(matrix_mdev);
+
+ mutex_lock(&matrix_dev->mdevs_lock);
+ vfio_ap_mdev_release_eventfds(matrix_mdev);
+ mutex_unlock(&matrix_dev->mdevs_lock);
}
static void vfio_ap_mdev_request(struct vfio_device *vdev, unsigned int count)
--
2.53.0
^ permalink raw reply related [flat|nested] 13+ messages in thread
* [PATCH v7 3/6] s390/vfio-ap: Fix unbounded loop in apq_reset_check()
2026-09-04 22:35 [PATCH v7 0/6] s390/vfio-ap: Fix pre-existing bugs in vfio_ap device driver Anthony Krowiak
2026-09-04 22:35 ` [PATCH v7 1/6] s390/vfio-ap: Fix leaks of pinned NIB and registered GISC Anthony Krowiak
2026-09-04 22:35 ` [PATCH v7 2/6] s390/vfio-ap: Fix failure to release IRQ notification eventfd contexts Anthony Krowiak
@ 2026-09-04 22:35 ` Anthony Krowiak
2026-09-04 22:49 ` sashiko-bot
2026-09-04 22:35 ` [PATCH v7 4/6] s390/vfio-ap: Use AP_DOMAINS for adm_add bitmap size in vfio_ap_mdev_cfg_add() Anthony Krowiak
` (2 subsequent siblings)
5 siblings, 1 reply; 13+ messages in thread
From: Anthony Krowiak @ 2026-09-04 22:35 UTC (permalink / raw)
To: linux-s390, linux-kernel, kvm
Cc: jjherne, borntraeger, mjrosato, pasic, alex, kwankhede, fiuczy,
pbonzini, frankja, imbrenda, agordeev, hca, gor, stable
s390/vfio-ap: Fix unbounded loop in apq_reset_check()
The apq_reset_check() worker polls ap_tapq() in a while(true)
loop waiting for a queue reset to complete. When ap_tapq()
returns AP_RESPONSE_BUSY or AP_RESPONSE_RESET_IN_PROGRESS,
apq_status_check() returns -EBUSY and the loop continues after
sleeping AP_RESET_INTERVAL (20ms). There is no upper bound on
how many times the loop iterates, so if the hardware
continuously returns a busy response the worker runs
indefinitely.
This is particularly harmful because several callers of
vfio_ap_mdev_reset_queue() - such as vfio_ap_mdev_reset_queues(),
vfio_ap_mdev_reset_qlist() and vfio_ap_mdev_remove_queue() -
call flush_work() on the queue's reset_work while holding one
or more of the global matrix_dev locks (guests_lock,
mdevs_lock) or the KVM lock. An indefinitely spinning worker
permanently blocks access to all ap_matrix_mdev objects, which
could hang other guests that are using them.
Fix this by introducing AP_RESET_MAX_WAIT (2000ms) and breaking
out of the poll loop when elapsed time reaches that threshold.
If apq_reset_check() times out before verifying completion of
the reset, the AQIC resources associated with the queue cannot
be freed. The NIB is the active DMA target for AP interrupt
delivery until the reset completes; freeing the pinned page
would allow it to be reallocated to a new owner. A subsequent
hardware DMA write to that physical address would corrupt the
new owner's memory - a wild DMA write that could crash or
compromise the host kernel.
If the reset eventually completes, interrupts will be
terminated, but the pinned NIB page and ISC registration will
be leaked. This is preferable to a compromised kernel or kernel
crash, or waiting indefinitely and blocking access to all
mdevs, hanging the guests to which they are attached.
On timeout, q->reset_status.response_code is set to
AP_RESPONSE_RESET_IN_PROGRESS. This is used internally to
signal that the reset did not complete, and ensures that if
the queue is reset again, the re-issue logic in
apq_reset_check() will re-issue the ZAPQ.
This patch also fixes a bug whereby AP_RESPONSE_NORMAL (0)
returned from PQAP(ZAPQ) was incorrectly treated as
confirmation that the queue was zeroized. AP_RESPONSE_NORMAL
only indicates that the ZAPQ was accepted; zeroization is
performed asynchronously. To confirm completion, the following
bits in the status word returned from PQAP(TAPQ) must all
be verified:
status->irq_enabled == 0
status->queue_empty == 1
status->replies_waiting == 0
status->async == 0
Fixes: dd174833e44e ("s390/vfio-ap: remove upper limit on wait for queue reset to complete")
Cc: stable@vger.kernel.org
Signed-off-by: Anthony Krowiak <akrowiak@linux.ibm.com>
---
drivers/s390/crypto/vfio_ap_ops.c | 91 ++++++++++++++++++++++++++++---
1 file changed, 84 insertions(+), 7 deletions(-)
diff --git a/drivers/s390/crypto/vfio_ap_ops.c b/drivers/s390/crypto/vfio_ap_ops.c
index 4a31454d725a..f9f35265b84f 100644
--- a/drivers/s390/crypto/vfio_ap_ops.c
+++ b/drivers/s390/crypto/vfio_ap_ops.c
@@ -2047,6 +2047,12 @@ static int apq_status_check(int apqn, struct ap_queue_status *status)
return -EBUSY;
case AP_RESPONSE_BUSY:
+ /*
+ * The queue is busy with something unrelated to a reset and our
+ * ZAPQ was rejected outright. Re-issue the ZAPQ.
+ */
+ return -EAGAIN;
+
case AP_RESPONSE_ASSOC_SECRET_NOT_UNIQUE:
case AP_RESPONSE_ASSOC_FAILED:
/*
@@ -2072,6 +2078,33 @@ static int apq_status_check(int apqn, struct ap_queue_status *status)
}
}
+static void report_aqic_resource_leak(struct vfio_ap_queue *q)
+{
+ if (q->saved_isc != VFIO_AP_ISC_INVALID || q->saved_iova) {
+ if (q->matrix_mdev) {
+ dev_warn_ratelimited(mdev_dev(q->matrix_mdev->mdev),
+ "Reset timed out for APQN %02x.%04x: leaking AQIC resources (NIB page & GISC) to prevent host crash\n",
+ AP_QID_CARD(q->apqn),
+ AP_QID_QUEUE(q->apqn));
+ } else {
+ pr_warn_ratelimited("Reset timed out for APQN %02x.%04x: leaking AQIC resources (NIB page & GISC) to prevent host crash\n",
+ AP_QID_CARD(q->apqn),
+ AP_QID_QUEUE(q->apqn));
+ }
+ } else {
+ if (q->matrix_mdev) {
+ dev_warn_ratelimited(mdev_dev(q->matrix_mdev->mdev),
+ "Reset timed out for APQN %02x.%04x\n",
+ AP_QID_CARD(q->apqn),
+ AP_QID_QUEUE(q->apqn));
+ } else {
+ pr_warn_ratelimited("Reset timed out for APQN %02x.%04x\n",
+ AP_QID_CARD(q->apqn),
+ AP_QID_QUEUE(q->apqn));
+ }
+ }
+}
+
#define WAIT_MSG "Waited %dms for reset of queue %02x.%04x (%u, %u, %u)"
static void apq_reset_check(struct work_struct *reset_work)
@@ -2096,6 +2129,53 @@ static void apq_reset_check(struct work_struct *reset_work)
*/
vfio_ap_free_aqic_resources(q);
return;
+
+ if (elapsed >= AP_RESET_MAX_WAIT) {
+ /*
+ * Zeroization confirmed (ret == 0): the TAPQ status bits
+ * indicate the async portion of the ZAPQ completed
+ * successfully. Free AQIC resources and return.
+ *
+ * Queue non-operational (ret == -ENODEV): the queue is
+ * deconfigured or checkstopped; interrupts are not
+ * possible so AQIC resources can be safely freed.
+ * Zeroization cannot be confirmed in this state, but the
+ * queue cannot generate interrupts, so the NIB page is
+ * no longer a DMA target and it is safe to free it.
+ */
+ if (!ret || ret == -ENODEV)
+ goto done;
+ /*
+ * Timed out without being able to verify zapq completed.
+ *
+ * The AQIC resources associated with this queue - the pinned
+ * page containing the NIB and the registered guest ISC -
+ * cannot be freed here. The NIB is the active DMA target
+ * for AP interrupt delivery until the reset completes;
+ * freeing the pinned page while the hardware may still
+ * write to it would result in a wild DMA write that could
+ * corrupt host memory.
+ *
+ * If the reset eventually completes, interrupts will be
+ * terminated and the pinned NIB page and ISC registration
+ * will be leaked. This is preferable to either a wild DMA
+ * write or waiting indefinitely: flush_work() callers hold
+ * the matrix_dev->mdevs_lock mutex which serializes access
+ * to all mdev objects system-wide, so blocking here would
+ * hang all guests to which those mdevs are attached.
+ */
+ report_aqic_resource_leak(q);
+ /*
+ * Zeroization could not be confirmed; set
+ * reset_status to AP_RESPONSE_RESET_IN_PROGRESS.
+ * This is used internally to signal that the reset
+ * did not complete, and ensures that if the queue
+ * is reset again, the re-issue logic in
+ * apq_reset_check() will re-issue the ZAPQ.
+ */
+ q->reset_status.response_code = AP_RESPONSE_RESET_IN_PROGRESS;
+
+ return;
}
if (ret == -EBUSY) {
pr_notice_ratelimited(WAIT_MSG, elapsed,
@@ -2113,15 +2193,12 @@ static void apq_reset_check(struct work_struct *reset_work)
memcpy(&q->reset_status, &status, sizeof(status));
continue;
}
- /*
- * We end up here when the ZAPQ has completed. ZAPQ
- * disables interrupts, so if the AQIC resources must be
- * freed; otherwise they may be leaked.
- */
- vfio_ap_free_aqic_resources(q);
- break;
}
}
+
+done:
+ if (q->saved_isc != VFIO_AP_ISC_INVALID)
+ vfio_ap_free_aqic_resources(q);
}
static void vfio_ap_mdev_reset_queue(struct vfio_ap_queue *q)
--
2.53.0
^ permalink raw reply related [flat|nested] 13+ messages in thread
* [PATCH v7 4/6] s390/vfio-ap: Use AP_DOMAINS for adm_add bitmap size in vfio_ap_mdev_cfg_add()
2026-09-04 22:35 [PATCH v7 0/6] s390/vfio-ap: Fix pre-existing bugs in vfio_ap device driver Anthony Krowiak
` (2 preceding siblings ...)
2026-09-04 22:35 ` [PATCH v7 3/6] s390/vfio-ap: Fix unbounded loop in apq_reset_check() Anthony Krowiak
@ 2026-09-04 22:35 ` Anthony Krowiak
2026-09-04 22:46 ` sashiko-bot
2026-09-04 22:35 ` [PATCH v7 5/6] s390/vfio-ap: fix queue state leakage to guest and host Anthony Krowiak
2026-09-04 22:35 ` [PATCH v7 6/6] s390/vfio-ap: replace guest-reachable WARNs with ratelimited warnings and add dmesg visibility to DBF_WARNs Anthony Krowiak
5 siblings, 1 reply; 13+ messages in thread
From: Anthony Krowiak @ 2026-09-04 22:35 UTC (permalink / raw)
To: linux-s390, linux-kernel, kvm
Cc: jjherne, borntraeger, mjrosato, pasic, alex, kwankhede, fiuczy,
pbonzini, frankja, imbrenda, agordeev, hca, gor
Domain and control domain bitmaps are sized by the AP_DOMAINS constant, not
AP_DEVICES. The two constants are both 256 today so there is no functional
impact, but using the wrong constant is inconsistent with every operation
on aqm/adm bitmaps.
Use AP_DOMAINS to keep the code consistent and correct in case
the two constants ever diverge.
Note:
This patch was submitted in response to a sashiko review comment pointing
out there are other functions besides vfio_ap_mdev_cfg_add(), so there are
fixes included here for those also. The subject line was kept the same
since this is in v2 of this patch.
Signed-off-by: Anthony Krowiak <akrowiak@linux.ibm.com>
Reviewed-by: Jason J. Herne <jjherne@linux.ibm.com>
Reviewed-by: Matthew Rosato <mjrosato@linux.ibm.com>
---
drivers/s390/crypto/vfio_ap_ops.c | 8 ++++----
1 file changed, 4 insertions(+), 4 deletions(-)
diff --git a/drivers/s390/crypto/vfio_ap_ops.c b/drivers/s390/crypto/vfio_ap_ops.c
index f9f35265b84f..672fae69739a 100644
--- a/drivers/s390/crypto/vfio_ap_ops.c
+++ b/drivers/s390/crypto/vfio_ap_ops.c
@@ -1503,7 +1503,7 @@ static void vfio_ap_mdev_hot_unplug_domain(struct ap_matrix_mdev *matrix_mdev,
{
DECLARE_BITMAP(apqis, AP_DOMAINS);
- bitmap_zero(apqis, AP_DEVICES);
+ bitmap_zero(apqis, AP_DOMAINS);
set_bit_inv(apqi, apqis);
vfio_ap_mdev_hot_unplug_domains(matrix_mdev, apqis);
}
@@ -2947,11 +2947,11 @@ static void vfio_ap_mdev_on_cfg_remove(struct ap_config_info *cur_config_info,
do_remove |= bitmap_andnot(aqrem,
(unsigned long *)prev_config_info->aqm,
(unsigned long *)cur_config_info->aqm,
- AP_DEVICES);
+ AP_DOMAINS);
do_remove |= bitmap_andnot(cdrem,
(unsigned long *)prev_config_info->adm,
(unsigned long *)cur_config_info->adm,
- AP_DEVICES);
+ AP_DOMAINS);
if (do_remove)
vfio_ap_mdev_cfg_remove(aprem, aqrem, cdrem);
@@ -3062,7 +3062,7 @@ static void vfio_ap_mdev_cfg_add(unsigned long *apm_add, unsigned long *aqm_add,
bitmap_and(matrix_mdev->aqm_add,
matrix_mdev->matrix.aqm, aqm_add, AP_DOMAINS);
bitmap_and(matrix_mdev->adm_add,
- matrix_mdev->matrix.adm, adm_add, AP_DEVICES);
+ matrix_mdev->matrix.adm, adm_add, AP_DOMAINS);
mutex_unlock(&matrix_dev->mdevs_lock);
}
--
2.53.0
^ permalink raw reply related [flat|nested] 13+ messages in thread
* [PATCH v7 5/6] s390/vfio-ap: fix queue state leakage to guest and host
2026-09-04 22:35 [PATCH v7 0/6] s390/vfio-ap: Fix pre-existing bugs in vfio_ap device driver Anthony Krowiak
` (3 preceding siblings ...)
2026-09-04 22:35 ` [PATCH v7 4/6] s390/vfio-ap: Use AP_DOMAINS for adm_add bitmap size in vfio_ap_mdev_cfg_add() Anthony Krowiak
@ 2026-09-04 22:35 ` Anthony Krowiak
2026-09-04 22:46 ` sashiko-bot
2026-09-04 22:35 ` [PATCH v7 6/6] s390/vfio-ap: replace guest-reachable WARNs with ratelimited warnings and add dmesg visibility to DBF_WARNs Anthony Krowiak
5 siblings, 1 reply; 13+ messages in thread
From: Anthony Krowiak @ 2026-09-04 22:35 UTC (permalink / raw)
To: linux-s390, linux-kernel, kvm
Cc: jjherne, borntraeger, mjrosato, pasic, alex, kwankhede, fiuczy,
pbonzini, frankja, imbrenda, agordeev, hca, gor, stable
Commit dd174833e44e ("s390/vfio-ap: remove upper limit on wait
for queue reset to complete") removed the upper bound on the
wait for a queue reset to complete in apq_reset_check(), thus
allowing the function to loop indefinitely. The reason given
was to ensure both the security requirements and prevent
resource leakage and corruption in the hypervisor.
That is a legitimate concern; however, functions initiating the
reset all hold the matrix_dev->mdevs_lock which guards access
to all of the mdevs under the control of the vfio_ap device
driver. Blocking of access prevents a system administrator from
configuring the mdevs (i.e., assigning/unassigning adapters,
domains and control domains via the mdev's sysfs interfaces)
and may hang any guest that is started using one of the mdevs
to supply its AP configuration.
This patch limits the potential hang to the AP_RESET_MAX_WAIT
(2000ms) timeout introduced in the preceding commit, and
prevents leakage of queue state to a guest.
_queue_passable() accepted AP_RESPONSE_DECONFIGURED and
AP_RESPONSE_CHECKSTOPPED as passable states in addition to
AP_RESPONSE_NORMAL. Neither DECONFIGURED nor CHECKSTOPPED
confirms that the queue was zeroized; only AP_RESPONSE_NORMAL
(0) does. A queue that is not confirmed zeroized must not be
passed through to a guest, as it may contain key material from
a previous guest or host operation.
To fix this, _queue_passable() is limited to returning true
only when reset_status.response_code == AP_RESPONSE_NORMAL.
A new helper, apq_reset_finalize(), is introduced to ensure
q->reset_status correctly reflects the confirmed end state
of the queue. It copies the full TAPQ status word to
q->reset_status and sets q->reset_status.response_code to
AP_RESPONSE_NORMAL only when apq_status_check() returns 0,
confirming zeroization via TAPQ status bit verification.
For -ENODEV (DECONFIGURED or CHECKSTOPPED), the non-zero
response code is left intact, ensuring _queue_passable()
correctly returns false.
Additionally, vfio_ap_mdev_probe_queue() now calls
vfio_ap_mdev_reset_queue() and flush_work() at probe time
to guarantee a clean queue before it can be assigned to
a guest.
Fixes: dd174833e44e ("s390/vfio-ap: remove upper limit on wait for queue reset to complete")
Cc: stable@vger.kernel.org
Signed-off-by: Anthony Krowiak <akrowiak@linux.ibm.com>
---
drivers/s390/crypto/vfio_ap_ops.c | 63 +++++++++++++++++++++----------
1 file changed, 44 insertions(+), 19 deletions(-)
diff --git a/drivers/s390/crypto/vfio_ap_ops.c b/drivers/s390/crypto/vfio_ap_ops.c
index 672fae69739a..100ec011497f 100644
--- a/drivers/s390/crypto/vfio_ap_ops.c
+++ b/drivers/s390/crypto/vfio_ap_ops.c
@@ -785,14 +785,14 @@ static bool _queue_passable(struct vfio_ap_queue *q)
if (!q)
return false;
- switch (q->reset_status.response_code) {
- case AP_RESPONSE_NORMAL:
- case AP_RESPONSE_DECONFIGURED:
- case AP_RESPONSE_CHECKSTOPPED:
- return true;
- default:
- return false;
- }
+ /*
+ * A queue is only passable if zeroization was confirmed by
+ * apq_reset_check() via TAPQ status bit verification. This is
+ * indicated by reset_status.response_code == AP_RESPONSE_NORMAL (0).
+ * This is to protect against leaking the internal state of the queue
+ * to the guest.
+ */
+ return q->reset_status.response_code == AP_RESPONSE_NORMAL;
}
/*
@@ -2107,6 +2107,30 @@ static void report_aqic_resource_leak(struct vfio_ap_queue *q)
#define WAIT_MSG "Waited %dms for reset of queue %02x.%04x (%u, %u, %u)"
+/**
+ * apq_reset_finalize - store final TAPQ status and free AQIC resources.
+ * @q: the vfio_ap_queue
+ * @status: the final AP queue status returned by PQAP(TAPQ)
+ * @ret: the return value from apq_status_check()
+ *
+ * Copies the full TAPQ status word to q->reset_status so that all status
+ * bits reflect the confirmed end state of the queue. If ret == 0,
+ * zeroization was confirmed and the response code is overridden with
+ * AP_RESPONSE_NORMAL so that _queue_passable() returns true. For
+ * ret == -ENODEV (DECONFIGURED or CHECKSTOPPED), the non-zero response
+ * code is left intact so _queue_passable() correctly returns false.
+ * AQIC resources are then freed.
+ */
+static void apq_reset_finalize(struct vfio_ap_queue *q,
+ struct ap_queue_status *status, int ret)
+{
+ memcpy(&q->reset_status, status, sizeof(*status));
+ if (!ret)
+ q->reset_status.response_code = AP_RESPONSE_NORMAL;
+ if (q->saved_isc != VFIO_AP_ISC_INVALID)
+ vfio_ap_free_aqic_resources(q);
+}
+
static void apq_reset_check(struct work_struct *reset_work)
{
int ret = -EBUSY, elapsed = 0;
@@ -2129,7 +2153,7 @@ static void apq_reset_check(struct work_struct *reset_work)
*/
vfio_ap_free_aqic_resources(q);
return;
-
+ }
if (elapsed >= AP_RESET_MAX_WAIT) {
/*
* Zeroization confirmed (ret == 0): the TAPQ status bits
@@ -2143,8 +2167,10 @@ static void apq_reset_check(struct work_struct *reset_work)
* queue cannot generate interrupts, so the NIB page is
* no longer a DMA target and it is safe to free it.
*/
- if (!ret || ret == -ENODEV)
- goto done;
+ if (!ret || ret == -ENODEV) {
+ apq_reset_finalize(q, &status, ret);
+ return;
+ }
/*
* Timed out without being able to verify zapq completed.
*
@@ -2174,7 +2200,10 @@ static void apq_reset_check(struct work_struct *reset_work)
* apq_reset_check() will re-issue the ZAPQ.
*/
q->reset_status.response_code = AP_RESPONSE_RESET_IN_PROGRESS;
-
+ return;
+ }
+ if (!ret || ret == -ENODEV) {
+ apq_reset_finalize(q, &status, ret);
return;
}
if (ret == -EBUSY) {
@@ -2191,14 +2220,9 @@ static void apq_reset_check(struct work_struct *reset_work)
ret == -EAGAIN) {
status = ap_zapq(q->apqn, 0);
memcpy(&q->reset_status, &status, sizeof(status));
- continue;
}
}
}
-
-done:
- if (q->saved_isc != VFIO_AP_ISC_INVALID)
- vfio_ap_free_aqic_resources(q);
}
static void vfio_ap_mdev_reset_queue(struct vfio_ap_queue *q)
@@ -2698,8 +2722,9 @@ int vfio_ap_mdev_probe_queue(struct ap_device *apdev)
q->apqn = apqn;
q->saved_isc = VFIO_AP_ISC_INVALID;
- memset(&q->reset_status, 0, sizeof(q->reset_status));
INIT_WORK(&q->reset_work, apq_reset_check);
+ vfio_ap_mdev_reset_queue(q);
+ flush_work(&q->reset_work);
if (matrix_mdev) {
vfio_ap_mdev_link_queue(matrix_mdev, q);
@@ -2791,8 +2816,8 @@ void vfio_ap_mdev_remove_queue(struct ap_device *apdev)
vfio_ap_unlink_queue_fr_mdev(q);
dev_set_drvdata(&apdev->device, NULL);
- kfree(q);
release_update_locks_for_mdev(matrix_mdev);
+ kfree(q);
}
/**
--
2.53.0
^ permalink raw reply related [flat|nested] 13+ messages in thread
* [PATCH v7 6/6] s390/vfio-ap: replace guest-reachable WARNs with ratelimited warnings and add dmesg visibility to DBF_WARNs
2026-09-04 22:35 [PATCH v7 0/6] s390/vfio-ap: Fix pre-existing bugs in vfio_ap device driver Anthony Krowiak
` (4 preceding siblings ...)
2026-09-04 22:35 ` [PATCH v7 5/6] s390/vfio-ap: fix queue state leakage to guest and host Anthony Krowiak
@ 2026-09-04 22:35 ` Anthony Krowiak
2026-09-04 22:50 ` sashiko-bot
5 siblings, 1 reply; 13+ messages in thread
From: Anthony Krowiak @ 2026-09-04 22:35 UTC (permalink / raw)
To: linux-s390, linux-kernel, kvm
Cc: jjherne, borntraeger, mjrosato, pasic, alex, kwankhede, fiuczy,
pbonzini, frankja, imbrenda, agordeev, hca, gor
WARN and WARN_ONCE macros in code paths reachable by a guest
can be triggered repeatedly by a malicious or misbehaving guest,
flooding the kernel log and potentially impacting system
stability. Replace all WARN and WARN_ONCE calls reachable from
the guest AP interrupt enable/disable and queue reset paths with
ratelimited warning functions. When the queue is assigned to
an mdev, dev_warn_ratelimited() is used so the mdev device name
(which includes the UUID) appears in the message. Otherwise,
pr_warn_ratelimited() is used.
Four reporting functions are introduced:
report_tapq_rc() - reports an invalid or unexpected response
code from PQAP(TAPQ). Used in vfio_ap_wait_for_irqclear() and
apq_status_check(). The signatures of both functions are changed
to accept a struct vfio_ap_queue pointer instead of an apqn so
the queue's mdev context is available for reporting.
report_irqclear_timeout() - reports a timeout waiting for the
IR bit to clear after a PQAP(AQIC) disable in
vfio_ap_wait_for_irqclear().
report_aqic_disable_error() - reports a failed PQAP(AQIC)
disable operation in vfio_ap_irq_disable(). Replaces three
WARN_ONCE calls covering the non-operational queue, rejected
disable, and retry exhaustion cases.
report_zapq_rc() - reports an invalid response code from
PQAP(ZAPQ) in vfio_ap_mdev_reset_queue().
The VFIO_AP_DBF_WARN() calls in vfio_ap_irq_enable() and
handle_pqap() are retained but augmented with companion
dev_warn_ratelimited() or pr_warn_ratelimited() calls.
VFIO_AP_DBF_WARN() writes only to the s390 debug feature
ring buffer, which requires a sysadmin to know to look in
/sys/kernel/debug/s390dbf/ to find the messages. The companion
dmesg log entries ensure that warning conditions are immediately
visible in the kernel log without requiring familiarity with
the s390 debug feature infrastructure.
Signed-off-by: Anthony Krowiak <akrowiak@linux.ibm.com>
---
drivers/s390/crypto/vfio_ap_ops.c | 128 +++++++++++++++++++++++-------
1 file changed, 98 insertions(+), 30 deletions(-)
diff --git a/drivers/s390/crypto/vfio_ap_ops.c b/drivers/s390/crypto/vfio_ap_ops.c
index 100ec011497f..d037632df80e 100644
--- a/drivers/s390/crypto/vfio_ap_ops.c
+++ b/drivers/s390/crypto/vfio_ap_ops.c
@@ -247,13 +247,65 @@ static struct vfio_ap_queue *vfio_ap_mdev_get_queue(
*
* - -ETIMEDOUT the function timed out before the IR bit was cleared.
*/
-static int vfio_ap_wait_for_irqclear(int apqn)
+static void report_tapq_rc(struct vfio_ap_queue *q, u8 rc)
+{
+ if (q->matrix_mdev)
+ dev_warn_ratelimited(mdev_dev(q->matrix_mdev->mdev),
+ "PQAP(TAPQ) for %02x.%04x failed with invalid rc=%#02x\n",
+ AP_QID_CARD(q->apqn),
+ AP_QID_QUEUE(q->apqn), rc);
+ else
+ pr_warn_ratelimited("PQAP(TAPQ) for %02x.%04x failed with invalid rc=%#02x\n",
+ AP_QID_CARD(q->apqn),
+ AP_QID_QUEUE(q->apqn), rc);
+}
+
+static void report_irqclear_timeout(struct vfio_ap_queue *q, u8 rc)
+{
+ if (q->matrix_mdev)
+ dev_warn_ratelimited(mdev_dev(q->matrix_mdev->mdev),
+ "PQAP(TAPQ) timed out waiting for IRQ clear on %02x.%04x: rc=%#02x\n",
+ AP_QID_CARD(q->apqn),
+ AP_QID_QUEUE(q->apqn), rc);
+ else
+ pr_warn_ratelimited("PQAP(TAPQ) timed out waiting for IRQ clear on %02x.%04x: rc=%#02x\n",
+ AP_QID_CARD(q->apqn),
+ AP_QID_QUEUE(q->apqn), rc);
+}
+
+static void report_aqic_disable_error(struct vfio_ap_queue *q, u8 rc)
+{
+ if (q->matrix_mdev)
+ dev_warn_ratelimited(mdev_dev(q->matrix_mdev->mdev),
+ "PQAP(AQIC) disable for %02x.%04x failed with rc=%#02x\n",
+ AP_QID_CARD(q->apqn),
+ AP_QID_QUEUE(q->apqn), rc);
+ else
+ pr_warn_ratelimited("PQAP(AQIC) disable for %02x.%04x failed with rc=%#02x\n",
+ AP_QID_CARD(q->apqn),
+ AP_QID_QUEUE(q->apqn), rc);
+}
+
+static void report_zapq_rc(struct vfio_ap_queue *q, u8 rc)
+{
+ if (q->matrix_mdev)
+ dev_warn_ratelimited(mdev_dev(q->matrix_mdev->mdev),
+ "PQAP(ZAPQ) for %02x.%04x failed with invalid rc=%#02x\n",
+ AP_QID_CARD(q->apqn),
+ AP_QID_QUEUE(q->apqn), rc);
+ else
+ pr_warn_ratelimited("PQAP(ZAPQ) for %02x.%04x failed with invalid rc=%#02x\n",
+ AP_QID_CARD(q->apqn),
+ AP_QID_QUEUE(q->apqn), rc);
+}
+
+static int vfio_ap_wait_for_irqclear(struct vfio_ap_queue *q)
{
struct ap_queue_status status;
int retry = 5;
do {
- status = ap_tapq(apqn, NULL);
+ status = ap_tapq(q->apqn, NULL);
switch (status.response_code) {
case AP_RESPONSE_NORMAL:
case AP_RESPONSE_RESET_IN_PROGRESS:
@@ -267,15 +319,12 @@ static int vfio_ap_wait_for_irqclear(int apqn)
case AP_RESPONSE_DECONFIGURED:
case AP_RESPONSE_CHECKSTOPPED:
default:
- WARN_ONCE(1, "%s: tapq rc %02x: %04x\n", __func__,
- status.response_code, apqn);
+ report_tapq_rc(q, status.response_code);
return -ENODEV;
}
} while (--retry);
- WARN_ONCE(1, "%s: tapq rc %02x: timed out waiting for interrupts disabled for %02x.%04x\n",
- __func__, status.response_code,
- AP_QID_CARD(apqn), AP_QID_QUEUE(apqn));
+ report_irqclear_timeout(q, status.response_code);
return -ETIMEDOUT;
}
@@ -347,7 +396,7 @@ static struct ap_queue_status vfio_ap_irq_disable(struct vfio_ap_queue *q)
* wait until interrupt processing has been disabled
* before proceeding.
*/
- ret = vfio_ap_wait_for_irqclear(q->apqn);
+ ret = vfio_ap_wait_for_irqclear(q);
if (ret == 0 || ret == -ENODEV)
goto end_free;
/*
@@ -374,8 +423,7 @@ static struct ap_queue_status vfio_ap_irq_disable(struct vfio_ap_queue *q)
case AP_RESPONSE_DECONFIGURED:
case AP_RESPONSE_CHECKSTOPPED:
/* AP not operational; no further interrupts possible */
- WARN_ONCE(1, "%s: ap_aqic status %d\n", __func__,
- status.response_code);
+ report_aqic_disable_error(q, status.response_code);
goto end_free;
case AP_RESPONSE_INVALID_ADDRESS:
case AP_RESPONSE_INVALID_GISA:
@@ -387,14 +435,12 @@ static struct ap_queue_status vfio_ap_irq_disable(struct vfio_ap_queue *q)
* and the hardware still holds the NIB address. Do not
* free resources.
*/
- WARN_ONCE(1, "%s: ap_aqic status %d\n", __func__,
- status.response_code);
+ report_aqic_disable_error(q, status.response_code);
goto end_fail;
}
} while (retries--);
- WARN_ONCE(1, "%s: ap_aqic status %d\n", __func__,
- status.response_code);
+ report_aqic_disable_error(q, status.response_code);
end_fail:
/*
@@ -523,7 +569,10 @@ static struct ap_queue_status vfio_ap_irq_enable(struct vfio_ap_queue *q,
if (vfio_ap_validate_nib(vcpu, &nib)) {
VFIO_AP_DBF_WARN("%s: invalid NIB address: nib=%pad, apqn=%#04x\n",
__func__, &nib, q->apqn);
-
+ dev_warn_ratelimited(mdev_dev(q->matrix_mdev->mdev),
+ "PQAP(AQIC) enable for %02x.%04x: invalid NIB address %pad\n",
+ AP_QID_CARD(q->apqn),
+ AP_QID_QUEUE(q->apqn), &nib);
status.response_code = AP_RESPONSE_INVALID_ADDRESS;
return status;
}
@@ -538,7 +587,10 @@ static struct ap_queue_status vfio_ap_irq_enable(struct vfio_ap_queue *q,
VFIO_AP_DBF_WARN("%s: vfio_pin_pages failed: rc=%d,"
"nib=%pad, apqn=%#04x\n",
__func__, ret, &nib, q->apqn);
-
+ dev_warn_ratelimited(mdev_dev(q->matrix_mdev->mdev),
+ "PQAP(AQIC) enable for %02x.%04x: vfio_pin_pages failed rc=%d\n",
+ AP_QID_CARD(q->apqn),
+ AP_QID_QUEUE(q->apqn), ret);
status.response_code = AP_RESPONSE_INVALID_ADDRESS;
return status;
}
@@ -561,7 +613,10 @@ static struct ap_queue_status vfio_ap_irq_enable(struct vfio_ap_queue *q,
if (nisc < 0) {
VFIO_AP_DBF_WARN("%s: gisc registration failed: nisc=%d, isc=%d, apqn=%#04x\n",
__func__, nisc, isc, q->apqn);
-
+ dev_warn_ratelimited(mdev_dev(q->matrix_mdev->mdev),
+ "PQAP(AQIC) enable for %02x.%04x: GISC registration failed rc=%d isc=%d\n",
+ AP_QID_CARD(q->apqn),
+ AP_QID_QUEUE(q->apqn), nisc, isc);
vfio_unpin_pages(&q->matrix_mdev->vdev, nib, 1);
status.response_code = AP_RESPONSE_INVALID_ADDRESS;
return status;
@@ -596,9 +651,14 @@ static struct ap_queue_status vfio_ap_irq_enable(struct vfio_ap_queue *q,
* ISC that were prepared for this (rejected) request.
*/
ret = kvm_s390_gisc_unregister(kvm, isc);
- if (ret)
+ if (ret) {
VFIO_AP_DBF_WARN("%s: kvm_s390_gisc_unregister: rc=%d isc=%d, apqn=%#04x\n",
__func__, ret, isc, q->apqn);
+ dev_warn_ratelimited(mdev_dev(q->matrix_mdev->mdev),
+ "PQAP(AQIC) enable for %02x.%04x: GISC unregister failed rc=%d isc=%d\n",
+ AP_QID_CARD(q->apqn),
+ AP_QID_QUEUE(q->apqn), ret, isc);
+ }
vfio_unpin_pages(&q->matrix_mdev->vdev, nib, 1);
break;
}
@@ -611,6 +671,11 @@ static struct ap_queue_status vfio_ap_irq_enable(struct vfio_ap_queue *q,
aqic_gisa.zone, aqic_gisa.ir, aqic_gisa.gisc,
aqic_gisa.gf, aqic_gisa.gisa, aqic_gisa.isc,
q->apqn);
+ dev_warn_ratelimited(mdev_dev(q->matrix_mdev->mdev),
+ "PQAP(AQIC) enable for %02x.%04x failed with rc=%#02x\n",
+ AP_QID_CARD(q->apqn),
+ AP_QID_QUEUE(q->apqn),
+ status.response_code);
}
return status;
@@ -695,7 +760,8 @@ static int handle_pqap(struct kvm_vcpu *vcpu)
if (!(vcpu->arch.sie_block->eca & ECA_AIV)) {
VFIO_AP_DBF_WARN("%s: AIV facility not installed: apqn=0x%04x, eca=0x%04x\n",
__func__, apqn, vcpu->arch.sie_block->eca);
-
+ pr_warn_ratelimited("PQAP(AQIC) for %02x.%04x: AIV facility not installed\n",
+ AP_QID_CARD(apqn), AP_QID_QUEUE(apqn));
return -EOPNOTSUPP;
}
@@ -704,7 +770,8 @@ static int handle_pqap(struct kvm_vcpu *vcpu)
if (!vcpu->kvm->arch.crypto.pqap_hook) {
VFIO_AP_DBF_WARN("%s: PQAP(AQIC) hook not registered with the vfio_ap driver: apqn=0x%04x\n",
__func__, apqn);
-
+ pr_warn_ratelimited("PQAP(AQIC) for %02x.%04x: hook not registered with the vfio_ap driver\n",
+ AP_QID_CARD(apqn), AP_QID_QUEUE(apqn));
goto out_unlock;
}
@@ -717,6 +784,9 @@ static int handle_pqap(struct kvm_vcpu *vcpu)
VFIO_AP_DBF_WARN("%s: mdev %08lx-%04lx-%04lx-%04lx-%04lx%08lx not in use: apqn=0x%04x\n",
__func__, uuid[0], uuid[1], uuid[2],
uuid[3], uuid[4], uuid[5], apqn);
+ dev_warn_ratelimited(mdev_dev(matrix_mdev->mdev),
+ "PQAP(AQIC) for %02x.%04x: mdev not in use\n",
+ AP_QID_CARD(apqn), AP_QID_QUEUE(apqn));
goto out_unlock;
}
@@ -725,6 +795,9 @@ static int handle_pqap(struct kvm_vcpu *vcpu)
VFIO_AP_DBF_WARN("%s: Queue %02x.%04x not bound to the vfio_ap driver\n",
__func__, AP_QID_CARD(apqn),
AP_QID_QUEUE(apqn));
+ dev_warn_ratelimited(mdev_dev(matrix_mdev->mdev),
+ "PQAP(AQIC) for %02x.%04x: queue not bound to the vfio_ap driver\n",
+ AP_QID_CARD(apqn), AP_QID_QUEUE(apqn));
goto out_unlock;
}
@@ -2008,7 +2081,8 @@ static struct vfio_ap_queue *vfio_ap_find_queue(int apqn)
return q;
}
-static int apq_status_check(int apqn, struct ap_queue_status *status)
+static int apq_status_check(struct vfio_ap_queue *q,
+ struct ap_queue_status *status)
{
switch (status->response_code) {
case AP_RESPONSE_NORMAL:
@@ -2070,10 +2144,7 @@ static int apq_status_check(int apqn, struct ap_queue_status *status)
return -EAGAIN;
default:
- WARN(true,
- "failed to verify reset of queue %02x.%04x: TAPQ rc=%u\n",
- AP_QID_CARD(apqn), AP_QID_QUEUE(apqn),
- status->response_code);
+ report_tapq_rc(q, status->response_code);
return -EIO;
}
}
@@ -2143,7 +2214,7 @@ static void apq_reset_check(struct work_struct *reset_work)
msleep(AP_RESET_INTERVAL);
elapsed += AP_RESET_INTERVAL;
status = ap_tapq(q->apqn, NULL);
- ret = apq_status_check(q->apqn, &status);
+ ret = apq_status_check(q, &status);
if (ret == -EIO) {
/*
* TAPQ returned an invalid response code. This
@@ -2255,10 +2326,7 @@ static void vfio_ap_mdev_reset_queue(struct vfio_ap_queue *q)
* AP interrupts or DMA-write to the NIB, free the AQIC resources
* rather than leak them.
*/
- WARN(true,
- "PQAP/ZAPQ for %02x.%04x failed with invalid rc=%u\n",
- AP_QID_CARD(q->apqn), AP_QID_QUEUE(q->apqn),
- status.response_code);
+ report_zapq_rc(q, status.response_code);
vfio_ap_free_aqic_resources(q);
}
}
--
2.53.0
^ permalink raw reply related [flat|nested] 13+ messages in thread
* Re: [PATCH v7 4/6] s390/vfio-ap: Use AP_DOMAINS for adm_add bitmap size in vfio_ap_mdev_cfg_add()
2026-09-04 22:35 ` [PATCH v7 4/6] s390/vfio-ap: Use AP_DOMAINS for adm_add bitmap size in vfio_ap_mdev_cfg_add() Anthony Krowiak
@ 2026-09-04 22:46 ` sashiko-bot
0 siblings, 0 replies; 13+ messages in thread
From: sashiko-bot @ 2026-09-04 22:46 UTC (permalink / raw)
To: Anthony Krowiak
Cc: linux-s390, kvm, Alexander Gordeev, Christian Borntraeger,
Vasily Gorbik, Heiko Carstens
> Domain and control domain bitmaps are sized by the AP_DOMAINS constant, not
> AP_DEVICES. The two constants are both 256 today so there is no functional
> impact, but using the wrong constant is inconsistent with every operation
> on aqm/adm bitmaps.
>
> Use AP_DOMAINS to keep the code consistent and correct in case
> the two constants ever diverge.
>
> Note:
> This patch was submitted in response to a sashiko review comment pointing
> out there are other functions besides vfio_ap_mdev_cfg_add(), so there are
> fixes included here for those also. The subject line was kept the same
> since this is in v2 of this patch.
>
> Signed-off-by: Anthony Krowiak <akrowiak@linux.ibm.com>
Sashiko has reviewed this patch and found no issues. It looks great!
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260904223531.1611088-1-akrowiak@linux.ibm.com?part=4
^ permalink raw reply [flat|nested] 13+ messages in thread
* Re: [PATCH v7 5/6] s390/vfio-ap: fix queue state leakage to guest and host
2026-09-04 22:35 ` [PATCH v7 5/6] s390/vfio-ap: fix queue state leakage to guest and host Anthony Krowiak
@ 2026-09-04 22:46 ` sashiko-bot
0 siblings, 0 replies; 13+ messages in thread
From: sashiko-bot @ 2026-09-04 22:46 UTC (permalink / raw)
To: Anthony Krowiak
Cc: linux-s390, Alexander Gordeev, Vasily Gorbik, kvm,
Christian Borntraeger, Heiko Carstens
Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
Pre-existing issues:
- [High] A hardware error during queue reset circumvents the newly introduced security check, incorrectly marking a potentially un-zeroized queue as 'passable'.
--
commit 239f603c9ac4d1441b4d22a11bde0df9a2b5aa5c
Author: Anthony Krowiak <akrowiak@linux.ibm.com>
s390/vfio-ap: fix queue state leakage to guest and host
This commit introduces an upper bound on the wait time for a queue reset
to complete and prevents the leakage of queue state to guests. It ensures
zeroization is strictly confirmed by returning true in _queue_passable()
only when the response code is AP_RESPONSE_NORMAL.
> diff --git a/drivers/s390/crypto/vfio_ap_ops.c b/drivers/s390/crypto/vfio_ap_ops.c
> --- a/drivers/s390/crypto/vfio_ap_ops.c
> +++ b/drivers/s390/crypto/vfio_ap_ops.c
> @@ -785,14 +785,14 @@ static bool _queue_passable(struct vfio_ap_queue *q)
> if (!q)
> return false;
>
> - switch (q->reset_status.response_code) {
> - case AP_RESPONSE_NORMAL:
> - case AP_RESPONSE_DECONFIGURED:
> - case AP_RESPONSE_CHECKSTOPPED:
> - return true;
> - default:
> - return false;
> - }
> + /*
> + * A queue is only passable if zeroization was confirmed by
> + * apq_reset_check() via TAPQ status bit verification. This is
> + * indicated by reset_status.response_code == AP_RESPONSE_NORMAL (0).
> + * This is to protect against leaking the internal state of the queue
> + * to the guest.
> + */
> + return q->reset_status.response_code == AP_RESPONSE_NORMAL;
> }
[ ... ]
> @@ -2129,7 +2153,7 @@ static void apq_reset_check(struct work_struct *reset_work)
> */
> vfio_ap_free_aqic_resources(q);
> return;
[Severity: High]
This is a pre-existing issue, but does this error path leave
q->reset_status.response_code set to AP_RESPONSE_NORMAL if a hardware or
firmware fault occurs?
When vfio_ap_mdev_reset_queue() successfully initiates a reset, ap_zapq()
returns AP_RESPONSE_NORMAL (0), which is initially copied into
q->reset_status.
If ap_tapq() subsequently returns an invalid response code causing
apq_status_check() to return -EIO, this workqueue function aborts and frees
the AQIC resources. However, it exits without updating q->reset_status to a
failure state.
Because _queue_passable() now strictly checks for AP_RESPONSE_NORMAL, could
this fail-open state result in an un-zeroized queue being incorrectly passed
to an untrusted guest, leaking cryptographic key material?
> -
> + }
> if (elapsed >= AP_RESET_MAX_WAIT) {
> /*
> * Zeroization confirmed (ret == 0): the TAPQ status bits
> @@ -2143,8 +2167,10 @@ static void apq_reset_check(struct work_struct *reset_work)
> * queue cannot generate interrupts, so the NIB page is
> * no longer a DMA target and it is safe to free it.
> */
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260904223531.1611088-1-akrowiak@linux.ibm.com?part=5
^ permalink raw reply [flat|nested] 13+ messages in thread
* Re: [PATCH v7 2/6] s390/vfio-ap: Fix failure to release IRQ notification eventfd contexts
2026-09-04 22:35 ` [PATCH v7 2/6] s390/vfio-ap: Fix failure to release IRQ notification eventfd contexts Anthony Krowiak
@ 2026-09-04 22:49 ` sashiko-bot
0 siblings, 0 replies; 13+ messages in thread
From: sashiko-bot @ 2026-09-04 22:49 UTC (permalink / raw)
To: Anthony Krowiak
Cc: Vasily Gorbik, linux-s390, Alexander Gordeev, kvm,
Christian Borntraeger, Heiko Carstens
Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
Pre-existing issues:
- [High] Out-of-bounds read of user memory and logic error in `vfio_ap_set_request_irq` and `vfio_ap_set_cfg_change_irq`.
--
commit 771c755d8bf563861fa80183f68a6dbe8966844c
Author: Anthony Krowiak <akrowiak@linux.ibm.com>
s390/vfio-ap: Fix failure to release IRQ notification eventfd contexts
When userspace registers IRQ notification eventfds via the
VFIO_DEVICE_SET_IRQS ioctl, vfio_ap_set_request_irq() and
vfio_ap_set_cfg_change_irq() each call eventfd_ctx_fdget(), which
takes a reference on the eventfd_ctx and stores it. These references
are dropped only when userspace explicitly replaces or clears them,
causing a leak if the device is closed without explicit teardown. This
commit fixes the leak by introducing vfio_ap_mdev_release_eventfds()
to clean up the references during close_device().
> diff --git a/drivers/s390/crypto/vfio_ap_ops.c b/drivers/s390/crypto/vfio_ap_ops.c
> index 9d4f5b9e3301f..4a31454d725a9 100644
> --- a/drivers/s390/crypto/vfio_ap_ops.c
> +++ b/drivers/s390/crypto/vfio_ap_ops.c
[ ... ]
This isn't a bug introduced by this patch, but I noticed a pre-existing issue
in vfio_ap_set_request_irq() and vfio_ap_set_cfg_change_irq().
If a userspace process issues the VFIO_DEVICE_SET_IRQS ioctl with count == 0
and DATA_EVENTFD to clear the IRQ, could this unconditionally read out of
bounds?
drivers/s390/crypto/vfio_ap_ops.c:vfio_ap_set_request_irq() {
s32 fd;
void __user *data;
unsigned long minsz;
...
minsz = offsetofend(struct vfio_irq_set, count);
data = (void __user *)(arg + minsz);
if (get_user(fd, (s32 __user *)data))
return -EFAULT;
...
}
[Severity: High]
Does this code read past the user-provided struct vfio_irq_set buffer if
count is 0?
Because there is no check for count > 0 before doing get_user(), it appears
this reads 4 bytes past the user-provided buffer and interprets the
uninitialized data as a file descriptor instead of safely disabling the
interrupt.
The same pattern appears in vfio_ap_set_cfg_change_irq():
drivers/s390/crypto/vfio_ap_ops.c:vfio_ap_set_cfg_change_irq() {
s32 fd;
void __user *data;
unsigned long minsz;
...
minsz = offsetofend(struct vfio_irq_set, count);
data = (void __user *)(arg + minsz);
if (get_user(fd, (s32 __user *)data))
return -EFAULT;
...
}
[Severity: High]
Does this code also have the same out-of-bounds read issue when count is 0?
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260904223531.1611088-1-akrowiak@linux.ibm.com?part=2
^ permalink raw reply [flat|nested] 13+ messages in thread
* Re: [PATCH v7 3/6] s390/vfio-ap: Fix unbounded loop in apq_reset_check()
2026-09-04 22:35 ` [PATCH v7 3/6] s390/vfio-ap: Fix unbounded loop in apq_reset_check() Anthony Krowiak
@ 2026-09-04 22:49 ` sashiko-bot
0 siblings, 0 replies; 13+ messages in thread
From: sashiko-bot @ 2026-09-04 22:49 UTC (permalink / raw)
To: Anthony Krowiak
Cc: Christian Borntraeger, Heiko Carstens, linux-s390,
Alexander Gordeev, Vasily Gorbik, kvm
> s390/vfio-ap: Fix unbounded loop in apq_reset_check()
>
> The apq_reset_check() worker polls ap_tapq() in a while(true)
> loop waiting for a queue reset to complete. When ap_tapq()
> returns AP_RESPONSE_BUSY or AP_RESPONSE_RESET_IN_PROGRESS,
> apq_status_check() returns -EBUSY and the loop continues after
> sleeping AP_RESET_INTERVAL (20ms). There is no upper bound on
> how many times the loop iterates, so if the hardware
> continuously returns a busy response the worker runs
> indefinitely.
>
> This is particularly harmful because several callers of
> vfio_ap_mdev_reset_queue() - such as vfio_ap_mdev_reset_queues(),
> vfio_ap_mdev_reset_qlist() and vfio_ap_mdev_remove_queue() -
> call flush_work() on the queue's reset_work while holding one
> [ ... ]
> status->async == 0
>
> Fixes: dd174833e44e ("s390/vfio-ap: remove upper limit on wait for queue reset to complete")
> Cc: stable@vger.kernel.org
> Signed-off-by: Anthony Krowiak <akrowiak@linux.ibm.com>
Sashiko has reviewed this patch and found no issues. It looks great!
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260904223531.1611088-1-akrowiak@linux.ibm.com?part=3
^ permalink raw reply [flat|nested] 13+ messages in thread
* Re: [PATCH v7 6/6] s390/vfio-ap: replace guest-reachable WARNs with ratelimited warnings and add dmesg visibility to DBF_WARNs
2026-09-04 22:35 ` [PATCH v7 6/6] s390/vfio-ap: replace guest-reachable WARNs with ratelimited warnings and add dmesg visibility to DBF_WARNs Anthony Krowiak
@ 2026-09-04 22:50 ` sashiko-bot
0 siblings, 0 replies; 13+ messages in thread
From: sashiko-bot @ 2026-09-04 22:50 UTC (permalink / raw)
To: Anthony Krowiak
Cc: Vasily Gorbik, linux-s390, Christian Borntraeger,
Alexander Gordeev, kvm, Heiko Carstens
> WARN and WARN_ONCE macros in code paths reachable by a guest
> can be triggered repeatedly by a malicious or misbehaving guest,
> flooding the kernel log and potentially impacting system
> stability. Replace all WARN and WARN_ONCE calls reachable from
> the guest AP interrupt enable/disable and queue reset paths with
> ratelimited warning functions. When the queue is assigned to
> an mdev, dev_warn_ratelimited() is used so the mdev device name
> (which includes the UUID) appears in the message. Otherwise,
> pr_warn_ratelimited() is used.
>
> Four reporting functions are introduced:
>
> report_tapq_rc() - reports an invalid or unexpected response
> code from PQAP(TAPQ). Used in vfio_ap_wait_for_irqclear() and
> apq_status_check(). The signatures of both functions are changed
> [ ... ]
> dmesg log entries ensure that warning conditions are immediately
> visible in the kernel log without requiring familiarity with
> the s390 debug feature infrastructure.
>
> Signed-off-by: Anthony Krowiak <akrowiak@linux.ibm.com>
Sashiko has reviewed this patch and found no issues. It looks great!
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260904223531.1611088-1-akrowiak@linux.ibm.com?part=6
^ permalink raw reply [flat|nested] 13+ messages in thread
* Re: [PATCH v7 1/6] s390/vfio-ap: Fix leaks of pinned NIB and registered GISC
2026-09-04 22:35 ` [PATCH v7 1/6] s390/vfio-ap: Fix leaks of pinned NIB and registered GISC Anthony Krowiak
@ 2026-09-04 22:51 ` sashiko-bot
0 siblings, 0 replies; 13+ messages in thread
From: sashiko-bot @ 2026-09-04 22:51 UTC (permalink / raw)
To: Anthony Krowiak
Cc: Heiko Carstens, Alexander Gordeev, Christian Borntraeger, kvm,
Vasily Gorbik, linux-s390
Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
- [Critical] Eagerly freeing AQIC resources when ap_zapq() or ap_tapq() returns an unrecognized response allows wild DMA writes to freed host memory.
--
commit 6c73d9ea4d311d5d68e8f5501374842cccbe2c90
Author: Anthony Krowiak <akrowiak@linux.ibm.com>
s390/vfio-ap: Fix leaks of pinned NIB and registered GISC
Several code paths in the vfio_ap driver failed to free the AQIC
resources — the pinned guest NIB page and the registered guest ISC
used to enable interrupts for a queue — when a queue became unavailable
or when unexpected response codes were returned. This could cause memory
exhaustion and depletion of KVM interrupt subclass registrations over time
with repeated dynamic AP reconfiguration.
> diff --git a/drivers/s390/crypto/vfio_ap_ops.c b/drivers/s390/crypto/vfio_ap_ops.c
> index 940c0ff668bee..9d4f5b9e3301f 100644
> --- a/drivers/s390/crypto/vfio_ap_ops.c
> +++ b/drivers/s390/crypto/vfio_ap_ops.c
[ ... ]
> @@ -1959,8 +2087,16 @@ static void apq_reset_check(struct work_struct *reset_work)
> elapsed += AP_RESET_INTERVAL;
> status = ap_tapq(q->apqn, NULL);
> ret = apq_status_check(q->apqn, &status);
> - if (ret == -EIO)
> + if (ret == -EIO) {
> + /*
> + * TAPQ returned an invalid response code. This
> + * indicates a hardware or firmware bug; the queue
> + * cannot generate AP interrupts or DMA-write to the
> + * NIB, so free the AQIC resources rather than leak them.
> + */
> + vfio_ap_free_aqic_resources(q);
> return;
> + }
[Severity: Critical]
If apq_status_check() returns -EIO due to an unrecognized TAPQ response,
is it safe to eagerly free the AQIC resources?
If the hardware is in an unknown state (due to a bug or unrecognized code),
could it still be actively DMA-writing to the pinned Notification Indicator
Byte (NIB) page?
If vfio_ap_free_aqic_resources() unpins the page and returns it to the
system allocator, could a subsequent wild DMA write from the malfunctioning
AP queue corrupt memory belonging to a new owner, potentially causing a host
panic?
[ ... ]
> @@ -1995,22 +2135,30 @@ static void vfio_ap_mdev_reset_queue(struct vfio_ap_queue *q)
> switch (status.response_code) {
> case AP_RESPONSE_NORMAL:
> case AP_RESPONSE_RESET_IN_PROGRESS:
> - case AP_RESPONSE_BUSY:
> case AP_RESPONSE_STATE_CHANGE_IN_PROGRESS:
> /*
> * Let's verify whether the ZAPQ completed successfully on a work queue.
> */
> queue_work(system_long_wq, &q->reset_work);
> break;
> + case AP_RESPONSE_Q_NOT_AVAIL:
> case AP_RESPONSE_DECONFIGURED:
> case AP_RESPONSE_CHECKSTOPPED:
> vfio_ap_free_aqic_resources(q);
> break;
> default:
> + /*
> + * The architecture defines only the response codes above as
> + * valid for ZAPQ. Any other response code indicates a hardware
> + * or firmware bug. Since a malfunctioning queue cannot generate
> + * AP interrupts or DMA-write to the NIB, free the AQIC resources
> + * rather than leak them.
> + */
> WARN(true,
> "PQAP/ZAPQ for %02x.%04x failed with invalid rc=%u\n",
> AP_QID_CARD(q->apqn), AP_QID_QUEUE(q->apqn),
> status.response_code);
> + vfio_ap_free_aqic_resources(q);
> }
> }
[Severity: Critical]
Similarly, if ZAPQ fails with an unrecognized response code in
vfio_ap_mdev_reset_queue(), does freeing the AQIC resources here present the
same wild DMA risk?
Even if a later patch in this series adds a retry loop that explicitly
handles AP_RESPONSE_BUSY, does this default case still expose the host to
memory corruption if any other unexpected response code is returned?
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260904223531.1611088-1-akrowiak@linux.ibm.com?part=1
^ permalink raw reply [flat|nested] 13+ messages in thread
end of thread, other threads:[~2026-09-04 22:51 UTC | newest]
Thread overview: 13+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-09-04 22:35 [PATCH v7 0/6] s390/vfio-ap: Fix pre-existing bugs in vfio_ap device driver Anthony Krowiak
2026-09-04 22:35 ` [PATCH v7 1/6] s390/vfio-ap: Fix leaks of pinned NIB and registered GISC Anthony Krowiak
2026-09-04 22:51 ` sashiko-bot
2026-09-04 22:35 ` [PATCH v7 2/6] s390/vfio-ap: Fix failure to release IRQ notification eventfd contexts Anthony Krowiak
2026-09-04 22:49 ` sashiko-bot
2026-09-04 22:35 ` [PATCH v7 3/6] s390/vfio-ap: Fix unbounded loop in apq_reset_check() Anthony Krowiak
2026-09-04 22:49 ` sashiko-bot
2026-09-04 22:35 ` [PATCH v7 4/6] s390/vfio-ap: Use AP_DOMAINS for adm_add bitmap size in vfio_ap_mdev_cfg_add() Anthony Krowiak
2026-09-04 22:46 ` sashiko-bot
2026-09-04 22:35 ` [PATCH v7 5/6] s390/vfio-ap: fix queue state leakage to guest and host Anthony Krowiak
2026-09-04 22:46 ` sashiko-bot
2026-09-04 22:35 ` [PATCH v7 6/6] s390/vfio-ap: replace guest-reachable WARNs with ratelimited warnings and add dmesg visibility to DBF_WARNs Anthony Krowiak
2026-09-04 22:50 ` sashiko-bot
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox