[PATCH v3] KVM: arm64: Fix protected VM fault on system with pages larger than 4K

Fuad Tabba fuad.tabba at linux.dev
Tue Sep 15 04:17:21 PDT 2026


Hi Vincent,

On Tue, 15 Sept 2026 at 10:16, 'Vincent Donnefort' via kernel-team
<kernel-team at android.com> wrote:
>
> Just like commit 08f97454b7fa ("KVM: arm64: Fix protected mode handling
> of pages larger than 4kB") fixed the boot of non-protected VMs on system
> larger than 4K pages, align the fault IPA down to the page-size for
> protected VMs.
>
> To paraphrase Marc, pkvm_pgtable_stage2_map() assumes the address passed
> as a parameter is aligned to the size of the intended mapping, while
> HPFAR_EL2 gives the IPA minus the bottom 12 bits, regardless of the
> system page size configuration.
>
> Add a check at the start of pkvm_pgtable_stage2_map() as we do not
> support !PAGE_ALIGNED arguments and ensure callers pass a page-aligned
> IPA.

The fix is correct, and I can reproduce the bug. On a 16K-page host an
unpatched v7.3-rc2 never gets a pVM to a prompt: no guest console
output at all, a core pegged at 100% when I sampled it a minute in,
killed at the 120s timeout. A guest_memfd-backed non-protected VM
times out the same way, while one without guest_memfd boots fine,
which puts the failure on the two paths you fix. With the patch all
three boot clean, and 4K still boots both. That is QEMU with kvmtool
guests; the same three 16K legs also pass on an M4 running pKVM at EL2
on the silicon.

Tested-by: Fuad Tabba <fuad.tabba at linux.dev>

> Fixes: ea03466e806f ("KVM: arm64: Handle aborts from protected VMs")

A second Fixes: for the gmem_abort() half? a7b57e099592 ("KVM: arm64:
Handle guest_memfd-backed guest page faults") added that call site,
and the ranges differ: ea03466e806f is in v7.1, a7b57e099592 in v6.18.

Should this carry Cc: stable at vger.kernel.org? 08f97454b7fa, the fix
this one follows, did, and 16K-page hosts are a shipping Android
configuration.

> Signed-off-by: Vincent Donnefort <vdonnefort at google.com>
> ---
>  arch/arm64/kvm/mmu.c  | 12 +++++++-----
>  arch/arm64/kvm/pkvm.c |  3 +++
>  2 files changed, 10 insertions(+), 5 deletions(-)
>
> Changelog:
>
> v3:
>   - Fix nested case in gmem_abort() (Sashiko)
>
> v2: https://lore.kernel.org/all/20260914075839.4019904-1-vdonnefort@google.com/
>
>   - Use gfn_to_gpa(gfn)
>   - Drop "phys" from the PAGE_ALIGNED check, it isn't a requirement.
>   - Fix gmem_abort() as well (Sashiko)
>
> v1: https://lore.kernel.org/all/20260913173516.3122436-1-vdonnefort@google.com/
>
> diff --git a/arch/arm64/kvm/mmu.c b/arch/arm64/kvm/mmu.c
> index 9ba86450fe4a..e199dd339583 100644
> --- a/arch/arm64/kvm/mmu.c
> +++ b/arch/arm64/kvm/mmu.c
> @@ -1610,6 +1610,7 @@ static int gmem_abort(const struct kvm_s2_fault_desc *s2fd)
>         bool write_fault, exec_fault;
>         bool perm_fault = kvm_vcpu_trap_is_permission_fault(s2fd->vcpu);
>         enum kvm_pgtable_walk_flags flags = KVM_PGTABLE_WALK_SHARED;
> +       phys_addr_t ipa = ALIGN_DOWN(s2fd->fault_ipa, PAGE_SIZE);
>         enum kvm_pgtable_prot prot = KVM_PGTABLE_PROT_R;
>         struct kvm_pgtable *pgt = s2fd->vcpu->arch.hw_mmu->pgt;
>         unsigned long mmu_seq;

One more in gmem_abort(): the memory fault exit at mmu.c:1647 still
reports the unaligned address, and that one is userspace-visible.

    kvm_prepare_memory_fault_exit(s2fd->vcpu, s2fd->fault_ipa, PAGE_SIZE,
         write_fault, exec_fault, false);

api.rst defines the range as [gpa, gpa + size), so on a 16K host it
starts mid-page. gfn_to_gpa(gfn) is the one to use: gfn is what
kvm_gmem_get_pfn() failed on, and the L1 IPA in the nested case. x86
passes fault->gfn << PAGE_SHIFT.

> @@ -1672,12 +1673,12 @@ static int gmem_abort(const struct kvm_s2_fault_desc *s2fd)
>                  * PTE, which will be preserved.
>                  */
>                 prot &= ~KVM_NV_GUEST_MAP_SZ;
> -               ret = KVM_PGT_FN(kvm_pgtable_stage2_relax_perms)(pgt, s2fd->fault_ipa,
> +               ret = KVM_PGT_FN(kvm_pgtable_stage2_relax_perms)(pgt, ipa,
>                                                                  prot, flags);
>         } else {
> -               ret = KVM_PGT_FN(kvm_pgtable_stage2_map)(pgt, s2fd->fault_ipa, PAGE_SIZE,
> -                                                        __pfn_to_phys(pfn), prot,
> -                                                        memcache, flags);
> +               ret = KVM_PGT_FN(kvm_pgtable_stage2_map)(pgt, ipa, PAGE_SIZE,
> +                                                        __pfn_to_phys(pfn),
> +                                                        prot, memcache, flags);
>         }
>
>  out_unlock:
> @@ -1710,6 +1711,7 @@ static int pkvm_mem_abort(const struct kvm_s2_fault_desc *s2fd)
>         unsigned int flags = FOLL_HWPOISON | FOLL_LONGTERM | FOLL_WRITE;
>         struct kvm_vcpu *vcpu = s2fd->vcpu;
>         struct kvm_pgtable *pgt = vcpu->arch.hw_mmu->pgt;
> +       gfn_t gfn = gpa_to_gfn(s2fd->fault_ipa);
>         struct mm_struct *mm = current->mm;
>         struct kvm *kvm = vcpu->kvm;
>         void *hyp_memcache;
> @@ -1756,7 +1758,7 @@ static int pkvm_mem_abort(const struct kvm_s2_fault_desc *s2fd)
>         }
>
>         write_lock(&kvm->mmu_lock);
> -       ret = pkvm_pgtable_stage2_map(pgt, s2fd->fault_ipa, PAGE_SIZE,
> +       ret = pkvm_pgtable_stage2_map(pgt, gfn_to_gpa(gfn), PAGE_SIZE,
>                                       page_to_phys(page), KVM_PGTABLE_PROT_RWX,
>                                       hyp_memcache, 0);
>         write_unlock(&kvm->mmu_lock);
> diff --git a/arch/arm64/kvm/pkvm.c b/arch/arm64/kvm/pkvm.c
> index 8e4c6e4bec12..b7340c430ed6 100644
> --- a/arch/arm64/kvm/pkvm.c
> +++ b/arch/arm64/kvm/pkvm.c
> @@ -414,6 +414,9 @@ int pkvm_pgtable_stage2_map(struct kvm_pgtable *pgt, u64 addr, u64 size,
>         u64 end = addr + size;
>         int ret;
>
> +       if (!PAGE_ALIGNED(addr | size))
> +               return -EINVAL;
> +

Could this be if (WARN_ON_ONCE(!PAGE_ALIGNED(addr | size)))? The three
checks just below WARN on the same class of caller bug, and this one
runs first, so a bad size now returns -EINVAL with no splat.

With the memory fault exit fixed:
Reviewed-by: Fuad Tabba <fuad.tabba at linux.dev>

Cheers,
/fuad

>         lockdep_assert_held_write(&kvm->mmu_lock);
>         mapping = pkvm_mapping_iter_first(&pgt->pkvm_mappings, addr, end - 1);
>
>
> base-commit: df2908090cda368b01ff43709f51890076c56157
> --
> 2.55.0.1032.g73a4cd73de-goog
>
> To unsubscribe from this group and stop receiving emails from it, send an email to kernel-team+unsubscribe at android.com.
>



More information about the linux-arm-kernel mailing list