KVM can't intercept a protected guest's event-select writes, and can't read or write the VMSA copy, so the guest forwards writes that trapped on a missing allow-list entry with SVM_VMGEXIT_PMC_FILTER, along with all of its event-selects. Emulate the write and run the resulting reprogramming synchronously, so the event filter decision is returned in the scratch buffer and RAX/RDX for the guest to program. As for other guests, a full allow list is rebuilt from the enabled event-selects, but as these are guest-supplied, only events the filter allows are listed. Now that KVM tracks the guest's event-selects, skip counting emulated instructions, KVM can't update the counters in the VMSA. Signed-off-by: Manali Shukla --- arch/x86/kvm/pmu.c | 7 +++- arch/x86/kvm/pmu.h | 1 + arch/x86/kvm/svm/pmu.c | 81 ++++++++++++++++++++++++++++++++++++++---- arch/x86/kvm/svm/sev.c | 54 ++++++++++++++++++++++++++++ arch/x86/kvm/svm/svm.c | 1 + arch/x86/kvm/svm/svm.h | 3 ++ 6 files changed, 140 insertions(+), 7 deletions(-) diff --git a/arch/x86/kvm/pmu.c b/arch/x86/kvm/pmu.c index ace39076977af..23d439ad182c6 100644 --- a/arch/x86/kvm/pmu.c +++ b/arch/x86/kvm/pmu.c @@ -573,7 +573,7 @@ static bool is_fixed_event_allowed(struct kvm_x86_pmu_event_filter *filter, return true; } -static bool pmc_is_event_allowed(struct kvm_pmc *pmc) +bool pmc_is_event_allowed(struct kvm_pmc *pmc) { struct kvm_x86_pmu_event_filter *filter; struct kvm *kvm = pmc->vcpu->kvm; @@ -587,6 +587,7 @@ static bool pmc_is_event_allowed(struct kvm_pmc *pmc) return is_fixed_event_allowed(filter, pmc->idx); } +EXPORT_SYMBOL_FOR_KVM_INTERNAL(pmc_is_event_allowed); static void kvm_mediated_pmu_refresh_event_filter(struct kvm_pmc *pmc) { @@ -1209,6 +1210,10 @@ static void kvm_pmu_trigger_event(struct kvm_vcpu *vcpu, BUILD_BUG_ON(sizeof(pmu->global_ctrl) * BITS_PER_BYTE != X86_PMC_IDX_MAX); + /* KVM can't access a protected guest's counters. */ + if (vcpu->arch.guest_state_protected) + return; + /* * The guest can reprogram allowed events without trapping. Don't count, * or raise a PMI, for a counter the guest has since repurposed. diff --git a/arch/x86/kvm/pmu.h b/arch/x86/kvm/pmu.h index d0b0589d3ae45..484e94307d731 100644 --- a/arch/x86/kvm/pmu.h +++ b/arch/x86/kvm/pmu.h @@ -175,6 +175,7 @@ static inline u64 pmc_read_counter(struct kvm_pmc *pmc) } void pmc_write_counter(struct kvm_pmc *pmc, u64 val); +bool pmc_is_event_allowed(struct kvm_pmc *pmc); static inline bool pmc_is_gp(struct kvm_pmc *pmc) { diff --git a/arch/x86/kvm/svm/pmu.c b/arch/x86/kvm/svm/pmu.c index 3ef05b651de21..967bd687b54c4 100644 --- a/arch/x86/kvm/svm/pmu.c +++ b/arch/x86/kvm/svm/pmu.c @@ -138,6 +138,46 @@ static u32 amd_pmu_adjust_msr_idx(struct kvm_vcpu *vcpu, u32 msr) return msr; } +/* KVM can't read a protected guest's event-selects, the guest forwards them. */ +static void amd_load_eventsels_from_ghcb(struct kvm_vcpu *vcpu) +{ + struct perf_ctls *ctls = to_svm(vcpu)->sev_es.ghcb_sa; + struct kvm_pmu *pmu = vcpu_to_pmu(vcpu); + struct kvm_pmc *pmc; + int i, n; + + n = min_t(int, READ_ONCE(ctls->num_counters), pmu->nr_arch_gp_counters); + for (i = 0; i < n; i++) { + pmc = get_gp_pmc_amd(pmu, READ_ONCE(ctls->counters[i].perf_ctl_idx), + PMU_TYPE_EVNTSEL); + if (pmc) { + pmc->eventsel = READ_ONCE(ctls->counters[i].perf_ctl_val); + pmc->eventsel_hw = pmc->eventsel; + } + } +} + +/* Return the filtered event-select, the guest programs it. */ +static void amd_publish_eventsel_to_ghcb(struct kvm_pmc *pmc) +{ + struct kvm_vcpu *vcpu = pmc->vcpu; + struct perf_ctls *ctls = to_svm(vcpu)->sev_es.ghcb_sa; + struct kvm_pmu *pmu = vcpu_to_pmu(vcpu); + int i, n; + + n = min_t(int, READ_ONCE(ctls->num_counters), pmu->nr_arch_gp_counters); + for (i = 0; i < n; i++) { + if (get_gp_pmc_amd(pmu, READ_ONCE(ctls->counters[i].perf_ctl_idx), + PMU_TYPE_EVNTSEL) == pmc) + WRITE_ONCE(ctls->counters[i].perf_ctl_val, pmc->eventsel_hw); + } + + if (get_gp_pmc_amd(pmu, kvm_ecx_read(vcpu), PMU_TYPE_EVNTSEL) == pmc) { + kvm_eax_write(vcpu, lower_32_bits(pmc->eventsel_hw)); + kvm_edx_write(vcpu, upper_32_bits(pmc->eventsel_hw)); + } +} + static void amd_sync_eventsels_from_hw(struct kvm_vcpu *vcpu) { struct kvm_pmu *pmu = vcpu_to_pmu(vcpu); @@ -151,6 +191,14 @@ static void amd_sync_eventsels_from_hw(struct kvm_vcpu *vcpu) */ if (!svm->pmc_eventsels_stale) return; + + if (vcpu->arch.guest_state_protected) { + if (svm->sev_es.pmc_filter_pending) { + amd_load_eventsels_from_ghcb(vcpu); + svm->pmc_eventsels_stale = false; + } + return; + } svm->pmc_eventsels_stale = false; for (i = 0; i < pmu->nr_arch_gp_counters; i++) { @@ -168,6 +216,21 @@ static void amd_rebuild_hw_pmc_allowlist(struct kvm_vcpu *vcpu) memset(control->allowed_perf_ctl, 0, sizeof(control->allowed_perf_ctl)); + /* + * Unlike the VMCB copy, which hardware keeps to listed events, a + * protected guest's event-selects are guest-supplied. Check each + * enabled event against the filter, or the guest could list any event. + */ + if (vcpu->arch.guest_state_protected) { + for (i = 0; i < pmu->nr_arch_gp_counters; i++) { + struct kvm_pmc *pmc = &pmu->gp_counters[i]; + + if (pmc_is_locally_enabled(pmc) && pmc_is_event_allowed(pmc)) + control->allowed_perf_ctl[i] = pmc->eventsel; + } + return; + } + for (i = 0; i < pmu->nr_arch_gp_counters; i++) if (save->pmc[i].perf_ctl & ARCH_PERFMON_EVENTSEL_ENABLE) control->allowed_perf_ctl[i] = save->pmc[i].perf_ctl; @@ -234,20 +297,22 @@ static int amd_get_vendor_state(struct kvm_vcpu *vcpu, u32 msr) static int amd_set_vendor_state(struct kvm_vcpu *vcpu, u32 msr) { struct vmcb_save_area *save = &to_svm(vcpu)->vmcb->save; + bool protected = vcpu->arch.guest_state_protected; struct kvm_pmu *pmu = vcpu_to_pmu(vcpu); struct kvm_pmc *pmc; - /* A protected guest's PMU state is in the VMSA, there's nothing to do. */ - if (vcpu->arch.guest_state_protected) + if (protected && !to_svm(vcpu)->sev_es.pmc_filter_pending) return 0; /* MSR_PERF_CNTR_GLOBAL_* */ switch (msr) { case MSR_AMD64_PERF_CNTR_GLOBAL_STATUS: - save->perf_cntr_global_status = pmu->global_status; + if (!protected) + save->perf_cntr_global_status = pmu->global_status; return 0; case MSR_AMD64_PERF_CNTR_GLOBAL_CTL: - save->perf_cntr_global_control = pmu->global_ctrl; + if (!protected) + save->perf_cntr_global_control = pmu->global_ctrl; if (kvm_vcpu_has_mediated_pmu_caps(vcpu, KVM_MEDIATED_PMU_CAP_HW_FILTERED) && pmu->nr_arch_gp_counters) { @@ -264,14 +329,18 @@ static int amd_set_vendor_state(struct kvm_vcpu *vcpu, u32 msr) /* MSR_PERFCTRn */ pmc = get_gp_pmc_amd(pmu, msr, PMU_TYPE_COUNTER); if (pmc) { - save->pmc[pmc->idx].perf_ctr = pmc->counter & pmc_bitmask(pmc); + if (!protected) + save->pmc[pmc->idx].perf_ctr = pmc->counter & pmc_bitmask(pmc); return 0; } /* MSR_EVNTSELn */ pmc = get_gp_pmc_amd(pmu, msr, PMU_TYPE_EVNTSEL); if (pmc) { - save->pmc[pmc->idx].perf_ctl = pmc->eventsel_hw; + if (protected) + amd_publish_eventsel_to_ghcb(pmc); + else + save->pmc[pmc->idx].perf_ctl = pmc->eventsel_hw; if (kvm_vcpu_has_mediated_pmu_caps(vcpu, KVM_MEDIATED_PMU_CAP_HW_FILTERED) && (pmc->eventsel_hw & ARCH_PERFMON_EVENTSEL_ENABLE)) diff --git a/arch/x86/kvm/svm/sev.c b/arch/x86/kvm/svm/sev.c index 1cf2260423573..b73484620e2a3 100644 --- a/arch/x86/kvm/svm/sev.c +++ b/arch/x86/kvm/svm/sev.c @@ -35,6 +35,7 @@ #include "svm.h" #include "svm_ops.h" #include "cpuid.h" +#include "pmu.h" #include "trace.h" #define GHCB_VERSION_MAX 2ULL @@ -3496,6 +3497,11 @@ static bool sev_es_are_required_ghcb_fields_valid(struct vcpu_svm *svm) case SVM_VMGEXIT_MMIO_WRITE: case SVM_VMGEXIT_PSC: return kvm_ghcb_sw_scratch_is_valid(svm); + case SVM_VMGEXIT_PMC_FILTER: + return kvm_ghcb_sw_scratch_is_valid(svm) && + kvm_ghcb_rax_is_valid(svm) && + kvm_ghcb_rcx_is_valid(svm) && + kvm_ghcb_rdx_is_valid(svm); default: return true; } @@ -4023,6 +4029,52 @@ static int snp_begin_psc(struct vcpu_svm *svm) return snp_do_psc(svm); } +static bool sev_es_is_pmc_filter_msr(u32 msr) +{ + switch (msr) { + case MSR_F15H_PERF_CTL0 ... MSR_F15H_PERF_CTR5: + return !(msr & 1); + case MSR_K7_EVNTSEL0 ... MSR_K7_EVNTSEL3: + case MSR_AMD64_PERF_CNTR_GLOBAL_CTL: + return true; + default: + return false; + } +} + +static int sev_es_handle_pmc_filter(struct vcpu_svm *svm) +{ + struct kvm_vcpu *vcpu = &svm->vcpu; + u32 msr = kvm_ecx_read(vcpu); + int ret; + + if (!kvm_vcpu_has_mediated_pmu_caps(vcpu, KVM_MEDIATED_PMU_CAP_HW_FILTERED) || + !sev_es_is_pmc_filter_msr(msr)) { + svm_vmgexit_bad_input(svm, GHCB_ERR_INVALID_INPUT); + return 1; + } + + ret = setup_vmgexit_scratch(svm, true, + struct_size_t(struct perf_ctls, counters, + vcpu_to_pmu(vcpu)->nr_arch_gp_counters)); + if (ret) + return ret; + + svm->sev_es.pmc_filter_pending = true; + ret = __kvm_emulate_msr_write(vcpu, msr, kvm_read_edx_eax(vcpu)); + /* Reprogram now, so the filtered event-selects go back to the guest. */ + if (!ret && kvm_check_request(KVM_REQ_PMU, vcpu)) + kvm_pmu_handle_event(vcpu); + svm->sev_es.pmc_filter_pending = false; + + if (ret) + svm_vmgexit_inject_exception(svm, X86_TRAP_GP); + else + svm_vmgexit_success(svm, 0); + + return 1; +} + static void __sev_snp_reload_vmsa(struct kvm_vcpu *vcpu, gpa_t gpa) { struct vcpu_svm *svm = to_svm(vcpu); @@ -4659,6 +4711,8 @@ int sev_handle_vmgexit(struct kvm_vcpu *vcpu) return snp_begin_psc(svm); } + case SVM_VMGEXIT_PMC_FILTER: + return sev_es_handle_pmc_filter(svm); case SVM_VMGEXIT_AP_CREATION: if (sev_snp_ap_creation(svm)) svm_vmgexit_bad_input(svm, GHCB_ERR_INVALID_INPUT); diff --git a/arch/x86/kvm/svm/svm.c b/arch/x86/kvm/svm/svm.c index ac8f06e1f5a10..cdd102c761994 100644 --- a/arch/x86/kvm/svm/svm.c +++ b/arch/x86/kvm/svm/svm.c @@ -2851,6 +2851,7 @@ static bool sev_es_prevent_msr_access(struct kvm_vcpu *vcpu, { return is_sev_es_guest(vcpu) && vcpu->arch.guest_state_protected && msr_info->index != MSR_IA32_XSS && + !to_svm(vcpu)->sev_es.pmc_filter_pending && !msr_write_intercepted(to_svm(vcpu), msr_info->index); } diff --git a/arch/x86/kvm/svm/svm.h b/arch/x86/kvm/svm/svm.h index 1f3cd12e44c46..3615997b339ec 100644 --- a/arch/x86/kvm/svm/svm.h +++ b/arch/x86/kvm/svm/svm.h @@ -282,6 +282,9 @@ struct vcpu_sev_es_state { gpa_t snp_guest_vmsa_gpa; bool snp_ap_waiting_for_reset; bool snp_has_guest_vmsa; + + /* Handling SVM_VMGEXIT_PMC_FILTER, the scratch area holds event-selects. */ + bool pmc_filter_pending; }; struct vcpu_svm { -- 2.43.0