From: David Woodhouse <[email protected]> The reference TSC page is written by the host, not by the guest, so the guest-side memory attribute tests never cover it. Add a test that a page at an unprotected gfn is maintained as usual, that pointing HV_X64_MSR_REFERENCE_TSC at a gfn carrying KVM_MEMORY_ATTRIBUTE_NW leaves the page contents untouched — on the MSR write itself and on a subsequent KVM_SET_CLOCK-forced update — and that re-pointing the MSR at a writable page recovers.
Signed-off-by: David Woodhouse <[email protected]> Assisted-by: LLM --- tools/testing/selftests/kvm/Makefile.kvm | 1 + .../selftests/kvm/x86/hyperv_tsc_page_attrs.c | 129 ++++++++++++++++++ 2 files changed, 130 insertions(+) create mode 100644 tools/testing/selftests/kvm/x86/hyperv_tsc_page_attrs.c diff --git a/tools/testing/selftests/kvm/Makefile.kvm b/tools/testing/selftests/kvm/Makefile.kvm index 47c7a4c732eb..6edeb6693375 100644 --- a/tools/testing/selftests/kvm/Makefile.kvm +++ b/tools/testing/selftests/kvm/Makefile.kvm @@ -89,6 +89,7 @@ TEST_GEN_PROGS_x86 += x86/hyperv_forward_hcall TEST_GEN_PROGS_x86 += x86/hyperv_ipi TEST_GEN_PROGS_x86 += x86/hyperv_svm_test TEST_GEN_PROGS_x86 += x86/hyperv_tlb_flush +TEST_GEN_PROGS_x86 += x86/hyperv_tsc_page_attrs TEST_GEN_PROGS_x86 += x86/kvm_clock_test TEST_GEN_PROGS_x86 += x86/kvm_pv_test TEST_GEN_PROGS_x86 += x86/kvm_buslock_test diff --git a/tools/testing/selftests/kvm/x86/hyperv_tsc_page_attrs.c b/tools/testing/selftests/kvm/x86/hyperv_tsc_page_attrs.c new file mode 100644 index 000000000000..efa47ae69780 --- /dev/null +++ b/tools/testing/selftests/kvm/x86/hyperv_tsc_page_attrs.c @@ -0,0 +1,129 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Test that KVM honours plane memory attributes when it maintains the + * Hyper-V reference TSC page. A page whose gfn carries + * KVM_MEMORY_ATTRIBUTE_NW in the owning plane's attributes must never + * be written by the host, on the HV_X64_MSR_REFERENCE_TSC write or on + * any later masterclock update; an unprotected page keeps working, and + * re-pointing the MSR at a writable page recovers. + */ +#include "test_util.h" +#include "kvm_util.h" +#include "processor.h" +#include "hyperv.h" + +struct ms_hyperv_tsc_page { + volatile u32 tsc_sequence; + u32 reserved1; + volatile u64 tsc_scale; + volatile s64 tsc_offset; +} __packed; + +#define SENTINEL_SEQ 0xdeadbeef +#define SENTINEL_U64 0xcafef00dcafef00dULL + +static void guest_main(struct ms_hyperv_tsc_page *page_a, gpa_t page_a_gpa, + struct ms_hyperv_tsc_page *page_b, gpa_t page_b_gpa) +{ + /* Set Guest OS id to enable Hyper-V emulation */ + wrmsr(HV_X64_MSR_GUEST_OS_ID, HYPERV_LINUX_OS_ID); + + /* An unprotected page is maintained by KVM as usual. */ + wrmsr(HV_X64_MSR_REFERENCE_TSC, page_a_gpa | 0x1); + GUEST_ASSERT(page_a->tsc_sequence != 0); + + /* Fill the page about to be protected, while it is still writable. */ + page_b->tsc_sequence = SENTINEL_SEQ; + page_b->tsc_scale = SENTINEL_U64; + page_b->tsc_offset = SENTINEL_U64; + + GUEST_SYNC(1); /* host sets KVM_MEMORY_ATTRIBUTE_NW on page_b */ + + /* + * Pointing the reference TSC MSR at the protected page must not + * have KVM write to it: not on the MSR write itself, and not on + * the masterclock update the write requests. + */ + wrmsr(HV_X64_MSR_REFERENCE_TSC, page_b_gpa | 0x1); + GUEST_ASSERT(page_b->tsc_sequence == SENTINEL_SEQ); + GUEST_ASSERT(page_b->tsc_scale == SENTINEL_U64); + GUEST_ASSERT(page_b->tsc_offset == SENTINEL_U64); + + GUEST_SYNC(2); /* host resets kvmclock, forcing a TSC page update */ + + GUEST_ASSERT(page_b->tsc_sequence == SENTINEL_SEQ); + GUEST_ASSERT(page_b->tsc_scale == SENTINEL_U64); + GUEST_ASSERT(page_b->tsc_offset == SENTINEL_U64); + + /* Re-pointing the MSR at a writable page recovers. */ + wrmsr(HV_X64_MSR_REFERENCE_TSC, page_a_gpa | 0x1); + GUEST_ASSERT(page_a->tsc_sequence != 0); + + GUEST_DONE(); +} + +int main(void) +{ + struct kvm_vcpu *vcpu; + struct kvm_vm *vm; + struct ucall uc; + gva_t page_a_gva, page_b_gva; + gpa_t page_b_gpa; + int stage; + + TEST_REQUIRE(kvm_has_cap(KVM_CAP_HYPERV_TIME)); + TEST_REQUIRE(kvm_check_cap(KVM_CAP_MEMORY_ATTRIBUTES) & + KVM_MEMORY_ATTRIBUTE_NW); + TEST_REQUIRE(sys_clocksource_is_based_on_tsc()); + + vm = vm_create_with_one_vcpu(&vcpu, guest_main); + + vcpu_set_hv_cpuid(vcpu); + + page_a_gva = vm_alloc_page(vm); + page_b_gva = vm_alloc_page(vm); + memset(addr_gva2hva(vm, page_a_gva), 0x0, getpagesize()); + memset(addr_gva2hva(vm, page_b_gva), 0x0, getpagesize()); + page_b_gpa = addr_gva2gpa(vm, page_b_gva); + + vcpu_args_set(vcpu, 4, page_a_gva, addr_gva2gpa(vm, page_a_gva), + page_b_gva, page_b_gpa); + + for (stage = 1;; stage++) { + vcpu_run(vcpu); + TEST_ASSERT_KVM_EXIT_REASON(vcpu, KVM_EXIT_IO); + + switch (get_ucall(vcpu, &uc)) { + case UCALL_ABORT: + REPORT_GUEST_ASSERT(uc); + /* NOT REACHED */ + case UCALL_SYNC: + break; + case UCALL_DONE: + /* Keep in sync with guest_main() */ + TEST_ASSERT(stage == 3, "Testing ended prematurely, stage %d", + stage); + goto out; + default: + TEST_FAIL("Unknown ucall %lu", uc.cmd); + } + + TEST_ASSERT(uc.args[1] == stage, + "Stage %d: Unexpected sync, got %lx", + stage, (ulong)uc.args[1]); + + if (stage == 1) + vm_set_memory_attributes(vm, page_b_gpa, getpagesize(), + KVM_MEMORY_ATTRIBUTE_NW); + + /* Reset kvmclock triggering TSC page update */ + if (stage == 2) { + struct kvm_clock_data clock = {0}; + + vm_ioctl(vm, KVM_SET_CLOCK, &clock); + } + } + +out: + kvm_vm_free(vm); +} -- 2.50.1

