From: Mushahid Hussain <[email protected]>

The reference TSC page is a page the hypervisor maintains for the guest.
A higher Virtual Trust Level keeps its own page, and it protects the
memory that holds that page against the lower levels. KVM writes the
page through kvm_write_guest(), which refuses a write to protected
memory. The refusal aborts the update of every page: KVM has written
sequence zero to all of them and leaves them that way for the rest of
the guest's life, so the guest falls back to the time reference count
register for every read of time.

Memory protections restrict the guest. They do not restrict the
hypervisor's update of a page the hypervisor owns. Add
kvm_write_guest_unprotected() for such a page and use it for the
reference TSC page.

Assisted-by: LLM
[dwmw2: the original message called the function kvm_write_guest_hv();
  it is kvm_write_guest_unprotected() in the code, say so. ]
Signed-off-by: David Woodhouse <[email protected]>
---
 arch/x86/kvm/hyperv.c    |  2 +-
 include/linux/kvm_host.h |  2 ++
 virt/kvm/kvm_main.c      | 27 +++++++++++++++++++++++++++
 3 files changed, 30 insertions(+), 1 deletion(-)

diff --git a/arch/x86/kvm/hyperv.c b/arch/x86/kvm/hyperv.c
index 2098daede7d3..eebe48f5be23 100644
--- a/arch/x86/kvm/hyperv.c
+++ b/arch/x86/kvm/hyperv.c
@@ -1260,7 +1260,7 @@ static int hv_write_tsc_pages(struct kvm *kvm, size_t len)
                if (!hv_tsc_page_gfn(hv, i, &gfn))
                        continue;
 
-               if (kvm_write_guest(kvm, gfn_to_gpa(gfn), &hv->tsc_ref, len))
+               if (kvm_write_guest_unprotected(kvm, gfn_to_gpa(gfn), 
&hv->tsc_ref, len))
                        return -EFAULT;
        }
 
diff --git a/include/linux/kvm_host.h b/include/linux/kvm_host.h
index fdcf401ccb46..beae4f2a982a 100644
--- a/include/linux/kvm_host.h
+++ b/include/linux/kvm_host.h
@@ -1551,6 +1551,8 @@ int kvm_write_guest_page(struct kvm *kvm, gfn_t gfn, 
const void *data,
                         int offset, int len);
 int kvm_write_guest(struct kvm *kvm, gpa_t gpa, const void *data,
                    unsigned long len);
+int kvm_write_guest_unprotected(struct kvm *kvm, gpa_t gpa, const void *data,
+                               unsigned long len);
 int kvm_write_guest_cached(struct kvm *kvm, struct gfn_to_hva_cache *ghc,
                           void *data, unsigned long len);
 int kvm_write_guest_offset_cached(struct kvm *kvm, struct gfn_to_hva_cache 
*ghc,
diff --git a/virt/kvm/kvm_main.c b/virt/kvm/kvm_main.c
index d37961cc5235..72fe64e5d4a1 100644
--- a/virt/kvm/kvm_main.c
+++ b/virt/kvm/kvm_main.c
@@ -3832,6 +3832,33 @@ int kvm_write_guest(struct kvm *kvm, gpa_t gpa, const 
void *data,
 }
 EXPORT_SYMBOL_FOR_KVM_INTERNAL(kvm_write_guest);
 
+/* Write a page KVM maintains for the guest; plane protections do not apply. */
+int kvm_write_guest_unprotected(struct kvm *kvm, gpa_t gpa, const void *data,
+                               unsigned long len)
+{
+       gfn_t gfn = gpa >> PAGE_SHIFT;
+       int seg;
+       int offset = offset_in_page(gpa);
+
+       while ((seg = next_segment(len, offset)) != 0) {
+               struct kvm_memory_slot *slot = gfn_to_memslot(kvm, gfn);
+               unsigned long addr;
+
+               addr = gfn_to_hva_many(slot, gfn, NULL);
+               if (kvm_is_error_hva(addr))
+                       return -EFAULT;
+               if (__copy_to_user((void __user *)addr + offset, data, seg))
+                       return -EFAULT;
+               mark_page_dirty_in_slot(kvm, slot, gfn);
+               offset = 0;
+               len -= seg;
+               data += seg;
+               ++gfn;
+       }
+       return 0;
+}
+EXPORT_SYMBOL_FOR_KVM_INTERNAL(kvm_write_guest_unprotected);
+
 int kvm_vcpu_write_guest(struct kvm_vcpu *vcpu, gpa_t gpa, const void *data,
                         unsigned long len)
 {
-- 
2.50.1


Reply via email to