Linux KVM/arm64 development list
 help / color / mirror / Atom feed
From: sashiko-bot@kernel.org
To: "Marc Zyngier" <maz@kernel.org>
Cc: "Marc Zyngier" <maz@kernel.org>,
	"Oliver Upton" <oupton@kernel.org>,
	kvmarm@lists.linux.dev
Subject: Re: [PATCH v3 2/2] KVM: arm64: nv: Delay freeing of shadow S2 structures until VM destruction
Date: Fri, 21 Aug 2026 16:45:17 +0000	[thread overview]
Message-ID: <20260821164517.BF34A1F00A3A@smtp.kernel.org> (raw)
In-Reply-To: <20260821161829.1032561-3-maz@kernel.org>

Thank you for your contribution! Sashiko AI review found 3 potential issue(s) to consider:

New issues:
- [High] Preserving nested MMU containers with freed page tables leads to NULL pointer dereference.

Pre-existing issues:
- [High] Permanent memory leak of nested stage-2 page tables and per-CPU state on VM destruction.
- [High] Data race between lockless array iteration and array population causes uninitialized pointer dereference.
--

--- Patch [2]: [PATCH v3 2/2] KVM: arm64: nv: Delay freeing of shadow S2 structures until VM destruction ---
commit 46c2225152ae78e4009a13ad7c3bcc3e69fbab04
Author: Marc Zyngier <maz@kernel.org>

KVM: arm64: nv: Delay freeing of shadow S2 structures until VM destruction

We free the shadow S2 structures from kvm_arch_flush_shadow_all(), which
is a Bad Idea(tm). Freeing the page tables is fair game (this is what
this callback is for), but freeing the container that could still be
referenced by another part of the system is not great.

Instead, grow separate destructors that gets called when we tear the VM
down for good. From there, we can nuke both the individual MMUs as well
as the global array that points to them, safe in the knowledge that the
vcpus themselves have been destroyed already.

> diff --git a/arch/arm64/kvm/nested.c b/arch/arm64/kvm/nested.c
> index 9070d5bc5af03..bc2b58e583b52 100644
> --- a/arch/arm64/kvm/nested.c
> +++ b/arch/arm64/kvm/nested.c

[ ... ]

> @@ -56,6 +56,15 @@ int kvm_init_nested(struct kvm *kvm)
>  	return kvm->arch.nested_mmus ? 0 : -ENOMEM;
>  }
>  
> +void kvm_destroy_nested(struct kvm *kvm)
> +{
> +	for (int i = 0; i < kvm->arch.nested_mmus_size; i+= S2_MMU_PER_VCPU)
> +		kvfree(kvm->arch.nested_mmus[i]);

[Severity: High]
This is a pre-existing issue, but does this loop permanently leak the nested
stage-2 page tables? The loop blindly calls kvfree() without first calling
kvm_free_stage2_pgd() to recursively free the page tables and per-CPU
allocations.

If a user creates a KVM VM, runs nested vCPUs to allocate stage-2 page tables,
and then closes the KVM file descriptor, the KVM VM teardown calls
kvm_destroy_nested(). Because it frees the container structures but forgets to
call kvm_free_stage2_pgd(), the pgt and last_vcpu_ran allocations are
permanently leaked.

> +
> +	kvm->arch.nested_mmus_size = 0;
> +	kvfree(kvm->arch.nested_mmus);
> +}
> +
>  static int init_nested_s2_mmu(struct kvm *kvm, struct kvm_s2_mmu *mmu)
>  {

[ ... ]

> @@ -1311,16 +1320,12 @@ void kvm_nested_s2_flush(struct kvm *kvm)
>  
>  void kvm_arch_flush_shadow_all(struct kvm *kvm)
>  {
> -	for (int i = kvm->arch.nested_mmus_size - 1; i >= 0; i--) {
> +	for (int i = 0; i < kvm->arch.nested_mmus_size; i++) {
>  		struct kvm_s2_mmu *mmu = kvm->arch.nested_mmus[i];
>  
>  		if (!WARN_ON(atomic_read(&mmu->refcnt)))

[Severity: High]
This isn't a bug introduced by this patch, but can this lockless array
iteration race with array population and cause an uninitialized pointer
dereference?

While one thread executes KVM_ARM_VCPU_INIT and appends newly allocated MMU
pointers to the nested_mmus array under mmu_lock in kvm_vcpu_init_nested():

	for (i = 0; i < S2_MMU_PER_VCPU; i++)
		kvm->arch.nested_mmus[i + kvm->arch.nested_mmus_size] = &tmp[i];

	kvm->arch.nested_mmus_size += S2_MMU_PER_VCPU;

Another process context (like OOM killer or thread exit) can trigger
mmu_notifier_release -> kvm_arch_flush_shadow_all().

kvm_arch_flush_shadow_all() iterates over nested_mmus reading
nested_mmus_size without mmu_lock or memory barriers. Because kvmalloc_objs
does not zero memory, the lockless reader can observe an incremented size but
a garbage pointer, causing a crash on atomic_read(&mmu->refcnt).

>  			kvm_free_stage2_pgd(mmu);

[Severity: High]
Since this patch now preserves the nested MMU containers, does this lead to a
NULL pointer dereference?

The array elements are kept, but kvm_free_stage2_pgd(mmu) frees their page
tables and sets mmu->pgt to NULL. If an application shares a KVM VM file
descriptor across processes and the process holding the mm_struct exits, it
fires mmu_notifier_release -> kvm_arch_flush_shadow_all().

If the surviving process calls KVM_RUN, get_s2_mmu_nested() can reuse the
container since its refcnt is 0:

	s2_mmu = kvm->arch.nested_mmus[i % kvm->arch.nested_mmus_size];

	if (atomic_read(&s2_mmu->refcnt) == 0)
		break;

However, because there is no logic to reallocate the missing page table tree,
a subsequent stage-2 fault will dereference mmu->pgt, panicking the kernel.

> -
> -		if ((i % S2_MMU_PER_VCPU) == 0)
> -			kvfree(mmu);
>  	}
> -	kvm->arch.nested_mmus_size = 0;
>  	kvm_uninit_stage2_mmu(kvm);
>  }

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20260821161829.1032561-1-maz@kernel.org?part=2

  reply	other threads:[~2026-08-21 16:45 UTC|newest]

Thread overview: 12+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-08-21 16:18 [PATCH v3 0/2] KVM: arm64: nv: Shadow S2 life-cycle fixes Marc Zyngier
2026-08-21 16:18 ` [PATCH v3 1/2] KVM: arm64: nv: Fix life cycle of the nested_mmus array Marc Zyngier
2026-08-21 16:48   ` sashiko-bot
2026-08-21 16:18 ` [PATCH v3 2/2] KVM: arm64: nv: Delay freeing of shadow S2 structures until VM destruction Marc Zyngier
2026-08-21 16:45   ` sashiko-bot [this message]
2026-08-21 18:15   ` Lorenzo Stoakes (ARM)
2026-08-22  8:12     ` Marc Zyngier
2026-08-24 11:18       ` Lorenzo Stoakes (ARM)
2026-08-23 14:11   ` Wei-Lin Chang
2026-08-24 11:07     ` Lorenzo Stoakes (ARM)
2026-08-24 11:09       ` Lorenzo Stoakes (ARM)
2026-08-23 14:07 ` [PATCH v3 0/2] KVM: arm64: nv: Shadow S2 life-cycle fixes Wei-Lin Chang

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=20260821164517.BF34A1F00A3A@smtp.kernel.org \
    --to=sashiko-bot@kernel.org \
    --cc=kvmarm@lists.linux.dev \
    --cc=maz@kernel.org \
    --cc=oupton@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