Linux s390 Architecture development
 help / color / mirror / Atom feed
From: sashiko-bot@kernel.org
To: "Claudio Imbrenda" <imbrenda@linux.ibm.com>, kvm@vger.kernel.org
Cc: linux-s390@vger.kernel.org, "Heiko Carstens" <hca@linux.ibm.com>,
	"Vasily Gorbik" <gor@linux.ibm.com>,
	"Alexander Gordeev" <agordeev@linux.ibm.com>,
	"Christian Borntraeger" <borntraeger@linux.ibm.com>
Subject: Re: [PATCH v4 7/7] KVM: s390: Allocate page tables when CMMA is used
Date: Wed, 28 Oct 2026 19:28:12 +0000	[thread overview]
Message-ID: <sashiko-outbox-188352@kernel.org> (raw)
In-Reply-To: <20261028185422.272146-8-imbrenda@linux.ibm.com>

Thank you for your contribution! Sashiko AI review found 4 potential issue(s) to consider:
- [Critical] KVM: s390: gmap_allocate_all_page_tables_best_effort() drops skeys on split
- [High] KVM: s390: hugepage flags left enabled when enabling CMMA in handle_essa()
- [Medium] KVM: s390: race in handle_essa() setting GMAP_FLAG_USES_CMM before allocation
- [Medium] KVM: s390: page tables allocated after dat_set_cmma_bits() in kvm_s390_set_cmma_bits()
--

commit 2d680e1a8c09302f7abfa247b43bee558b17d8ec
Author: Claudio Imbrenda <imbrenda@linux.ibm.com>

KVM: s390: Allocate page tables when CMMA is used

This commit calls gmap_allocate_all_page_tables_best_effort() in
handle_essa() and kvm_s390_set_cmma_bits() when GMAP_FLAG_USES_CMM is first
set on the gmap, so that page tables are pre-allocated and existing large
page mappings are split when CMMA is used.

> diff --git a/arch/s390/kvm/s390/priv.c b/arch/s390/kvm/s390/priv.c
> index 856939acf012f..4f0dd3173ab91 100644
> --- a/arch/s390/kvm/s390/priv.c
> +++ b/arch/s390/kvm/s390/priv.c
> @@ -1248,7 +1248,10 @@ static int handle_essa(struct kvm_vcpu *vcpu)
>  		 * value really needs to be written to; if the value is
>  		 * already correct, we do nothing and avoid the lock.
>  		 */
> -		set_bit(GMAP_FLAG_USES_CMM, &vcpu->arch.gmap->flags);
> +		if (!uses_cmm(vcpu->arch.gmap)) {
> +			set_bit(GMAP_FLAG_USES_CMM, &vcpu->arch.gmap->flags);
> +			gmap_allocate_all_page_tables_best_effort(vcpu->arch.gmap);
> +		}

[Severity: Medium]
What happens if two vCPUs in a multi-vCPU guest intercept on ESSA
concurrently in handle_essa() when !READ_ONCE(vcpu->kvm->arch.migration_mode)?

In this path only vcpu->mutex and the kvm->srcu read lock are held. When the
first vCPU sees !uses_cmm(vcpu->arch.gmap), it immediately sets
GMAP_FLAG_USES_CMM via set_bit() and then enters
gmap_allocate_all_page_tables_best_effort(), which iterates over all memslots
while repeatedly dropping gmap->kvm->mmu_lock and sleeping in
kvm_s390_mmu_cache_topup():

arch/s390/kvm/gmap/gmap.c:gmap_allocate_all_page_tables_best_effort() {
    ...
		for (gfn = start; gfn < end; gfn += _PAGE_ENTRIES) {
			if (kvm_s390_mmu_cache_topup(mc))
				break;

			guard(write_lock)(&gmap->kvm->mmu_lock);

			dat_entry_walk(mc, gfn, gmap->asce, DAT_WALK_ALLOC | DAT_WALK_SPLIT,
				       TABLE_TYPE_PAGE_TABLE, &crstep, &ptep);
		}
}

If a second vCPU enters handle_essa() while the first vCPU is still running
gmap_allocate_all_page_tables_best_effort(), the second vCPU will observe
uses_cmm(vcpu->arch.gmap) == true, skip the allocation, set
vcpu->arch.sie_block->ecb2 |= ECB2_CMMA, and retry ESSA in SIE before all
page tables have been allocated and all large pages have been split.
Conversely, because !uses_cmm() and set_bit() are not atomic or serialized by
a VM-wide lock such as kvm->slots_arch_lock, two vCPUs can both pass
!uses_cmm() and run duplicate allocation walks across all memslots.

Should the check, flag update, and page table allocation be serialized under
a VM-wide lock so other vCPUs wait until allocation finishes before re-entering
SIE with ECB2_CMMA enabled?

[Severity: Critical]
Can calling gmap_allocate_all_page_tables_best_effort() here (and in
kvm_s390_set_cmma_bits()) corrupt guest storage keys when splitting aged
1M or 2G large pages?

If storage keys are already enabled (GMAP_FLAG_USES_SKEYS) and a 1M large
page has been aged via gmap_age_gfn() -> gmap_clear_young_crste() so that
crste.h.i is set while the storage keys remain in the physical pages,
gmap_allocate_all_page_tables_best_effort() calls dat_entry_walk() without
DAT_WALK_USES_SKEYS:

arch/s390/kvm/gmap/gmap.c:gmap_allocate_all_page_tables_best_effort()
  -> dat_entry_walk(mc, gfn, gmap->asce, DAT_WALK_ALLOC | DAT_WALK_SPLIT, ...)
     -> dat_split_ste()

Inside dat_split_ste() in arch/s390/kvm/gmap/dat.c, saving the physical
storage keys into the newly allocated PGSTEs for an invalid segment entry is
gated on uses_skeys from the walk flags:

arch/s390/kvm/gmap/dat.c:dat_split_ste() {
    ...
			pgste_init.pcl = uses_skeys && init.h.i;
			dat_init_pgstes(pt, pgste_init.val);
		} else {
			dat_init_page_table(pt, init.val, 0);
		}

		if (dat_pmdp_xchg_atomic(pmdp, old, new, gfn, asce)) {
			if (!pgste_init.pcl)
				return 0;
			for (i = 0; i < _PAGE_ENTRIES; i++) {
				union pgste pgste = pt->pgstes[i];

				pgste = dat_save_storage_key_into_pgste(pt->ptes[i], pgste);
				pgste_set_unlock(pt->ptes + i, pgste);
			}
    ...
}

Because DAT_WALK_USES_SKEYS is not passed, pgste_init.pcl is false and the new
PGSTEs are left with zeroed storage keys while pt->ptes[i].h.i == 1. When
those 4K pages are later faulted back in via _gmap_handle_minor_pte_fault() ->
__dat_ptep_xchg() (which does pass uses_skeys(gmap)), old.h.i && !new.h.i
triggers dat_set_storage_key_from_pgste() and overwrites the guest's hardware
storage keys with zero.

Should gmap_allocate_all_page_tables_best_effort() include
(uses_skeys(gmap) ? DAT_WALK_USES_SKEYS : 0) in the flags passed to
dat_entry_walk()?

[Severity: High]
Could leaving GMAP_FLAG_ALLOW_HPAGE_1M and GMAP_FLAG_ALLOW_HPAGE_2G set on
gmap->flags when enabling CMMA lead to a guest vCPU infinite fault loop?

When userspace enables KVM_CAP_S390_HPAGE_1M or KVM_CAP_S390_HPAGE_2G and
also enables KVM_S390_VM_MEM_ENABLE_CMMA, neither handle_essa(),
kvm_s390_set_cmma_bits(), nor gmap_allocate_all_page_tables_best_effort()
clears GMAP_FLAG_ALLOW_HPAGE_1M or GMAP_FLAG_ALLOW_HPAGE_2G, and
gmap_1m_allowed() / gmap_2g_allowed() in arch/s390/kvm/gmap/gmap.c do not
check !uses_cmm(gmap):

arch/s390/kvm/gmap/gmap.c:gmap_1m_allowed() {
	return test_bit(GMAP_FLAG_ALLOW_HPAGE_1M, &gmap->flags) &&
	       !((f->gfn ^ f->pfn) & ~_SEGMENT_FR_MASK) &&
	       slot->base_gfn <= ALIGN_DOWN(f->gfn, _PAGES_PER_SEGMENT) &&
	       slot->base_gfn + slot->npages >= ALIGN(f->gfn + 1, _PAGES_PER_SEGMENT);
}

If gmap_allocate_all_page_tables_best_effort() bails out early on an
allocation failure, if another vCPU faults on a segment not yet visited while
mmu_lock is dropped between segments, or if a memslot is added or moved after
GMAP_FLAG_USES_CMM is set (where s390_kvm_mmu_prepare_memory_region() ->
dat_create_slot() installs _CRSTE_EMPTY entries without page tables),
gmap_link() will install a valid 1M or 2G large CRSTE.

When a vCPU with ECB2_CMMA set subsequently faults on that valid large page,
kvm_s390_faultin_gfn() -> gmap_try_fixup_minor() ->
gmap_handle_minor_crste_fault() sees:

arch/s390/kvm/gmap/gmap.c:gmap_handle_minor_crste_fault() {
    ...
	/* Appropriate permissions already (race with another handler), nothing to do. */
	if (!oldcrste.h.i && !(f->write_attempt && oldcrste.h.p))
		return 0;
    ...
}

and returns 0 without splitting the large page, causing the vCPU to fault
indefinitely. Should GMAP_FLAG_ALLOW_HPAGE_1M and GMAP_FLAG_ALLOW_HPAGE_2G be
cleared (or checked against uses_cmm(gmap)) before splitting existing large
pages?

> diff --git a/arch/s390/kvm/s390/s390.c b/arch/s390/kvm/s390/s390.c
> index 18fb200f854a8..739e8c23c3ae0 100644
> --- a/arch/s390/kvm/s390/s390.c
> +++ b/arch/s390/kvm/s390/s390.c
> @@ -2359,7 +2359,10 @@ static int kvm_s390_set_cmma_bits(struct kvm *kvm,
>  		}
>  	} while (r == -ENOMEM);
>  
> -	set_bit(GMAP_FLAG_USES_CMM, &kvm->arch.gmap->flags);
> +	if (!uses_cmm(kvm->arch.gmap)) {
> +		set_bit(GMAP_FLAG_USES_CMM, &kvm->arch.gmap->flags);
> +		gmap_allocate_all_page_tables_best_effort(kvm->arch.gmap);
> +	}

[Severity: Medium]
In kvm_s390_set_cmma_bits(), is it intentional that
gmap_allocate_all_page_tables_best_effort() is called after
dat_set_cmma_bits() rather than before it?

Inside dat_set_cmma_bits() in arch/s390/kvm/gmap/dat.c, dat_entry_walk() is
called under read_lock(&kvm->mmu_lock) with DAT_WALK_ALLOC and without
DAT_WALK_SPLIT:

arch/s390/kvm/gmap/dat.c:dat_set_cmma_bits() {
    ...
	for (cur = ALIGN_DOWN(gfn, _PAGE_ENTRIES); cur < gfn + count; cur += _PAGE_ENTRIES) {
		rc = dat_entry_walk(mc, cur, asce, DAT_WALK_ALLOC, TABLE_TYPE_PAGE_TABLE,
				    &crstep, &ptep);
		if (rc)
			return rc;
	}
    ...
}

If any 1M or 2G large page mappings already exist in the target GFN range
when KVM_S390_SET_CMMA_BITS is first invoked, dat_set_cmma_bits() will hit
the unsplit large page entry in dat_entry_walk() and fail with -EFBIG before
gmap_allocate_all_page_tables_best_effort() has had a chance to split it.

In addition, because the !uses_cmm(kvm->arch.gmap) block runs unconditionally
after the loop even when r != 0, any error from dat_set_cmma_bits() (such as
-EFBIG or PGM_ADDRESSING on an invalid GFN range) will still set
GMAP_FLAG_USES_CMM and allocate page tables across all memslots before
returning the error.

Should the !uses_cmm(kvm->arch.gmap) check and
gmap_allocate_all_page_tables_best_effort() happen before the
dat_set_cmma_bits() loop (or be gated on success if placed after)?

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

      reply	other threads:[~2026-10-28 19:28 UTC|newest]

Thread overview: 15+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-10-28 18:54 [PATCH v4 0/7] KVM: s390: Misc bugfixes for 7.4 Claudio Imbrenda
2026-10-28 18:54 ` [PATCH v4 1/7] KVM: s390: Fix UCONTROL inconsistencies Claudio Imbrenda
2026-10-28 19:15   ` sashiko-bot
2026-10-28 18:54 ` [PATCH v4 2/7] KVM: s390: Move some skey functions to headers Claudio Imbrenda
2026-10-28 18:57   ` sashiko-bot
2026-10-28 18:54 ` [PATCH v4 3/7] KVM: s390: Fix idte_crste() clobber and table type Claudio Imbrenda
2026-10-28 19:00   ` sashiko-bot
2026-10-28 18:54 ` [PATCH v4 4/7] KVM: s390: Improve unmapping of large ranges Claudio Imbrenda
2026-10-28 19:10   ` sashiko-bot
2026-10-28 18:54 ` [PATCH v4 5/7] KVM: s390: vsie: Fix pinning using the wrong flag Claudio Imbrenda
2026-10-28 18:58   ` sashiko-bot
2026-10-28 18:54 ` [PATCH v4 6/7] KVM: s390: Introduce gmap_allocate_all_page_tables_best_effort() Claudio Imbrenda
2026-10-28 19:10   ` sashiko-bot
2026-10-28 18:54 ` [PATCH v4 7/7] KVM: s390: Allocate page tables when CMMA is used Claudio Imbrenda
2026-10-28 19:28   ` sashiko-bot [this message]

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=sashiko-outbox-188352@kernel.org \
    --to=sashiko-bot@kernel.org \
    --cc=agordeev@linux.ibm.com \
    --cc=borntraeger@linux.ibm.com \
    --cc=gor@linux.ibm.com \
    --cc=hca@linux.ibm.com \
    --cc=imbrenda@linux.ibm.com \
    --cc=kvm@vger.kernel.org \
    --cc=linux-s390@vger.kernel.org \
    --cc=sashiko-reviews@lists.linux.dev \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox