]> git.ipfire.org Git - thirdparty/kernel/linux.git/commitdiff
KVM: arm64: Add fail-safe for refcounted pages in __pkvm_hyp_donate_host
authorVincent Donnefort <vdonnefort@google.com>
Thu, 21 May 2026 14:36:26 +0000 (15:36 +0100)
committerMarc Zyngier <maz@kernel.org>
Tue, 26 May 2026 15:12:29 +0000 (16:12 +0100)
A previous bug in __pkvm_init_vm error path showed that the hypervisor
could leak refcounted pages, (i.e. losing access to a page while its
refcount is still elevated). This poses a threat to the pKVM state
machine.

Address this by introducing a fail-safe in __pkvm_hyp_donate_host.
Transitions are not a hot path so added security is worth the extra
check.

Reviewed-by: Fuad Tabba <tabba@google.com>
Tested-by: Fuad Tabba <tabba@google.com>
Signed-off-by: Vincent Donnefort <vdonnefort@google.com>
Link: https://patch.msgid.link/20260521143626.1005660-4-vdonnefort@google.com
Signed-off-by: Marc Zyngier <maz@kernel.org>
arch/arm64/kvm/hyp/nvhe/mem_protect.c

index 88cd723322085b7892351138c98fd34d2218e651..3263f6d0a0a428daf9c7709dc233b4d28b395842 100644 (file)
@@ -833,6 +833,16 @@ static int __hyp_check_page_state_range(phys_addr_t phys, u64 size, enum pkvm_pa
        return 0;
 }
 
+static int __hyp_check_page_count_range(phys_addr_t phys, u64 size)
+{
+       for_each_hyp_page(page, phys, size) {
+               if (page->refcount)
+                       return -EBUSY;
+       }
+
+       return 0;
+}
+
 static bool guest_pte_is_poisoned(kvm_pte_t pte)
 {
        if (kvm_pte_valid(pte))
@@ -1031,7 +1041,6 @@ unlock:
 int __pkvm_host_unshare_hyp(u64 pfn)
 {
        u64 phys = hyp_pfn_to_phys(pfn);
-       u64 virt = (u64)__hyp_va(phys);
        u64 size = PAGE_SIZE;
        int ret;
 
@@ -1044,10 +1053,9 @@ int __pkvm_host_unshare_hyp(u64 pfn)
        ret = __hyp_check_page_state_range(phys, size, PKVM_PAGE_SHARED_BORROWED);
        if (ret)
                goto unlock;
-       if (hyp_page_count((void *)virt)) {
-               ret = -EBUSY;
+       ret = __hyp_check_page_count_range(phys, size);
+       if (ret)
                goto unlock;
-       }
 
        __hyp_set_page_state_range(phys, size, PKVM_NOPAGE);
        WARN_ON(__host_set_page_state_range(phys, size, PKVM_PAGE_OWNED));
@@ -1110,6 +1118,10 @@ int __pkvm_hyp_donate_host(u64 pfn, u64 nr_pages)
        if (ret)
                goto unlock;
 
+       ret = __hyp_check_page_count_range(phys, size);
+       if (ret)
+               goto unlock;
+
        __hyp_set_page_state_range(phys, size, PKVM_NOPAGE);
        WARN_ON(kvm_pgtable_hyp_unmap(&pkvm_pgtable, virt, size) != size);
        WARN_ON(host_stage2_set_owner_locked(phys, size, PKVM_ID_HOST));