From: Zeng Chi kvm_vm_set_mem_attributes() reserves an xarray entry for every gfn in the range before storing the new attributes, so that the store loop can't fail partway through. If one of the reservations fails, e.g. with -ENOMEM, the entries that were already reserved are left in the array. That is harmless as far as xa_reserve() is concerned, as the reserved entries read back as NULL via xa_load(), but it confuses the "does this range have no attributes at all" check: if (!attrs) return !xas_find(&xas, end - 1); A reserved entry is XA_ZERO_ENTRY, not NULL, and xas_find() returns it as present. So a leftover reservation makes KVM report that a fully shared range has attributes even though kvm_get_memory_attributes() returns none for every gfn in the range. On x86, the next time mixed-attribute tracking is recomputed for the range (memslot creation, or a later attribute change that straddles the 2MiB page), hugepage_has_attrs() treats a fully shared 2MiB range as mixed and refuses to map it with a hugepage, until userspace happens to set attributes on the range again. Walk the range and ignore reserved-but-unset entries when checking for the absence of attributes, so a leftover reservation is treated the same as an empty slot. Note, the generic loop for the attrs != 0 case already skips zero entries via xas_retry(), i.e. only the !attrs shortcut was affected. While at it, skip the reservation loop entirely when clearing attributes, as storing NULL only erases the entry and never needs to allocate, so no reservation (and no cleanup of a failed one) is required in that case. Fixes: 5a475554db1e ("KVM: Introduce per-page memory attributes") Signed-off-by: Zeng Chi --- virt/kvm/kvm_main.c | 21 +++++++++++++++++---- 1 file changed, 17 insertions(+), 4 deletions(-) diff --git a/virt/kvm/kvm_main.c b/virt/kvm/kvm_main.c index 65eb26a0520d..29534bcc7f02 100644 --- a/virt/kvm/kvm_main.c +++ b/virt/kvm/kvm_main.c @@ -2447,8 +2447,19 @@ bool kvm_range_has_memory_attributes(struct kvm *kvm, gfn_t start, gfn_t end, return (kvm_get_memory_attributes(kvm, start) & mask) == attrs; guard(rcu)(); - if (!attrs) - return !xas_find(&xas, end - 1); + if (!attrs) { + /* + * Reserved but unset entries (XA_ZERO_ENTRY, e.g. left behind by + * a failed reservation in kvm_vm_set_mem_attributes()) are + * returned as present by xas_find(), but hold no attributes. + * Skip them so that the range is correctly reported as having no + * attributes. + */ + xas_for_each(&xas, entry, end - 1) + if (!xa_is_zero(entry)) + return false; + return true; + } for (index = start; index < end; index++) { do { @@ -2571,9 +2582,11 @@ static int kvm_vm_set_mem_attributes(struct kvm *kvm, gfn_t start, gfn_t end, /* * Reserve memory ahead of time to avoid having to deal with failures - * partway through setting the new attributes. + * partway through setting the new attributes. Clearing attributes + * only stores NULL, which never needs to allocate, so skip the + * reservations entirely in that case. */ - for (i = start; i < end; i++) { + for (i = start; entry && i < end; i++) { r = xa_reserve(&kvm->mem_attr_array, i, GFP_KERNEL_ACCOUNT); if (r) goto out_unlock; -- 2.25.1 No virus found Checked by Hillstone Network AntiVirus