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: Michal Luczaj <mhal@rbox.co> Co-developed-by: Sean Christopherson <seanjc@google.com> Signed-off-by: Sean Christopherson <seanjc@google.com> Message-Id: <20221013211234.1318131-16-seanjc@google.com> Signed-off-by: Paolo Bonzini <pbonzini@redhat.com>
This commit is contained in:
parent
ecbcf030b4
commit
a51abbbf25
@ -15,9 +15,13 @@
|
|||||||
#include <time.h>
|
#include <time.h>
|
||||||
#include <sched.h>
|
#include <sched.h>
|
||||||
#include <signal.h>
|
#include <signal.h>
|
||||||
|
#include <pthread.h>
|
||||||
|
|
||||||
#include <sys/eventfd.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_GVA 0xc0000000ULL
|
||||||
#define SHINFO_REGION_GPA 0xc0000000ULL
|
#define SHINFO_REGION_GPA 0xc0000000ULL
|
||||||
#define SHINFO_REGION_SLOT 10
|
#define SHINFO_REGION_SLOT 10
|
||||||
@ -44,6 +48,8 @@
|
|||||||
|
|
||||||
#define MIN_STEAL_TIME 50000
|
#define MIN_STEAL_TIME 50000
|
||||||
|
|
||||||
|
#define SHINFO_RACE_TIMEOUT 2 /* seconds */
|
||||||
|
|
||||||
#define __HYPERVISOR_set_timer_op 15
|
#define __HYPERVISOR_set_timer_op 15
|
||||||
#define __HYPERVISOR_sched_op 29
|
#define __HYPERVISOR_sched_op 29
|
||||||
#define __HYPERVISOR_event_channel_op 32
|
#define __HYPERVISOR_event_channel_op 32
|
||||||
@ -148,6 +154,7 @@ static void guest_wait_for_irq(void)
|
|||||||
static void guest_code(void)
|
static void guest_code(void)
|
||||||
{
|
{
|
||||||
struct vcpu_runstate_info *rs = (void *)RUNSTATE_VADDR;
|
struct vcpu_runstate_info *rs = (void *)RUNSTATE_VADDR;
|
||||||
|
int i;
|
||||||
|
|
||||||
__asm__ __volatile__(
|
__asm__ __volatile__(
|
||||||
"sti\n"
|
"sti\n"
|
||||||
@ -325,6 +332,49 @@ static void guest_code(void)
|
|||||||
guest_wait_for_irq();
|
guest_wait_for_irq();
|
||||||
|
|
||||||
GUEST_SYNC(21);
|
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)
|
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");
|
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[])
|
int main(int argc, char *argv[])
|
||||||
{
|
{
|
||||||
struct timespec min_ts, max_ts, vm_ts;
|
struct timespec min_ts, max_ts, vm_ts;
|
||||||
struct kvm_vm *vm;
|
struct kvm_vm *vm;
|
||||||
|
pthread_t thread;
|
||||||
bool verbose;
|
bool verbose;
|
||||||
|
int ret;
|
||||||
|
|
||||||
verbose = argc > 1 && (!strncmp(argv[1], "-v", 3) ||
|
verbose = argc > 1 && (!strncmp(argv[1], "-v", 3) ||
|
||||||
!strncmp(argv[1], "--verbose", 10));
|
!strncmp(argv[1], "--verbose", 10));
|
||||||
@ -785,6 +860,71 @@ int main(int argc, char *argv[])
|
|||||||
case 21:
|
case 21:
|
||||||
TEST_ASSERT(!evtchn_irq_expected,
|
TEST_ASSERT(!evtchn_irq_expected,
|
||||||
"Expected event channel IRQ but it didn't happen");
|
"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;
|
goto done;
|
||||||
|
|
||||||
case 0x20:
|
case 0x20:
|
||||||
|
Loading…
Reference in New Issue
Block a user