Commit a51abbbf authored by Michal Luczaj's avatar Michal Luczaj Committed by Paolo Bonzini
Browse files

KVM: selftests: Add tests in xen_shinfo_test to detect lock races



Tests for races between shinfo_cache (de)activation and hypercall+ioctl()
processing.  KVM has had bugs where activating the shared info cache
multiple times and/or with concurrent users results in lock corruption,
NULL pointer dereferences, and other fun.

For the timer injection testcase (#22), re-arm the timer until the IRQ
is successfully injected.  If the timer expires while the shared info
is deactivated (invalid), KVM will drop the event.

Signed-off-by: default avatarMichal Luczaj <mhal@rbox.co>
Co-developed-by: default avatarSean Christopherson <seanjc@google.com>
Signed-off-by: default avatarSean Christopherson <seanjc@google.com>
Message-Id: <20221013211234.1318131-16-seanjc@google.com>
Signed-off-by: default avatarPaolo Bonzini <pbonzini@redhat.com>
parent ecbcf030
Loading
Loading
Loading
Loading
+140 −0
Original line number Diff line number Diff line
@@ -15,9 +15,13 @@
#include <time.h>
#include <sched.h>
#include <signal.h>
#include <pthread.h>

#include <sys/eventfd.h>

/* Defined in include/linux/kvm_types.h */
#define GPA_INVALID		(~(ulong)0)

#define SHINFO_REGION_GVA	0xc0000000ULL
#define SHINFO_REGION_GPA	0xc0000000ULL
#define SHINFO_REGION_SLOT	10
@@ -44,6 +48,8 @@

#define MIN_STEAL_TIME		50000

#define SHINFO_RACE_TIMEOUT	2	/* seconds */

#define __HYPERVISOR_set_timer_op	15
#define __HYPERVISOR_sched_op		29
#define __HYPERVISOR_event_channel_op	32
@@ -148,6 +154,7 @@ static void guest_wait_for_irq(void)
static void guest_code(void)
{
	struct vcpu_runstate_info *rs = (void *)RUNSTATE_VADDR;
	int i;

	__asm__ __volatile__(
		"sti\n"
@@ -325,6 +332,49 @@ static void guest_code(void)
	guest_wait_for_irq();

	GUEST_SYNC(21);
	/* Racing host ioctls */

	guest_wait_for_irq();

	GUEST_SYNC(22);
	/* Racing vmcall against host ioctl */

	ports[0] = 0;

	p = (struct sched_poll) {
		.ports = ports,
		.nr_ports = 1,
		.timeout = 0
	};

wait_for_timer:
	/*
	 * Poll for a timer wake event while the worker thread is mucking with
	 * the shared info.  KVM XEN drops timer IRQs if the shared info is
	 * invalid when the timer expires.  Arbitrarily poll 100 times before
	 * giving up and asking the VMM to re-arm the timer.  100 polls should
	 * consume enough time to beat on KVM without taking too long if the
	 * timer IRQ is dropped due to an invalid event channel.
	 */
	for (i = 0; i < 100 && !guest_saw_irq; i++)
		asm volatile("vmcall"
			     : "=a" (rax)
			     : "a" (__HYPERVISOR_sched_op),
			       "D" (SCHEDOP_poll),
			       "S" (&p)
			     : "memory");

	/*
	 * Re-send the timer IRQ if it was (likely) dropped due to the timer
	 * expiring while the event channel was invalid.
	 */
	if (!guest_saw_irq) {
		GUEST_SYNC(23);
		goto wait_for_timer;
	}
	guest_saw_irq = false;

	GUEST_SYNC(24);
}

static int cmp_timespec(struct timespec *a, struct timespec *b)
@@ -352,11 +402,36 @@ static void handle_alrm(int sig)
	TEST_FAIL("IRQ delivery timed out");
}

static void *juggle_shinfo_state(void *arg)
{
	struct kvm_vm *vm = (struct kvm_vm *)arg;

	struct kvm_xen_hvm_attr cache_init = {
		.type = KVM_XEN_ATTR_TYPE_SHARED_INFO,
		.u.shared_info.gfn = SHINFO_REGION_GPA / PAGE_SIZE
	};

	struct kvm_xen_hvm_attr cache_destroy = {
		.type = KVM_XEN_ATTR_TYPE_SHARED_INFO,
		.u.shared_info.gfn = GPA_INVALID
	};

	for (;;) {
		__vm_ioctl(vm, KVM_XEN_HVM_SET_ATTR, &cache_init);
		__vm_ioctl(vm, KVM_XEN_HVM_SET_ATTR, &cache_destroy);
		pthread_testcancel();
	};

	return NULL;
}

int main(int argc, char *argv[])
{
	struct timespec min_ts, max_ts, vm_ts;
	struct kvm_vm *vm;
	pthread_t thread;
	bool verbose;
	int ret;

	verbose = argc > 1 && (!strncmp(argv[1], "-v", 3) ||
			       !strncmp(argv[1], "--verbose", 10));
@@ -785,6 +860,71 @@ int main(int argc, char *argv[])
			case 21:
				TEST_ASSERT(!evtchn_irq_expected,
					    "Expected event channel IRQ but it didn't happen");
				alarm(0);

				if (verbose)
					printf("Testing shinfo lock corruption (KVM_XEN_HVM_EVTCHN_SEND)\n");

				ret = pthread_create(&thread, NULL, &juggle_shinfo_state, (void *)vm);
				TEST_ASSERT(ret == 0, "pthread_create() failed: %s", strerror(ret));

				struct kvm_irq_routing_xen_evtchn uxe = {
					.port = 1,
					.vcpu = vcpu->id,
					.priority = KVM_IRQ_ROUTING_XEN_EVTCHN_PRIO_2LEVEL
				};

				evtchn_irq_expected = true;
				for (time_t t = time(NULL) + SHINFO_RACE_TIMEOUT; time(NULL) < t;)
					__vm_ioctl(vm, KVM_XEN_HVM_EVTCHN_SEND, &uxe);
				break;

			case 22:
				TEST_ASSERT(!evtchn_irq_expected,
					    "Expected event channel IRQ but it didn't happen");

				if (verbose)
					printf("Testing shinfo lock corruption (SCHEDOP_poll)\n");

				shinfo->evtchn_pending[0] = 1;

				evtchn_irq_expected = true;
				tmr.u.timer.expires_ns = rs->state_entry_time +
							 SHINFO_RACE_TIMEOUT * 1000000000ULL;
				vcpu_ioctl(vcpu, KVM_XEN_VCPU_SET_ATTR, &tmr);
				break;

			case 23:
				/*
				 * Optional and possibly repeated sync point.
				 * Injecting the timer IRQ may fail if the
				 * shinfo is invalid when the timer expires.
				 * If the timer has expired but the IRQ hasn't
				 * been delivered, rearm the timer and retry.
				 */
				vcpu_ioctl(vcpu, KVM_XEN_VCPU_GET_ATTR, &tmr);

				/* Resume the guest if the timer is still pending. */
				if (tmr.u.timer.expires_ns)
					break;

				/* All done if the IRQ was delivered. */
				if (!evtchn_irq_expected)
					break;

				tmr.u.timer.expires_ns = rs->state_entry_time +
							 SHINFO_RACE_TIMEOUT * 1000000000ULL;
				vcpu_ioctl(vcpu, KVM_XEN_VCPU_SET_ATTR, &tmr);
				break;
			case 24:
				TEST_ASSERT(!evtchn_irq_expected,
					    "Expected event channel IRQ but it didn't happen");

				ret = pthread_cancel(thread);
				TEST_ASSERT(ret == 0, "pthread_cancel() failed: %s", strerror(ret));

				ret = pthread_join(thread, 0);
				TEST_ASSERT(ret == 0, "pthread_join() failed: %s", strerror(ret));
				goto done;

			case 0x20: