Kernel KVM virtualization development
 help / color / mirror / Atom feed
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 08/11] KVM: arm64: CCA: Add Realm RTT mapping and invalidation helpers
Date: Mon, 02 Nov 2026 15:37:34 +0000	[thread overview]
Message-ID: <sashiko-outbox-193686@kernel.org> (raw)
In-Reply-To: <20261102141304.126883-9-steven.price@arm.com>

Thank you for your contribution! Sashiko AI review found 3 potential issue(s) to consider:
- [High] KVM: arm64: uninitialized next_addr in realm_unmap_private_range()
- [High] KVM: arm64: broken address alignment and recursion in realm_fold_rtt_level()
- [Medium] KVM: arm64: page accounting after free_page() in free_rtt()
--

commit cc0b24728b73f60b1dd39eb4cc60fe5eb12322c6
Author: Steven Price <steven.price@arm.com>

KVM: arm64: CCA: Add Realm RTT mapping and invalidation helpers

This commit adds Realm Management Interface (RMI) Realm Translation Table
(RTT) creation, folding, and unmap helpers needed for CCA Realm stage-2
memory operations.

> diff --git a/arch/arm64/kvm/rmi.c b/arch/arm64/kvm/rmi.c
> index f190143b33ba5..1066f767f85d9 100644
> --- a/arch/arm64/kvm/rmi.c
> +++ b/arch/arm64/kvm/rmi.c
> @@ -34,6 +34,83 @@ static int get_start_level(struct realm *realm)

[ ... ]

> +static phys_addr_t alloc_delegated_granule(struct kvm_mmu_memory_cache *mc)
> +{
> +	phys_addr_t phys;
> +	void *virt;
> +
> +	if (mc) {
> +		virt = kvm_mmu_memory_cache_alloc(mc);
> +	} else {
> +		virt = (void *)__get_free_page(GFP_ATOMIC | __GFP_ZERO |
> +					       __GFP_ACCOUNT);
> +	}
> +
> +	if (!virt)
> +		return PHYS_ADDR_MAX;
> +
> +	phys = virt_to_phys(virt);
> +	if (rmi_delegate_page(phys)) {
> +		free_page((unsigned long)virt);
> +		return PHYS_ADDR_MAX;
> +	}
> +
> +	return phys;
> +}
> +
> +static phys_addr_t alloc_rtt(struct kvm_mmu_memory_cache *mc)
> +{
> +	phys_addr_t phys = alloc_delegated_granule(mc);
> +
> +	if (phys != PHYS_ADDR_MAX)
> +		kvm_account_pgtable_pages(phys_to_virt(phys), 1);
> +
> +	return phys;
> +}
> +
>  static void free_rtt(phys_addr_t phys)
>  {
>  	if (rmi_free_delegated_page(phys))
> @@ -42,6 +119,32 @@ static void free_rtt(phys_addr_t phys)
>  	kvm_account_pgtable_pages(phys_to_virt(phys), -1);
>  }

[Severity: Medium]
Does free_rtt() leak the NR_SECONDARY_PAGETABLE counter in the memory cgroup
lruvec (or touch a reallocated page) by calling kvm_account_pgtable_pages()
after rmi_free_delegated_page()?

In alloc_rtt(), RTT pages are allocated via alloc_delegated_granule() with
__GFP_ACCOUNT and accounted with kvm_account_pgtable_pages(..., 1). However,
when free_rtt() frees an RTT, rmi_free_delegated_page() already calls
free_page() before returning:

drivers/firmware/arm_rmm/rmi.c:rmi_free_delegated_page() {
	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 free_page() uncharges the page and clears page->memcg_data (and
returns the page to the allocator where another CPU can immediately
reallocate it), the subsequent kvm_account_pgtable_pages(phys_to_virt(phys),
-1) in free_rtt() operates on a freed page whose memcg association has been
cleared.

[ ... ]

> @@ -161,6 +301,64 @@ static int realm_tear_down_rtt_range(struct realm *realm,
>  					 start, end);
>  }
>  
> +/*
> + * Returns 0 on successful fold, a negative value on error, a positive value if
> + * we were not able to fold all tables at this level.
> + */
> +static int realm_fold_rtt_level(struct realm *realm, int level,
> +				unsigned long start, unsigned long end)
> +{
> +	int not_folded = 0;
> +	ssize_t map_size;
> +	unsigned long addr, next_addr;
> +
> +	if (WARN_ON(level > KVM_PGTABLE_LAST_LEVEL))
> +		return -EINVAL;
> +
> +	map_size = rmi_rtt_level_mapsize(level - 1);
> +
> +	for (addr = start; addr < end; addr = next_addr) {
> +		phys_addr_t rtt_granule;
> +		long ret;
> +		unsigned long align_addr = ALIGN(addr, map_size);
> +
> +		next_addr = ALIGN(addr + 1, map_size);
> +
> +		ret = realm_rtt_fold(realm, align_addr, level, &rtt_granule);
> +		if (ret < 0)
> +			return ret;
> +
> +		switch (RMI_RESULT_STATUS(ret)) {
> +		case RMI_SUCCESS:\
> +			free_rtt(rtt_granule);
> +			break;
> +		case RMI_ERROR_RTT:
> +			if (level == KVM_PGTABLE_LAST_LEVEL ||
> +			    RMI_RESULT_DATA_LEVEL(ret) < level) {
> +				not_folded++;
> +				break;
> +			}
> +			/* Recurse a level deeper */
> +			ret = realm_fold_rtt_level(realm,
> +						   level + 1,
> +						   addr,
> +						   next_addr);
> +			if (ret < 0) {
> +				return ret;
> +			} else if (ret == 0) {
> +				/* Try again at this level */
> +				next_addr = addr;
> +			}
> +			break;
> +		default:
> +			WARN_ON(1);
> +			return -ENXIO;
> +		}
> +	}
> +
> +	return not_folded;
> +}

[Severity: High]
In realm_fold_rtt_level(), align_addr is computed using ALIGN(addr, map_size)
before calling realm_rtt_fold(realm, align_addr, level, &rtt_granule).

When addr is not aligned to map_size, ALIGN(addr, map_size) rounds addr up
to the next RTT boundary (equal to next_addr), defeating the ALIGN_DOWN()
inside realm_rtt_fold(). As a result, realm_rtt_fold() folds the next RTT
instead of the RTT covering addr, and if addr lies in the final map_size
block below BIT(realm->ia_bits), align_addr rounds up to BIT(realm->ia_bits),
which is out of bounds for rmi_rtt_fold() and triggers the default WARN_ON(1)
path.

Additionally, in the RMI_ERROR_RTT case:
1. The recursive call passes next_addr instead of min(next_addr, end),
   scanning up to a full parent map_size beyond end while holding
   kvm->mmu_lock for write.
2. When the recursive call returns a positive value (some child tables were
   not folded), not_folded is not incremented at the current level. If this
   level returns 0 to its caller, the caller sees ret == 0, sets
   next_addr = addr to retry, and can loop indefinitely.
3. Conversely, when recursing to level + 1 across [addr, next_addr), any
   entry at level that is already a leaf entry (such as UNASSIGNED) has no
   table at level + 1, so rmi_rtt_fold() returns RMI_ERROR_RTT with
   RMI_RESULT_DATA_LEVEL(ret) < level + 1 and increments not_folded,
   preventing the parent table at level from being retried and folded even if
   all of its child RTTs were folded. And if ret == 0 is returned when the
   table at level still cannot be folded (because entries in [addr, next_addr)
   are non-homogeneous or align_addr targeted the next block), setting
   next_addr = addr will retry and recurse on the same range in an infinite
   loop.

Should realm_fold_rtt_level() pass addr directly to realm_rtt_fold() (which
already aligns down to map_size), clamp the recursive end to
min(next_addr, end), and avoid retrying in a loop when the parent table
itself cannot be folded?

> @@ -196,6 +394,154 @@ int kvm_realm_teardown_stage2(struct kvm *kvm)

[ ... ]

> +static void realm_unmap_private_range(struct kvm *kvm,
> +				      unsigned long start,
> +				      unsigned long end,
> +				      bool may_block)
> +{
> +	struct realm *realm = &kvm->arch.realm;
> +	unsigned long rd = virt_to_phys(realm->rd);
> +	unsigned long next_addr, addr;
> +	long ret;
> +
> +	for (addr = start; addr < end; addr = next_addr) {
> +		unsigned long out_range;
> +		unsigned long flags = RMI_ADDR_TYPE_SINGLE;
> +		/* TODO: Optimise using RMI_ADDR_TYPE_LIST */
> +
> +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 */
> +
> +			rtt = alloc_rtt(NULL);
> +			if (WARN_ON(rtt == PHYS_ADDR_MAX))
> +				return;
> +			ret = realm_rtt_create(realm, addr,
> +					       RMI_RESULT_DATA_LEVEL(ret) + 1, rtt);
> +			if (WARN_ON(ret)) {
> +				free_rtt(rtt);
> +				return;
> +			}
> +			goto retry;
> +		} else if (WARN_ON(ret)) {
> +			continue;
> +		}

[Severity: High]
What happens in realm_unmap_private_range() when rmi_rtt_data_unmap() returns
RMI_ERROR_RTT or another non-zero status?

Looking at rmi_rtt_data_unmap() in include/linux/arm-rmi-cmds.h:

	ret = rmi_sro_execute(&regs);

	if (ret != RMI_SUCCESS)
		return ret;

	if (out_top)
		*out_top = regs.a1;

*out_top (&next_addr) is only written when ret == RMI_SUCCESS.

When rmi_rtt_data_unmap() returns RMI_ERROR_RTT on the first iteration
(addr == start), next_addr is uninitialized on the stack when evaluated by:

	if (next_addr > addr)
		continue; /* UNASSIGNED */

If the uninitialized stack value happens to be greater than start, the loop
advances addr to that garbage address and skips unmapping the range.

If next_addr <= addr on the first iteration (or on any subsequent iteration
where addr == next_addr from the loop header), next_addr > addr is false even
when the entry is UNASSIGNED. The code then allocates RTTs and calls
realm_rtt_create(realm, addr, RMI_RESULT_DATA_LEVEL(ret) + 1, rtt) in a retry
loop until RMI_RESULT_DATA_LEVEL(ret) reaches KVM_PGTABLE_LAST_LEVEL (3),
where it attempts to create an RTT at level 4, hits WARN_ON(ret), and returns
early without unmapping the remaining range.

Similarly, in the else if (WARN_ON(ret)) continue; branch, next_addr was not
updated by rmi_rtt_data_unmap(), so continuing either jumps to uninitialized
stack memory on the first iteration or repeats the same addr in an infinite
loop while holding kvm->mmu_lock for write.

Should this handle RMI_ERROR_RTT in the same way as
realm_unmap_shared_range(), using find_map_level(realm, addr, end) and
advancing next_addr by rmi_rtt_level_mapsize(err_level) when err_level >=
level?

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20261102141304.126883-1-steven.price@arm.com?part=8

  reply	other threads:[~2026-11-02 15:37 UTC|newest]

Thread overview: 24+ 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 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 [this message]
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

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-193686@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