]> git.ipfire.org Git - thirdparty/kernel/linux.git/commitdiff
KVM: s390: Prevent memslots outside the ASCE range
authorClaudio Imbrenda <imbrenda@linux.ibm.com>
Tue, 2 Jun 2026 14:23:54 +0000 (16:23 +0200)
committerClaudio Imbrenda <imbrenda@linux.ibm.com>
Tue, 2 Jun 2026 17:27:11 +0000 (19:27 +0200)
With KVM_S390_VM_MEM_LIMIT_SIZE, userspace can set the highest address
allowed for the VM. Creating a memslot that lies over the maximum
address does not make sense and is only a potential source of bugs.

Prevent creation of memslots over the maximum address, and prevent the
maximum address from being reduced below the end of existing memslots.

Fixes: e38c884df921 ("KVM: s390: Switch to new gmap")
Signed-off-by: Claudio Imbrenda <imbrenda@linux.ibm.com>
Message-ID: <20260602142356.169458-9-imbrenda@linux.ibm.com>

arch/s390/kvm/kvm-s390.c

index e09960c2e6ed1ad54808a2c330c9b145cb01a9e7..ffb20a64d328f30b0294cd6abe213074266446e7 100644 (file)
@@ -999,7 +999,10 @@ static int kvm_s390_set_mem_control(struct kvm *kvm, struct kvm_device_attr *att
                break;
        }
        case KVM_S390_VM_MEM_LIMIT_SIZE: {
+               struct kvm_memslots *slots;
+               struct kvm_memory_slot *ms;
                unsigned long new_limit;
+               int bkt;
 
                if (kvm_is_ucontrol(kvm))
                        return -EINVAL;
@@ -1007,6 +1010,9 @@ static int kvm_s390_set_mem_control(struct kvm *kvm, struct kvm_device_attr *att
                if (get_user(new_limit, (u64 __user *)attr->addr))
                        return -EFAULT;
 
+               guard(mutex)(&kvm->lock);
+
+               new_limit = ALIGN(new_limit, HPAGE_SIZE);
                if (kvm->arch.mem_limit != KVM_S390_NO_MEM_LIMIT &&
                    new_limit > kvm->arch.mem_limit)
                        return -E2BIG;
@@ -1014,12 +1020,27 @@ static int kvm_s390_set_mem_control(struct kvm *kvm, struct kvm_device_attr *att
                if (!new_limit)
                        return -EINVAL;
 
-               ret = -EBUSY;
-               if (!kvm->created_vcpus)
-                       ret = gmap_set_limit(kvm->arch.gmap, gpa_to_gfn(new_limit));
+               if (kvm->created_vcpus)
+                       return -EBUSY;
+
+               ret = 0;
+               scoped_guard(mutex, &kvm->slots_lock) {
+                       slots = kvm_memslots(kvm);
+                       if (slots && !kvm_memslots_empty(slots)) {
+                               kvm_for_each_memslot(ms, bkt, slots) {
+                                       if (gpa_to_gfn(new_limit) < ms->base_gfn + ms->npages) {
+                                               ret = -EBUSY;
+                                               break;
+                                       }
+                               }
+                       }
+                       if (!ret)
+                               ret = gmap_set_limit(kvm->arch.gmap, gpa_to_gfn(new_limit));
+               }
+               if (ret)
+                       break;
                VM_EVENT(kvm, 3, "SET: max guest address: %lu", new_limit);
-               VM_EVENT(kvm, 3, "New guest asce: 0x%p",
-                        (void *)kvm->arch.gmap->asce.val);
+               VM_EVENT(kvm, 3, "New guest asce: 0x%p", (void *)kvm->arch.gmap->asce.val);
                break;
        }
        default:
@@ -5672,6 +5693,8 @@ int kvm_arch_prepare_memory_region(struct kvm *kvm,
                        return -EINVAL;
                if ((new->base_gfn + new->npages) * PAGE_SIZE > kvm->arch.mem_limit)
                        return -EINVAL;
+               if (!asce_contains_gfn(kvm->arch.gmap->asce, new->base_gfn + new->npages - 1))
+                       return -EINVAL;
        }
 
        if (!kvm->arch.migration_mode)