* [PATCH v2 0/8] KVM: s390: More gmap and vsie fixes
@ 2026-06-01 15:29 Claudio Imbrenda
2026-06-01 15:29 ` [PATCH v2 1/8] KVM: s390: Fix _gmap_unmap_crste() Claudio Imbrenda
` (7 more replies)
0 siblings, 8 replies; 15+ messages in thread
From: Claudio Imbrenda @ 2026-06-01 15:29 UTC (permalink / raw)
To: linux-kernel
Cc: kvm, linux-s390, borntraeger, frankja, david, seiden, nrb,
schlameuss, gra
Another batch of fixups for gmap and vsie. Some minor fixes, some
not-so-minor fixes that could have caused guest corruption under
particular circumstances.
v1->v2:
* Reordered the patches
* _gmap_crstep_xchg_atomic() will now attempt to clear the vsie_notif
bit when failing due to unshadowing; this prevents potential loops
* try_get_locked_pte() will now return -EAGAIN instead of NULL if the
pte was reached but the lock was contended; this prevents potential
loops
* _kvm_s390_pv_make_secure() will now attempt mmap_read_trylock(); this
prevents try_get_locked_pte() from potentially racing
* Take kvm->slots_lock instead of kvm->slots_arch_lock in
kvm_s390_set_mem_control() when handling KVM_S390_VM_MEM_LIMIT_SIZE,
and also take kvm->lock.
* Minor cosmetic / style fixes.
Claudio Imbrenda (8):
KVM: s390: Fix _gmap_unmap_crste()
KVM: s390: Fix _gmap_crstep_xchg_atomic()
KVM: s390: Avoid potentially sleeping while atomic when zapping pages
KVM: s390: Fix guest / virtual address confusion in _essa_clear_cbrl()
KVM: s390: vsie: Fix rmap handling in _do_shadow_crste()
KVM: s390: Fix fault-in code
KVM: s390: Lock pte when making page secure
KVM: s390: Prevent memslots outside the ASCE range
arch/s390/include/asm/gmap_helpers.h | 1 +
arch/s390/kvm/faultin.c | 14 ++--
arch/s390/kvm/gaccess.c | 11 +--
arch/s390/kvm/gmap.c | 19 ++++-
arch/s390/kvm/gmap.h | 3 +
arch/s390/kvm/kvm-s390.c | 30 ++++++-
arch/s390/kvm/priv.c | 8 +-
arch/s390/kvm/pv.c | 21 ++++-
arch/s390/mm/gmap_helpers.c | 117 ++++++++++++++++-----------
9 files changed, 157 insertions(+), 67 deletions(-)
--
2.54.0
^ permalink raw reply [flat|nested] 15+ messages in thread
* [PATCH v2 1/8] KVM: s390: Fix _gmap_unmap_crste()
2026-06-01 15:29 [PATCH v2 0/8] KVM: s390: More gmap and vsie fixes Claudio Imbrenda
@ 2026-06-01 15:29 ` Claudio Imbrenda
2026-06-01 15:29 ` [PATCH v2 2/8] KVM: s390: Fix _gmap_crstep_xchg_atomic() Claudio Imbrenda
` (6 subsequent siblings)
7 siblings, 0 replies; 15+ messages in thread
From: Claudio Imbrenda @ 2026-06-01 15:29 UTC (permalink / raw)
To: linux-kernel
Cc: kvm, linux-s390, borntraeger, frankja, david, seiden, nrb,
schlameuss, gra
In _gmap_unmap_crste(), the crste to be unmapped is zapped calling
gmap_crstep_xchg_atomic() exactly once, and expecting it to succeed.
This is a reasonable sanity check, since kvm->mmu_lock is being held in
write mode, and thus no races should be possible.
An upcoming patch will change the behaviour of gmap_crstep_xchg_atomic()
to return false and clear the vsie_notif bit if the operation triggers
an unshadow operation. With the new behaviour, an unmap operation that
triggers an unshadow would cause the VM to be killed.
Prepare for the change by checking if the vsie_notif bit was set in
the old crste if gmap_crstep_xchg_atomic() fails the first time, and
try a second time. The second time no failures are allowed.
Signed-off-by: Claudio Imbrenda <imbrenda@linux.ibm.com>
Fixes: b827ef02f409 ("KVM: s390: Remove non-atomic dat_crstep_xchg()")
Fixes: a2c17f9270cc ("KVM: s390: New gmap code")
---
arch/s390/kvm/gmap.c | 19 ++++++++++++++++---
1 file changed, 16 insertions(+), 3 deletions(-)
diff --git a/arch/s390/kvm/gmap.c b/arch/s390/kvm/gmap.c
index 957126ab991c..52d55ddea8d4 100644
--- a/arch/s390/kvm/gmap.c
+++ b/arch/s390/kvm/gmap.c
@@ -395,15 +395,28 @@ static long _gmap_unmap_crste(union crste *crstep, gfn_t gfn, gfn_t next, struct
struct gmap_unmap_priv *priv = walk->priv;
struct folio *folio = NULL;
union crste old = *crstep;
+ bool ok;
if (!old.h.fc)
return 0;
if (old.s.fc1.pr && test_bit(GMAP_FLAG_EXPORT_ON_UNMAP, &priv->gmap->flags))
folio = phys_to_folio(crste_origin_large(old));
- /* No races should happen because kvm->mmu_lock is held in write mode */
- KVM_BUG_ON(!gmap_crstep_xchg_atomic(priv->gmap, crstep, old, _CRSTE_EMPTY(old.h.tt), gfn),
- priv->gmap->kvm);
+ /*
+ * No races should happen because kvm->mmu_lock is held in write mode,
+ * but the unmap operation could have triggered an unshadow, which
+ * causes gmap_crstep_xchg_atomic() to return false and clear the
+ * vsie_notif bit. Allow the operation to fail once, if the old crste
+ * had the vsie_notif bit set. A second failure is not allowed, for
+ * the reasons above.
+ */
+ ok = gmap_crstep_xchg_atomic(priv->gmap, crstep, old, _CRSTE_EMPTY(old.h.tt), gfn);
+ if (!ok) {
+ KVM_BUG_ON(!old.s.fc1.vsie_notif, priv->gmap->kvm);
+ old.s.fc1.vsie_notif = 0;
+ ok = gmap_crstep_xchg_atomic(priv->gmap, crstep, old, _CRSTE_EMPTY(old.h.tt), gfn);
+ KVM_BUG_ON(!ok, priv->gmap->kvm);
+ }
if (folio)
uv_convert_from_secure_folio(folio);
--
2.54.0
^ permalink raw reply related [flat|nested] 15+ messages in thread
* [PATCH v2 2/8] KVM: s390: Fix _gmap_crstep_xchg_atomic()
2026-06-01 15:29 [PATCH v2 0/8] KVM: s390: More gmap and vsie fixes Claudio Imbrenda
2026-06-01 15:29 ` [PATCH v2 1/8] KVM: s390: Fix _gmap_unmap_crste() Claudio Imbrenda
@ 2026-06-01 15:29 ` Claudio Imbrenda
2026-06-01 15:29 ` [PATCH v2 3/8] KVM: s390: Avoid potentially sleeping while atomic when zapping pages Claudio Imbrenda
` (5 subsequent siblings)
7 siblings, 0 replies; 15+ messages in thread
From: Claudio Imbrenda @ 2026-06-01 15:29 UTC (permalink / raw)
To: linux-kernel
Cc: kvm, linux-s390, borntraeger, frankja, david, seiden, nrb,
schlameuss, gra
The previous incorrect behaviour cleared the vsie_notif bit without
returning false, which allowed shadow crstes to be installed without
the vsie_notif bit.
Return false and do not perform the operation if an unshadow event has
been triggered, but still attempt to clear the vsie_notif bit from the
existing crste.
This will prevent the installation of shadow crstes without vsie_notif
bit and will also prevent the caller from looping forever if it was
not checking for the sg->invalidated flag.
Signed-off-by: Claudio Imbrenda <imbrenda@linux.ibm.com>
Fixes: b827ef02f409 ("KVM: s390: Remove non-atomic dat_crstep_xchg()")
Fixes: a2c17f9270cc ("KVM: s390: New gmap code")
---
arch/s390/kvm/gmap.h | 3 +++
1 file changed, 3 insertions(+)
diff --git a/arch/s390/kvm/gmap.h b/arch/s390/kvm/gmap.h
index 742e42a31744..5374f21aaf8d 100644
--- a/arch/s390/kvm/gmap.h
+++ b/arch/s390/kvm/gmap.h
@@ -273,11 +273,14 @@ static inline bool __must_check _gmap_crstep_xchg_atomic(struct gmap *gmap, unio
gmap_unmap_prefix(gmap, gfn, gfn + align);
}
if (crste_leaf(oldcrste) && crste_needs_unshadow(oldcrste, newcrste)) {
+ newcrste = oldcrste;
newcrste.s.fc1.vsie_notif = 0;
if (needs_lock)
gmap_handle_vsie_unshadow_event(gmap, gfn);
else
_gmap_handle_vsie_unshadow_event(gmap, gfn);
+ dat_crstep_xchg_atomic(crstep, oldcrste, newcrste, gfn, gmap->asce);
+ return false;
}
if (!oldcrste.s.fc1.d && newcrste.s.fc1.d && !newcrste.s.fc1.s)
SetPageDirty(phys_to_page(crste_origin_large(newcrste)));
--
2.54.0
^ permalink raw reply related [flat|nested] 15+ messages in thread
* [PATCH v2 3/8] KVM: s390: Avoid potentially sleeping while atomic when zapping pages
2026-06-01 15:29 [PATCH v2 0/8] KVM: s390: More gmap and vsie fixes Claudio Imbrenda
2026-06-01 15:29 ` [PATCH v2 1/8] KVM: s390: Fix _gmap_unmap_crste() Claudio Imbrenda
2026-06-01 15:29 ` [PATCH v2 2/8] KVM: s390: Fix _gmap_crstep_xchg_atomic() Claudio Imbrenda
@ 2026-06-01 15:29 ` Claudio Imbrenda
2026-06-01 16:09 ` sashiko-bot
2026-06-01 15:29 ` [PATCH v2 4/8] KVM: s390: Fix guest / virtual address confusion in _essa_clear_cbrl() Claudio Imbrenda
` (4 subsequent siblings)
7 siblings, 1 reply; 15+ messages in thread
From: Claudio Imbrenda @ 2026-06-01 15:29 UTC (permalink / raw)
To: linux-kernel
Cc: kvm, linux-s390, borntraeger, frankja, david, seiden, nrb,
schlameuss, gra
Factor out try_get_locked_pte(), which behaves similarly to
get_locked_pte(), but does not attempt to allocate missing tables and
performs a spin_trylock() instead of blocking.
The new function is also exported, since it will be used in other
patches.
If intermediate entries are missing, there can be no pte swap entry to
free, so it's safe to ignore them.
This avoids potentially sleeping while atomic.
Signed-off-by: Claudio Imbrenda <imbrenda@linux.ibm.com>
Fixes: e38c884df921 ("KVM: s390: Switch to new gmap")
---
arch/s390/include/asm/gmap_helpers.h | 1 +
arch/s390/mm/gmap_helpers.c | 117 ++++++++++++++++-----------
2 files changed, 73 insertions(+), 45 deletions(-)
diff --git a/arch/s390/include/asm/gmap_helpers.h b/arch/s390/include/asm/gmap_helpers.h
index 2d3ae421077e..d2b616604a46 100644
--- a/arch/s390/include/asm/gmap_helpers.h
+++ b/arch/s390/include/asm/gmap_helpers.h
@@ -12,5 +12,6 @@ void gmap_helper_zap_one_page(struct mm_struct *mm, unsigned long vmaddr);
void gmap_helper_discard(struct mm_struct *mm, unsigned long vmaddr, unsigned long end);
int gmap_helper_disable_cow_sharing(void);
void gmap_helper_try_set_pte_unused(struct mm_struct *mm, unsigned long vmaddr);
+pte_t *try_get_locked_pte(struct mm_struct *mm, unsigned long addr, spinlock_t **ptl);
#endif /* _ASM_S390_GMAP_HELPERS_H */
diff --git a/arch/s390/mm/gmap_helpers.c b/arch/s390/mm/gmap_helpers.c
index f8789ffcc05c..396207163ca6 100644
--- a/arch/s390/mm/gmap_helpers.c
+++ b/arch/s390/mm/gmap_helpers.c
@@ -34,6 +34,70 @@ static void ptep_zap_softleaf_entry(struct mm_struct *mm, softleaf_t entry)
swap_put_entries_direct(entry, 1);
}
+/**
+ * try_get_locked_pte() - like get_locked_pte(), but atomic and with trylock
+ * @mm: the mm
+ * @vmaddr: the userspace virtual address whose pte is to be found
+ * @ptl: will be set to the pointer to the lock used to lock the pte in case
+ * of success.
+ *
+ * This function returns the pointer to the pte corresponding to @addr in @mm,
+ * similarly to get_locked_pte(). Unlike get_locked_pte(), no attempt is made
+ * to allocate missing page tables. If a missing or large entry is found, the
+ * function will return NULL. If the ptl lock is contended, %-EAGAIN is
+ * returned.
+ *
+ * In case of success, *@ptl will point to the locked pte lock for the returned
+ * pte, like get_locked_pte() does.
+ *
+ * Context: mmap_lock or vma lock for read or for write needs to be held.
+ * Return:
+ * * %NULL if the pte cannot be reached.
+ * * %-EAGAIN if the pte can be reached, but cannot be locked.
+ * * the pointer to the pte corresponding to @addr in @mm, if it can be reached
+ * and locked.
+ */
+pte_t *try_get_locked_pte(struct mm_struct *mm, unsigned long vmaddr, spinlock_t **ptl)
+{
+ pmd_t *pmdp, pmd, pmdval;
+ pud_t *pudp, pud;
+ p4d_t *p4dp, p4d;
+ pgd_t *pgdp, pgd;
+ pte_t *ptep;
+
+ pgdp = pgd_offset(mm, vmaddr);
+ pgd = pgdp_get(pgdp);
+ if (pgd_none(pgd) || !pgd_present(pgd))
+ return NULL;
+ p4dp = p4d_offset(pgdp, vmaddr);
+ p4d = p4dp_get(p4dp);
+ if (p4d_none(p4d) || !p4d_present(p4d))
+ return NULL;
+ pudp = pud_offset(p4dp, vmaddr);
+ pud = pudp_get(pudp);
+ if (pud_none(pud) || pud_leaf(pud) || !pud_present(pud))
+ return NULL;
+ pmdp = pmd_offset(pudp, vmaddr);
+ pmd = pmdp_get_lockless(pmdp);
+ if (pmd_none(pmd) || pmd_leaf(pmd) || !pmd_present(pmd))
+ return NULL;
+ ptep = pte_offset_map_rw_nolock(mm, pmdp, vmaddr, &pmdval, ptl);
+ if (!ptep)
+ return NULL;
+
+ if (spin_trylock(*ptl)) {
+ if (unlikely(!pmd_same(pmdval, pmdp_get_lockless(pmdp)))) {
+ pte_unmap_unlock(ptep, *ptl);
+ return ERR_PTR(-EAGAIN);
+ }
+ return ptep;
+ }
+
+ pte_unmap(ptep);
+ return ERR_PTR(-EAGAIN);
+}
+EXPORT_SYMBOL_GPL(try_get_locked_pte);
+
/**
* gmap_helper_zap_one_page() - discard a page if it was swapped.
* @mm: the mm
@@ -46,7 +110,7 @@ static void ptep_zap_softleaf_entry(struct mm_struct *mm, softleaf_t entry)
void gmap_helper_zap_one_page(struct mm_struct *mm, unsigned long vmaddr)
{
struct vm_area_struct *vma;
- spinlock_t *ptl;
+ spinlock_t *ptl; /* Lock for the host (userspace) page table */
pte_t *ptep;
mmap_assert_locked(mm);
@@ -57,8 +121,8 @@ void gmap_helper_zap_one_page(struct mm_struct *mm, unsigned long vmaddr)
return;
/* Get pointer to the page table entry */
- ptep = get_locked_pte(mm, vmaddr, &ptl);
- if (unlikely(!ptep))
+ ptep = try_get_locked_pte(mm, vmaddr, &ptl);
+ if (IS_ERR_OR_NULL(ptep))
return;
if (pte_swap(*ptep)) {
ptep_zap_softleaf_entry(mm, softleaf_from_pte(*ptep));
@@ -113,37 +177,9 @@ EXPORT_SYMBOL_GPL(gmap_helper_discard);
*/
void gmap_helper_try_set_pte_unused(struct mm_struct *mm, unsigned long vmaddr)
{
- pmd_t *pmdp, pmd, pmdval;
- pud_t *pudp, pud;
- p4d_t *p4dp, p4d;
- pgd_t *pgdp, pgd;
spinlock_t *ptl; /* Lock for the host (userspace) page table */
pte_t *ptep;
- pgdp = pgd_offset(mm, vmaddr);
- pgd = pgdp_get(pgdp);
- if (pgd_none(pgd) || !pgd_present(pgd))
- return;
-
- p4dp = p4d_offset(pgdp, vmaddr);
- p4d = p4dp_get(p4dp);
- if (p4d_none(p4d) || !p4d_present(p4d))
- return;
-
- pudp = pud_offset(p4dp, vmaddr);
- pud = pudp_get(pudp);
- if (pud_none(pud) || pud_leaf(pud) || !pud_present(pud))
- return;
-
- pmdp = pmd_offset(pudp, vmaddr);
- pmd = pmdp_get_lockless(pmdp);
- if (pmd_none(pmd) || pmd_leaf(pmd) || !pmd_present(pmd))
- return;
-
- ptep = pte_offset_map_rw_nolock(mm, pmdp, vmaddr, &pmdval, &ptl);
- if (!ptep)
- return;
-
/*
* Several paths exists that takes the ptl lock and then call the
* mmu_notifier, which takes the mmu_lock. The unmap path, instead,
@@ -156,21 +192,12 @@ void gmap_helper_try_set_pte_unused(struct mm_struct *mm, unsigned long vmaddr)
* If the lock is contended the bit is not set and the deadlock is
* avoided.
*/
- if (spin_trylock(ptl)) {
- /*
- * Make sure the pte we are touching is still the correct
- * one. In theory this check should not be needed, but
- * better safe than sorry.
- * Disabling interrupts or holding the mmap lock is enough to
- * guarantee that no concurrent updates to the page tables
- * are possible.
- */
- if (likely(pmd_same(pmdval, pmdp_get_lockless(pmdp))))
- __atomic64_or(_PAGE_UNUSED, (long *)ptep);
- spin_unlock(ptl);
- }
+ ptep = try_get_locked_pte(mm, vmaddr, &ptl);
+ if (IS_ERR_OR_NULL(ptep))
+ return;
- pte_unmap(ptep);
+ __atomic64_or(_PAGE_UNUSED, (long *)ptep);
+ pte_unmap_unlock(ptep, ptl);
}
EXPORT_SYMBOL_GPL(gmap_helper_try_set_pte_unused);
--
2.54.0
^ permalink raw reply related [flat|nested] 15+ messages in thread
* [PATCH v2 4/8] KVM: s390: Fix guest / virtual address confusion in _essa_clear_cbrl()
2026-06-01 15:29 [PATCH v2 0/8] KVM: s390: More gmap and vsie fixes Claudio Imbrenda
` (2 preceding siblings ...)
2026-06-01 15:29 ` [PATCH v2 3/8] KVM: s390: Avoid potentially sleeping while atomic when zapping pages Claudio Imbrenda
@ 2026-06-01 15:29 ` Claudio Imbrenda
2026-06-01 16:25 ` sashiko-bot
2026-06-01 15:29 ` [PATCH v2 5/8] KVM: s390: vsie: Fix rmap handling in _do_shadow_crste() Claudio Imbrenda
` (3 subsequent siblings)
7 siblings, 1 reply; 15+ messages in thread
From: Claudio Imbrenda @ 2026-06-01 15:29 UTC (permalink / raw)
To: linux-kernel
Cc: kvm, linux-s390, borntraeger, frankja, david, seiden, nrb,
schlameuss, gra
Until now, gmap_helper_zap_one_page() was being called with the guest
absolute address, but it expects a userspace virtual address.
This meant that in the best case the requested pages were not being
discarded, and in the worst case that the wrong pages were being
discarded.
Fix this by converting the guest absolute address to host virtual
before passing it to gmap_helper_zap_one_page().
Fixes: e38c884df921 ("KVM: s390: Switch to new gmap")
Signed-off-by: Claudio Imbrenda <imbrenda@linux.ibm.com>
---
arch/s390/kvm/priv.c | 8 ++++++--
1 file changed, 6 insertions(+), 2 deletions(-)
diff --git a/arch/s390/kvm/priv.c b/arch/s390/kvm/priv.c
index cc0553da14cb..447ec7ed423d 100644
--- a/arch/s390/kvm/priv.c
+++ b/arch/s390/kvm/priv.c
@@ -1188,6 +1188,7 @@ static void _essa_clear_cbrl(struct kvm_vcpu *vcpu, unsigned long *cbrl, int len
union crste *crstep;
union pgste pgste;
union pte *ptep;
+ hva_t hva;
int i;
lockdep_assert_held(&vcpu->kvm->mmu_lock);
@@ -1199,8 +1200,11 @@ static void _essa_clear_cbrl(struct kvm_vcpu *vcpu, unsigned long *cbrl, int len
if (!ptep || ptep->s.pr)
continue;
pgste = pgste_get_lock(ptep);
- if (pgste.usage == PGSTE_GPS_USAGE_UNUSED || pgste.zero)
- gmap_helper_zap_one_page(vcpu->kvm->mm, cbrl[i]);
+ if (pgste.usage == PGSTE_GPS_USAGE_UNUSED || pgste.zero) {
+ hva = gpa_to_hva(vcpu->kvm, cbrl[i]);
+ if (!kvm_is_error_hva(hva))
+ gmap_helper_zap_one_page(vcpu->kvm->mm, hva);
+ }
pgste_set_unlock(ptep, pgste);
}
}
--
2.54.0
^ permalink raw reply related [flat|nested] 15+ messages in thread
* [PATCH v2 5/8] KVM: s390: vsie: Fix rmap handling in _do_shadow_crste()
2026-06-01 15:29 [PATCH v2 0/8] KVM: s390: More gmap and vsie fixes Claudio Imbrenda
` (3 preceding siblings ...)
2026-06-01 15:29 ` [PATCH v2 4/8] KVM: s390: Fix guest / virtual address confusion in _essa_clear_cbrl() Claudio Imbrenda
@ 2026-06-01 15:29 ` Claudio Imbrenda
2026-06-01 16:41 ` sashiko-bot
2026-06-01 15:29 ` [PATCH v2 6/8] KVM: s390: Fix fault-in code Claudio Imbrenda
` (2 subsequent siblings)
7 siblings, 1 reply; 15+ messages in thread
From: Claudio Imbrenda @ 2026-06-01 15:29 UTC (permalink / raw)
To: linux-kernel
Cc: kvm, linux-s390, borntraeger, frankja, david, seiden, nrb,
schlameuss, gra
Fix _do_shadow_crste() to also apply a mask on the reverse address, to
prevent spurious entries from being created, like already done in
gmap_protect_rmap().
Signed-off-by: Claudio Imbrenda <imbrenda@linux.ibm.com>
Fixes: e38c884df921 ("KVM: s390: Switch to new gmap")
---
arch/s390/kvm/gaccess.c | 11 ++++++-----
1 file changed, 6 insertions(+), 5 deletions(-)
diff --git a/arch/s390/kvm/gaccess.c b/arch/s390/kvm/gaccess.c
index 4f8d5592c9a9..20e28b183c1a 100644
--- a/arch/s390/kvm/gaccess.c
+++ b/arch/s390/kvm/gaccess.c
@@ -1466,15 +1466,17 @@ static int _do_shadow_crste(struct gmap *sg, gpa_t raddr, union crste *host, uni
struct guest_fault *f, bool p)
{
union crste newcrste, oldcrste;
- gfn_t gfn;
+ unsigned long mask;
+ gfn_t r_gfn;
int rc;
lockdep_assert_held(&sg->kvm->mmu_lock);
lockdep_assert_held(&sg->parent->children_lock);
- gfn = f->gfn & (is_pmd(*table) ? _SEGMENT_FR_MASK : _REGION3_FR_MASK);
+ mask = is_pmd(*table) ? _SEGMENT_FR_MASK : _REGION3_FR_MASK;
+ r_gfn = gpa_to_gfn(raddr) & mask;
scoped_guard(spinlock, &sg->host_to_rmap_lock)
- rc = gmap_insert_rmap(sg, gfn, gpa_to_gfn(raddr), host->h.tt);
+ rc = gmap_insert_rmap(sg, f->gfn & mask, r_gfn, host->h.tt);
if (rc)
return rc;
@@ -1497,8 +1499,7 @@ static int _do_shadow_crste(struct gmap *sg, gpa_t raddr, union crste *host, uni
return -EAGAIN;
newcrste = _crste_fc1(f->pfn, oldcrste.h.tt, 0, !p);
- gfn = gpa_to_gfn(raddr);
- while (!dat_crstep_xchg_atomic(table, READ_ONCE(*table), newcrste, gfn, sg->asce))
+ while (!dat_crstep_xchg_atomic(table, READ_ONCE(*table), newcrste, r_gfn, sg->asce))
;
return 0;
}
--
2.54.0
^ permalink raw reply related [flat|nested] 15+ messages in thread
* [PATCH v2 6/8] KVM: s390: Fix fault-in code
2026-06-01 15:29 [PATCH v2 0/8] KVM: s390: More gmap and vsie fixes Claudio Imbrenda
` (4 preceding siblings ...)
2026-06-01 15:29 ` [PATCH v2 5/8] KVM: s390: vsie: Fix rmap handling in _do_shadow_crste() Claudio Imbrenda
@ 2026-06-01 15:29 ` Claudio Imbrenda
2026-06-01 16:52 ` sashiko-bot
2026-06-01 15:29 ` [PATCH v2 7/8] KVM: s390: Lock pte when making page secure Claudio Imbrenda
2026-06-01 15:29 ` [PATCH v2 8/8] KVM: s390: Prevent memslots outside the ASCE range Claudio Imbrenda
7 siblings, 1 reply; 15+ messages in thread
From: Claudio Imbrenda @ 2026-06-01 15:29 UTC (permalink / raw)
To: linux-kernel
Cc: kvm, linux-s390, borntraeger, frankja, david, seiden, nrb,
schlameuss, gra
Fix the fault-in code so that it does not return success if a
concurrent unmap event invalidated the fault-in process between the
best-effort lockless check and the proper check with lock.
The new behaviour is to retry, like the best-effort lockless check
already did.
This prevents the fault-in handler from returning success without
having actually faulted in the requested page.
Fixes: e907ae530133 ("KVM: s390: Add helper functions for fault handling")
Signed-off-by: Claudio Imbrenda <imbrenda@linux.ibm.com>
Reviewed-by: Steffen Eiden <seiden@linux.ibm.com>
---
arch/s390/kvm/faultin.c | 14 +++++++-------
1 file changed, 7 insertions(+), 7 deletions(-)
diff --git a/arch/s390/kvm/faultin.c b/arch/s390/kvm/faultin.c
index ddf0ca71f374..cf542b0a7e8e 100644
--- a/arch/s390/kvm/faultin.c
+++ b/arch/s390/kvm/faultin.c
@@ -36,7 +36,8 @@ int kvm_s390_faultin_gfn(struct kvm_vcpu *vcpu, struct kvm *kvm, struct guest_fa
struct kvm_s390_mmu_cache *mc = NULL;
struct kvm_memory_slot *slot;
unsigned long inv_seq;
- int foll, rc = 0;
+ int rc = -EAGAIN;
+ int foll;
foll = f->write_attempt ? FOLL_WRITE : 0;
foll |= f->attempt_pfault ? FOLL_NOWAIT : 0;
@@ -53,7 +54,7 @@ int kvm_s390_faultin_gfn(struct kvm_vcpu *vcpu, struct kvm *kvm, struct guest_fa
return 0;
}
- while (1) {
+ while (rc == -EAGAIN) {
f->valid = false;
inv_seq = kvm->mmu_invalidate_seq;
/* Pairs with the smp_wmb() in kvm_mmu_invalidate_end(). */
@@ -110,20 +111,19 @@ int kvm_s390_faultin_gfn(struct kvm_vcpu *vcpu, struct kvm *kvm, struct guest_fa
if (!mmu_invalidate_retry_gfn(kvm, inv_seq, f->gfn)) {
f->valid = true;
rc = gmap_link(mc, kvm->arch.gmap, f, slot);
- kvm_release_faultin_page(kvm, f->page, !!rc, f->write_attempt);
- f->page = NULL;
}
+ kvm_release_faultin_page(kvm, f->page, !!rc, f->write_attempt);
}
- kvm_release_faultin_page(kvm, f->page, true, false);
if (rc == -ENOMEM) {
rc = kvm_s390_mmu_cache_topup(mc);
if (rc)
return rc;
- } else if (rc != -EAGAIN) {
- return rc;
+ rc = -EAGAIN;
}
}
+
+ return rc;
}
int kvm_s390_get_guest_page(struct kvm *kvm, struct guest_fault *f, gfn_t gfn, bool w)
--
2.54.0
^ permalink raw reply related [flat|nested] 15+ messages in thread
* [PATCH v2 7/8] KVM: s390: Lock pte when making page secure
2026-06-01 15:29 [PATCH v2 0/8] KVM: s390: More gmap and vsie fixes Claudio Imbrenda
` (5 preceding siblings ...)
2026-06-01 15:29 ` [PATCH v2 6/8] KVM: s390: Fix fault-in code Claudio Imbrenda
@ 2026-06-01 15:29 ` Claudio Imbrenda
2026-06-01 17:10 ` sashiko-bot
2026-06-01 15:29 ` [PATCH v2 8/8] KVM: s390: Prevent memslots outside the ASCE range Claudio Imbrenda
7 siblings, 1 reply; 15+ messages in thread
From: Claudio Imbrenda @ 2026-06-01 15:29 UTC (permalink / raw)
To: linux-kernel
Cc: kvm, linux-s390, borntraeger, frankja, david, seiden, nrb,
schlameuss, gra
Make sure _kvm_s390_pv_make_secure() takes the pte lock for the given
address when attempting to make the page secure.
One of the steps in making the page secure is freezing the folio using
folio_ref_freeze(), which temporarily sets the reference count to 0.
Any attempt to get such a folio while frozen will fail and cause a
warning to be printed.
Other users of folio_ref_freeze() make sure that the page is not mapped
while it's being frozen, thus preventing gup functions from being able
to access it. For _kvm_s390_pv_make_secure(), this is not possible,
because the page needs to be mapped in order for the import to succeed.
By taking the pte lock, gup functions will be blocked until the import
operation is done, thus avoiding the race.
In theory this does not completely solve the issue: if a page is mapped
through multiple mappings, locking one pte does not protect from
calling gup on it through the other mapping. In practice this does not
happen and it is a decent stopgap solution until a more correct
solution is available.
Signed-off-by: Claudio Imbrenda <imbrenda@linux.ibm.com>
Fixes: e38c884df921 ("KVM: s390: Switch to new gmap")
---
arch/s390/kvm/pv.c | 21 ++++++++++++++++++++-
1 file changed, 20 insertions(+), 1 deletion(-)
diff --git a/arch/s390/kvm/pv.c b/arch/s390/kvm/pv.c
index c2dafd812a3b..4b865e75351c 100644
--- a/arch/s390/kvm/pv.c
+++ b/arch/s390/kvm/pv.c
@@ -17,6 +17,7 @@
#include <linux/pagewalk.h>
#include <linux/sched/mm.h>
#include <linux/mmu_notifier.h>
+#include <asm/gmap_helpers.h>
#include "kvm-s390.h"
#include "dat.h"
#include "gaccess.h"
@@ -73,6 +74,7 @@ static bool should_export_before_import(struct uv_cb_header *uvcb, struct mm_str
struct pv_make_secure {
void *uvcb;
struct folio *folio;
+ struct kvm *kvm;
int rc;
bool needs_export;
};
@@ -103,9 +105,21 @@ static void _kvm_s390_pv_make_secure(struct guest_fault *f)
{
struct pv_make_secure *priv = f->priv;
struct folio *folio;
+ spinlock_t *ptl; /* pte lock from try_get_locked_pte() */
+ pte_t *ptep;
folio = pfn_folio(f->pfn);
priv->rc = -EAGAIN;
+
+ if (!mmap_read_trylock(priv->kvm->mm))
+ return;
+
+ ptep = try_get_locked_pte(priv->kvm->mm, gfn_to_hva(priv->kvm, f->gfn), &ptl);
+ if (IS_ERR_VALUE(ptep)) {
+ priv->rc = PTR_ERR(ptep);
+ goto out;
+ }
+
if (folio_trylock(folio)) {
priv->rc = __kvm_s390_pv_make_secure(f, folio);
if (priv->rc == -E2BIG || priv->rc == -EBUSY) {
@@ -114,6 +128,11 @@ static void _kvm_s390_pv_make_secure(struct guest_fault *f)
}
folio_unlock(folio);
}
+
+ if (ptep)
+ pte_unmap_unlock(ptep, ptl);
+out:
+ mmap_read_unlock(priv->kvm->mm);
}
/**
@@ -127,7 +146,7 @@ static void _kvm_s390_pv_make_secure(struct guest_fault *f)
*/
int kvm_s390_pv_make_secure(struct kvm *kvm, unsigned long gaddr, void *uvcb)
{
- struct pv_make_secure priv = { .uvcb = uvcb };
+ struct pv_make_secure priv = { .uvcb = uvcb, .kvm = kvm, };
struct guest_fault f = {
.write_attempt = true,
.gfn = gpa_to_gfn(gaddr),
--
2.54.0
^ permalink raw reply related [flat|nested] 15+ messages in thread
* [PATCH v2 8/8] KVM: s390: Prevent memslots outside the ASCE range
2026-06-01 15:29 [PATCH v2 0/8] KVM: s390: More gmap and vsie fixes Claudio Imbrenda
` (6 preceding siblings ...)
2026-06-01 15:29 ` [PATCH v2 7/8] KVM: s390: Lock pte when making page secure Claudio Imbrenda
@ 2026-06-01 15:29 ` Claudio Imbrenda
2026-06-01 17:26 ` sashiko-bot
7 siblings, 1 reply; 15+ messages in thread
From: Claudio Imbrenda @ 2026-06-01 15:29 UTC (permalink / raw)
To: linux-kernel
Cc: kvm, linux-s390, borntraeger, frankja, david, seiden, nrb,
schlameuss, gra
With KVM_S390_VM_MEM_LIMIT_SIZE, userspace can set the highest address
allowed for the VM. Creating a memslot that lies over the maximum
address does not make sense and is only a potential source of bugs.
Prevent creation of memslots over the maximum address, and prevent the
maximum address from being reduced below the end of existing memslots.
Signed-off-by: Claudio Imbrenda <imbrenda@linux.ibm.com>
---
arch/s390/kvm/kvm-s390.c | 30 ++++++++++++++++++++++++++----
1 file changed, 26 insertions(+), 4 deletions(-)
diff --git a/arch/s390/kvm/kvm-s390.c b/arch/s390/kvm/kvm-s390.c
index e09960c2e6ed..1796a7c645a9 100644
--- a/arch/s390/kvm/kvm-s390.c
+++ b/arch/s390/kvm/kvm-s390.c
@@ -999,7 +999,10 @@ static int kvm_s390_set_mem_control(struct kvm *kvm, struct kvm_device_attr *att
break;
}
case KVM_S390_VM_MEM_LIMIT_SIZE: {
+ struct kvm_memslots *slots;
+ struct kvm_memory_slot *ms;
unsigned long new_limit;
+ int bkt;
if (kvm_is_ucontrol(kvm))
return -EINVAL;
@@ -1007,6 +1010,8 @@ static int kvm_s390_set_mem_control(struct kvm *kvm, struct kvm_device_attr *att
if (get_user(new_limit, (u64 __user *)attr->addr))
return -EFAULT;
+ guard(mutex)(&kvm->lock);
+
if (kvm->arch.mem_limit != KVM_S390_NO_MEM_LIMIT &&
new_limit > kvm->arch.mem_limit)
return -E2BIG;
@@ -1014,12 +1019,27 @@ static int kvm_s390_set_mem_control(struct kvm *kvm, struct kvm_device_attr *att
if (!new_limit)
return -EINVAL;
- ret = -EBUSY;
- if (!kvm->created_vcpus)
+ if (kvm->created_vcpus)
+ return -EBUSY;
+
+ ret = 0;
+ scoped_guard(mutex, &kvm->slots_lock) {
+ slots = kvm_memslots(kvm);
+ if (slots && !kvm_memslots_empty(slots)) {
+ kvm_for_each_memslot(ms, bkt, slots) {
+ if (gpa_to_gfn(new_limit) < ms->base_gfn + ms->npages) {
+ ret = -EBUSY;
+ break;
+ }
+ }
+ }
+ }
+ if (!ret)
ret = gmap_set_limit(kvm->arch.gmap, gpa_to_gfn(new_limit));
+ if (ret)
+ break;
VM_EVENT(kvm, 3, "SET: max guest address: %lu", new_limit);
- VM_EVENT(kvm, 3, "New guest asce: 0x%p",
- (void *)kvm->arch.gmap->asce.val);
+ VM_EVENT(kvm, 3, "New guest asce: 0x%p", (void *)kvm->arch.gmap->asce.val);
break;
}
default:
@@ -5672,6 +5692,8 @@ int kvm_arch_prepare_memory_region(struct kvm *kvm,
return -EINVAL;
if ((new->base_gfn + new->npages) * PAGE_SIZE > kvm->arch.mem_limit)
return -EINVAL;
+ if (!asce_contains_gfn(kvm->arch.gmap->asce, new->base_gfn + new->npages - 1))
+ return -EINVAL;
}
if (!kvm->arch.migration_mode)
--
2.54.0
^ permalink raw reply related [flat|nested] 15+ messages in thread
* Re: [PATCH v2 3/8] KVM: s390: Avoid potentially sleeping while atomic when zapping pages
2026-06-01 15:29 ` [PATCH v2 3/8] KVM: s390: Avoid potentially sleeping while atomic when zapping pages Claudio Imbrenda
@ 2026-06-01 16:09 ` sashiko-bot
0 siblings, 0 replies; 15+ messages in thread
From: sashiko-bot @ 2026-06-01 16:09 UTC (permalink / raw)
To: Claudio Imbrenda; +Cc: kvm
Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
Pre-existing issues:
- [High] ptep_zap_softleaf_entry unconditionally calls swap_put_entries_direct on non-present PTEs, which triggers warnings and fails when processing migration entries.
--
commit 98730d8ed19b62efe3e43850095ef240bb137294
Author: Claudio Imbrenda <imbrenda@linux.ibm.com>
KVM: s390: Avoid potentially sleeping while atomic when zapping pages
This commit factors out try_get_locked_pte(), which performs a spin_trylock()
instead of blocking and avoids allocating missing page tables. This new
function is used to prevent sleeping while atomic when zapping pages.
> diff --git a/arch/s390/mm/gmap_helpers.c b/arch/s390/mm/gmap_helpers.c
> index f8789ffcc05ca..396207163ca6c 100644
> --- a/arch/s390/mm/gmap_helpers.c
> +++ b/arch/s390/mm/gmap_helpers.c
> @@ -34,6 +34,70 @@ static void ptep_zap_softleaf_entry(struct mm_struct *mm, softleaf_t entry)
> swap_put_entries_direct(entry, 1);
[Severity: High]
This isn't a bug introduced by this patch, but does ptep_zap_softleaf_entry()
incorrectly pass non-swap softleaf entries to generic swap reclaim code?
The function acts as a dispatch for non-present PTEs, handling both swap and
migration entries, but unconditionally passes the entry to
swap_put_entries_direct().
Migration entries use swap types greater than MAX_SWAPFILES. When
swap_put_entries_direct() passes this to get_swap_device(), it will fail,
printing a 'Bad swap file entry' error to the kernel log and hitting a
WARN_ON_ONCE(!si).
Because this path is reachable by a guest issuing ESSA instructions on memory
regions undergoing host-side migration, could a malicious guest intentionally
trigger this path to spam the host's dmesg logs on every migrated page,
creating a host-side denial of service?
> }
>
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260601152951.196859-1-imbrenda@linux.ibm.com?part=3
^ permalink raw reply [flat|nested] 15+ messages in thread
* Re: [PATCH v2 4/8] KVM: s390: Fix guest / virtual address confusion in _essa_clear_cbrl()
2026-06-01 15:29 ` [PATCH v2 4/8] KVM: s390: Fix guest / virtual address confusion in _essa_clear_cbrl() Claudio Imbrenda
@ 2026-06-01 16:25 ` sashiko-bot
0 siblings, 0 replies; 15+ messages in thread
From: sashiko-bot @ 2026-06-01 16:25 UTC (permalink / raw)
To: Claudio Imbrenda; +Cc: kvm
Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
Pre-existing issues:
- [High] ptep_zap_softleaf_entry unconditionally calls swap_put_entries_direct for all non-present PTEs, including migration and device private entries, leading to WARN_ON_ONCE and potential reference leaks.
--
commit 9ec1cdb739d15e125c9d1bb7c4db08fb59c07068
Author: Claudio Imbrenda <imbrenda@linux.ibm.com>
KVM: s390: Fix guest / virtual address confusion in _essa_clear_cbrl()
This patch fixes a bug where gmap_helper_zap_one_page was being called with a
guest absolute address instead of the expected host virtual address. It
resolves the issue by converting the address to a host virtual address before
performing the zap.
> diff --git a/arch/s390/kvm/priv.c b/arch/s390/kvm/priv.c
> index cc0553da14cbb..447ec7ed423dc 100644
> --- a/arch/s390/kvm/priv.c
> +++ b/arch/s390/kvm/priv.c
[ ... ]
> @@ -1199,8 +1200,11 @@ static void _essa_clear_cbrl(struct kvm_vcpu *vcpu, unsigned long *cbrl, int len
> if (!ptep || ptep->s.pr)
> continue;
> pgste = pgste_get_lock(ptep);
> - if (pgste.usage == PGSTE_GPS_USAGE_UNUSED || pgste.zero)
> - gmap_helper_zap_one_page(vcpu->kvm->mm, cbrl[i]);
> + if (pgste.usage == PGSTE_GPS_USAGE_UNUSED || pgste.zero) {
> + hva = gpa_to_hva(vcpu->kvm, cbrl[i]);
> + if (!kvm_is_error_hva(hva))
> + gmap_helper_zap_one_page(vcpu->kvm->mm, hva);
^^^^^^^^^^^^^^^^^^^^^^^^
[Severity: High]
This is a pre-existing issue, but does fixing the address passed to
gmap_helper_zap_one_page expose a bug when encountering non-present PTEs?
Looking at gmap_helper_zap_one_page in arch/s390/mm/gmap_helpers.c, it
dispatches all pte_swap entries to ptep_zap_softleaf_entry:
if (pte_swap(*ptep)) {
ptep_zap_softleaf_entry(mm, softleaf_from_pte(*ptep));
pte_clear(mm, vmaddr, ptep);
}
In ptep_zap_softleaf_entry, swap_put_entries_direct is called
unconditionally. For non-genuine swap entries like migration or device
private entries, get_swap_device returns NULL, which causes
swap_put_entries_direct to hit a WARN_ON_ONCE and return early.
Furthermore, for device private entries, the required folio teardown sequence
(normally performed by zap_nonpresent_ptes) is completely skipped. Since
pte_clear is then called, could this lead to a permanent folio reference leak
in addition to triggering a kernel warning?
> + }
> pgste_set_unlock(ptep, pgste);
> }
> }
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260601152951.196859-1-imbrenda@linux.ibm.com?part=4
^ permalink raw reply [flat|nested] 15+ messages in thread
* Re: [PATCH v2 5/8] KVM: s390: vsie: Fix rmap handling in _do_shadow_crste()
2026-06-01 15:29 ` [PATCH v2 5/8] KVM: s390: vsie: Fix rmap handling in _do_shadow_crste() Claudio Imbrenda
@ 2026-06-01 16:41 ` sashiko-bot
0 siblings, 0 replies; 15+ messages in thread
From: sashiko-bot @ 2026-06-01 16:41 UTC (permalink / raw)
To: Claudio Imbrenda; +Cc: kvm
Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
Pre-existing issues:
- [Critical] `_do_shadow_crste()` creates a corrupted Region-3 large page entry (PUD) due to `_crste_fc1()` hardcoding `_SEGMENT_MASK`, leading to hardware Translation-Specification exceptions.
--
commit 249b86f32c315315b0e5e5c41fd24415c3c08a7f
Author: Claudio Imbrenda <imbrenda@linux.ibm.com>
KVM: s390: vsie: Fix rmap handling in _do_shadow_crste()
Fix _do_shadow_crste() to also apply a mask on the reverse address, to
prevent spurious entries from being created, like already done in
gmap_protect_rmap().
> diff --git a/arch/s390/kvm/gaccess.c b/arch/s390/kvm/gaccess.c
> index 4f8d5592c9a92..20e28b183c1ac 100644
> --- a/arch/s390/kvm/gaccess.c
> +++ b/arch/s390/kvm/gaccess.c
[ ... ]
> @@ -1497,8 +1499,7 @@ static int _do_shadow_crste(struct gmap *sg, gpa_t raddr, union crste *host, uni
> return -EAGAIN;
>
> newcrste = _crste_fc1(f->pfn, oldcrste.h.tt, 0, !p);
[Severity: Critical]
This is a pre-existing issue, but does this create a corrupted Region-3 large
page entry when dealing with 2GB large pages?
Looking at _crste_fc1() in arch/s390/kvm/dat.h, it hardcodes _SEGMENT_MASK:
arch/s390/kvm/dat.h:_crste_fc1() {
union crste res = { .val = PFN_PHYS(pfn) & _SEGMENT_MASK };
...
}
If oldcrste.h.tt is TABLE_TYPE_REGION3, f->pfn may have non-zero bits in
the 20-30 range (which map to IBM bits 33-43) if the fault was not at the
start of the 2GB page. Since _SEGMENT_MASK only masks out the lower 20 bits,
these bits remain set.
Will writing this entry with bits 20-30 set (which should be strictly
zero per architecture) cause the hardware to raise a Translation-Specification
exception?
> - gfn = gpa_to_gfn(raddr);
> - while (!dat_crstep_xchg_atomic(table, READ_ONCE(*table), newcrste, gfn, sg->asce))
> + while (!dat_crstep_xchg_atomic(table, READ_ONCE(*table), newcrste, r_gfn, sg->asce))
> ;
> return 0;
> }
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260601152951.196859-1-imbrenda@linux.ibm.com?part=5
^ permalink raw reply [flat|nested] 15+ messages in thread
* Re: [PATCH v2 6/8] KVM: s390: Fix fault-in code
2026-06-01 15:29 ` [PATCH v2 6/8] KVM: s390: Fix fault-in code Claudio Imbrenda
@ 2026-06-01 16:52 ` sashiko-bot
0 siblings, 0 replies; 15+ messages in thread
From: sashiko-bot @ 2026-06-01 16:52 UTC (permalink / raw)
To: Claudio Imbrenda; +Cc: kvm
Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
Pre-existing issues:
- [High] Memory leak of the pinned host page reference when `kvm_s390_new_mmu_cache()` allocation fails.
--
commit 132617ab1e43f506af43740ee664fcdba7a95ff5
Author: Claudio Imbrenda <imbrenda@linux.ibm.com>
KVM: s390: Fix fault-in code
This commit fixes the fault-in code so that it does not return success if
a concurrent unmap event invalidated the fault-in process. It ensures the
code retries properly instead of returning success without actually
faulting in the requested page.
> diff --git a/arch/s390/kvm/faultin.c b/arch/s390/kvm/faultin.c
> index ddf0ca71f3741..cf542b0a7e8e3 100644
> --- a/arch/s390/kvm/faultin.c
> +++ b/arch/s390/kvm/faultin.c
[ ... ]
> @@ -110,20 +111,19 @@ int kvm_s390_faultin_gfn(struct kvm_vcpu *vcpu, struct kvm *kvm, struct guest_fa
> if (!mmu_invalidate_retry_gfn(kvm, inv_seq, f->gfn)) {
> f->valid = true;
> rc = gmap_link(mc, kvm->arch.gmap, f, slot);
> - kvm_release_faultin_page(kvm, f->page, !!rc, f->write_attempt);
> - f->page = NULL;
> }
> + kvm_release_faultin_page(kvm, f->page, !!rc, f->write_attempt);
> }
[Severity: High]
This isn't a bug introduced by this patch, but I have a question about how
f->page is handled when kvm_s390_new_mmu_cache() fails earlier in this loop.
Earlier in kvm_s390_faultin_gfn() in arch/s390/kvm/faultin.c,
__kvm_faultin_pfn() resolves the PFN and pins the host page, storing it
in f->page.
Then, if mc is NULL, the code attempts to allocate a new cache:
if (!mc) {
local_mc = kvm_s390_new_mmu_cache();
if (!local_mc)
return -ENOMEM;
mc = local_mc;
}
When kvm_s390_new_mmu_cache() fails and returns -ENOMEM, it looks like the
function returns directly without calling kvm_release_faultin_page() to drop
the previously acquired reference in f->page.
Callers like kvm_s390_faultin_gfn_simple() and access_guest_page_with_key_gpa()
declare struct guest_fault f locally on the stack and propagate the -ENOMEM
error without inspecting or cleaning up f.page.
Can this permanently leak the pinned host page? Under memory pressure, could
an unprivileged guest repeatedly trigger page faults to accumulate leaked page
references and exhaust host memory?
> - kvm_release_faultin_page(kvm, f->page, true, false);
>
> if (rc == -ENOMEM) {
> rc = kvm_s390_mmu_cache_topup(mc);
> if (rc)
> return rc;
> - } else if (rc != -EAGAIN) {
> - return rc;
> + rc = -EAGAIN;
> }
> }
> +
> + return rc;
> }
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260601152951.196859-1-imbrenda@linux.ibm.com?part=6
^ permalink raw reply [flat|nested] 15+ messages in thread
* Re: [PATCH v2 7/8] KVM: s390: Lock pte when making page secure
2026-06-01 15:29 ` [PATCH v2 7/8] KVM: s390: Lock pte when making page secure Claudio Imbrenda
@ 2026-06-01 17:10 ` sashiko-bot
0 siblings, 0 replies; 15+ messages in thread
From: sashiko-bot @ 2026-06-01 17:10 UTC (permalink / raw)
To: Claudio Imbrenda; +Cc: kvm
Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
Pre-existing issues:
- [Critical] Unconditional conversion of a raw PFN to a folio using `pfn_folio()` in `_kvm_s390_pv_make_secure()` leads to a host kernel panic when applied to device memory.
--
commit e0fa339799f284890bdbcc3dd174cd7125deb24a
Author: Claudio Imbrenda <imbrenda@linux.ibm.com>
KVM: s390: Lock pte when making page secure
This commit ensures that _kvm_s390_pv_make_secure() takes the pte lock
for the given address before freezing the folio during Secure Execution
transitions. This prevents concurrent GUP access while the page is mapped.
> diff --git a/arch/s390/kvm/pv.c b/arch/s390/kvm/pv.c
> index c2dafd812a3b2..4b865e75351c1 100644
> --- a/arch/s390/kvm/pv.c
> +++ b/arch/s390/kvm/pv.c
[ ... ]
> @@ -103,9 +105,21 @@ static void _kvm_s390_pv_make_secure(struct guest_fault *f)
> {
> struct pv_make_secure *priv = f->priv;
> struct folio *folio;
> + spinlock_t *ptl; /* pte lock from try_get_locked_pte() */
> + pte_t *ptep;
>
> folio = pfn_folio(f->pfn);
[Severity: Critical]
This is a pre-existing issue, but can this unconditional conversion of a raw
PFN to a folio cause a host kernel panic when a guest maps device memory?
If a malicious or misconfigured guest maps device memory (e.g., via VFIO PCI)
into its address space and attempts to transition it to Secure Execution,
KVM's fault-in logic will return a valid PFN but set f->page = NULL for raw
device memory without a vmemmap.
If we unconditionally call pfn_folio(f->pfn) before checking f->page,
pfn_to_page() yields an invalid pointer. The page_folio() wrapper then
immediately dereferences this pointer to read the compound_head field.
Furthermore, even if the PFN is valid but points to a reserved page, the
fallback logic in __kvm_s390_pv_make_secure() could attempt to increment
the refcount of a reserved page:
__kvm_s390_pv_make_secure() {
...
if (!f->page)
folio_get(folio);
...
}
Could this crash the kernel via VM_BUG_ON_FOLIO() on systems with
CONFIG_DEBUG_VM enabled? Should the folio conversion be delayed until
after verifying we have a normal refcounted memory page?
> priv->rc = -EAGAIN;
> +
> + if (!mmap_read_trylock(priv->kvm->mm))
> + return;
> +
> + ptep = try_get_locked_pte(priv->kvm->mm, gfn_to_hva(priv->kvm, f->gfn), &ptl);
> + if (IS_ERR_VALUE(ptep)) {
> + priv->rc = PTR_ERR(ptep);
> + goto out;
> + }
> +
> if (folio_trylock(folio)) {
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260601152951.196859-1-imbrenda@linux.ibm.com?part=7
^ permalink raw reply [flat|nested] 15+ messages in thread
* Re: [PATCH v2 8/8] KVM: s390: Prevent memslots outside the ASCE range
2026-06-01 15:29 ` [PATCH v2 8/8] KVM: s390: Prevent memslots outside the ASCE range Claudio Imbrenda
@ 2026-06-01 17:26 ` sashiko-bot
0 siblings, 0 replies; 15+ messages in thread
From: sashiko-bot @ 2026-06-01 17:26 UTC (permalink / raw)
To: Claudio Imbrenda; +Cc: kvm
Thank you for your contribution! Sashiko AI review found 3 potential issue(s) to consider:
New issues:
- [High] Dropping `kvm->slots_lock` before updating the ASCE limit creates a TOCTOU race and data race condition that allows memslots to bypass the new limit and risk reading torn values.
- [High] The exact user-provided memory limit is discarded and new memslots are checked against a coarse, rounded-up hardware ASCE boundary, allowing userspace to bypass the exact limit.
Pre-existing issues:
- [High] Setting a sub-page limit (e.g., 4095 bytes) silently removes the memory limit by expanding the VM address space to the maximum 16 Exabytes.
--
commit afeca87ebcc558baee29b01c63a93364ae7ae27f
Author: Claudio Imbrenda <imbrenda@linux.ibm.com>
KVM: s390: Prevent memslots outside the ASCE range
With KVM_S390_VM_MEM_LIMIT_SIZE, userspace can set the highest address
allowed for the VM. This patch prevents creation of memslots over the
maximum address, and prevents the maximum address from being reduced
below the end of existing memslots.
> diff --git a/arch/s390/kvm/kvm-s390.c b/arch/s390/kvm/kvm-s390.c
> index e09960c2e6ed1..1796a7c645a9d 100644
> --- a/arch/s390/kvm/kvm-s390.c
> +++ b/arch/s390/kvm/kvm-s390.c
> @@ -1014,12 +1019,27 @@ static int kvm_s390_set_mem_control(struct kvm *kvm, struct kvm_device_attr *att
> if (!new_limit)
> return -EINVAL;
[Severity: High]
This is a pre-existing issue, but does this check fail to reject sub-page
size limits that truncate to 0 GFNs?
If a sub-page limit (e.g., 4095) is passed, it passes this zero check but
truncates to 0 when converted via gpa_to_gfn(new_limit) later in the function.
When gmap_set_limit() is called with a 0 limit, gmap_limit_to_type(0)
returns TABLE_TYPE_REGION1, granting the maximum 16 EB limit. Could this
allow an extremely restricted address limit to silently grant the maximum
possible address space?
> - ret = -EBUSY;
> - if (!kvm->created_vcpus)
> + if (kvm->created_vcpus)
> + return -EBUSY;
> +
> + ret = 0;
> + scoped_guard(mutex, &kvm->slots_lock) {
> + slots = kvm_memslots(kvm);
> + if (slots && !kvm_memslots_empty(slots)) {
> + kvm_for_each_memslot(ms, bkt, slots) {
> + if (gpa_to_gfn(new_limit) < ms->base_gfn + ms->npages) {
> + ret = -EBUSY;
> + break;
> + }
> + }
> + }
> + }
> + if (!ret)
> ret = gmap_set_limit(kvm->arch.gmap, gpa_to_gfn(new_limit));
[Severity: High]
Is there a Time-of-Check to Time-of-Use (TOCTOU) race here by dropping
kvm->slots_lock before updating the ASCE limit?
By dropping slots_lock before calling gmap_set_limit(), the 64-bit
gmap->asce value is modified non-atomically under mmu_lock.
Concurrently, KVM_SET_USER_MEMORY_REGION can take slots_lock and call
kvm_arch_prepare_memory_region(), which reads gmap->asce locklessly:
kvm_arch_prepare_memory_region() {
...
if (!asce_contains_gfn(kvm->arch.gmap->asce, ...))
return -EINVAL;
}
Could this allow kvm_arch_prepare_memory_region() to read the old, larger
ASCE limit and validate a memslot that violates the newly requested limit?
Additionally, is there a risk of reading a torn gmap->asce struct value
during the non-atomic modification?
[ ... ]
> @@ -5672,6 +5692,8 @@ int kvm_arch_prepare_memory_region(struct kvm *kvm,
> return -EINVAL;
> if ((new->base_gfn + new->npages) * PAGE_SIZE > kvm->arch.mem_limit)
> return -EINVAL;
> + if (!asce_contains_gfn(kvm->arch.gmap->asce, new->base_gfn + new->npages - 1))
> + return -EINVAL;
[Severity: High]
Does this allow userspace to bypass the exact requested memory limit?
Earlier in kvm_s390_set_mem_control(), the exact new_limit (e.g., 1 GB)
is verified against existing memslots, but it is discarded and never stored
in kvm->arch.mem_limit.
gmap_set_limit() then converts the exact limit to a coarse ASCE boundary,
rounding it up to the next hardware limit (e.g., 2 GB).
Because this new check in kvm_arch_prepare_memory_region() only evaluates
against the coarse ASCE boundary via asce_contains_gfn(), could userspace
request a strict limit but silently allocate memslots up to the next
hardware boundary?
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260601152951.196859-1-imbrenda@linux.ibm.com?part=8
^ permalink raw reply [flat|nested] 15+ messages in thread
end of thread, other threads:[~2026-06-01 17:26 UTC | newest]
Thread overview: 15+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-06-01 15:29 [PATCH v2 0/8] KVM: s390: More gmap and vsie fixes Claudio Imbrenda
2026-06-01 15:29 ` [PATCH v2 1/8] KVM: s390: Fix _gmap_unmap_crste() Claudio Imbrenda
2026-06-01 15:29 ` [PATCH v2 2/8] KVM: s390: Fix _gmap_crstep_xchg_atomic() Claudio Imbrenda
2026-06-01 15:29 ` [PATCH v2 3/8] KVM: s390: Avoid potentially sleeping while atomic when zapping pages Claudio Imbrenda
2026-06-01 16:09 ` sashiko-bot
2026-06-01 15:29 ` [PATCH v2 4/8] KVM: s390: Fix guest / virtual address confusion in _essa_clear_cbrl() Claudio Imbrenda
2026-06-01 16:25 ` sashiko-bot
2026-06-01 15:29 ` [PATCH v2 5/8] KVM: s390: vsie: Fix rmap handling in _do_shadow_crste() Claudio Imbrenda
2026-06-01 16:41 ` sashiko-bot
2026-06-01 15:29 ` [PATCH v2 6/8] KVM: s390: Fix fault-in code Claudio Imbrenda
2026-06-01 16:52 ` sashiko-bot
2026-06-01 15:29 ` [PATCH v2 7/8] KVM: s390: Lock pte when making page secure Claudio Imbrenda
2026-06-01 17:10 ` sashiko-bot
2026-06-01 15:29 ` [PATCH v2 8/8] KVM: s390: Prevent memslots outside the ASCE range Claudio Imbrenda
2026-06-01 17:26 ` sashiko-bot
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox