lkml.org 
[lkml]   [2022]   [Oct]   [13]   [last100]   RSS Feed
Views: [wrap][no wrap]   [headers]  [forward] 
 
Messages in this thread
    Patch in this message
    /
    Date
    Subject[PATCH v2 15/16] KVM: selftests: Add tests in xen_shinfo_test to detect lock races
    From
    From: Michal Luczaj <mhal@rbox.co>

    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>
    ---
    .../selftests/kvm/x86_64/xen_shinfo_test.c | 140 ++++++++++++++++++
    1 file changed, 140 insertions(+)

    diff --git a/tools/testing/selftests/kvm/x86_64/xen_shinfo_test.c b/tools/testing/selftests/kvm/x86_64/xen_shinfo_test.c
    index 8a5cb800f50e..caa3f5ab9e10 100644
    --- a/tools/testing/selftests/kvm/x86_64/xen_shinfo_test.c
    +++ b/tools/testing/selftests/kvm/x86_64/xen_shinfo_test.c
    @@ -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:
    --
    2.38.0.413.g74048e4d9e-goog
    \
     
     \ /
      Last update: 2022-10-13 23:15    [W:4.547 / U:1.204 seconds]
    ©2003-2020 Jasper Spaans|hosted at Digital Ocean and TransIP|Read the blog|Advertise on this site