KVM: x86: PMU Event Filter
Some events can provide a guest with information about other guests or the host (e.g. L3 cache stats); providing the capability to restrict access to a "safe" set of events would limit the potential for the PMU to be used in any side channel attacks. This change introduces a new VM ioctl that sets an event filter. If the guest attempts to program a counter for any blacklisted or non-whitelisted event, the kernel counter won't be created, so any RDPMC/RDMSR will show 0 instances of that event. Signed-off-by: Eric Hankland <ehankland@google.com> [Lots of changes. All remaining bugs are probably mine. - Paolo] Signed-off-by: Paolo Bonzini <pbonzini@redhat.com>
This commit is contained in:
parent
cdc238eb72
commit
66bb8a065f
@ -4065,6 +4065,32 @@ KVM_ARM_VCPU_FINALIZE call.
|
|||||||
See KVM_ARM_VCPU_INIT for details of vcpu features that require finalization
|
See KVM_ARM_VCPU_INIT for details of vcpu features that require finalization
|
||||||
using this ioctl.
|
using this ioctl.
|
||||||
|
|
||||||
|
4.120 KVM_SET_PMU_EVENT_FILTER
|
||||||
|
|
||||||
|
Capability: KVM_CAP_PMU_EVENT_FILTER
|
||||||
|
Architectures: x86
|
||||||
|
Type: vm ioctl
|
||||||
|
Parameters: struct kvm_pmu_event_filter (in)
|
||||||
|
Returns: 0 on success, -1 on error
|
||||||
|
|
||||||
|
struct kvm_pmu_event_filter {
|
||||||
|
__u32 action;
|
||||||
|
__u32 nevents;
|
||||||
|
__u64 events[0];
|
||||||
|
};
|
||||||
|
|
||||||
|
This ioctl restricts the set of PMU events that the guest can program.
|
||||||
|
The argument holds a list of events which will be allowed or denied.
|
||||||
|
The eventsel+umask of each event the guest attempts to program is compared
|
||||||
|
against the events field to determine whether the guest should have access.
|
||||||
|
This only affects general purpose counters; fixed purpose counters can
|
||||||
|
be disabled by changing the perfmon CPUID leaf.
|
||||||
|
|
||||||
|
Valid values for 'action':
|
||||||
|
#define KVM_PMU_EVENT_ALLOW 0
|
||||||
|
#define KVM_PMU_EVENT_DENY 1
|
||||||
|
|
||||||
|
|
||||||
5. The kvm_run structure
|
5. The kvm_run structure
|
||||||
------------------------
|
------------------------
|
||||||
|
|
||||||
|
@ -933,6 +933,8 @@ struct kvm_arch {
|
|||||||
|
|
||||||
bool guest_can_read_msr_platform_info;
|
bool guest_can_read_msr_platform_info;
|
||||||
bool exception_payload_enabled;
|
bool exception_payload_enabled;
|
||||||
|
|
||||||
|
struct kvm_pmu_event_filter *pmu_event_filter;
|
||||||
};
|
};
|
||||||
|
|
||||||
struct kvm_vm_stat {
|
struct kvm_vm_stat {
|
||||||
|
@ -422,4 +422,14 @@ struct kvm_nested_state {
|
|||||||
__u8 data[0];
|
__u8 data[0];
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/* for KVM_CAP_PMU_EVENT_FILTER */
|
||||||
|
struct kvm_pmu_event_filter {
|
||||||
|
__u32 action;
|
||||||
|
__u32 nevents;
|
||||||
|
__u64 events[0];
|
||||||
|
};
|
||||||
|
|
||||||
|
#define KVM_PMU_EVENT_ALLOW 0
|
||||||
|
#define KVM_PMU_EVENT_DENY 1
|
||||||
|
|
||||||
#endif /* _ASM_X86_KVM_H */
|
#endif /* _ASM_X86_KVM_H */
|
||||||
|
@ -22,6 +22,9 @@
|
|||||||
#include "lapic.h"
|
#include "lapic.h"
|
||||||
#include "pmu.h"
|
#include "pmu.h"
|
||||||
|
|
||||||
|
/* This keeps the total size of the filter under 4k. */
|
||||||
|
#define KVM_PMU_EVENT_FILTER_MAX_EVENTS 63
|
||||||
|
|
||||||
/* NOTE:
|
/* NOTE:
|
||||||
* - Each perf counter is defined as "struct kvm_pmc";
|
* - Each perf counter is defined as "struct kvm_pmc";
|
||||||
* - There are two types of perf counters: general purpose (gp) and fixed.
|
* - There are two types of perf counters: general purpose (gp) and fixed.
|
||||||
@ -144,6 +147,10 @@ void reprogram_gp_counter(struct kvm_pmc *pmc, u64 eventsel)
|
|||||||
{
|
{
|
||||||
unsigned config, type = PERF_TYPE_RAW;
|
unsigned config, type = PERF_TYPE_RAW;
|
||||||
u8 event_select, unit_mask;
|
u8 event_select, unit_mask;
|
||||||
|
struct kvm *kvm = pmc->vcpu->kvm;
|
||||||
|
struct kvm_pmu_event_filter *filter;
|
||||||
|
int i;
|
||||||
|
bool allow_event = true;
|
||||||
|
|
||||||
if (eventsel & ARCH_PERFMON_EVENTSEL_PIN_CONTROL)
|
if (eventsel & ARCH_PERFMON_EVENTSEL_PIN_CONTROL)
|
||||||
printk_once("kvm pmu: pin control bit is ignored\n");
|
printk_once("kvm pmu: pin control bit is ignored\n");
|
||||||
@ -155,6 +162,22 @@ void reprogram_gp_counter(struct kvm_pmc *pmc, u64 eventsel)
|
|||||||
if (!(eventsel & ARCH_PERFMON_EVENTSEL_ENABLE) || !pmc_is_enabled(pmc))
|
if (!(eventsel & ARCH_PERFMON_EVENTSEL_ENABLE) || !pmc_is_enabled(pmc))
|
||||||
return;
|
return;
|
||||||
|
|
||||||
|
filter = srcu_dereference(kvm->arch.pmu_event_filter, &kvm->srcu);
|
||||||
|
if (filter) {
|
||||||
|
for (i = 0; i < filter->nevents; i++)
|
||||||
|
if (filter->events[i] ==
|
||||||
|
(eventsel & AMD64_RAW_EVENT_MASK_NB))
|
||||||
|
break;
|
||||||
|
if (filter->action == KVM_PMU_EVENT_ALLOW &&
|
||||||
|
i == filter->nevents)
|
||||||
|
allow_event = false;
|
||||||
|
if (filter->action == KVM_PMU_EVENT_DENY &&
|
||||||
|
i < filter->nevents)
|
||||||
|
allow_event = false;
|
||||||
|
}
|
||||||
|
if (!allow_event)
|
||||||
|
return;
|
||||||
|
|
||||||
event_select = eventsel & ARCH_PERFMON_EVENTSEL_EVENT;
|
event_select = eventsel & ARCH_PERFMON_EVENTSEL_EVENT;
|
||||||
unit_mask = (eventsel & ARCH_PERFMON_EVENTSEL_UMASK) >> 8;
|
unit_mask = (eventsel & ARCH_PERFMON_EVENTSEL_UMASK) >> 8;
|
||||||
|
|
||||||
@ -351,3 +374,43 @@ void kvm_pmu_destroy(struct kvm_vcpu *vcpu)
|
|||||||
{
|
{
|
||||||
kvm_pmu_reset(vcpu);
|
kvm_pmu_reset(vcpu);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
int kvm_vm_ioctl_set_pmu_event_filter(struct kvm *kvm, void __user *argp)
|
||||||
|
{
|
||||||
|
struct kvm_pmu_event_filter tmp, *filter;
|
||||||
|
size_t size;
|
||||||
|
int r;
|
||||||
|
|
||||||
|
if (copy_from_user(&tmp, argp, sizeof(tmp)))
|
||||||
|
return -EFAULT;
|
||||||
|
|
||||||
|
if (tmp.action != KVM_PMU_EVENT_ALLOW &&
|
||||||
|
tmp.action != KVM_PMU_EVENT_DENY)
|
||||||
|
return -EINVAL;
|
||||||
|
|
||||||
|
if (tmp.nevents > KVM_PMU_EVENT_FILTER_MAX_EVENTS)
|
||||||
|
return -E2BIG;
|
||||||
|
|
||||||
|
size = struct_size(filter, events, tmp.nevents);
|
||||||
|
filter = kmalloc(size, GFP_KERNEL_ACCOUNT);
|
||||||
|
if (!filter)
|
||||||
|
return -ENOMEM;
|
||||||
|
|
||||||
|
r = -EFAULT;
|
||||||
|
if (copy_from_user(filter, argp, size))
|
||||||
|
goto cleanup;
|
||||||
|
|
||||||
|
/* Ensure nevents can't be changed between the user copies. */
|
||||||
|
*filter = tmp;
|
||||||
|
|
||||||
|
mutex_lock(&kvm->lock);
|
||||||
|
rcu_swap_protected(kvm->arch.pmu_event_filter, filter,
|
||||||
|
mutex_is_locked(&kvm->lock));
|
||||||
|
mutex_unlock(&kvm->lock);
|
||||||
|
|
||||||
|
synchronize_srcu_expedited(&kvm->srcu);
|
||||||
|
r = 0;
|
||||||
|
cleanup:
|
||||||
|
kfree(filter);
|
||||||
|
return r;
|
||||||
|
}
|
||||||
|
@ -118,6 +118,7 @@ void kvm_pmu_refresh(struct kvm_vcpu *vcpu);
|
|||||||
void kvm_pmu_reset(struct kvm_vcpu *vcpu);
|
void kvm_pmu_reset(struct kvm_vcpu *vcpu);
|
||||||
void kvm_pmu_init(struct kvm_vcpu *vcpu);
|
void kvm_pmu_init(struct kvm_vcpu *vcpu);
|
||||||
void kvm_pmu_destroy(struct kvm_vcpu *vcpu);
|
void kvm_pmu_destroy(struct kvm_vcpu *vcpu);
|
||||||
|
int kvm_vm_ioctl_set_pmu_event_filter(struct kvm *kvm, void __user *argp);
|
||||||
|
|
||||||
bool is_vmware_backdoor_pmc(u32 pmc_idx);
|
bool is_vmware_backdoor_pmc(u32 pmc_idx);
|
||||||
|
|
||||||
|
@ -3132,6 +3132,7 @@ int kvm_vm_ioctl_check_extension(struct kvm *kvm, long ext)
|
|||||||
case KVM_CAP_SET_BOOT_CPU_ID:
|
case KVM_CAP_SET_BOOT_CPU_ID:
|
||||||
case KVM_CAP_SPLIT_IRQCHIP:
|
case KVM_CAP_SPLIT_IRQCHIP:
|
||||||
case KVM_CAP_IMMEDIATE_EXIT:
|
case KVM_CAP_IMMEDIATE_EXIT:
|
||||||
|
case KVM_CAP_PMU_EVENT_FILTER:
|
||||||
case KVM_CAP_GET_MSR_FEATURES:
|
case KVM_CAP_GET_MSR_FEATURES:
|
||||||
case KVM_CAP_MSR_PLATFORM_INFO:
|
case KVM_CAP_MSR_PLATFORM_INFO:
|
||||||
case KVM_CAP_EXCEPTION_PAYLOAD:
|
case KVM_CAP_EXCEPTION_PAYLOAD:
|
||||||
@ -4978,6 +4979,9 @@ set_identity_unlock:
|
|||||||
r = kvm_vm_ioctl_hv_eventfd(kvm, &hvevfd);
|
r = kvm_vm_ioctl_hv_eventfd(kvm, &hvevfd);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
case KVM_SET_PMU_EVENT_FILTER:
|
||||||
|
r = kvm_vm_ioctl_set_pmu_event_filter(kvm, argp);
|
||||||
|
break;
|
||||||
default:
|
default:
|
||||||
r = -ENOTTY;
|
r = -ENOTTY;
|
||||||
}
|
}
|
||||||
@ -9428,6 +9432,7 @@ void kvm_arch_destroy_vm(struct kvm *kvm)
|
|||||||
kvm_ioapic_destroy(kvm);
|
kvm_ioapic_destroy(kvm);
|
||||||
kvm_free_vcpus(kvm);
|
kvm_free_vcpus(kvm);
|
||||||
kvfree(rcu_dereference_check(kvm->arch.apic_map, 1));
|
kvfree(rcu_dereference_check(kvm->arch.apic_map, 1));
|
||||||
|
kfree(srcu_dereference_check(kvm->arch.pmu_event_filter, &kvm->srcu, 1));
|
||||||
kvm_mmu_uninit_vm(kvm);
|
kvm_mmu_uninit_vm(kvm);
|
||||||
kvm_page_track_cleanup(kvm);
|
kvm_page_track_cleanup(kvm);
|
||||||
kvm_hv_destroy_vm(kvm);
|
kvm_hv_destroy_vm(kvm);
|
||||||
|
@ -995,6 +995,7 @@ struct kvm_ppc_resize_hpt {
|
|||||||
#define KVM_CAP_ARM_SVE 170
|
#define KVM_CAP_ARM_SVE 170
|
||||||
#define KVM_CAP_ARM_PTRAUTH_ADDRESS 171
|
#define KVM_CAP_ARM_PTRAUTH_ADDRESS 171
|
||||||
#define KVM_CAP_ARM_PTRAUTH_GENERIC 172
|
#define KVM_CAP_ARM_PTRAUTH_GENERIC 172
|
||||||
|
#define KVM_CAP_PMU_EVENT_FILTER 173
|
||||||
|
|
||||||
#ifdef KVM_CAP_IRQ_ROUTING
|
#ifdef KVM_CAP_IRQ_ROUTING
|
||||||
|
|
||||||
@ -1329,6 +1330,8 @@ struct kvm_s390_ucas_mapping {
|
|||||||
#define KVM_PPC_GET_RMMU_INFO _IOW(KVMIO, 0xb0, struct kvm_ppc_rmmu_info)
|
#define KVM_PPC_GET_RMMU_INFO _IOW(KVMIO, 0xb0, struct kvm_ppc_rmmu_info)
|
||||||
/* Available with KVM_CAP_PPC_GET_CPU_CHAR */
|
/* Available with KVM_CAP_PPC_GET_CPU_CHAR */
|
||||||
#define KVM_PPC_GET_CPU_CHAR _IOR(KVMIO, 0xb1, struct kvm_ppc_cpu_char)
|
#define KVM_PPC_GET_CPU_CHAR _IOR(KVMIO, 0xb1, struct kvm_ppc_cpu_char)
|
||||||
|
/* Available with KVM_CAP_PMU_EVENT_FILTER */
|
||||||
|
#define KVM_SET_PMU_EVENT_FILTER _IOW(KVMIO, 0xb2, struct kvm_pmu_event_filter)
|
||||||
|
|
||||||
/* ioctl for vm fd */
|
/* ioctl for vm fd */
|
||||||
#define KVM_CREATE_DEVICE _IOWR(KVMIO, 0xe0, struct kvm_create_device)
|
#define KVM_CREATE_DEVICE _IOWR(KVMIO, 0xe0, struct kvm_create_device)
|
||||||
|
Loading…
Reference in New Issue
Block a user