From: sashiko-bot@kernel.org
To: "Steven Price" <steven.price@arm.com>,
kvm@vger.kernel.org, kvmarm@lists.linux.dev
Cc: "Oliver Upton" <oupton@kernel.org>, "Marc Zyngier" <maz@kernel.org>
Subject: Re: [PATCH 11/11] KVM: arm64: CCA: Add Realm stage-2 MMU operations
Date: Mon, 02 Nov 2026 17:12:48 +0000 [thread overview]
Message-ID: <sashiko-outbox-193812@kernel.org> (raw)
In-Reply-To: <20261102141304.126883-12-steven.price@arm.com>
Thank you for your contribution! Sashiko AI review found 7 potential issue(s) to consider:
- [Critical] KVM: arm64: read_lock() used instead of write_lock() in kvm_fault_lock()
- [Critical] KVM: arm64: unaligned PFN and empty memcache on Realm permission faults
- [High] KVM: arm64: uninitialized next_addr in realm_unmap_private_range()
- [High] KVM: arm64: page accounting after page free in free_rtt()
- [High] KVM: arm64: stage-2 PGD leak in kvm_uninit_stage2_mmu() for Realms
- [High] KVM: arm64: leaked delegated pages on error in realm_map_protected()
- [High] KVM: arm64: uninitialized out_top and positive error in realm_map_non_secure()
--
commit c7041c45fed830c50c8f37ff21d6036da29c1302
Author: Suzuki K Poulose <suzuki.poulose@arm.com>
KVM: arm64: CCA: Add Realm stage-2 MMU operations
This commit defines and registers realm_vm_s2_ops for VM_REALM, routing
stage-2 unmapping through kvm_realm_unmap_range_filter() and stage-2 memory
aborts through kvm_vm_mem_abort(). It also provides no-op TLB flush and GFN
aging callbacks since the RMM manages stage-2 TLB invalidation and does not
support stage-2 access flags.
> diff --git a/arch/arm64/kvm/mmu.c b/arch/arm64/kvm/mmu.c
> index 20aedbe6ec072..bb33c07569e3b 100644
> --- a/arch/arm64/kvm/mmu.c
> +++ b/arch/arm64/kvm/mmu.c
[ ... ]
> @@ -353,6 +366,15 @@ static void no_stage2_unmap_range(struct kvm_s2_mmu *mmu,
> {
> }
>
> +static void realm_stage2_unmap_range(struct kvm_s2_mmu *mmu,
> + phys_addr_t start, u64 size, bool may_block,
> + enum kvm_gfn_range_filter filter)
> +{
> + struct kvm *kvm = kvm_s2_mmu_to_kvm(mmu);
> +
> + kvm_realm_unmap_range_filter(kvm, start, size, may_block, filter);
> +}
[Severity: High]
When realm_stage2_unmap_range() calls
kvm_realm_unmap_range_filter()->realm_unmap_private_range(), can next_addr be
read uninitialized or left stale when rmi_rtt_data_unmap() returns a non-zero
status?
In arch/arm64/kvm/rmi.c, realm_unmap_private_range() loops with next_addr:
unsigned long next_addr, addr;
long ret;
for (addr = start; addr < end; addr = next_addr) {
...
retry:
ret = rmi_rtt_data_unmap(rd, addr, end, flags, 0,
&next_addr, &out_range, NULL);
if (WARN_ON(ret < 0))
return;
if (RMI_RESULT_STATUS(ret) == RMI_ERROR_RTT) {
phys_addr_t rtt;
if (next_addr > addr)
continue; /* UNASSIGNED */
...
} else if (WARN_ON(ret)) {
continue;
}
However, in include/linux/arm-rmi-cmds.h, rmi_rtt_data_unmap() returns early
before writing *out_top when ret != RMI_SUCCESS:
ret = rmi_sro_execute(®s);
if (ret != RMI_SUCCESS)
return ret;
if (out_top)
*out_top = regs.a1;
If rmi_rtt_data_unmap() returns RMI_ERROR_RTT on an unpopulated private IPA
range, next_addr is either uninitialized on the first iteration or equal to
addr from the previous iteration. As a result, next_addr > addr is not
reliably true for unassigned entries, causing realm_unmap_private_range() to
allocate and create RTT levels down to KVM_PGTABLE_LAST_LEVEL + 1 where
realm_rtt_create() triggers WARN_ON(ret) and aborts the unmap early (or jumps
to a garbage address if stack contents happen to be greater than addr).
[Severity: High]
In the same unmap path, when realm_unmap_shared_range() or
realm_unmap_private_range() folds RTT levels via
realm_fold_rtt_level()->free_rtt(), does free_rtt() access the page after it
has already been freed?
In arch/arm64/kvm/rmi.c:
static void free_rtt(phys_addr_t phys)
{
if (rmi_free_delegated_page(phys))
return;
kvm_account_pgtable_pages(phys_to_virt(phys), -1);
}
And in drivers/firmware/arm_rmm/rmi.c:
int rmi_free_delegated_page(phys_addr_t phys)
{
if (WARN_ON_ONCE(rmi_undelegate_page(phys))) {
/* Undelegate failed: leak the page */
return -EBUSY;
}
free_page((unsigned long)phys_to_virt(phys));
return 0;
}
Because rmi_free_delegated_page() calls free_page() first (which uncharges the
folio and clears its memcg association before returning the page to the
allocator), calling kvm_account_pgtable_pages() afterwards touches a freed
page and fails to decrement NR_SECONDARY_PAGETABLE on the original memcg.
Should kvm_account_pgtable_pages(phys_to_virt(phys), -1) be called before
free_page() (or before rmi_free_delegated_page())?
[ ... ]
> @@ -3291,6 +3313,15 @@ static const struct kvm_vm_s2_ops kvm_default_vm_s2_ops = {
> .vm_mem_abort = kvm_vm_mem_abort,
> };
>
> +static const struct kvm_vm_s2_ops realm_vm_s2_ops = {
> + .vm_flush_remote_tlbs = realm_flush_remote_tlbs,
> + .vm_flush_remote_tlbs_range = realm_flush_remote_tlbs_range,
> + .vm_age_gfn = no_age_gfn,
> + .vm_test_age_gfn = no_age_gfn,
> + .vm_stage2_unmap_range = realm_stage2_unmap_range,
> + .vm_mem_abort = kvm_vm_mem_abort,
[Severity: Critical]
When kvm_vm_mem_abort() handles stage-2 faults for a Realm VM via
gmem_abort() or user_mem_abort()->kvm_s2_fault_map(), it acquires
kvm->mmu_lock using kvm_fault_lock(kvm). Does kvm_fault_lock() take a read
lock instead of a write lock for Realm VMs?
In arch/arm64/include/asm/kvm_mmu.h:
static inline void kvm_fault_lock(struct kvm *kvm)
{
if (is_protected_kvm_enabled())
write_lock(&kvm->mmu_lock);
else
read_lock(&kvm->mmu_lock);
}
is_protected_kvm_enabled() checks ARM64_KVM_PROTECTED_MODE, which is false in
KVM_MODE_RMM. However, struct realm in arch/arm64/include/asm/kvm_rmi.h notes
that RTT map/unmap SRO commands require kvm->mmu_lock to be held for write
during fault handling:
* Reused by RTT map/unmap SRO commands. Those commands are only
* issued from Realm stage-2 map/unmap paths while kvm->mmu_lock is
* held for write, including Realm fault handling where
* kvm_fault_lock() takes the write side, so concurrent use is
* serialized.
Furthermore, during fault handling, realm_map_non_secure() calls
realm_unmap_shared_range(kvm, ipa, ipa_top, true) and realm_map_protected()
calls realm_unmap_private_range(kvm, base_ipa, ipa, true) on error. Both pass
may_block = true and execute cond_resched_rwlock_write(&kvm->mmu_lock) while
only a read lock is held, which triggers lockdep_assert_held_write() and calls
write_unlock() followed by write_lock() on a read-locked rwlock, corrupting
the lock state and self-deadlocking on the caller's own reader count.
In addition, calling realm_unmap_shared_range() with may_block = true inside
realm_map_non_secure() can drop kvm->mmu_lock after mmu_invalidate_retry() has
already been checked in gmem_abort() or kvm_s2_fault_map(), allowing a
concurrent mmu_notifier invalidation to unmap and free the page before
rmi_rtt_unprot_map() installs the mapping. Should kvm_fault_lock() and
kvm_fault_unlock() also take the write lock when kvm_vm_is_realm(kvm) is true,
and should realm_map_non_secure() pass may_block = false to
realm_unmap_shared_range()?
[Severity: Critical]
In kvm_vm_mem_abort()->user_mem_abort()->kvm_s2_fault_map(), how are stage-2
permission faults on shared IPAs handled for Realm VMs?
First, in user_mem_abort():
memcache = get_mmu_memcache(s2fd->vcpu);
if (!perm_fault || memslot_is_logging(s2fd->memslot) ||
is_protected_kvm_enabled()) {
ret = topup_mmu_memcache(s2fd->mmu, memcache);
if (ret)
return ret;
}
Because is_protected_kvm_enabled() is false for Realm VMs, memcache is not
topped up on permission faults, even though realm_map_non_secure() unmaps and
folds the existing RTT entry via realm_unmap_shared_range() and then allocates
RTTs from memcache via realm_create_rtt_levels().
Second, in kvm_s2_fault_map():
if (mapping_size == PAGE_SIZE && s2vi->max_map_size != PAGE_SIZE) {
if (perm_fault_granule > PAGE_SIZE) {
mapping_size = perm_fault_granule;
} else {
mapping_size = transparent_hugepage_adjust(s2fd, &pfn, &gfn);
if (mapping_size < 0) {
ret = mapping_size;
goto out_unlock;
}
}
canonical_ipa = ALIGN_DOWN(canonical_ipa, mapping_size);
}
if (!perm_fault_granule && !s2vi->map_non_cacheable && kvm_has_mte(kvm))
sanitise_mte_tags(kvm, pfn, mapping_size);
if (kvm_vm_is_realm(kvm)) {
ret = realm_map_ipa(kvm, s2fd->fault_ipa, pfn, mapping_size,
prot, memcache);
When perm_fault_granule > PAGE_SIZE on a THP VMA (where s2vi->vma_pagesize is
PAGE_SIZE), kvm_s2_fault_map() sets mapping_size = perm_fault_granule without
calling transparent_hugepage_adjust() to verify that the host VMA is still
backed by a huge page (for example, if a write fault split the THP via CoW) or
to align pfn down to mapping_size. For normal VMs this is safe because
kvm_pgtable_stage2_relax_perms() does not use pfn, but for a Realm VM
realm_map_ipa() aligns fault_ipa down to mapping_size while mapping from the
unaligned 4KB pfn for mapping_size bytes. Can this map adjacent host physical
pages beyond the end of the folio (or from a split THP) into the Realm guest?
[Severity: High]
When kvm_vm_mem_abort() calls realm_map_ipa()->realm_map_protected(), can the
err_undelegate path fail to undelegate the remaining unmapped pages if an
error occurs after part of the range was already mapped?
In arch/arm64/kvm/rmi.c:
err_undelegate:
realm_unmap_private_range(kvm, base_ipa, ipa, true);
/* A range which cannot be undelegated remains owned by the RMM. */
WARN_ON(rmi_undelegate_range(base_phys, map_size));
return ret < 0 ? ret : -ENXIO;
If the loop mapped [base_ipa, ipa) before failing, realm_unmap_private_range()
unmaps [base_ipa, ipa) and undelegates [base_phys, phys). Calling
rmi_undelegate_range(base_phys, map_size) then starts at base_phys, which is
already undelegated, so rmi_granule_range_undelegate() fails on the first page
and aborts the loop, triggering the WARN_ON and leaving [phys,
base_phys + map_size) permanently delegated. Should this call
rmi_undelegate_range(phys, ipa_top - ipa) instead?
[Severity: High]
In kvm_vm_mem_abort()->...->realm_map_ipa()->realm_map_non_secure(), what
happens if rmi_rtt_unprot_map() returns a positive RMI error code?
In arch/arm64/kvm/rmi.c:
if (RMI_RESULT_STATUS(ret) == RMI_ERROR_RTT) {
...
ret = realm_create_rtt_levels(realm, ipa, level,
req_level,
memcache);
if (ret)
return ret;
ret = rmi_rtt_unprot_map(rd, ipa, ipa_top, flags,
range_desc, &out_top);
if (ret < 0)
return ret;
}
if (ret && RMI_RESULT_STATUS(ret) != RMI_ERROR_RTT)
return ret;
phys += out_top - ipa;
ipa = out_top;
If the retry of rmi_rtt_unprot_map() after realm_create_rtt_levels() returns
RMI_ERROR_RTT, out_top is never written by rmi_rtt_unprot_map(), and execution
falls through to phys += out_top - ipa; ipa = out_top; using an uninitialized
out_top. And if rmi_rtt_unprot_map() returns any other positive RMI status
code, realm_map_non_secure() returns that positive value up through
kvm_vm_mem_abort() to kvm_handle_guest_abort(), which treats positive return
values as a successfully handled fault and re-enters the guest.
> +};
> +
> #define KVM_VM_S2_OPS(flavor, ops) \
> [flavor] = &(ops)
>
> @@ -3299,7 +3330,7 @@ static const struct kvm_vm_s2_ops *arm64_vm_s2_ops[] = {
> KVM_VM_S2_OPS(VM_NVHE, kvm_default_vm_s2_ops),
> KVM_VM_S2_OPS(VM_PKVM, pkvm_vm_s2_ops),
> KVM_VM_S2_OPS(VM_PROTECTED_PKVM, protected_pkvm_vm_s2_ops),
> - [VM_REALM] = NULL,
> + KVM_VM_S2_OPS(VM_REALM, realm_vm_s2_ops),
[Severity: High]
Now that arm64_vm_s2_ops[VM_REALM] is populated, kvm_init_stage2_mmu()
succeeds for Realm VMs and allocates mmu->pgt, its PGD pages, and
mmu->last_vcpu_ran. Does kvm_uninit_stage2_mmu() leak those allocations if
kvm_arch_init_vm() fails at err_uninit_mmu or if the VM is destroyed before
the Realm is created?
In arch/arm64/kvm/mmu.c:
void kvm_uninit_stage2_mmu(struct kvm *kvm)
{
if (kvm_vm_is_realm(kvm))
kvm_realm_uninit_stage2(&kvm->arch.mmu);
else
kvm_free_stage2_pgd(&kvm->arch.mmu);
kvm_mmu_free_memory_cache(&kvm->arch.mmu.split_page_cache);
}
And in arch/arm64/kvm/rmi.c:
void kvm_destroy_realm(struct kvm *kvm)
{
struct realm *realm = &kvm->arch.realm;
int i;
if (!kvm_realm_is_created(kvm)) {
kfree(realm->sro);
realm->sro = NULL;
return;
}
...
kvm_free_stage2_pgd(&kvm->arch.mmu);
}
When realm->state is REALM_STATE_NONE, kvm_realm_uninit_stage2() is a no-op
and kvm_destroy_realm() returns early before calling
kvm_free_stage2_pgd(&kvm->arch.mmu) (or is not called at all on the
err_uninit_mmu failure path of kvm_arch_init_vm()), leaving mmu->pgt, the
stage-2 PGD pages, and mmu->last_vcpu_ran allocated.
--
Sashiko AI review · https://sashiko.dev/#/patchset/20261102141304.126883-1-steven.price@arm.com?part=11
prev parent reply other threads:[~2026-11-02 17:12 UTC|newest]
Thread overview: 28+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-11-02 14:12 [PATCH 00/11] KVM: arm64: CCA: Prepare Realm memory management Steven Price
2026-11-02 14:12 ` [PATCH 01/11] KVM: arm64: CCA: Reserve the RMM mode Steven Price
2026-11-02 14:12 ` [PATCH 02/11] KVM: arm64: CCA: Introduce Realms Steven Price
2026-11-02 14:30 ` sashiko-bot
2026-11-02 15:45 ` Steven Price
2026-11-03 11:34 ` Suzuki K Poulose
2026-11-02 14:12 ` [PATCH 03/11] KVM: arm64: CCA: Check for RMI support at KVM init Steven Price
2026-11-02 14:12 ` [PATCH 04/11] KVM: arm64: CCA: Check for LPA2 support and SHA-256 Steven Price
2026-11-02 14:38 ` sashiko-bot
2026-11-02 15:56 ` Steven Price
2026-11-03 11:47 ` Suzuki K Poulose
2026-11-02 14:12 ` [PATCH 05/11] KVM: arm64: Handle attributes in stage2_unmap_range Steven Price
2026-11-02 14:12 ` [PATCH 06/11] KVM: arm64: CCA: Add basic infrastructure for creating a realm Steven Price
2026-11-02 15:05 ` sashiko-bot
2026-11-02 16:24 ` Steven Price
2026-11-02 23:49 ` Suzuki K Poulose
2026-11-02 14:12 ` [PATCH 07/11] KVM: arm64: CCA: Tear down RTTs Steven Price
2026-11-02 15:18 ` sashiko-bot
2026-11-02 17:01 ` Steven Price
2026-11-03 15:20 ` Suzuki K Poulose
2026-11-02 14:12 ` [PATCH 08/11] KVM: arm64: CCA: Add Realm RTT mapping and invalidation helpers Steven Price
2026-11-02 15:37 ` sashiko-bot
2026-11-02 14:13 ` [PATCH 09/11] KVM: arm64: CCA: Prevent memslot modifications after Realm creation Steven Price
2026-11-02 15:50 ` sashiko-bot
2026-11-02 14:13 ` [PATCH 10/11] KVM: arm64: CCA: Support runtime faulting of memory Steven Price
2026-11-02 16:26 ` sashiko-bot
2026-11-02 14:13 ` [PATCH 11/11] KVM: arm64: CCA: Add Realm stage-2 MMU operations Steven Price
2026-11-02 17:12 ` 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-193812@kernel.org \
--to=sashiko-bot@kernel.org \
--cc=kvm@vger.kernel.org \
--cc=kvmarm@lists.linux.dev \
--cc=maz@kernel.org \
--cc=oupton@kernel.org \
--cc=sashiko-reviews@lists.linux.dev \
--cc=steven.price@arm.com \
/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 an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.