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: 25+ 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-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-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-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 a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox