Commit 66bb8a06 authored by Eric Hankland's avatar Eric Hankland Committed by Paolo Bonzini
Browse files

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: default avatarEric Hankland <ehankland@google.com>
[Lots of changes. All remaining bugs are probably mine. - Paolo]
Signed-off-by: default avatarPaolo Bonzini <pbonzini@redhat.com>
parent cdc238eb
Loading
Loading
Loading
Loading
+26 −0
Original line number Diff line number Diff line
@@ -4065,6 +4065,32 @@ KVM_ARM_VCPU_FINALIZE call.
See KVM_ARM_VCPU_INIT for details of vcpu features that require finalization
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
------------------------

+2 −0
Original line number Diff line number Diff line
@@ -933,6 +933,8 @@ struct kvm_arch {

	bool guest_can_read_msr_platform_info;
	bool exception_payload_enabled;

	struct kvm_pmu_event_filter *pmu_event_filter;
};

struct kvm_vm_stat {
+10 −0
Original line number Diff line number Diff line
@@ -422,4 +422,14 @@ struct kvm_nested_state {
	__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 */
+63 −0
Original line number Diff line number Diff line
@@ -22,6 +22,9 @@
#include "lapic.h"
#include "pmu.h"

/* This keeps the total size of the filter under 4k. */
#define KVM_PMU_EVENT_FILTER_MAX_EVENTS 63

/* NOTE:
 * - Each perf counter is defined as "struct kvm_pmc";
 * - 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;
	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)
		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))
		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;
	unit_mask = (eventsel & ARCH_PERFMON_EVENTSEL_UMASK) >> 8;

@@ -351,3 +374,43 @@ void kvm_pmu_destroy(struct kvm_vcpu *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;
}
+1 −0
Original line number Diff line number Diff line
@@ -118,6 +118,7 @@ void kvm_pmu_refresh(struct kvm_vcpu *vcpu);
void kvm_pmu_reset(struct kvm_vcpu *vcpu);
void kvm_pmu_init(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);

Loading