]> git.ipfire.org Git - thirdparty/linux.git/commitdiff
KVM: arm64: Adjust range correctly during host stage-2 faults
authorQuentin Perret <qperret@google.com>
Wed, 25 Jun 2025 10:55:48 +0000 (10:55 +0000)
committerMarc Zyngier <maz@kernel.org>
Thu, 26 Jun 2025 07:04:43 +0000 (08:04 +0100)
host_stage2_adjust_range() tries to find the largest block mapping that
fits within a memory or mmio region (represented by a kvm_mem_range in
this function) during host stage-2 faults under pKVM. To do so, it walks
the host stage-2 page-table, finds the faulting PTE and its level, and
then progressively increments the level until it finds a granule of the
appropriate size. However, the condition in the loop implementing the
above is broken as it checks kvm_level_supports_block_mapping() for the
next level instead of the current, so pKVM may attempt to map a region
larger than can be covered with a single block.

This is not a security problem and is quite rare in practice (the
kvm_mem_range check usually forces host_stage2_adjust_range() to choose a
smaller granule), but this is clearly not the expected behaviour.

Refactor the loop to fix the bug and improve readability.

Fixes: c4f0935e4d95 ("KVM: arm64: Optimize host memory aborts")
Signed-off-by: Quentin Perret <qperret@google.com>
Link: https://lore.kernel.org/r/20250625105548.984572-1-qperret@google.com
Signed-off-by: Marc Zyngier <maz@kernel.org>
arch/arm64/kvm/hyp/nvhe/mem_protect.c

index 95d7534c967958ce72c2e3d80fd698dd510b1794..8957734d6183e63b7daa2b666d529eea8d060fa7 100644 (file)
@@ -479,6 +479,7 @@ static int host_stage2_adjust_range(u64 addr, struct kvm_mem_range *range)
 {
        struct kvm_mem_range cur;
        kvm_pte_t pte;
+       u64 granule;
        s8 level;
        int ret;
 
@@ -496,18 +497,21 @@ static int host_stage2_adjust_range(u64 addr, struct kvm_mem_range *range)
                return -EPERM;
        }
 
-       do {
-               u64 granule = kvm_granule_size(level);
+       for (; level <= KVM_PGTABLE_LAST_LEVEL; level++) {
+               if (!kvm_level_supports_block_mapping(level))
+                       continue;
+               granule = kvm_granule_size(level);
                cur.start = ALIGN_DOWN(addr, granule);
                cur.end = cur.start + granule;
-               level++;
-       } while ((level <= KVM_PGTABLE_LAST_LEVEL) &&
-                       !(kvm_level_supports_block_mapping(level) &&
-                         range_included(&cur, range)));
+               if (!range_included(&cur, range))
+                       continue;
+               *range = cur;
+               return 0;
+       }
 
-       *range = cur;
+       WARN_ON(1);
 
-       return 0;
+       return -EINVAL;
 }
 
 int host_stage2_idmap_locked(phys_addr_t addr, u64 size,